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() {
70 NS_ReleaseOnMainThread("OffscreenCanvas::mExpandedReader",
71 mExpandedReader
.forget());
74 void OffscreenCanvas::Destroy() {
76 mDisplay
->DestroyCanvas();
80 JSObject
* OffscreenCanvas::WrapObject(JSContext
* aCx
,
81 JS::Handle
<JSObject
*> aGivenProto
) {
82 return OffscreenCanvas_Binding::Wrap(aCx
, this, aGivenProto
);
86 already_AddRefed
<OffscreenCanvas
> OffscreenCanvas::Constructor(
87 const GlobalObject
& aGlobal
, uint32_t aWidth
, uint32_t aHeight
,
89 // CanvasRenderingContextHelper::GetWidthHeight wants us to return
90 // an nsIntSize, so make sure that that will work.
91 if (!CheckedInt
<int32_t>(aWidth
).isValid()) {
93 nsPrintfCString("OffscreenCanvas width %u is out of range: must be no "
94 "greater than 2147483647.",
98 if (!CheckedInt
<int32_t>(aHeight
).isValid()) {
100 nsPrintfCString("OffscreenCanvas height %u is out of range: must be no "
101 "greater than 2147483647.",
106 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(aGlobal
.GetAsSupports());
107 RefPtr
<OffscreenCanvas
> offscreenCanvas
=
108 new OffscreenCanvas(global
, aWidth
, aHeight
);
109 return offscreenCanvas
.forget();
112 void OffscreenCanvas::SetWidth(uint32_t aWidth
, ErrorResult
& aRv
) {
114 aRv
.ThrowInvalidStateError("Cannot set width of detached OffscreenCanvas.");
118 // CanvasRenderingContextHelper::GetWidthHeight wants us to return
119 // an nsIntSize, so make sure that that will work.
120 if (!CheckedInt
<int32_t>(aWidth
).isValid()) {
122 nsPrintfCString("OffscreenCanvas width %u is out of range: must be no "
123 "greater than 2147483647.",
132 void OffscreenCanvas::SetHeight(uint32_t aHeight
, ErrorResult
& aRv
) {
134 aRv
.ThrowInvalidStateError(
135 "Cannot set height of detached OffscreenCanvas.");
139 // CanvasRenderingContextHelper::GetWidthHeight wants us to return
140 // an nsIntSize, so make sure that that will work.
141 if (!CheckedInt
<int32_t>(aHeight
).isValid()) {
143 nsPrintfCString("OffscreenCanvas height %u is out of range: must be no "
144 "greater than 2147483647.",
153 void OffscreenCanvas::SetSize(const nsIntSize
& aSize
, ErrorResult
& aRv
) {
155 aRv
.ThrowInvalidStateError(
156 "Cannot set dimensions of detached OffscreenCanvas.");
160 if (NS_WARN_IF(aSize
.IsEmpty())) {
161 aRv
.ThrowRangeError("OffscreenCanvas size is empty, must be non-empty.");
165 mWidth
= aSize
.width
;
166 mHeight
= aSize
.height
;
170 void OffscreenCanvas::GetContext(
171 JSContext
* aCx
, const OffscreenRenderingContextId
& aContextId
,
172 JS::Handle
<JS::Value
> aContextOptions
,
173 Nullable
<OwningOffscreenRenderingContext
>& aResult
, ErrorResult
& aRv
) {
176 aRv
.ThrowInvalidStateError(
177 "Cannot create context for detached OffscreenCanvas.");
181 CanvasContextType contextType
;
182 switch (aContextId
) {
183 case OffscreenRenderingContextId::_2d
:
184 contextType
= CanvasContextType::OffscreenCanvas2D
;
186 case OffscreenRenderingContextId::Bitmaprenderer
:
187 contextType
= CanvasContextType::ImageBitmap
;
189 case OffscreenRenderingContextId::Webgl
:
190 contextType
= CanvasContextType::WebGL1
;
192 case OffscreenRenderingContextId::Webgl2
:
193 contextType
= CanvasContextType::WebGL2
;
195 case OffscreenRenderingContextId::Webgpu
:
196 contextType
= CanvasContextType::WebGPU
;
199 MOZ_ASSERT_UNREACHABLE("Unhandled canvas type!");
201 aRv
.Throw(NS_ERROR_NOT_IMPLEMENTED
);
205 // If we are on a worker, we need to give our OffscreenCanvasDisplayHelper
206 // object access to a worker ref so we can dispatch properly during painting
207 // if we need to flush our contents to its ImageContainer for display.
208 RefPtr
<ThreadSafeWorkerRef
> workerRef
;
210 if (WorkerPrivate
* workerPrivate
= GetCurrentThreadWorkerPrivate()) {
211 RefPtr
<StrongWorkerRef
> strongRef
= StrongWorkerRef::Create(
212 workerPrivate
, "OffscreenCanvas::GetContext",
213 [display
= mDisplay
]() { display
->DestroyCanvas(); });
214 if (NS_WARN_IF(!strongRef
)) {
216 aRv
.ThrowUnknownError("Worker shutting down");
220 workerRef
= new ThreadSafeWorkerRef(strongRef
);
222 MOZ_ASSERT(NS_IsMainThread());
226 RefPtr
<nsISupports
> result
= CanvasRenderingContextHelper::GetOrCreateContext(
227 aCx
, contextType
, aContextOptions
, aRv
);
233 Maybe
<int32_t> childId
;
235 MOZ_ASSERT(mCurrentContext
);
236 switch (mCurrentContextType
) {
237 case CanvasContextType::OffscreenCanvas2D
:
238 aResult
.SetValue().SetAsOffscreenCanvasRenderingContext2D() =
239 *static_cast<OffscreenCanvasRenderingContext2D
*>(
240 mCurrentContext
.get());
242 case CanvasContextType::ImageBitmap
:
243 aResult
.SetValue().SetAsImageBitmapRenderingContext() =
244 *static_cast<ImageBitmapRenderingContext
*>(mCurrentContext
.get());
246 case CanvasContextType::WebGL1
:
247 case CanvasContextType::WebGL2
: {
248 auto* webgl
= static_cast<ClientWebGLContext
*>(mCurrentContext
.get());
249 WebGLChild
* webglChild
= webgl
->GetChild();
251 childId
.emplace(webglChild
->Id());
253 aResult
.SetValue().SetAsWebGLRenderingContext() = *webgl
;
256 case CanvasContextType::WebGPU
:
257 aResult
.SetValue().SetAsGPUCanvasContext() =
258 *static_cast<webgpu::CanvasContext
*>(mCurrentContext
.get());
261 MOZ_ASSERT_UNREACHABLE("Unhandled canvas type!");
267 mDisplay
->UpdateContext(this, std::move(workerRef
), mCurrentContextType
,
272 already_AddRefed
<nsICanvasRenderingContextInternal
>
273 OffscreenCanvas::CreateContext(CanvasContextType aContextType
) {
274 RefPtr
<nsICanvasRenderingContextInternal
> ret
=
275 CanvasRenderingContextHelper::CreateContext(aContextType
);
276 if (NS_WARN_IF(!ret
)) {
280 ret
->SetOffscreenCanvas(this);
284 Maybe
<uint64_t> OffscreenCanvas::GetWindowID() {
285 if (NS_IsMainThread()) {
286 if (nsIGlobalObject
* global
= GetOwnerGlobal()) {
287 if (auto* window
= global
->GetAsInnerWindow()) {
288 return Some(window
->WindowID());
291 } else if (auto* workerPrivate
= GetCurrentThreadWorkerPrivate()) {
292 return Some(workerPrivate
->WindowID());
297 void OffscreenCanvas::UpdateDisplayData(
298 const OffscreenCanvasDisplayData
& aData
) {
303 mPendingUpdate
= Some(aData
);
304 QueueCommitToCompositor();
307 void OffscreenCanvas::QueueCommitToCompositor() {
308 if (!mDisplay
|| !mCurrentContext
|| mPendingCommit
) {
309 // If we already have a commit pending, or we have no bound display/context,
314 mPendingCommit
= NS_NewCancelableRunnableFunction(
315 "OffscreenCanvas::QueueCommitToCompositor",
316 [self
= RefPtr
{this}] { self
->DequeueCommitToCompositor(); });
317 NS_DispatchToCurrentThread(mPendingCommit
);
320 void OffscreenCanvas::DequeueCommitToCompositor() {
321 MOZ_ASSERT(mPendingCommit
);
322 mPendingCommit
= nullptr;
323 Maybe
<OffscreenCanvasDisplayData
> update
= std::move(mPendingUpdate
);
324 mDisplay
->CommitFrameToCompositor(mCurrentContext
, mTextureType
, update
);
327 void OffscreenCanvas::CommitFrameToCompositor() {
328 if (!mDisplay
|| !mCurrentContext
) {
329 // This offscreen canvas doesn't associate to any HTML canvas element.
330 // So, just bail out.
334 if (mPendingCommit
) {
335 // We got an explicit commit while waiting for an implicit.
336 mPendingCommit
->Cancel();
337 mPendingCommit
= nullptr;
340 Maybe
<OffscreenCanvasDisplayData
> update
= std::move(mPendingUpdate
);
341 mDisplay
->CommitFrameToCompositor(mCurrentContext
, mTextureType
, update
);
344 UniquePtr
<OffscreenCanvasCloneData
> OffscreenCanvas::ToCloneData(
346 if (NS_WARN_IF(mNeutered
)) {
348 rv
.ThrowDataCloneError(
349 "Cannot clone OffscreenCanvas that is already transferred.");
350 MOZ_ALWAYS_TRUE(rv
.MaybeSetPendingException(aCx
));
354 if (NS_WARN_IF(mCurrentContext
)) {
356 rv
.ThrowInvalidStateError("Cannot clone canvas with context.");
357 MOZ_ALWAYS_TRUE(rv
.MaybeSetPendingException(aCx
));
361 auto cloneData
= MakeUnique
<OffscreenCanvasCloneData
>(
362 mDisplay
, mWidth
, mHeight
, mCompositorBackendType
, mTextureType
,
363 mNeutered
, mIsWriteOnly
, mExpandedReader
);
368 already_AddRefed
<ImageBitmap
> OffscreenCanvas::TransferToImageBitmap(
371 aRv
.ThrowInvalidStateError(
372 "Cannot get bitmap from detached OffscreenCanvas.");
376 if (!mCurrentContext
) {
377 aRv
.ThrowInvalidStateError(
378 "Cannot get bitmap from canvas without a context.");
382 RefPtr
<ImageBitmap
> result
=
383 ImageBitmap::CreateFromOffscreenCanvas(GetOwnerGlobal(), *this, aRv
);
388 if (mCurrentContext
) {
389 mCurrentContext
->ResetBitmap();
391 return result
.forget();
394 already_AddRefed
<EncodeCompleteCallback
>
395 OffscreenCanvas::CreateEncodeCompleteCallback(Promise
* aPromise
) {
396 // Encoder callback when encoding is complete.
397 class EncodeCallback
: public EncodeCompleteCallback
{
399 explicit EncodeCallback(Promise
* aPromise
)
400 : mPromise(aPromise
), mCanceled(false) {}
402 void MaybeInitWorkerRef() {
403 WorkerPrivate
* wp
= GetCurrentThreadWorkerPrivate();
405 mWorkerRef
= WeakWorkerRef::Create(
406 wp
, [self
= RefPtr
{this}]() { self
->Cancel(); });
413 nsresult
ReceiveBlobImpl(already_AddRefed
<BlobImpl
> aBlobImpl
) override
{
414 RefPtr
<BlobImpl
> blobImpl
= aBlobImpl
;
415 mWorkerRef
= nullptr;
418 RefPtr
<nsIGlobalObject
> global
= mPromise
->GetGlobalObject();
419 if (NS_WARN_IF(!global
) || NS_WARN_IF(!blobImpl
)) {
420 mPromise
->MaybeReject(NS_ERROR_FAILURE
);
422 RefPtr
<Blob
> blob
= Blob::Create(global
, blobImpl
);
423 if (NS_WARN_IF(!blob
)) {
424 mPromise
->MaybeReject(NS_ERROR_FAILURE
);
426 mPromise
->MaybeResolve(blob
);
436 bool CanBeDeletedOnAnyThread() override
{ return mCanceled
; }
440 mWorkerRef
= nullptr;
444 RefPtr
<Promise
> mPromise
;
445 RefPtr
<WeakWorkerRef
> mWorkerRef
;
446 Atomic
<bool> mCanceled
;
449 RefPtr
<EncodeCallback
> p
= MakeAndAddRef
<EncodeCallback
>(aPromise
);
450 p
->MaybeInitWorkerRef();
454 already_AddRefed
<Promise
> OffscreenCanvas::ConvertToBlob(
455 const ImageEncodeOptions
& aOptions
, ErrorResult
& aRv
) {
456 // do a trust check if this is a write-only canvas
458 aRv
.ThrowSecurityError("Cannot get blob from write-only canvas.");
463 aRv
.ThrowInvalidStateError(
464 "Cannot get blob from detached OffscreenCanvas.");
468 if (mWidth
== 0 || mHeight
== 0) {
469 aRv
.ThrowIndexSizeError("Cannot get blob from empty canvas.");
473 nsCOMPtr
<nsIGlobalObject
> global
= GetOwnerGlobal();
475 RefPtr
<Promise
> promise
= Promise::Create(global
, aRv
);
481 nsContentUtils::ASCIIToLower(aOptions
.mType
, type
);
483 nsAutoString encodeOptions
;
485 // Only image/jpeg and image/webp support the quality parameter.
486 if (aOptions
.mQuality
.WasPassed() &&
487 (type
.EqualsLiteral("image/jpeg") || type
.EqualsLiteral("image/webp"))) {
488 encodeOptions
.AppendLiteral("quality=");
489 encodeOptions
.AppendInt(NS_lround(aOptions
.mQuality
.Value() * 100.0));
492 RefPtr
<EncodeCompleteCallback
> callback
=
493 CreateEncodeCompleteCallback(promise
);
494 bool usePlaceholder
=
495 ShouldResistFingerprinting(RFPTarget::CanvasImageExtractionPrompt
);
496 CanvasRenderingContextHelper::ToBlob(callback
, type
, encodeOptions
,
497 /* aUsingCustomOptions */ false,
498 usePlaceholder
, aRv
);
500 promise
->MaybeReject(std::move(aRv
));
503 return promise
.forget();
506 already_AddRefed
<Promise
> OffscreenCanvas::ToBlob(JSContext
* aCx
,
507 const nsAString
& aType
,
508 JS::Handle
<JS::Value
> aParams
,
510 // do a trust check if this is a write-only canvas
512 aRv
.ThrowSecurityError("Cannot get blob from write-only canvas.");
517 aRv
.ThrowInvalidStateError(
518 "Cannot get blob from detached OffscreenCanvas.");
522 if (mWidth
== 0 || mHeight
== 0) {
523 aRv
.ThrowIndexSizeError("Cannot get blob from empty canvas.");
527 nsCOMPtr
<nsIGlobalObject
> global
= GetOwnerGlobal();
529 RefPtr
<Promise
> promise
= Promise::Create(global
, aRv
);
534 RefPtr
<EncodeCompleteCallback
> callback
=
535 CreateEncodeCompleteCallback(promise
);
536 bool usePlaceholder
=
537 ShouldResistFingerprinting(RFPTarget::CanvasImageExtractionPrompt
);
538 CanvasRenderingContextHelper::ToBlob(aCx
, callback
, aType
, aParams
,
539 usePlaceholder
, aRv
);
541 return promise
.forget();
544 already_AddRefed
<gfx::SourceSurface
> OffscreenCanvas::GetSurfaceSnapshot(
545 gfxAlphaType
* const aOutAlphaType
) {
546 if (!mCurrentContext
) {
550 return mCurrentContext
->GetSurfaceSnapshot(aOutAlphaType
);
553 void OffscreenCanvas::SetWriteOnly(RefPtr
<nsIPrincipal
>&& aExpandedReader
) {
554 NS_ReleaseOnMainThread("OffscreenCanvas::mExpandedReader",
555 mExpandedReader
.forget());
556 mExpandedReader
= std::move(aExpandedReader
);
560 bool OffscreenCanvas::CallerCanRead(nsIPrincipal
& aPrincipal
) const {
565 // If mExpandedReader is set, this canvas was tainted only by
566 // mExpandedReader's resources. So allow reading if the subject
567 // principal subsumes mExpandedReader.
568 if (mExpandedReader
&& aPrincipal
.Subsumes(mExpandedReader
)) {
572 return nsContentUtils::PrincipalHasPermission(aPrincipal
,
573 nsGkAtoms::all_urlsPermission
);
576 bool OffscreenCanvas::ShouldResistFingerprinting(RFPTarget aTarget
) const {
577 return nsContentUtils::ShouldResistFingerprinting(GetOwnerGlobal(), aTarget
);
581 already_AddRefed
<OffscreenCanvas
> OffscreenCanvas::CreateFromCloneData(
582 nsIGlobalObject
* aGlobal
, OffscreenCanvasCloneData
* aData
) {
584 RefPtr
<OffscreenCanvas
> wc
= new OffscreenCanvas(
585 aGlobal
, aData
->mWidth
, aData
->mHeight
, aData
->mCompositorBackendType
,
586 aData
->mTextureType
, aData
->mDisplay
.forget());
587 if (aData
->mNeutered
) {
590 if (aData
->mIsWriteOnly
) {
591 wc
->SetWriteOnly(std::move(aData
->mExpandedReader
));
597 bool OffscreenCanvas::PrefEnabledOnWorkerThread(JSContext
* aCx
,
599 return NS_IsMainThread() || StaticPrefs::gfx_offscreencanvas_enabled();
602 NS_IMPL_CYCLE_COLLECTION_INHERITED(OffscreenCanvas
, DOMEventTargetHelper
,
605 NS_IMPL_ADDREF_INHERITED(OffscreenCanvas
, DOMEventTargetHelper
)
606 NS_IMPL_RELEASE_INHERITED(OffscreenCanvas
, DOMEventTargetHelper
)
608 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(OffscreenCanvas
)
609 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, EventTarget
)
610 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper
)
612 } // namespace mozilla::dom