commit 9c6c7491b8d64852956853f9f2730040034ea471
parent d5ad2b2885d6fe9e92d810d92b1dc80738710809
Author: Tom Ritter <tom@mozilla.com>
Date: Tue, 9 Dec 2025 16:34:59 +0000
Bug 1873716: Adding more recording sites r=timhuang
SKIP_BMO_CHECK
Differential Revision: https://phabricator.services.mozilla.com/D273665
Diffstat:
6 files changed, 133 insertions(+), 20 deletions(-)
diff --git a/dom/canvas/CanvasRenderingContext2D.cpp b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -71,6 +71,7 @@
#include "mozilla/dom/ToJSValue.h"
#include "mozilla/dom/TypedArray.h"
#include "mozilla/dom/VideoFrame.h"
+#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/CanvasShutdownManager.h"
#include "mozilla/gfx/DataSurfaceHelpers.h"
@@ -6511,6 +6512,8 @@ already_AddRefed<ImageData> CanvasRenderingContext2D::GetImageData(
h = 1;
}
+ RecordCanvasUsage(CanvasExtractionAPI::GetImageData, CSSIntSize(w, h));
+
JS::Rooted<JSObject*> array(aCx);
aError = GetImageDataArray(aCx, aSx, aSy, w, h, aSubjectPrincipal,
array.address());
diff --git a/dom/canvas/ClientWebGLContext.cpp b/dom/canvas/ClientWebGLContext.cpp
@@ -5354,26 +5354,30 @@ void ClientWebGLContext::ReadPixels(GLint x, GLint y, GLsizei width,
if (extraction == CanvasUtils::ImageExtraction::Placeholder) {
dom::GeneratePlaceholderCanvasData(range->size(), range->Elements());
- } else if (extraction == CanvasUtils::ImageExtraction::Randomize) {
- const auto pii = webgl::PackingInfoInfo::For(desc.pi);
- // DoReadPixels() requres pii to be Some().
- MOZ_ASSERT(pii.isSome());
-
- // With WebGL, the alpha channel is always the last element (if it
- // exists) in the pixel. With nsRFPService::RandomizeElements, we do
- // random % (pii->elementsPerPixel - 1) + offset to get the channel
- // we want to randomize. With the offset being 0, we avoid the last
- // element, which is the alpha channel.
- // If WebGL had ARGB or some other format where the alpha channel
- // was not the last element, we would need to adjust the offset.
- constexpr uint8_t alphaChannelOffset = 0;
- bool hasAlphaChannel =
- format == LOCAL_GL_SRGB_ALPHA || format == LOCAL_GL_RGBA ||
- format == LOCAL_GL_BGRA || format == LOCAL_GL_LUMINANCE_ALPHA;
- nsRFPService::RandomizeElements(
- GetCookieJarSettings(), PrincipalOrNull(), range->data(),
- range->size_bytes(), pii->elementsPerPixel, pii->bytesPerElement,
- alphaChannelOffset, hasAlphaChannel);
+ } else {
+ RecordCanvasUsage(CanvasExtractionAPI::ReadPixels,
+ CSSIntSize(width, height));
+ if (extraction == CanvasUtils::ImageExtraction::Randomize) {
+ const auto pii = webgl::PackingInfoInfo::For(desc.pi);
+ // DoReadPixels() requres pii to be Some().
+ MOZ_ASSERT(pii.isSome());
+
+ // With WebGL, the alpha channel is always the last element (if it
+ // exists) in the pixel. With nsRFPService::RandomizeElements, we do
+ // random % (pii->elementsPerPixel - 1) + offset to get the channel
+ // we want to randomize. With the offset being 0, we avoid the last
+ // element, which is the alpha channel.
+ // If WebGL had ARGB or some other format where the alpha channel
+ // was not the last element, we would need to adjust the offset.
+ constexpr uint8_t alphaChannelOffset = 0;
+ bool hasAlphaChannel =
+ format == LOCAL_GL_SRGB_ALPHA || format == LOCAL_GL_RGBA ||
+ format == LOCAL_GL_BGRA || format == LOCAL_GL_LUMINANCE_ALPHA;
+ nsRFPService::RandomizeElements(
+ GetCookieJarSettings(), PrincipalOrNull(), range->data(),
+ range->size_bytes(), pii->elementsPerPixel, pii->bytesPerElement,
+ alphaChannelOffset, hasAlphaChannel);
+ }
}
}
});
diff --git a/dom/canvas/OffscreenCanvas.cpp b/dom/canvas/OffscreenCanvas.cpp
@@ -515,9 +515,16 @@ already_AddRefed<Promise> OffscreenCanvas::ConvertToBlob(
this, nsContentUtils::GetCurrentJSContext(),
mCurrentContext ? mCurrentContext->PrincipalOrNull() : nullptr);
+ if (extractionBehaviour != CanvasUtils::ImageExtraction::Placeholder &&
+ GetContext()) {
+ GetContext()->RecordCanvasUsage(CanvasExtractionAPI::ToBlob,
+ GetWidthHeight());
+ }
+
CanvasRenderingContextHelper::ToBlob(callback, type, encodeOptions,
/* aUsingCustomOptions */ false,
extractionBehaviour, aRv);
+
if (aRv.Failed()) {
promise->MaybeReject(std::move(aRv));
}
@@ -559,6 +566,13 @@ already_AddRefed<Promise> OffscreenCanvas::ToBlob(JSContext* aCx,
CanvasUtils::ImageExtractionResult(
this, aCx,
mCurrentContext ? mCurrentContext->PrincipalOrNull() : nullptr);
+
+ if (extractionBehaviour != CanvasUtils::ImageExtraction::Placeholder &&
+ GetContext()) {
+ GetContext()->RecordCanvasUsage(CanvasExtractionAPI::ToBlob,
+ GetWidthHeight());
+ }
+
CanvasRenderingContextHelper::ToBlob(aCx, callback, aType, aParams,
extractionBehaviour, aRv);
diff --git a/dom/canvas/OffscreenCanvas.h b/dom/canvas/OffscreenCanvas.h
@@ -175,6 +175,9 @@ class OffscreenCanvas final : public DOMEventTargetHelper,
private:
~OffscreenCanvas();
+ void RecordCanvasUsage(CanvasExtractionAPI aExtractionAPI,
+ CanvasUtils::ImageExtraction aExtractionBehaviour);
+
already_AddRefed<EncodeCompleteCallback> CreateEncodeCompleteCallback(
Promise* aPromise);
diff --git a/dom/canvas/nsICanvasRenderingContextInternal.cpp b/dom/canvas/nsICanvasRenderingContextInternal.cpp
@@ -12,10 +12,15 @@
#include "mozilla/dom/Event.h"
#include "mozilla/dom/WorkerCommon.h"
#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkerRunnable.h"
#include "mozilla/gfx/DrawTargetRecording.h"
#include "nsContentUtils.h"
#include "nsPIDOMWindow.h"
+#include "nsRFPService.h"
#include "nsRefreshDriver.h"
+#include "nsThreadUtils.h"
+
+static mozilla::LazyLogModule gFingerprinterDetection("FingerprinterDetection");
nsICanvasRenderingContextInternal::nsICanvasRenderingContextInternal() =
default;
@@ -40,6 +45,87 @@ nsIGlobalObject* nsICanvasRenderingContextInternal::GetParentObject() const {
return nullptr;
}
+class RecordCanvasUsageRunnable final
+ : public mozilla::dom::WorkerMainThreadRunnable {
+ public:
+ RecordCanvasUsageRunnable(mozilla::dom::WorkerPrivate* aWorkerPrivate,
+ const mozilla::CanvasUsage& aUsage)
+ : WorkerMainThreadRunnable(aWorkerPrivate,
+ "RecordCanvasUsageRunnable"_ns),
+ mUsage(aUsage) {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ }
+
+ protected:
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY bool MainThreadRun() override {
+ mozilla::AssertIsOnMainThread();
+ RefPtr<mozilla::dom::Document> doc;
+ if (!mWorkerRef) {
+ MOZ_LOG(gFingerprinterDetection, mozilla::LogLevel::Error,
+ ("RecordCanvasUsageRunnable::MainThreadRun - null mWorkerRef"));
+ return false;
+ }
+ auto* priv = mWorkerRef->Private();
+ if (!priv) {
+ MOZ_LOG(
+ gFingerprinterDetection, mozilla::LogLevel::Error,
+ ("RecordCanvasUsageRunnable::MainThreadRun - null worker private"));
+ return false;
+ }
+ doc = priv->GetDocument();
+ if (!doc) {
+ MOZ_LOG(gFingerprinterDetection, mozilla::LogLevel::Error,
+ ("RecordCanvasUsageRunnable::MainThreadRun - null document"));
+ return false;
+ }
+ doc->RecordCanvasUsage(mUsage);
+ return true;
+ }
+
+ private:
+ mozilla::CanvasUsage mUsage;
+};
+
+void nsICanvasRenderingContextInternal::RecordCanvasUsage(
+ mozilla::CanvasExtractionAPI aAPI, mozilla::CSSIntSize size) const {
+ mozilla::dom::CanvasContextType contextType;
+ if (mCanvasElement) {
+ contextType = mCanvasElement->GetCurrentContextType();
+ auto usage =
+ mozilla::CanvasUsage::CreateUsage(false, contextType, aAPI, size, this);
+ mCanvasElement->OwnerDoc()->RecordCanvasUsage(usage);
+ }
+ if (mOffscreenCanvas) {
+ contextType = mOffscreenCanvas->GetContextType();
+ auto usage =
+ mozilla::CanvasUsage::CreateUsage(true, contextType, aAPI, size, this);
+ if (NS_IsMainThread()) {
+ if (nsPIDOMWindowInner* inner =
+ mOffscreenCanvas->GetOwnerGlobal()->GetAsInnerWindow()) {
+ if (mozilla::dom::Document* doc = inner->GetExtantDoc()) {
+ doc->RecordCanvasUsage(usage);
+ }
+ }
+ } else {
+ mozilla::dom::WorkerPrivate* workerPrivate =
+ mozilla::dom::GetCurrentThreadWorkerPrivate();
+ if (workerPrivate) {
+ RefPtr<RecordCanvasUsageRunnable> runnable =
+ new RecordCanvasUsageRunnable(workerPrivate, usage);
+ mozilla::ErrorResult rv;
+ runnable->Dispatch(workerPrivate, mozilla::dom::WorkerStatus::Canceling,
+ rv);
+ if (rv.Failed()) {
+ rv.SuppressException();
+ MOZ_LOG(gFingerprinterDetection, mozilla::LogLevel::Error,
+ ("RecordCanvasUsageRunnable dispatch failed"));
+ }
+ }
+ }
+ }
+}
+
nsIPrincipal* nsICanvasRenderingContextInternal::PrincipalOrNull() const {
if (mCanvasElement) {
return mCanvasElement->NodePrincipal();
diff --git a/dom/canvas/nsICanvasRenderingContextInternal.h b/dom/canvas/nsICanvasRenderingContextInternal.h
@@ -231,6 +231,9 @@ class nsICanvasRenderingContextInternal : public nsISupports,
bool DispatchEvent(const nsAString& eventName, mozilla::CanBubble aCanBubble,
mozilla::Cancelable aIsCancelable) const;
+ void RecordCanvasUsage(mozilla::CanvasExtractionAPI aAPI,
+ mozilla::CSSIntSize size) const;
+
protected:
RefPtr<mozilla::dom::HTMLCanvasElement> mCanvasElement;
RefPtr<mozilla::dom::OffscreenCanvas> mOffscreenCanvas;