morgan pushed to branch base-browser-140.2.0esr-15.0-1 at The Tor Project / Applications / Tor Browser
Commits:
-
9ae87bd3
by Fatih Kilic at 2025-09-09T17:41:43+00:00
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:
| ... | ... | @@ -2844,7 +2844,7 @@ bool nsContentUtils::ShouldResistFingerprinting_dangerous( |
| 2844 | 2844 | }
|
| 2845 | 2845 | |
| 2846 | 2846 | // Web extension principals are also excluded
|
| 2847 | - if (BasePrincipal::Cast(aPrincipal)->AddonPolicy()) {
|
|
| 2847 | + if (NS_IsMainThread() && BasePrincipal::Cast(aPrincipal)->AddonPolicy()) {
|
|
| 2848 | 2848 | MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
|
| 2849 | 2849 | ("Inside ShouldResistFingerprinting(nsIPrincipal*)"
|
| 2850 | 2850 | " and AddonPolicy said false"));
|
| ... | ... | @@ -1423,7 +1423,7 @@ JSObject* VideoFrame::WrapObject(JSContext* aCx, |
| 1423 | 1423 | |
| 1424 | 1424 | /* static */
|
| 1425 | 1425 | bool VideoFrame::PrefEnabled(JSContext* aCx, JSObject* aObj) {
|
| 1426 | - return StaticPrefs::dom_media_webcodecs_enabled() ||
|
|
| 1426 | + return nsRFPService::ExposeWebCodecsAPI(aCx, aObj) &&
|
|
| 1427 | 1427 | StaticPrefs::dom_media_webcodecs_image_decoder_enabled();
|
| 1428 | 1428 | }
|
| 1429 | 1429 |
| ... | ... | @@ -16,4 +16,13 @@ scheme = "https" |
| 16 | 16 | ["test_imageDecoder_desiredSize.html"]
|
| 17 | 17 | scheme = "https"
|
| 18 | 18 | |
| 19 | +["test_rfp_api_disabling_disabled.html"]
|
|
| 20 | +scheme = "https"
|
|
| 21 | + |
|
| 22 | +["test_rfp_api_disabling_enabled.html"]
|
|
| 23 | +scheme = "https"
|
|
| 24 | + |
|
| 25 | +["test_rfp_api_disabling_exemption.html"]
|
|
| 26 | +scheme = "https"
|
|
| 27 | + |
|
| 19 | 28 | ["test_videoFrame_mismatched_codedSize.html"] |
| 1 | +<!DOCTYPE html>
|
|
| 2 | +<html>
|
|
| 3 | +<head>
|
|
| 4 | +<title></title>
|
|
| 5 | +<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
|
| 6 | +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
|
| 7 | +</head>
|
|
| 8 | +<body>
|
|
| 9 | +<script>
|
|
| 10 | +const apis = [
|
|
| 11 | + "AudioData",
|
|
| 12 | + "AudioDecoder",
|
|
| 13 | + "AudioEncoder",
|
|
| 14 | + "EncodedAudioChunk",
|
|
| 15 | + "EncodedVideoChunk",
|
|
| 16 | + "ImageDecoder",
|
|
| 17 | + "ImageTrack",
|
|
| 18 | + "ImageTrackList",
|
|
| 19 | + "VideoColorSpace",
|
|
| 20 | + "VideoDecoder",
|
|
| 21 | + "VideoEncoder",
|
|
| 22 | + "VideoFrame",
|
|
| 23 | +];
|
|
| 24 | + |
|
| 25 | +function enabledAPIs() {
|
|
| 26 | + return apis.filter(api => typeof window[api] !== "undefined");
|
|
| 27 | +}
|
|
| 28 | + |
|
| 29 | +function enabledAPIsWorker() {
|
|
| 30 | + const code = `
|
|
| 31 | + onmessage = e => {
|
|
| 32 | + const apis = ${JSON.stringify(apis)};
|
|
| 33 | + postMessage(apis.filter(api => typeof self[api] !== "undefined"));
|
|
| 34 | + };`;
|
|
| 35 | + const blob = new Blob([code], { type: "application/javascript" });
|
|
| 36 | + const worker = new Worker(URL.createObjectURL(blob));
|
|
| 37 | + |
|
| 38 | + return new Promise((resolve) => {
|
|
| 39 | + worker.addEventListener("message", async (e) => {
|
|
| 40 | + worker.terminate();
|
|
| 41 | + resolve(e.data);
|
|
| 42 | + });
|
|
| 43 | + |
|
| 44 | + worker.postMessage({});
|
|
| 45 | + });
|
|
| 46 | +}
|
|
| 47 | + |
|
| 48 | +add_setup(async () => {
|
|
| 49 | + await SpecialPowers.pushPrefEnv({
|
|
| 50 | + set: [
|
|
| 51 | + ["dom.media.webcodecs.enabled", true],
|
|
| 52 | + ["dom.media.webcodecs.image-decoder.enabled", true]
|
|
| 53 | + ],
|
|
| 54 | + });
|
|
| 55 | +});
|
|
| 56 | + |
|
| 57 | +add_task(async () => {
|
|
| 58 | + is(enabledAPIs().length, apis.length, true, "All WebCodecs APIs should be enabled");
|
|
| 59 | + is(
|
|
| 60 | + (await enabledAPIsWorker()).length,
|
|
| 61 | + apis.length,
|
|
| 62 | + "All WebCodecs APIs should be enabled in workers too"
|
|
| 63 | + );
|
|
| 64 | +});
|
|
| 65 | +</script>
|
|
| 66 | +</body>
|
|
| 67 | +</html> |
| 1 | +<!DOCTYPE html>
|
|
| 2 | +<html>
|
|
| 3 | +<head>
|
|
| 4 | +<title></title>
|
|
| 5 | +<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
|
| 6 | +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
|
| 7 | +</head>
|
|
| 8 | +<body>
|
|
| 9 | +<script>
|
|
| 10 | +const apis = [
|
|
| 11 | + "AudioData",
|
|
| 12 | + "AudioDecoder",
|
|
| 13 | + "AudioEncoder",
|
|
| 14 | + "EncodedAudioChunk",
|
|
| 15 | + "EncodedVideoChunk",
|
|
| 16 | + "ImageDecoder",
|
|
| 17 | + "ImageTrack",
|
|
| 18 | + "ImageTrackList",
|
|
| 19 | + "VideoColorSpace",
|
|
| 20 | + "VideoDecoder",
|
|
| 21 | + "VideoEncoder",
|
|
| 22 | + "VideoFrame",
|
|
| 23 | +];
|
|
| 24 | + |
|
| 25 | +function enabledAPIs() {
|
|
| 26 | + return apis.filter(api => typeof window[api] !== "undefined");
|
|
| 27 | +}
|
|
| 28 | + |
|
| 29 | +function enabledAPIsWorker() {
|
|
| 30 | + const code = `
|
|
| 31 | + onmessage = e => {
|
|
| 32 | + const apis = ${JSON.stringify(apis)};
|
|
| 33 | + postMessage(apis.filter(api => typeof self[api] !== "undefined"));
|
|
| 34 | + };`;
|
|
| 35 | + const blob = new Blob([code], { type: "application/javascript" });
|
|
| 36 | + const worker = new Worker(URL.createObjectURL(blob));
|
|
| 37 | + |
|
| 38 | + return new Promise((resolve) => {
|
|
| 39 | + worker.addEventListener("message", async (e) => {
|
|
| 40 | + worker.terminate();
|
|
| 41 | + resolve(e.data);
|
|
| 42 | + });
|
|
| 43 | + |
|
| 44 | + worker.postMessage({});
|
|
| 45 | + });
|
|
| 46 | +}
|
|
| 47 | + |
|
| 48 | +add_setup(async () => {
|
|
| 49 | + await SpecialPowers.pushPrefEnv({
|
|
| 50 | + set: [
|
|
| 51 | + ["dom.media.webcodecs.enabled", true],
|
|
| 52 | + ["dom.media.webcodecs.image-decoder.enabled", true],
|
|
| 53 | + ["privacy.fingerprintingProtection", true],
|
|
| 54 | + ["privacy.fingerprintingProtection.overrides", "-AllTargets,+WebCodecs"],
|
|
| 55 | + ],
|
|
| 56 | + });
|
|
| 57 | +});
|
|
| 58 | + |
|
| 59 | +add_task(async () => {
|
|
| 60 | + is(enabledAPIs().length, 0, true, "All WebCodecs APIs should be disabled");
|
|
| 61 | + is(
|
|
| 62 | + (await enabledAPIsWorker()).length,
|
|
| 63 | + 0,
|
|
| 64 | + "All WebCodecs APIs should be disabled in workers too"
|
|
| 65 | + );
|
|
| 66 | +});
|
|
| 67 | +</script>
|
|
| 68 | +</body>
|
|
| 69 | +</html> |
| 1 | +<!DOCTYPE html>
|
|
| 2 | +<html>
|
|
| 3 | +<head>
|
|
| 4 | +<title></title>
|
|
| 5 | +<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
|
| 6 | +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
|
| 7 | +</head>
|
|
| 8 | +<body>
|
|
| 9 | +<script>
|
|
| 10 | +const apis = [
|
|
| 11 | + "AudioData",
|
|
| 12 | + "AudioDecoder",
|
|
| 13 | + "AudioEncoder",
|
|
| 14 | + "EncodedAudioChunk",
|
|
| 15 | + "EncodedVideoChunk",
|
|
| 16 | + "ImageDecoder",
|
|
| 17 | + "ImageTrack",
|
|
| 18 | + "ImageTrackList",
|
|
| 19 | + "VideoColorSpace",
|
|
| 20 | + "VideoDecoder",
|
|
| 21 | + "VideoEncoder",
|
|
| 22 | + "VideoFrame",
|
|
| 23 | +];
|
|
| 24 | + |
|
| 25 | +function enabledAPIs() {
|
|
| 26 | + return apis.filter(api => typeof window[api] !== "undefined");
|
|
| 27 | +}
|
|
| 28 | + |
|
| 29 | +function enabledAPIsWorker() {
|
|
| 30 | + const code = `
|
|
| 31 | + onmessage = e => {
|
|
| 32 | + const apis = ${JSON.stringify(apis)};
|
|
| 33 | + postMessage(apis.filter(api => typeof self[api] !== "undefined"));
|
|
| 34 | + };`;
|
|
| 35 | + const blob = new Blob([code], { type: "application/javascript" });
|
|
| 36 | + const worker = new Worker(URL.createObjectURL(blob));
|
|
| 37 | + |
|
| 38 | + return new Promise((resolve) => {
|
|
| 39 | + worker.addEventListener("message", async (e) => {
|
|
| 40 | + worker.terminate();
|
|
| 41 | + resolve(e.data);
|
|
| 42 | + });
|
|
| 43 | + |
|
| 44 | + worker.postMessage({});
|
|
| 45 | + });
|
|
| 46 | +}
|
|
| 47 | + |
|
| 48 | +add_setup(async () => {
|
|
| 49 | + await SpecialPowers.pushPrefEnv({
|
|
| 50 | + set: [
|
|
| 51 | + ["dom.media.webcodecs.enabled", true],
|
|
| 52 | + ["dom.media.webcodecs.image-decoder.enabled", true],
|
|
| 53 | + ["privacy.fingerprintingProtection", true],
|
|
| 54 | + ["privacy.fingerprintingProtection.overrides", "-AllTargets,+WebCodecs"],
|
|
| 55 | + ["privacy.resistFingerprinting.exemptedDomains", location.hostname]
|
|
| 56 | + ],
|
|
| 57 | + });
|
|
| 58 | +});
|
|
| 59 | + |
|
| 60 | +add_task(async () => {
|
|
| 61 | + is(enabledAPIs().length, apis.length, true, "All WebCodecs APIs should be disabled");
|
|
| 62 | + is(
|
|
| 63 | + (await enabledAPIsWorker()).length,
|
|
| 64 | + apis.length,
|
|
| 65 | + "All WebCodecs APIs should be disabled in workers too"
|
|
| 66 | + );
|
|
| 67 | +});
|
|
| 68 | +</script>
|
|
| 69 | +</body>
|
|
| 70 | +</html> |
| ... | ... | @@ -9,7 +9,7 @@ |
| 9 | 9 | |
| 10 | 10 | // [Serializable, Transferable] are implemented without adding attributes here,
|
| 11 | 11 | // but directly with {Read,Write}StructuredClone and Transfer/FromTransfered.
|
| 12 | -[Exposed=(Window,DedicatedWorker), Pref="dom.media.webcodecs.enabled"]
|
|
| 12 | +[Exposed=(Window,DedicatedWorker), Func="nsRFPService::ExposeWebCodecsAPI"]
|
|
| 13 | 13 | interface AudioData {
|
| 14 | 14 | [Throws]
|
| 15 | 15 | constructor(AudioDataInit init);
|
| ... | ... | @@ -7,7 +7,7 @@ |
| 7 | 7 | * https://w3c.github.io/webcodecs/#audiodecoder
|
| 8 | 8 | */
|
| 9 | 9 | |
| 10 | -[Exposed=(Window,DedicatedWorker), SecureContext, Pref="dom.media.webcodecs.enabled"]
|
|
| 10 | +[Exposed=(Window,DedicatedWorker), SecureContext, Func="nsRFPService::ExposeWebCodecsAPI"]
|
|
| 11 | 11 | interface AudioDecoder : EventTarget {
|
| 12 | 12 | [Throws]
|
| 13 | 13 | constructor(AudioDecoderInit init);
|
| ... | ... | @@ -42,7 +42,7 @@ dictionary OpusEncoderConfig { |
| 42 | 42 | boolean usedtx = false;
|
| 43 | 43 | };
|
| 44 | 44 | |
| 45 | -[Exposed=(Window,DedicatedWorker), SecureContext, Pref="dom.media.webcodecs.enabled"]
|
|
| 45 | +[Exposed=(Window,DedicatedWorker), SecureContext, Func="nsRFPService::ExposeWebCodecsAPI"]
|
|
| 46 | 46 | interface AudioEncoder : EventTarget {
|
| 47 | 47 | [Throws]
|
| 48 | 48 | constructor(AudioEncoderInit init);
|
| ... | ... | @@ -8,7 +8,7 @@ |
| 8 | 8 | */
|
| 9 | 9 | |
| 10 | 10 | // [Serializable] is implemented without adding attribute here.
|
| 11 | -[Exposed=(Window,DedicatedWorker), Pref="dom.media.webcodecs.enabled"]
|
|
| 11 | +[Exposed=(Window,DedicatedWorker), Func="nsRFPService::ExposeWebCodecsAPI"]
|
|
| 12 | 12 | interface EncodedAudioChunk {
|
| 13 | 13 | [Throws]
|
| 14 | 14 | constructor(EncodedAudioChunkInit init);
|
| ... | ... | @@ -8,7 +8,7 @@ |
| 8 | 8 | */
|
| 9 | 9 | |
| 10 | 10 | // [Serializable] is implemented without adding attribute here.
|
| 11 | -[Exposed=(Window,DedicatedWorker), Pref="dom.media.webcodecs.enabled"]
|
|
| 11 | +[Exposed=(Window,DedicatedWorker), Func="nsRFPService::ExposeWebCodecsAPI"]
|
|
| 12 | 12 | interface EncodedVideoChunk {
|
| 13 | 13 | [Throws]
|
| 14 | 14 | constructor(EncodedVideoChunkInit init);
|
| ... | ... | @@ -30,7 +30,7 @@ dictionary ImageDecodeResult { |
| 30 | 30 | |
| 31 | 31 | [Exposed=(Window,DedicatedWorker),
|
| 32 | 32 | SecureContext,
|
| 33 | - Pref="dom.media.webcodecs.image-decoder.enabled"]
|
|
| 33 | + Func="nsRFPService::ExposeWebCodecsAPIImageDecoder"]
|
|
| 34 | 34 | interface ImageTrack {
|
| 35 | 35 | readonly attribute boolean animated;
|
| 36 | 36 | readonly attribute unsigned long frameCount;
|
| ... | ... | @@ -40,7 +40,7 @@ interface ImageTrack { |
| 40 | 40 | |
| 41 | 41 | [Exposed=(Window,DedicatedWorker),
|
| 42 | 42 | SecureContext,
|
| 43 | - Pref="dom.media.webcodecs.image-decoder.enabled"]
|
|
| 43 | + Func="nsRFPService::ExposeWebCodecsAPIImageDecoder"]
|
|
| 44 | 44 | interface ImageTrackList {
|
| 45 | 45 | getter ImageTrack (unsigned long index);
|
| 46 | 46 | |
| ... | ... | @@ -52,7 +52,7 @@ interface ImageTrackList { |
| 52 | 52 | |
| 53 | 53 | [Exposed=(Window,DedicatedWorker),
|
| 54 | 54 | SecureContext,
|
| 55 | - Pref="dom.media.webcodecs.image-decoder.enabled"]
|
|
| 55 | + Func="nsRFPService::ExposeWebCodecsAPIImageDecoder"]
|
|
| 56 | 56 | interface ImageDecoder {
|
| 57 | 57 | [Throws]
|
| 58 | 58 | constructor(ImageDecoderInit init);
|
| ... | ... | @@ -7,7 +7,7 @@ |
| 7 | 7 | * https://w3c.github.io/webcodecs/#videodecoder
|
| 8 | 8 | */
|
| 9 | 9 | |
| 10 | -[Exposed=(Window,DedicatedWorker), SecureContext, Pref="dom.media.webcodecs.enabled"]
|
|
| 10 | +[Exposed=(Window,DedicatedWorker), SecureContext, Func="nsRFPService::ExposeWebCodecsAPI"]
|
|
| 11 | 11 | interface VideoDecoder : EventTarget {
|
| 12 | 12 | [Throws]
|
| 13 | 13 | constructor(VideoDecoderInit init);
|
| ... | ... | @@ -12,7 +12,7 @@ |
| 12 | 12 | * commented with a link of the document in which the member is listed.
|
| 13 | 13 | */
|
| 14 | 14 | |
| 15 | -[Exposed=(Window,DedicatedWorker), SecureContext, Pref="dom.media.webcodecs.enabled"]
|
|
| 15 | +[Exposed=(Window,DedicatedWorker), SecureContext, Func="nsRFPService::ExposeWebCodecsAPI"]
|
|
| 16 | 16 | interface VideoEncoder : EventTarget {
|
| 17 | 17 | [Throws]
|
| 18 | 18 | constructor(VideoEncoderInit init);
|
| ... | ... | @@ -101,6 +101,7 @@ ITEM_VALUE(JSLocalePrompt, 67) |
| 101 | 101 | ITEM_VALUE(ScreenAvailToResolution, 68)
|
| 102 | 102 | ITEM_VALUE(UseHardcodedFontSubstitutes, 69)
|
| 103 | 103 | ITEM_VALUE(DiskStorageLimit, 70)
|
| 104 | +ITEM_VALUE(WebCodecs, 71)
|
|
| 104 | 105 | |
| 105 | 106 | |
| 106 | 107 | // !!! Don't forget to update kDefaultFingerprintingProtections in nsRFPService.cpp
|
| ... | ... | @@ -2690,3 +2690,48 @@ uint64_t nsRFPService::GetSpoofedStorageLimit() { |
| 2690 | 2690 | |
| 2691 | 2691 | return limit;
|
| 2692 | 2692 | }
|
| 2693 | + |
|
| 2694 | +/* static */
|
|
| 2695 | +bool nsRFPService::ExposeWebCodecsAPI(JSContext* aCx, JSObject* aObj) {
|
|
| 2696 | + if (!StaticPrefs::dom_media_webcodecs_enabled()) {
|
|
| 2697 | + return false;
|
|
| 2698 | + }
|
|
| 2699 | + |
|
| 2700 | + return !IsWebCodecsRFPTargetEnabled(aCx);
|
|
| 2701 | +}
|
|
| 2702 | + |
|
| 2703 | +/* static */
|
|
| 2704 | +bool nsRFPService::ExposeWebCodecsAPIImageDecoder(JSContext* aCx,
|
|
| 2705 | + JSObject* aObj) {
|
|
| 2706 | + if (!StaticPrefs::dom_media_webcodecs_image_decoder_enabled()) {
|
|
| 2707 | + return false;
|
|
| 2708 | + }
|
|
| 2709 | + |
|
| 2710 | + return !IsWebCodecsRFPTargetEnabled(aCx);
|
|
| 2711 | +}
|
|
| 2712 | + |
|
| 2713 | +/* static */
|
|
| 2714 | +bool nsRFPService::IsWebCodecsRFPTargetEnabled(JSContext* aCx) {
|
|
| 2715 | + if (!nsContentUtils::ShouldResistFingerprinting("Efficiency check",
|
|
| 2716 | + RFPTarget::WebCodecs)) {
|
|
| 2717 | + return false;
|
|
| 2718 | + }
|
|
| 2719 | + |
|
| 2720 | + // We know that the RFPTarget::WebCodecs is enabled, check if principal
|
|
| 2721 | + // is exempted.
|
|
| 2722 | + |
|
| 2723 | + // VideoFrame::PrefEnabled function can be called without a JSContext.
|
|
| 2724 | + if (!aCx) {
|
|
| 2725 | + return true;
|
|
| 2726 | + }
|
|
| 2727 | + |
|
| 2728 | + // Once bug 1973966 is resolved, we can replace this section with just
|
|
| 2729 | + // nsIPrincipal* principal = nsContentUtils::SubjectPrincipal(aCx);
|
|
| 2730 | + JS::Realm* realm = js::GetContextRealm(aCx);
|
|
| 2731 | + MOZ_ASSERT(realm);
|
|
| 2732 | + JSPrincipals* principals = JS::GetRealmPrincipals(realm);
|
|
| 2733 | + nsIPrincipal* principal = nsJSPrincipals::get(principals);
|
|
| 2734 | + |
|
| 2735 | + return nsContentUtils::ShouldResistFingerprinting_dangerous(
|
|
| 2736 | + principal, "Principal is the best context we have", RFPTarget::WebCodecs);
|
|
| 2737 | +} |
| ... | ... | @@ -416,6 +416,9 @@ class nsRFPService final : public nsIObserver, public nsIRFPService { |
| 416 | 416 | |
| 417 | 417 | static uint64_t GetSpoofedStorageLimit();
|
| 418 | 418 | |
| 419 | + static bool ExposeWebCodecsAPI(JSContext* aCx, JSObject* aObj);
|
|
| 420 | + static bool ExposeWebCodecsAPIImageDecoder(JSContext* aCx, JSObject* aObj);
|
|
| 421 | + |
|
| 419 | 422 | private:
|
| 420 | 423 | nsresult Init();
|
| 421 | 424 | |
| ... | ... | @@ -527,6 +530,8 @@ class nsRFPService final : public nsIObserver, public nsIRFPService { |
| 527 | 530 | static bool IsTargetActiveForMode(RFPTarget aTarget,
|
| 528 | 531 | FingerprintingProtectionType aMode);
|
| 529 | 532 | |
| 533 | + static bool IsWebCodecsRFPTargetEnabled(JSContext* aCx);
|
|
| 534 | + |
|
| 530 | 535 | static nsCString* sExemptedDomainsLowercase;
|
| 531 | 536 | };
|
| 532 | 537 |