Bug 1936278 - Prevent search mode chiclet from being dismissed when clicking in page...
[gecko.git] / dom / canvas / CanvasRenderingContextHelper.cpp
blob992eb829c375f0b6f88506dc5644589bc4341c32
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/. */
6 #include "CanvasRenderingContextHelper.h"
7 #include "GLContext.h"
8 #include "ImageBitmapRenderingContext.h"
9 #include "ImageEncoder.h"
10 #include "mozilla/dom/BlobImpl.h"
11 #include "mozilla/dom/CanvasRenderingContext2D.h"
12 #include "mozilla/dom/OffscreenCanvasRenderingContext2D.h"
13 #include "mozilla/GfxMessageUtils.h"
14 #include "mozilla/Telemetry.h"
15 #include "mozilla/UniquePtr.h"
16 #include "mozilla/webgpu/CanvasContext.h"
17 #include "MozFramebuffer.h"
18 #include "nsContentUtils.h"
19 #include "nsDOMJSUtils.h"
20 #include "nsIScriptContext.h"
21 #include "nsJSUtils.h"
22 #include "ClientWebGLContext.h"
24 namespace mozilla::dom {
26 CanvasRenderingContextHelper::CanvasRenderingContextHelper()
27 : mCurrentContextType(CanvasContextType::NoContext) {}
29 void CanvasRenderingContextHelper::ToBlob(
30 JSContext* aCx, nsIGlobalObject* aGlobal, BlobCallback& aCallback,
31 const nsAString& aType, JS::Handle<JS::Value> aParams, bool aUsePlaceholder,
32 ErrorResult& aRv) {
33 // Encoder callback when encoding is complete.
34 class EncodeCallback : public EncodeCompleteCallback {
35 public:
36 EncodeCallback(nsIGlobalObject* aGlobal, BlobCallback* aCallback)
37 : mGlobal(aGlobal), mBlobCallback(aCallback) {}
39 // This is called on main thread.
40 MOZ_CAN_RUN_SCRIPT
41 nsresult ReceiveBlobImpl(already_AddRefed<BlobImpl> aBlobImpl) override {
42 MOZ_ASSERT(NS_IsMainThread());
44 RefPtr<BlobImpl> blobImpl = aBlobImpl;
46 RefPtr<Blob> blob;
48 if (blobImpl) {
49 blob = Blob::Create(mGlobal, blobImpl);
52 RefPtr<BlobCallback> callback(std::move(mBlobCallback));
53 ErrorResult rv;
55 callback->Call(blob, rv);
57 mGlobal = nullptr;
58 MOZ_ASSERT(!mBlobCallback);
60 return rv.StealNSResult();
63 bool CanBeDeletedOnAnyThread() override {
64 // EncodeCallback is used from the main thread only.
65 return false;
68 nsCOMPtr<nsIGlobalObject> mGlobal;
69 RefPtr<BlobCallback> mBlobCallback;
72 RefPtr<EncodeCompleteCallback> callback =
73 new EncodeCallback(aGlobal, &aCallback);
75 ToBlob(aCx, callback, aType, aParams, aUsePlaceholder, aRv);
78 void CanvasRenderingContextHelper::ToBlob(
79 JSContext* aCx, EncodeCompleteCallback* aCallback, const nsAString& aType,
80 JS::Handle<JS::Value> aParams, bool aUsePlaceholder, ErrorResult& aRv) {
81 nsAutoString type;
82 nsContentUtils::ASCIIToLower(aType, type);
84 nsAutoString params;
85 bool usingCustomParseOptions;
86 aRv = ParseParams(aCx, type, aParams, params, &usingCustomParseOptions);
87 if (aRv.Failed()) {
88 return;
91 ToBlob(aCallback, type, params, usingCustomParseOptions, aUsePlaceholder,
92 aRv);
95 void CanvasRenderingContextHelper::ToBlob(EncodeCompleteCallback* aCallback,
96 nsAString& aType,
97 const nsAString& aEncodeOptions,
98 bool aUsingCustomOptions,
99 bool aUsePlaceholder,
100 ErrorResult& aRv) {
101 const CSSIntSize elementSize = GetWidthHeight();
102 if (mCurrentContext) {
103 // We disallow canvases of width or height zero, and set them to 1, so
104 // we will have a discrepancy with the sizes of the canvas and the context.
105 // That discrepancy is OK, the rest are not.
106 if ((elementSize.width != mCurrentContext->GetWidth() &&
107 (elementSize.width != 0 || mCurrentContext->GetWidth() != 1)) ||
108 (elementSize.height != mCurrentContext->GetHeight() &&
109 (elementSize.height != 0 || mCurrentContext->GetHeight() != 1))) {
110 aRv.Throw(NS_ERROR_FAILURE);
111 return;
115 int32_t format = 0;
116 auto imageSize = gfx::IntSize{elementSize.width, elementSize.height};
117 UniquePtr<uint8_t[]> imageBuffer = GetImageBuffer(&format, &imageSize);
118 RefPtr<EncodeCompleteCallback> callback = aCallback;
120 aRv = ImageEncoder::ExtractDataAsync(
121 aType, aEncodeOptions, aUsingCustomOptions, std::move(imageBuffer),
122 format, CSSIntSize::FromUnknownSize(imageSize), aUsePlaceholder,
123 callback);
126 UniquePtr<uint8_t[]> CanvasRenderingContextHelper::GetImageBuffer(
127 int32_t* aOutFormat, gfx::IntSize* aOutImageSize) {
128 if (mCurrentContext) {
129 return mCurrentContext->GetImageBuffer(aOutFormat, aOutImageSize);
131 return nullptr;
134 already_AddRefed<nsICanvasRenderingContextInternal>
135 CanvasRenderingContextHelper::CreateContext(CanvasContextType aContextType) {
136 return CreateContextHelper(aContextType, layers::LayersBackend::LAYERS_NONE);
139 already_AddRefed<nsICanvasRenderingContextInternal>
140 CanvasRenderingContextHelper::CreateContextHelper(
141 CanvasContextType aContextType, layers::LayersBackend aCompositorBackend) {
142 MOZ_ASSERT(aContextType != CanvasContextType::NoContext);
143 RefPtr<nsICanvasRenderingContextInternal> ret;
145 switch (aContextType) {
146 case CanvasContextType::NoContext:
147 break;
149 case CanvasContextType::Canvas2D:
150 Telemetry::Accumulate(Telemetry::CANVAS_2D_USED, 1);
151 ret = new CanvasRenderingContext2D(aCompositorBackend);
152 break;
154 case CanvasContextType::OffscreenCanvas2D:
155 Telemetry::Accumulate(Telemetry::CANVAS_2D_USED, 1);
156 ret = new OffscreenCanvasRenderingContext2D(aCompositorBackend);
157 break;
159 case CanvasContextType::WebGL1:
160 Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_USED, 1);
162 ret = new ClientWebGLContext(/*webgl2:*/ false);
164 break;
166 case CanvasContextType::WebGL2:
167 Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_USED, 1);
169 ret = new ClientWebGLContext(/*webgl2:*/ true);
171 break;
173 case CanvasContextType::WebGPU:
174 // TODO
175 // Telemetry::Accumulate(Telemetry::CANVAS_WEBGPU_USED, 1);
177 ret = new webgpu::CanvasContext();
179 break;
181 case CanvasContextType::ImageBitmap:
182 ret = new ImageBitmapRenderingContext();
184 break;
186 MOZ_ASSERT(ret);
188 if (NS_WARN_IF(NS_FAILED(ret->Initialize()))) {
189 return nullptr;
191 return ret.forget();
194 already_AddRefed<nsISupports> CanvasRenderingContextHelper::GetOrCreateContext(
195 JSContext* aCx, const nsAString& aContextId,
196 JS::Handle<JS::Value> aContextOptions, ErrorResult& aRv) {
197 CanvasContextType contextType;
198 if (!CanvasUtils::GetCanvasContextType(aContextId, &contextType))
199 return nullptr;
201 return GetOrCreateContext(aCx, contextType, aContextOptions, aRv);
204 already_AddRefed<nsISupports> CanvasRenderingContextHelper::GetOrCreateContext(
205 JSContext* aCx, CanvasContextType aContextType,
206 JS::Handle<JS::Value> aContextOptions, ErrorResult& aRv) {
207 if (!mCurrentContext) {
208 // This canvas doesn't have a context yet.
209 RefPtr<nsICanvasRenderingContextInternal> context;
210 context = CreateContext(aContextType);
211 if (!context) {
212 aRv.ThrowUnknownError("Failed to create context");
213 return nullptr;
216 // Ensure that the context participates in CC. Note that returning a
217 // CC participant from QI doesn't addref.
218 nsXPCOMCycleCollectionParticipant* cp = nullptr;
219 CallQueryInterface(context, &cp);
220 if (!cp) {
221 aRv.Throw(NS_ERROR_FAILURE);
222 return nullptr;
225 mCurrentContext = std::move(context);
226 mCurrentContextType = aContextType;
228 // https://html.spec.whatwg.org/multipage/canvas.html#dom-canvas-getcontext-dev
229 // Step 1. If options is not an object, then set options to null.
230 JS::Rooted<JS::Value> options(RootingCx(), aContextOptions);
231 if (!options.isObject()) {
232 options.setNull();
235 nsresult rv = UpdateContext(aCx, options, aRv);
236 if (NS_FAILED(rv)) {
237 // See bug 645792 and bug 1215072.
238 // We want to throw only if dictionary initialization fails,
239 // so only in case aRv has been set to some error value.
240 if (aContextType == CanvasContextType::WebGL1) {
241 Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_SUCCESS, 0);
242 } else if (aContextType == CanvasContextType::WebGL2) {
243 Telemetry::Accumulate(Telemetry::CANVAS_WEBGL2_SUCCESS, 0);
244 } else if (aContextType == CanvasContextType::WebGPU) {
245 // Telemetry::Accumulate(Telemetry::CANVAS_WEBGPU_SUCCESS, 0);
247 return nullptr;
249 if (aContextType == CanvasContextType::WebGL1) {
250 Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_SUCCESS, 1);
251 } else if (aContextType == CanvasContextType::WebGL2) {
252 Telemetry::Accumulate(Telemetry::CANVAS_WEBGL2_SUCCESS, 1);
253 } else if (aContextType == CanvasContextType::WebGPU) {
254 // Telemetry::Accumulate(Telemetry::CANVAS_WEBGPU_SUCCESS, 1);
256 } else {
257 // We already have a context of some type.
258 if (aContextType != mCurrentContextType) return nullptr;
261 nsCOMPtr<nsICanvasRenderingContextInternal> context = mCurrentContext;
262 return context.forget();
265 nsresult CanvasRenderingContextHelper::UpdateContext(
266 JSContext* aCx, JS::Handle<JS::Value> aNewContextOptions,
267 ErrorResult& aRvForDictionaryInit) {
268 if (!mCurrentContext) return NS_OK;
270 CSSIntSize sz = GetWidthHeight();
272 nsCOMPtr<nsICanvasRenderingContextInternal> currentContext = mCurrentContext;
274 currentContext->SetOpaqueValueFromOpaqueAttr(GetOpaqueAttr());
276 nsresult rv = currentContext->SetContextOptions(aCx, aNewContextOptions,
277 aRvForDictionaryInit);
278 if (NS_FAILED(rv)) {
279 mCurrentContext = nullptr;
280 return rv;
283 rv = currentContext->SetDimensions(sz.width, sz.height);
284 if (NS_FAILED(rv)) {
285 mCurrentContext = nullptr;
288 return rv;
291 nsresult CanvasRenderingContextHelper::ParseParams(
292 JSContext* aCx, const nsAString& aType, const JS::Value& aEncoderOptions,
293 nsAString& outParams, bool* const outUsingCustomParseOptions) {
294 // Quality parameter is only valid for the image/jpeg and image/webp MIME
295 // types.
296 if (aType.EqualsLiteral("image/jpeg") || aType.EqualsLiteral("image/webp")) {
297 if (aEncoderOptions.isNumber()) {
298 double quality = aEncoderOptions.toNumber();
299 // Quality must be between 0.0 and 1.0, inclusive
300 if (quality >= 0.0 && quality <= 1.0) {
301 outParams.AppendLiteral("quality=");
302 outParams.AppendInt(NS_lround(quality * 100.0));
307 // If we haven't parsed the aParams check for proprietary options.
308 // The proprietary option -moz-parse-options will take a image lib encoder
309 // parse options string as is and pass it to the encoder.
310 *outUsingCustomParseOptions = false;
311 if (outParams.Length() == 0 && aEncoderOptions.isString()) {
312 constexpr auto mozParseOptions = u"-moz-parse-options:"_ns;
313 nsAutoJSString paramString;
314 if (!paramString.init(aCx, aEncoderOptions.toString())) {
315 return NS_ERROR_FAILURE;
317 if (StringBeginsWith(paramString, mozParseOptions)) {
318 nsDependentSubstring parseOptions =
319 Substring(paramString, mozParseOptions.Length(),
320 paramString.Length() - mozParseOptions.Length());
321 outParams.Append(parseOptions);
322 *outUsingCustomParseOptions = true;
326 return NS_OK;
329 } // namespace mozilla::dom