Merge mozilla-central to autoland on a CLOSED TREE
[gecko.git] / dom / canvas / OffscreenCanvas.cpp
blobdebe9c8e65fa8a5fd513d8bb6371f31b582350fb
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 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);
78 /* static */
79 already_AddRefed<OffscreenCanvas> OffscreenCanvas::Constructor(
80 const GlobalObject& aGlobal, uint32_t aWidth, uint32_t aHeight,
81 ErrorResult& aRv) {
82 // CanvasRenderingContextHelper::GetWidthHeight wants us to return
83 // an nsIntSize, so make sure that that will work.
84 if (!CheckedInt<int32_t>(aWidth).isValid()) {
85 aRv.ThrowRangeError(
86 nsPrintfCString("OffscreenCanvas width %u is out of range: must be no "
87 "greater than 2147483647.",
88 aWidth));
89 return nullptr;
91 if (!CheckedInt<int32_t>(aHeight).isValid()) {
92 aRv.ThrowRangeError(
93 nsPrintfCString("OffscreenCanvas height %u is out of range: must be no "
94 "greater than 2147483647.",
95 aHeight));
96 return nullptr;
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) {
106 if (mNeutered) {
107 aRv.ThrowInvalidStateError(
108 "Cannot set width of placeholder canvas transferred to worker.");
109 return;
112 // CanvasRenderingContextHelper::GetWidthHeight wants us to return
113 // an nsIntSize, so make sure that that will work.
114 if (!CheckedInt<int32_t>(aWidth).isValid()) {
115 aRv.ThrowRangeError(
116 nsPrintfCString("OffscreenCanvas width %u is out of range: must be no "
117 "greater than 2147483647.",
118 aWidth));
119 return;
122 mWidth = aWidth;
123 CanvasAttrChanged();
126 void OffscreenCanvas::SetHeight(uint32_t aHeight, ErrorResult& aRv) {
127 if (mNeutered) {
128 aRv.ThrowInvalidStateError(
129 "Cannot set height of placeholder canvas transferred to worker.");
130 return;
133 // CanvasRenderingContextHelper::GetWidthHeight wants us to return
134 // an nsIntSize, so make sure that that will work.
135 if (!CheckedInt<int32_t>(aHeight).isValid()) {
136 aRv.ThrowRangeError(
137 nsPrintfCString("OffscreenCanvas height %u is out of range: must be no "
138 "greater than 2147483647.",
139 aHeight));
140 return;
143 mHeight = aHeight;
144 CanvasAttrChanged();
147 void OffscreenCanvas::GetContext(
148 JSContext* aCx, const OffscreenRenderingContextId& aContextId,
149 JS::Handle<JS::Value> aContextOptions,
150 Nullable<OwningOffscreenRenderingContext>& aResult, ErrorResult& aRv) {
151 if (mNeutered) {
152 aResult.SetNull();
153 aRv.ThrowInvalidStateError(
154 "Cannot create context for placeholder canvas transferred to worker.");
155 return;
158 CanvasContextType contextType;
159 switch (aContextId) {
160 case OffscreenRenderingContextId::_2d:
161 contextType = CanvasContextType::OffscreenCanvas2D;
162 break;
163 case OffscreenRenderingContextId::Bitmaprenderer:
164 contextType = CanvasContextType::ImageBitmap;
165 break;
166 case OffscreenRenderingContextId::Webgl:
167 contextType = CanvasContextType::WebGL1;
168 break;
169 case OffscreenRenderingContextId::Webgl2:
170 contextType = CanvasContextType::WebGL2;
171 break;
172 case OffscreenRenderingContextId::Webgpu:
173 contextType = CanvasContextType::WebGPU;
174 break;
175 default:
176 MOZ_ASSERT_UNREACHABLE("Unhandled canvas type!");
177 aResult.SetNull();
178 aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
179 return;
182 RefPtr<nsISupports> result = CanvasRenderingContextHelper::GetOrCreateContext(
183 aCx, contextType, aContextOptions, aRv);
184 if (!result) {
185 aResult.SetNull();
186 return;
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());
197 break;
198 case CanvasContextType::ImageBitmap:
199 aResult.SetValue().SetAsImageBitmapRenderingContext() =
200 *static_cast<ImageBitmapRenderingContext*>(mCurrentContext.get());
201 break;
202 case CanvasContextType::WebGL1:
203 case CanvasContextType::WebGL2: {
204 auto* webgl = static_cast<ClientWebGLContext*>(mCurrentContext.get());
205 WebGLChild* webglChild = webgl->GetChild();
206 if (webglChild) {
207 childId.emplace(webglChild->Id());
209 aResult.SetValue().SetAsWebGLRenderingContext() = *webgl;
210 break;
212 case CanvasContextType::WebGPU:
213 aResult.SetValue().SetAsGPUCanvasContext() =
214 *static_cast<webgpu::CanvasContext*>(mCurrentContext.get());
215 break;
216 default:
217 MOZ_ASSERT_UNREACHABLE("Unhandled canvas type!");
218 aResult.SetNull();
219 break;
222 if (mDisplay) {
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);
233 return ret.forget();
236 void OffscreenCanvas::UpdateDisplayData(
237 const OffscreenCanvasDisplayData& aData) {
238 if (!mDisplay) {
239 return;
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,
249 // just bail out.
250 return;
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.
270 return;
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(
290 ErrorResult& aRv) {
291 if (mNeutered) {
292 aRv.ThrowInvalidStateError(
293 "Cannot get bitmap from placeholder canvas transferred to worker.");
294 return nullptr;
297 if (!mCurrentContext) {
298 aRv.ThrowInvalidStateError(
299 "Cannot get bitmap from canvas without a context.");
300 return nullptr;
303 RefPtr<ImageBitmap> result =
304 ImageBitmap::CreateFromOffscreenCanvas(GetOwnerGlobal(), *this, aRv);
305 if (!result) {
306 return nullptr;
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 {
319 public:
320 explicit EncodeCallback(Promise* aPromise)
321 : mPromise(aPromise), mCanceled(false) {}
323 void MaybeInitWorkerRef() {
324 WorkerPrivate* wp = GetCurrentThreadWorkerPrivate();
325 if (wp) {
326 mWorkerRef = WeakWorkerRef::Create(
327 wp, [self = RefPtr{this}]() { self->Cancel(); });
328 if (!mWorkerRef) {
329 Cancel();
334 nsresult ReceiveBlobImpl(already_AddRefed<BlobImpl> aBlobImpl) override {
335 RefPtr<BlobImpl> blobImpl = aBlobImpl;
336 mWorkerRef = nullptr;
338 if (mPromise) {
339 RefPtr<nsIGlobalObject> global = mPromise->GetGlobalObject();
340 if (NS_WARN_IF(!global) || NS_WARN_IF(!blobImpl)) {
341 mPromise->MaybeReject(NS_ERROR_FAILURE);
342 } else {
343 RefPtr<Blob> blob = Blob::Create(global, blobImpl);
344 if (NS_WARN_IF(!blob)) {
345 mPromise->MaybeReject(NS_ERROR_FAILURE);
346 } else {
347 mPromise->MaybeResolve(blob);
352 mPromise = nullptr;
354 return NS_OK;
357 bool CanBeDeletedOnAnyThread() override { return mCanceled; }
359 void Cancel() {
360 mPromise = nullptr;
361 mWorkerRef = nullptr;
362 mCanceled = true;
365 RefPtr<Promise> mPromise;
366 RefPtr<WeakWorkerRef> mWorkerRef;
367 Atomic<bool> mCanceled;
370 RefPtr<EncodeCallback> p = MakeAndAddRef<EncodeCallback>(aPromise);
371 p->MaybeInitWorkerRef();
372 return p.forget();
375 already_AddRefed<Promise> OffscreenCanvas::ConvertToBlob(
376 const ImageEncodeOptions& aOptions, ErrorResult& aRv) {
377 // do a trust check if this is a write-only canvas
378 if (mIsWriteOnly) {
379 aRv.ThrowSecurityError("Cannot get blob from write-only canvas.");
380 return nullptr;
383 if (mNeutered) {
384 aRv.ThrowInvalidStateError(
385 "Cannot get blob from placeholder canvas transferred to worker.");
386 return nullptr;
389 if (mWidth == 0 || mHeight == 0) {
390 aRv.ThrowIndexSizeError("Cannot get blob from empty canvas.");
391 return nullptr;
394 nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
396 RefPtr<Promise> promise = Promise::Create(global, aRv);
397 if (aRv.Failed()) {
398 return nullptr;
401 nsAutoString type;
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);
420 if (aRv.Failed()) {
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,
430 ErrorResult& aRv) {
431 // do a trust check if this is a write-only canvas
432 if (mIsWriteOnly) {
433 aRv.ThrowSecurityError("Cannot get blob from write-only canvas.");
434 return nullptr;
437 if (mNeutered) {
438 aRv.ThrowInvalidStateError(
439 "Cannot get blob from placeholder canvas transferred to worker.");
440 return nullptr;
443 if (mWidth == 0 || mHeight == 0) {
444 aRv.ThrowIndexSizeError("Cannot get blob from empty canvas.");
445 return nullptr;
448 nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
450 RefPtr<Promise> promise = Promise::Create(global, aRv);
451 if (aRv.Failed()) {
452 return nullptr;
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) {
468 return nullptr;
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);
478 mIsWriteOnly = true;
481 bool OffscreenCanvas::CallerCanRead(nsIPrincipal& aPrincipal) const {
482 if (!mIsWriteOnly) {
483 return true;
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)) {
490 return true;
493 return nsContentUtils::PrincipalHasPermission(aPrincipal,
494 nsGkAtoms::all_urlsPermission);
497 bool OffscreenCanvas::ShouldResistFingerprinting(RFPTarget aTarget) const {
498 return nsContentUtils::ShouldResistFingerprinting(GetOwnerGlobal(), aTarget);
501 /* static */
502 already_AddRefed<OffscreenCanvas> OffscreenCanvas::CreateFromCloneData(
503 nsIGlobalObject* aGlobal, OffscreenCanvasCloneData* aData) {
504 MOZ_ASSERT(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) {
509 wc->SetNeutered();
511 if (aData->mIsWriteOnly) {
512 wc->SetWriteOnly(std::move(aData->mExpandedReader));
514 return wc.forget();
517 /* static */
518 bool OffscreenCanvas::PrefEnabledOnWorkerThread(JSContext* aCx,
519 JSObject* aObj) {
520 return NS_IsMainThread() || StaticPrefs::gfx_offscreencanvas_enabled();
523 NS_IMPL_CYCLE_COLLECTION_INHERITED(OffscreenCanvas, DOMEventTargetHelper,
524 mCurrentContext)
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