CanvasRenderingContextHelper.cpp (10765B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #include "CanvasRenderingContextHelper.h" 7 8 #include "ClientWebGLContext.h" 9 #include "GLContext.h" 10 #include "ImageBitmapRenderingContext.h" 11 #include "ImageEncoder.h" 12 #include "MozFramebuffer.h" 13 #include "mozilla/GfxMessageUtils.h" 14 #include "mozilla/UniquePtr.h" 15 #include "mozilla/dom/CanvasRenderingContext2D.h" 16 #include "mozilla/dom/OffscreenCanvasRenderingContext2D.h" 17 #include "mozilla/glean/DomCanvasMetrics.h" 18 #include "mozilla/webgpu/CanvasContext.h" 19 #include "nsContentUtils.h" 20 #include "nsDOMJSUtils.h" 21 #include "nsIScriptContext.h" 22 #include "nsJSUtils.h" 23 24 namespace mozilla::dom { 25 26 CanvasRenderingContextHelper::CanvasRenderingContextHelper() 27 : mCurrentContextType(CanvasContextType::NoContext) {} 28 29 void CanvasRenderingContextHelper::ToBlob( 30 JSContext* aCx, EncodeCompleteCallback* aCallback, const nsAString& aType, 31 JS::Handle<JS::Value> aParams, 32 CanvasUtils::ImageExtraction aExtractionBehavior, ErrorResult& aRv) { 33 nsAutoString type; 34 nsContentUtils::ASCIIToLower(aType, type); 35 36 nsAutoString params; 37 bool usingCustomParseOptions; 38 aRv = ParseParams(aCx, type, aParams, params, &usingCustomParseOptions); 39 if (aRv.Failed()) { 40 return; 41 } 42 43 ToBlob(aCallback, type, params, usingCustomParseOptions, aExtractionBehavior, 44 aRv); 45 } 46 47 void CanvasRenderingContextHelper::ToBlob( 48 EncodeCompleteCallback* aCallback, nsAString& aType, 49 const nsAString& aEncodeOptions, bool aUsingCustomOptions, 50 CanvasUtils::ImageExtraction aExtractionBehavior, ErrorResult& aRv) { 51 const CSSIntSize elementSize = GetWidthHeight(); 52 if (mCurrentContext) { 53 // We disallow canvases of width or height zero, and set them to 1, so 54 // we will have a discrepancy with the sizes of the canvas and the context. 55 // That discrepancy is OK, the rest are not. 56 if ((elementSize.width != mCurrentContext->GetWidth() && 57 (elementSize.width != 0 || mCurrentContext->GetWidth() != 1)) || 58 (elementSize.height != mCurrentContext->GetHeight() && 59 (elementSize.height != 0 || mCurrentContext->GetHeight() != 1))) { 60 aRv.Throw(NS_ERROR_FAILURE); 61 return; 62 } 63 } 64 65 nsCString randomizationKeyStr = VoidCString(); 66 if (aExtractionBehavior == CanvasUtils::ImageExtraction::EfficientRandomize) { 67 nsRFPService::GetFingerprintingRandomizationKeyAsString( 68 GetCookieJarSettings(), randomizationKeyStr); 69 } 70 71 int32_t format = 0; 72 auto imageSize = gfx::IntSize{elementSize.width, elementSize.height}; 73 UniquePtr<uint8_t[]> imageBuffer = 74 GetImageBuffer(aExtractionBehavior, &format, &imageSize); 75 RefPtr<EncodeCompleteCallback> callback = aCallback; 76 77 aRv = ImageEncoder::ExtractDataAsync( 78 aType, aEncodeOptions, aUsingCustomOptions, std::move(imageBuffer), 79 format, CSSIntSize::FromUnknownSize(imageSize), aExtractionBehavior, 80 randomizationKeyStr, callback); 81 } 82 83 UniquePtr<uint8_t[]> CanvasRenderingContextHelper::GetImageBuffer( 84 CanvasUtils::ImageExtraction aExtractionBehavior, int32_t* aOutFormat, 85 gfx::IntSize* aOutImageSize) { 86 if (mCurrentContext) { 87 return mCurrentContext->GetImageBuffer(aExtractionBehavior, aOutFormat, 88 aOutImageSize); 89 } 90 return nullptr; 91 } 92 93 nsICookieJarSettings* CanvasRenderingContextHelper::GetCookieJarSettings() 94 const { 95 if (mCurrentContext) { 96 return mCurrentContext->GetCookieJarSettings(); 97 } 98 return nullptr; 99 } 100 101 already_AddRefed<nsICanvasRenderingContextInternal> 102 CanvasRenderingContextHelper::CreateContext(CanvasContextType aContextType) { 103 return CreateContextHelper(aContextType, layers::LayersBackend::LAYERS_NONE); 104 } 105 106 already_AddRefed<nsICanvasRenderingContextInternal> 107 CanvasRenderingContextHelper::CreateContextHelper( 108 CanvasContextType aContextType, layers::LayersBackend aCompositorBackend) { 109 MOZ_ASSERT(aContextType != CanvasContextType::NoContext); 110 RefPtr<nsICanvasRenderingContextInternal> ret; 111 112 switch (aContextType) { 113 case CanvasContextType::NoContext: 114 break; 115 116 case CanvasContextType::Canvas2D: 117 glean::canvas::used_2d.EnumGet(glean::canvas::Used2dLabel::eTrue).Add(); 118 ret = new CanvasRenderingContext2D(aCompositorBackend); 119 break; 120 121 case CanvasContextType::OffscreenCanvas2D: 122 glean::canvas::used_2d.EnumGet(glean::canvas::Used2dLabel::eTrue).Add(); 123 ret = new OffscreenCanvasRenderingContext2D(aCompositorBackend); 124 break; 125 126 case CanvasContextType::WebGL1: 127 glean::canvas::webgl_used.EnumGet(glean::canvas::WebglUsedLabel::eTrue) 128 .Add(); 129 130 ret = new ClientWebGLContext(/*webgl2:*/ false); 131 132 break; 133 134 case CanvasContextType::WebGL2: 135 glean::canvas::webgl_used.EnumGet(glean::canvas::WebglUsedLabel::eTrue) 136 .Add(); 137 138 ret = new ClientWebGLContext(/*webgl2:*/ true); 139 140 break; 141 142 case CanvasContextType::WebGPU: 143 // TODO 144 // Telemetry::Accumulate(Telemetry::CANVAS_WEBGPU_USED, 1); 145 146 ret = new webgpu::CanvasContext(); 147 148 break; 149 150 case CanvasContextType::ImageBitmap: 151 ret = new ImageBitmapRenderingContext(); 152 153 break; 154 } 155 MOZ_ASSERT(ret); 156 157 if (NS_WARN_IF(NS_FAILED(ret->Initialize()))) { 158 return nullptr; 159 } 160 return ret.forget(); 161 } 162 163 already_AddRefed<nsISupports> CanvasRenderingContextHelper::GetOrCreateContext( 164 JSContext* aCx, const nsAString& aContextId, 165 JS::Handle<JS::Value> aContextOptions, ErrorResult& aRv) { 166 CanvasContextType contextType; 167 if (!CanvasUtils::GetCanvasContextType(aContextId, &contextType)) 168 return nullptr; 169 170 return GetOrCreateContext(aCx, contextType, aContextOptions, aRv); 171 } 172 173 already_AddRefed<nsISupports> CanvasRenderingContextHelper::GetOrCreateContext( 174 JSContext* aCx, CanvasContextType aContextType, 175 JS::Handle<JS::Value> aContextOptions, ErrorResult& aRv) { 176 if (!mCurrentContext) { 177 // This canvas doesn't have a context yet. 178 RefPtr<nsICanvasRenderingContextInternal> context; 179 context = CreateContext(aContextType); 180 if (!context) { 181 aRv.ThrowUnknownError("Failed to create context"); 182 return nullptr; 183 } 184 185 // Ensure that the context participates in CC. Note that returning a 186 // CC participant from QI doesn't addref. 187 nsXPCOMCycleCollectionParticipant* cp = nullptr; 188 CallQueryInterface(context, &cp); 189 if (!cp) { 190 aRv.Throw(NS_ERROR_FAILURE); 191 return nullptr; 192 } 193 194 mCurrentContext = std::move(context); 195 mCurrentContextType = aContextType; 196 197 // https://html.spec.whatwg.org/multipage/canvas.html#dom-canvas-getcontext-dev 198 // Step 1. If options is not an object, then set options to null. 199 JS::Rooted<JS::Value> options(RootingCx(), aContextOptions); 200 if (!options.isObject()) { 201 options.setNull(); 202 } 203 204 nsresult rv = UpdateContext(aCx, options, aRv); 205 if (NS_FAILED(rv)) { 206 // See bug 645792 and bug 1215072. 207 // We want to throw only if dictionary initialization fails, 208 // so only in case aRv has been set to some error value. 209 if (aContextType == CanvasContextType::WebGL1) { 210 glean::canvas::webgl_success 211 .EnumGet(glean::canvas::WebglSuccessLabel::eFalse) 212 .Add(); 213 } else if (aContextType == CanvasContextType::WebGL2) { 214 glean::canvas::webgl2_success 215 .EnumGet(glean::canvas::Webgl2SuccessLabel::eFalse) 216 .Add(); 217 } else if (aContextType == CanvasContextType::WebGPU) { 218 // Telemetry::Accumulate(Telemetry::CANVAS_WEBGPU_SUCCESS, 0); 219 } 220 return nullptr; 221 } 222 if (aContextType == CanvasContextType::WebGL1) { 223 glean::canvas::webgl_success 224 .EnumGet(glean::canvas::WebglSuccessLabel::eTrue) 225 .Add(); 226 } else if (aContextType == CanvasContextType::WebGL2) { 227 glean::canvas::webgl2_success 228 .EnumGet(glean::canvas::Webgl2SuccessLabel::eTrue) 229 .Add(); 230 } else if (aContextType == CanvasContextType::WebGPU) { 231 // Telemetry::Accumulate(Telemetry::CANVAS_WEBGPU_SUCCESS, 1); 232 } 233 } else { 234 // We already have a context of some type. 235 if (aContextType != mCurrentContextType) return nullptr; 236 } 237 238 nsCOMPtr<nsICanvasRenderingContextInternal> context = mCurrentContext; 239 return context.forget(); 240 } 241 242 nsresult CanvasRenderingContextHelper::UpdateContext( 243 JSContext* aCx, JS::Handle<JS::Value> aNewContextOptions, 244 ErrorResult& aRvForDictionaryInit) { 245 if (!mCurrentContext) return NS_OK; 246 247 CSSIntSize sz = GetWidthHeight(); 248 249 nsCOMPtr<nsICanvasRenderingContextInternal> currentContext = mCurrentContext; 250 251 currentContext->SetOpaqueValueFromOpaqueAttr(GetOpaqueAttr()); 252 253 nsresult rv = currentContext->SetContextOptions(aCx, aNewContextOptions, 254 aRvForDictionaryInit); 255 if (NS_FAILED(rv)) { 256 mCurrentContext = nullptr; 257 return rv; 258 } 259 260 rv = currentContext->SetDimensions(sz.width, sz.height); 261 if (NS_FAILED(rv)) { 262 mCurrentContext = nullptr; 263 } 264 265 return rv; 266 } 267 268 nsresult CanvasRenderingContextHelper::ParseParams( 269 JSContext* aCx, const nsAString& aType, const JS::Value& aEncoderOptions, 270 nsAString& outParams, bool* const outUsingCustomParseOptions) { 271 // Quality parameter is only valid for the image/jpeg and image/webp MIME 272 // types. 273 if (aType.EqualsLiteral("image/jpeg") || aType.EqualsLiteral("image/webp")) { 274 if (aEncoderOptions.isNumber()) { 275 double quality = aEncoderOptions.toNumber(); 276 // Quality must be between 0.0 and 1.0, inclusive 277 if (quality >= 0.0 && quality <= 1.0) { 278 outParams.AppendLiteral("quality="); 279 outParams.AppendInt(NS_lround(quality * 100.0)); 280 } 281 } 282 } 283 284 // If we haven't parsed the aParams check for proprietary options. 285 // The proprietary option -moz-parse-options will take a image lib encoder 286 // parse options string as is and pass it to the encoder. 287 *outUsingCustomParseOptions = false; 288 if (outParams.Length() == 0 && aEncoderOptions.isString()) { 289 constexpr auto mozParseOptions = u"-moz-parse-options:"_ns; 290 nsAutoJSString paramString; 291 if (!paramString.init(aCx, aEncoderOptions.toString())) { 292 return NS_ERROR_FAILURE; 293 } 294 if (StringBeginsWith(paramString, mozParseOptions)) { 295 nsDependentSubstring parseOptions = 296 Substring(paramString, mozParseOptions.Length(), 297 paramString.Length() - mozParseOptions.Length()); 298 outParams.Append(parseOptions); 299 *outUsingCustomParseOptions = true; 300 } 301 } 302 303 return NS_OK; 304 } 305 306 } // namespace mozilla::dom