
morgan pushed to branch tor-browser-140.2.0esr-15.0-1 at The Tor Project / Applications / Tor Browser Commits: 64132f03 by Fatih Kilic at 2025-09-09T17:36:29+00:00 Bug 1973265 - Put WebCodecs API behind RFP Target. r=tjr,webidl,smaug Differential Revision: https://phabricator.services.mozilla.com/D254549 - - - - - 17 changed files: - dom/base/nsContentUtils.cpp - dom/media/webcodecs/VideoFrame.cpp - dom/media/webcodecs/test/mochitest.toml - + dom/media/webcodecs/test/test_rfp_api_disabling_disabled.html - + dom/media/webcodecs/test/test_rfp_api_disabling_enabled.html - + dom/media/webcodecs/test/test_rfp_api_disabling_exemption.html - dom/webidl/AudioData.webidl - dom/webidl/AudioDecoder.webidl - dom/webidl/AudioEncoder.webidl - dom/webidl/EncodedAudioChunk.webidl - dom/webidl/EncodedVideoChunk.webidl - dom/webidl/ImageDecoder.webidl - dom/webidl/VideoDecoder.webidl - dom/webidl/VideoEncoder.webidl - toolkit/components/resistfingerprinting/RFPTargets.inc - toolkit/components/resistfingerprinting/nsRFPService.cpp - toolkit/components/resistfingerprinting/nsRFPService.h Changes: ===================================== dom/base/nsContentUtils.cpp ===================================== @@ -2844,7 +2844,7 @@ bool nsContentUtils::ShouldResistFingerprinting_dangerous( } // Web extension principals are also excluded - if (BasePrincipal::Cast(aPrincipal)->AddonPolicy()) { + if (NS_IsMainThread() && BasePrincipal::Cast(aPrincipal)->AddonPolicy()) { MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug, ("Inside ShouldResistFingerprinting(nsIPrincipal*)" " and AddonPolicy said false")); ===================================== dom/media/webcodecs/VideoFrame.cpp ===================================== @@ -1423,7 +1423,7 @@ JSObject* VideoFrame::WrapObject(JSContext* aCx, /* static */ bool VideoFrame::PrefEnabled(JSContext* aCx, JSObject* aObj) { - return StaticPrefs::dom_media_webcodecs_enabled() || + return nsRFPService::ExposeWebCodecsAPI(aCx, aObj) && StaticPrefs::dom_media_webcodecs_image_decoder_enabled(); } ===================================== dom/media/webcodecs/test/mochitest.toml ===================================== @@ -16,4 +16,13 @@ scheme = "https" ["test_imageDecoder_desiredSize.html"] scheme = "https" +["test_rfp_api_disabling_disabled.html"] +scheme = "https" + +["test_rfp_api_disabling_enabled.html"] +scheme = "https" + +["test_rfp_api_disabling_exemption.html"] +scheme = "https" + ["test_videoFrame_mismatched_codedSize.html"] ===================================== dom/media/webcodecs/test/test_rfp_api_disabling_disabled.html ===================================== @@ -0,0 +1,67 @@ +<!DOCTYPE html> +<html> +<head> +<title></title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script> +const apis = [ + "AudioData", + "AudioDecoder", + "AudioEncoder", + "EncodedAudioChunk", + "EncodedVideoChunk", + "ImageDecoder", + "ImageTrack", + "ImageTrackList", + "VideoColorSpace", + "VideoDecoder", + "VideoEncoder", + "VideoFrame", +]; + +function enabledAPIs() { + return apis.filter(api => typeof window[api] !== "undefined"); +} + +function enabledAPIsWorker() { + const code = ` + onmessage = e => { + const apis = ${JSON.stringify(apis)}; + postMessage(apis.filter(api => typeof self[api] !== "undefined")); + };`; + const blob = new Blob([code], { type: "application/javascript" }); + const worker = new Worker(URL.createObjectURL(blob)); + + return new Promise((resolve) => { + worker.addEventListener("message", async (e) => { + worker.terminate(); + resolve(e.data); + }); + + worker.postMessage({}); + }); +} + +add_setup(async () => { + await SpecialPowers.pushPrefEnv({ + set: [ + ["dom.media.webcodecs.enabled", true], + ["dom.media.webcodecs.image-decoder.enabled", true] + ], + }); +}); + +add_task(async () => { + is(enabledAPIs().length, apis.length, true, "All WebCodecs APIs should be enabled"); + is( + (await enabledAPIsWorker()).length, + apis.length, + "All WebCodecs APIs should be enabled in workers too" + ); +}); +</script> +</body> +</html> ===================================== dom/media/webcodecs/test/test_rfp_api_disabling_enabled.html ===================================== @@ -0,0 +1,69 @@ +<!DOCTYPE html> +<html> +<head> +<title></title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script> +const apis = [ + "AudioData", + "AudioDecoder", + "AudioEncoder", + "EncodedAudioChunk", + "EncodedVideoChunk", + "ImageDecoder", + "ImageTrack", + "ImageTrackList", + "VideoColorSpace", + "VideoDecoder", + "VideoEncoder", + "VideoFrame", +]; + +function enabledAPIs() { + return apis.filter(api => typeof window[api] !== "undefined"); +} + +function enabledAPIsWorker() { + const code = ` + onmessage = e => { + const apis = ${JSON.stringify(apis)}; + postMessage(apis.filter(api => typeof self[api] !== "undefined")); + };`; + const blob = new Blob([code], { type: "application/javascript" }); + const worker = new Worker(URL.createObjectURL(blob)); + + return new Promise((resolve) => { + worker.addEventListener("message", async (e) => { + worker.terminate(); + resolve(e.data); + }); + + worker.postMessage({}); + }); +} + +add_setup(async () => { + await SpecialPowers.pushPrefEnv({ + set: [ + ["dom.media.webcodecs.enabled", true], + ["dom.media.webcodecs.image-decoder.enabled", true], + ["privacy.fingerprintingProtection", true], + ["privacy.fingerprintingProtection.overrides", "-AllTargets,+WebCodecs"], + ], + }); +}); + +add_task(async () => { + is(enabledAPIs().length, 0, true, "All WebCodecs APIs should be disabled"); + is( + (await enabledAPIsWorker()).length, + 0, + "All WebCodecs APIs should be disabled in workers too" + ); +}); +</script> +</body> +</html> ===================================== dom/media/webcodecs/test/test_rfp_api_disabling_exemption.html ===================================== @@ -0,0 +1,70 @@ +<!DOCTYPE html> +<html> +<head> +<title></title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script> +const apis = [ + "AudioData", + "AudioDecoder", + "AudioEncoder", + "EncodedAudioChunk", + "EncodedVideoChunk", + "ImageDecoder", + "ImageTrack", + "ImageTrackList", + "VideoColorSpace", + "VideoDecoder", + "VideoEncoder", + "VideoFrame", +]; + +function enabledAPIs() { + return apis.filter(api => typeof window[api] !== "undefined"); +} + +function enabledAPIsWorker() { + const code = ` + onmessage = e => { + const apis = ${JSON.stringify(apis)}; + postMessage(apis.filter(api => typeof self[api] !== "undefined")); + };`; + const blob = new Blob([code], { type: "application/javascript" }); + const worker = new Worker(URL.createObjectURL(blob)); + + return new Promise((resolve) => { + worker.addEventListener("message", async (e) => { + worker.terminate(); + resolve(e.data); + }); + + worker.postMessage({}); + }); +} + +add_setup(async () => { + await SpecialPowers.pushPrefEnv({ + set: [ + ["dom.media.webcodecs.enabled", true], + ["dom.media.webcodecs.image-decoder.enabled", true], + ["privacy.fingerprintingProtection", true], + ["privacy.fingerprintingProtection.overrides", "-AllTargets,+WebCodecs"], + ["privacy.resistFingerprinting.exemptedDomains", location.hostname] + ], + }); +}); + +add_task(async () => { + is(enabledAPIs().length, apis.length, true, "All WebCodecs APIs should be disabled"); + is( + (await enabledAPIsWorker()).length, + apis.length, + "All WebCodecs APIs should be disabled in workers too" + ); +}); +</script> +</body> +</html> ===================================== dom/webidl/AudioData.webidl ===================================== @@ -9,7 +9,7 @@ // [Serializable, Transferable] are implemented without adding attributes here, // but directly with {Read,Write}StructuredClone and Transfer/FromTransfered. -[Exposed=(Window,DedicatedWorker), Pref="dom.media.webcodecs.enabled"] +[Exposed=(Window,DedicatedWorker), Func="nsRFPService::ExposeWebCodecsAPI"] interface AudioData { [Throws] constructor(AudioDataInit init); ===================================== dom/webidl/AudioDecoder.webidl ===================================== @@ -7,7 +7,7 @@ * https://w3c.github.io/webcodecs/#audiodecoder */ -[Exposed=(Window,DedicatedWorker), SecureContext, Pref="dom.media.webcodecs.enabled"] +[Exposed=(Window,DedicatedWorker), SecureContext, Func="nsRFPService::ExposeWebCodecsAPI"] interface AudioDecoder : EventTarget { [Throws] constructor(AudioDecoderInit init); ===================================== dom/webidl/AudioEncoder.webidl ===================================== @@ -42,7 +42,7 @@ dictionary OpusEncoderConfig { boolean usedtx = false; }; -[Exposed=(Window,DedicatedWorker), SecureContext, Pref="dom.media.webcodecs.enabled"] +[Exposed=(Window,DedicatedWorker), SecureContext, Func="nsRFPService::ExposeWebCodecsAPI"] interface AudioEncoder : EventTarget { [Throws] constructor(AudioEncoderInit init); ===================================== dom/webidl/EncodedAudioChunk.webidl ===================================== @@ -8,7 +8,7 @@ */ // [Serializable] is implemented without adding attribute here. -[Exposed=(Window,DedicatedWorker), Pref="dom.media.webcodecs.enabled"] +[Exposed=(Window,DedicatedWorker), Func="nsRFPService::ExposeWebCodecsAPI"] interface EncodedAudioChunk { [Throws] constructor(EncodedAudioChunkInit init); ===================================== dom/webidl/EncodedVideoChunk.webidl ===================================== @@ -8,7 +8,7 @@ */ // [Serializable] is implemented without adding attribute here. -[Exposed=(Window,DedicatedWorker), Pref="dom.media.webcodecs.enabled"] +[Exposed=(Window,DedicatedWorker), Func="nsRFPService::ExposeWebCodecsAPI"] interface EncodedVideoChunk { [Throws] constructor(EncodedVideoChunkInit init); ===================================== dom/webidl/ImageDecoder.webidl ===================================== @@ -30,7 +30,7 @@ dictionary ImageDecodeResult { [Exposed=(Window,DedicatedWorker), SecureContext, - Pref="dom.media.webcodecs.image-decoder.enabled"] + Func="nsRFPService::ExposeWebCodecsAPIImageDecoder"] interface ImageTrack { readonly attribute boolean animated; readonly attribute unsigned long frameCount; @@ -40,7 +40,7 @@ interface ImageTrack { [Exposed=(Window,DedicatedWorker), SecureContext, - Pref="dom.media.webcodecs.image-decoder.enabled"] + Func="nsRFPService::ExposeWebCodecsAPIImageDecoder"] interface ImageTrackList { getter ImageTrack (unsigned long index); @@ -52,7 +52,7 @@ interface ImageTrackList { [Exposed=(Window,DedicatedWorker), SecureContext, - Pref="dom.media.webcodecs.image-decoder.enabled"] + Func="nsRFPService::ExposeWebCodecsAPIImageDecoder"] interface ImageDecoder { [Throws] constructor(ImageDecoderInit init); ===================================== dom/webidl/VideoDecoder.webidl ===================================== @@ -7,7 +7,7 @@ * https://w3c.github.io/webcodecs/#videodecoder */ -[Exposed=(Window,DedicatedWorker), SecureContext, Pref="dom.media.webcodecs.enabled"] +[Exposed=(Window,DedicatedWorker), SecureContext, Func="nsRFPService::ExposeWebCodecsAPI"] interface VideoDecoder : EventTarget { [Throws] constructor(VideoDecoderInit init); ===================================== dom/webidl/VideoEncoder.webidl ===================================== @@ -12,7 +12,7 @@ * commented with a link of the document in which the member is listed. */ -[Exposed=(Window,DedicatedWorker), SecureContext, Pref="dom.media.webcodecs.enabled"] +[Exposed=(Window,DedicatedWorker), SecureContext, Func="nsRFPService::ExposeWebCodecsAPI"] interface VideoEncoder : EventTarget { [Throws] constructor(VideoEncoderInit init); ===================================== toolkit/components/resistfingerprinting/RFPTargets.inc ===================================== @@ -101,6 +101,7 @@ ITEM_VALUE(JSLocalePrompt, 67) ITEM_VALUE(ScreenAvailToResolution, 68) ITEM_VALUE(UseHardcodedFontSubstitutes, 69) ITEM_VALUE(DiskStorageLimit, 70) +ITEM_VALUE(WebCodecs, 71) // !!! Don't forget to update kDefaultFingerprintingProtections in nsRFPService.cpp ===================================== toolkit/components/resistfingerprinting/nsRFPService.cpp ===================================== @@ -2697,3 +2697,48 @@ uint64_t nsRFPService::GetSpoofedStorageLimit() { return limit; } + +/* static */ +bool nsRFPService::ExposeWebCodecsAPI(JSContext* aCx, JSObject* aObj) { + if (!StaticPrefs::dom_media_webcodecs_enabled()) { + return false; + } + + return !IsWebCodecsRFPTargetEnabled(aCx); +} + +/* static */ +bool nsRFPService::ExposeWebCodecsAPIImageDecoder(JSContext* aCx, + JSObject* aObj) { + if (!StaticPrefs::dom_media_webcodecs_image_decoder_enabled()) { + return false; + } + + return !IsWebCodecsRFPTargetEnabled(aCx); +} + +/* static */ +bool nsRFPService::IsWebCodecsRFPTargetEnabled(JSContext* aCx) { + if (!nsContentUtils::ShouldResistFingerprinting("Efficiency check", + RFPTarget::WebCodecs)) { + return false; + } + + // We know that the RFPTarget::WebCodecs is enabled, check if principal + // is exempted. + + // VideoFrame::PrefEnabled function can be called without a JSContext. + if (!aCx) { + return true; + } + + // Once bug 1973966 is resolved, we can replace this section with just + // nsIPrincipal* principal = nsContentUtils::SubjectPrincipal(aCx); + JS::Realm* realm = js::GetContextRealm(aCx); + MOZ_ASSERT(realm); + JSPrincipals* principals = JS::GetRealmPrincipals(realm); + nsIPrincipal* principal = nsJSPrincipals::get(principals); + + return nsContentUtils::ShouldResistFingerprinting_dangerous( + principal, "Principal is the best context we have", RFPTarget::WebCodecs); +} ===================================== toolkit/components/resistfingerprinting/nsRFPService.h ===================================== @@ -416,6 +416,9 @@ class nsRFPService final : public nsIObserver, public nsIRFPService { static uint64_t GetSpoofedStorageLimit(); + static bool ExposeWebCodecsAPI(JSContext* aCx, JSObject* aObj); + static bool ExposeWebCodecsAPIImageDecoder(JSContext* aCx, JSObject* aObj); + private: nsresult Init(); @@ -527,6 +530,8 @@ class nsRFPService final : public nsIObserver, public nsIRFPService { static bool IsTargetActiveForMode(RFPTarget aTarget, FingerprintingProtectionType aMode); + static bool IsWebCodecsRFPTargetEnabled(JSContext* aCx); + static nsCString* sExemptedDomainsLowercase; }; View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/64132f03... -- View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/64132f03... You're receiving this email because of your account on gitlab.torproject.org.
participants (1)
-
morgan (@morgan)