commit a59040a6b05d918cd8ed64f7fd7325a7115a6f44
parent a66fab0931d975204ebe2cf943bf6885c756acf1
Author: Tom Ritter <tom@mozilla.com>
Date: Mon, 8 Dec 2025 16:31:01 +0000
Bug 1873716: Record (and separate activity) by source r=timhuang
SKIP_BMO_CHECK
Differential Revision: https://phabricator.services.mozilla.com/D273663
Diffstat:
7 files changed, 539 insertions(+), 52 deletions(-)
diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp
@@ -477,6 +477,7 @@ mozilla::LazyLogModule gPageCacheLog("PageCache");
mozilla::LazyLogModule gSHIPBFCacheLog("SHIPBFCache");
mozilla::LazyLogModule gTimeoutDeferralLog("TimeoutDefer");
mozilla::LazyLogModule gUseCountersLog("UseCounters");
+static mozilla::LazyLogModule gFingerprinterDetection("FingerprinterDetection");
namespace mozilla {
@@ -17591,35 +17592,91 @@ bool Document::ShouldResistFingerprinting(RFPTarget aTarget) const {
void Document::RecordCanvasUsage(CanvasUsage& aUsage) {
// Limit the number of recent canvas extraction uses that are tracked.
- const size_t kTrackedCanvasLimit = 8;
+ // Because we are now covering more canvas extraction sources, we need to
+ // increase this a bit
+ const size_t kTrackedCanvasLimit = 15;
// Timeout between different canvas extractions.
const uint64_t kTimeoutUsec = 3000 * 1000;
uint64_t now = PR_Now();
- if ((mCanvasUsage.Length() > kTrackedCanvasLimit) ||
- ((now - mLastCanvasUsage) > kTimeoutUsec)) {
- mCanvasUsage.ClearAndRetainStorage();
- }
- mCanvasUsage.AppendElement(aUsage);
- mLastCanvasUsage = now;
nsCString originNoSuffix;
+ nsCString uri;
if (NS_FAILED(NodePrincipal()->GetOriginNoSuffix(originNoSuffix))) {
- return;
+ MOZ_LOG(gFingerprinterDetection, LogLevel::Error,
+ ("Document:: %p Could not get originsuffix", this));
+ return;
+ }
+ if (NS_FAILED(NodePrincipal()->GetSpec(uri))) {
+ MOZ_LOG(gFingerprinterDetection, LogLevel::Error,
+ ("Document:: %p Could not get uri", this));
+ return;
+ }
+ if (MOZ_LOG_TEST(gFingerprinterDetection, LogLevel::Debug)) {
+ nsAutoCString filename;
+ uint32_t lineNum = 0;
+ filename.AssignLiteral("<unknown>");
+ JSContext* cx = nsContentUtils::GetCurrentJSContext();
+ if (cx) {
+ JS::AutoFilename scriptFilename;
+ JS::ColumnNumberOneOrigin colOneOrigin;
+ if (JS::DescribeScriptedCaller(&scriptFilename, cx, &lineNum,
+ &colOneOrigin)) {
+ if (const char* file = scriptFilename.get()) {
+ filename = nsDependentCString(file);
+ }
+ }
+ }
+
+ MOZ_LOG(gFingerprinterDetection, LogLevel::Debug,
+ ("Document:: %p %s recording canvas usage of type %s on %s in %s",
+ this, originNoSuffix.get(),
+ CanvasUsageSourceToString(aUsage.mUsageSource).get(), uri.get(),
+ filename.get()));
}
- nsRFPService::MaybeReportCanvasFingerprinter(mCanvasUsage, GetChannel(),
+ // Check if we need to clear the usage data for this source.
+ if (mCanvasUsageLastTimestamp != 0 &&
+ (now - mCanvasUsageLastTimestamp) > kTimeoutUsec) {
+ MOZ_LOG(
+ gFingerprinterDetection, LogLevel::Verbose,
+ ("Document:: %p %s clearing canvas array", this, originNoSuffix.get()));
+ mCanvasUsageData.Clear();
+ } else if (mCanvasUsageData.Length() > kTrackedCanvasLimit) {
+ MOZ_LOG(gFingerprinterDetection, LogLevel::Verbose,
+ ("Document:: %p %s removing oldest canvas "
+ "usage in array",
+ this, originNoSuffix.get()));
+ mCanvasUsageData.RemoveElementAt(0);
+ } else {
+ MOZ_LOG(gFingerprinterDetection, LogLevel::Verbose,
+ ("Document:: %p %s recorded canvas "
+ "usage of type %s in array, records: %zu",
+ this, originNoSuffix.get(),
+ CanvasUsageSourceToString(aUsage.mUsageSource).get(),
+ static_cast<size_t>(mCanvasUsageData.Length() + 1)));
+ }
+
+ // Update the timestamp and append the new usage.
+ mCanvasUsageLastTimestamp = now;
+ mCanvasUsageData.AppendElement(aUsage);
+
+ nsRFPService::MaybeReportCanvasFingerprinter(mCanvasUsageData, channel, uri,
originNoSuffix);
}
void Document::RecordFontFingerprinting() {
+ nsCString uri;
nsCString originNoSuffix;
if (NS_FAILED(NodePrincipal()->GetOriginNoSuffix(originNoSuffix))) {
return;
}
+ if (NS_FAILED(NodePrincipal()->GetSpec(uri))) {
+ return;
+ }
- nsRFPService::MaybeReportFontFingerprinter(GetChannel(), originNoSuffix);
+ nsRFPService::MaybeReportFontFingerprinter(GetChannel(), uri, originNoSuffix);
}
bool Document::IsInPrivateBrowsing() const { return mIsInPrivateBrowsing; }
diff --git a/dom/base/Document.h b/dom/base/Document.h
@@ -5744,8 +5744,10 @@ class Document : public nsINode,
// Used for tracking a number of recent canvas extractions (e.g. toDataURL),
// this is used for a canvas fingerprinter detection heuristic.
- nsTArray<CanvasUsage> mCanvasUsage;
- uint64_t mLastCanvasUsage = 0;
+ // Store all recent usages in a single nsTArray instead of a hash map.
+ nsTArray<CanvasUsage> mCanvasUsageData;
+ // Timestamp (PR_Now microseconds) of the last update to mCanvasUsageData.
+ uint64_t mCanvasUsageLastTimestamp = 0;
RefPtr<class FragmentDirective> mFragmentDirective;
UniquePtr<RadioGroupContainer> mRadioGroupContainer;
diff --git a/dom/canvas/nsICanvasRenderingContextInternal.h b/dom/canvas/nsICanvasRenderingContextInternal.h
@@ -21,7 +21,6 @@
#include "nsIDocShell.h"
#include "nsIInputStream.h"
#include "nsISupports.h"
-#include "nsRFPService.h"
#include "nsRefreshObservers.h"
#define NS_ICANVASRENDERINGCONTEXTINTERNAL_IID \
diff --git a/dom/html/HTMLCanvasElement.cpp b/dom/html/HTMLCanvasElement.cpp
@@ -928,16 +928,9 @@ nsresult HTMLCanvasElement::ExtractData(JSContext* aCx,
if (extractionBehaviour != CanvasUtils::ImageExtraction::Placeholder) {
auto size = GetWidthHeight();
- CanvasContextType type = GetCurrentContextType();
- CanvasFeatureUsage featureUsage = CanvasFeatureUsage::None;
- if (type == CanvasContextType::Canvas2D) {
- if (auto ctx =
- static_cast<CanvasRenderingContext2D*>(GetCurrentContext())) {
- featureUsage = ctx->FeatureUsage();
- }
- }
-
- CanvasUsage usage(size, type, featureUsage);
+ auto usage = CanvasUsage::CreateUsage(false, GetCurrentContextType(),
+ CanvasExtractionAPI::ToDataURL, size,
+ GetCurrentContext());
OwnerDoc()->RecordCanvasUsage(usage);
}
@@ -1097,6 +1090,13 @@ void HTMLCanvasElement::ToBlob(JSContext* aCx, BlobCallback& aCallback,
global, &aCallback, recheckCanRead ? mOffscreenDisplay.get() : nullptr,
recheckCanRead ? &aSubjectPrincipal : nullptr);
+ auto usage = CanvasUsage::CreateUsage(false, GetCurrentContextType(),
+ CanvasExtractionAPI::ToBlob,
+ GetWidthHeight(), GetCurrentContext());
+ if (extractionBehaviour != CanvasUtils::ImageExtraction::Placeholder) {
+ OwnerDoc()->RecordCanvasUsage(usage);
+ }
+
CanvasRenderingContextHelper::ToBlob(aCx, callback, aType, aParams,
extractionBehaviour, aRv);
}
diff --git a/toolkit/components/resistfingerprinting/nsRFPIPCUtils.h b/toolkit/components/resistfingerprinting/nsRFPIPCUtils.h
@@ -19,7 +19,7 @@ struct ParamTraits<mozilla::CanvasFingerprinterAlias>
: public ContiguousEnumSerializerInclusive<
mozilla::CanvasFingerprinterAlias,
mozilla::CanvasFingerprinterAlias::eNoneIdentified,
- mozilla::CanvasFingerprinterAlias::eMaybe> {};
+ mozilla::CanvasFingerprinterAlias::eLastAlias> {};
// CanvasFingerprintingEvent
template <>
@@ -29,21 +29,21 @@ struct ParamTraits<mozilla::CanvasFingerprintingEvent> {
static void Write(MessageWriter* aWriter, const paramType& aParam) {
WriteParam(aWriter, aParam.alias);
WriteParam(aWriter, aParam.knownTextBitmask);
- WriteParam(aWriter, aParam.source);
+ WriteParam(aWriter, aParam.sourcesBitmask);
}
static bool Read(MessageReader* aReader, paramType* aResult) {
mozilla::CanvasFingerprinterAlias alias;
uint32_t knownTextBitmask;
- uint8_t source;
+ uint64_t sourcesBitmask;
if (!ReadParam(aReader, &alias) || !ReadParam(aReader, &knownTextBitmask) ||
- !ReadParam(aReader, &source)) {
+ !ReadParam(aReader, &sourcesBitmask)) {
return false;
}
- *aResult =
- mozilla::CanvasFingerprintingEvent(alias, knownTextBitmask, source);
+ *aResult = mozilla::CanvasFingerprintingEvent(alias, knownTextBitmask,
+ sourcesBitmask);
return true;
}
};
diff --git a/toolkit/components/resistfingerprinting/nsRFPService.cpp b/toolkit/components/resistfingerprinting/nsRFPService.cpp
@@ -44,6 +44,7 @@
#include "mozilla/TextEvents.h"
#include "mozilla/dom/BrowsingContext.h"
#include "mozilla/dom/CanvasRenderingContextHelper.h"
+#include "mozilla/dom/CanvasRenderingContext2D.h"
#include "mozilla/dom/CanonicalBrowsingContext.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/Element.h"
@@ -1951,6 +1952,340 @@ static const char* CanvasFingerprinterToString(
return "<error>";
}
+namespace mozilla {
+nsCString CanvasUsageSourceToString(CanvasUsageSource aSource) {
+ if (aSource == CanvasUsageSource::Unknown) {
+ return "None"_ns;
+ }
+
+ nsCString accumulated;
+
+ auto append = [&](const char* name) {
+ if (!accumulated.IsEmpty()) {
+ accumulated.AppendLiteral(", ");
+ }
+ accumulated.Append(name);
+ };
+
+#define APPEND_IF_SET(flag, name) \
+ if ((aSource & (flag)) == (flag)) { \
+ append(name); \
+ }
+
+ APPEND_IF_SET(Impossible, "Impossible");
+
+ APPEND_IF_SET(MainThread_Canvas_ImageBitmap_toDataURL,
+ "MainThread_Canvas_ImageBitmap_toDataURL");
+ APPEND_IF_SET(MainThread_Canvas_ImageBitmap_toBlob,
+ "MainThread_Canvas_ImageBitmap_toBlob");
+ APPEND_IF_SET(MainThread_Canvas_ImageBitmap_getImageData,
+ "MainThread_Canvas_ImageBitmap_getImageData");
+
+ APPEND_IF_SET(MainThread_Canvas_Canvas2D_toDataURL,
+ "MainThread_Canvas_Canvas2D_toDataURL");
+ APPEND_IF_SET(MainThread_Canvas_Canvas2D_toBlob,
+ "MainThread_Canvas_Canvas2D_toBlob");
+ APPEND_IF_SET(MainThread_Canvas_Canvas2D_getImageData,
+ "MainThread_Canvas_Canvas2D_getImageData");
+
+ APPEND_IF_SET(MainThread_Canvas_WebGL_toDataURL,
+ "MainThread_Canvas_WebGL_toDataURL");
+ APPEND_IF_SET(MainThread_Canvas_WebGL_toBlob,
+ "MainThread_Canvas_WebGL_toBlob");
+ APPEND_IF_SET(MainThread_Canvas_WebGL_getImageData,
+ "MainThread_Canvas_WebGL_getImageData");
+ APPEND_IF_SET(MainThread_Canvas_WebGL_readPixels,
+ "MainThread_Canvas_WebGL_readPixels");
+
+ APPEND_IF_SET(MainThread_Canvas_WebGPU_toDataURL,
+ "MainThread_Canvas_WebGPU_toDataURL");
+ APPEND_IF_SET(MainThread_Canvas_WebGPU_toBlob,
+ "MainThread_Canvas_WebGPU_toBlob");
+ APPEND_IF_SET(MainThread_Canvas_WebGPU_getImageData,
+ "MainThread_Canvas_WebGPU_getImageData");
+
+ APPEND_IF_SET(MainThread_OffscreenCanvas_ImageBitmap_toDataURL,
+ "MainThread_OffscreenCanvas_ImageBitmap_toDataURL");
+ APPEND_IF_SET(MainThread_OffscreenCanvas_ImageBitmap_toBlob,
+ "MainThread_OffscreenCanvas_ImageBitmap_toBlob");
+ APPEND_IF_SET(MainThread_OffscreenCanvas_ImageBitmap_getImageData,
+ "MainThread_OffscreenCanvas_ImageBitmap_getImageData");
+
+ APPEND_IF_SET(MainThread_OffscreenCanvas_Canvas2D_toDataURL,
+ "MainThread_OffscreenCanvas_Canvas2D_toDataURL");
+ APPEND_IF_SET(MainThread_OffscreenCanvas_Canvas2D_toBlob,
+ "MainThread_OffscreenCanvas_Canvas2D_toBlob");
+ APPEND_IF_SET(MainThread_OffscreenCanvas_Canvas2D_getImageData,
+ "MainThread_OffscreenCanvas_Canvas2D_getImageData");
+
+ APPEND_IF_SET(Worker_OffscreenCanvas_Canvas2D_toBlob,
+ "Worker_OffscreenCanvas_Canvas2D_toBlob");
+ APPEND_IF_SET(Worker_OffscreenCanvas_Canvas2D_getImageData,
+ "Worker_OffscreenCanvas_Canvas2D_getImageData");
+
+ APPEND_IF_SET(MainThread_OffscreenCanvas_WebGL_toDataURL,
+ "MainThread_OffscreenCanvas_WebGL_toDataURL");
+ APPEND_IF_SET(MainThread_OffscreenCanvas_WebGL_toBlob,
+ "MainThread_OffscreenCanvas_WebGL_toBlob");
+ APPEND_IF_SET(MainThread_OffscreenCanvas_WebGL_getImageData,
+ "MainThread_OffscreenCanvas_WebGL_getImageData");
+ APPEND_IF_SET(MainThread_OffscreenCanvas_WebGL_readPixels,
+ "MainThread_OffscreenCanvas_WebGL_readPixels");
+
+ APPEND_IF_SET(Worker_OffscreenCanvas_ImageBitmap_toBlob,
+ "Worker_OffscreenCanvas_ImageBitmap_toBlob");
+ APPEND_IF_SET(Worker_OffscreenCanvas_ImageBitmap_getImageData,
+ "Worker_OffscreenCanvas_ImageBitmap_getImageData");
+
+ APPEND_IF_SET(Worker_OffscreenCanvas_WebGL_toBlob,
+ "Worker_OffscreenCanvas_WebGL_toBlob");
+ APPEND_IF_SET(Worker_OffscreenCanvas_WebGL_getImageData,
+ "Worker_OffscreenCanvas_WebGL_getImageData");
+ APPEND_IF_SET(Worker_OffscreenCanvas_WebGL_readPixels,
+ "Worker_OffscreenCanvas_WebGL_readPixels");
+
+ APPEND_IF_SET(MainThread_OffscreenCanvas_WebGPU_toDataURL,
+ "MainThread_OffscreenCanvas_WebGPU_toDataURL");
+ APPEND_IF_SET(MainThread_OffscreenCanvas_WebGPU_toBlob,
+ "MainThread_OffscreenCanvas_WebGPU_toBlob");
+ APPEND_IF_SET(MainThread_OffscreenCanvas_WebGPU_getImageData,
+ "MainThread_OffscreenCanvas_WebGPU_getImageData");
+
+ APPEND_IF_SET(Worker_OffscreenCanvas_WebGPU_toBlob,
+ "Worker_OffscreenCanvas_WebGPU_toBlob");
+ APPEND_IF_SET(Worker_OffscreenCanvas_WebGPU_getImageData,
+ "Worker_OffscreenCanvas_WebGPU_getImageData");
+
+ APPEND_IF_SET(Worker_OffscreenCanvasCanvas2D_Canvas2D_getImageData,
+ "Worker_OffscreenCanvasCanvas2D_Canvas2D_getImageData");
+ APPEND_IF_SET(Worker_OffscreenCanvasCanvas2D_Canvas2D_toBlob,
+ "Worker_OffscreenCanvasCanvas2D_Canvas2D_toBlob");
+
+#undef APPEND_IF_SET
+
+ if (accumulated.IsEmpty()) {
+ accumulated.AssignLiteral("<error>");
+ }
+
+ return accumulated;
+}
+} // namespace mozilla
+
+CanvasUsage CanvasUsage::CreateUsage(
+ bool aIsOffscreen, dom::CanvasContextType aContextType,
+ CanvasExtractionAPI aApi, CSSIntSize aSize,
+ const nsICanvasRenderingContextInternal* aContext) {
+ CanvasFeatureUsage featureUsage = CanvasFeatureUsage::None;
+
+ // Only 2D contexts currently expose feature usage metrics.
+ if (aContextType == dom::CanvasContextType::Canvas2D && aContext) {
+ // Downcast is safe because 2D contexts derive from
+ // nsICanvasRenderingContextInternal and we gated on the supplied type. The
+ // header is included so the relationship is known to the compiler.
+ auto* ctx2D = static_cast<const dom::CanvasRenderingContext2D*>(aContext);
+ featureUsage = ctx2D->FeatureUsage();
+ }
+
+ CanvasUsageSource usageSource =
+ CanvasUsage::GetCanvasUsageSource(aIsOffscreen, aContextType, aApi);
+ return CanvasUsage(aSize, aContextType, usageSource, featureUsage);
+}
+
+CanvasUsageSource CanvasUsage::GetCanvasUsageSource(
+ bool isOffscreen, dom::CanvasContextType contextType,
+ CanvasExtractionAPI api) {
+ const bool isMainThread = NS_IsMainThread();
+
+ auto logImpossible = [&](const char* aComment = "") {
+ MOZ_LOG(gFingerprinterDetection, LogLevel::Error,
+ ("CanvasUsageSource impossible: comment=%s isOffscreen=%d "
+ "contextType=%d api=%d isMainThread=%d",
+ aComment, isOffscreen, static_cast<int>(contextType),
+ static_cast<int>(api), isMainThread));
+ };
+
+ // Non-offscreen (HTMLCanvasElement) path (always main thread).
+ if (!isOffscreen) {
+ if (!isMainThread) {
+ logImpossible("Non-offscreen canvas accessed off main thread");
+ return CanvasUsageSource::Impossible;
+ }
+ switch (contextType) {
+ case dom::CanvasContextType::Canvas2D:
+ switch (api) {
+ case CanvasExtractionAPI::ToDataURL:
+ return MainThread_Canvas_Canvas2D_toDataURL;
+ case CanvasExtractionAPI::ToBlob:
+ return MainThread_Canvas_Canvas2D_toBlob;
+ case CanvasExtractionAPI::GetImageData:
+ return MainThread_Canvas_Canvas2D_getImageData;
+ case CanvasExtractionAPI::ReadPixels:
+ logImpossible("ReadPixels invalid for Canvas2D");
+ return CanvasUsageSource::Impossible;
+ default:
+ logImpossible("Unknown API for Canvas2D");
+ return CanvasUsageSource::Impossible;
+ }
+ case dom::CanvasContextType::WebGL1:
+ case dom::CanvasContextType::WebGL2:
+ switch (api) {
+ case CanvasExtractionAPI::ToDataURL:
+ return MainThread_Canvas_WebGL_toDataURL;
+ case CanvasExtractionAPI::ToBlob:
+ return MainThread_Canvas_WebGL_toBlob;
+ case CanvasExtractionAPI::GetImageData:
+ return MainThread_Canvas_WebGL_getImageData;
+ case CanvasExtractionAPI::ReadPixels:
+ return MainThread_Canvas_WebGL_readPixels;
+ default:
+ logImpossible("Unknown API for WebGL");
+ return CanvasUsageSource::Impossible;
+ }
+ case dom::CanvasContextType::WebGPU:
+ switch (api) {
+ case CanvasExtractionAPI::ToDataURL:
+ return MainThread_Canvas_WebGPU_toDataURL;
+ case CanvasExtractionAPI::ToBlob:
+ return MainThread_Canvas_WebGPU_toBlob;
+ case CanvasExtractionAPI::GetImageData:
+ return MainThread_Canvas_WebGPU_getImageData;
+ case CanvasExtractionAPI::ReadPixels:
+ logImpossible("ReadPixels invalid for WebGPU");
+ return CanvasUsageSource::Impossible;
+ default:
+ logImpossible("Unknown API for WebGPU");
+ return CanvasUsageSource::Impossible;
+ }
+ case dom::CanvasContextType::ImageBitmap:
+ switch (api) {
+ case CanvasExtractionAPI::ToDataURL:
+ return MainThread_Canvas_ImageBitmap_toDataURL;
+ case CanvasExtractionAPI::ToBlob:
+ return MainThread_Canvas_ImageBitmap_toBlob;
+ case CanvasExtractionAPI::GetImageData:
+ return MainThread_Canvas_ImageBitmap_getImageData;
+ case CanvasExtractionAPI::ReadPixels:
+ logImpossible("ReadPixels invalid for ImageBitmap");
+ return CanvasUsageSource::Impossible;
+ default:
+ logImpossible("Unknown API for ImageBitmap");
+ return CanvasUsageSource::Impossible;
+ }
+ default:
+ logImpossible("Unknown context type (main thread, non-offscreen)");
+ return CanvasUsageSource::Impossible;
+ }
+ }
+
+ // OffscreenCanvas path.
+ switch (contextType) {
+ case dom::CanvasContextType::Canvas2D:
+ switch (api) {
+ case CanvasExtractionAPI::ToDataURL:
+ if (isMainThread) {
+ return MainThread_OffscreenCanvas_Canvas2D_toDataURL;
+ }
+ logImpossible("ToDataURL invalid for Offscreen Canvas2D on worker");
+ return CanvasUsageSource::Impossible; // not supported
+ case CanvasExtractionAPI::ToBlob:
+ return isMainThread ? MainThread_OffscreenCanvas_Canvas2D_toBlob
+ : Worker_OffscreenCanvas_Canvas2D_toBlob;
+ case CanvasExtractionAPI::GetImageData:
+ return isMainThread ? MainThread_OffscreenCanvas_Canvas2D_getImageData
+ : Worker_OffscreenCanvas_Canvas2D_getImageData;
+ case CanvasExtractionAPI::ReadPixels:
+ logImpossible("ReadPixels invalid for Offscreen 2D");
+ return CanvasUsageSource::Impossible;
+ default:
+ logImpossible("Unknown API for Offscreen Canvas2D");
+ return CanvasUsageSource::Impossible;
+ }
+ case dom::CanvasContextType::OffscreenCanvas2D:
+ if (isMainThread) {
+ logImpossible("OffscreenCanvas2D on main thread");
+ return CanvasUsageSource::Impossible;
+ }
+ switch (api) {
+ case CanvasExtractionAPI::GetImageData:
+ return Worker_OffscreenCanvasCanvas2D_Canvas2D_getImageData;
+ case CanvasExtractionAPI::ToBlob:
+ return Worker_OffscreenCanvasCanvas2D_Canvas2D_toBlob;
+ default:
+ logImpossible("Unsupported API for OffscreenCanvas2D");
+ return CanvasUsageSource::Impossible;
+ }
+ case dom::CanvasContextType::WebGL1:
+ case dom::CanvasContextType::WebGL2:
+ switch (api) {
+ case CanvasExtractionAPI::ToDataURL:
+ if (!isMainThread) {
+ logImpossible("ToDataURL invalid for Offscreen WebGL on worker");
+ return CanvasUsageSource::Impossible; // not supported
+ }
+ return MainThread_OffscreenCanvas_WebGL_toDataURL;
+ case CanvasExtractionAPI::ToBlob:
+ return isMainThread ? MainThread_OffscreenCanvas_WebGL_toBlob
+ : Worker_OffscreenCanvas_WebGL_toBlob;
+ case CanvasExtractionAPI::GetImageData:
+ return isMainThread ? MainThread_OffscreenCanvas_WebGL_getImageData
+ : Worker_OffscreenCanvas_WebGL_getImageData;
+ case CanvasExtractionAPI::ReadPixels:
+ return isMainThread ? MainThread_OffscreenCanvas_WebGL_readPixels
+ : Worker_OffscreenCanvas_WebGL_readPixels;
+ default:
+ logImpossible("Unknown API for Offscreen WebGL");
+ return CanvasUsageSource::Impossible;
+ }
+ case dom::CanvasContextType::WebGPU:
+ switch (api) {
+ case CanvasExtractionAPI::ToDataURL:
+ if (!isMainThread) {
+ logImpossible("ToDataURL invalid for Offscreen WebGPU on worker");
+ return CanvasUsageSource::Impossible; // not supported
+ }
+ return MainThread_OffscreenCanvas_WebGPU_toDataURL;
+ case CanvasExtractionAPI::ToBlob:
+ return isMainThread ? MainThread_OffscreenCanvas_WebGPU_toBlob
+ : Worker_OffscreenCanvas_WebGPU_toBlob;
+ case CanvasExtractionAPI::GetImageData:
+ return isMainThread ? MainThread_OffscreenCanvas_WebGPU_getImageData
+ : Worker_OffscreenCanvas_WebGPU_getImageData;
+ case CanvasExtractionAPI::ReadPixels:
+ logImpossible("ReadPixels invalid for Offscreen WebGPU");
+ return CanvasUsageSource::Impossible;
+ default:
+ logImpossible("Unknown API for Offscreen WebGPU");
+ return CanvasUsageSource::Impossible;
+ }
+ case dom::CanvasContextType::ImageBitmap:
+ switch (api) {
+ case CanvasExtractionAPI::ToDataURL:
+ if (!isMainThread) {
+ logImpossible(
+ "ToDataURL invalid for Offscreen ImageBitmap on worker");
+ return CanvasUsageSource::Impossible; // not supported worker
+ }
+ return MainThread_OffscreenCanvas_ImageBitmap_toDataURL;
+ case CanvasExtractionAPI::ToBlob:
+ return isMainThread ? MainThread_OffscreenCanvas_ImageBitmap_toBlob
+ : Worker_OffscreenCanvas_ImageBitmap_toBlob;
+ case CanvasExtractionAPI::GetImageData:
+ return isMainThread
+ ? MainThread_OffscreenCanvas_ImageBitmap_getImageData
+ : Worker_OffscreenCanvas_ImageBitmap_getImageData;
+ case CanvasExtractionAPI::ReadPixels:
+ logImpossible("ReadPixels invalid for Offscreen ImageBitmap");
+ return CanvasUsageSource::Impossible;
+ default:
+ logImpossible("Unknown API for Offscreen ImageBitmap");
+ return CanvasUsageSource::Impossible;
+ }
+ default:
+ logImpossible("Unknown context type (offscreen)");
+ return CanvasUsageSource::Impossible;
+ }
+}
+
static void MaybeCurrentCaller(nsACString& aFilename, uint32_t& aLineNum,
uint32_t& aColumnNum) {
aFilename.AssignLiteral("<unknown>");
@@ -1971,8 +2306,8 @@ static void MaybeCurrentCaller(nsACString& aFilename, uint32_t& aLineNum,
}
/* static */ void nsRFPService::MaybeReportCanvasFingerprinter(
- nsTArray<CanvasUsage>& aUses, nsIChannel* aChannel,
- nsACString& aOriginNoSuffix) {
+ nsTArray<CanvasUsage>& aUses, nsIChannel* aChannel, const nsACString& aURI,
+ const nsACString& aOriginNoSuffix) {
if (!aChannel) {
return;
}
@@ -1987,6 +2322,7 @@ static void MaybeCurrentCaller(nsACString& aFilename, uint32_t& aLineNum,
bool seenExtracted2D_280x60 = false;
bool seenExtracted2D_860x6 = false;
CanvasFeatureUsage accumulatedFeatureUsage = CanvasFeatureUsage::None;
+ CanvasUsageSource accumulatedUsageSource = CanvasUsageSource::Unknown;
uint32_t extractedOther = 0;
@@ -1999,6 +2335,8 @@ static void MaybeCurrentCaller(nsACString& aFilename, uint32_t& aLineNum,
continue;
}
+ accumulatedUsageSource |= usage.mUsageSource;
+
if (usage.mType == dom::CanvasContextType::Canvas2D) {
accumulatedFeatureUsage |= usage.mFeatureUsage;
extracted2D++;
@@ -2069,19 +2407,22 @@ static void MaybeCurrentCaller(nsACString& aFilename, uint32_t& aLineNum,
MOZ_LOG(
gFingerprinterDetection, LogLevel::Info,
("Detected a potential canvas fingerprinter on %s in script %s:%d:%d "
- "(KnownFingerprintTextBitmask: %u, CanvasFingerprinterAlias: %s)",
+ "(KnownFingerprintTextBitmask: %u, CanvasFingerprinterAlias: %s, "
+ "CanvasUsageSource: %s)",
origin.get(), filename.get(), lineNum, columnNum, knownTextBitmask,
- CanvasFingerprinterToString(fingerprinter)));
+ CanvasFingerprinterToString(fingerprinter),
+ CanvasUsageSourceToString(aSource)));
}
ContentBlockingNotifier::OnEvent(
aChannel, false,
- nsIWebProgressListener::STATE_ALLOWED_CANVAS_FINGERPRINTING,
- aOriginNoSuffix, Nothing(), Some(event));
+ nsIWebProgressListener::STATE_ALLOWED_CANVAS_FINGERPRINTING, origin,
+ Nothing(), Some(event));
}
/* static */ void nsRFPService::MaybeReportFontFingerprinter(
- nsIChannel* aChannel, const nsACString& aOriginNoSuffix) {
+ nsIChannel* aChannel, const nsACString& aURI,
+ const nsACString& aOriginNoSuffix) {
if (!aChannel) {
return;
}
@@ -2093,29 +2434,32 @@ static void MaybeCurrentCaller(nsACString& aFilename, uint32_t& aLineNum,
NS_DispatchToMainThread(NS_NewRunnableFunction(
"nsRFPService::MaybeReportFontFingerprinter",
[channel = nsCOMPtr{aChannel},
- originNoSuffix = nsCString(aOriginNoSuffix)]() {
- nsRFPService::MaybeReportFontFingerprinter(channel, originNoSuffix);
+ originNoSuffix = nsCString(aOriginNoSuffix), uri = nsCString(aURI)]() {
+ nsRFPService::MaybeReportFontFingerprinter(channel, uri,
+ originNoSuffix);
}));
return;
}
+ nsAutoCString uri(aURI);
+ nsAutoCString origin(aOriginNoSuffix);
+
if (MOZ_LOG_TEST(gFingerprinterDetection, LogLevel::Info)) {
nsAutoCString filename;
uint32_t lineNum = 0;
uint32_t columnNum = 0;
MaybeCurrentCaller(filename, lineNum, columnNum);
- nsAutoCString origin(aOriginNoSuffix);
MOZ_LOG(gFingerprinterDetection, LogLevel::Info,
- ("Detected a potential font fingerprinter on %s in script %s:%d:%d",
- origin.get(), filename.get(), lineNum, columnNum));
+ ("Detected a potential font fingerprinter on %s on %s in script "
+ "%s:%d:%d",
+ origin.get(), uri.get(), filename.get(), lineNum, columnNum));
}
ContentBlockingNotifier::OnEvent(
aChannel, false,
- nsIWebProgressListener::STATE_ALLOWED_FONT_FINGERPRINTING,
- aOriginNoSuffix);
+ nsIWebProgressListener::STATE_ALLOWED_FONT_FINGERPRINTING, origin);
}
/* static */
diff --git a/toolkit/components/resistfingerprinting/nsRFPService.h b/toolkit/components/resistfingerprinting/nsRFPService.h
@@ -69,6 +69,8 @@ struct JSContext;
class nsIChannel;
+class nsICanvasRenderingContextInternal;
+
namespace mozilla {
class WidgetKeyboardEvent;
class OriginAttributes;
@@ -234,15 +236,93 @@ enum CanvasFingerprinterAlias {
eLastAlias = eVariant8
};
+enum CanvasExtractionAPI : uint8_t {
+ ToDataURL = 0,
+ ToBlob = 1,
+ GetImageData = 2,
+ ReadPixels = 3
+};
+
+enum CanvasUsageSource : uint64_t {
+ Unknown = 0,
+ Impossible =
+ 1llu << 0, // This represents API calls that I don't believe are possible
+ MainThread_Canvas_ImageBitmap_toDataURL = 1llu << 1,
+ MainThread_Canvas_ImageBitmap_toBlob = 1llu << 2,
+ MainThread_Canvas_ImageBitmap_getImageData = 1llu << 3,
+
+ MainThread_Canvas_Canvas2D_toDataURL = 1llu << 4,
+ MainThread_Canvas_Canvas2D_toBlob = 1llu << 5,
+ MainThread_Canvas_Canvas2D_getImageData = 1llu << 6,
+
+ MainThread_Canvas_WebGL_toDataURL = 1llu << 7,
+ MainThread_Canvas_WebGL_toBlob = 1llu << 8,
+ MainThread_Canvas_WebGL_getImageData = 1llu << 9,
+ MainThread_Canvas_WebGL_readPixels = 1llu << 10,
+
+ MainThread_Canvas_WebGPU_toDataURL = 1llu << 11,
+ MainThread_Canvas_WebGPU_toBlob = 1llu << 12,
+ MainThread_Canvas_WebGPU_getImageData = 1llu << 13,
+
+ MainThread_OffscreenCanvas_ImageBitmap_toDataURL = 1llu << 14,
+ MainThread_OffscreenCanvas_ImageBitmap_toBlob = 1llu << 15,
+ MainThread_OffscreenCanvas_ImageBitmap_getImageData = 1llu << 16,
+
+ MainThread_OffscreenCanvas_Canvas2D_toDataURL = 1llu << 17,
+ MainThread_OffscreenCanvas_Canvas2D_toBlob = 1llu << 18,
+ MainThread_OffscreenCanvas_Canvas2D_getImageData = 1llu << 19,
+
+ MainThread_OffscreenCanvas_WebGL_toDataURL = 1llu << 20,
+ MainThread_OffscreenCanvas_WebGL_toBlob = 1llu << 21,
+ MainThread_OffscreenCanvas_WebGL_getImageData = 1llu << 22,
+ MainThread_OffscreenCanvas_WebGL_readPixels = 1llu << 23,
+
+ MainThread_OffscreenCanvas_WebGPU_toDataURL = 1llu << 24,
+ MainThread_OffscreenCanvas_WebGPU_toBlob = 1llu << 25,
+ MainThread_OffscreenCanvas_WebGPU_getImageData = 1llu << 26,
+
+ Worker_OffscreenCanvas_ImageBitmap_toBlob = 1llu << 27,
+ Worker_OffscreenCanvas_ImageBitmap_getImageData = 1llu << 28,
+
+ Worker_OffscreenCanvas_Canvas2D_toBlob = 1llu << 29,
+ Worker_OffscreenCanvas_Canvas2D_getImageData = 1llu << 30,
+
+ Worker_OffscreenCanvasCanvas2D_Canvas2D_toBlob = 1llu << 31,
+ Worker_OffscreenCanvasCanvas2D_Canvas2D_getImageData = 1llu << 32,
+
+ Worker_OffscreenCanvas_WebGL_toBlob = 1llu << 33,
+ Worker_OffscreenCanvas_WebGL_getImageData = 1llu << 34,
+ Worker_OffscreenCanvas_WebGL_readPixels = 1llu << 35,
+
+ Worker_OffscreenCanvas_WebGPU_toBlob = 1llu << 36,
+ Worker_OffscreenCanvas_WebGPU_getImageData = 1llu << 37,
+
+};
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(CanvasUsageSource);
+nsCString CanvasUsageSourceToString(CanvasUsageSource aSource);
+
class CanvasUsage {
public:
CSSIntSize mSize;
dom::CanvasContextType mType;
+ CanvasUsageSource mUsageSource;
CanvasFeatureUsage mFeatureUsage;
CanvasUsage(CSSIntSize aSize, dom::CanvasContextType aType,
- CanvasFeatureUsage aFeatureUsage)
- : mSize(aSize), mType(aType), mFeatureUsage(aFeatureUsage) {}
+ CanvasUsageSource aUsageSource, CanvasFeatureUsage aFeatureUsage)
+ : mSize(aSize),
+ mType(aType),
+ mUsageSource(aUsageSource),
+ mFeatureUsage(aFeatureUsage) {}
+
+ static CanvasUsage CreateUsage(
+ bool aIsOffscreen, dom::CanvasContextType aContextType,
+ CanvasExtractionAPI aApi, CSSIntSize aSize,
+ const nsICanvasRenderingContextInternal* aContext);
+
+ static inline CanvasUsageSource GetCanvasUsageSource(
+ bool isOffscreen, dom::CanvasContextType contextType,
+ CanvasExtractionAPI api);
};
struct CanvasFingerprintingEvent {
// The identity or alias of the entity doing the fingerprinting.
@@ -251,21 +331,24 @@ struct CanvasFingerprintingEvent {
// non-zero indicates some known fingerprinting text was used, making the
// event highly likely to be fingerprinting.
uint32_t knownTextBitmask;
- // The API source of the fingerprinting, e.g. <canvas>.toDataURL
- uint8_t source;
+ // A bitmap of all the sources that were used to extract canvas data
+ uint64_t sourcesBitmask;
CanvasFingerprintingEvent()
: alias(CanvasFingerprinterAlias::eNoneIdentified),
knownTextBitmask(0),
- source(0) {}
+ sourcesBitmask(0) {}
CanvasFingerprintingEvent(CanvasFingerprinterAlias aAlias,
- uint32_t aKnownTextBitmask, uint8_t aSource)
- : alias(aAlias), knownTextBitmask(aKnownTextBitmask), source(aSource) {}
+ uint32_t aKnownTextBitmask,
+ uint64_t aSourcesBitmask)
+ : alias(aAlias),
+ knownTextBitmask(aKnownTextBitmask),
+ sourcesBitmask(aSourcesBitmask) {}
bool operator==(const CanvasFingerprintingEvent& other) const {
return alias == other.alias && knownTextBitmask == other.knownTextBitmask &&
- source == other.source;
+ sourcesBitmask == other.sourcesBitmask;
}
};
@@ -466,9 +549,11 @@ class nsRFPService final : public nsIObserver, public nsIRFPService {
static void MaybeReportCanvasFingerprinter(nsTArray<CanvasUsage>& aUses,
nsIChannel* aChannel,
- nsACString& aOriginNoSuffix);
+ const nsACString& aURI,
+ const nsACString& aOriginNoSuffix);
static void MaybeReportFontFingerprinter(nsIChannel* aChannel,
+ const nsACString& aURI,
const nsACString& aOriginNoSuffix);
// --------------------------------------------------------------------------