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 if (mCurrentContext
) {
102 // We disallow canvases of width or height zero, and set them to 1, so
103 // we will have a discrepancy with the sizes of the canvas and the context.
104 // That discrepancy is OK, the rest are not.
105 nsIntSize elementSize
= GetWidthHeight();
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 if (mCurrentContext
) {
118 imageBuffer
= mCurrentContext
->GetImageBuffer(&format
);
121 RefPtr
<EncodeCompleteCallback
> callback
= aCallback
;
123 aRv
= ImageEncoder::ExtractDataAsync(
124 aType
, aEncodeOptions
, aUsingCustomOptions
, std::move(imageBuffer
),
125 format
, GetWidthHeight(), aUsePlaceholder
, callback
);
128 already_AddRefed
<nsICanvasRenderingContextInternal
>
129 CanvasRenderingContextHelper::CreateContext(CanvasContextType aContextType
) {
130 return CreateContextHelper(aContextType
, layers::LayersBackend::LAYERS_NONE
);
133 already_AddRefed
<nsICanvasRenderingContextInternal
>
134 CanvasRenderingContextHelper::CreateContextHelper(
135 CanvasContextType aContextType
, layers::LayersBackend aCompositorBackend
) {
136 MOZ_ASSERT(aContextType
!= CanvasContextType::NoContext
);
137 RefPtr
<nsICanvasRenderingContextInternal
> ret
;
139 switch (aContextType
) {
140 case CanvasContextType::NoContext
:
143 case CanvasContextType::Canvas2D
:
144 Telemetry::Accumulate(Telemetry::CANVAS_2D_USED
, 1);
145 ret
= new CanvasRenderingContext2D(aCompositorBackend
);
148 case CanvasContextType::OffscreenCanvas2D
:
149 Telemetry::Accumulate(Telemetry::CANVAS_2D_USED
, 1);
150 ret
= new OffscreenCanvasRenderingContext2D(aCompositorBackend
);
153 case CanvasContextType::WebGL1
:
154 Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_USED
, 1);
156 ret
= new ClientWebGLContext(/*webgl2:*/ false);
160 case CanvasContextType::WebGL2
:
161 Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_USED
, 1);
163 ret
= new ClientWebGLContext(/*webgl2:*/ true);
167 case CanvasContextType::WebGPU
:
169 // Telemetry::Accumulate(Telemetry::CANVAS_WEBGPU_USED, 1);
171 ret
= new webgpu::CanvasContext();
175 case CanvasContextType::ImageBitmap
:
176 ret
= new ImageBitmapRenderingContext();
186 already_AddRefed
<nsISupports
> CanvasRenderingContextHelper::GetOrCreateContext(
187 JSContext
* aCx
, const nsAString
& aContextId
,
188 JS::Handle
<JS::Value
> aContextOptions
, ErrorResult
& aRv
) {
189 CanvasContextType contextType
;
190 if (!CanvasUtils::GetCanvasContextType(aContextId
, &contextType
))
193 return GetOrCreateContext(aCx
, contextType
, aContextOptions
, aRv
);
196 already_AddRefed
<nsISupports
> CanvasRenderingContextHelper::GetOrCreateContext(
197 JSContext
* aCx
, CanvasContextType aContextType
,
198 JS::Handle
<JS::Value
> aContextOptions
, ErrorResult
& aRv
) {
199 if (!mCurrentContext
) {
200 // This canvas doesn't have a context yet.
201 RefPtr
<nsICanvasRenderingContextInternal
> context
;
202 context
= CreateContext(aContextType
);
207 // Ensure that the context participates in CC. Note that returning a
208 // CC participant from QI doesn't addref.
209 nsXPCOMCycleCollectionParticipant
* cp
= nullptr;
210 CallQueryInterface(context
, &cp
);
212 aRv
.Throw(NS_ERROR_FAILURE
);
216 mCurrentContext
= std::move(context
);
217 mCurrentContextType
= aContextType
;
219 nsresult rv
= UpdateContext(aCx
, aContextOptions
, aRv
);
221 // See bug 645792 and bug 1215072.
222 // We want to throw only if dictionary initialization fails,
223 // so only in case aRv has been set to some error value.
224 if (aContextType
== CanvasContextType::WebGL1
) {
225 Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_SUCCESS
, 0);
226 } else if (aContextType
== CanvasContextType::WebGL2
) {
227 Telemetry::Accumulate(Telemetry::CANVAS_WEBGL2_SUCCESS
, 0);
228 } else if (aContextType
== CanvasContextType::WebGPU
) {
229 // Telemetry::Accumulate(Telemetry::CANVAS_WEBGPU_SUCCESS, 0);
233 if (aContextType
== CanvasContextType::WebGL1
) {
234 Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_SUCCESS
, 1);
235 } else if (aContextType
== CanvasContextType::WebGL2
) {
236 Telemetry::Accumulate(Telemetry::CANVAS_WEBGL2_SUCCESS
, 1);
237 } else if (aContextType
== CanvasContextType::WebGPU
) {
238 // Telemetry::Accumulate(Telemetry::CANVAS_WEBGPU_SUCCESS, 1);
241 // We already have a context of some type.
242 if (aContextType
!= mCurrentContextType
) return nullptr;
245 nsCOMPtr
<nsICanvasRenderingContextInternal
> context
= mCurrentContext
;
246 return context
.forget();
249 nsresult
CanvasRenderingContextHelper::UpdateContext(
250 JSContext
* aCx
, JS::Handle
<JS::Value
> aNewContextOptions
,
251 ErrorResult
& aRvForDictionaryInit
) {
252 if (!mCurrentContext
) return NS_OK
;
254 nsIntSize sz
= GetWidthHeight();
256 nsCOMPtr
<nsICanvasRenderingContextInternal
> currentContext
= mCurrentContext
;
258 currentContext
->SetOpaqueValueFromOpaqueAttr(GetOpaqueAttr());
260 nsresult rv
= currentContext
->SetContextOptions(aCx
, aNewContextOptions
,
261 aRvForDictionaryInit
);
263 mCurrentContext
= nullptr;
267 rv
= currentContext
->SetDimensions(sz
.width
, sz
.height
);
269 mCurrentContext
= nullptr;
275 nsresult
CanvasRenderingContextHelper::ParseParams(
276 JSContext
* aCx
, const nsAString
& aType
, const JS::Value
& aEncoderOptions
,
277 nsAString
& outParams
, bool* const outUsingCustomParseOptions
) {
278 // Quality parameter is only valid for the image/jpeg and image/webp MIME
280 if (aType
.EqualsLiteral("image/jpeg") || aType
.EqualsLiteral("image/webp")) {
281 if (aEncoderOptions
.isNumber()) {
282 double quality
= aEncoderOptions
.toNumber();
283 // Quality must be between 0.0 and 1.0, inclusive
284 if (quality
>= 0.0 && quality
<= 1.0) {
285 outParams
.AppendLiteral("quality=");
286 outParams
.AppendInt(NS_lround(quality
* 100.0));
291 // If we haven't parsed the aParams check for proprietary options.
292 // The proprietary option -moz-parse-options will take a image lib encoder
293 // parse options string as is and pass it to the encoder.
294 *outUsingCustomParseOptions
= false;
295 if (outParams
.Length() == 0 && aEncoderOptions
.isString()) {
296 constexpr auto mozParseOptions
= u
"-moz-parse-options:"_ns
;
297 nsAutoJSString paramString
;
298 if (!paramString
.init(aCx
, aEncoderOptions
.toString())) {
299 return NS_ERROR_FAILURE
;
301 if (StringBeginsWith(paramString
, mozParseOptions
)) {
302 nsDependentSubstring parseOptions
=
303 Substring(paramString
, mozParseOptions
.Length(),
304 paramString
.Length() - mozParseOptions
.Length());
305 outParams
.Append(parseOptions
);
306 *outUsingCustomParseOptions
= true;
313 } // namespace mozilla::dom