Merge autoland to mozilla-central. a=merge
[gecko.git] / dom / canvas / OffscreenCanvas.cpp
blob5732ebbc45f7e9dbadb2e1ad99b19c09f7ad2858
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)
39 : mDisplay(aDisplay),
40 mWidth(aWidth),
41 mHeight(aHeight),
42 mCompositorBackendType(aCompositorBackend),
43 mNeutered(aNeutered),
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,
53 uint32_t aHeight)
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),
61 mWidth(aWidth),
62 mHeight(aHeight),
63 mCompositorBackendType(aCompositorBackend),
64 mDisplay(aDisplay) {}
66 OffscreenCanvas::~OffscreenCanvas() {
67 Destroy();
68 NS_ReleaseOnMainThread("OffscreenCanvas::mExpandedReader",
69 mExpandedReader.forget());
72 void OffscreenCanvas::Destroy() {
73 if (mDisplay) {
74 mDisplay->DestroyCanvas();
78 JSObject* OffscreenCanvas::WrapObject(JSContext* aCx,
79 JS::Handle<JSObject*> aGivenProto) {
80 return OffscreenCanvas_Binding::Wrap(aCx, this, aGivenProto);
83 /* static */
84 already_AddRefed<OffscreenCanvas> OffscreenCanvas::Constructor(
85 const GlobalObject& aGlobal, uint32_t aWidth, uint32_t aHeight,
86 ErrorResult& aRv) {
87 // CanvasRenderingContextHelper::GetWidthHeight wants us to return
88 // an nsIntSize, so make sure that that will work.
89 if (!CheckedInt<int32_t>(aWidth).isValid()) {
90 aRv.ThrowRangeError(
91 nsPrintfCString("OffscreenCanvas width %u is out of range: must be no "
92 "greater than 2147483647.",
93 aWidth));
94 return nullptr;
96 if (!CheckedInt<int32_t>(aHeight).isValid()) {
97 aRv.ThrowRangeError(
98 nsPrintfCString("OffscreenCanvas height %u is out of range: must be no "
99 "greater than 2147483647.",
100 aHeight));
101 return nullptr;
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) {
111 if (mNeutered) {
112 aRv.ThrowInvalidStateError("Cannot set width of detached OffscreenCanvas.");
113 return;
116 // CanvasRenderingContextHelper::GetWidthHeight wants us to return
117 // an nsIntSize, so make sure that that will work.
118 if (!CheckedInt<int32_t>(aWidth).isValid()) {
119 aRv.ThrowRangeError(
120 nsPrintfCString("OffscreenCanvas width %u is out of range: must be no "
121 "greater than 2147483647.",
122 aWidth));
123 return;
126 mWidth = aWidth;
127 CanvasAttrChanged();
130 void OffscreenCanvas::SetHeight(uint32_t aHeight, ErrorResult& aRv) {
131 if (mNeutered) {
132 aRv.ThrowInvalidStateError(
133 "Cannot set height of detached OffscreenCanvas.");
134 return;
137 // CanvasRenderingContextHelper::GetWidthHeight wants us to return
138 // an nsIntSize, so make sure that that will work.
139 if (!CheckedInt<int32_t>(aHeight).isValid()) {
140 aRv.ThrowRangeError(
141 nsPrintfCString("OffscreenCanvas height %u is out of range: must be no "
142 "greater than 2147483647.",
143 aHeight));
144 return;
147 mHeight = aHeight;
148 CanvasAttrChanged();
151 void OffscreenCanvas::SetSize(const nsIntSize& aSize, ErrorResult& aRv) {
152 if (mNeutered) {
153 aRv.ThrowInvalidStateError(
154 "Cannot set dimensions of detached OffscreenCanvas.");
155 return;
158 if (NS_WARN_IF(aSize.IsEmpty())) {
159 aRv.ThrowRangeError("OffscreenCanvas size is empty, must be non-empty.");
160 return;
163 mWidth = aSize.width;
164 mHeight = aSize.height;
165 CanvasAttrChanged();
168 void OffscreenCanvas::GetContext(
169 JSContext* aCx, const OffscreenRenderingContextId& aContextId,
170 JS::Handle<JS::Value> aContextOptions,
171 Nullable<OwningOffscreenRenderingContext>& aResult, ErrorResult& aRv) {
172 if (mNeutered) {
173 aResult.SetNull();
174 aRv.ThrowInvalidStateError(
175 "Cannot create context for detached OffscreenCanvas.");
176 return;
179 CanvasContextType contextType;
180 switch (aContextId) {
181 case OffscreenRenderingContextId::_2d:
182 contextType = CanvasContextType::OffscreenCanvas2D;
183 break;
184 case OffscreenRenderingContextId::Bitmaprenderer:
185 contextType = CanvasContextType::ImageBitmap;
186 break;
187 case OffscreenRenderingContextId::Webgl:
188 contextType = CanvasContextType::WebGL1;
189 break;
190 case OffscreenRenderingContextId::Webgl2:
191 contextType = CanvasContextType::WebGL2;
192 break;
193 case OffscreenRenderingContextId::Webgpu:
194 contextType = CanvasContextType::WebGPU;
195 break;
196 default:
197 MOZ_ASSERT_UNREACHABLE("Unhandled canvas type!");
198 aResult.SetNull();
199 aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
200 return;
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;
207 if (mDisplay) {
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)) {
213 aResult.SetNull();
214 aRv.ThrowUnknownError("Worker shutting down");
215 return;
218 workerRef = new ThreadSafeWorkerRef(strongRef);
219 } else {
220 MOZ_ASSERT(NS_IsMainThread());
224 RefPtr<nsISupports> result = CanvasRenderingContextHelper::GetOrCreateContext(
225 aCx, contextType, aContextOptions, aRv);
226 if (!result) {
227 aResult.SetNull();
228 return;
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());
239 break;
240 case CanvasContextType::ImageBitmap:
241 aResult.SetValue().SetAsImageBitmapRenderingContext() =
242 *static_cast<ImageBitmapRenderingContext*>(mCurrentContext.get());
243 break;
244 case CanvasContextType::WebGL1:
245 case CanvasContextType::WebGL2: {
246 auto* webgl = static_cast<ClientWebGLContext*>(mCurrentContext.get());
247 WebGLChild* webglChild = webgl->GetChild();
248 if (webglChild) {
249 childId.emplace(webglChild->Id());
251 aResult.SetValue().SetAsWebGLRenderingContext() = *webgl;
252 break;
254 case CanvasContextType::WebGPU:
255 aResult.SetValue().SetAsGPUCanvasContext() =
256 *static_cast<webgpu::CanvasContext*>(mCurrentContext.get());
257 break;
258 default:
259 MOZ_ASSERT_UNREACHABLE("Unhandled canvas type!");
260 aResult.SetNull();
261 break;
264 if (mDisplay) {
265 mDisplay->UpdateContext(this, std::move(workerRef), mCurrentContextType,
266 childId);
270 already_AddRefed<nsICanvasRenderingContextInternal>
271 OffscreenCanvas::CreateContext(CanvasContextType aContextType) {
272 RefPtr<nsICanvasRenderingContextInternal> ret =
273 CanvasRenderingContextHelper::CreateContext(aContextType);
274 if (NS_WARN_IF(!ret)) {
275 return nullptr;
278 ret->SetOffscreenCanvas(this);
279 return ret.forget();
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());
292 return Nothing();
295 void OffscreenCanvas::UpdateDisplayData(
296 const OffscreenCanvasDisplayData& aData) {
297 if (!mDisplay) {
298 return;
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,
308 // just bail out.
309 return;
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.
329 return;
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(
343 JSContext* aCx) {
344 if (NS_WARN_IF(mNeutered)) {
345 ErrorResult rv;
346 rv.ThrowDataCloneError(
347 "Cannot clone OffscreenCanvas that is already transferred.");
348 MOZ_ALWAYS_TRUE(rv.MaybeSetPendingException(aCx));
349 return nullptr;
352 if (NS_WARN_IF(mCurrentContext)) {
353 ErrorResult rv;
354 rv.ThrowInvalidStateError("Cannot clone canvas with context.");
355 MOZ_ALWAYS_TRUE(rv.MaybeSetPendingException(aCx));
356 return nullptr;
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())) {
366 ErrorResult rv;
367 rv.ThrowNotSupportedError(
368 "Cannot transfer OffscreenCanvas bound to element using "
369 "captureStream.");
370 MOZ_ALWAYS_TRUE(rv.MaybeSetPendingException(aCx));
371 return nullptr;
374 auto cloneData = MakeUnique<OffscreenCanvasCloneData>(
375 mDisplay, mWidth, mHeight, mCompositorBackendType, mNeutered,
376 mIsWriteOnly, mExpandedReader);
377 SetNeutered();
378 return cloneData;
381 already_AddRefed<ImageBitmap> OffscreenCanvas::TransferToImageBitmap(
382 ErrorResult& aRv) {
383 if (mNeutered) {
384 aRv.ThrowInvalidStateError(
385 "Cannot get bitmap from detached OffscreenCanvas.");
386 return nullptr;
389 if (!mCurrentContext) {
390 aRv.ThrowInvalidStateError(
391 "Cannot get bitmap from canvas without a context.");
392 return nullptr;
395 RefPtr<ImageBitmap> result =
396 ImageBitmap::CreateFromOffscreenCanvas(GetOwnerGlobal(), *this, aRv);
397 if (!result) {
398 return nullptr;
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 {
411 public:
412 explicit EncodeCallback(Promise* aPromise)
413 : mPromise(aPromise), mCanceled(false) {}
415 void MaybeInitWorkerRef() {
416 WorkerPrivate* wp = GetCurrentThreadWorkerPrivate();
417 if (wp) {
418 mWorkerRef = WeakWorkerRef::Create(
419 wp, [self = RefPtr{this}]() { self->Cancel(); });
420 if (!mWorkerRef) {
421 Cancel();
426 nsresult ReceiveBlobImpl(already_AddRefed<BlobImpl> aBlobImpl) override {
427 RefPtr<BlobImpl> blobImpl = aBlobImpl;
428 mWorkerRef = nullptr;
430 if (mPromise) {
431 RefPtr<nsIGlobalObject> global = mPromise->GetGlobalObject();
432 if (NS_WARN_IF(!global) || NS_WARN_IF(!blobImpl)) {
433 mPromise->MaybeReject(NS_ERROR_FAILURE);
434 } else {
435 RefPtr<Blob> blob = Blob::Create(global, blobImpl);
436 if (NS_WARN_IF(!blob)) {
437 mPromise->MaybeReject(NS_ERROR_FAILURE);
438 } else {
439 mPromise->MaybeResolve(blob);
444 mPromise = nullptr;
446 return NS_OK;
449 bool CanBeDeletedOnAnyThread() override { return mCanceled; }
451 void Cancel() {
452 mPromise = nullptr;
453 mWorkerRef = nullptr;
454 mCanceled = true;
457 RefPtr<Promise> mPromise;
458 RefPtr<WeakWorkerRef> mWorkerRef;
459 Atomic<bool> mCanceled;
462 RefPtr<EncodeCallback> p = MakeAndAddRef<EncodeCallback>(aPromise);
463 p->MaybeInitWorkerRef();
464 return p.forget();
467 already_AddRefed<Promise> OffscreenCanvas::ConvertToBlob(
468 const ImageEncodeOptions& aOptions, ErrorResult& aRv) {
469 // do a trust check if this is a write-only canvas
470 if (mIsWriteOnly) {
471 aRv.ThrowSecurityError("Cannot get blob from write-only canvas.");
472 return nullptr;
475 if (mNeutered) {
476 aRv.ThrowInvalidStateError(
477 "Cannot get blob from detached OffscreenCanvas.");
478 return nullptr;
481 if (mWidth == 0 || mHeight == 0) {
482 aRv.ThrowIndexSizeError("Cannot get blob from empty canvas.");
483 return nullptr;
486 nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
488 RefPtr<Promise> promise = Promise::Create(global, aRv);
489 if (aRv.Failed()) {
490 return nullptr;
493 nsAutoString type;
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);
512 if (aRv.Failed()) {
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,
522 ErrorResult& aRv) {
523 // do a trust check if this is a write-only canvas
524 if (mIsWriteOnly) {
525 aRv.ThrowSecurityError("Cannot get blob from write-only canvas.");
526 return nullptr;
529 if (mNeutered) {
530 aRv.ThrowInvalidStateError(
531 "Cannot get blob from detached OffscreenCanvas.");
532 return nullptr;
535 if (mWidth == 0 || mHeight == 0) {
536 aRv.ThrowIndexSizeError("Cannot get blob from empty canvas.");
537 return nullptr;
540 nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
542 RefPtr<Promise> promise = Promise::Create(global, aRv);
543 if (aRv.Failed()) {
544 return nullptr;
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) {
560 return nullptr;
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);
570 mIsWriteOnly = true;
573 bool OffscreenCanvas::CallerCanRead(nsIPrincipal& aPrincipal) const {
574 if (!mIsWriteOnly) {
575 return true;
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)) {
582 return true;
585 return nsContentUtils::PrincipalHasPermission(aPrincipal,
586 nsGkAtoms::all_urlsPermission);
589 bool OffscreenCanvas::ShouldResistFingerprinting(RFPTarget aTarget) const {
590 return nsContentUtils::ShouldResistFingerprinting(GetOwnerGlobal(), aTarget);
593 /* static */
594 already_AddRefed<OffscreenCanvas> OffscreenCanvas::CreateFromCloneData(
595 nsIGlobalObject* aGlobal, OffscreenCanvasCloneData* aData) {
596 MOZ_ASSERT(aData);
597 RefPtr<OffscreenCanvas> wc = new OffscreenCanvas(
598 aGlobal, aData->mWidth, aData->mHeight, aData->mCompositorBackendType,
599 aData->mDisplay.forget());
600 if (aData->mNeutered) {
601 wc->SetNeutered();
603 if (aData->mIsWriteOnly) {
604 wc->SetWriteOnly(std::move(aData->mExpandedReader));
606 return wc.forget();
609 NS_IMPL_CYCLE_COLLECTION_INHERITED(OffscreenCanvas, DOMEventTargetHelper,
610 mCurrentContext)
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