From 48f822060f469d57dac96f6ee03577dd60e315ef Mon Sep 17 00:00:00 2001 From: Andrew Osmond Date: Wed, 2 Feb 2022 16:41:26 +0000 Subject: [PATCH] Bug 1746750 - Part 3. Implement partial OffscreenCanvasRenderingContext2D. r=lsalzman,webidl,smaug This patch adds a partial OffscreenCanvasRenderingContext2D implementation. It is missing anything text and UI related, including CanvasFilters, CanvasUserInterface, CanvasText, CanvasTextDrawingStyles, and CanvasHitRegions. Differential Revision: https://phabricator.services.mozilla.com/D135354 --- dom/bindings/Bindings.conf | 6 + dom/canvas/CanvasRenderingContext2D.cpp | 32 +++-- dom/canvas/CanvasRenderingContextHelper.cpp | 6 + dom/canvas/CanvasRenderingContextHelper.h | 1 + dom/canvas/OffscreenCanvas.cpp | 24 ++++ dom/canvas/OffscreenCanvas.h | 4 +- .../OffscreenCanvasRenderingContext2D.cpp | 117 ++++++++++++++++++ .../OffscreenCanvasRenderingContext2D.h | 60 +++++++++ dom/canvas/moz.build | 2 + dom/webidl/CanvasRenderingContext2D.webidl | 9 +- dom/webidl/OffscreenCanvas.webidl | 4 +- .../OffscreenCanvasRenderingContext2D.webidl | 30 +++++ dom/webidl/moz.build | 4 + modules/libpref/init/StaticPrefList.yaml | 2 +- 14 files changed, 282 insertions(+), 19 deletions(-) create mode 100644 dom/canvas/OffscreenCanvasRenderingContext2D.cpp create mode 100644 dom/canvas/OffscreenCanvasRenderingContext2D.h create mode 100644 dom/webidl/OffscreenCanvasRenderingContext2D.webidl diff --git a/dom/bindings/Bindings.conf b/dom/bindings/Bindings.conf index 66f444a57904..41a241ae2cc2 100644 --- a/dom/bindings/Bindings.conf +++ b/dom/bindings/Bindings.conf @@ -609,6 +609,12 @@ DOMInterfaces = { 'nativeType': 'nsDOMOfflineResourceList', }, +'OffscreenCanvasRenderingContext2D': { + 'implicitJSContext': [ + 'createImageData', 'getImageData', 'isPointInPath', 'isPointInStroke' + ], +}, + 'PaintRequestList': { 'headerFile': 'mozilla/dom/PaintRequest.h', }, diff --git a/dom/canvas/CanvasRenderingContext2D.cpp b/dom/canvas/CanvasRenderingContext2D.cpp index 787c25139c30..c3df9f53be75 100644 --- a/dom/canvas/CanvasRenderingContext2D.cpp +++ b/dom/canvas/CanvasRenderingContext2D.cpp @@ -1140,15 +1140,15 @@ nsresult CanvasRenderingContext2D::Redraw() { mIsEntireFrameInvalid = true; - if (!mCanvasElement) { + if (mCanvasElement) { + SVGObserverUtils::InvalidateDirectRenderingObservers(mCanvasElement); + mCanvasElement->InvalidateCanvasContent(nullptr); + } else if (mOffscreenCanvas) { + mOffscreenCanvas->QueueCommitToCompositor(); + } else { NS_ASSERTION(mDocShell, "Redraw with no canvas element or docshell!"); - return NS_OK; } - SVGObserverUtils::InvalidateDirectRenderingObservers(mCanvasElement); - - mCanvasElement->InvalidateCanvasContent(nullptr); - return NS_OK; } @@ -1166,14 +1166,14 @@ void CanvasRenderingContext2D::Redraw(const gfx::Rect& aR) { return; } - if (!mCanvasElement) { + if (mCanvasElement) { + SVGObserverUtils::InvalidateDirectRenderingObservers(mCanvasElement); + mCanvasElement->InvalidateCanvasContent(&aR); + } else if (mOffscreenCanvas) { + mOffscreenCanvas->QueueCommitToCompositor(); + } else { NS_ASSERTION(mDocShell, "Redraw with no canvas element or docshell!"); - return; } - - SVGObserverUtils::InvalidateDirectRenderingObservers(mCanvasElement); - - mCanvasElement->InvalidateCanvasContent(&aR); } void CanvasRenderingContext2D::DidRefresh() {} @@ -1563,6 +1563,14 @@ void CanvasRenderingContext2D::ClearTarget(int32_t aWidth, int32_t aHeight) { } } + if (mOffscreenCanvas) { + OffscreenCanvasDisplayData data; + data.mSize = {mWidth, mHeight}; + data.mIsOpaque = mOpaque; + data.mIsAlphaPremult = true; + mOffscreenCanvas->UpdateDisplayData(data); + } + if (!mCanvasElement || !mCanvasElement->IsInComposedDoc()) { return; } diff --git a/dom/canvas/CanvasRenderingContextHelper.cpp b/dom/canvas/CanvasRenderingContextHelper.cpp index 914036b4330b..88301814fcda 100644 --- a/dom/canvas/CanvasRenderingContextHelper.cpp +++ b/dom/canvas/CanvasRenderingContextHelper.cpp @@ -9,6 +9,7 @@ #include "ImageEncoder.h" #include "mozilla/dom/BlobImpl.h" #include "mozilla/dom/CanvasRenderingContext2D.h" +#include "mozilla/dom/OffscreenCanvasRenderingContext2D.h" #include "mozilla/GfxMessageUtils.h" #include "mozilla/Telemetry.h" #include "mozilla/UniquePtr.h" @@ -137,6 +138,11 @@ CanvasRenderingContextHelper::CreateContextHelper( ret = new CanvasRenderingContext2D(aCompositorBackend); break; + case CanvasContextType::OffscreenCanvas2D: + Telemetry::Accumulate(Telemetry::CANVAS_2D_USED, 1); + ret = new OffscreenCanvasRenderingContext2D(aCompositorBackend); + break; + case CanvasContextType::WebGL1: Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_USED, 1); diff --git a/dom/canvas/CanvasRenderingContextHelper.h b/dom/canvas/CanvasRenderingContextHelper.h index 22b56743e61a..d51d3c69940c 100644 --- a/dom/canvas/CanvasRenderingContextHelper.h +++ b/dom/canvas/CanvasRenderingContextHelper.h @@ -25,6 +25,7 @@ class EncodeCompleteCallback; enum class CanvasContextType : uint8_t { NoContext, Canvas2D, + OffscreenCanvas2D, WebGL1, WebGL2, WebGPU, diff --git a/dom/canvas/OffscreenCanvas.cpp b/dom/canvas/OffscreenCanvas.cpp index 41cd166c8446..eabc955f9b0f 100644 --- a/dom/canvas/OffscreenCanvas.cpp +++ b/dom/canvas/OffscreenCanvas.cpp @@ -9,6 +9,7 @@ #include "mozilla/dom/BlobImpl.h" #include "mozilla/dom/OffscreenCanvasBinding.h" #include "mozilla/dom/OffscreenCanvasDisplayHelper.h" +#include "mozilla/dom/OffscreenCanvasRenderingContext2D.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/WorkerPrivate.h" #include "mozilla/dom/WorkerScope.h" @@ -118,6 +119,9 @@ void OffscreenCanvas::GetContext( CanvasContextType contextType; switch (aContextId) { + case OffscreenRenderingContextId::_2d: + contextType = CanvasContextType::OffscreenCanvas2D; + break; case OffscreenRenderingContextId::Bitmaprenderer: contextType = CanvasContextType::ImageBitmap; break; @@ -148,6 +152,11 @@ void OffscreenCanvas::GetContext( MOZ_ASSERT(mCurrentContext); switch (mCurrentContextType) { + case CanvasContextType::OffscreenCanvas2D: + aResult.SetValue().SetAsOffscreenCanvasRenderingContext2D() = + *static_cast( + mCurrentContext.get()); + break; case CanvasContextType::ImageBitmap: aResult.SetValue().SetAsImageBitmapRenderingContext() = *static_cast(mCurrentContext.get()); @@ -305,6 +314,12 @@ already_AddRefed OffscreenCanvas::ConvertToBlob( return nullptr; } + if (mNeutered) { + aRv.ThrowInvalidStateError( + "Cannot get blob from placeholder canvas transferred to worker."); + return nullptr; + } + nsCOMPtr global = GetOwnerGlobal(); RefPtr promise = Promise::Create(global, aRv); @@ -327,6 +342,9 @@ already_AddRefed OffscreenCanvas::ConvertToBlob( CanvasRenderingContextHelper::ToBlob(callback, type, encodeOptions, /* aUsingCustomOptions */ false, usePlaceholder, aRv); + if (aRv.Failed()) { + promise->MaybeReject(std::move(aRv)); + } return promise.forget(); } @@ -341,6 +359,12 @@ already_AddRefed OffscreenCanvas::ToBlob(JSContext* aCx, return nullptr; } + if (mNeutered) { + aRv.ThrowInvalidStateError( + "Cannot get blob from placeholder canvas transferred to worker."); + return nullptr; + } + nsCOMPtr global = GetOwnerGlobal(); RefPtr promise = Promise::Create(global, aRv); diff --git a/dom/canvas/OffscreenCanvas.h b/dom/canvas/OffscreenCanvas.h index c70172cce5d9..fa69ede19ba4 100644 --- a/dom/canvas/OffscreenCanvas.h +++ b/dom/canvas/OffscreenCanvas.h @@ -36,7 +36,7 @@ class ImageBitmap; struct ImageEncodeOptions; using OwningOffscreenRenderingContext = class - OwningImageBitmapRenderingContextOrWebGLRenderingContextOrWebGL2RenderingContextOrGPUCanvasContext; + OwningOffscreenCanvasRenderingContext2DOrImageBitmapRenderingContextOrWebGLRenderingContextOrWebGL2RenderingContextOrGPUCanvasContext; // This is helper class for transferring OffscreenCanvas to worker thread. // Because OffscreenCanvas is not thread-safe. So we cannot pass Offscreen- @@ -151,6 +151,8 @@ class OffscreenCanvas final : public DOMEventTargetHelper, bool ShouldResistFingerprinting() const; + bool IsTransferredFromElement() const { return !!mDisplay; } + private: ~OffscreenCanvas(); diff --git a/dom/canvas/OffscreenCanvasRenderingContext2D.cpp b/dom/canvas/OffscreenCanvasRenderingContext2D.cpp new file mode 100644 index 000000000000..6da70d55d682 --- /dev/null +++ b/dom/canvas/OffscreenCanvasRenderingContext2D.cpp @@ -0,0 +1,117 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "OffscreenCanvasRenderingContext2D.h" +#include "mozilla/dom/OffscreenCanvasRenderingContext2DBinding.h" +#include "mozilla/dom/OffscreenCanvas.h" +#include "mozilla/dom/WorkerCommon.h" +#include "mozilla/dom/WorkerRef.h" + +namespace mozilla::dom { + +class OffscreenCanvasShutdownObserver final { + NS_INLINE_DECL_REFCOUNTING(OffscreenCanvasShutdownObserver) + + public: + explicit OffscreenCanvasShutdownObserver( + OffscreenCanvasRenderingContext2D* aOwner) + : mOwner(aOwner) {} + + void OnShutdown() { + if (mOwner) { + mOwner->OnShutdown(); + mOwner = nullptr; + } + } + + void ClearOwner() { mOwner = nullptr; } + + private: + ~OffscreenCanvasShutdownObserver() = default; + + OffscreenCanvasRenderingContext2D* mOwner; +}; + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_INHERITED( + OffscreenCanvasRenderingContext2D, CanvasRenderingContext2D) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(OffscreenCanvasRenderingContext2D) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY +NS_INTERFACE_MAP_END_INHERITING(CanvasRenderingContext2D) + +NS_IMPL_ADDREF_INHERITED(OffscreenCanvasRenderingContext2D, + CanvasRenderingContext2D) +NS_IMPL_RELEASE_INHERITED(OffscreenCanvasRenderingContext2D, + CanvasRenderingContext2D) + +OffscreenCanvasRenderingContext2D::OffscreenCanvasRenderingContext2D( + layers::LayersBackend aCompositorBackend) + : CanvasRenderingContext2D(aCompositorBackend) {} + +OffscreenCanvasRenderingContext2D::~OffscreenCanvasRenderingContext2D() = + default; + +JSObject* OffscreenCanvasRenderingContext2D::WrapObject( + JSContext* aCx, JS::Handle aGivenProto) { + return OffscreenCanvasRenderingContext2D_Binding::Wrap(aCx, this, + aGivenProto); +} + +nsIGlobalObject* OffscreenCanvasRenderingContext2D::GetParentObject() const { + return mOffscreenCanvas->GetOwnerGlobal(); +} + +void OffscreenCanvasRenderingContext2D::AddShutdownObserver() { + WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); + if (!workerPrivate) { + // We may be using OffscreenCanvas on the main thread. + CanvasRenderingContext2D::AddShutdownObserver(); + return; + } + + mOffscreenShutdownObserver = + MakeAndAddRef(this); + mWorkerRef = WeakWorkerRef::Create( + workerPrivate, + [observer = mOffscreenShutdownObserver] { observer->OnShutdown(); }); +} + +void OffscreenCanvasRenderingContext2D::RemoveShutdownObserver() { + WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); + if (!workerPrivate) { + // We may be using OffscreenCanvas on the main thread. + CanvasRenderingContext2D::RemoveShutdownObserver(); + return; + } + + if (mOffscreenShutdownObserver) { + mOffscreenShutdownObserver->ClearOwner(); + } + mOffscreenShutdownObserver = nullptr; + mWorkerRef = nullptr; +} + +void OffscreenCanvasRenderingContext2D::OnShutdown() { + if (mOffscreenShutdownObserver) { + mOffscreenShutdownObserver->ClearOwner(); + mOffscreenShutdownObserver = nullptr; + } + + CanvasRenderingContext2D::OnShutdown(); +} + +void OffscreenCanvasRenderingContext2D::Commit(ErrorResult& aRv) { + if (!mOffscreenCanvas->IsTransferredFromElement()) { + aRv.ThrowInvalidStateError( + "Cannot commit on an OffscreenCanvas that is not transferred from an " + "HTMLCanvasElement."); + return; + } + + mOffscreenCanvas->CommitFrameToCompositor(); +} + +} // namespace mozilla::dom diff --git a/dom/canvas/OffscreenCanvasRenderingContext2D.h b/dom/canvas/OffscreenCanvasRenderingContext2D.h new file mode 100644 index 000000000000..c95a049c9595 --- /dev/null +++ b/dom/canvas/OffscreenCanvasRenderingContext2D.h @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_DOM_OFFSCREENCANVASRENDERINGCONTEXT2D_H_ +#define MOZILLA_DOM_OFFSCREENCANVASRENDERINGCONTEXT2D_H_ + +#include "mozilla/RefPtr.h" +#include "mozilla/dom/CanvasRenderingContext2D.h" + +struct JSContext; +class nsIGlobalObject; + +namespace mozilla::dom { +class OffscreenCanvas; +class OffscreenCanvasShutdownObserver; +class WeakWorkerRef; + +class OffscreenCanvasRenderingContext2D final + : public CanvasRenderingContext2D { + public: + // nsISupports interface + CC + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED( + OffscreenCanvasRenderingContext2D, CanvasRenderingContext2D) + + explicit OffscreenCanvasRenderingContext2D( + layers::LayersBackend aCompositorBackend); + + nsIGlobalObject* GetParentObject() const; + + JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + + OffscreenCanvas* Canvas() { return mOffscreenCanvas; } + const OffscreenCanvas* Canvas() const { return mOffscreenCanvas; } + + void Commit(ErrorResult& aRv); + + void OnShutdown() override; + + private: + void AddShutdownObserver() override; + void RemoveShutdownObserver() override; + bool AlreadyShutDown() const override { + return !mOffscreenShutdownObserver && + CanvasRenderingContext2D::AlreadyShutDown(); + } + + ~OffscreenCanvasRenderingContext2D() override; + + RefPtr mOffscreenShutdownObserver; + RefPtr mWorkerRef; +}; + +} // namespace mozilla::dom + +#endif // MOZILLA_DOM_OFFSCREENCANVASRENDERINGCONTEXT2D_H_ diff --git a/dom/canvas/moz.build b/dom/canvas/moz.build index 59ea18deaf86..770e9e9e6a1e 100644 --- a/dom/canvas/moz.build +++ b/dom/canvas/moz.build @@ -61,6 +61,7 @@ EXPORTS.mozilla.dom += [ "ImageUtils.h", "OffscreenCanvas.h", "OffscreenCanvasDisplayHelper.h", + "OffscreenCanvasRenderingContext2D.h", "QueueParamTraits.h", "TextMetrics.h", "WebGLChild.h", @@ -101,6 +102,7 @@ SOURCES += [ "DrawTargetWebgl.cpp", # Isolate Skia "ImageUtils.cpp", "OffscreenCanvasDisplayHelper.cpp", # See bug 1745384 + "OffscreenCanvasRenderingContext2D.cpp", # See bug 1745384 ] # WebGL Sources diff --git a/dom/webidl/CanvasRenderingContext2D.webidl b/dom/webidl/CanvasRenderingContext2D.webidl index d655cb3958ce..223f6c33d412 100644 --- a/dom/webidl/CanvasRenderingContext2D.webidl +++ b/dom/webidl/CanvasRenderingContext2D.webidl @@ -338,7 +338,8 @@ interface mixin CanvasHitRegions { [Pref="canvas.hitregions.enabled"] void clearHitRegions(); }; -[Exposed=Window] +[Exposed=(Window,Worker), + Func="mozilla::dom::OffscreenCanvas::PrefEnabledOnWorkerThread"] interface CanvasGradient { // opaque object [Throws] @@ -346,7 +347,8 @@ interface CanvasGradient { void addColorStop(float offset, UTF8String color); }; -[Exposed=Window] +[Exposed=(Window,Worker), + Func="mozilla::dom::OffscreenCanvas::PrefEnabledOnWorkerThread"] interface CanvasPattern { // opaque object // [Throws, LenientFloat] - could not do this overload because of bug 1020975 @@ -397,7 +399,8 @@ interface TextMetrics { }; [Pref="canvas.path.enabled", - Exposed=Window] + Func="mozilla::dom::OffscreenCanvas::PrefEnabledOnWorkerThread", + Exposed=(Window,Worker)] interface Path2D { constructor(); diff --git a/dom/webidl/OffscreenCanvas.webidl b/dom/webidl/OffscreenCanvas.webidl index fdd07eaf4e86..d0880bb4adcb 100644 --- a/dom/webidl/OffscreenCanvas.webidl +++ b/dom/webidl/OffscreenCanvas.webidl @@ -7,14 +7,14 @@ * https://html.spec.whatwg.org/#the-offscreencanvas-interface */ -typedef (ImageBitmapRenderingContext or WebGLRenderingContext or WebGL2RenderingContext or GPUCanvasContext) OffscreenRenderingContext; +typedef (OffscreenCanvasRenderingContext2D or ImageBitmapRenderingContext or WebGLRenderingContext or WebGL2RenderingContext or GPUCanvasContext) OffscreenRenderingContext; dictionary ImageEncodeOptions { DOMString type = "image/png"; unrestricted double quality; }; -enum OffscreenRenderingContextId { /* "2d", */ "bitmaprenderer", "webgl", "webgl2", "webgpu" }; +enum OffscreenRenderingContextId { "2d", "bitmaprenderer", "webgl", "webgl2", "webgpu" }; [Exposed=(Window,Worker), Func="CanvasUtils::IsOffscreenCanvasEnabled"] diff --git a/dom/webidl/OffscreenCanvasRenderingContext2D.webidl b/dom/webidl/OffscreenCanvasRenderingContext2D.webidl new file mode 100644 index 000000000000..05df8e3d6d09 --- /dev/null +++ b/dom/webidl/OffscreenCanvasRenderingContext2D.webidl @@ -0,0 +1,30 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * For more information on this interface, please see + * https://html.spec.whatwg.org/#the-offscreen-2d-rendering-context + */ + +[Exposed=(Window,Worker), + Func="CanvasUtils::IsOffscreenCanvasEnabled"] +interface OffscreenCanvasRenderingContext2D { + [Throws] + void commit(); + + readonly attribute OffscreenCanvas canvas; +}; + +OffscreenCanvasRenderingContext2D includes CanvasState; +OffscreenCanvasRenderingContext2D includes CanvasTransform; +OffscreenCanvasRenderingContext2D includes CanvasCompositing; +OffscreenCanvasRenderingContext2D includes CanvasImageSmoothing; +OffscreenCanvasRenderingContext2D includes CanvasFillStrokeStyles; +OffscreenCanvasRenderingContext2D includes CanvasShadowStyles; +OffscreenCanvasRenderingContext2D includes CanvasRect; +OffscreenCanvasRenderingContext2D includes CanvasDrawPath; +OffscreenCanvasRenderingContext2D includes CanvasDrawImage; +OffscreenCanvasRenderingContext2D includes CanvasImageData; +OffscreenCanvasRenderingContext2D includes CanvasPathDrawingStyles; +OffscreenCanvasRenderingContext2D includes CanvasPathMethods; diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build index e84a88fd5ff3..a52c68727282 100644 --- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -241,6 +241,9 @@ with Files("OfflineAudio*"): with Files("OffscreenCanvas.webidl"): BUG_COMPONENT = ("Core", "Canvas: 2D") +with Files("OffscreenCanvasRenderingContext2D.webidl"): + BUG_COMPONENT = ("Core", "Canvas: 2D") + with Files("OscillatorNode.webidl"): BUG_COMPONENT = ("Core", "Web Audio") @@ -752,6 +755,7 @@ WEBIDL_FILES = [ "OfflineAudioContext.webidl", "OfflineResourceList.webidl", "OffscreenCanvas.webidl", + "OffscreenCanvasRenderingContext2D.webidl", "OscillatorNode.webidl", "PaintRequest.webidl", "PaintRequestList.webidl", diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml index 557fc3689755..69497db53553 100644 --- a/modules/libpref/init/StaticPrefList.yaml +++ b/modules/libpref/init/StaticPrefList.yaml @@ -1525,7 +1525,7 @@ # Add support for canvas path objects - name: canvas.path.enabled - type: bool + type: RelaxedAtomicBool value: true mirror: always