summaryrefslogtreecommitdiffstats
path: root/dom/canvas
diff options
context:
space:
mode:
Diffstat (limited to 'dom/canvas')
-rw-r--r--dom/canvas/CacheInvalidator.h19
-rw-r--r--dom/canvas/CanvasRenderingContext2D.cpp271
-rw-r--r--dom/canvas/CanvasRenderingContext2D.h32
-rw-r--r--dom/canvas/ClientWebGLContext.cpp139
-rw-r--r--dom/canvas/ClientWebGLContext.h12
-rw-r--r--dom/canvas/DmdStdContainers.h100
-rw-r--r--dom/canvas/DrawTargetWebgl.cpp41
-rw-r--r--dom/canvas/DrawTargetWebgl.h3
-rw-r--r--dom/canvas/HostWebGLContext.h34
-rw-r--r--dom/canvas/ImageBitmap.cpp27
-rw-r--r--dom/canvas/ImageUtils.cpp40
-rw-r--r--dom/canvas/ImageUtils.h5
-rw-r--r--dom/canvas/OffscreenCanvasDisplayHelper.cpp164
-rw-r--r--dom/canvas/QueueParamTraits.h12
-rw-r--r--dom/canvas/TexUnpackBlob.cpp87
-rw-r--r--dom/canvas/TexUnpackBlob.h2
-rw-r--r--dom/canvas/WebGL2Context.h4
-rw-r--r--dom/canvas/WebGL2ContextFramebuffers.cpp14
-rw-r--r--dom/canvas/WebGLChild.cpp33
-rw-r--r--dom/canvas/WebGLContext.cpp33
-rw-r--r--dom/canvas/WebGLContext.h11
-rw-r--r--dom/canvas/WebGLContextGL.cpp16
-rw-r--r--dom/canvas/WebGLIpdl.h139
-rw-r--r--dom/canvas/WebGLMemoryTracker.cpp15
-rw-r--r--dom/canvas/WebGLQueueParamTraits.h18
-rw-r--r--dom/canvas/WebGLShaderValidator.h2
-rw-r--r--dom/canvas/WebGLTexture.h8
-rw-r--r--dom/canvas/WebGLTextureUpload.cpp22
-rw-r--r--dom/canvas/WebGLTypes.h143
-rw-r--r--dom/canvas/crashtests/crashtests.list6
-rw-r--r--dom/canvas/moz.build1
-rw-r--r--dom/canvas/nsICanvasRenderingContextInternal.cpp24
-rw-r--r--dom/canvas/nsICanvasRenderingContextInternal.h4
-rw-r--r--dom/canvas/test/mochitest.toml4
-rw-r--r--dom/canvas/test/reftest/colors/_generated_reftest.list176
-rw-r--r--dom/canvas/test/reftest/colors/color_canvas.html461
-rw-r--r--dom/canvas/test/reftest/colors/generate_color_canvas_reftests.py427
-rw-r--r--dom/canvas/test/reftest/filters/reftest.list4
-rw-r--r--dom/canvas/test/reftest/reftest.list12
-rw-r--r--dom/canvas/test/test_accelerated_canvas_context_loss.html121
-rw-r--r--dom/canvas/test/webgl-conf/generated-mochitest.toml9
-rw-r--r--dom/canvas/test/webgl-conf/mochitest-errata.toml10
-rw-r--r--dom/canvas/test/webgl-mochitest/mochitest.toml53
43 files changed, 2138 insertions, 620 deletions
diff --git a/dom/canvas/CacheInvalidator.h b/dom/canvas/CacheInvalidator.h
index e63f1e4cc2..909aacfc37 100644
--- a/dom/canvas/CacheInvalidator.h
+++ b/dom/canvas/CacheInvalidator.h
@@ -9,8 +9,7 @@
#include "mozilla/Maybe.h"
#include "mozilla/UniquePtr.h"
-#include <unordered_map>
-#include <unordered_set>
+#include "DmdStdContainers.h"
#include <vector>
// -
@@ -25,7 +24,7 @@ class CacheInvalidator {
friend class AbstractCache;
private:
- mutable std::unordered_set<AbstractCache*> mCaches;
+ mutable webgl::dmd_unordered_set<AbstractCache*> mCaches;
public:
virtual ~CacheInvalidator() {
@@ -38,6 +37,12 @@ class CacheInvalidator {
}
void InvalidateCaches() const;
+
+ // -
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf mso) const {
+ return mCaches.SizeOfExcludingThis(mso);
+ }
};
// -
@@ -122,11 +127,15 @@ class CacheWeakMap final {
}
};
- using MapT =
- std::unordered_map<const KeyT*, UniquePtr<Entry>, DerefHash, DerefEqual>;
+ using MapT = webgl::dmd_unordered_map<const KeyT*, UniquePtr<Entry>,
+ DerefHash, DerefEqual>;
MapT mMap;
public:
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf mso) const {
+ return mMap.SizeOfExcludingThis(mso);
+ }
+
UniquePtr<Entry> MakeEntry(const KeyT& key, ValueT&& value) {
return UniquePtr<Entry>(new Entry(*this, key, std::move(value)));
}
diff --git a/dom/canvas/CanvasRenderingContext2D.cpp b/dom/canvas/CanvasRenderingContext2D.cpp
index 28579d4b75..529466cc81 100644
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -24,7 +24,7 @@
#include "mozilla/dom/HTMLCanvasElement.h"
#include "mozilla/dom/GeneratePlaceholderCanvasData.h"
#include "mozilla/dom/VideoFrame.h"
-#include "mozilla/gfx/CanvasManagerChild.h"
+#include "mozilla/gfx/CanvasShutdownManager.h"
#include "nsPresContext.h"
#include "nsIInterfaceRequestorUtils.h"
@@ -118,6 +118,7 @@
#include "mozilla/dom/SVGImageElement.h"
#include "mozilla/dom/TextMetrics.h"
#include "mozilla/FloatingPoint.h"
+#include "mozilla/Logging.h"
#include "nsGlobalWindowInner.h"
#include "nsDeviceContext.h"
#include "nsFontMetrics.h"
@@ -871,41 +872,6 @@ NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasGradient, mContext)
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasPattern, mContext)
-class CanvasShutdownObserver final : public nsIObserver {
- public:
- explicit CanvasShutdownObserver(CanvasRenderingContext2D* aCanvas)
- : mCanvas(aCanvas) {}
-
- void OnShutdown() {
- if (!mCanvas) {
- return;
- }
-
- mCanvas = nullptr;
- nsContentUtils::UnregisterShutdownObserver(this);
- }
-
- NS_DECL_ISUPPORTS
- NS_DECL_NSIOBSERVER
- private:
- ~CanvasShutdownObserver() = default;
-
- CanvasRenderingContext2D* mCanvas;
-};
-
-NS_IMPL_ISUPPORTS(CanvasShutdownObserver, nsIObserver)
-
-NS_IMETHODIMP
-CanvasShutdownObserver::Observe(nsISupports* aSubject, const char* aTopic,
- const char16_t* aData) {
- if (mCanvas && strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
- mCanvas->OnShutdown();
- OnShutdown();
- }
-
- return NS_OK;
-}
-
NS_IMPL_CYCLE_COLLECTING_ADDREF(CanvasRenderingContext2D)
NS_IMPL_CYCLE_COLLECTING_RELEASE(CanvasRenderingContext2D)
@@ -1242,14 +1208,8 @@ void CanvasRenderingContext2D::OnShutdown() {
}
bool CanvasRenderingContext2D::AddShutdownObserver() {
- auto* const canvasManager = CanvasManagerChild::Get();
+ auto* const canvasManager = CanvasShutdownManager::Get();
if (NS_WARN_IF(!canvasManager)) {
- if (NS_IsMainThread()) {
- mShutdownObserver = new CanvasShutdownObserver(this);
- nsContentUtils::RegisterShutdownObserver(mShutdownObserver);
- return true;
- }
-
mHasShutdown = true;
return false;
}
@@ -1259,18 +1219,70 @@ bool CanvasRenderingContext2D::AddShutdownObserver() {
}
void CanvasRenderingContext2D::RemoveShutdownObserver() {
- if (mShutdownObserver) {
- mShutdownObserver->OnShutdown();
- mShutdownObserver = nullptr;
+ auto* const canvasManager = CanvasShutdownManager::MaybeGet();
+ if (!canvasManager) {
return;
}
- auto* const canvasManager = CanvasManagerChild::MaybeGet();
- if (!canvasManager) {
+ canvasManager->RemoveShutdownObserver(this);
+}
+
+void CanvasRenderingContext2D::OnRemoteCanvasLost() {
+ // We only lose context / data if we are using remote canvas, which is only
+ // for accelerated targets.
+ if (!mBufferProvider || !mBufferProvider->IsAccelerated() || mIsContextLost) {
return;
}
- canvasManager->RemoveShutdownObserver(this);
+ // 2. Set context's context lost to true.
+ mIsContextLost = mAllowContextRestore = true;
+
+ // 3. Reset the rendering context to its default state given context.
+ ClearTarget();
+
+ // We dispatch because it isn't safe to call into the script event handlers,
+ // and we don't want to mutate our state in CanvasShutdownManager.
+ NS_DispatchToCurrentThread(NS_NewCancelableRunnableFunction(
+ "CanvasRenderingContext2D::OnRemoteCanvasLost", [self = RefPtr{this}] {
+ // 4. Let shouldRestore be the result of firing an event named
+ // contextlost at canvas, with the cancelable attribute initialized to
+ // true.
+ self->mAllowContextRestore = self->DispatchEvent(
+ u"contextlost"_ns, CanBubble::eNo, Cancelable::eYes);
+ }));
+}
+
+void CanvasRenderingContext2D::OnRemoteCanvasRestored() {
+ // We never lost our context if it was not a remote canvas, nor can we restore
+ // if we have already shutdown.
+ if (mHasShutdown || !mIsContextLost || !mAllowContextRestore) {
+ return;
+ }
+
+ // We dispatch because it isn't safe to call into the script event handlers,
+ // and we don't want to mutate our state in CanvasShutdownManager.
+ NS_DispatchToCurrentThread(NS_NewCancelableRunnableFunction(
+ "CanvasRenderingContext2D::OnRemoteCanvasRestored",
+ [self = RefPtr{this}] {
+ // 5. If shouldRestore is false, then abort these steps.
+ if (!self->mHasShutdown && self->mIsContextLost &&
+ self->mAllowContextRestore) {
+ // 7. Set context's context lost to false.
+ self->mIsContextLost = false;
+
+ // 6. Attempt to restore context by creating a backing storage using
+ // context's attributes and associating them with context. If this
+ // fails, then abort these steps.
+ if (!self->EnsureTarget()) {
+ self->mIsContextLost = true;
+ return;
+ }
+
+ // 8. Fire an event named contextrestored at canvas.
+ self->DispatchEvent(u"contextrestored"_ns, CanBubble::eNo,
+ Cancelable::eNo);
+ }
+ }));
}
void CanvasRenderingContext2D::SetStyleFromString(const nsACString& aStr,
@@ -1489,23 +1501,47 @@ bool CanvasRenderingContext2D::BorrowTarget(const IntRect& aPersistedRect,
return true;
}
-bool CanvasRenderingContext2D::EnsureTarget(const gfx::Rect* aCoveredRect,
+bool CanvasRenderingContext2D::EnsureTarget(ErrorResult& aError,
+ const gfx::Rect* aCoveredRect,
bool aWillClear) {
if (AlreadyShutDown()) {
gfxCriticalNoteOnce << "Attempt to render into a Canvas2d after shutdown.";
SetErrorState();
+ aError.ThrowInvalidStateError(
+ "Cannot use canvas after shutdown initiated.");
+ return false;
+ }
+
+ // The spec doesn't say what to do in this case, but Chrome silently fails
+ // without throwing an error. We should at least throw if the canvas is
+ // permanently disabled.
+ if (NS_WARN_IF(mIsContextLost)) {
+ if (!mAllowContextRestore) {
+ aError.ThrowInvalidStateError(
+ "Cannot use canvas as context is lost forever.");
+ }
return false;
}
if (mTarget) {
- return mTarget != sErrorTarget.get();
+ if (mTarget == sErrorTarget.get()) {
+ aError.ThrowInvalidStateError("Canvas is already in error state.");
+ return false;
+ }
+ return true;
}
// Check that the dimensions are sane
if (mWidth > StaticPrefs::gfx_canvas_max_size() ||
- mHeight > StaticPrefs::gfx_canvas_max_size() || mWidth < 0 ||
- mHeight < 0) {
+ mHeight > StaticPrefs::gfx_canvas_max_size()) {
+ SetErrorState();
+ aError.ThrowInvalidStateError("Canvas exceeds max size.");
+ return false;
+ }
+
+ if (mWidth < 0 || mHeight < 0) {
SetErrorState();
+ aError.ThrowInvalidStateError("Canvas has invalid size.");
return false;
}
@@ -1547,7 +1583,7 @@ bool CanvasRenderingContext2D::EnsureTarget(const gfx::Rect* aCoveredRect,
if (!TryAcceleratedTarget(newTarget, newProvider) &&
!TrySharedTarget(newTarget, newProvider) &&
- !TryBasicTarget(newTarget, newProvider)) {
+ !TryBasicTarget(newTarget, newProvider, aError)) {
gfxCriticalError(
CriticalLog::DefaultOptions(Factory::ReasonableSurfaceSize(GetSize())))
<< "Failed borrow shared and basic targets.";
@@ -1684,15 +1720,27 @@ bool CanvasRenderingContext2D::TryAcceleratedTarget(
return false;
}
- if (!mCanvasElement) {
- return false;
- }
- WindowRenderer* renderer = WindowRendererFromCanvasElement(mCanvasElement);
- if (!renderer) {
- return false;
+ if (mCanvasElement) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ WindowRenderer* renderer = WindowRendererFromCanvasElement(mCanvasElement);
+ if (NS_WARN_IF(!renderer)) {
+ return false;
+ }
+
+ aOutProvider = PersistentBufferProviderAccelerated::Create(
+ GetSize(), GetSurfaceFormat(), renderer->AsKnowsCompositor());
+ } else if (mOffscreenCanvas &&
+ StaticPrefs::gfx_canvas_remote_allow_offscreen()) {
+ RefPtr<ImageBridgeChild> imageBridge = ImageBridgeChild::GetSingleton();
+ if (NS_WARN_IF(!imageBridge)) {
+ return false;
+ }
+
+ aOutProvider = PersistentBufferProviderAccelerated::Create(
+ GetSize(), GetSurfaceFormat(), imageBridge);
}
- aOutProvider = PersistentBufferProviderAccelerated::Create(
- GetSize(), GetSurfaceFormat(), renderer->AsKnowsCompositor());
+
if (!aOutProvider) {
return false;
}
@@ -1716,8 +1764,10 @@ bool CanvasRenderingContext2D::TrySharedTarget(
}
if (mCanvasElement) {
+ MOZ_ASSERT(NS_IsMainThread());
+
WindowRenderer* renderer = WindowRendererFromCanvasElement(mCanvasElement);
- if (!renderer) {
+ if (NS_WARN_IF(!renderer)) {
return false;
}
@@ -1735,7 +1785,7 @@ bool CanvasRenderingContext2D::TrySharedTarget(
return false;
}
- aOutProvider = layers::PersistentBufferProviderShared::Create(
+ aOutProvider = PersistentBufferProviderShared::Create(
GetSize(), GetSurfaceFormat(), imageBridge,
!mAllowAcceleration || GetEffectiveWillReadFrequently(),
mOffscreenCanvas->GetWindowID());
@@ -1755,10 +1805,12 @@ bool CanvasRenderingContext2D::TrySharedTarget(
bool CanvasRenderingContext2D::TryBasicTarget(
RefPtr<gfx::DrawTarget>& aOutDT,
- RefPtr<layers::PersistentBufferProvider>& aOutProvider) {
+ RefPtr<layers::PersistentBufferProvider>& aOutProvider,
+ ErrorResult& aError) {
aOutDT = gfxPlatform::GetPlatform()->CreateOffscreenCanvasDrawTarget(
GetSize(), GetSurfaceFormat());
if (!aOutDT) {
+ aError.ThrowInvalidStateError("Canvas could not create basic draw target.");
return false;
}
@@ -1767,6 +1819,7 @@ bool CanvasRenderingContext2D::TryBasicTarget(
if (!aOutDT->IsValid()) {
aOutDT = nullptr;
+ aError.ThrowInvalidStateError("Canvas could not init basic draw target.");
return false;
}
@@ -2042,7 +2095,7 @@ CanvasRenderingContext2D::GetOptimizedSnapshot(DrawTarget* aTarget,
// already exists, otherwise we get performance issues. See bug 1567054.
if (!EnsureTarget()) {
MOZ_ASSERT(
- mTarget == sErrorTarget.get(),
+ mTarget == sErrorTarget.get() || mIsContextLost,
"On EnsureTarget failure mTarget should be set to sErrorTarget.");
// In rare circumstances we may have failed to create an error target.
return mTarget ? mTarget->Snapshot() : nullptr;
@@ -2110,36 +2163,36 @@ void CanvasRenderingContext2D::Restore() {
void CanvasRenderingContext2D::Scale(double aX, double aY,
ErrorResult& aError) {
- EnsureTarget();
- if (!IsTargetValid()) {
- aError.Throw(NS_ERROR_FAILURE);
+ if (!EnsureTarget(aError)) {
return;
}
+ MOZ_ASSERT(IsTargetValid());
+
Matrix newMatrix = mTarget->GetTransform();
newMatrix.PreScale(aX, aY);
SetTransformInternal(newMatrix);
}
void CanvasRenderingContext2D::Rotate(double aAngle, ErrorResult& aError) {
- EnsureTarget();
- if (!IsTargetValid()) {
- aError.Throw(NS_ERROR_FAILURE);
+ if (!EnsureTarget(aError)) {
return;
}
+ MOZ_ASSERT(IsTargetValid());
+
Matrix newMatrix = Matrix::Rotation(aAngle) * mTarget->GetTransform();
SetTransformInternal(newMatrix);
}
void CanvasRenderingContext2D::Translate(double aX, double aY,
ErrorResult& aError) {
- EnsureTarget();
- if (!IsTargetValid()) {
- aError.Throw(NS_ERROR_FAILURE);
+ if (!EnsureTarget(aError)) {
return;
}
+ MOZ_ASSERT(IsTargetValid());
+
Matrix newMatrix = mTarget->GetTransform();
newMatrix.PreTranslate(aX, aY);
SetTransformInternal(newMatrix);
@@ -2148,12 +2201,12 @@ void CanvasRenderingContext2D::Translate(double aX, double aY,
void CanvasRenderingContext2D::Transform(double aM11, double aM12, double aM21,
double aM22, double aDx, double aDy,
ErrorResult& aError) {
- EnsureTarget();
- if (!IsTargetValid()) {
- aError.Throw(NS_ERROR_FAILURE);
+ if (!EnsureTarget(aError)) {
return;
}
+ MOZ_ASSERT(IsTargetValid());
+
Matrix newMatrix(aM11, aM12, aM21, aM22, aDx, aDy);
newMatrix *= mTarget->GetTransform();
SetTransformInternal(newMatrix);
@@ -2161,11 +2214,12 @@ void CanvasRenderingContext2D::Transform(double aM11, double aM12, double aM21,
already_AddRefed<DOMMatrix> CanvasRenderingContext2D::GetTransform(
ErrorResult& aError) {
- EnsureTarget();
- if (!IsTargetValid()) {
- aError.Throw(NS_ERROR_FAILURE);
+ if (!EnsureTarget(aError)) {
return nullptr;
}
+
+ MOZ_ASSERT(IsTargetValid());
+
RefPtr<DOMMatrix> matrix =
new DOMMatrix(GetParentObject(), mTarget->GetTransform());
return matrix.forget();
@@ -2175,24 +2229,24 @@ void CanvasRenderingContext2D::SetTransform(double aM11, double aM12,
double aM21, double aM22,
double aDx, double aDy,
ErrorResult& aError) {
- EnsureTarget();
- if (!IsTargetValid()) {
- aError.Throw(NS_ERROR_FAILURE);
+ if (!EnsureTarget(aError)) {
return;
}
+ MOZ_ASSERT(IsTargetValid());
+
Matrix newMatrix(aM11, aM12, aM21, aM22, aDx, aDy);
SetTransformInternal(newMatrix);
}
void CanvasRenderingContext2D::SetTransform(const DOMMatrix2DInit& aInit,
ErrorResult& aError) {
- EnsureTarget();
- if (!IsTargetValid()) {
- aError.Throw(NS_ERROR_FAILURE);
+ if (!EnsureTarget(aError)) {
return;
}
+ MOZ_ASSERT(IsTargetValid());
+
RefPtr<DOMMatrixReadOnly> matrix =
DOMMatrixReadOnly::FromMatrix(GetParentObject(), aInit, aError);
if (!aError.Failed()) {
@@ -2420,11 +2474,12 @@ already_AddRefed<CanvasPattern> CanvasRenderingContext2D::CreatePattern(
} else {
// Special case for ImageBitmap
ImageBitmap& imgBitmap = aSource.GetAsImageBitmap();
- EnsureTarget();
- if (!IsTargetValid()) {
- aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ if (!EnsureTarget(aError)) {
return nullptr;
}
+
+ MOZ_ASSERT(IsTargetValid());
+
RefPtr<SourceSurface> srcSurf = imgBitmap.PrepareForDrawTarget(mTarget);
if (!srcSurf) {
aError.ThrowInvalidStateError(
@@ -2441,12 +2496,12 @@ already_AddRefed<CanvasPattern> CanvasRenderingContext2D::CreatePattern(
return pat.forget();
}
- EnsureTarget();
- if (!IsTargetValid()) {
- aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ if (!EnsureTarget(aError)) {
return nullptr;
}
+ MOZ_ASSERT(IsTargetValid());
+
// The canvas spec says that createPattern should use the first frame
// of animated images
auto flags = nsLayoutUtils::SFE_WANT_FIRST_FRAME_IF_IMAGE |
@@ -4883,12 +4938,12 @@ UniquePtr<TextMetrics> CanvasRenderingContext2D::DrawOrMeasureText(
processor.mPt.x *= processor.mAppUnitsPerDevPixel;
processor.mPt.y *= processor.mAppUnitsPerDevPixel;
- EnsureTarget();
- if (!IsTargetValid()) {
- aError = NS_ERROR_FAILURE;
+ if (!EnsureTarget(aError)) {
return nullptr;
}
+ MOZ_ASSERT(IsTargetValid());
+
Matrix oldTransform = mTarget->GetTransform();
bool restoreTransform = false;
// if text is over aMaxWidth, then scale the text horizontally such that its
@@ -5329,11 +5384,12 @@ void CanvasRenderingContext2D::DrawImage(const CanvasImageSource& aImage,
OffscreenCanvas* offscreenCanvas = nullptr;
VideoFrame* videoFrame = nullptr;
- EnsureTarget();
- if (!IsTargetValid()) {
+ if (!EnsureTarget(aError)) {
return;
}
+ MOZ_ASSERT(IsTargetValid());
+
if (aImage.IsHTMLCanvasElement()) {
HTMLCanvasElement* canvas = &aImage.GetAsHTMLCanvasElement();
element = canvas;
@@ -5776,11 +5832,12 @@ void CanvasRenderingContext2D::DrawWindow(nsGlobalWindowInner& aWindow,
GlobalAlpha() == 1.0f &&
(op == CompositionOp::OP_OVER || op == CompositionOp::OP_SOURCE);
const gfx::Rect drawRect(aX, aY, aW, aH);
- EnsureTarget(discardContent ? &drawRect : nullptr);
- if (!IsTargetValid()) {
+ if (!EnsureTarget(aError, discardContent ? &drawRect : nullptr)) {
return;
}
+ MOZ_ASSERT(IsTargetValid());
+
RefPtr<nsPresContext> presContext;
nsIDocShell* docshell = aWindow.GetDocShell();
if (docshell) {
@@ -5882,7 +5939,11 @@ void CanvasRenderingContext2D::DrawWindow(nsGlobalWindowInner& aWindow,
&thebes.ref());
// If this canvas was contained in the drawn window, the pre-transaction
// callback may have returned its DT. If so, we must reacquire it here.
- EnsureTarget(discardContent ? &drawRect : nullptr);
+ if (!EnsureTarget(aError, discardContent ? &drawRect : nullptr)) {
+ return;
+ }
+
+ MOZ_ASSERT(IsTargetValid());
if (drawDT) {
RefPtr<SourceSurface> snapshot = drawDT->Snapshot();
@@ -6222,12 +6283,12 @@ void CanvasRenderingContext2D::PutImageData_explicit(
// composition operator must not affect the getImageData() and
// putImageData() methods.
const gfx::Rect putRect(dirtyRect);
- EnsureTarget(&putRect);
-
- if (!IsTargetValid()) {
- return aRv.Throw(NS_ERROR_FAILURE);
+ if (!EnsureTarget(aRv, &putRect)) {
+ return;
}
+ MOZ_ASSERT(IsTargetValid());
+
DataSourceSurface::MappedSurface map;
uint8_t* dstData;
IntSize dstSize;
diff --git a/dom/canvas/CanvasRenderingContext2D.h b/dom/canvas/CanvasRenderingContext2D.h
index 3376e1033c..75a57ab14f 100644
--- a/dom/canvas/CanvasRenderingContext2D.h
+++ b/dom/canvas/CanvasRenderingContext2D.h
@@ -570,6 +570,10 @@ class CanvasRenderingContext2D : public nsICanvasRenderingContextInternal,
void OnShutdown();
+ bool IsContextLost() const { return mIsContextLost; }
+ void OnRemoteCanvasLost();
+ void OnRemoteCanvasRestored();
+
/**
* Update CurrentState().filter with the filter description for
* CurrentState().filterChain.
@@ -699,8 +703,16 @@ class CanvasRenderingContext2D : public nsICanvasRenderingContextInternal,
*
* Returns true on success.
*/
- bool EnsureTarget(const gfx::Rect* aCoveredRect = nullptr,
+ bool EnsureTarget(ErrorResult& aError,
+ const gfx::Rect* aCoveredRect = nullptr,
bool aWillClear = false);
+
+ bool EnsureTarget(const gfx::Rect* aCoveredRect = nullptr,
+ bool aWillClear = false) {
+ IgnoredErrorResult error;
+ return EnsureTarget(error, aCoveredRect, aWillClear);
+ }
+
// Attempt to borrow a new target from an existing buffer provider.
bool BorrowTarget(const gfx::IntRect& aPersistedRect, bool aNeedsClear);
@@ -714,7 +726,8 @@ class CanvasRenderingContext2D : public nsICanvasRenderingContextInternal,
RefPtr<layers::PersistentBufferProvider>& aOutProvider);
bool TryBasicTarget(RefPtr<gfx::DrawTarget>& aOutDT,
- RefPtr<layers::PersistentBufferProvider>& aOutProvider);
+ RefPtr<layers::PersistentBufferProvider>& aOutProvider,
+ ErrorResult& aError);
void RegisterAllocation();
@@ -753,7 +766,7 @@ class CanvasRenderingContext2D : public nsICanvasRenderingContextInternal,
* Check if the target is valid after calling EnsureTarget.
*/
bool IsTargetValid() const {
- return !!mTarget && mTarget != sErrorTarget.get();
+ return !!mTarget && mTarget != sErrorTarget.get() && !mIsContextLost;
}
/**
@@ -836,8 +849,11 @@ class CanvasRenderingContext2D : public nsICanvasRenderingContextInternal,
bool mWillReadFrequently = false;
// Whether or not we have already shutdown.
bool mHasShutdown = false;
+ // Whether or not remote canvas is currently unavailable.
+ bool mIsContextLost = false;
+ // Whether or not we can restore the context after restoration.
+ bool mAllowContextRestore = true;
- RefPtr<CanvasShutdownObserver> mShutdownObserver;
bool AddShutdownObserver();
void RemoveShutdownObserver();
bool AlreadyShutDown() const { return mHasShutdown; }
@@ -1008,9 +1024,11 @@ class CanvasRenderingContext2D : public nsICanvasRenderingContextInternal,
RefPtr<nsAtom> fontLanguage;
nsFont fontFont;
- EnumeratedArray<Style, Style::MAX, RefPtr<CanvasGradient>> gradientStyles;
- EnumeratedArray<Style, Style::MAX, RefPtr<CanvasPattern>> patternStyles;
- EnumeratedArray<Style, Style::MAX, nscolor> colorStyles;
+ EnumeratedArray<Style, RefPtr<CanvasGradient>, size_t(Style::MAX)>
+ gradientStyles;
+ EnumeratedArray<Style, RefPtr<CanvasPattern>, size_t(Style::MAX)>
+ patternStyles;
+ EnumeratedArray<Style, nscolor, size_t(Style::MAX)> colorStyles;
nsCString font;
CanvasTextAlign textAlign = CanvasTextAlign::Start;
diff --git a/dom/canvas/ClientWebGLContext.cpp b/dom/canvas/ClientWebGLContext.cpp
index bde265f9a0..1614d2ead2 100644
--- a/dom/canvas/ClientWebGLContext.cpp
+++ b/dom/canvas/ClientWebGLContext.cpp
@@ -847,7 +847,12 @@ bool ClientWebGLContext::CreateHostContext(const uvec2& requestedSize) {
ShouldResistFingerprinting(RFPTarget::WebGLRenderCapability);
const auto principalKey = GetPrincipalHashValue();
const auto initDesc = webgl::InitContextDesc{
- mIsWebGL2, resistFingerprinting, requestedSize, options, principalKey};
+ .isWebgl2 = mIsWebGL2,
+ .resistFingerprinting = resistFingerprinting,
+ .principalKey = principalKey,
+ .size = requestedSize,
+ .options = options,
+ };
// -
@@ -1264,28 +1269,13 @@ RefPtr<gfx::DataSourceSurface> ClientWebGLContext::BackBufferSnapshot() {
MOZ_ASSERT(static_cast<uint32_t>(map.GetStride()) == stride);
const auto desc = webgl::ReadPixelsDesc{{0, 0}, size};
- const auto range = Range<uint8_t>(map.GetData(), stride * size.y);
- if (!DoReadPixels(desc, range)) return nullptr;
-
- const auto begin = range.begin().get();
+ const auto pixels = Span<uint8_t>(map.GetData(), stride * size.y);
+ if (!DoReadPixels(desc, pixels)) return nullptr;
- std::vector<uint8_t> temp;
- temp.resize(stride);
- for (const auto i : IntegerRange(size.y / 2)) {
- const auto top = begin + stride * i;
- const auto bottom = begin + stride * (size.y - 1 - i);
- memcpy(temp.data(), top, stride);
- memcpy(top, bottom, stride);
- gfxUtils::ConvertBGRAtoRGBA(top, stride);
-
- memcpy(bottom, temp.data(), stride);
- gfxUtils::ConvertBGRAtoRGBA(bottom, stride);
- }
-
- if (size.y % 2) {
- const auto middle = begin + stride * (size.y / 2);
- gfxUtils::ConvertBGRAtoRGBA(middle, stride);
- }
+ // RGBA->BGRA and flip-y.
+ MOZ_RELEASE_ASSERT(gfx::SwizzleYFlipData(
+ pixels.data(), stride, gfx::SurfaceFormat::R8G8B8A8, pixels.data(),
+ stride, gfx::SurfaceFormat::B8G8R8A8, {size.x, size.y}));
}
return surf;
@@ -3419,7 +3409,7 @@ void ClientWebGLContext::GetBufferSubData(GLenum target, GLintptr srcByteOffset,
const auto& child = notLost->outOfProcess;
child->FlushPendingCmds();
mozilla::ipc::Shmem rawShmem;
- if (!child->SendGetBufferSubData(target, srcByteOffset, destView->length(),
+ if (!child->SendGetBufferSubData(target, srcByteOffset, destView->size(),
&rawShmem)) {
return;
}
@@ -3429,14 +3419,13 @@ void ClientWebGLContext::GetBufferSubData(GLenum target, GLintptr srcByteOffset,
return;
}
- const auto shmemView = shmem.ByteRange();
- MOZ_RELEASE_ASSERT(shmemView.length() == 1 + destView->length());
+ const auto shmemView = Span{shmem.ByteRange()};
+ MOZ_RELEASE_ASSERT(shmemView.size() == 1 + destView->size());
- const auto ok = bool(*(shmemView.begin().get()));
- const auto srcView =
- Range<const uint8_t>{shmemView.begin() + 1, shmemView.end()};
+ const auto ok = bool(shmemView[0]);
+ const auto srcView = shmemView.subspan(1);
if (ok) {
- Memcpy(destView->begin(), srcView.begin(), srcView.length());
+ Memcpy(&*destView, srcView);
}
});
}
@@ -3463,8 +3452,8 @@ void ClientWebGLContext::BufferData(
if (!ValidateNonNull("src", maybeSrc)) return;
const auto& src = maybeSrc.Value();
- src.ProcessFixedData([&](const Span<uint8_t>& aData) {
- Run<RPROC(BufferData)>(target, RawBuffer<>(aData), usage);
+ src.ProcessFixedData([&](const Span<const uint8_t>& aData) {
+ Run<RPROC(BufferData)>(target, aData, usage);
});
}
@@ -3481,7 +3470,7 @@ void ClientWebGLContext::BufferData(GLenum target,
if (!range) {
return;
}
- Run<RPROC(BufferData)>(target, RawBuffer<>(*range), usage);
+ Run<RPROC(BufferData)>(target, *range, usage);
});
}
@@ -3491,8 +3480,8 @@ void ClientWebGLContext::BufferSubData(GLenum target,
WebGLsizeiptr dstByteOffset,
const dom::ArrayBuffer& src) {
const FuncScope funcScope(*this, "bufferSubData");
- src.ProcessFixedData([&](const Span<uint8_t>& aData) {
- Run<RPROC(BufferSubData)>(target, dstByteOffset, RawBuffer<>(aData),
+ src.ProcessFixedData([&](const Span<const uint8_t>& aData) {
+ Run<RPROC(BufferSubData)>(target, dstByteOffset, aData,
/* unsynchronized */ false);
});
}
@@ -3511,7 +3500,7 @@ void ClientWebGLContext::BufferSubData(GLenum target,
if (!range) {
return;
}
- Run<RPROC(BufferSubData)>(target, dstByteOffset, RawBuffer<>(*range),
+ Run<RPROC(BufferSubData)>(target, dstByteOffset, *range,
/* unsynchronized */ false);
});
}
@@ -3811,9 +3800,7 @@ void ClientWebGLContext::BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1,
void ClientWebGLContext::InvalidateFramebuffer(
GLenum target, const dom::Sequence<GLenum>& attachments,
ErrorResult& unused) {
- const auto range = MakeRange(attachments);
- const auto& buffer = RawBufferView(range);
- Run<RPROC(InvalidateFramebuffer)>(target, buffer);
+ Run<RPROC(InvalidateFramebuffer)>(target, Span{attachments});
// Never invalidate the backbuffer, so never needs AfterDrawCall.
}
@@ -3821,9 +3808,8 @@ void ClientWebGLContext::InvalidateFramebuffer(
void ClientWebGLContext::InvalidateSubFramebuffer(
GLenum target, const dom::Sequence<GLenum>& attachments, GLint x, GLint y,
GLsizei width, GLsizei height, ErrorResult& unused) {
- const auto range = MakeRange(attachments);
- const auto& buffer = RawBufferView(range);
- Run<RPROC(InvalidateSubFramebuffer)>(target, buffer, x, y, width, height);
+ Run<RPROC(InvalidateSubFramebuffer)>(target, Span{attachments}, x, y, width,
+ height);
// Never invalidate the backbuffer, so never needs AfterDrawCall.
}
@@ -4102,13 +4088,11 @@ Range<T> SubRange(const Range<T>& full, const size_t offset,
return Range<T>{newBegin, newBegin + length};
}
-Maybe<Range<const uint8_t>> GetRangeFromData(const Span<uint8_t>& data,
- size_t bytesPerElem,
- GLuint elemOffset,
- GLuint elemCountOverride) {
- const auto byteRange = Range(data); // In bytes.
-
- auto elemCount = byteRange.length() / bytesPerElem;
+Maybe<Span<const uint8_t>> GetRangeFromData(const Span<uint8_t>& data,
+ size_t bytesPerElem,
+ GLuint elemOffset,
+ GLuint elemCountOverride) {
+ auto elemCount = data.size() / bytesPerElem;
if (elemOffset > elemCount) return {};
elemCount -= elemOffset;
@@ -4116,9 +4100,8 @@ Maybe<Range<const uint8_t>> GetRangeFromData(const Span<uint8_t>& data,
if (elemCountOverride > elemCount) return {};
elemCount = elemCountOverride;
}
- const auto subrange =
- SubRange(byteRange, elemOffset * bytesPerElem, elemCount * bytesPerElem);
- return Some(subrange);
+ return Some(
+ data.subspan(elemOffset * bytesPerElem, elemCount * bytesPerElem));
}
// -
@@ -4165,7 +4148,8 @@ void webgl::TexUnpackBlobDesc::Shrink(const webgl::PackingInfo& pi) {
CheckedInt<size_t>(unpack.metrics.bytesPerRowStride) *
unpack.metrics.totalRows;
if (bytesUpperBound.isValid()) {
- cpuData->Shrink(bytesUpperBound.value());
+ auto& span = *cpuData;
+ span = span.subspan(0, std::min(span.size(), bytesUpperBound.value()));
}
}
}
@@ -4241,7 +4225,7 @@ void ClientWebGLContext::TexImage(uint8_t funcDims, GLenum imageTarget,
return Some(webgl::TexUnpackBlobDesc{imageTarget,
size.value(),
gfxAlphaType::NonPremult,
- Some(RawBuffer<>{*range}),
+ Some(*range),
{}});
});
}
@@ -4378,12 +4362,9 @@ void ClientWebGLContext::TexImage(uint8_t funcDims, GLenum imageTarget,
const auto& contextInfo = mNotLost->info;
const auto fallbackReason = [&]() -> Maybe<std::string> {
- if (!respecFormat) {
- return Some(std::string{
- "Fast uploads not supported for TexSubImage. Use TexImage."});
- }
- auto fallbackReason = BlitPreventReason(
- level, offset, respecFormat, pi, *desc, contextInfo.isRgb8Renderable);
+ auto fallbackReason =
+ BlitPreventReason(level, offset, respecFormat, pi, *desc,
+ contextInfo.optionalRenderableFormatBits);
if (fallbackReason) return fallbackReason;
const bool canUploadViaSd = contextInfo.uploadableSdTypes[sdType];
@@ -4588,7 +4569,7 @@ void ClientWebGLContext::CompressedTexImage(bool sub, uint8_t funcDims,
RunWithGCData<RPROC(CompressedTexImage)>(
std::move(aNoGC), sub, imageTarget, static_cast<uint32_t>(level),
- format, CastUvec3(offset), CastUvec3(isize), RawBuffer<>{*range},
+ format, CastUvec3(offset), CastUvec3(isize), *range,
static_cast<uint32_t>(pboImageSize), Maybe<uint64_t>());
return;
});
@@ -4603,8 +4584,8 @@ void ClientWebGLContext::CompressedTexImage(bool sub, uint8_t funcDims,
Run<RPROC(CompressedTexImage)>(
sub, imageTarget, static_cast<uint32_t>(level), format, CastUvec3(offset),
- CastUvec3(isize), RawBuffer<>(), static_cast<uint32_t>(pboImageSize),
- Some(*src.mPboOffset));
+ CastUvec3(isize), Span<const uint8_t>{},
+ static_cast<uint32_t>(pboImageSize), Some(*src.mPboOffset));
}
void ClientWebGLContext::CopyTexImage(uint8_t funcDims, GLenum imageTarget,
@@ -4876,9 +4857,8 @@ void ClientWebGLContext::UniformData(const GLenum funcElemType,
const auto begin =
reinterpret_cast<const webgl::UniformDataVal*>(bytes.begin().get()) +
elemOffset;
- const auto range = Range{begin, availCount};
- RunWithGCData<RPROC(UniformData)>(std::move(nogc), locId, transpose,
- RawBuffer{range});
+ const auto range = Span{begin, availCount};
+ RunWithGCData<RPROC(UniformData)>(std::move(nogc), locId, transpose, range);
}
// -
@@ -5095,7 +5075,7 @@ void ClientWebGLContext::ReadPixels(GLint x, GLint y, GLsizei width,
}
bool ClientWebGLContext::DoReadPixels(const webgl::ReadPixelsDesc& desc,
- const Range<uint8_t> dest) const {
+ const Span<uint8_t> dest) const {
const auto notLost =
mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
if (!notLost) return false;
@@ -5107,7 +5087,7 @@ bool ClientWebGLContext::DoReadPixels(const webgl::ReadPixelsDesc& desc,
const auto& child = notLost->outOfProcess;
child->FlushPendingCmds();
webgl::ReadPixelsResultIpc res = {};
- if (!child->SendReadPixels(desc, dest.length(), &res)) {
+ if (!child->SendReadPixels(desc, dest.size(), &res)) {
res = {};
}
if (!res.byteStride || !res.shmem) return false;
@@ -5119,7 +5099,7 @@ bool ClientWebGLContext::DoReadPixels(const webgl::ReadPixelsDesc& desc,
return false;
}
- const auto& shmemBytes = shmem.ByteRange();
+ const auto& shmemBytes = Span{shmem.ByteRange()};
const auto pii = webgl::PackingInfoInfo::For(desc.pi);
if (!pii) {
@@ -5136,19 +5116,13 @@ bool ClientWebGLContext::DoReadPixels(const webgl::ReadPixelsDesc& desc,
const auto xByteSize = bpp * static_cast<uint32_t>(subrect.width);
const ptrdiff_t byteOffset = packRect.y * byteStride + packRect.x * bpp;
- auto srcItr = shmemBytes.begin() + byteOffset;
- auto destItr = dest.begin() + byteOffset;
+ const auto srcSubrect = shmemBytes.subspan(byteOffset);
+ const auto destSubrect = dest.subspan(byteOffset);
for (const auto i : IntegerRange(subrect.height)) {
- if (i) {
- // Don't trigger an assert on the last loop by pushing a RangedPtr past
- // its bounds.
- srcItr += byteStride;
- destItr += byteStride;
- MOZ_RELEASE_ASSERT(srcItr + xByteSize <= shmemBytes.end());
- MOZ_RELEASE_ASSERT(destItr + xByteSize <= dest.end());
- }
- Memcpy(destItr, srcItr, xByteSize);
+ const auto srcRow = srcSubrect.subspan(i * byteStride, xByteSize);
+ const auto destRow = destSubrect.subspan(i * byteStride, xByteSize);
+ Memcpy(&destRow, srcRow);
}
return true;
@@ -5858,7 +5832,7 @@ void ClientWebGLContext::AttachShader(WebGLProgramJS& prog,
void ClientWebGLContext::BindAttribLocation(WebGLProgramJS& prog,
const GLuint location,
const nsAString& name) const {
- const FuncScope funcScope(*this, "detachShader");
+ const FuncScope funcScope(*this, "bindAttribLocation");
if (IsContextLost()) return;
if (!prog.ValidateUsable(*this, "program")) return;
@@ -6572,7 +6546,7 @@ const webgl::LinkResult& ClientWebGLContext::GetLinkResult(
// ---------------------------
-Maybe<Range<uint8_t>> ClientWebGLContext::ValidateArrayBufferView(
+Maybe<Span<uint8_t>> ClientWebGLContext::ValidateArrayBufferView(
const Span<uint8_t>& bytes, size_t elemSize, GLuint elemOffset,
GLuint elemCountOverride, const GLenum errorEnum) const {
size_t elemCount = bytes.Length() / elemSize;
@@ -6590,8 +6564,7 @@ Maybe<Range<uint8_t>> ClientWebGLContext::ValidateArrayBufferView(
elemCount = elemCountOverride;
}
- return Some(Range<uint8_t>(
- bytes.Subspan(elemOffset * elemSize, elemCount * elemSize)));
+ return Some(bytes.Subspan(elemOffset * elemSize, elemCount * elemSize));
}
// ---------------------------
diff --git a/dom/canvas/ClientWebGLContext.h b/dom/canvas/ClientWebGLContext.h
index 47e03d29c5..3c011a3027 100644
--- a/dom/canvas/ClientWebGLContext.h
+++ b/dom/canvas/ClientWebGLContext.h
@@ -929,11 +929,11 @@ class ClientWebGLContext final : public nsICanvasRenderingContextInternal,
void EnqueueErrorImpl(GLenum errorOrZero, const nsACString&) const;
public:
- Maybe<Range<uint8_t>> ValidateArrayBufferView(const Span<uint8_t>& bytes,
- size_t elemSize,
- GLuint elemOffset,
- GLuint elemCountOverride,
- const GLenum errorEnum) const;
+ Maybe<Span<uint8_t>> ValidateArrayBufferView(const Span<uint8_t>& bytes,
+ size_t elemSize,
+ GLuint elemOffset,
+ GLuint elemCountOverride,
+ const GLenum errorEnum) const;
protected:
template <typename T>
@@ -1087,7 +1087,7 @@ class ClientWebGLContext final : public nsICanvasRenderingContextInternal,
private:
RefPtr<gfx::DataSourceSurface> BackBufferSnapshot();
[[nodiscard]] bool DoReadPixels(const webgl::ReadPixelsDesc&,
- Range<uint8_t>) const;
+ Span<uint8_t>) const;
uvec2 DrawingBufferSize();
// -
diff --git a/dom/canvas/DmdStdContainers.h b/dom/canvas/DmdStdContainers.h
new file mode 100644
index 0000000000..2975abc580
--- /dev/null
+++ b/dom/canvas/DmdStdContainers.h
@@ -0,0 +1,100 @@
+/* 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 dom_canvas_DmdStdContainers_h
+#define dom_canvas_DmdStdContainers_h
+
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/layers/BuildConstants.h"
+#include <unordered_map>
+#include <unordered_set>
+
+namespace mozilla::webgl {
+
+// -
+
+template <class T>
+class dmd_allocator {
+ public:
+ using value_type = T;
+
+ private:
+ size_t mMallocSize = 0;
+
+ public:
+ dmd_allocator() = default;
+
+ // -
+
+ template <class U>
+ friend class dmd_allocator;
+
+ template <class U>
+ explicit dmd_allocator(const dmd_allocator<U>& rhs) {
+ if constexpr (kIsDmd) {
+ mMallocSize = rhs.mMallocSize;
+ }
+ }
+
+ // -
+
+ value_type* allocate(const size_t n) {
+ const auto p = std::allocator<value_type>{}.allocate(n);
+ if constexpr (kIsDmd) {
+ mMallocSize += moz_malloc_size_of(p);
+ }
+ return p;
+ }
+
+ void deallocate(value_type* const p, const size_t n) {
+ if constexpr (kIsDmd) {
+ mMallocSize -= moz_malloc_size_of(p);
+ }
+ std::allocator<value_type>{}.deallocate(p, n);
+ }
+
+ // -
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf) const {
+ return mMallocSize;
+ }
+};
+
+// -
+
+template <class Key, class T, class Hash = std::hash<Key>,
+ class KeyEqual = std::equal_to<Key>,
+ class Allocator = dmd_allocator<std::pair<const Key, T>>,
+ class _StdT = std::unordered_map<Key, T, Hash, KeyEqual, Allocator>>
+class dmd_unordered_map : public _StdT {
+ public:
+ using StdT = _StdT;
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf mso) const {
+ const auto& a = StdT::get_allocator();
+ return a.SizeOfExcludingThis(mso);
+ }
+};
+
+// -
+
+template <class Key, class Hash = std::hash<Key>,
+ class KeyEqual = std::equal_to<Key>,
+ class Allocator = dmd_allocator<Key>,
+ class _StdT = std::unordered_set<Key, Hash, KeyEqual, Allocator>>
+class dmd_unordered_set : public _StdT {
+ public:
+ using StdT = _StdT;
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf mso) const {
+ const auto& a = StdT::get_allocator();
+ return a.SizeOfExcludingThis(mso);
+ }
+};
+
+// -
+
+} // namespace mozilla::webgl
+
+#endif // dom_canvas_DmdStdContainers_h
diff --git a/dom/canvas/DrawTargetWebgl.cpp b/dom/canvas/DrawTargetWebgl.cpp
index 6055fa72e1..eae6dd78f3 100644
--- a/dom/canvas/DrawTargetWebgl.cpp
+++ b/dom/canvas/DrawTargetWebgl.cpp
@@ -19,8 +19,6 @@
#include "mozilla/gfx/PathHelpers.h"
#include "mozilla/gfx/PathSkia.h"
#include "mozilla/gfx/Swizzle.h"
-#include "mozilla/layers/CanvasRenderer.h"
-#include "mozilla/layers/ImageBridgeChild.h"
#include "mozilla/layers/ImageDataSerializer.h"
#include "mozilla/layers/RemoteTextureMap.h"
#include "skia/include/core/SkPixmap.h"
@@ -467,9 +465,13 @@ bool SharedContextWebgl::Initialize() {
const bool resistFingerprinting = nsContentUtils::ShouldResistFingerprinting(
"Fallback", RFPTarget::WebGLRenderCapability);
- const auto initDesc =
- webgl::InitContextDesc{/*isWebgl2*/ true, resistFingerprinting,
- /*size*/ {1, 1}, options, /*principalKey*/ 0};
+ const auto initDesc = webgl::InitContextDesc{
+ .isWebgl2 = true,
+ .resistFingerprinting = resistFingerprinting,
+ .principalKey = 0,
+ .size = {1, 1},
+ .options = options,
+ };
webgl::InitContextResult initResult;
mWebgl = WebGLContext::Create(nullptr, initDesc, &initResult);
@@ -621,13 +623,14 @@ bool SharedContextWebgl::SetNoClipMask() {
}
mWebgl->ActiveTexture(1);
mWebgl->BindTexture(LOCAL_GL_TEXTURE_2D, mNoClipMask);
- static const uint8_t solidMask[4] = {0xFF, 0xFF, 0xFF, 0xFF};
- mWebgl->TexImage(
- 0, LOCAL_GL_RGBA8, {0, 0, 0}, {LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE},
- {LOCAL_GL_TEXTURE_2D,
- {1, 1, 1},
- gfxAlphaType::NonPremult,
- Some(RawBuffer(Range<const uint8_t>(solidMask, sizeof(solidMask))))});
+ static const auto solidMask =
+ std::array<const uint8_t, 4>{0xFF, 0xFF, 0xFF, 0xFF};
+ mWebgl->TexImage(0, LOCAL_GL_RGBA8, {0, 0, 0},
+ {LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE},
+ {LOCAL_GL_TEXTURE_2D,
+ {1, 1, 1},
+ gfxAlphaType::NonPremult,
+ Some(Span{solidMask})});
InitTexParameters(mNoClipMask, false);
mWebgl->ActiveTexture(0);
mLastClipMask = mNoClipMask;
@@ -1916,11 +1919,11 @@ bool SharedContextWebgl::UploadSurface(DataSourceSurface* aData,
int32_t bpp = BytesPerPixel(aFormat);
// Get the data pointer range considering the sampling rect offset and
// size.
- Range<const uint8_t> range(
+ Span<const uint8_t> range(
map.GetData() + aSrcRect.y * size_t(stride) + aSrcRect.x * bpp,
std::max(aSrcRect.height - 1, 0) * size_t(stride) +
aSrcRect.width * bpp);
- texDesc.cpuData = Some(RawBuffer(range));
+ texDesc.cpuData = Some(range);
// If the stride happens to be 4 byte aligned, assume that is the
// desired alignment regardless of format (even A8). Otherwise, we
// default to byte alignment.
@@ -4724,7 +4727,8 @@ void DrawTargetWebgl::EndFrame() {
}
bool DrawTargetWebgl::CopyToSwapChain(
- layers::RemoteTextureId aId, layers::RemoteTextureOwnerId aOwnerId,
+ layers::TextureType aTextureType, layers::RemoteTextureId aId,
+ layers::RemoteTextureOwnerId aOwnerId,
layers::RemoteTextureOwnerClient* aOwnerClient) {
if (!mWebglValid && !FlushFromSkia()) {
return false;
@@ -4739,11 +4743,8 @@ bool DrawTargetWebgl::CopyToSwapChain(
StaticPrefs::gfx_canvas_accelerated_async_present();
options.remoteTextureId = aId;
options.remoteTextureOwnerId = aOwnerId;
- const RefPtr<layers::ImageBridgeChild> imageBridge =
- layers::ImageBridgeChild::GetSingleton();
- auto texType = layers::TexTypeForWebgl(imageBridge);
- return mSharedContext->mWebgl->CopyToSwapChain(mFramebuffer, texType, options,
- aOwnerClient);
+ return mSharedContext->mWebgl->CopyToSwapChain(mFramebuffer, aTextureType,
+ options, aOwnerClient);
}
already_AddRefed<DrawTarget> DrawTargetWebgl::CreateSimilarDrawTarget(
diff --git a/dom/canvas/DrawTargetWebgl.h b/dom/canvas/DrawTargetWebgl.h
index 7bc1a83abb..955ccaabf0 100644
--- a/dom/canvas/DrawTargetWebgl.h
+++ b/dom/canvas/DrawTargetWebgl.h
@@ -577,7 +577,8 @@ class DrawTargetWebgl : public DrawTarget, public SupportsWeakPtr {
void* GetNativeSurface(NativeSurfaceType aType) override;
bool CopyToSwapChain(
- layers::RemoteTextureId aId, layers::RemoteTextureOwnerId aOwnerId,
+ layers::TextureType aTextureType, layers::RemoteTextureId aId,
+ layers::RemoteTextureOwnerId aOwnerId,
layers::RemoteTextureOwnerClient* aOwnerClient = nullptr);
void OnMemoryPressure() { mSharedContext->OnMemoryPressure(); }
diff --git a/dom/canvas/HostWebGLContext.h b/dom/canvas/HostWebGLContext.h
index c508a164c8..cc385bc26a 100644
--- a/dom/canvas/HostWebGLContext.h
+++ b/dom/canvas/HostWebGLContext.h
@@ -482,8 +482,9 @@ class HostWebGLContext final : public SupportsWeakPtr {
return GetWebGL2Context()->GetBufferSubData(target, srcByteOffset, dest);
}
- void BufferData(GLenum target, const RawBuffer<>& srcData, GLenum usage) const {
- mContext->BufferData(target, srcData.size(), srcData.begin(), usage);
+ void BufferData(GLenum target, const Span<const uint8_t>& srcData,
+ GLenum usage) const {
+ mContext->BufferData(target, srcData.size(), srcData.data(), usage);
}
void BufferData_SizeOnly(GLenum target, size_t byteSize, GLenum usage) const {
@@ -491,11 +492,10 @@ class HostWebGLContext final : public SupportsWeakPtr {
}
void BufferSubData(GLenum target, uint64_t dstByteOffset,
- const RawBuffer<>& srcData,
+ const Span<const uint8_t>& srcData,
bool unsynchronized = false) const {
- const auto& range = srcData.Data();
- mContext->BufferSubData(target, dstByteOffset, range.length(),
- range.begin().get(), unsynchronized);
+ mContext->BufferSubData(target, dstByteOffset, srcData.size(),
+ srcData.data(), unsynchronized);
}
// -------------------------- Framebuffer Objects --------------------------
@@ -507,16 +507,15 @@ class HostWebGLContext final : public SupportsWeakPtr {
}
void InvalidateFramebuffer(GLenum target,
- const RawBuffer<const GLenum>& attachments) const {
- GetWebGL2Context()->InvalidateFramebuffer(target, MakeRange(attachments));
+ const Span<const GLenum>& attachments) const {
+ GetWebGL2Context()->InvalidateFramebuffer(target, attachments);
}
void InvalidateSubFramebuffer(GLenum target,
- const RawBuffer<const GLenum>& attachments,
- GLint x, GLint y, GLsizei width,
- GLsizei height) const {
- GetWebGL2Context()->InvalidateSubFramebuffer(target, MakeRange(attachments),
- x, y, width, height);
+ const Span<const GLenum>& attachments, GLint x,
+ GLint y, GLsizei width, GLsizei height) const {
+ GetWebGL2Context()->InvalidateSubFramebuffer(target, attachments, x, y,
+ width, height);
}
void ReadBuffer(GLenum mode) const { GetWebGL2Context()->ReadBuffer(mode); }
@@ -554,10 +553,11 @@ class HostWebGLContext final : public SupportsWeakPtr {
// CompressedTexSubImage if `sub`
void CompressedTexImage(bool sub, GLenum imageTarget, uint32_t level,
GLenum format, const uvec3& offset, const uvec3& size,
- const RawBuffer<>& src, const uint32_t pboImageSize,
+ const Span<const uint8_t>& src,
+ const uint32_t pboImageSize,
const Maybe<uint64_t>& pboOffset) const {
mContext->CompressedTexImage(sub, imageTarget, level, format, offset, size,
- MakeRange(src), pboImageSize, pboOffset);
+ src, pboImageSize, pboOffset);
}
// CopyTexSubImage if `!respecFormat`
@@ -603,8 +603,8 @@ class HostWebGLContext final : public SupportsWeakPtr {
// ------------------------ Uniforms and attributes ------------------------
void UniformData(uint32_t loc, bool transpose,
- const RawBuffer<webgl::UniformDataVal>& data) const {
- mContext->UniformData(loc, transpose, data.Data());
+ const Span<const webgl::UniformDataVal>& data) const {
+ mContext->UniformData(loc, transpose, data);
}
void VertexAttrib4T(GLuint index, const webgl::TypedQuad& data) const {
diff --git a/dom/canvas/ImageBitmap.cpp b/dom/canvas/ImageBitmap.cpp
index 65a9feaa56..28641a1c68 100644
--- a/dom/canvas/ImageBitmap.cpp
+++ b/dom/canvas/ImageBitmap.cpp
@@ -5,6 +5,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/ImageBitmap.h"
+#include "mozilla/AppShutdown.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/dom/BlobImpl.h"
#include "mozilla/dom/CanvasRenderingContext2D.h"
@@ -362,6 +363,7 @@ static DataSourceSurface* FlipYDataSourceSurface(DataSourceSurface* aSurface) {
return nullptr;
}
+ const int bpp = BytesPerPixel(aSurface->GetFormat());
const IntSize srcSize = aSurface->GetSize();
uint8_t* srcBufferPtr = srcMap.GetData();
const uint32_t stride = srcMap.GetStride();
@@ -372,7 +374,8 @@ static DataSourceSurface* FlipYDataSourceSurface(DataSourceSurface* aSurface) {
}
for (int i = 0; i < srcSize.height / 2; ++i) {
- std::swap_ranges(srcBufferPtr + stride * i, srcBufferPtr + stride * (i + 1),
+ std::swap_ranges(srcBufferPtr + stride * i,
+ srcBufferPtr + stride * i + srcSize.width * bpp,
srcBufferPtr + stride * (srcSize.height - 1 - i));
}
@@ -1018,7 +1021,6 @@ already_AddRefed<ImageBitmap> ImageBitmap::CreateImageBitmapInternal(
// handle alpha premultiplication if surface not of correct type
gfxAlphaType alphaType = aAlphaType;
- bool mustCopy = aMustCopy;
bool requiresPremultiply = false;
bool requiresUnpremultiply = false;
@@ -1027,29 +1029,26 @@ already_AddRefed<ImageBitmap> ImageBitmap::CreateImageBitmapInternal(
aOptions.mPremultiplyAlpha == PremultiplyAlpha::None) {
requiresUnpremultiply = true;
alphaType = gfxAlphaType::NonPremult;
- if (!aAllocatedImageData) {
- mustCopy = true;
- }
} else if (aAlphaType == gfxAlphaType::NonPremult &&
aOptions.mPremultiplyAlpha == PremultiplyAlpha::Premultiply) {
requiresPremultiply = true;
alphaType = gfxAlphaType::Premult;
- if (!aAllocatedImageData) {
- mustCopy = true;
- }
}
}
/*
- * if we don't own the data and need to create a new buffer to flip Y.
+ * if we don't own the data and need to modify the buffer.
* or
* we need to crop and flip, where crop must come first.
* or
- * Or the caller demands a copy (WebGL contexts).
+ * the caller demands a copy (WebGL contexts).
*/
- if ((aOptions.mImageOrientation == ImageOrientation::FlipY &&
- (!aAllocatedImageData || aCropRect.isSome())) ||
- mustCopy) {
+ bool willModify = aOptions.mImageOrientation == ImageOrientation::FlipY ||
+ requiresPremultiply || requiresUnpremultiply;
+ if ((willModify && !aAllocatedImageData) ||
+ (aOptions.mImageOrientation == ImageOrientation::FlipY &&
+ aCropRect.isSome()) ||
+ aMustCopy) {
dataSurface = surface->GetDataSurface();
dataSurface = CropAndCopyDataSourceSurface(dataSurface, cropRect);
@@ -1430,7 +1429,7 @@ already_AddRefed<ImageBitmap> ImageBitmap::CreateInternal(
return nullptr;
}
- bool needToReportMemoryAllocation = true;
+ bool needToReportMemoryAllocation = false;
return CreateImageBitmapInternal(aGlobal, surface, aCropRect, aOptions,
writeOnly, needToReportMemoryAllocation,
diff --git a/dom/canvas/ImageUtils.cpp b/dom/canvas/ImageUtils.cpp
index 485b87c162..432fa3355e 100644
--- a/dom/canvas/ImageUtils.cpp
+++ b/dom/canvas/ImageUtils.cpp
@@ -15,27 +15,27 @@ using namespace mozilla::gfx;
namespace mozilla::dom {
-static ImageBitmapFormat GetImageBitmapFormatFromSurfaceFromat(
+static Maybe<ImageBitmapFormat> GetImageBitmapFormatFromSurfaceFromat(
SurfaceFormat aSurfaceFormat) {
switch (aSurfaceFormat) {
case SurfaceFormat::B8G8R8A8:
case SurfaceFormat::B8G8R8X8:
- return ImageBitmapFormat::BGRA32;
+ return Some(ImageBitmapFormat::BGRA32);
case SurfaceFormat::R8G8B8A8:
case SurfaceFormat::R8G8B8X8:
- return ImageBitmapFormat::RGBA32;
+ return Some(ImageBitmapFormat::RGBA32);
case SurfaceFormat::R8G8B8:
- return ImageBitmapFormat::RGB24;
+ return Some(ImageBitmapFormat::RGB24);
case SurfaceFormat::B8G8R8:
- return ImageBitmapFormat::BGR24;
+ return Some(ImageBitmapFormat::BGR24);
case SurfaceFormat::HSV:
- return ImageBitmapFormat::HSV;
+ return Some(ImageBitmapFormat::HSV);
case SurfaceFormat::Lab:
- return ImageBitmapFormat::Lab;
+ return Some(ImageBitmapFormat::Lab);
case SurfaceFormat::Depth:
- return ImageBitmapFormat::DEPTH;
+ return Some(ImageBitmapFormat::DEPTH);
case SurfaceFormat::A8:
- return ImageBitmapFormat::GRAY8;
+ return Some(ImageBitmapFormat::GRAY8);
case SurfaceFormat::R5G6B5_UINT16:
case SurfaceFormat::YUV:
case SurfaceFormat::NV12:
@@ -43,11 +43,11 @@ static ImageBitmapFormat GetImageBitmapFormatFromSurfaceFromat(
case SurfaceFormat::P016:
case SurfaceFormat::UNKNOWN:
default:
- return ImageBitmapFormat::EndGuard_;
+ return Nothing();
}
}
-static ImageBitmapFormat GetImageBitmapFormatFromPlanarYCbCrData(
+static Maybe<ImageBitmapFormat> GetImageBitmapFormatFromPlanarYCbCrData(
layers::PlanarYCbCrData const* aData) {
MOZ_ASSERT(aData);
@@ -68,11 +68,11 @@ static ImageBitmapFormat GetImageBitmapFormatFromPlanarYCbCrData(
!CbInterval.Intersects(CrInterval)) { // Three planes.
switch (aData->mChromaSubsampling) {
case ChromaSubsampling::FULL:
- return ImageBitmapFormat::YUV444P;
+ return Some(ImageBitmapFormat::YUV444P);
case ChromaSubsampling::HALF_WIDTH:
- return ImageBitmapFormat::YUV422P;
+ return Some(ImageBitmapFormat::YUV422P);
case ChromaSubsampling::HALF_WIDTH_AND_HEIGHT:
- return ImageBitmapFormat::YUV420P;
+ return Some(ImageBitmapFormat::YUV420P);
default:
break;
}
@@ -83,14 +83,14 @@ static ImageBitmapFormat GetImageBitmapFormatFromPlanarYCbCrData(
// planes.
if (!YInterval.Intersects(CbInterval) &&
aData->mCbChannel == aData->mCrChannel - 1) { // Two planes.
- return ImageBitmapFormat::YUV420SP_NV12; // Y-Cb-Cr
+ return Some(ImageBitmapFormat::YUV420SP_NV12); // Y-Cb-Cr
} else if (!YInterval.Intersects(CrInterval) &&
aData->mCrChannel == aData->mCbChannel - 1) { // Two planes.
- return ImageBitmapFormat::YUV420SP_NV21; // Y-Cr-Cb
+ return Some(ImageBitmapFormat::YUV420SP_NV21); // Y-Cr-Cb
}
}
- return ImageBitmapFormat::EndGuard_;
+ return Nothing();
}
// ImageUtils::Impl implements the _generic_ algorithm which always readback
@@ -104,7 +104,7 @@ class ImageUtils::Impl {
virtual ~Impl() = default;
- virtual ImageBitmapFormat GetFormat() const {
+ virtual Maybe<ImageBitmapFormat> GetFormat() const {
return GetImageBitmapFormatFromSurfaceFromat(Surface()->GetFormat());
}
@@ -144,7 +144,7 @@ class YUVImpl final : public ImageUtils::Impl {
aImage->GetFormat() == ImageFormat::NV_IMAGE);
}
- ImageBitmapFormat GetFormat() const override {
+ Maybe<ImageBitmapFormat> GetFormat() const override {
return GetImageBitmapFormatFromPlanarYCbCrData(GetPlanarYCbCrData());
}
@@ -189,7 +189,7 @@ ImageUtils::~ImageUtils() {
}
}
-ImageBitmapFormat ImageUtils::GetFormat() const {
+Maybe<ImageBitmapFormat> ImageUtils::GetFormat() const {
MOZ_ASSERT(mImpl);
return mImpl->GetFormat();
}
diff --git a/dom/canvas/ImageUtils.h b/dom/canvas/ImageUtils.h
index 46b391515c..6b9cd9ca71 100644
--- a/dom/canvas/ImageUtils.h
+++ b/dom/canvas/ImageUtils.h
@@ -13,6 +13,9 @@
namespace mozilla {
+template <typename T>
+class Maybe;
+
namespace layers {
class Image;
}
@@ -51,7 +54,7 @@ class ImageUtils {
explicit ImageUtils(layers::Image* aImage);
~ImageUtils();
- ImageBitmapFormat GetFormat() const;
+ Maybe<ImageBitmapFormat> GetFormat() const;
uint32_t GetBufferLength() const;
diff --git a/dom/canvas/OffscreenCanvasDisplayHelper.cpp b/dom/canvas/OffscreenCanvasDisplayHelper.cpp
index b1e2ec1694..9e1cbb3e75 100644
--- a/dom/canvas/OffscreenCanvasDisplayHelper.cpp
+++ b/dom/canvas/OffscreenCanvasDisplayHelper.cpp
@@ -16,6 +16,7 @@
#include "mozilla/layers/PersistentBufferProvider.h"
#include "mozilla/layers/TextureClientSharedSurface.h"
#include "mozilla/layers/TextureWrapperImage.h"
+#include "mozilla/StaticPrefs_gfx.h"
#include "mozilla/SVGObserverUtils.h"
#include "nsICanvasRenderingContextInternal.h"
#include "nsRFPService.h"
@@ -37,11 +38,25 @@ void OffscreenCanvasDisplayHelper::DestroyElement() {
MOZ_ASSERT(NS_IsMainThread());
MutexAutoLock lock(mMutex);
+ if (mImageContainer) {
+ mImageContainer->ClearAllImages();
+ mImageContainer = nullptr;
+ }
+ mFrontBufferSurface = nullptr;
mCanvasElement = nullptr;
}
void OffscreenCanvasDisplayHelper::DestroyCanvas() {
+ if (auto* cm = gfx::CanvasManagerChild::Get()) {
+ cm->EndCanvasTransaction();
+ }
+
MutexAutoLock lock(mMutex);
+ if (mImageContainer) {
+ mImageContainer->ClearAllImages();
+ mImageContainer = nullptr;
+ }
+ mFrontBufferSurface = nullptr;
mOffscreenCanvas = nullptr;
mWorkerRef = nullptr;
}
@@ -140,6 +155,12 @@ bool OffscreenCanvasDisplayHelper::CommitFrameToCompositor(
nsICanvasRenderingContextInternal* aContext,
layers::TextureType aTextureType,
const Maybe<OffscreenCanvasDisplayData>& aData) {
+ auto endTransaction = MakeScopeExit([&]() {
+ if (auto* cm = gfx::CanvasManagerChild::Get()) {
+ cm->EndCanvasTransaction();
+ }
+ });
+
MutexAutoLock lock(mMutex);
gfx::SurfaceFormat format = gfx::SurfaceFormat::B8G8R8A8;
@@ -393,81 +414,110 @@ already_AddRefed<gfx::SourceSurface>
OffscreenCanvasDisplayHelper::GetSurfaceSnapshot() {
MOZ_ASSERT(NS_IsMainThread());
+ class SnapshotWorkerRunnable final : public MainThreadWorkerRunnable {
+ public:
+ SnapshotWorkerRunnable(WorkerPrivate* aWorkerPrivate,
+ OffscreenCanvasDisplayHelper* aDisplayHelper)
+ : MainThreadWorkerRunnable(aWorkerPrivate, "SnapshotWorkerRunnable"),
+ mMonitor("SnapshotWorkerRunnable::mMonitor"),
+ mDisplayHelper(aDisplayHelper) {}
+
+ bool WorkerRun(JSContext*, WorkerPrivate*) override {
+ // The OffscreenCanvas can only be freed on the worker thread, so we
+ // cannot be racing with an OffscreenCanvas::DestroyCanvas call and its
+ // destructor. We just need to make sure we don't call into
+ // OffscreenCanvas while holding the lock since it calls back into the
+ // OffscreenCanvasDisplayHelper.
+ RefPtr<OffscreenCanvas> canvas;
+ {
+ MutexAutoLock lock(mDisplayHelper->mMutex);
+ canvas = mDisplayHelper->mOffscreenCanvas;
+ }
+
+ // Now that we are on the correct thread, we can extract the snapshot. If
+ // it is a Skia surface, perform a copy to threading issues.
+ RefPtr<gfx::SourceSurface> surface;
+ if (canvas) {
+ if (auto* context = canvas->GetContext()) {
+ surface =
+ context->GetFrontBufferSnapshot(/* requireAlphaPremult */ false);
+ if (surface && surface->GetType() == gfx::SurfaceType::SKIA) {
+ surface = gfx::Factory::CopyDataSourceSurface(
+ static_cast<gfx::DataSourceSurface*>(surface.get()));
+ }
+ }
+ }
+
+ MonitorAutoLock lock(mMonitor);
+ mSurface = std::move(surface);
+ mComplete = true;
+ lock.NotifyAll();
+ return true;
+ }
+
+ already_AddRefed<gfx::SourceSurface> Wait(int32_t aTimeoutMs) {
+ MonitorAutoLock lock(mMonitor);
+
+ TimeDuration timeout = TimeDuration::FromMilliseconds(aTimeoutMs);
+ while (!mComplete) {
+ if (lock.Wait(timeout) == CVStatus::Timeout) {
+ return nullptr;
+ }
+ }
+
+ return mSurface.forget();
+ }
+
+ private:
+ Monitor mMonitor;
+ RefPtr<OffscreenCanvasDisplayHelper> mDisplayHelper;
+ RefPtr<gfx::SourceSurface> mSurface MOZ_GUARDED_BY(mMonitor);
+ bool mComplete MOZ_GUARDED_BY(mMonitor) = false;
+ };
+
bool hasAlpha;
bool isAlphaPremult;
gl::OriginPos originPos;
- Maybe<uint32_t> managerId;
- Maybe<int32_t> childId;
HTMLCanvasElement* canvasElement;
RefPtr<gfx::SourceSurface> surface;
- Maybe<layers::RemoteTextureOwnerId> ownerId;
+ RefPtr<SnapshotWorkerRunnable> workerRunnable;
{
MutexAutoLock lock(mMutex);
+#ifdef MOZ_WIDGET_ANDROID
+ // On Android, we cannot both display a GL context and read back the pixels.
+ if (mCanvasElement) {
+ return nullptr;
+ }
+#endif
+
hasAlpha = !mData.mIsOpaque;
isAlphaPremult = mData.mIsAlphaPremult;
originPos = mData.mOriginPos;
- ownerId = mData.mOwnerId;
- managerId = mContextManagerId;
- childId = mContextChildId;
canvasElement = mCanvasElement;
- surface = mFrontBufferSurface;
- }
-
- if (surface) {
- // We already have a copy of the front buffer in our process.
- return TransformSurface(surface, hasAlpha, isAlphaPremult, originPos);
- }
-
-#ifdef MOZ_WIDGET_ANDROID
- // On Android, we cannot both display a GL context and read back the pixels.
- if (canvasElement) {
- return nullptr;
- }
-#endif
-
- if (managerId && childId) {
- // We don't have a usable surface, and the context lives in the compositor
- // process.
- return gfx::CanvasManagerChild::Get()->GetSnapshot(
- managerId.value(), childId.value(), ownerId,
- hasAlpha ? gfx::SurfaceFormat::R8G8B8A8 : gfx::SurfaceFormat::R8G8B8X8,
- hasAlpha && !isAlphaPremult, originPos == gl::OriginPos::BottomLeft);
- }
-
- if (!canvasElement) {
- return nullptr;
- }
-
- // If we don't have any protocol IDs, or an existing surface, it is possible
- // it is a main thread OffscreenCanvas instance. If so, then the element's
- // OffscreenCanvas is not neutered and has access to the context. We can use
- // that to get the snapshot directly.
- const auto* offscreenCanvas = canvasElement->GetOffscreenCanvas();
- if (nsICanvasRenderingContextInternal* context =
- offscreenCanvas->GetContext()) {
- surface = context->GetFrontBufferSnapshot(/* requireAlphaPremult */ false);
- surface = TransformSurface(surface, hasAlpha, isAlphaPremult, originPos);
- if (surface) {
- return surface.forget();
+ if (mWorkerRef) {
+ workerRunnable =
+ MakeRefPtr<SnapshotWorkerRunnable>(mWorkerRef->Private(), this);
+ workerRunnable->Dispatch();
}
}
- // Finally, we can try peeking into the image container to see if we are able
- // to do a readback via the TextureClient.
- if (layers::ImageContainer* container = canvasElement->GetImageContainer()) {
- AutoTArray<layers::ImageContainer::OwningImage, 1> images;
- uint32_t generationCounter;
- container->GetCurrentImages(&images, &generationCounter);
- if (!images.IsEmpty()) {
- if (layers::Image* image = images.LastElement().mImage) {
- surface = image->GetAsSourceSurface();
- return TransformSurface(surface, hasAlpha, isAlphaPremult, originPos);
- }
+ if (workerRunnable) {
+ // We transferred to a DOM worker, so we need to do the readback on the
+ // owning thread and wait for the result.
+ surface = workerRunnable->Wait(
+ StaticPrefs::gfx_offscreencanvas_snapshot_timeout_ms());
+ } else if (canvasElement) {
+ // If we have a context, it is owned by the main thread.
+ const auto* offscreenCanvas = canvasElement->GetOffscreenCanvas();
+ if (nsICanvasRenderingContextInternal* context =
+ offscreenCanvas->GetContext()) {
+ surface =
+ context->GetFrontBufferSnapshot(/* requireAlphaPremult */ false);
}
}
- return nullptr;
+ return TransformSurface(surface, hasAlpha, isAlphaPremult, originPos);
}
already_AddRefed<layers::Image> OffscreenCanvasDisplayHelper::GetAsImage() {
diff --git a/dom/canvas/QueueParamTraits.h b/dom/canvas/QueueParamTraits.h
index fbedcdea0b..a67f5abf51 100644
--- a/dom/canvas/QueueParamTraits.h
+++ b/dom/canvas/QueueParamTraits.h
@@ -98,6 +98,10 @@ template <>
struct BytesAlwaysValidT<webgl::UniformDataVal> {
static constexpr bool value = true;
};
+template <>
+struct BytesAlwaysValidT<const webgl::UniformDataVal> {
+ static constexpr bool value = true;
+};
// -
@@ -350,13 +354,13 @@ struct EnumSerializer {
static bool Read(ConsumerView<U>& aConsumerView, ParamType* aResult) {
DataType value;
if (!aConsumerView.ReadParam(&value)) {
- CrashReporter::AnnotateCrashReport(
- CrashReporter::Annotation::IPCReadErrorReason, "Bad iter"_ns);
+ CrashReporter::RecordAnnotationCString(
+ CrashReporter::Annotation::IPCReadErrorReason, "Bad iter");
return false;
}
if (!EnumValidator::IsLegalValue(static_cast<DataType>(value))) {
- CrashReporter::AnnotateCrashReport(
- CrashReporter::Annotation::IPCReadErrorReason, "Illegal value"_ns);
+ CrashReporter::RecordAnnotationCString(
+ CrashReporter::Annotation::IPCReadErrorReason, "Illegal value");
return false;
}
diff --git a/dom/canvas/TexUnpackBlob.cpp b/dom/canvas/TexUnpackBlob.cpp
index 8598c36227..e13dc1c064 100644
--- a/dom/canvas/TexUnpackBlob.cpp
+++ b/dom/canvas/TexUnpackBlob.cpp
@@ -476,8 +476,7 @@ bool TexUnpackBytes::Validate(const WebGLContext* const webgl,
CheckedInt<size_t> availBytes = 0;
if (mDesc.cpuData) {
- const auto& range = mDesc.cpuData->Data();
- availBytes = range.length();
+ availBytes = mDesc.cpuData->size();
} else if (mDesc.pboOffset) {
const auto& pboOffset = *mDesc.pboOffset;
@@ -514,11 +513,7 @@ bool TexUnpackBytes::TexOrSubImage(bool isSubImage, bool needsRespec,
const uint8_t* uploadPtr = nullptr;
if (mDesc.cpuData) {
- const auto range = mDesc.cpuData->Data();
- uploadPtr = range.begin().get();
- if (!uploadPtr) {
- MOZ_ASSERT(!range.length());
- }
+ uploadPtr = mDesc.cpuData->data();
} else if (mDesc.pboOffset) {
uploadPtr = reinterpret_cast<const uint8_t*>(*mDesc.pboOffset);
}
@@ -673,11 +668,10 @@ bool TexUnpackImage::Validate(const WebGLContext* const webgl,
return ValidateUnpackPixels(webgl, pi, fullRows, *this);
}
-Maybe<std::string> BlitPreventReason(const int32_t level, const ivec3& offset,
- const GLenum internalFormat,
- const webgl::PackingInfo& pi,
- const TexUnpackBlobDesc& desc,
- const bool isRgb8Renderable) {
+Maybe<std::string> BlitPreventReason(
+ const int32_t level, const ivec3& offset, const GLenum internalFormat,
+ const webgl::PackingInfo& pi, const TexUnpackBlobDesc& desc,
+ const OptionalRenderableFormatBits optionalRenderableFormatBits) {
const auto& size = desc.size;
const auto& unpacking = desc.unpacking;
@@ -710,26 +704,71 @@ Maybe<std::string> BlitPreventReason(const int32_t level, const ivec3& offset,
const auto formatReason = [&]() -> const char* {
if (pi.type != LOCAL_GL_UNSIGNED_BYTE) {
- return "`type` must be `UNSIGNED_BYTE`";
+ return "`unpackType` must be `UNSIGNED_BYTE`";
}
- switch (internalFormat) {
+ switch (pi.format) {
case LOCAL_GL_RGBA:
- case LOCAL_GL_RGBA8:
- return nullptr;
+ return nullptr; // All internalFormats for unpackFormat=RGBA are
+ // renderable.
case LOCAL_GL_RGB:
+ break;
+
+ default:
+ return "`unpackFormat` must be `RGBA` or maybe `RGB`";
+ }
+
+ // -
+
+ struct {
+ OptionalRenderableFormatBits bits;
+ const char* errorMsg;
+ } required;
+
+ switch (internalFormat) {
+ case LOCAL_GL_RGB565:
+ return nullptr;
+ case LOCAL_GL_RGB:
case LOCAL_GL_RGB8:
- if (isRgb8Renderable) {
- return nullptr;
- }
+ required = {
+ OptionalRenderableFormatBits::RGB8,
+ "Unavailable, as blitting internalFormats RGB or RGB8 requires "
+ "that RGB8 must be a renderable format.",
+ };
+ break;
+ case LOCAL_GL_SRGB:
+ case LOCAL_GL_SRGB8:
+ required = {
+ OptionalRenderableFormatBits::SRGB8,
+ "Unavailable, as blitting internalFormats SRGB or SRGB8 requires "
+ "that SRGB8 must be a renderable format.",
+ };
+ break;
+ case 0:
+ // texSubImage, so internalFormat is unknown, and could be anything!
+ required = {
+ OptionalRenderableFormatBits::RGB8 |
+ OptionalRenderableFormatBits::SRGB8,
+ "Unavailable, as blitting texSubImage with unpackFormat=RGB "
+ "requires that RGB8 and SRGB8 must be renderable formats.",
+ };
break;
+ default:
+ gfxCriticalError()
+ << "Unexpected internalFormat for unpackFormat=RGB: 0x"
+ << gfx::hexa(internalFormat);
+ return "Unexpected internalFormat for unpackFormat=RGB";
}
- if (isRgb8Renderable) {
- return "effective format must be RGB8 or RGBA8";
- } else {
- return "effective format must be RGBA8";
+
+ const auto availableBits = optionalRenderableFormatBits;
+ if ((required.bits | availableBits) != availableBits) {
+ return required.errorMsg;
}
+
+ // -
+
+ return nullptr;
}();
if (formatReason) return formatReason;
@@ -761,7 +800,7 @@ bool TexUnpackImage::TexOrSubImage(bool isSubImage, bool needsRespec,
const auto reason =
BlitPreventReason(level, {xOffset, yOffset, zOffset}, dui->internalFormat,
- pi, mDesc, webgl->mIsRgb8Renderable);
+ pi, mDesc, webgl->mOptionalRenderableFormatBits);
if (reason) {
webgl->GeneratePerfWarning(
"Failed to hit GPU-copy fast-path."
diff --git a/dom/canvas/TexUnpackBlob.h b/dom/canvas/TexUnpackBlob.h
index 02cb8dec41..dc850fe5d4 100644
--- a/dom/canvas/TexUnpackBlob.h
+++ b/dom/canvas/TexUnpackBlob.h
@@ -45,7 +45,7 @@ Maybe<std::string> BlitPreventReason(int32_t level, const ivec3& offset,
GLenum internalFormat,
const webgl::PackingInfo&,
const TexUnpackBlobDesc&,
- bool isRgb8Renderable);
+ OptionalRenderableFormatBits);
class TexUnpackBlob {
public:
diff --git a/dom/canvas/WebGL2Context.h b/dom/canvas/WebGL2Context.h
index 138dd85b80..018c81f298 100644
--- a/dom/canvas/WebGL2Context.h
+++ b/dom/canvas/WebGL2Context.h
@@ -47,9 +47,9 @@ class WebGL2Context final : public WebGLContext {
GLbitfield mask, GLenum filter);
void InvalidateFramebuffer(GLenum target,
- const Range<const GLenum>& attachments);
+ const Span<const GLenum>& attachments);
void InvalidateSubFramebuffer(GLenum target,
- const Range<const GLenum>& attachments, GLint x,
+ const Span<const GLenum>& attachments, GLint x,
GLint y, GLsizei width, GLsizei height);
void ReadBuffer(GLenum mode);
diff --git a/dom/canvas/WebGL2ContextFramebuffers.cpp b/dom/canvas/WebGL2ContextFramebuffers.cpp
index 7056c83d03..ab9e848f62 100644
--- a/dom/canvas/WebGL2ContextFramebuffers.cpp
+++ b/dom/canvas/WebGL2ContextFramebuffers.cpp
@@ -106,7 +106,7 @@ static bool ValidateFramebufferAttachmentEnum(WebGLContext* webgl,
}
bool WebGLContext::ValidateInvalidateFramebuffer(
- GLenum target, const Range<const GLenum>& attachments,
+ GLenum target, const Span<const GLenum>& attachments,
std::vector<GLenum>* const scopedVector,
GLsizei* const out_glNumAttachments,
const GLenum** const out_glAttachments) {
@@ -139,8 +139,8 @@ bool WebGLContext::ValidateInvalidateFramebuffer(
}
DoBindFB(fb, target);
- *out_glNumAttachments = attachments.length();
- *out_glAttachments = attachments.begin().get();
+ *out_glNumAttachments = AutoAssertCast(attachments.size());
+ *out_glAttachments = attachments.data();
if (fb) {
for (const auto& attachment : attachments) {
@@ -153,7 +153,7 @@ bool WebGLContext::ValidateInvalidateFramebuffer(
if (!isDefaultFB) {
MOZ_ASSERT(scopedVector->empty());
- scopedVector->reserve(attachments.length());
+ scopedVector->reserve(attachments.size());
for (const auto& attachment : attachments) {
switch (attachment) {
case LOCAL_GL_COLOR:
@@ -172,7 +172,7 @@ bool WebGLContext::ValidateInvalidateFramebuffer(
MOZ_CRASH();
}
}
- *out_glNumAttachments = scopedVector->size();
+ *out_glNumAttachments = AutoAssertCast(scopedVector->size());
*out_glAttachments = scopedVector->data();
}
}
@@ -183,7 +183,7 @@ bool WebGLContext::ValidateInvalidateFramebuffer(
}
void WebGL2Context::InvalidateFramebuffer(
- GLenum target, const Range<const GLenum>& attachments) {
+ GLenum target, const Span<const GLenum>& attachments) {
const FuncScope funcScope(*this, "invalidateFramebuffer");
std::vector<GLenum> scopedVector;
@@ -210,7 +210,7 @@ void WebGL2Context::InvalidateFramebuffer(
}
void WebGL2Context::InvalidateSubFramebuffer(
- GLenum target, const Range<const GLenum>& attachments, GLint x, GLint y,
+ GLenum target, const Span<const GLenum>& attachments, GLint x, GLint y,
GLsizei width, GLsizei height) {
const FuncScope funcScope(*this, "invalidateSubFramebuffer");
diff --git a/dom/canvas/WebGLChild.cpp b/dom/canvas/WebGLChild.cpp
index cf676c2f53..ebfe35db88 100644
--- a/dom/canvas/WebGLChild.cpp
+++ b/dom/canvas/WebGLChild.cpp
@@ -87,10 +87,8 @@ void WebGLChild::FlushPendingCmds() {
// Handle flushesSinceLastCongestionCheck
mFlushedCmdInfo.flushesSinceLastCongestionCheck += 1;
- const auto startCongestionCheck = 20;
- const auto maybeIPCMessageCongestion = 70;
- const auto eventTarget = GetCurrentSerialEventTarget();
- MOZ_ASSERT(eventTarget);
+ constexpr auto START_CONGESTION_CHECK_THRESHOLD = 20;
+ constexpr auto ASSUME_IPC_CONGESTION_THRESHOLD = 70;
RefPtr<WebGLChild> self = this;
size_t generation = self->mFlushedCmdInfo.congestionCheckGeneration;
@@ -107,17 +105,24 @@ void WebGLChild::FlushPendingCmds() {
// messages.
// Due to the async nature of the async ping, it is possible for the flush
// check to exceed maybeIPCMessageCongestion, but that it it still bounded.
- if (mFlushedCmdInfo.flushesSinceLastCongestionCheck == startCongestionCheck) {
- SendPing()->Then(eventTarget, __func__, [self, generation]() {
- if (generation == self->mFlushedCmdInfo.congestionCheckGeneration) {
- // Confirmed IPC messages congestion does not happen.
- // Reset flushesSinceLastCongestionCheck for next congestion check.
- self->mFlushedCmdInfo.flushesSinceLastCongestionCheck = 0;
- self->mFlushedCmdInfo.congestionCheckGeneration++;
- }
- });
+ if (mFlushedCmdInfo.flushesSinceLastCongestionCheck ==
+ START_CONGESTION_CHECK_THRESHOLD) {
+ const auto eventTarget = RefPtr{GetCurrentSerialEventTarget()};
+ MOZ_ASSERT(eventTarget);
+ if (!eventTarget) {
+ NS_WARNING("GetCurrentSerialEventTarget()->nullptr in FlushPendingCmds.");
+ } else {
+ SendPing()->Then(eventTarget, __func__, [self, generation]() {
+ if (generation == self->mFlushedCmdInfo.congestionCheckGeneration) {
+ // Confirmed IPC messages congestion does not happen.
+ // Reset flushesSinceLastCongestionCheck for next congestion check.
+ self->mFlushedCmdInfo.flushesSinceLastCongestionCheck = 0;
+ self->mFlushedCmdInfo.congestionCheckGeneration++;
+ }
+ });
+ }
} else if (mFlushedCmdInfo.flushesSinceLastCongestionCheck >
- maybeIPCMessageCongestion) {
+ ASSUME_IPC_CONGESTION_THRESHOLD) {
// IPC messages congestion might happen, send sync SyncPing for flushing
// pending messages.
SendSyncPing();
diff --git a/dom/canvas/WebGLContext.cpp b/dom/canvas/WebGLContext.cpp
index c1e6cbbed2..ec19bfef3d 100644
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -653,7 +653,7 @@ RefPtr<WebGLContext> WebGLContext::Create(HostWebGLContext* host,
out->limits = *webgl->mLimits;
out->uploadableSdTypes = UploadableSdTypes();
out->vendor = webgl->gl->Vendor();
- out->isRgb8Renderable = webgl->mIsRgb8Renderable;
+ out->optionalRenderableFormatBits = webgl->mOptionalRenderableFormatBits;
return webgl;
}
@@ -710,15 +710,36 @@ void WebGLContext::FinishInit() {
const auto tex = gl::ScopedTexture(gl);
const auto fb = gl::ScopedFramebuffer(gl);
gl->fBindTexture(LOCAL_GL_TEXTURE_2D, tex);
- gl->fTexImage2D(LOCAL_GL_TEXTURE_2D, 0, LOCAL_GL_RGB, 1, 1, 0, LOCAL_GL_RGB,
- LOCAL_GL_UNSIGNED_BYTE, nullptr);
-
gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, fb);
gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0,
LOCAL_GL_TEXTURE_2D, tex, 0);
- const auto status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
- mIsRgb8Renderable = (status == LOCAL_GL_FRAMEBUFFER_COMPLETE);
+ const auto IsRenderable = [&](const GLint internalFormat,
+ const GLenum unpackFormat) {
+ gl->fTexImage2D(LOCAL_GL_TEXTURE_2D, 0, internalFormat, 1, 1, 0,
+ unpackFormat, LOCAL_GL_UNSIGNED_BYTE, nullptr);
+ const auto status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
+ return (status == LOCAL_GL_FRAMEBUFFER_COMPLETE);
+ };
+
+ if (IsRenderable(LOCAL_GL_RGB, LOCAL_GL_RGB)) {
+ mOptionalRenderableFormatBits |=
+ webgl::OptionalRenderableFormatBits::RGB8;
+ }
+ if (gl->IsSupported(gl::GLFeature::sRGB)) {
+ struct {
+ GLint internal;
+ GLenum unpack;
+ } formats = {LOCAL_GL_SRGB8, LOCAL_GL_RGB};
+ const bool isEs2 = (gl->IsGLES() && gl->Version() < 300);
+ if (isEs2) {
+ formats = {LOCAL_GL_SRGB, LOCAL_GL_SRGB};
+ }
+ if (IsRenderable(formats.internal, formats.unpack)) {
+ mOptionalRenderableFormatBits |=
+ webgl::OptionalRenderableFormatBits::SRGB8;
+ }
+ }
}
//////
diff --git a/dom/canvas/WebGLContext.h b/dom/canvas/WebGLContext.h
index 12727c4969..5ab584174c 100644
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -317,7 +317,8 @@ class WebGLContext : public VRefCounted, public SupportsWeakPtr {
webgl::InitContextResult* out);
private:
- bool mIsRgb8Renderable = false;
+ webgl::OptionalRenderableFormatBits mOptionalRenderableFormatBits =
+ webgl::OptionalRenderableFormatBits{0};
void FinishInit();
protected:
@@ -667,7 +668,7 @@ class WebGLContext : public VRefCounted, public SupportsWeakPtr {
//////////////////////////
void UniformData(uint32_t loc, bool transpose,
- const Range<const webgl::UniformDataVal>& data) const;
+ const Span<const webgl::UniformDataVal>& data) const;
////////////////////////////////////
@@ -968,8 +969,8 @@ class WebGLContext : public VRefCounted, public SupportsWeakPtr {
// -------------------------------------------------------------------------
// WebGL extensions (implemented in WebGLContextExtensions.cpp)
- EnumeratedArray<WebGLExtensionID, WebGLExtensionID::Max,
- std::unique_ptr<WebGLExtensionBase>>
+ EnumeratedArray<WebGLExtensionID, std::unique_ptr<WebGLExtensionBase>,
+ size_t(WebGLExtensionID::Max)>
mExtensions;
public:
@@ -1156,7 +1157,7 @@ class WebGLContext : public VRefCounted, public SupportsWeakPtr {
bool ValidateFramebufferTarget(GLenum target) const;
bool ValidateInvalidateFramebuffer(GLenum target,
- const Range<const GLenum>& attachments,
+ const Span<const GLenum>& attachments,
std::vector<GLenum>* const scopedVector,
GLsizei* const out_glNumAttachments,
const GLenum** const out_glAttachments);
diff --git a/dom/canvas/WebGLContextGL.cpp b/dom/canvas/WebGLContextGL.cpp
index 7411cec02f..ef068c8999 100644
--- a/dom/canvas/WebGLContextGL.cpp
+++ b/dom/canvas/WebGLContextGL.cpp
@@ -1280,7 +1280,7 @@ void WebGLContext::StencilOpSeparate(GLenum face, GLenum sfail, GLenum dpfail,
void WebGLContext::UniformData(
const uint32_t loc, const bool transpose,
- const Range<const webgl::UniformDataVal>& data) const {
+ const Span<const webgl::UniformDataVal>& data) const {
const FuncScope funcScope(*this, "uniform setter");
if (!IsWebGL2() && transpose) {
@@ -1306,13 +1306,13 @@ void WebGLContext::UniformData(
// -
- const auto lengthInType = data.length();
+ const auto lengthInType = data.size();
const auto elemCount = lengthInType / channels;
if (elemCount > 1 && !validationInfo.isArray) {
GenerateError(
LOCAL_GL_INVALID_OPERATION,
"(uniform %s) `values` length (%u) must exactly match size of %s.",
- activeInfo.name.c_str(), lengthInType,
+ activeInfo.name.c_str(), (uint32_t)lengthInType,
EnumString(activeInfo.elemType).c_str());
return;
}
@@ -1321,9 +1321,9 @@ void WebGLContext::UniformData(
const auto& samplerInfo = locInfo->samplerInfo;
if (samplerInfo) {
- const auto idata = reinterpret_cast<const uint32_t*>(data.begin().get());
+ const auto idata = ReinterpretToSpan<const uint32_t>::From(data);
const auto maxTexUnits = GLMaxTextureUnits();
- for (const auto& val : Range<const uint32_t>(idata, elemCount)) {
+ for (const auto& val : idata) {
if (val >= maxTexUnits) {
ErrorInvalidValue(
"This uniform location is a sampler, but %d"
@@ -1337,7 +1337,7 @@ void WebGLContext::UniformData(
// -
// This is a little galaxy-brain, sorry!
- const auto ptr = static_cast<const void*>(data.begin().get());
+ const auto ptr = static_cast<const void*>(data.data());
(*pfn)(*gl, static_cast<GLint>(loc), elemCount, transpose, ptr);
// -
@@ -1345,12 +1345,12 @@ void WebGLContext::UniformData(
if (samplerInfo) {
auto& texUnits = samplerInfo->texUnits;
- const auto srcBegin = reinterpret_cast<const uint32_t*>(data.begin().get());
+ const auto srcBegin = reinterpret_cast<const uint32_t*>(data.data());
auto destIndex = locInfo->indexIntoUniform;
if (destIndex < texUnits.length()) {
// Only sample as many indexes as available tex units allow.
const auto destCount = std::min(elemCount, texUnits.length() - destIndex);
- for (const auto& val : Range<const uint32_t>(srcBegin, destCount)) {
+ for (const auto& val : Span<const uint32_t>(srcBegin, destCount)) {
texUnits[destIndex] = AssertedCast<uint8_t>(val);
destIndex += 1;
}
diff --git a/dom/canvas/WebGLIpdl.h b/dom/canvas/WebGLIpdl.h
index 748c1a30a6..5a35c05909 100644
--- a/dom/canvas/WebGLIpdl.h
+++ b/dom/canvas/WebGLIpdl.h
@@ -10,6 +10,7 @@
#include "ipc/EnumSerializer.h"
#include "ipc/IPCMessageUtils.h"
#include "mozilla/GfxMessageUtils.h"
+#include "mozilla/dom/BindingIPCUtils.h"
#include "mozilla/ipc/IPDLParamTraits.h"
#include "mozilla/ipc/Shmem.h"
#include "mozilla/layers/LayersSurfaces.h"
@@ -233,6 +234,16 @@ struct ParamTraits<gfxAlphaType>
: public ContiguousEnumSerializerInclusive<
gfxAlphaType, gfxAlphaType::Opaque, gfxAlphaType::NonPremult> {};
+template <>
+struct ParamTraits<mozilla::dom::WebGLPowerPreference> final
+ : public mozilla::dom::WebIDLEnumSerializer<
+ mozilla::dom::WebGLPowerPreference> {};
+
+template <>
+struct ParamTraits<mozilla::dom::PredefinedColorSpace> final
+ : public mozilla::dom::WebIDLEnumSerializer<
+ mozilla::dom::PredefinedColorSpace> {};
+
// -
// ParamTraits_TiedFields
@@ -242,7 +253,7 @@ struct ParamTraits_TiedFields {
static void Write(MessageWriter* const writer, const T& in) {
const auto& fields = mozilla::TiedFields(in);
- MapTuple(fields, [&](const auto& field) {
+ mozilla::MapTuple(fields, [&](const auto& field) {
WriteParam(writer, field);
return true; // ignored
});
@@ -251,7 +262,7 @@ struct ParamTraits_TiedFields {
static bool Read(MessageReader* const reader, T* const out) {
const auto& fields = mozilla::TiedFields(*out);
bool ok = true;
- MapTuple(fields, [&](auto& field) {
+ mozilla::MapTuple(fields, [&](auto& field) {
if (ok) {
ok &= ReadParam(reader, &field);
}
@@ -263,72 +274,17 @@ struct ParamTraits_TiedFields {
// -
-template <typename T>
-bool ValidateParam(const T& val) {
- return ParamTraits<T>::Validate(val);
-}
-
-template <typename T>
-struct ValidatedPlainOldDataSerializer : public PlainOldDataSerializer<T> {
- static void Write(MessageWriter* const writer, const T& in) {
- MOZ_ASSERT(ValidateParam(in));
- PlainOldDataSerializer<T>::Write(writer, in);
- }
-
- static bool Read(MessageReader* const reader, T* const out) {
- if (!PlainOldDataSerializer<T>::Read(reader, out)) return false;
- return ValidateParam(*out);
- }
-
- // static bool Validate(const T&) = 0;
-};
-
-// -
-
template <>
struct ParamTraits<mozilla::webgl::InitContextDesc> final
- : public ValidatedPlainOldDataSerializer<mozilla::webgl::InitContextDesc> {
- using T = mozilla::webgl::InitContextDesc;
-
- static bool Validate(const T& val) {
- return ValidateParam(val.options) && (val.size.x && val.size.y);
- }
-};
+ : public ParamTraits_TiedFields<mozilla::webgl::InitContextDesc> {};
template <>
struct ParamTraits<mozilla::WebGLContextOptions> final
- : public ValidatedPlainOldDataSerializer<mozilla::WebGLContextOptions> {
- using T = mozilla::WebGLContextOptions;
-
- static bool Validate(const T& val) {
- bool ok = true;
- ok &= ValidateParam(val.powerPreference);
- ok &= ValidateParam(val.colorSpace);
- return ok;
- }
-};
-
-template <>
-struct ParamTraits<mozilla::dom::WebGLPowerPreference> final
- : public ValidatedPlainOldDataSerializer<
- mozilla::dom::WebGLPowerPreference> {
- using T = mozilla::dom::WebGLPowerPreference;
-
- static bool Validate(const T& val) { return val <= T::High_performance; }
-};
-
-template <>
-struct ParamTraits<mozilla::dom::PredefinedColorSpace> final
- : public ValidatedPlainOldDataSerializer<
- mozilla::dom::PredefinedColorSpace> {
- using T = mozilla::dom::PredefinedColorSpace;
-
- static bool Validate(const T& val) { return val < T::EndGuard_; }
-};
+ : public ParamTraits_TiedFields<mozilla::WebGLContextOptions> {};
template <>
struct ParamTraits<mozilla::webgl::OpaqueFramebufferOptions> final
- : public PlainOldDataSerializer<mozilla::webgl::OpaqueFramebufferOptions> {
+ : public ParamTraits_TiedFields<mozilla::webgl::OpaqueFramebufferOptions> {
};
// -
@@ -340,28 +296,24 @@ struct ParamTraits<mozilla::gl::GLVendor>
mozilla::gl::kHighestGLVendor> {
};
-template <typename T>
-struct ParamTraits<mozilla::webgl::EnumMask<T>> final
- : public PlainOldDataSerializer<mozilla::webgl::EnumMask<T>> {};
+template <typename U>
+struct ParamTraits<mozilla::webgl::EnumMask<U>> final
+ : public ParamTraits_TiedFields<mozilla::webgl::EnumMask<U>> {};
template <>
struct ParamTraits<mozilla::webgl::InitContextResult> final
: public ParamTraits_TiedFields<mozilla::webgl::InitContextResult> {};
template <>
-struct ParamTraits<mozilla::webgl::ExtensionBits> final
- : public PlainOldDataSerializer<mozilla::webgl::ExtensionBits> {};
-
-template <>
struct ParamTraits<mozilla::webgl::Limits> final
- : public PlainOldDataSerializer<mozilla::webgl::Limits> {};
+ : public ParamTraits_TiedFields<mozilla::webgl::Limits> {};
template <>
struct ParamTraits<mozilla::webgl::PixelPackingState> final
- : public PlainOldDataSerializer<mozilla::webgl::PixelPackingState> {};
+ : public ParamTraits_TiedFields<mozilla::webgl::PixelPackingState> {};
template <>
struct ParamTraits<mozilla::webgl::PixelUnpackStateWebgl> final
- : public PlainOldDataSerializer<mozilla::webgl::PixelUnpackStateWebgl> {};
+ : public ParamTraits_TiedFields<mozilla::webgl::PixelUnpackStateWebgl> {};
// -
@@ -570,6 +522,24 @@ struct ParamTraits<mozilla::webgl::ShaderPrecisionFormat> final {
// -
template <typename U, size_t N>
+struct ParamTraits<std::array<U, N>> final {
+ using T = std::array<U, N>;
+
+ static void Write(MessageWriter* const writer, const T& in) {
+ for (const auto& v : in) {
+ WriteParam(writer, v);
+ }
+ }
+
+ static bool Read(MessageReader* const reader, T* const out) {
+ for (auto& v : *out) {
+ if (!ReadParam(reader, &v)) return false;
+ }
+ return true;
+ }
+};
+
+template <typename U, size_t N>
struct ParamTraits<U[N]> final {
using T = U[N];
static constexpr size_t kByteSize = sizeof(U) * N;
@@ -639,6 +609,35 @@ struct ParamTraits<mozilla::avec3<U>> final {
}
};
+// -
+
+template <class TT>
+struct ParamTraits_IsEnumCase {
+ using T = TT;
+
+ static void Write(IPC::MessageWriter* const writer, const T& in) {
+ MOZ_RELEASE_ASSERT(IsEnumCase(in));
+ WriteParam(writer, mozilla::UnderlyingValue(in));
+ }
+
+ static bool Read(IPC::MessageReader* const reader, T* const out) {
+ std::underlying_type_t<T> rawVal;
+ if (!ReadParam(reader, &rawVal)) return false;
+ *out = static_cast<T>(rawVal);
+ return IsEnumCase(*out);
+ }
+};
+
+// -
+
+#define USE_IS_ENUM_CASE(T) \
+ template <> \
+ struct ParamTraits<T> : public ParamTraits_IsEnumCase<T> {};
+
+USE_IS_ENUM_CASE(mozilla::webgl::OptionalRenderableFormatBits)
+
+#undef USE_IS_ENUM_CASE
+
} // namespace IPC
#endif
diff --git a/dom/canvas/WebGLMemoryTracker.cpp b/dom/canvas/WebGLMemoryTracker.cpp
index 361d1be695..19d3847acc 100644
--- a/dom/canvas/WebGLMemoryTracker.cpp
+++ b/dom/canvas/WebGLMemoryTracker.cpp
@@ -12,8 +12,9 @@
#include "WebGLTexture.h"
namespace mozilla {
-
-MOZ_DEFINE_MALLOC_SIZE_OF(WebGLShaderMallocSizeOf)
+namespace webgl {
+MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
+} // namespace webgl
void WebGLMemoryTracker::EnsureRegistered() {
static bool sIsRegistered = []() {
@@ -42,6 +43,7 @@ WebGLMemoryTracker::CollectReports(nsIHandleReportCallback* aHandleReport,
int64_t shaderCpuSize = 0;
size_t texCount = 0;
+ size_t texHeapOverhead = 0;
int64_t texGpuSize = 0;
for (const auto& context : contexts) {
@@ -70,7 +72,7 @@ WebGLMemoryTracker::CollectReports(nsIHandleReportCallback* aHandleReport,
shaderCount += context->mShaderMap.size();
for (const auto& pair : context->mShaderMap) {
const auto& shader = pair.second;
- shaderCpuSize += shader->SizeOfIncludingThis(WebGLShaderMallocSizeOf);
+ shaderCpuSize += shader->SizeOfIncludingThis(webgl::MallocSizeOf);
}
// -
@@ -79,6 +81,7 @@ WebGLMemoryTracker::CollectReports(nsIHandleReportCallback* aHandleReport,
for (const auto& pair : context->mTextureMap) {
const auto& texture = pair.second;
texGpuSize += texture->MemoryUsage();
+ texHeapOverhead += texture->SizeOfIncludingThis(webgl::MallocSizeOf);
}
}
@@ -126,10 +129,14 @@ WebGLMemoryTracker::CollectReports(nsIHandleReportCallback* aHandleReport,
"Number of WebGL renderbuffers.");
MOZ_COLLECT_REPORT(
- "explicit/webgl/shader", KIND_HEAP, UNITS_BYTES, shaderCpuSize,
+ "explicit/webgl/shaders", KIND_HEAP, UNITS_BYTES, shaderCpuSize,
"Combined size of WebGL shader ASCII sources and translation logs "
"cached on the heap.");
+ MOZ_COLLECT_REPORT("explicit/webgl/textures", KIND_HEAP, UNITS_BYTES,
+ texHeapOverhead,
+ "WebGLTexture implementation detail overhead.");
+
MOZ_COLLECT_REPORT("webgl-shader-count", KIND_OTHER, UNITS_COUNT,
static_cast<int64_t>(shaderCount),
"Number of WebGL shaders.");
diff --git a/dom/canvas/WebGLQueueParamTraits.h b/dom/canvas/WebGLQueueParamTraits.h
index db3ef0a44b..3c74f08750 100644
--- a/dom/canvas/WebGLQueueParamTraits.h
+++ b/dom/canvas/WebGLQueueParamTraits.h
@@ -67,8 +67,6 @@ inline constexpr bool IsEnumCase(const dom::WebGLPowerPreference raw) {
case dom::WebGLPowerPreference::Low_power:
case dom::WebGLPowerPreference::High_performance:
return true;
- case dom::WebGLPowerPreference::EndGuard_:
- break;
}
return false;
}
@@ -78,8 +76,6 @@ inline constexpr bool IsEnumCase(const dom::PredefinedColorSpace raw) {
case dom::PredefinedColorSpace::Srgb:
case dom::PredefinedColorSpace::Display_p3:
return true;
- case dom::PredefinedColorSpace::EndGuard_:
- break;
}
return false;
}
@@ -114,23 +110,21 @@ USE_IS_ENUM_CASE(webgl::ProvokingVertex)
// Custom QueueParamTraits
template <typename T>
-struct QueueParamTraits<RawBuffer<T>> {
- using ParamType = RawBuffer<T>;
-
+struct QueueParamTraits<Span<T>> {
template <typename U>
- static bool Write(ProducerView<U>& view, const ParamType& in) {
+ static bool Write(ProducerView<U>& view, const Span<T>& in) {
const auto& elemCount = in.size();
auto status = view.WriteParam(elemCount);
if (!status) return status;
if (!elemCount) return status;
- status = view.WriteFromRange(in.Data());
+ status = view.WriteFromRange(Range<const T>{in});
return status;
}
template <typename U>
- static bool Read(ConsumerView<U>& view, ParamType* const out) {
+ static bool Read(ConsumerView<U>& view, Span<const T>* const out) {
size_t elemCount = 0;
auto status = view.ReadParam(&elemCount);
if (!status) return status;
@@ -140,9 +134,9 @@ struct QueueParamTraits<RawBuffer<T>> {
return true;
}
- auto data = view.template ReadRange<T>(elemCount);
+ auto data = view.template ReadRange<const T>(elemCount);
if (!data) return false;
- *out = std::move(RawBuffer<T>{*data});
+ *out = Span{*data};
return true;
}
};
diff --git a/dom/canvas/WebGLShaderValidator.h b/dom/canvas/WebGLShaderValidator.h
index ab05528f19..7fc700937f 100644
--- a/dom/canvas/WebGLShaderValidator.h
+++ b/dom/canvas/WebGLShaderValidator.h
@@ -39,7 +39,7 @@ class ShaderValidatorResults final {
bool CanLinkTo(const ShaderValidatorResults& vert,
nsCString* const out_log) const;
- size_t SizeOfIncludingThis(MallocSizeOf) const;
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf) const;
};
class ShaderValidator final {
diff --git a/dom/canvas/WebGLTexture.h b/dom/canvas/WebGLTexture.h
index 8a451a9277..7d75bf50f6 100644
--- a/dom/canvas/WebGLTexture.h
+++ b/dom/canvas/WebGLTexture.h
@@ -203,6 +203,14 @@ class WebGLTexture final : public WebGLContextBoundObject,
~WebGLTexture() override;
public:
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf mso) const {
+ return CacheInvalidator::SizeOfExcludingThis(mso) +
+ mSamplingCache.SizeOfExcludingThis(mso);
+ }
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf mso) const {
+ return mso(this) + SizeOfExcludingThis(mso);
+ }
+
////////////////////////////////////
// GL calls
bool BindTexture(TexTarget texTarget);
diff --git a/dom/canvas/WebGLTextureUpload.cpp b/dom/canvas/WebGLTextureUpload.cpp
index 6686c34178..1a439ca5bc 100644
--- a/dom/canvas/WebGLTextureUpload.cpp
+++ b/dom/canvas/WebGLTextureUpload.cpp
@@ -60,6 +60,10 @@ Maybe<TexUnpackBlobDesc> FromImageBitmap(const GLenum target, Maybe<uvec3> size,
}
const RefPtr<gfx::DataSourceSurface> surf = cloneData->mSurface;
+ if (NS_WARN_IF(!surf)) {
+ return {};
+ }
+
const auto imageSize = *uvec2::FromSize(surf->GetSize());
if (!size) {
size.emplace(imageSize.x, imageSize.y, 1);
@@ -925,23 +929,7 @@ void WebGLTexture::TexStorage(TexTarget target, uint32_t levels,
void WebGLTexture::TexImage(uint32_t level, GLenum respecFormat,
const uvec3& offset, const webgl::PackingInfo& pi,
const webgl::TexUnpackBlobDesc& src) {
- Maybe<RawBuffer<>> cpuDataView;
- if (src.cpuData) {
- cpuDataView = Some(RawBuffer<>{src.cpuData->Data()});
- }
- const auto srcViewDesc = webgl::TexUnpackBlobDesc{src.imageTarget,
- src.size,
- src.srcAlphaType,
- std::move(cpuDataView),
- src.pboOffset,
- src.structuredSrcSize,
- src.image,
- src.sd,
- src.dataSurf,
- src.unpacking,
- src.applyUnpackTransforms};
-
- const auto blob = webgl::TexUnpackBlob::Create(srcViewDesc);
+ const auto blob = webgl::TexUnpackBlob::Create(src);
if (!blob) {
MOZ_ASSERT(false);
return;
diff --git a/dom/canvas/WebGLTypes.h b/dom/canvas/WebGLTypes.h
index b23839b9ca..c268047930 100644
--- a/dom/canvas/WebGLTypes.h
+++ b/dom/canvas/WebGLTypes.h
@@ -19,11 +19,14 @@
#include "ImageContainer.h"
#include "mozilla/Casting.h"
#include "mozilla/CheckedInt.h"
+#include "mozilla/EnumTypeTraits.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/Range.h"
#include "mozilla/RefCounted.h"
#include "mozilla/Result.h"
#include "mozilla/ResultVariant.h"
+#include "mozilla/Span.h"
+#include "mozilla/TypedEnumBits.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/BuildConstants.h"
#include "mozilla/gfx/Logging.h"
@@ -407,8 +410,6 @@ inline ColorSpace2 ToColorSpace2(const dom::PredefinedColorSpace cs) {
return ColorSpace2::SRGB;
case dom::PredefinedColorSpace::Display_p3:
return ColorSpace2::DISPLAY_P3;
- case dom::PredefinedColorSpace::EndGuard_:
- break;
}
MOZ_CRASH("Exhaustive switch");
}
@@ -598,9 +599,13 @@ class EnumMask {
public:
BitRef operator[](const E i) { return {*this, Mask(i)}; }
bool operator[](const E i) const { return mBits & Mask(i); }
+
+ // -
+
+ auto MutTiedFields() { return std::tie(mBits); }
};
-class ExtensionBits : public EnumMask<WebGLExtensionID> {};
+using ExtensionBits = EnumMask<WebGLExtensionID>;
// -
@@ -624,9 +629,16 @@ inline bool ReadContextLossReason(const uint8_t val,
struct InitContextDesc final {
bool isWebgl2 = false;
bool resistFingerprinting = false;
+ std::array<uint8_t, 2> _padding;
+ uint32_t principalKey = 0;
uvec2 size = {};
WebGLContextOptions options;
- uint32_t principalKey = 0;
+ std::array<uint8_t, 3> _padding2;
+
+ auto MutTiedFields() {
+ return std::tie(isWebgl2, resistFingerprinting, _padding, principalKey,
+ size, options, _padding2);
+ }
};
constexpr uint32_t kMaxTransformFeedbackSeparateAttribs = 4;
@@ -651,10 +663,24 @@ struct Limits final {
// Exts
bool astcHdr = false;
+ std::array<uint8_t, 3> _padding;
uint32_t maxColorDrawBuffers = 1;
+ uint32_t maxMultiviewLayers = 0;
uint64_t queryCounterBitsTimeElapsed = 0;
uint64_t queryCounterBitsTimestamp = 0;
- uint32_t maxMultiviewLayers = 0;
+
+ auto MutTiedFields() {
+ return std::tie(supportedExtensions,
+
+ maxTexUnits, maxTex2dSize, maxTexCubeSize, maxVertexAttribs,
+ maxViewportDim, pointSizeRange, lineWidthRange,
+
+ maxTexArrayLayers, maxTex3dSize, maxUniformBufferBindings,
+ uniformBufferOffsetAlignment,
+
+ astcHdr, _padding, maxColorDrawBuffers, maxMultiviewLayers,
+ queryCounterBitsTimeElapsed, queryCounterBitsTimestamp);
+ }
};
// -
@@ -680,18 +706,41 @@ struct Padded {
// -
+enum class OptionalRenderableFormatBits : uint8_t {
+ RGB8 = (1 << 0),
+ SRGB8 = (1 << 1),
+};
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(OptionalRenderableFormatBits)
+inline constexpr bool IsEnumCase(const OptionalRenderableFormatBits raw) {
+ auto rawWithoutValidBits = UnderlyingValue(raw);
+ auto bit = decltype(rawWithoutValidBits){1};
+ while (bit) {
+ switch (OptionalRenderableFormatBits{bit}) {
+ // -Werror=switch ensures exhaustive.
+ case OptionalRenderableFormatBits::RGB8:
+ case OptionalRenderableFormatBits::SRGB8:
+ rawWithoutValidBits &= ~bit;
+ break;
+ }
+ bit <<= 1;
+ }
+ return rawWithoutValidBits == 0;
+}
+
+// -
+
struct InitContextResult final {
Padded<std::string, 32> error; // MINGW 32-bit needs this padding.
WebGLContextOptions options;
gl::GLVendor vendor;
- bool isRgb8Renderable;
+ OptionalRenderableFormatBits optionalRenderableFormatBits;
uint8_t _padding = {};
- webgl::Limits limits;
+ Limits limits;
EnumMask<layers::SurfaceDescriptor::Type> uploadableSdTypes;
auto MutTiedFields() {
- return std::tie(error, options, vendor, isRgb8Renderable, _padding, limits,
- uploadableSdTypes);
+ return std::tie(error, options, vendor, optionalRenderableFormatBits,
+ _padding, limits, uploadableSdTypes);
}
};
@@ -732,8 +781,13 @@ struct CompileResult final {
struct OpaqueFramebufferOptions final {
bool depthStencil = true;
bool antialias = true;
+ std::array<uint8_t, 2> _padding;
uint32_t width = 0;
uint32_t height = 0;
+
+ auto MutTiedFields() {
+ return std::tie(depthStencil, antialias, _padding, width, height);
+ }
};
// -
@@ -853,47 +907,6 @@ struct VertAttribPointerCalculated final {
} // namespace webgl
-// TODO: s/RawBuffer/Span/
-template <typename T = uint8_t>
-class RawBuffer final {
- const T* mBegin = nullptr;
- size_t mLen = 0;
-
- public:
- using ElementType = T;
-
- explicit RawBuffer(const Range<const T>& data)
- : mBegin(data.begin().get()), mLen(data.length()) {
- if (mLen) {
- MOZ_ASSERT(mBegin);
- }
- }
-
- ~RawBuffer() = default;
-
- Range<const T> Data() const { return {begin(), mLen}; }
- const auto& begin() const {
- if (mLen) {
- MOZ_RELEASE_ASSERT(mBegin);
- }
- return mBegin;
- }
- const auto& size() const { return mLen; }
-
- void Shrink(const size_t newLen) {
- if (mLen <= newLen) return;
- mLen = newLen;
- }
-
- RawBuffer() = default;
-
- RawBuffer(const RawBuffer&) = delete;
- RawBuffer& operator=(const RawBuffer&) = delete;
-
- RawBuffer(RawBuffer&&) = default;
- RawBuffer& operator=(RawBuffer&&) = default;
-};
-
template <class T>
inline Range<T> ShmemRange(const mozilla::ipc::Shmem& shmem) {
return {shmem.get<T>(), shmem.Size<T>()};
@@ -1096,7 +1109,7 @@ struct TexUnpackBlobDesc final {
uvec3 size;
gfxAlphaType srcAlphaType = gfxAlphaType::NonPremult;
- Maybe<RawBuffer<>> cpuData;
+ Maybe<Span<const uint8_t>> cpuData;
Maybe<uint64_t> pboOffset;
Maybe<uvec2> structuredSrcSize;
@@ -1134,11 +1147,6 @@ inline Range<const T> MakeRange(const dom::Sequence<T>& seq) {
return {seq.Elements(), seq.Length()};
}
-template <typename T>
-inline Range<const T> MakeRange(const RawBuffer<T>& from) {
- return from.Data();
-}
-
// -
constexpr auto kUniversalAlignment = alignof(std::max_align_t);
@@ -1159,13 +1167,6 @@ inline size_t ByteSize(const Range<T>& range) {
// -
-template <typename T>
-RawBuffer<T> RawBufferView(const Range<T>& range) {
- return RawBuffer<T>{range};
-}
-
-// -
-
Maybe<webgl::ErrorInfo> CheckBindBufferRange(
const GLenum target, const GLuint index, const bool isBuffer,
const uint64_t offset, const uint64_t size, const webgl::Limits& limits);
@@ -1207,6 +1208,13 @@ inline void Memcpy(const RangedPtr<T>* const destBegin,
Memcpy(destBegin, srcRange->begin(), srcRange->length());
}
+template <typename Dst, typename Src>
+inline void Memcpy(const Span<Dst>* const dest, const Span<Src>& src) {
+ MOZ_RELEASE_ASSERT(src.size_bytes() >= dest->size_bytes());
+ MOZ_ASSERT(src.size_bytes() == dest->size_bytes());
+ memcpy(dest->data(), src.data(), dest->size_bytes());
+}
+
// -
inline bool StartsWith(const std::string_view str,
@@ -1285,6 +1293,15 @@ inline const char* ToChars(const bool val) {
return "false";
}
+template <class To>
+struct ReinterpretToSpan {
+ template <class FromT>
+ static inline constexpr Span<To> From(const Span<FromT>& from) {
+ static_assert(sizeof(FromT) == sizeof(To));
+ return {reinterpret_cast<To*>(from.data()), from.size()};
+ }
+};
+
} // namespace mozilla
#endif
diff --git a/dom/canvas/crashtests/crashtests.list b/dom/canvas/crashtests/crashtests.list
index 2cd7177b77..e1920b20ee 100644
--- a/dom/canvas/crashtests/crashtests.list
+++ b/dom/canvas/crashtests/crashtests.list
@@ -4,7 +4,7 @@ load 360293-1.html
load 421715-1.html
load 553938-1.html
load 647480.html
-skip-if(Android&&browserIsRemote) load 727547.html # bug 1507207
+skip-if(Android) load 727547.html # bug 1507207
load 729116.html
load 745699-1.html
load 746813-1.html
@@ -27,13 +27,13 @@ load 1183363.html
load 1190705.html
load 1223740-1.html
load 1225381-1.html
-skip-if(azureCairo) load 1229983-1.html
+load 1229983-1.html
load 1229932-1.html
load 1244850-1.html
load 1246775-1.html
load 1284356-1.html
load 1284578-1.html
-skip-if(d2d) load 1287515-1.html
+load 1287515-1.html
load 1287652-1.html
load 1288872-1.html
load 1290628-1.html
diff --git a/dom/canvas/moz.build b/dom/canvas/moz.build
index 3337e97ad9..3a533d36d1 100644
--- a/dom/canvas/moz.build
+++ b/dom/canvas/moz.build
@@ -52,6 +52,7 @@ EXPORTS.mozilla.dom += [
"CanvasRenderingContext2D.h",
"CanvasRenderingContextHelper.h",
"CanvasUtils.h",
+ "DmdStdContainers.h",
"GeneratePlaceholderCanvasData.h",
"ImageBitmap.h",
"ImageBitmapRenderingContext.h",
diff --git a/dom/canvas/nsICanvasRenderingContextInternal.cpp b/dom/canvas/nsICanvasRenderingContextInternal.cpp
index c463a00a1a..ca8e16136c 100644
--- a/dom/canvas/nsICanvasRenderingContextInternal.cpp
+++ b/dom/canvas/nsICanvasRenderingContextInternal.cpp
@@ -7,9 +7,12 @@
#include "mozilla/dom/CanvasUtils.h"
#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Event.h"
#include "mozilla/dom/WorkerCommon.h"
#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/ErrorResult.h"
#include "mozilla/PresShell.h"
+#include "nsContentUtils.h"
#include "nsPIDOMWindow.h"
#include "nsRefreshDriver.h"
@@ -116,3 +119,24 @@ bool nsICanvasRenderingContextInternal::ShouldResistFingerprinting(
// Last resort, just check the global preference
return nsContentUtils::ShouldResistFingerprinting("Fallback", aTarget);
}
+
+bool nsICanvasRenderingContextInternal::DispatchEvent(
+ const nsAString& eventName, mozilla::CanBubble aCanBubble,
+ mozilla::Cancelable aIsCancelable) const {
+ bool useDefaultHandler = true;
+
+ if (mCanvasElement) {
+ nsContentUtils::DispatchTrustedEvent(mCanvasElement->OwnerDoc(),
+ mCanvasElement, eventName, aCanBubble,
+ aIsCancelable, &useDefaultHandler);
+ } else if (mOffscreenCanvas) {
+ // OffscreenCanvas case
+ auto event = mozilla::MakeRefPtr<mozilla::dom::Event>(mOffscreenCanvas,
+ nullptr, nullptr);
+ event->InitEvent(eventName, aCanBubble, aIsCancelable);
+ event->SetTrusted(true);
+ useDefaultHandler = mOffscreenCanvas->DispatchEvent(
+ *event, mozilla::dom::CallerType::System, mozilla::IgnoreErrors());
+ }
+ return useDefaultHandler;
+}
diff --git a/dom/canvas/nsICanvasRenderingContextInternal.h b/dom/canvas/nsICanvasRenderingContextInternal.h
index 580a9f07b3..deb642e9e8 100644
--- a/dom/canvas/nsICanvasRenderingContextInternal.h
+++ b/dom/canvas/nsICanvasRenderingContextInternal.h
@@ -15,6 +15,7 @@
#include "nsRFPService.h"
#include "mozilla/dom/HTMLCanvasElement.h"
#include "mozilla/dom/OffscreenCanvas.h"
+#include "mozilla/EventForwards.h"
#include "mozilla/Maybe.h"
#include "mozilla/RefPtr.h"
#include "mozilla/StateWatching.h"
@@ -225,6 +226,9 @@ class nsICanvasRenderingContextInternal : public nsISupports,
// Checking if fingerprinting protection is enable for the given target.
bool ShouldResistFingerprinting(mozilla::RFPTarget aTarget) const;
+ bool DispatchEvent(const nsAString& eventName, mozilla::CanBubble aCanBubble,
+ mozilla::Cancelable aIsCancelable) const;
+
protected:
RefPtr<mozilla::dom::HTMLCanvasElement> mCanvasElement;
RefPtr<mozilla::dom::OffscreenCanvas> mOffscreenCanvas;
diff --git a/dom/canvas/test/mochitest.toml b/dom/canvas/test/mochitest.toml
index 86cf26b632..3e96fff26b 100644
--- a/dom/canvas/test/mochitest.toml
+++ b/dom/canvas/test/mochitest.toml
@@ -185,6 +185,10 @@ support-files = [
["test_ImageData_ctor.html"]
+["test_accelerated_canvas_context_loss.html"]
+subsuite = "gpu"
+skip-if = ["verify || (os == 'win' && debug)"]
+
["test_bitmaprenderer.html"]
["test_bug232227.html"]
diff --git a/dom/canvas/test/reftest/colors/_generated_reftest.list b/dom/canvas/test/reftest/colors/_generated_reftest.list
new file mode 100644
index 0000000000..29761e3df8
--- /dev/null
+++ b/dom/canvas/test/reftest/colors/_generated_reftest.list
@@ -0,0 +1,176 @@
+### Generated, do not edit. ###
+ ### Generated, do not edit. ###
+ ### Generated, do not edit. ###
+
+# Generated by `./generate_color_canvas_reftests.py --write`.
+# -
+
+defaults pref(webgl.colorspaces.prototype,true)
+
+### Generated, do not edit. ###
+# -
+
+# Ensure not white-screening:
+!= color_canvas.html?= about:blank
+# Ensure differing results with different args:
+!= color_canvas.html?e_color=color(srgb,1,0,0) color_canvas.html?e_color=color(srgb,0,1,0)
+
+### Generated, do not edit. ###
+# -
+skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.000)
+skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.000)
+skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.200,0.200)
+skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.200,0.200)
+ ### Generated, do not edit. ###
+skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.000,0.000)
+skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.000,0.000)
+skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.200,0.000)
+skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.200,0.000)
+ ### Generated, do not edit. ###
+skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.200)
+skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.200)
+skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.502,0.502)
+skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.502,0.502)
+ ### Generated, do not edit. ###
+skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.000,0.000)
+skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.000,0.000)
+skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.502,0.000)
+skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.502,0.000)
+ ### Generated, do not edit. ###
+skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.502)
+skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.502)
+skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(srgb,1.000,1.000,1.000)
+skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(srgb,1.000,1.000,1.000)
+ ### Generated, do not edit. ###
+skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.000)
+skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.000)
+skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.200,0.200)
+skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.200,0.200)
+ ### Generated, do not edit. ###
+skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.000,0.000)
+skip-if(cocoaWidget) fails == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.000,0.000)
+skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.000,0.000)
+skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.200,0.000)
+ ### Generated, do not edit. ###
+skip-if(cocoaWidget) fails-if(!Android) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.200,0.000)
+skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.200,0.000)
+skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.200)
+skip-if(cocoaWidget) fails == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.200)
+ ### Generated, do not edit. ###
+skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.200)
+skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.502,0.502)
+skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.502,0.502)
+skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.000,0.000)
+ ### Generated, do not edit. ###
+skip-if(cocoaWidget) fails == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.000,0.000)
+skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.000,0.000)
+skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.502,0.000)
+skip-if(cocoaWidget) fails-if(!Android) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.502,0.000)
+ ### Generated, do not edit. ###
+skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.502,0.000)
+skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.502)
+skip-if(cocoaWidget) fails == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.502)
+skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.502)
+ ### Generated, do not edit. ###
+skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(display-p3,1.000,1.000,1.000)
+skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(display-p3,1.000,1.000,1.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.200,0.200)
+ ### Generated, do not edit. ###
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.200,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.200)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.502,0.502)
+ ### Generated, do not edit. ###
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.502,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.502)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(srgb,1.000,1.000,1.000)
+ ### Generated, do not edit. ###
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.200,0.200)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.200,0.000)
+ ### Generated, do not edit. ###
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.200)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.502,0.502)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.502,0.000)
+ ### Generated, do not edit. ###
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.502)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(display-p3,1.000,1.000,1.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.200,0.200)
+ ### Generated, do not edit. ###
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.200,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.200)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.502,0.502)
+ ### Generated, do not edit. ###
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.502,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.502)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(srgb,1.000,1.000,1.000)
+ ### Generated, do not edit. ###
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.200,0.200)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.200,0.000)
+ ### Generated, do not edit. ###
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.200)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.502,0.502)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.502,0.000)
+ ### Generated, do not edit. ###
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.502)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(display-p3,1.000,1.000,1.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.200,0.200)
+ ### Generated, do not edit. ###
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.200,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.200)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.502,0.502)
+ ### Generated, do not edit. ###
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.502,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.502)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(srgb,1.000,1.000,1.000)
+ ### Generated, do not edit. ###
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.200,0.200)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.200,0.000)
+ ### Generated, do not edit. ###
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.200)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.502,0.502)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.502,0.000)
+ ### Generated, do not edit. ###
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.502)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(display-p3,1.000,1.000,1.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.200,0.200)
+ ### Generated, do not edit. ###
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.200,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.200)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.502,0.502)
+ ### Generated, do not edit. ###
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.502,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.502)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(srgb,1.000,1.000,1.000)
+ ### Generated, do not edit. ###
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.200,0.200)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.200,0.000)
+ ### Generated, do not edit. ###
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.200)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.502,0.502)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.502,0.000)
+ ### Generated, do not edit. ###
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.502)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(display-p3,1.000,1.000,1.000)
diff --git a/dom/canvas/test/reftest/colors/color_canvas.html b/dom/canvas/test/reftest/colors/color_canvas.html
new file mode 100644
index 0000000000..7abbc86255
--- /dev/null
+++ b/dom/canvas/test/reftest/colors/color_canvas.html
@@ -0,0 +1,461 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset=utf-8>
+ <title>color_canvas.html</title>
+ </head>
+ <!--
+# color_canvas.html
+
+* Default is a 100x100 'css' canvas-equivalent div, filled with 50% srgb red.
+
+* We default to showing the settings pane when loaded without a query string.
+ This way, someone naively opens this in a browser, they can immediately see
+ all available options.
+
+* The 'Publish' button updates the url, and so causes the settings pane to
+ hide.
+
+* Clicking on the canvas toggles the settings pane for further editing.
+ -->
+ <body>
+ <form id=e_settings><fieldset><legend>Settings</legend>
+ Width: <input id=e_width type=text value=100>
+ <br>
+ Height: <input id=e_height type=text value=100>
+ <br>
+ <fieldset><legend>Canvas Context</legend>
+ Type: <select id=e_context>
+ <option value=css selected>css</option>
+ <option value=2d>2d</option>
+ <option value=webgl>webgl</option>
+ </select>
+ <br>
+ Options: <input id=e_options type=text value={}>
+ <br>
+ Colorspace: <input id=e_cspace type=text placeholder=srgb>
+ <br>
+ WebGL Format: <input id=e_webgl_format type=text placeholder=RGBA8>
+ </fieldset>
+ <br>
+ Color: <input id=e_color type=text value='color(srgb 0 0.5 0)'>
+ <br>
+ <input id=e_publish type=button value=Publish>
+ <input type=checkbox id=e_publish_omit_defaults checked><label for=e_publish_omit_defaults>Omit defaults</label>
+ <hr>
+ </fieldset></form>
+ <div id=e_canvas_list><canvas></canvas></div>
+ <script>
+'use strict';
+
+// -
+
+function walk_nodes_depth_first(e, fn) {
+ if (fn(e) === false) return; // Don't stop on `true`, or `undefined`!
+ for (const c of e.childNodes) {
+ walk_nodes_depth_first(c, fn);
+ }
+}
+
+// -
+
+// Click the canvas to toggle the settings pane.
+e_canvas_list.addEventListener('click', () => {
+ // Toggle display:none to hide/unhide.
+ e_settings.hidden = !e_settings.hidden;
+});
+
+// Hide settings initially if there's a query string in the url.
+if (window.location.search.startsWith('?')) {
+ e_settings.hidden = true;
+}
+
+// -
+
+// Imply .name from .id, because `new FormData` collects based on names.
+walk_nodes_depth_first(e_settings, e => {
+ if (e.id) {
+ e.name = e.id;
+ e._default_value = e.value;
+ }
+});
+
+// -
+
+const URL_PARAMS = new URLSearchParams(window.location.search);
+URL_PARAMS.forEach((v,k) => {
+ const e = window[k];
+ if (!e) {
+ if (k) {
+ console.warn(`Unrecognized setting: ${k} = ${v}`);
+ }
+ return;
+ }
+ v = decode_url_v(k,v);
+ e.value = v;
+});
+
+// -
+
+globalThis.ASSERT = (() => {
+ function toPrettyString(arg) {
+ if (!arg) return ''+arg;
+
+ if (arg.call) {
+ let s = arg.toString();
+ const RE_TRIVIAL_LAMBDA = /\( *\) *=> *(.*)/;
+ const m = RE_TRIVIAL_LAMBDA.exec(s);
+ if (m) {
+ s = '`' + m[1] + '`';
+ }
+ return s;
+ }
+ if (arg.constructor == Array) {
+ return `[${[].join.call(arg, ', ')}]`;
+ }
+ return JSON.stringify(arg);
+ }
+
+ /// new AssertArg(): Construct a wrapper for args to assert functions.
+ function AssertArg(dict) {
+ this.label = Object.keys(dict)[0];
+
+ this.set = function(arg) {
+ this.arg = arg;
+ this.value = (arg && arg.call) ? arg.call() : arg;
+ this.value = toPrettyString(this.value);
+ };
+ this.set(dict[this.label]);
+
+ this.toString = function() {
+ let ret = `${this.label} ${toPrettyString(this.arg)}`;
+ if (this.arg.call) {
+ ret += ` (${this.value})`;
+ }
+ return ret;
+ }
+ }
+
+ const eq = (a,b) => a == b;
+ const neq = (a,b) => a != b;
+
+ const CMP_BY_NAME = {
+ '==': eq,
+ '!=': neq,
+ };
+
+ function IS(cmp, was, expected, _console) {
+ _console = _console || console;
+
+ _console.assert(was.call, '`was.call` not defined.');
+ was = new AssertArg({was});
+ expected = new AssertArg({expected});
+
+ const fn_cmp = CMP_BY_NAME[cmp] || cmp;
+
+ _console.assert(fn_cmp(was.value, expected.value), `${toPrettyString(was.arg)} => ${was.value} not ${cmp} ${expected}`);
+ if (was.value != expected.value) {
+ } else if (globalThis.ASSERT && globalThis.ASSERT.verbose) {
+ const maybe_cmp_str = (cmp == '==') ? '' : ` ${was.value} ${cmp}`;
+ _console.log(`${toPrettyString(was.arg)} => ${maybe_cmp_str}${expected}`);
+ }
+ }
+
+ // -
+
+ const MOCK_CONSOLE = {
+ _asserts: [],
+ assert: function(expr, ...args) {
+ if (!expr) {
+ this._asserts.push(args);
+ }
+ },
+ log: function(...args) {
+ // Don't record.
+ },
+ };
+
+ // -
+ // Test `==`
+
+ IS('==', () => 1, 1, MOCK_CONSOLE);
+ console.assert(MOCK_CONSOLE._asserts.length == 0, MOCK_CONSOLE._asserts);
+ MOCK_CONSOLE._asserts = [];
+
+ IS('==', () => 2, 2, MOCK_CONSOLE);
+ console.assert(MOCK_CONSOLE._asserts.length == 0, MOCK_CONSOLE._asserts);
+ MOCK_CONSOLE._asserts = [];
+
+ IS('==', () => 5, () => 3, MOCK_CONSOLE);
+ console.assert(MOCK_CONSOLE._asserts.length == 1, MOCK_CONSOLE._asserts);
+ MOCK_CONSOLE._asserts = [];
+
+ IS('==', () => [1,2], () => [1,2], MOCK_CONSOLE);
+ console.assert(MOCK_CONSOLE._asserts.length == 0, MOCK_CONSOLE._asserts);
+ MOCK_CONSOLE._asserts = [];
+
+ // -
+ // Test `!=`
+
+ IS('!=', () => [1,2,5], () => [1,2,3], MOCK_CONSOLE);
+ console.assert(MOCK_CONSOLE._asserts.length == 0, MOCK_CONSOLE._asserts);
+ MOCK_CONSOLE._asserts = [];
+
+ // -
+
+ const ret = {
+ verbose: false,
+ IS,
+ };
+ ret.EQ = (was,expected) => ret.IS('==', was, expected);
+ ret.NEQ = (was,expected) => ret.IS('!=', was, expected);
+ ret.EEQ = (was,expected) => ret.IS('===', was, expected);
+ ret.NEEQ = (was,expected) => ret.IS('!==', was, expected);
+ ret.TRUE = was => ret.EQ(was, true);
+ ret.FALSE = was => ret.EQ(was, false);
+ ret.NULL = was => ret.EEQ(was, null);
+ return ret;
+})();
+
+// -
+
+function parse_css_rgb(str) {
+ // rgb (R ,G ,B /A )
+ const m = /rgba?\(([^,]+),([^,]+),([^/)]+)(?: *\/([^)]+))?\)/.exec(str);
+ if (!m) throw str;
+ const rgba = m.slice(1,1+4).map((s,i) => {
+ if (s === undefined && i == 3) {
+ s = '1'; // Alpha defaults to 1.
+ }
+ s = s.trim();
+ let v = parseFloat(s);
+ if (s.endsWith('%')) {
+ v /= 100;
+ } else {
+ if (i < 3) { // r,g,b but not a!
+ v /= 255;
+ }
+ }
+ return v;
+ });
+ return rgba;
+}
+ASSERT.EQ(() => parse_css_rgb('rgb(255,255,255)'), [1,1,1,1]);
+ASSERT.EQ(() => parse_css_rgb('rgba(255,255,255)'), [1,1,1,1]);
+ASSERT.EQ(() => parse_css_rgb('rgb(255,255,255)'), [1,1,1,1]);
+ASSERT.EQ(() => parse_css_rgb('rgba(255,255,255)'), [1,1,1,1]);
+ASSERT.EQ(() => parse_css_rgb('rgb(20,40,60)'), () => [20/255, 40/255, 60/255, 1]);
+ASSERT.EQ(() => parse_css_rgb('rgb(20,40,60 / 0.5)'), () => [20/255, 40/255, 60/255, 0.5]);
+ASSERT.EQ(() => parse_css_rgb('rgb(20,40,60 / 0)'), () => [20/255, 40/255, 60/255, 0]);
+
+// -
+
+function parse_css_color(str) {
+ // color ( srgb R G B /A )
+ const m = /color\( *([^ ]+) +([^ ]+) +([^ ]+) +([^/)]+)(?:\/([^)]+))?\)/.exec(str);
+ if (!m) {
+ return ['srgb', ...parse_css_rgb(str)];
+ }
+
+ const cspace = m[1].trim();
+ let has_extreme_colors = false;
+ const rgba = m.slice(2, 2+4).map((s,i) => {
+ if (s === undefined && i == 3) {
+ s = '1'; // Alpha defaults to 1.
+ }
+ s = s.trim();
+ let v = parseFloat(s);
+ if (s.endsWith('%')) {
+ v /= 100;
+ }
+ if (v < 0 || v > 1) {
+ has_extreme_colors = true;
+ }
+ return v;
+ });
+ if (has_extreme_colors) {
+ console.warn(`parse_css_color('${str}') has colors outside [0.0,1.0]: ${JSON.stringify(rgba)}`);
+ }
+ return [cspace, ...rgba];
+}
+ASSERT.EQ(() => parse_css_color('rgb(255,255,255)'), ['srgb',1,1,1,1]);
+ASSERT.EQ(() => parse_css_color('rgb(20,40,60 / 0.5)'), () => ['srgb', 20/255, 40/255, 60/255, 0.5]);
+ASSERT.EQ(() => parse_css_color('color(srgb 1 0 1 /0.3)'), ['srgb',1,0,1,0.3]);
+ASSERT.EQ(() => parse_css_color('color(display-p3 1 0% 100%/ 30%)'), ['display-p3',1,0,1,0.3]);
+
+// -
+
+class CssColor {
+ constructor(cspace, r,g,b,a=1) {
+ this.cspace = cspace;
+ this.rgba = [this.r, this.g, this.b, this.a] = [r,g,b,a];
+ this.rgb = this.rgba.slice(0,3);
+ this.tuple = [this.cspace, ...this.rgba];
+ }
+
+ toString() {
+ return `color(${this.cspace} ${this.rgb.join(' ')} / ${this.a})`;
+ }
+};
+CssColor.parse = function(str) {
+ return new CssColor(...parse_css_color(str));
+}
+
+{
+ let STR;
+ // Test round-trip.
+ STR = 'color(display-p3 1 0 1 / 0.3)';
+ ASSERT.EQ(() => CssColor.parse(STR).toString(), STR);
+
+ // Test round-trip normalization
+ ASSERT.EQ(() => CssColor.parse('color( display-p3 1 0 1/30% )').toString(), 'color(display-p3 1 0 1 / 0.3)');
+}
+
+// -
+
+function redraw() {
+ while (e_canvas_list.firstChild) {
+ e_canvas_list.removeChild(e_canvas_list.firstChild);
+ }
+
+ const c = make_canvas(e_color.value.trim());
+ c.style.border = '4px solid black';
+ e_canvas_list.appendChild(c);
+}
+
+function fill_canvas_rect(context /*: CanvasRenderingContext | WebGLRenderingContext*/, css_color, rect=null) {
+ rect = rect || {left: 0, top: 0, w: context.canvas.width, h: context.canvas.height};
+
+ const is_c2d = ('fillRect' in context);
+ if (is_c2d) {
+ const c2d = context;
+ c2d.fillStyle = css_color;
+ c2d.fillRect(rect.left, rect.top, rect.w, rect.h);
+ return;
+ }
+
+ const is_webgl = ('drawArrays' in context);
+ if (is_webgl) {
+ const gl = context;
+ console.assert(context.canvas.width == gl.drawingBufferWidth, context.canvas.width, '!=', gl.drawingBufferWidth);
+ console.assert(context.canvas.height == gl.drawingBufferHeight, context.canvas.height, '!=', gl.drawingBufferHeight);
+
+ gl.enable(gl.SCISSOR_TEST);
+ gl.disable(gl.DEPTH_TEST);
+ const bottom = rect.top + rect.h; // in y-down c2d coords
+ gl.scissor(rect.left, context.canvas.height - bottom, rect.w, rect.h);
+
+ const canvas_cspace = context.drawingBufferColorSpace || 'srgb';
+ if (css_color.cspace != canvas_cspace) {
+ console.warn(`Ignoring mismatched color vs webgl canvas cspace: ${css_color.cspace} vs ${canvas_cspace}`);
+ }
+ gl.clearColor(...css_color.rgba);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ return;
+ }
+
+ console.error('Unhandled context kind:', context);
+}
+
+window.e_canvas = null;
+
+function make_canvas(css_color) {
+ css_color = CssColor.parse(css_color);
+
+ // `e_width` and e_friends are elements (by id) that we added to the raw HTML above.
+ // `e_width` is an old shorthand for `window.e_width || document.getElementById('e_width')`.
+ const W = parseInt(e_width.value);
+ const H = parseInt(e_height.value);
+ if (e_context.value == 'css') {
+ e_canvas = document.createElement('div');
+ e_canvas.style.width = `${W}px`;
+ e_canvas.style.height = `${H}px`;
+ e_canvas.style.backgroundColor = css_color;
+ return e_canvas;
+ }
+ e_canvas = document.createElement('canvas');
+ e_canvas.width = W;
+ e_canvas.height = H;
+
+ let requested_options = JSON.parse(e_options.value);
+ requested_options.colorSpace = e_cspace.value || undefined;
+
+ const context = e_canvas.getContext(e_context.value, requested_options);
+ if (requested_options.colorSpace) {
+ if (!context.drawingBufferColorSpace) {
+ console.warn(`${context.constructor.name}.drawingBufferColorSpace not supported by browser.`);
+ } else {
+ context.drawingBufferColorSpace = requested_options.colorSpace;
+ }
+ }
+
+ if (e_webgl_format.value) {
+ if (!context.drawingBufferStorage) {
+ console.warn(`${context.constructor.name}.drawingBufferStorage not supported by browser.`);
+ } else {
+ context.drawingBufferStorage(W, H, context[e_webgl_format.value]);
+ }
+ }
+
+ let actual_options;
+ if (!context.getContextAttributes) {
+ console.warn(`${canvas.constructor.name}.getContextAttributes not supported by browser.`);
+ actual_options = requested_options;
+ } else {
+ actual_options = context.getContextAttributes();
+ }
+
+ // -
+
+ fill_canvas_rect(context, css_color);
+
+ return e_canvas;
+}
+
+e_settings.addEventListener('change', async () => {
+ redraw();
+ const e_updated = document.createElement('i');
+ e_updated.textContent = '(Updated!)';
+ document.body.appendChild(e_updated);
+ await new Promise(go => setTimeout(go, 1000));
+ document.body.removeChild(e_updated);
+});
+redraw();
+
+// -
+
+function encode_url_v(k,v) {
+ if (k == 'e_color') {
+ v = v.replaceAll(' ', ',');
+ }
+ console.assert(!v.includes(' '), v);
+ return v
+}
+function decode_url_v(k,v) {
+ console.assert(!v.includes(' '), v);
+ if (k == 'e_color') {
+ v = v.replaceAll(',', ' ');
+ }
+ return v
+}
+ASSERT.EQ(() => decode_url_v('e_color', encode_url_v('e_color', 'color(srgb 1 0 0)')), 'color(srgb 1 0 0)')
+
+e_publish.addEventListener('click', () => {
+ const fd = new FormData(e_settings);
+ let settings = [];
+ for (let [k,v] of fd) {
+ const e = window[k];
+ if (e_publish_omit_defaults.checked && v == e._default_value) continue;
+
+ v = encode_url_v(k,v);
+ settings.push(`${k}=${v}`);
+ }
+ settings = settings.join('&');
+ if (!settings) {
+ settings = '='; // Empty key-value pair is 'publish with default settings'
+ }
+ window.location.search = '?' + settings;
+});
+ </script>
+ </body>
+</html>
diff --git a/dom/canvas/test/reftest/colors/generate_color_canvas_reftests.py b/dom/canvas/test/reftest/colors/generate_color_canvas_reftests.py
new file mode 100644
index 0000000000..8c1e5f3788
--- /dev/null
+++ b/dom/canvas/test/reftest/colors/generate_color_canvas_reftests.py
@@ -0,0 +1,427 @@
+#! python3
+
+# Typecheck:
+# `pip -m mypy generate_color_canvas_reftests.py`
+#
+# Run:
+# `./generate_color_canvas_reftests.py [--write]`
+
+import functools
+import json
+import math
+import pathlib
+import re
+import sys
+from typing import Iterable, NamedTuple, TypeVar
+
+ARGS = sys.argv[1:]
+DEST = pathlib.Path(__file__).parent / "_generated_reftest.list"
+
+COL_DELIM = " "
+COL_ALIGNMENT = 4
+
+# -
+
+T = TypeVar("T")
+U = TypeVar("U")
+
+# -
+
+
+# crossCombine([{a:false},{a:5}], [{},{b:5}])
+# [{a:false}, {a:true}, {a:false,b:5}, {a:true,b:5}]
+def cross_combine(*args_tup: list[dict]) -> list[dict]:
+ args = list(args_tup)
+ for i, a in enumerate(args):
+ assert type(a) == list, f"Arg{i} is {type(a)}, expected {list}."
+
+ def cross_combine2(listA, listB):
+ listC = []
+ for a in listA:
+ for b in listB:
+ c = dict()
+ c.update(a)
+ c.update(b)
+ listC.append(c)
+ return listC
+
+ res: list[dict] = [dict()]
+ while True:
+ try:
+ next = args.pop(0)
+ except IndexError:
+ break
+ res = cross_combine2(res, next)
+ return res
+
+
+# keyed_alternatives('count', [1,2,3]) -> [{count: 1}, {count: 2}, {count: 3}]
+def keyed_alternatives(key: T, vals: Iterable[U]) -> list[dict[T, U]]:
+ """
+ res = []
+ for v in vals:
+ d = dict()
+ d[key] = v
+ res.append(d)
+ return res
+ """
+ # return [dict([[key,v]]) for v in vals]
+ return [{key: v} for v in vals]
+
+
+# -
+
+
+def eprint(*args, **kwargs):
+ sys.stdout.flush()
+ print(*args, file=sys.stderr, **kwargs)
+ sys.stderr.flush()
+
+
+# -
+
+# color_canvas.html?e_width=100&e_height=100&e_context=css&e_options={}&e_cspace=&e_webgl_format=&e_color=rgb(127,0,0)
+
+CSPACE_LIST = ["srgb", "display-p3"]
+CANVAS_CSPACES = keyed_alternatives("e_cspace", CSPACE_LIST)
+
+RGB_LIST = [
+ "0.000 0.000 0.000",
+ "0.200 0.200 0.200", # 0.2*255 = 51
+ "0.200 0.000 0.000",
+ "0.000 0.200 0.000",
+ "0.000 0.000 0.200",
+ "0.502 0.502 0.502", # 0.502*255 = 128.01
+ "0.502 0.000 0.000",
+ "0.000 0.502 0.000",
+ "0.000 0.000 0.502",
+ "1.000 1.000 1.000",
+ #'1.000 0.000 0.000', # These will hit gamut clipping on most displays.
+ #'0.000 1.000 0.000',
+ #'0.000 0.000 1.000',
+]
+
+WEBGL_COLORS = keyed_alternatives("e_color", [f"color(srgb {rgb})" for rgb in RGB_LIST])
+
+C2D_COLORS = []
+for cspace in CSPACE_LIST:
+ C2D_COLORS += keyed_alternatives(
+ "e_color", [f"color({cspace} {rgb})" for rgb in RGB_LIST]
+ )
+
+# -
+
+WEBGL_FORMATS = keyed_alternatives(
+ "e_webgl_format",
+ [
+ "RGBA8",
+ # Bug 1883748: (webgl.drawingbufferStorage)
+ #'SRGB8_ALPHA8',
+ #'RGBA16F',
+ ],
+)
+WEBGL = cross_combine(
+ [{"e_context": "webgl"}], WEBGL_FORMATS, CANVAS_CSPACES, WEBGL_COLORS
+)
+
+# -
+
+C2D_OPTIONS_COMBOS = cross_combine(
+ keyed_alternatives("willReadFrequently", ["true", "false"]), # E.g. D2D vs Skia
+ # keyed_alternatives('alpha', ['true','false'])
+)
+C2D_OPTIONS = [
+ json.dumps(config, separators=(",", ":")) for config in C2D_OPTIONS_COMBOS
+]
+
+C2D = cross_combine(
+ [{"e_context": "2d"}],
+ keyed_alternatives("e_options", C2D_OPTIONS),
+ CANVAS_CSPACES,
+ C2D_COLORS,
+)
+
+# -
+
+COMBOS: list[dict[str, str]] = cross_combine(WEBGL + C2D)
+
+eprint(f"{len(COMBOS)} combinations...")
+
+# -
+
+Config = dict[str, str]
+
+
+class CssColor(NamedTuple):
+ cspace: str
+ rgb: str
+
+ def rgb_vals(self) -> tuple[float, float, float]:
+ (r, g, b) = [float(z) for z in self.rgb.split(" ")]
+ return (r, g, b)
+
+ def is_same_color(x, y) -> bool:
+ if x == y:
+ return True
+ (r, g, b) = x.rgb_vals()
+ if x.rgb == y.rgb and r == g and g == b:
+ return True
+ return False
+
+
+class Reftest(NamedTuple):
+ notes: list[str]
+ op: str
+ test_config: Config
+ ref_config: Config
+
+
+def make_ref_config(color: CssColor) -> Config:
+ return {
+ "e_context": "css",
+ "e_color": f"color({color.cspace} {color.rgb})",
+ }
+
+
+class ColorReftest(NamedTuple):
+ notes: list[str]
+ test_config: Config
+ ref_color: CssColor
+
+ def to_reftest(self):
+ ref_config = make_ref_config(self.ref_color)
+ return Reftest(self.notes.copy(), "==", self.test_config.copy(), ref_config)
+
+
+class Expectation(NamedTuple):
+ notes: list[str]
+ test_config: Config
+ color: CssColor
+
+
+def parse_css_color(s: str) -> CssColor:
+ m = re.match("color[(]([^)]+)[)]", s)
+ assert m, s
+ (cspace, rgb) = m.group(1).split(" ", 1)
+ return CssColor(cspace, rgb)
+
+
+def correct_color_from_test_config(test_config: Config) -> CssColor:
+ canvas_cspace = test_config["e_cspace"]
+ if not canvas_cspace:
+ canvas_cspace = "srgb"
+
+ correct_color = parse_css_color(test_config["e_color"])
+ if test_config["e_context"] == "webgl":
+ # Webgl ignores the color's cspace, because webgl has no concept of
+ # source colorspace for clears/draws to the backbuffer.
+ # This (correct) behavior is as if the color's cspace were overwritten by the
+ # cspace of the canvas. (so expect that)
+ correct_color = CssColor(canvas_cspace, correct_color.rgb)
+
+ return correct_color
+
+
+# -------------------------------------
+# -------------------------------------
+# -------------------------------------
+# Choose (multiple?) reference configs given a test config.
+
+
+def reftests_from_config(test_config: Config) -> Iterable[ColorReftest]:
+ correct_color = correct_color_from_test_config(test_config)
+
+ if test_config["e_context"] == "2d":
+ # Canvas2d generally has the same behavior as css, so expect all passing.
+ yield ColorReftest([], test_config, correct_color)
+ return
+
+ assert test_config["e_context"] == "webgl", test_config["e_context"]
+
+ # -
+
+ def reftests_from_expected_color(
+ notes: list[str], expected_color: CssColor
+ ) -> Iterable[ColorReftest]:
+ # If expecting failure, generate two tests, both expecting failure:
+ # 1. expected-fail test == correct_color
+ # 2. expected-pass test == (incorrect) expected_color
+ # If we fix an error, we'll see one unexpected-pass and one unexpected-fail.
+ # If we get a new wrong answer, we'll see one unexpected-fail.
+
+ if not expected_color.is_same_color(correct_color):
+ yield ColorReftest(notes + ["fails"], test_config, correct_color)
+ yield ColorReftest(notes, test_config, expected_color)
+ else:
+ yield ColorReftest(notes, test_config, correct_color)
+
+ # -
+ # On Mac, (with the pref) we do tag the IOSurface with the right cspace.
+ # On other platforms, Webgl always outputs in the display color profile
+ # right now. This is the same as "srgb".
+
+ expected_color_srgb = CssColor("srgb", correct_color.rgb)
+ # Mac
+ yield from reftests_from_expected_color(["skip-if(!cocoaWidget)"], correct_color)
+ # Win, Lin, Android
+ yield from reftests_from_expected_color(
+ ["skip-if(cocoaWidget) "], expected_color_srgb
+ )
+
+
+# -
+
+
+def amended_notes_from_reftest(reftest: ColorReftest) -> list[str]:
+ notes = reftest.notes[:]
+
+ ref_rgb_vals = reftest.ref_color.rgb_vals()
+ is_green_only = ref_rgb_vals == (0, ref_rgb_vals[1], 0)
+ if (
+ "fails" in reftest.notes
+ and reftest.test_config["e_context"] == "webgl"
+ and reftest.test_config["e_cspace"] == "display-p3"
+ and is_green_only
+ ):
+ # Android's display bitdepth rounds srgb green and p3 green to the same thing.
+ notes[notes.index("fails")] = "fails-if(!Android)"
+
+ return notes
+
+
+# -------------------------------------
+# -------------------------------------
+# -------------------------------------
+# Ok, back to implementation.
+
+
+def encode_url_v(k, v):
+ if k == "e_color":
+ # reftest harness can't deal with spaces in urls, and 'color(srgb%201%200%200)' is hard to read.
+ v = v.replace(" ", ",")
+
+ assert " " not in v, (k, v)
+ return v
+
+
+# Cool:
+assert encode_url_v("e_color", "color(srgb 0 0 0)") == "color(srgb,0,0,0)"
+# Unfortunate, but tolerable:
+assert encode_url_v("e_color", "color(srgb 0 0 0)") == "color(srgb,,,0,,,0,,,0)"
+
+# -
+
+
+def url_from_config(kvs: Config) -> str:
+ parts = [f"{k}={encode_url_v(k,v)}" for k, v in kvs.items()]
+ url = "color_canvas.html?" + "&".join(parts)
+ return url
+
+
+# -
+
+color_reftests: list[ColorReftest] = []
+for c in COMBOS:
+ color_reftests += reftests_from_config(c)
+color_reftests = [
+ ColorReftest(amended_notes_from_reftest(r), r.test_config, r.ref_color)
+ for r in color_reftests
+]
+reftests = [r.to_reftest() for r in color_reftests]
+
+# -
+
+HEADINGS = ["# annotations", "op", "test", "reference"]
+table: list[list[str]] = [HEADINGS]
+table = [
+ [
+ " ".join(r.notes),
+ r.op,
+ url_from_config(r.test_config),
+ url_from_config(r.ref_config),
+ ]
+ for r in reftests
+]
+
+# -
+
+
+def round_to(a, b: int) -> int:
+ return int(math.ceil(a / b) * b)
+
+
+def aligned_lines_from_table(
+ rows: list[list[str]], col_delim=" ", col_alignment=4
+) -> Iterable[str]:
+ max_col_len = functools.reduce(
+ lambda accum, input: [max(r, len(c)) for r, c in zip(accum, input)],
+ rows,
+ [0 for _ in rows[0]],
+ )
+ max_col_len = [round_to(x, col_alignment) for x in max_col_len]
+
+ for i, row in enumerate(rows):
+ parts = [s + (" " * (col_len - len(s))) for s, col_len in zip(row, max_col_len)]
+ line = col_delim.join(parts)
+ yield line
+
+
+# -
+
+GENERATED_FILE_LINE = "### Generated, do not edit. ###"
+
+lines = list(aligned_lines_from_table(table, COL_DELIM, COL_ALIGNMENT))
+WARN_EVERY_N_LINES = 5
+i = WARN_EVERY_N_LINES - 1
+while i < len(lines):
+ lines.insert(i, " " + GENERATED_FILE_LINE)
+ i += WARN_EVERY_N_LINES
+
+# -
+
+GENERATED_BY_ARGS = [f"./{pathlib.Path(__file__).name}"] + ARGS
+
+REFTEST_LIST_PREAMBLE = f"""\
+{GENERATED_FILE_LINE}
+ {GENERATED_FILE_LINE}
+ {GENERATED_FILE_LINE}
+
+# Generated by `{' '.join(GENERATED_BY_ARGS)}`.
+# -
+
+defaults pref(webgl.colorspaces.prototype,true)
+
+{GENERATED_FILE_LINE}
+# -
+
+# Ensure not white-screening:
+!= {url_from_config({})+'='} about:blank
+# Ensure differing results with different args:
+!= {url_from_config({'e_color':'color(srgb 1 0 0)'})} {url_from_config({'e_color':'color(srgb 0 1 0)'})}
+
+{GENERATED_FILE_LINE}
+# -
+"""
+
+lines.insert(0, REFTEST_LIST_PREAMBLE)
+lines.append("")
+
+# -
+
+for line in lines:
+ print(line)
+
+if "--write" not in ARGS:
+ eprint("Use --write to write. Exiting...")
+ sys.exit(0)
+
+# -
+
+eprint("Concatenating...")
+file_str = "\n".join([line.rstrip() for line in lines])
+
+eprint(f"Writing to {DEST}...")
+DEST.write_bytes(file_str.encode())
+eprint("Done!")
+
+sys.exit(0)
diff --git a/dom/canvas/test/reftest/filters/reftest.list b/dom/canvas/test/reftest/filters/reftest.list
index cf851e2a3b..e4f21db6ce 100644
--- a/dom/canvas/test/reftest/filters/reftest.list
+++ b/dom/canvas/test/reftest/filters/reftest.list
@@ -1,7 +1,7 @@
== default-color.html ref.html
== drop-shadow.html ref.html
== drop-shadow-transformed.html ref.html
-fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)||azureSkia,0-1,0-1500) == global-alpha.html global-alpha-ref.html
+fuzzy(0-1,0-1500) == global-alpha.html global-alpha-ref.html
== global-composite-operation.html global-composite-operation-ref.html
== liveness.html ref.html
== liveness-document-open.html data:text/html,PASS
@@ -20,7 +20,7 @@ fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)||azureSkia,0-1,0-1500) == gl
== units-off-screen.html ref.html
fuzzy(0-2,0-700) == fillText-with-filter-opacity-1.html fillText-with-filter-opacity-1-ref.html
fuzzy(0-1,0-302) == fillText-with-filter-opacity-2.html fillText-with-filter-opacity-2-ref.html
-fuzzy(0-1,0-600) fuzzy-if(d2d&&!swgl,0-36,0-15) == strokeText-with-filter-grayscale-1.html strokeText-with-filter-grayscale-1-ref.html
+fuzzy(0-1,0-600) fuzzy-if(winWidget&&!swgl,0-36,0-15) == strokeText-with-filter-grayscale-1.html strokeText-with-filter-grayscale-1-ref.html
fuzzy(0-1,0-600) == strokeText-with-filter-grayscale-2.html strokeText-with-filter-grayscale-2-ref.html
!= fillText-with-shadow-1.html fillText-without-shadow-1-ref.html
!= fillText-with-shadow-2.html fillText-without-shadow-2-ref.html
diff --git a/dom/canvas/test/reftest/reftest.list b/dom/canvas/test/reftest/reftest.list
index 0eef0b3daf..26f373b32c 100644
--- a/dom/canvas/test/reftest/reftest.list
+++ b/dom/canvas/test/reftest/reftest.list
@@ -1,4 +1,5 @@
# Canvas Filter Reftests
+include colors/_generated_reftest.list
include filters/reftest.list
include color_quads.list
@@ -26,15 +27,6 @@ skip-if(Android) == webgl-resize-test.html wrapper.html?green.png
# Check that captureStream() displays in a local video element
skip-if(Android) == webgl-capturestream-test.html?preserve wrapper.html?green.png
-# Some of the failure conditions are a little weird. I'm (jgilbert) setting these based on
-# failures encountered when running on Try, and then targetting the Try config by
-# differences in the `sandbox` contents. That is, I'm labeling based on symptoms rather
-# than cause.
-# WinXP R: winWidget && layersGPUAccelerated && !d3d11
-# Win7+ R: winWidget && layersGPUAccelerated && d3d11
-# Win7+ Ru: winWidget && !layersGPUAccelerated && d3d11
-# (Note that we have to remove spaces when used below)
-
# IMPORTANT: Expected outcomes are evaluated left-to-right, and they replace eachother.
# That means that if an unconditional status (`fuzzy()`) is to the right of another status
# (such as fails-if), it will overwrite the old status.
@@ -233,7 +225,7 @@ fuzzy(0-1,0-150) == clip-multiple-move-2.html clip-multiple-move-2-ref.html
== stroketext-shadow.html stroketext-shadow-ref.html
# focus rings
-fuzzy(0-1,0-2) skip-if(cocoaWidget||winWidget||gtkWidget) needs-focus == drawFocusIfNeeded.html drawFocusIfNeeded-ref.html
+fuzzy(0-1,0-2) skip-if(cocoaWidget||gtkWidget||winWidget) needs-focus == drawFocusIfNeeded.html drawFocusIfNeeded-ref.html
# Check that captureStream() displays in a local video element
== capturestream.html wrapper.html?green.png
diff --git a/dom/canvas/test/test_accelerated_canvas_context_loss.html b/dom/canvas/test/test_accelerated_canvas_context_loss.html
new file mode 100644
index 0000000000..6172420bcb
--- /dev/null
+++ b/dom/canvas/test/test_accelerated_canvas_context_loss.html
@@ -0,0 +1,121 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Check for contextlost/restored events after GPU process restart</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<canvas id="c" width="512" height="512"></canvas>
+
+<script type="application/javascript">
+
+function waitRAF() {
+ return new Promise((resolve, reject) => {
+ window.requestAnimationFrame(resolve);
+ });
+}
+
+async function restartGPUProcess() {
+ return await SpecialPowers.spawnChrome([], async () => {
+ const gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
+
+ if (gfxInfo.usingGPUProcess) {
+ const { TestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TestUtils.sys.mjs"
+ );
+ let promise = TestUtils.topicObserved("compositor-reinitialized");
+
+ const remoteCanvas = gfxInfo.usingRemoteCanvas;
+ const acceleratedCanvas = gfxInfo.usingAcceleratedCanvas;
+ ok(true, "Restarting GPU process, remote canvas " + remoteCanvas + ", accelerated canvas " + acceleratedCanvas);
+
+ gfxInfo.killGPUProcessForTests();
+ await promise;
+
+ return remoteCanvas || acceleratedCanvas;
+ }
+
+ ok(true, "Not using GPU process");
+ return false;
+ });
+}
+
+const canvas = document.getElementById("c");
+const context = canvas.getContext("2d");
+
+let restoredPromiseResolve;
+let restoredPromiseReject;
+
+const restoredPromise = new Promise((resolve, reject) => {
+ restoredPromiseResolve = resolve;
+ restoredPromiseReject = reject;
+});
+
+let countLostEvents = 0;
+let countRestoredEvents = 0;
+
+function onContextLost() {
+ ok(context.isContextLost(), "Canvas context should be lost during contextlost event");
+ countLostEvents += 1;
+}
+
+function onContextRestored() {
+ ok(!context.isContextLost(), "Canvas context should not be lost during contextrestored event");
+ countRestoredEvents += 1;
+
+ restoredPromiseResolve(true);
+}
+
+function waitContextRestored() {
+ let timeoutId = window.setTimeout(restoredPromiseReject, 5000);
+ return restoredPromise.then(() => {
+ window.clearTimeout(timeoutId);
+ });
+}
+
+async function start() {
+ try {
+ canvas.addEventListener("contextlost", onContextLost);
+ canvas.addEventListener("contextrestored", onContextRestored);
+
+ ok(!context.isContextLost(), "Canvas context should not be lost before initial fill");
+
+ context.fillStyle = 'red';
+ context.fill();
+ await waitRAF();
+
+ ok(!context.isContextLost(), "Canvas context should not be lost after initial fill");
+
+ const restarted = await restartGPUProcess();
+ const expectedEvents = restarted ? 1 : 0;
+
+ if (expectedEvents) {
+ await waitContextRestored();
+ }
+ await waitRAF();
+
+ is(countLostEvents, expectedEvents, "Should have fired " + expectedEvents + " contextlost events");
+ is(countRestoredEvents, expectedEvents, "Should have fired " + expectedEvents + " contextrestored events");
+ ok(!context.isContextLost(), "Canvas context should not be lost after restoration");
+
+ context.fillStyle = 'green';
+ context.fill();
+ await waitRAF();
+
+ ok(!context.isContextLost(), "Canvas context should not be lost at completion");
+ } catch (err) {
+ ok(false, "Caught exception " + err);
+ } finally {
+ SimpleTest.finish();
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("Wait for failure condition");
+start();
+
+</script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/generated-mochitest.toml b/dom/canvas/test/webgl-conf/generated-mochitest.toml
index 8a4694100d..0cee17acf4 100644
--- a/dom/canvas/test/webgl-conf/generated-mochitest.toml
+++ b/dom/canvas/test/webgl-conf/generated-mochitest.toml
@@ -3,7 +3,6 @@
# Mark failing (fail-if) and crashing (skip-if) tests in mochitest-errata.ini.
[DEFAULT]
-skip-if = ["os == 'android' && isEmulator"]
prefs = "media.seamless-looping-video=false"
support-files = [
@@ -5669,7 +5668,6 @@ subsuite = "webgl2-core"
["generated/test_2_conformance2__renderbuffers__multisampled-renderbuffer-initialization.html"]
subsuite = "webgl2-core"
-fail-if = ["os == 'android' && android_version == '26'"]
["generated/test_2_conformance2__renderbuffers__multisampled-stencil-renderbuffer-initialization.html"]
subsuite = "webgl2-core"
@@ -9275,7 +9273,6 @@ subsuite = "webgl2-ext"
["generated/test_2_conformance__glsl__bugs__vector-matrix-constructor-scalarization.html"]
subsuite = "webgl2-ext"
fail-if = [
- "os == 'android' && android_version == '26'",
"os == 'android' && !debug",
"os == 'mac' && !apple_silicon",
]
@@ -14501,7 +14498,6 @@ subsuite = "webgl1-ext"
["generated/test_conformance__glsl__bugs__vector-matrix-constructor-scalarization.html"]
subsuite = "webgl1-ext"
fail-if = [
- "os == 'android' && android_version == '26'",
"os == 'mac' && !apple_silicon",
]
@@ -16706,7 +16702,10 @@ skip-if = ["os == 'android'"]
["generated/test_conformance__textures__misc__texture-npot-video.html"]
subsuite = "webgl1-core"
-skip-if = ["win11_2009"] # win11 - 50/50 intermittent
+skip-if = [
+ "win11_2009", # win11 - 50/50 intermittent
+ "os == 'android' && android_version == '33'", #Bug 1873144
+ ]
["generated/test_conformance__textures__misc__texture-npot.html"]
subsuite = "webgl1-core"
diff --git a/dom/canvas/test/webgl-conf/mochitest-errata.toml b/dom/canvas/test/webgl-conf/mochitest-errata.toml
index 05bcf2c6ac..5bf2b3f89b 100644
--- a/dom/canvas/test/webgl-conf/mochitest-errata.toml
+++ b/dom/canvas/test/webgl-conf/mochitest-errata.toml
@@ -23,8 +23,6 @@
# * Windows 10: 10.0
[DEFAULT]
-# Cross-process WebGL doesn't seem to work under an emulator
-skip-if = ["os == 'android' && isEmulator"]
# Bug 1799213
prefs = "media.seamless-looping-video=false"
@@ -87,7 +85,10 @@ fail-if = ["os == 'mac' && !apple_silicon"]
fail-if = ["os == 'android'"]
["generated/test_conformance__textures__misc__texture-npot-video.html"]
-skip-if = ["win11_2009"] # win11 - 50/50 intermittent
+skip-if = [
+ "win11_2009", # win11 - 50/50 intermittent
+ "os == 'android' && android_version == '33'", #Bug 1873144
+ ]
####################
# Bugs surfaced during fx93 CTS update
@@ -141,14 +142,12 @@ skip-if = ["os == 'linux' && debug"]
["generated/test_conformance__glsl__bugs__vector-matrix-constructor-scalarization.html"]
# https://bugzilla.mozilla.org/show_bug.cgi?id=1725075
fail-if = [
- "os == 'android' && android_version == '26'",
"os == 'mac' && !apple_silicon",
]
["generated/test_2_conformance__glsl__bugs__vector-matrix-constructor-scalarization.html"]
# Ditto
fail-if = [
- "os == 'android' && android_version == '26'",
"os == 'android' && !debug",
"os == 'mac' && !apple_silicon",
]
@@ -1102,7 +1101,6 @@ fail-if = ["os == 'win' && debug"]
skip-if = ["os == 'android'"]
["generated/test_2_conformance2__renderbuffers__multisampled-renderbuffer-initialization.html"]
-fail-if = ["os == 'android' && android_version == '26'"]
["generated/test_conformance__extensions__ext-sRGB.html"]
fail-if = ["os == 'android'"]
diff --git a/dom/canvas/test/webgl-mochitest/mochitest.toml b/dom/canvas/test/webgl-mochitest/mochitest.toml
index c76e2773e0..88e2fab88b 100644
--- a/dom/canvas/test/webgl-mochitest/mochitest.toml
+++ b/dom/canvas/test/webgl-mochitest/mochitest.toml
@@ -35,7 +35,8 @@ fail-if = ["os == 'android'"]
["ensure-exts/test_EXT_texture_compression_bptc.html"]
fail-if = [
"os == 'android'",
- "os == 'mac'",
+ "apple_catalina",
+ "apple_silicon",
]
["ensure-exts/test_EXT_texture_compression_rgtc.html"]
@@ -53,36 +54,45 @@ fail-if = ["os == 'linux' && display != 'wayland'"]
["ensure-exts/test_OVR_multiview2.html"]
fail-if = [
"os == 'linux'",
- "os == 'mac'",
+ "apple_catalina",
+ "apple_silicon",
]
["ensure-exts/test_WEBGL_color_buffer_float.html"]
["ensure-exts/test_WEBGL_compressed_texture_astc.html"]
fail-if = [
- "os == 'mac'",
- "os == 'win'",
+ "apple_catalina",
+ "apple_silicon",
+ "win10_2009",
+ "win11_2009",
]
["ensure-exts/test_WEBGL_compressed_texture_etc.html"]
fail-if = [
- "os == 'mac'",
- "os == 'win'",
+ "apple_catalina",
+ "apple_silicon",
+ "win10_2009",
+ "win11_2009",
]
["ensure-exts/test_WEBGL_compressed_texture_etc1.html"]
fail-if = [
"os == 'linux'",
- "os == 'mac'",
- "os == 'win'",
+ "apple_catalina",
+ "apple_silicon",
+ "win10_2009",
+ "win11_2009",
]
["ensure-exts/test_WEBGL_compressed_texture_pvrtc.html"]
fail-if = [
"os == 'android'",
"os == 'linux'",
- "os == 'mac'",
- "os == 'win'",
+ "apple_catalina",
+ "apple_silicon",
+ "win10_2009",
+ "win11_2009",
]
["ensure-exts/test_WEBGL_compressed_texture_s3tc.html"]
@@ -137,9 +147,9 @@ support-files = ["../captureStream_common.js"]
["test_has_rbab.html"]
fail-if = [
- "os == 'android' && android_version == '26'",
"os == 'linux'",
- "os == 'mac'",
+ "apple_catalina",
+ "apple_silicon",
]
["test_hidden_alpha.html"]
@@ -157,9 +167,9 @@ fail-if = [
["test_noprog_draw.html"]
["test_pixel_pack_buffer.html"]
-# skip-if = os == "win" && os_version == "10.0" # Bug 1302199
skip-if = [
- "os == 'win'", # Unofficial DXGL support regressed by bug 1632249
+ "win10_2009", # Unofficial DXGL support regressed by bug 1632249
+ "win11_2009", # Unofficial DXGL support regressed by bug 1632249
"apple_silicon",
]
@@ -191,22 +201,25 @@ skip-if = ["win11_2009 && bits == 32"] # No fast video path for h264 decoder (do
["test_video_fastpath_theora.html"]
skip-if = [
- "os == 'linux'",
- "os == 'mac'",
+ "os == 'linux' && os_version == '18.04'",
+ "apple_catalina",
+ "apple_silicon",
"win11_2009 && bits == 32", # No fast video path for theora decoder (done in RDD, can't be read in content)
]
["test_video_fastpath_vp8.html"]
skip-if = [
- "os == 'linux'",
- "os == 'mac'",
+ "os == 'linux' && os_version == '18.04'",
+ "apple_catalina",
+ "apple_silicon",
"win11_2009 && bits == 32", # No fast video path for theora decoder (done in RDD, can't be read in content)
]
["test_video_fastpath_vp9.html"]
skip-if = [
- "os == 'linux'",
- "os == 'mac'",
+ "os == 'linux' && os_version == '18.04'",
+ "apple_catalina",
+ "apple_silicon",
"win11_2009 && bits == 32", # No fast video path for theora decoder (done in RDD, can't be read in content)
]