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"
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
,
33 // Encoder callback when encoding is complete.
34 class EncodeCallback
: public EncodeCompleteCallback
{
36 EncodeCallback(nsIGlobalObject
* aGlobal
, BlobCallback
* aCallback
)
37 : mGlobal(aGlobal
), mBlobCallback(aCallback
) {}
39 // This is called on main thread.
41 nsresult
ReceiveBlobImpl(already_AddRefed
<BlobImpl
> aBlobImpl
) override
{
42 MOZ_ASSERT(NS_IsMainThread());
44 RefPtr
<BlobImpl
> blobImpl
= aBlobImpl
;
49 blob
= Blob::Create(mGlobal
, blobImpl
);
52 RefPtr
<BlobCallback
> callback(std::move(mBlobCallback
));
55 callback
->Call(blob
, rv
);
58 MOZ_ASSERT(!mBlobCallback
);
60 return rv
.StealNSResult();
63 bool CanBeDeletedOnAnyThread() override
{
64 // EncodeCallback is used from the main thread only.
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
) {
82 nsContentUtils::ASCIIToLower(aType
, type
);
85 bool usingCustomParseOptions
;
86 aRv
= ParseParams(aCx
, type
, aParams
, params
, &usingCustomParseOptions
);
91 ToBlob(aCallback
, type
, params
, usingCustomParseOptions
, aUsePlaceholder
,
95 void CanvasRenderingContextHelper::ToBlob(EncodeCompleteCallback
* aCallback
,
97 const nsAString
& aEncodeOptions
,
98 bool aUsingCustomOptions
,
101 const nsIntSize 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
);
115 UniquePtr
<uint8_t[]> imageBuffer
;
117 auto imageSize
= gfx::IntSize
{elementSize
.width
, elementSize
.height
};
118 if (mCurrentContext
) {
119 imageBuffer
= mCurrentContext
->GetImageBuffer(&format
, &imageSize
);
122 RefPtr
<EncodeCompleteCallback
> callback
= aCallback
;
124 aRv
= ImageEncoder::ExtractDataAsync(
125 aType
, aEncodeOptions
, aUsingCustomOptions
, std::move(imageBuffer
),
126 format
, {imageSize
.width
, imageSize
.height
}, aUsePlaceholder
, callback
);
129 already_AddRefed
<nsICanvasRenderingContextInternal
>
130 CanvasRenderingContextHelper::CreateContext(CanvasContextType aContextType
) {
131 return CreateContextHelper(aContextType
, layers::LayersBackend::LAYERS_NONE
);
134 already_AddRefed
<nsICanvasRenderingContextInternal
>
135 CanvasRenderingContextHelper::CreateContextHelper(
136 CanvasContextType aContextType
, layers::LayersBackend aCompositorBackend
) {
137 MOZ_ASSERT(aContextType
!= CanvasContextType::NoContext
);
138 RefPtr
<nsICanvasRenderingContextInternal
> ret
;
140 switch (aContextType
) {
141 case CanvasContextType::NoContext
:
144 case CanvasContextType::Canvas2D
:
145 Telemetry::Accumulate(Telemetry::CANVAS_2D_USED
, 1);
146 ret
= new CanvasRenderingContext2D(aCompositorBackend
);
149 case CanvasContextType::OffscreenCanvas2D
:
150 Telemetry::Accumulate(Telemetry::CANVAS_2D_USED
, 1);
151 ret
= new OffscreenCanvasRenderingContext2D(aCompositorBackend
);
154 case CanvasContextType::WebGL1
:
155 Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_USED
, 1);
157 ret
= new ClientWebGLContext(/*webgl2:*/ false);
161 case CanvasContextType::WebGL2
:
162 Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_USED
, 1);
164 ret
= new ClientWebGLContext(/*webgl2:*/ true);
168 case CanvasContextType::WebGPU
:
170 // Telemetry::Accumulate(Telemetry::CANVAS_WEBGPU_USED, 1);
172 ret
= new webgpu::CanvasContext();
176 case CanvasContextType::ImageBitmap
:
177 ret
= new ImageBitmapRenderingContext();
187 already_AddRefed
<nsISupports
> CanvasRenderingContextHelper::GetOrCreateContext(
188 JSContext
* aCx
, const nsAString
& aContextId
,
189 JS::Handle
<JS::Value
> aContextOptions
, ErrorResult
& aRv
) {
190 CanvasContextType contextType
;
191 if (!CanvasUtils::GetCanvasContextType(aContextId
, &contextType
))
194 return GetOrCreateContext(aCx
, contextType
, aContextOptions
, aRv
);
197 already_AddRefed
<nsISupports
> CanvasRenderingContextHelper::GetOrCreateContext(
198 JSContext
* aCx
, CanvasContextType aContextType
,
199 JS::Handle
<JS::Value
> aContextOptions
, ErrorResult
& aRv
) {
200 if (!mCurrentContext
) {
201 // This canvas doesn't have a context yet.
202 RefPtr
<nsICanvasRenderingContextInternal
> context
;
203 context
= CreateContext(aContextType
);
208 // Ensure that the context participates in CC. Note that returning a
209 // CC participant from QI doesn't addref.
210 nsXPCOMCycleCollectionParticipant
* cp
= nullptr;
211 CallQueryInterface(context
, &cp
);
213 aRv
.Throw(NS_ERROR_FAILURE
);
217 mCurrentContext
= std::move(context
);
218 mCurrentContextType
= aContextType
;
220 // https://html.spec.whatwg.org/multipage/canvas.html#dom-canvas-getcontext-dev
221 // Step 1. If options is not an object, then set options to null.
222 JS::Rooted
<JS::Value
> options(RootingCx(), aContextOptions
);
223 if (!options
.isObject()) {
227 nsresult rv
= UpdateContext(aCx
, options
, aRv
);
229 // See bug 645792 and bug 1215072.
230 // We want to throw only if dictionary initialization fails,
231 // so only in case aRv has been set to some error value.
232 if (aContextType
== CanvasContextType::WebGL1
) {
233 Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_SUCCESS
, 0);
234 } else if (aContextType
== CanvasContextType::WebGL2
) {
235 Telemetry::Accumulate(Telemetry::CANVAS_WEBGL2_SUCCESS
, 0);
236 } else if (aContextType
== CanvasContextType::WebGPU
) {
237 // Telemetry::Accumulate(Telemetry::CANVAS_WEBGPU_SUCCESS, 0);
241 if (aContextType
== CanvasContextType::WebGL1
) {
242 Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_SUCCESS
, 1);
243 } else if (aContextType
== CanvasContextType::WebGL2
) {
244 Telemetry::Accumulate(Telemetry::CANVAS_WEBGL2_SUCCESS
, 1);
245 } else if (aContextType
== CanvasContextType::WebGPU
) {
246 // Telemetry::Accumulate(Telemetry::CANVAS_WEBGPU_SUCCESS, 1);
249 // We already have a context of some type.
250 if (aContextType
!= mCurrentContextType
) return nullptr;
253 nsCOMPtr
<nsICanvasRenderingContextInternal
> context
= mCurrentContext
;
254 return context
.forget();
257 nsresult
CanvasRenderingContextHelper::UpdateContext(
258 JSContext
* aCx
, JS::Handle
<JS::Value
> aNewContextOptions
,
259 ErrorResult
& aRvForDictionaryInit
) {
260 if (!mCurrentContext
) return NS_OK
;
262 nsIntSize sz
= GetWidthHeight();
264 nsCOMPtr
<nsICanvasRenderingContextInternal
> currentContext
= mCurrentContext
;
266 currentContext
->SetOpaqueValueFromOpaqueAttr(GetOpaqueAttr());
268 nsresult rv
= currentContext
->SetContextOptions(aCx
, aNewContextOptions
,
269 aRvForDictionaryInit
);
271 mCurrentContext
= nullptr;
275 rv
= currentContext
->SetDimensions(sz
.width
, sz
.height
);
277 mCurrentContext
= nullptr;
283 nsresult
CanvasRenderingContextHelper::ParseParams(
284 JSContext
* aCx
, const nsAString
& aType
, const JS::Value
& aEncoderOptions
,
285 nsAString
& outParams
, bool* const outUsingCustomParseOptions
) {
286 // Quality parameter is only valid for the image/jpeg and image/webp MIME
288 if (aType
.EqualsLiteral("image/jpeg") || aType
.EqualsLiteral("image/webp")) {
289 if (aEncoderOptions
.isNumber()) {
290 double quality
= aEncoderOptions
.toNumber();
291 // Quality must be between 0.0 and 1.0, inclusive
292 if (quality
>= 0.0 && quality
<= 1.0) {
293 outParams
.AppendLiteral("quality=");
294 outParams
.AppendInt(NS_lround(quality
* 100.0));
299 // If we haven't parsed the aParams check for proprietary options.
300 // The proprietary option -moz-parse-options will take a image lib encoder
301 // parse options string as is and pass it to the encoder.
302 *outUsingCustomParseOptions
= false;
303 if (outParams
.Length() == 0 && aEncoderOptions
.isString()) {
304 constexpr auto mozParseOptions
= u
"-moz-parse-options:"_ns
;
305 nsAutoJSString paramString
;
306 if (!paramString
.init(aCx
, aEncoderOptions
.toString())) {
307 return NS_ERROR_FAILURE
;
309 if (StringBeginsWith(paramString
, mozParseOptions
)) {
310 nsDependentSubstring parseOptions
=
311 Substring(paramString
, mozParseOptions
.Length(),
312 paramString
.Length() - mozParseOptions
.Length());
313 outParams
.Append(parseOptions
);
314 *outUsingCustomParseOptions
= true;
321 } // namespace mozilla::dom