no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / dom / canvas / OffscreenCanvas.cpp
blob208d78daec8a5bab162a7b518d221c9b2dce0ceb
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)
39 : mDisplay(aDisplay),
40 mWidth(aWidth),
41 mHeight(aHeight),
42 mCompositorBackendType(aCompositorBackend),
43 mTextureType(aTextureType),
44 mNeutered(aNeutered),
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,
54 uint32_t aHeight)
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),
62 mWidth(aWidth),
63 mHeight(aHeight),
64 mCompositorBackendType(aCompositorBackend),
65 mTextureType(aTextureType),
66 mDisplay(aDisplay) {}
68 OffscreenCanvas::~OffscreenCanvas() {
69 Destroy();
70 NS_ReleaseOnMainThread("OffscreenCanvas::mExpandedReader",
71 mExpandedReader.forget());
74 void OffscreenCanvas::Destroy() {
75 if (mDisplay) {
76 mDisplay->DestroyCanvas();
80 JSObject* OffscreenCanvas::WrapObject(JSContext* aCx,
81 JS::Handle<JSObject*> aGivenProto) {
82 return OffscreenCanvas_Binding::Wrap(aCx, this, aGivenProto);
85 /* static */
86 already_AddRefed<OffscreenCanvas> OffscreenCanvas::Constructor(
87 const GlobalObject& aGlobal, uint32_t aWidth, uint32_t aHeight,
88 ErrorResult& aRv) {
89 // CanvasRenderingContextHelper::GetWidthHeight wants us to return
90 // an nsIntSize, so make sure that that will work.
91 if (!CheckedInt<int32_t>(aWidth).isValid()) {
92 aRv.ThrowRangeError(
93 nsPrintfCString("OffscreenCanvas width %u is out of range: must be no "
94 "greater than 2147483647.",
95 aWidth));
96 return nullptr;
98 if (!CheckedInt<int32_t>(aHeight).isValid()) {
99 aRv.ThrowRangeError(
100 nsPrintfCString("OffscreenCanvas height %u is out of range: must be no "
101 "greater than 2147483647.",
102 aHeight));
103 return nullptr;
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) {
113 if (mNeutered) {
114 aRv.ThrowInvalidStateError("Cannot set width of detached OffscreenCanvas.");
115 return;
118 // CanvasRenderingContextHelper::GetWidthHeight wants us to return
119 // an nsIntSize, so make sure that that will work.
120 if (!CheckedInt<int32_t>(aWidth).isValid()) {
121 aRv.ThrowRangeError(
122 nsPrintfCString("OffscreenCanvas width %u is out of range: must be no "
123 "greater than 2147483647.",
124 aWidth));
125 return;
128 mWidth = aWidth;
129 CanvasAttrChanged();
132 void OffscreenCanvas::SetHeight(uint32_t aHeight, ErrorResult& aRv) {
133 if (mNeutered) {
134 aRv.ThrowInvalidStateError(
135 "Cannot set height of detached OffscreenCanvas.");
136 return;
139 // CanvasRenderingContextHelper::GetWidthHeight wants us to return
140 // an nsIntSize, so make sure that that will work.
141 if (!CheckedInt<int32_t>(aHeight).isValid()) {
142 aRv.ThrowRangeError(
143 nsPrintfCString("OffscreenCanvas height %u is out of range: must be no "
144 "greater than 2147483647.",
145 aHeight));
146 return;
149 mHeight = aHeight;
150 CanvasAttrChanged();
153 void OffscreenCanvas::SetSize(const nsIntSize& aSize, ErrorResult& aRv) {
154 if (mNeutered) {
155 aRv.ThrowInvalidStateError(
156 "Cannot set dimensions of detached OffscreenCanvas.");
157 return;
160 if (NS_WARN_IF(aSize.IsEmpty())) {
161 aRv.ThrowRangeError("OffscreenCanvas size is empty, must be non-empty.");
162 return;
165 mWidth = aSize.width;
166 mHeight = aSize.height;
167 CanvasAttrChanged();
170 void OffscreenCanvas::GetContext(
171 JSContext* aCx, const OffscreenRenderingContextId& aContextId,
172 JS::Handle<JS::Value> aContextOptions,
173 Nullable<OwningOffscreenRenderingContext>& aResult, ErrorResult& aRv) {
174 if (mNeutered) {
175 aResult.SetNull();
176 aRv.ThrowInvalidStateError(
177 "Cannot create context for detached OffscreenCanvas.");
178 return;
181 CanvasContextType contextType;
182 switch (aContextId) {
183 case OffscreenRenderingContextId::_2d:
184 contextType = CanvasContextType::OffscreenCanvas2D;
185 break;
186 case OffscreenRenderingContextId::Bitmaprenderer:
187 contextType = CanvasContextType::ImageBitmap;
188 break;
189 case OffscreenRenderingContextId::Webgl:
190 contextType = CanvasContextType::WebGL1;
191 break;
192 case OffscreenRenderingContextId::Webgl2:
193 contextType = CanvasContextType::WebGL2;
194 break;
195 case OffscreenRenderingContextId::Webgpu:
196 contextType = CanvasContextType::WebGPU;
197 break;
198 default:
199 MOZ_ASSERT_UNREACHABLE("Unhandled canvas type!");
200 aResult.SetNull();
201 aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
202 return;
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;
209 if (mDisplay) {
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)) {
215 aResult.SetNull();
216 aRv.ThrowUnknownError("Worker shutting down");
217 return;
220 workerRef = new ThreadSafeWorkerRef(strongRef);
221 } else {
222 MOZ_ASSERT(NS_IsMainThread());
226 RefPtr<nsISupports> result = CanvasRenderingContextHelper::GetOrCreateContext(
227 aCx, contextType, aContextOptions, aRv);
228 if (!result) {
229 aResult.SetNull();
230 return;
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());
241 break;
242 case CanvasContextType::ImageBitmap:
243 aResult.SetValue().SetAsImageBitmapRenderingContext() =
244 *static_cast<ImageBitmapRenderingContext*>(mCurrentContext.get());
245 break;
246 case CanvasContextType::WebGL1:
247 case CanvasContextType::WebGL2: {
248 auto* webgl = static_cast<ClientWebGLContext*>(mCurrentContext.get());
249 WebGLChild* webglChild = webgl->GetChild();
250 if (webglChild) {
251 childId.emplace(webglChild->Id());
253 aResult.SetValue().SetAsWebGLRenderingContext() = *webgl;
254 break;
256 case CanvasContextType::WebGPU:
257 aResult.SetValue().SetAsGPUCanvasContext() =
258 *static_cast<webgpu::CanvasContext*>(mCurrentContext.get());
259 break;
260 default:
261 MOZ_ASSERT_UNREACHABLE("Unhandled canvas type!");
262 aResult.SetNull();
263 break;
266 if (mDisplay) {
267 mDisplay->UpdateContext(this, std::move(workerRef), mCurrentContextType,
268 childId);
272 already_AddRefed<nsICanvasRenderingContextInternal>
273 OffscreenCanvas::CreateContext(CanvasContextType aContextType) {
274 RefPtr<nsICanvasRenderingContextInternal> ret =
275 CanvasRenderingContextHelper::CreateContext(aContextType);
276 if (NS_WARN_IF(!ret)) {
277 return nullptr;
280 ret->SetOffscreenCanvas(this);
281 return ret.forget();
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());
294 return Nothing();
297 void OffscreenCanvas::UpdateDisplayData(
298 const OffscreenCanvasDisplayData& aData) {
299 if (!mDisplay) {
300 return;
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,
310 // just bail out.
311 return;
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.
331 return;
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(
345 JSContext* aCx) {
346 if (NS_WARN_IF(mNeutered)) {
347 ErrorResult rv;
348 rv.ThrowDataCloneError(
349 "Cannot clone OffscreenCanvas that is already transferred.");
350 MOZ_ALWAYS_TRUE(rv.MaybeSetPendingException(aCx));
351 return nullptr;
354 if (NS_WARN_IF(mCurrentContext)) {
355 ErrorResult rv;
356 rv.ThrowInvalidStateError("Cannot clone canvas with context.");
357 MOZ_ALWAYS_TRUE(rv.MaybeSetPendingException(aCx));
358 return nullptr;
361 auto cloneData = MakeUnique<OffscreenCanvasCloneData>(
362 mDisplay, mWidth, mHeight, mCompositorBackendType, mTextureType,
363 mNeutered, mIsWriteOnly, mExpandedReader);
364 SetNeutered();
365 return cloneData;
368 already_AddRefed<ImageBitmap> OffscreenCanvas::TransferToImageBitmap(
369 ErrorResult& aRv) {
370 if (mNeutered) {
371 aRv.ThrowInvalidStateError(
372 "Cannot get bitmap from detached OffscreenCanvas.");
373 return nullptr;
376 if (!mCurrentContext) {
377 aRv.ThrowInvalidStateError(
378 "Cannot get bitmap from canvas without a context.");
379 return nullptr;
382 RefPtr<ImageBitmap> result =
383 ImageBitmap::CreateFromOffscreenCanvas(GetOwnerGlobal(), *this, aRv);
384 if (!result) {
385 return nullptr;
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 {
398 public:
399 explicit EncodeCallback(Promise* aPromise)
400 : mPromise(aPromise), mCanceled(false) {}
402 void MaybeInitWorkerRef() {
403 WorkerPrivate* wp = GetCurrentThreadWorkerPrivate();
404 if (wp) {
405 mWorkerRef = WeakWorkerRef::Create(
406 wp, [self = RefPtr{this}]() { self->Cancel(); });
407 if (!mWorkerRef) {
408 Cancel();
413 nsresult ReceiveBlobImpl(already_AddRefed<BlobImpl> aBlobImpl) override {
414 RefPtr<BlobImpl> blobImpl = aBlobImpl;
415 mWorkerRef = nullptr;
417 if (mPromise) {
418 RefPtr<nsIGlobalObject> global = mPromise->GetGlobalObject();
419 if (NS_WARN_IF(!global) || NS_WARN_IF(!blobImpl)) {
420 mPromise->MaybeReject(NS_ERROR_FAILURE);
421 } else {
422 RefPtr<Blob> blob = Blob::Create(global, blobImpl);
423 if (NS_WARN_IF(!blob)) {
424 mPromise->MaybeReject(NS_ERROR_FAILURE);
425 } else {
426 mPromise->MaybeResolve(blob);
431 mPromise = nullptr;
433 return NS_OK;
436 bool CanBeDeletedOnAnyThread() override { return mCanceled; }
438 void Cancel() {
439 mPromise = nullptr;
440 mWorkerRef = nullptr;
441 mCanceled = true;
444 RefPtr<Promise> mPromise;
445 RefPtr<WeakWorkerRef> mWorkerRef;
446 Atomic<bool> mCanceled;
449 RefPtr<EncodeCallback> p = MakeAndAddRef<EncodeCallback>(aPromise);
450 p->MaybeInitWorkerRef();
451 return p.forget();
454 already_AddRefed<Promise> OffscreenCanvas::ConvertToBlob(
455 const ImageEncodeOptions& aOptions, ErrorResult& aRv) {
456 // do a trust check if this is a write-only canvas
457 if (mIsWriteOnly) {
458 aRv.ThrowSecurityError("Cannot get blob from write-only canvas.");
459 return nullptr;
462 if (mNeutered) {
463 aRv.ThrowInvalidStateError(
464 "Cannot get blob from detached OffscreenCanvas.");
465 return nullptr;
468 if (mWidth == 0 || mHeight == 0) {
469 aRv.ThrowIndexSizeError("Cannot get blob from empty canvas.");
470 return nullptr;
473 nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
475 RefPtr<Promise> promise = Promise::Create(global, aRv);
476 if (aRv.Failed()) {
477 return nullptr;
480 nsAutoString type;
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);
499 if (aRv.Failed()) {
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,
509 ErrorResult& aRv) {
510 // do a trust check if this is a write-only canvas
511 if (mIsWriteOnly) {
512 aRv.ThrowSecurityError("Cannot get blob from write-only canvas.");
513 return nullptr;
516 if (mNeutered) {
517 aRv.ThrowInvalidStateError(
518 "Cannot get blob from detached OffscreenCanvas.");
519 return nullptr;
522 if (mWidth == 0 || mHeight == 0) {
523 aRv.ThrowIndexSizeError("Cannot get blob from empty canvas.");
524 return nullptr;
527 nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
529 RefPtr<Promise> promise = Promise::Create(global, aRv);
530 if (aRv.Failed()) {
531 return nullptr;
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) {
547 return nullptr;
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);
557 mIsWriteOnly = true;
560 bool OffscreenCanvas::CallerCanRead(nsIPrincipal& aPrincipal) const {
561 if (!mIsWriteOnly) {
562 return true;
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)) {
569 return true;
572 return nsContentUtils::PrincipalHasPermission(aPrincipal,
573 nsGkAtoms::all_urlsPermission);
576 bool OffscreenCanvas::ShouldResistFingerprinting(RFPTarget aTarget) const {
577 return nsContentUtils::ShouldResistFingerprinting(GetOwnerGlobal(), aTarget);
580 /* static */
581 already_AddRefed<OffscreenCanvas> OffscreenCanvas::CreateFromCloneData(
582 nsIGlobalObject* aGlobal, OffscreenCanvasCloneData* aData) {
583 MOZ_ASSERT(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) {
588 wc->SetNeutered();
590 if (aData->mIsWriteOnly) {
591 wc->SetWriteOnly(std::move(aData->mExpandedReader));
593 return wc.forget();
596 /* static */
597 bool OffscreenCanvas::PrefEnabledOnWorkerThread(JSContext* aCx,
598 JSObject* aObj) {
599 return NS_IsMainThread() || StaticPrefs::gfx_offscreencanvas_enabled();
602 NS_IMPL_CYCLE_COLLECTION_INHERITED(OffscreenCanvas, DOMEventTargetHelper,
603 mCurrentContext)
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