1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "OffscreenCanvas.h"
9 #include "mozilla/Atomics.h"
10 #include "mozilla/CheckedInt.h"
11 #include "mozilla/dom/BlobImpl.h"
12 #include "mozilla/dom/OffscreenCanvasBinding.h"
13 #include "mozilla/dom/OffscreenCanvasDisplayHelper.h"
14 #include "mozilla/dom/OffscreenCanvasRenderingContext2D.h"
15 #include "mozilla/dom/Promise.h"
16 #include "mozilla/dom/WorkerPrivate.h"
17 #include "mozilla/dom/WorkerRef.h"
18 #include "mozilla/dom/WorkerScope.h"
19 #include "mozilla/layers/ImageBridgeChild.h"
20 #include "mozilla/Telemetry.h"
21 #include "mozilla/webgpu/CanvasContext.h"
22 #include "CanvasRenderingContext2D.h"
23 #include "CanvasUtils.h"
24 #include "ClientWebGLContext.h"
25 #include "GLContext.h"
26 #include "GLScreenBuffer.h"
27 #include "ImageBitmap.h"
28 #include "ImageBitmapRenderingContext.h"
29 #include "nsContentUtils.h"
30 #include "nsProxyRelease.h"
31 #include "WebGLChild.h"
33 namespace mozilla::dom
{
35 OffscreenCanvasCloneData::OffscreenCanvasCloneData(
36 OffscreenCanvasDisplayHelper
* aDisplay
, uint32_t aWidth
, uint32_t aHeight
,
37 layers::LayersBackend aCompositorBackend
, layers::TextureType aTextureType
,
38 bool aNeutered
, bool aIsWriteOnly
, nsIPrincipal
* aExpandedReader
)
42 mCompositorBackendType(aCompositorBackend
),
43 mTextureType(aTextureType
),
45 mIsWriteOnly(aIsWriteOnly
),
46 mExpandedReader(aExpandedReader
) {}
48 OffscreenCanvasCloneData::~OffscreenCanvasCloneData() {
49 NS_ReleaseOnMainThread("OffscreenCanvasCloneData::mExpandedReader",
50 mExpandedReader
.forget());
53 OffscreenCanvas::OffscreenCanvas(nsIGlobalObject
* aGlobal
, uint32_t aWidth
,
55 : DOMEventTargetHelper(aGlobal
), mWidth(aWidth
), mHeight(aHeight
) {}
57 OffscreenCanvas::OffscreenCanvas(
58 nsIGlobalObject
* aGlobal
, uint32_t aWidth
, uint32_t aHeight
,
59 layers::LayersBackend aCompositorBackend
, layers::TextureType aTextureType
,
60 already_AddRefed
<OffscreenCanvasDisplayHelper
> aDisplay
)
61 : DOMEventTargetHelper(aGlobal
),
64 mCompositorBackendType(aCompositorBackend
),
65 mTextureType(aTextureType
),
68 OffscreenCanvas::~OffscreenCanvas() {
69 NS_ReleaseOnMainThread("OffscreenCanvas::mExpandedReader",
70 mExpandedReader
.forget());
73 JSObject
* OffscreenCanvas::WrapObject(JSContext
* aCx
,
74 JS::Handle
<JSObject
*> aGivenProto
) {
75 return OffscreenCanvas_Binding::Wrap(aCx
, this, aGivenProto
);
79 already_AddRefed
<OffscreenCanvas
> OffscreenCanvas::Constructor(
80 const GlobalObject
& aGlobal
, uint32_t aWidth
, uint32_t aHeight
,
82 // CanvasRenderingContextHelper::GetWidthHeight wants us to return
83 // an nsIntSize, so make sure that that will work.
84 if (!CheckedInt
<int32_t>(aWidth
).isValid()) {
86 nsPrintfCString("OffscreenCanvas width %u is out of range: must be no "
87 "greater than 2147483647.",
91 if (!CheckedInt
<int32_t>(aHeight
).isValid()) {
93 nsPrintfCString("OffscreenCanvas height %u is out of range: must be no "
94 "greater than 2147483647.",
99 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(aGlobal
.GetAsSupports());
100 RefPtr
<OffscreenCanvas
> offscreenCanvas
=
101 new OffscreenCanvas(global
, aWidth
, aHeight
);
102 return offscreenCanvas
.forget();
105 void OffscreenCanvas::SetWidth(uint32_t aWidth
, ErrorResult
& aRv
) {
107 aRv
.ThrowInvalidStateError(
108 "Cannot set width of placeholder canvas transferred to worker.");
112 // CanvasRenderingContextHelper::GetWidthHeight wants us to return
113 // an nsIntSize, so make sure that that will work.
114 if (!CheckedInt
<int32_t>(aWidth
).isValid()) {
116 nsPrintfCString("OffscreenCanvas width %u is out of range: must be no "
117 "greater than 2147483647.",
126 void OffscreenCanvas::SetHeight(uint32_t aHeight
, ErrorResult
& aRv
) {
128 aRv
.ThrowInvalidStateError(
129 "Cannot set height of placeholder canvas transferred to worker.");
133 // CanvasRenderingContextHelper::GetWidthHeight wants us to return
134 // an nsIntSize, so make sure that that will work.
135 if (!CheckedInt
<int32_t>(aHeight
).isValid()) {
137 nsPrintfCString("OffscreenCanvas height %u is out of range: must be no "
138 "greater than 2147483647.",
147 void OffscreenCanvas::GetContext(
148 JSContext
* aCx
, const OffscreenRenderingContextId
& aContextId
,
149 JS::Handle
<JS::Value
> aContextOptions
,
150 Nullable
<OwningOffscreenRenderingContext
>& aResult
, ErrorResult
& aRv
) {
153 aRv
.ThrowInvalidStateError(
154 "Cannot create context for placeholder canvas transferred to worker.");
158 CanvasContextType contextType
;
159 switch (aContextId
) {
160 case OffscreenRenderingContextId::_2d
:
161 contextType
= CanvasContextType::OffscreenCanvas2D
;
163 case OffscreenRenderingContextId::Bitmaprenderer
:
164 contextType
= CanvasContextType::ImageBitmap
;
166 case OffscreenRenderingContextId::Webgl
:
167 contextType
= CanvasContextType::WebGL1
;
169 case OffscreenRenderingContextId::Webgl2
:
170 contextType
= CanvasContextType::WebGL2
;
172 case OffscreenRenderingContextId::Webgpu
:
173 contextType
= CanvasContextType::WebGPU
;
176 MOZ_ASSERT_UNREACHABLE("Unhandled canvas type!");
178 aRv
.Throw(NS_ERROR_NOT_IMPLEMENTED
);
182 RefPtr
<nsISupports
> result
= CanvasRenderingContextHelper::GetOrCreateContext(
183 aCx
, contextType
, aContextOptions
, aRv
);
189 Maybe
<int32_t> childId
;
191 MOZ_ASSERT(mCurrentContext
);
192 switch (mCurrentContextType
) {
193 case CanvasContextType::OffscreenCanvas2D
:
194 aResult
.SetValue().SetAsOffscreenCanvasRenderingContext2D() =
195 *static_cast<OffscreenCanvasRenderingContext2D
*>(
196 mCurrentContext
.get());
198 case CanvasContextType::ImageBitmap
:
199 aResult
.SetValue().SetAsImageBitmapRenderingContext() =
200 *static_cast<ImageBitmapRenderingContext
*>(mCurrentContext
.get());
202 case CanvasContextType::WebGL1
:
203 case CanvasContextType::WebGL2
: {
204 auto* webgl
= static_cast<ClientWebGLContext
*>(mCurrentContext
.get());
205 WebGLChild
* webglChild
= webgl
->GetChild();
207 childId
.emplace(webglChild
->Id());
209 aResult
.SetValue().SetAsWebGLRenderingContext() = *webgl
;
212 case CanvasContextType::WebGPU
:
213 aResult
.SetValue().SetAsGPUCanvasContext() =
214 *static_cast<webgpu::CanvasContext
*>(mCurrentContext
.get());
217 MOZ_ASSERT_UNREACHABLE("Unhandled canvas type!");
223 mDisplay
->UpdateContext(mCurrentContextType
, childId
);
227 already_AddRefed
<nsICanvasRenderingContextInternal
>
228 OffscreenCanvas::CreateContext(CanvasContextType aContextType
) {
229 RefPtr
<nsICanvasRenderingContextInternal
> ret
=
230 CanvasRenderingContextHelper::CreateContext(aContextType
);
232 ret
->SetOffscreenCanvas(this);
236 void OffscreenCanvas::UpdateDisplayData(
237 const OffscreenCanvasDisplayData
& aData
) {
242 mPendingUpdate
= Some(aData
);
243 QueueCommitToCompositor();
246 void OffscreenCanvas::QueueCommitToCompositor() {
247 if (!mDisplay
|| !mCurrentContext
|| mPendingCommit
) {
248 // If we already have a commit pending, or we have no bound display/context,
253 mPendingCommit
= NS_NewCancelableRunnableFunction(
254 "OffscreenCanvas::QueueCommitToCompositor",
255 [self
= RefPtr
{this}] { self
->DequeueCommitToCompositor(); });
256 NS_DispatchToCurrentThread(mPendingCommit
);
259 void OffscreenCanvas::DequeueCommitToCompositor() {
260 MOZ_ASSERT(mPendingCommit
);
261 mPendingCommit
= nullptr;
262 Maybe
<OffscreenCanvasDisplayData
> update
= std::move(mPendingUpdate
);
263 mDisplay
->CommitFrameToCompositor(mCurrentContext
, mTextureType
, update
);
266 void OffscreenCanvas::CommitFrameToCompositor() {
267 if (!mDisplay
|| !mCurrentContext
) {
268 // This offscreen canvas doesn't associate to any HTML canvas element.
269 // So, just bail out.
273 if (mPendingCommit
) {
274 // We got an explicit commit while waiting for an implicit.
275 mPendingCommit
->Cancel();
276 mPendingCommit
= nullptr;
279 Maybe
<OffscreenCanvasDisplayData
> update
= std::move(mPendingUpdate
);
280 mDisplay
->CommitFrameToCompositor(mCurrentContext
, mTextureType
, update
);
283 OffscreenCanvasCloneData
* OffscreenCanvas::ToCloneData() {
284 return new OffscreenCanvasCloneData(mDisplay
, mWidth
, mHeight
,
285 mCompositorBackendType
, mTextureType
,
286 mNeutered
, mIsWriteOnly
, mExpandedReader
);
289 already_AddRefed
<ImageBitmap
> OffscreenCanvas::TransferToImageBitmap(
292 aRv
.ThrowInvalidStateError(
293 "Cannot get bitmap from placeholder canvas transferred to worker.");
297 if (!mCurrentContext
) {
298 aRv
.ThrowInvalidStateError(
299 "Cannot get bitmap from canvas without a context.");
303 RefPtr
<ImageBitmap
> result
=
304 ImageBitmap::CreateFromOffscreenCanvas(GetOwnerGlobal(), *this, aRv
);
309 if (mCurrentContext
) {
310 mCurrentContext
->ResetBitmap();
312 return result
.forget();
315 already_AddRefed
<EncodeCompleteCallback
>
316 OffscreenCanvas::CreateEncodeCompleteCallback(Promise
* aPromise
) {
317 // Encoder callback when encoding is complete.
318 class EncodeCallback
: public EncodeCompleteCallback
{
320 explicit EncodeCallback(Promise
* aPromise
)
321 : mPromise(aPromise
), mCanceled(false) {}
323 void MaybeInitWorkerRef() {
324 WorkerPrivate
* wp
= GetCurrentThreadWorkerPrivate();
326 mWorkerRef
= WeakWorkerRef::Create(
327 wp
, [self
= RefPtr
{this}]() { self
->Cancel(); });
334 nsresult
ReceiveBlobImpl(already_AddRefed
<BlobImpl
> aBlobImpl
) override
{
335 RefPtr
<BlobImpl
> blobImpl
= aBlobImpl
;
336 mWorkerRef
= nullptr;
339 RefPtr
<nsIGlobalObject
> global
= mPromise
->GetGlobalObject();
340 if (NS_WARN_IF(!global
) || NS_WARN_IF(!blobImpl
)) {
341 mPromise
->MaybeReject(NS_ERROR_FAILURE
);
343 RefPtr
<Blob
> blob
= Blob::Create(global
, blobImpl
);
344 if (NS_WARN_IF(!blob
)) {
345 mPromise
->MaybeReject(NS_ERROR_FAILURE
);
347 mPromise
->MaybeResolve(blob
);
357 bool CanBeDeletedOnAnyThread() override
{ return mCanceled
; }
361 mWorkerRef
= nullptr;
365 RefPtr
<Promise
> mPromise
;
366 RefPtr
<WeakWorkerRef
> mWorkerRef
;
367 Atomic
<bool> mCanceled
;
370 RefPtr
<EncodeCallback
> p
= MakeAndAddRef
<EncodeCallback
>(aPromise
);
371 p
->MaybeInitWorkerRef();
375 already_AddRefed
<Promise
> OffscreenCanvas::ConvertToBlob(
376 const ImageEncodeOptions
& aOptions
, ErrorResult
& aRv
) {
377 // do a trust check if this is a write-only canvas
379 aRv
.ThrowSecurityError("Cannot get blob from write-only canvas.");
384 aRv
.ThrowInvalidStateError(
385 "Cannot get blob from placeholder canvas transferred to worker.");
389 if (mWidth
== 0 || mHeight
== 0) {
390 aRv
.ThrowIndexSizeError("Cannot get blob from empty canvas.");
394 nsCOMPtr
<nsIGlobalObject
> global
= GetOwnerGlobal();
396 RefPtr
<Promise
> promise
= Promise::Create(global
, aRv
);
402 nsContentUtils::ASCIIToLower(aOptions
.mType
, type
);
404 nsAutoString encodeOptions
;
406 // Only image/jpeg and image/webp support the quality parameter.
407 if (aOptions
.mQuality
.WasPassed() &&
408 (type
.EqualsLiteral("image/jpeg") || type
.EqualsLiteral("image/webp"))) {
409 encodeOptions
.AppendLiteral("quality=");
410 encodeOptions
.AppendInt(NS_lround(aOptions
.mQuality
.Value() * 100.0));
413 RefPtr
<EncodeCompleteCallback
> callback
=
414 CreateEncodeCompleteCallback(promise
);
415 bool usePlaceholder
=
416 ShouldResistFingerprinting(RFPTarget::CanvasImageExtractionPrompt
);
417 CanvasRenderingContextHelper::ToBlob(callback
, type
, encodeOptions
,
418 /* aUsingCustomOptions */ false,
419 usePlaceholder
, aRv
);
421 promise
->MaybeReject(std::move(aRv
));
424 return promise
.forget();
427 already_AddRefed
<Promise
> OffscreenCanvas::ToBlob(JSContext
* aCx
,
428 const nsAString
& aType
,
429 JS::Handle
<JS::Value
> aParams
,
431 // do a trust check if this is a write-only canvas
433 aRv
.ThrowSecurityError("Cannot get blob from write-only canvas.");
438 aRv
.ThrowInvalidStateError(
439 "Cannot get blob from placeholder canvas transferred to worker.");
443 if (mWidth
== 0 || mHeight
== 0) {
444 aRv
.ThrowIndexSizeError("Cannot get blob from empty canvas.");
448 nsCOMPtr
<nsIGlobalObject
> global
= GetOwnerGlobal();
450 RefPtr
<Promise
> promise
= Promise::Create(global
, aRv
);
455 RefPtr
<EncodeCompleteCallback
> callback
=
456 CreateEncodeCompleteCallback(promise
);
457 bool usePlaceholder
=
458 ShouldResistFingerprinting(RFPTarget::CanvasImageExtractionPrompt
);
459 CanvasRenderingContextHelper::ToBlob(aCx
, callback
, aType
, aParams
,
460 usePlaceholder
, aRv
);
462 return promise
.forget();
465 already_AddRefed
<gfx::SourceSurface
> OffscreenCanvas::GetSurfaceSnapshot(
466 gfxAlphaType
* const aOutAlphaType
) {
467 if (!mCurrentContext
) {
471 return mCurrentContext
->GetSurfaceSnapshot(aOutAlphaType
);
474 void OffscreenCanvas::SetWriteOnly(RefPtr
<nsIPrincipal
>&& aExpandedReader
) {
475 NS_ReleaseOnMainThread("OffscreenCanvas::mExpandedReader",
476 mExpandedReader
.forget());
477 mExpandedReader
= std::move(aExpandedReader
);
481 bool OffscreenCanvas::CallerCanRead(nsIPrincipal
& aPrincipal
) const {
486 // If mExpandedReader is set, this canvas was tainted only by
487 // mExpandedReader's resources. So allow reading if the subject
488 // principal subsumes mExpandedReader.
489 if (mExpandedReader
&& aPrincipal
.Subsumes(mExpandedReader
)) {
493 return nsContentUtils::PrincipalHasPermission(aPrincipal
,
494 nsGkAtoms::all_urlsPermission
);
497 bool OffscreenCanvas::ShouldResistFingerprinting(RFPTarget aTarget
) const {
498 return nsContentUtils::ShouldResistFingerprinting(GetOwnerGlobal(), aTarget
);
502 already_AddRefed
<OffscreenCanvas
> OffscreenCanvas::CreateFromCloneData(
503 nsIGlobalObject
* aGlobal
, OffscreenCanvasCloneData
* aData
) {
505 RefPtr
<OffscreenCanvas
> wc
= new OffscreenCanvas(
506 aGlobal
, aData
->mWidth
, aData
->mHeight
, aData
->mCompositorBackendType
,
507 aData
->mTextureType
, aData
->mDisplay
.forget());
508 if (aData
->mNeutered
) {
511 if (aData
->mIsWriteOnly
) {
512 wc
->SetWriteOnly(std::move(aData
->mExpandedReader
));
518 bool OffscreenCanvas::PrefEnabledOnWorkerThread(JSContext
* aCx
,
520 return NS_IsMainThread() || StaticPrefs::gfx_offscreencanvas_enabled();
523 NS_IMPL_CYCLE_COLLECTION_INHERITED(OffscreenCanvas
, DOMEventTargetHelper
,
526 NS_IMPL_ADDREF_INHERITED(OffscreenCanvas
, DOMEventTargetHelper
)
527 NS_IMPL_RELEASE_INHERITED(OffscreenCanvas
, DOMEventTargetHelper
)
529 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(OffscreenCanvas
)
530 NS_INTERFACE_MAP_ENTRY(nsISupports
)
531 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper
)
533 } // namespace mozilla::dom