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
, bool aNeutered
, bool aIsWriteOnly
,
38 nsIPrincipal
* aExpandedReader
)
42 mCompositorBackendType(aCompositorBackend
),
44 mIsWriteOnly(aIsWriteOnly
),
45 mExpandedReader(aExpandedReader
) {}
47 OffscreenCanvasCloneData::~OffscreenCanvasCloneData() {
48 NS_ReleaseOnMainThread("OffscreenCanvasCloneData::mExpandedReader",
49 mExpandedReader
.forget());
52 OffscreenCanvas::OffscreenCanvas(nsIGlobalObject
* aGlobal
, uint32_t aWidth
,
54 : DOMEventTargetHelper(aGlobal
), mWidth(aWidth
), mHeight(aHeight
) {}
56 OffscreenCanvas::OffscreenCanvas(
57 nsIGlobalObject
* aGlobal
, uint32_t aWidth
, uint32_t aHeight
,
58 layers::LayersBackend aCompositorBackend
,
59 already_AddRefed
<OffscreenCanvasDisplayHelper
> aDisplay
)
60 : DOMEventTargetHelper(aGlobal
),
63 mCompositorBackendType(aCompositorBackend
),
66 OffscreenCanvas::~OffscreenCanvas() {
68 NS_ReleaseOnMainThread("OffscreenCanvas::mExpandedReader",
69 mExpandedReader
.forget());
72 void OffscreenCanvas::Destroy() {
74 mDisplay
->DestroyCanvas();
78 JSObject
* OffscreenCanvas::WrapObject(JSContext
* aCx
,
79 JS::Handle
<JSObject
*> aGivenProto
) {
80 return OffscreenCanvas_Binding::Wrap(aCx
, this, aGivenProto
);
84 already_AddRefed
<OffscreenCanvas
> OffscreenCanvas::Constructor(
85 const GlobalObject
& aGlobal
, uint32_t aWidth
, uint32_t aHeight
,
87 // CanvasRenderingContextHelper::GetWidthHeight wants us to return
88 // an nsIntSize, so make sure that that will work.
89 if (!CheckedInt
<int32_t>(aWidth
).isValid()) {
91 nsPrintfCString("OffscreenCanvas width %u is out of range: must be no "
92 "greater than 2147483647.",
96 if (!CheckedInt
<int32_t>(aHeight
).isValid()) {
98 nsPrintfCString("OffscreenCanvas height %u is out of range: must be no "
99 "greater than 2147483647.",
104 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(aGlobal
.GetAsSupports());
105 RefPtr
<OffscreenCanvas
> offscreenCanvas
=
106 new OffscreenCanvas(global
, aWidth
, aHeight
);
107 return offscreenCanvas
.forget();
110 void OffscreenCanvas::SetWidth(uint32_t aWidth
, ErrorResult
& aRv
) {
112 aRv
.ThrowInvalidStateError("Cannot set width of detached OffscreenCanvas.");
116 // CanvasRenderingContextHelper::GetWidthHeight wants us to return
117 // an nsIntSize, so make sure that that will work.
118 if (!CheckedInt
<int32_t>(aWidth
).isValid()) {
120 nsPrintfCString("OffscreenCanvas width %u is out of range: must be no "
121 "greater than 2147483647.",
130 void OffscreenCanvas::SetHeight(uint32_t aHeight
, ErrorResult
& aRv
) {
132 aRv
.ThrowInvalidStateError(
133 "Cannot set height of detached OffscreenCanvas.");
137 // CanvasRenderingContextHelper::GetWidthHeight wants us to return
138 // an nsIntSize, so make sure that that will work.
139 if (!CheckedInt
<int32_t>(aHeight
).isValid()) {
141 nsPrintfCString("OffscreenCanvas height %u is out of range: must be no "
142 "greater than 2147483647.",
151 void OffscreenCanvas::SetSize(const nsIntSize
& aSize
, ErrorResult
& aRv
) {
153 aRv
.ThrowInvalidStateError(
154 "Cannot set dimensions of detached OffscreenCanvas.");
158 if (NS_WARN_IF(aSize
.IsEmpty())) {
159 aRv
.ThrowRangeError("OffscreenCanvas size is empty, must be non-empty.");
163 mWidth
= aSize
.width
;
164 mHeight
= aSize
.height
;
168 void OffscreenCanvas::GetContext(
169 JSContext
* aCx
, const OffscreenRenderingContextId
& aContextId
,
170 JS::Handle
<JS::Value
> aContextOptions
,
171 Nullable
<OwningOffscreenRenderingContext
>& aResult
, ErrorResult
& aRv
) {
174 aRv
.ThrowInvalidStateError(
175 "Cannot create context for detached OffscreenCanvas.");
179 CanvasContextType contextType
;
180 switch (aContextId
) {
181 case OffscreenRenderingContextId::_2d
:
182 contextType
= CanvasContextType::OffscreenCanvas2D
;
184 case OffscreenRenderingContextId::Bitmaprenderer
:
185 contextType
= CanvasContextType::ImageBitmap
;
187 case OffscreenRenderingContextId::Webgl
:
188 contextType
= CanvasContextType::WebGL1
;
190 case OffscreenRenderingContextId::Webgl2
:
191 contextType
= CanvasContextType::WebGL2
;
193 case OffscreenRenderingContextId::Webgpu
:
194 contextType
= CanvasContextType::WebGPU
;
197 MOZ_ASSERT_UNREACHABLE("Unhandled canvas type!");
199 aRv
.Throw(NS_ERROR_NOT_IMPLEMENTED
);
203 // If we are on a worker, we need to give our OffscreenCanvasDisplayHelper
204 // object access to a worker ref so we can dispatch properly during painting
205 // if we need to flush our contents to its ImageContainer for display.
206 RefPtr
<ThreadSafeWorkerRef
> workerRef
;
208 if (WorkerPrivate
* workerPrivate
= GetCurrentThreadWorkerPrivate()) {
209 RefPtr
<StrongWorkerRef
> strongRef
= StrongWorkerRef::Create(
210 workerPrivate
, "OffscreenCanvas::GetContext",
211 [display
= mDisplay
]() { display
->DestroyCanvas(); });
212 if (NS_WARN_IF(!strongRef
)) {
214 aRv
.ThrowUnknownError("Worker shutting down");
218 workerRef
= new ThreadSafeWorkerRef(strongRef
);
220 MOZ_ASSERT(NS_IsMainThread());
224 RefPtr
<nsISupports
> result
= CanvasRenderingContextHelper::GetOrCreateContext(
225 aCx
, contextType
, aContextOptions
, aRv
);
231 Maybe
<int32_t> childId
;
233 MOZ_ASSERT(mCurrentContext
);
234 switch (mCurrentContextType
) {
235 case CanvasContextType::OffscreenCanvas2D
:
236 aResult
.SetValue().SetAsOffscreenCanvasRenderingContext2D() =
237 *static_cast<OffscreenCanvasRenderingContext2D
*>(
238 mCurrentContext
.get());
240 case CanvasContextType::ImageBitmap
:
241 aResult
.SetValue().SetAsImageBitmapRenderingContext() =
242 *static_cast<ImageBitmapRenderingContext
*>(mCurrentContext
.get());
244 case CanvasContextType::WebGL1
:
245 case CanvasContextType::WebGL2
: {
246 auto* webgl
= static_cast<ClientWebGLContext
*>(mCurrentContext
.get());
247 WebGLChild
* webglChild
= webgl
->GetChild();
249 childId
.emplace(webglChild
->Id());
251 aResult
.SetValue().SetAsWebGLRenderingContext() = *webgl
;
254 case CanvasContextType::WebGPU
:
255 aResult
.SetValue().SetAsGPUCanvasContext() =
256 *static_cast<webgpu::CanvasContext
*>(mCurrentContext
.get());
259 MOZ_ASSERT_UNREACHABLE("Unhandled canvas type!");
265 mDisplay
->UpdateContext(this, std::move(workerRef
), mCurrentContextType
,
270 already_AddRefed
<nsICanvasRenderingContextInternal
>
271 OffscreenCanvas::CreateContext(CanvasContextType aContextType
) {
272 RefPtr
<nsICanvasRenderingContextInternal
> ret
=
273 CanvasRenderingContextHelper::CreateContext(aContextType
);
274 if (NS_WARN_IF(!ret
)) {
278 ret
->SetOffscreenCanvas(this);
282 Maybe
<uint64_t> OffscreenCanvas::GetWindowID() {
283 if (NS_IsMainThread()) {
284 if (nsIGlobalObject
* global
= GetOwnerGlobal()) {
285 if (auto* window
= global
->GetAsInnerWindow()) {
286 return Some(window
->WindowID());
289 } else if (auto* workerPrivate
= GetCurrentThreadWorkerPrivate()) {
290 return Some(workerPrivate
->WindowID());
295 void OffscreenCanvas::UpdateDisplayData(
296 const OffscreenCanvasDisplayData
& aData
) {
301 mPendingUpdate
= Some(aData
);
302 QueueCommitToCompositor();
305 void OffscreenCanvas::QueueCommitToCompositor() {
306 if (!mDisplay
|| !mCurrentContext
|| mPendingCommit
) {
307 // If we already have a commit pending, or we have no bound display/context,
312 mPendingCommit
= NS_NewCancelableRunnableFunction(
313 "OffscreenCanvas::QueueCommitToCompositor",
314 [self
= RefPtr
{this}] { self
->DequeueCommitToCompositor(); });
315 NS_DispatchToCurrentThread(mPendingCommit
);
318 void OffscreenCanvas::DequeueCommitToCompositor() {
319 MOZ_ASSERT(mPendingCommit
);
320 mPendingCommit
= nullptr;
321 Maybe
<OffscreenCanvasDisplayData
> update
= std::move(mPendingUpdate
);
322 mDisplay
->CommitFrameToCompositor(mCurrentContext
, update
);
325 void OffscreenCanvas::CommitFrameToCompositor() {
326 if (!mDisplay
|| !mCurrentContext
) {
327 // This offscreen canvas doesn't associate to any HTML canvas element.
328 // So, just bail out.
332 if (mPendingCommit
) {
333 // We got an explicit commit while waiting for an implicit.
334 mPendingCommit
->Cancel();
335 mPendingCommit
= nullptr;
338 Maybe
<OffscreenCanvasDisplayData
> update
= std::move(mPendingUpdate
);
339 mDisplay
->CommitFrameToCompositor(mCurrentContext
, update
);
342 UniquePtr
<OffscreenCanvasCloneData
> OffscreenCanvas::ToCloneData(
344 if (NS_WARN_IF(mNeutered
)) {
346 rv
.ThrowDataCloneError(
347 "Cannot clone OffscreenCanvas that is already transferred.");
348 MOZ_ALWAYS_TRUE(rv
.MaybeSetPendingException(aCx
));
352 if (NS_WARN_IF(mCurrentContext
)) {
354 rv
.ThrowInvalidStateError("Cannot clone canvas with context.");
355 MOZ_ALWAYS_TRUE(rv
.MaybeSetPendingException(aCx
));
359 // Check if we are using HTMLCanvasElement::captureStream. This is not
360 // defined by the spec yet, so it is better to fail now than implement
361 // something not compliant:
362 // https://github.com/w3c/mediacapture-fromelement/issues/65
363 // https://github.com/w3c/mediacapture-extensions/pull/26
364 // https://github.com/web-platform-tests/wpt/issues/21102
365 if (mDisplay
&& NS_WARN_IF(mDisplay
->UsingElementCaptureStream())) {
367 rv
.ThrowNotSupportedError(
368 "Cannot transfer OffscreenCanvas bound to element using "
370 MOZ_ALWAYS_TRUE(rv
.MaybeSetPendingException(aCx
));
374 auto cloneData
= MakeUnique
<OffscreenCanvasCloneData
>(
375 mDisplay
, mWidth
, mHeight
, mCompositorBackendType
, mNeutered
,
376 mIsWriteOnly
, mExpandedReader
);
381 already_AddRefed
<ImageBitmap
> OffscreenCanvas::TransferToImageBitmap(
384 aRv
.ThrowInvalidStateError(
385 "Cannot get bitmap from detached OffscreenCanvas.");
389 if (!mCurrentContext
) {
390 aRv
.ThrowInvalidStateError(
391 "Cannot get bitmap from canvas without a context.");
395 RefPtr
<ImageBitmap
> result
=
396 ImageBitmap::CreateFromOffscreenCanvas(GetOwnerGlobal(), *this, aRv
);
401 if (mCurrentContext
) {
402 mCurrentContext
->ResetBitmap();
404 return result
.forget();
407 already_AddRefed
<EncodeCompleteCallback
>
408 OffscreenCanvas::CreateEncodeCompleteCallback(Promise
* aPromise
) {
409 // Encoder callback when encoding is complete.
410 class EncodeCallback
: public EncodeCompleteCallback
{
412 explicit EncodeCallback(Promise
* aPromise
)
413 : mPromise(aPromise
), mCanceled(false) {}
415 void MaybeInitWorkerRef() {
416 WorkerPrivate
* wp
= GetCurrentThreadWorkerPrivate();
418 mWorkerRef
= WeakWorkerRef::Create(
419 wp
, [self
= RefPtr
{this}]() { self
->Cancel(); });
426 nsresult
ReceiveBlobImpl(already_AddRefed
<BlobImpl
> aBlobImpl
) override
{
427 RefPtr
<BlobImpl
> blobImpl
= aBlobImpl
;
428 mWorkerRef
= nullptr;
431 RefPtr
<nsIGlobalObject
> global
= mPromise
->GetGlobalObject();
432 if (NS_WARN_IF(!global
) || NS_WARN_IF(!blobImpl
)) {
433 mPromise
->MaybeReject(NS_ERROR_FAILURE
);
435 RefPtr
<Blob
> blob
= Blob::Create(global
, blobImpl
);
436 if (NS_WARN_IF(!blob
)) {
437 mPromise
->MaybeReject(NS_ERROR_FAILURE
);
439 mPromise
->MaybeResolve(blob
);
449 bool CanBeDeletedOnAnyThread() override
{ return mCanceled
; }
453 mWorkerRef
= nullptr;
457 RefPtr
<Promise
> mPromise
;
458 RefPtr
<WeakWorkerRef
> mWorkerRef
;
459 Atomic
<bool> mCanceled
;
462 RefPtr
<EncodeCallback
> p
= MakeAndAddRef
<EncodeCallback
>(aPromise
);
463 p
->MaybeInitWorkerRef();
467 already_AddRefed
<Promise
> OffscreenCanvas::ConvertToBlob(
468 const ImageEncodeOptions
& aOptions
, ErrorResult
& aRv
) {
469 // do a trust check if this is a write-only canvas
471 aRv
.ThrowSecurityError("Cannot get blob from write-only canvas.");
476 aRv
.ThrowInvalidStateError(
477 "Cannot get blob from detached OffscreenCanvas.");
481 if (mWidth
== 0 || mHeight
== 0) {
482 aRv
.ThrowIndexSizeError("Cannot get blob from empty canvas.");
486 nsCOMPtr
<nsIGlobalObject
> global
= GetOwnerGlobal();
488 RefPtr
<Promise
> promise
= Promise::Create(global
, aRv
);
494 nsContentUtils::ASCIIToLower(aOptions
.mType
, type
);
496 nsAutoString encodeOptions
;
498 // Only image/jpeg and image/webp support the quality parameter.
499 if (aOptions
.mQuality
.WasPassed() &&
500 (type
.EqualsLiteral("image/jpeg") || type
.EqualsLiteral("image/webp"))) {
501 encodeOptions
.AppendLiteral("quality=");
502 encodeOptions
.AppendInt(NS_lround(aOptions
.mQuality
.Value() * 100.0));
505 RefPtr
<EncodeCompleteCallback
> callback
=
506 CreateEncodeCompleteCallback(promise
);
507 bool usePlaceholder
=
508 ShouldResistFingerprinting(RFPTarget::CanvasImageExtractionPrompt
);
509 CanvasRenderingContextHelper::ToBlob(callback
, type
, encodeOptions
,
510 /* aUsingCustomOptions */ false,
511 usePlaceholder
, aRv
);
513 promise
->MaybeReject(std::move(aRv
));
516 return promise
.forget();
519 already_AddRefed
<Promise
> OffscreenCanvas::ToBlob(JSContext
* aCx
,
520 const nsAString
& aType
,
521 JS::Handle
<JS::Value
> aParams
,
523 // do a trust check if this is a write-only canvas
525 aRv
.ThrowSecurityError("Cannot get blob from write-only canvas.");
530 aRv
.ThrowInvalidStateError(
531 "Cannot get blob from detached OffscreenCanvas.");
535 if (mWidth
== 0 || mHeight
== 0) {
536 aRv
.ThrowIndexSizeError("Cannot get blob from empty canvas.");
540 nsCOMPtr
<nsIGlobalObject
> global
= GetOwnerGlobal();
542 RefPtr
<Promise
> promise
= Promise::Create(global
, aRv
);
547 RefPtr
<EncodeCompleteCallback
> callback
=
548 CreateEncodeCompleteCallback(promise
);
549 bool usePlaceholder
=
550 ShouldResistFingerprinting(RFPTarget::CanvasImageExtractionPrompt
);
551 CanvasRenderingContextHelper::ToBlob(aCx
, callback
, aType
, aParams
,
552 usePlaceholder
, aRv
);
554 return promise
.forget();
557 already_AddRefed
<gfx::SourceSurface
> OffscreenCanvas::GetSurfaceSnapshot(
558 gfxAlphaType
* const aOutAlphaType
) {
559 if (!mCurrentContext
) {
563 return mCurrentContext
->GetSurfaceSnapshot(aOutAlphaType
);
566 void OffscreenCanvas::SetWriteOnly(RefPtr
<nsIPrincipal
>&& aExpandedReader
) {
567 NS_ReleaseOnMainThread("OffscreenCanvas::mExpandedReader",
568 mExpandedReader
.forget());
569 mExpandedReader
= std::move(aExpandedReader
);
573 bool OffscreenCanvas::CallerCanRead(nsIPrincipal
& aPrincipal
) const {
578 // If mExpandedReader is set, this canvas was tainted only by
579 // mExpandedReader's resources. So allow reading if the subject
580 // principal subsumes mExpandedReader.
581 if (mExpandedReader
&& aPrincipal
.Subsumes(mExpandedReader
)) {
585 return nsContentUtils::PrincipalHasPermission(aPrincipal
,
586 nsGkAtoms::all_urlsPermission
);
589 bool OffscreenCanvas::ShouldResistFingerprinting(RFPTarget aTarget
) const {
590 return nsContentUtils::ShouldResistFingerprinting(GetOwnerGlobal(), aTarget
);
594 already_AddRefed
<OffscreenCanvas
> OffscreenCanvas::CreateFromCloneData(
595 nsIGlobalObject
* aGlobal
, OffscreenCanvasCloneData
* aData
) {
597 RefPtr
<OffscreenCanvas
> wc
= new OffscreenCanvas(
598 aGlobal
, aData
->mWidth
, aData
->mHeight
, aData
->mCompositorBackendType
,
599 aData
->mDisplay
.forget());
600 if (aData
->mNeutered
) {
603 if (aData
->mIsWriteOnly
) {
604 wc
->SetWriteOnly(std::move(aData
->mExpandedReader
));
609 NS_IMPL_CYCLE_COLLECTION_INHERITED(OffscreenCanvas
, DOMEventTargetHelper
,
612 NS_IMPL_ADDREF_INHERITED(OffscreenCanvas
, DOMEventTargetHelper
)
613 NS_IMPL_RELEASE_INHERITED(OffscreenCanvas
, DOMEventTargetHelper
)
615 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(OffscreenCanvas
)
616 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, EventTarget
)
617 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper
)
619 } // namespace mozilla::dom