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 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
);
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
,
126 UniquePtr
<uint8_t[]> CanvasRenderingContextHelper::GetImageBuffer(
127 int32_t* aOutFormat
, gfx::IntSize
* aOutImageSize
) {
128 if (mCurrentContext
) {
129 return mCurrentContext
->GetImageBuffer(aOutFormat
, aOutImageSize
);
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
:
149 case CanvasContextType::Canvas2D
:
150 Telemetry::Accumulate(Telemetry::CANVAS_2D_USED
, 1);
151 ret
= new CanvasRenderingContext2D(aCompositorBackend
);
154 case CanvasContextType::OffscreenCanvas2D
:
155 Telemetry::Accumulate(Telemetry::CANVAS_2D_USED
, 1);
156 ret
= new OffscreenCanvasRenderingContext2D(aCompositorBackend
);
159 case CanvasContextType::WebGL1
:
160 Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_USED
, 1);
162 ret
= new ClientWebGLContext(/*webgl2:*/ false);
166 case CanvasContextType::WebGL2
:
167 Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_USED
, 1);
169 ret
= new ClientWebGLContext(/*webgl2:*/ true);
173 case CanvasContextType::WebGPU
:
175 // Telemetry::Accumulate(Telemetry::CANVAS_WEBGPU_USED, 1);
177 ret
= new webgpu::CanvasContext();
181 case CanvasContextType::ImageBitmap
:
182 ret
= new ImageBitmapRenderingContext();
188 if (NS_WARN_IF(NS_FAILED(ret
->Initialize()))) {
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
))
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
);
212 aRv
.ThrowUnknownError("Failed to create context");
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
);
221 aRv
.Throw(NS_ERROR_FAILURE
);
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()) {
235 nsresult rv
= UpdateContext(aCx
, options
, aRv
);
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);
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);
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
);
279 mCurrentContext
= nullptr;
283 rv
= currentContext
->SetDimensions(sz
.width
, sz
.height
);
285 mCurrentContext
= nullptr;
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
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;
329 } // namespace mozilla::dom