tbb-commits
Threads by month
- ----- 2026 -----
- July
- June
- May
- April
- March
- February
- January
- ----- 2025 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- 1 participants
- 20633 discussions
[Git][tpo/applications/mullvad-browser][mullvad-browser-140.2.0esr-15.0-1] fixup! MB 1: Mullvad Browser branding
by morgan (@morgan) 10 Sep '25
by morgan (@morgan) 10 Sep '25
10 Sep '25
morgan pushed to branch mullvad-browser-140.2.0esr-15.0-1 at The Tor Project / Applications / Mullvad Browser
Commits:
8f3e5664 by Henry Wilkes at 2025-09-10T17:22:14+01:00
fixup! MB 1: Mullvad Browser branding
TB 44142: Add missing document_pdf.svg file.
- - - - -
6 changed files:
- + browser/branding/mb-alpha/content/document_pdf.svg
- browser/branding/mb-alpha/content/jar.mn
- + browser/branding/mb-nightly/content/document_pdf.svg
- browser/branding/mb-nightly/content/jar.mn
- + browser/branding/mb-release/content/document_pdf.svg
- browser/branding/mb-release/content/jar.mn
Changes:
=====================================
browser/branding/mb-alpha/content/document_pdf.svg
=====================================
@@ -0,0 +1,9 @@
+<svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
+ <path d="M37.5 9.53728C37.5 9.26197 37.7226 9.03846 37.9979 9.03729L163.745 8.50263L218.5 62.7471V247.317C218.5 247.593 218.276 247.817 218 247.817H38C37.7239 247.817 37.5 247.593 37.5 247.317V9.53728Z" fill="#FAFAFA" stroke="#80808E" stroke-width="3"/>
+ <path d="m70 57v116h24.489v-60.629l16.756 30.624h33.511l16.756-30.624v60.629h24.489v-116h-24.489l-33.511 60.939-33.511-60.939z" fill="#CFCFD8"/>
+ <rect x="28" y="136" width="200" height="100" fill="#FF0000"/>
+ <rect x="29.5" y="137.5" width="197" height="97" stroke="black" stroke-opacity="0.25" stroke-width="3"/>
+ <path d="M65.4878 194.342V213.441H53V158H72.5629C86.5327 158 93.5176 163.889 93.5176 175.668C93.5176 181.236 91.5072 185.746 87.4864 189.2C83.4913 192.628 78.1431 194.342 71.4417 194.342H65.4878ZM65.4878 167.588V184.87H70.3978C77.0477 184.87 80.3726 181.957 80.3726 176.132C80.3726 170.436 77.0477 167.588 70.3978 167.588H65.4878Z" fill="white"/>
+ <path d="M107.767 213.441V158H127.407C147.099 158 156.945 167.008 156.945 185.025C156.945 193.659 154.251 200.554 148.864 205.709C143.503 210.864 136.351 213.441 127.407 213.441H107.767ZM120.255 168.168V203.312H126.441C131.853 203.312 136.093 201.688 139.16 198.44C142.253 195.193 143.8 190.772 143.8 185.179C143.8 179.896 142.266 175.746 139.199 172.73C136.158 169.689 131.879 168.168 126.363 168.168H120.255Z" fill="white"/>
+ <path d="M203.051 168.624H183.488V182.233H201.466V192.362H183.488V213.897H171V158.456H203.051V168.624Z" fill="white"/>
+</svg>
=====================================
browser/branding/mb-alpha/content/jar.mn
=====================================
@@ -9,6 +9,8 @@ browser.jar:
content/branding/about-logo.svg
content/branding/about-logo(a)2x.png
content/branding/about-wordmark.svg
+ content/branding/document.ico (../document.ico)
+ content/branding/document_pdf.svg
content/branding/firefox-wordmark.svg
content/branding/icon16.png
content/branding/icon32.png
=====================================
browser/branding/mb-nightly/content/document_pdf.svg
=====================================
@@ -0,0 +1,9 @@
+<svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
+ <path d="M37.5 9.53728C37.5 9.26197 37.7226 9.03846 37.9979 9.03729L163.745 8.50263L218.5 62.7471V247.317C218.5 247.593 218.276 247.817 218 247.817H38C37.7239 247.817 37.5 247.593 37.5 247.317V9.53728Z" fill="#FAFAFA" stroke="#80808E" stroke-width="3"/>
+ <path d="m70 57v116h24.489v-60.629l16.756 30.624h33.511l16.756-30.624v60.629h24.489v-116h-24.489l-33.511 60.939-33.511-60.939z" fill="#CFCFD8"/>
+ <rect x="28" y="136" width="200" height="100" fill="#FF0000"/>
+ <rect x="29.5" y="137.5" width="197" height="97" stroke="black" stroke-opacity="0.25" stroke-width="3"/>
+ <path d="M65.4878 194.342V213.441H53V158H72.5629C86.5327 158 93.5176 163.889 93.5176 175.668C93.5176 181.236 91.5072 185.746 87.4864 189.2C83.4913 192.628 78.1431 194.342 71.4417 194.342H65.4878ZM65.4878 167.588V184.87H70.3978C77.0477 184.87 80.3726 181.957 80.3726 176.132C80.3726 170.436 77.0477 167.588 70.3978 167.588H65.4878Z" fill="white"/>
+ <path d="M107.767 213.441V158H127.407C147.099 158 156.945 167.008 156.945 185.025C156.945 193.659 154.251 200.554 148.864 205.709C143.503 210.864 136.351 213.441 127.407 213.441H107.767ZM120.255 168.168V203.312H126.441C131.853 203.312 136.093 201.688 139.16 198.44C142.253 195.193 143.8 190.772 143.8 185.179C143.8 179.896 142.266 175.746 139.199 172.73C136.158 169.689 131.879 168.168 126.363 168.168H120.255Z" fill="white"/>
+ <path d="M203.051 168.624H183.488V182.233H201.466V192.362H183.488V213.897H171V158.456H203.051V168.624Z" fill="white"/>
+</svg>
=====================================
browser/branding/mb-nightly/content/jar.mn
=====================================
@@ -9,6 +9,8 @@ browser.jar:
content/branding/about-logo.svg
content/branding/about-logo(a)2x.png
content/branding/about-wordmark.svg
+ content/branding/document.ico (../document.ico)
+ content/branding/document_pdf.svg
content/branding/firefox-wordmark.svg
content/branding/icon16.png
content/branding/icon32.png
=====================================
browser/branding/mb-release/content/document_pdf.svg
=====================================
@@ -0,0 +1,9 @@
+<svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
+ <path d="M37.5 9.53728C37.5 9.26197 37.7226 9.03846 37.9979 9.03729L163.745 8.50263L218.5 62.7471V247.317C218.5 247.593 218.276 247.817 218 247.817H38C37.7239 247.817 37.5 247.593 37.5 247.317V9.53728Z" fill="#FAFAFA" stroke="#80808E" stroke-width="3"/>
+ <path d="m70 57v116h24.489v-60.629l16.756 30.624h33.511l16.756-30.624v60.629h24.489v-116h-24.489l-33.511 60.939-33.511-60.939z" fill="#CFCFD8"/>
+ <rect x="28" y="136" width="200" height="100" fill="#FF0000"/>
+ <rect x="29.5" y="137.5" width="197" height="97" stroke="black" stroke-opacity="0.25" stroke-width="3"/>
+ <path d="M65.4878 194.342V213.441H53V158H72.5629C86.5327 158 93.5176 163.889 93.5176 175.668C93.5176 181.236 91.5072 185.746 87.4864 189.2C83.4913 192.628 78.1431 194.342 71.4417 194.342H65.4878ZM65.4878 167.588V184.87H70.3978C77.0477 184.87 80.3726 181.957 80.3726 176.132C80.3726 170.436 77.0477 167.588 70.3978 167.588H65.4878Z" fill="white"/>
+ <path d="M107.767 213.441V158H127.407C147.099 158 156.945 167.008 156.945 185.025C156.945 193.659 154.251 200.554 148.864 205.709C143.503 210.864 136.351 213.441 127.407 213.441H107.767ZM120.255 168.168V203.312H126.441C131.853 203.312 136.093 201.688 139.16 198.44C142.253 195.193 143.8 190.772 143.8 185.179C143.8 179.896 142.266 175.746 139.199 172.73C136.158 169.689 131.879 168.168 126.363 168.168H120.255Z" fill="white"/>
+ <path d="M203.051 168.624H183.488V182.233H201.466V192.362H183.488V213.897H171V158.456H203.051V168.624Z" fill="white"/>
+</svg>
=====================================
browser/branding/mb-release/content/jar.mn
=====================================
@@ -9,6 +9,8 @@ browser.jar:
content/branding/about-logo.svg
content/branding/about-logo(a)2x.png
content/branding/about-wordmark.svg
+ content/branding/document.ico (../document.ico)
+ content/branding/document_pdf.svg
content/branding/firefox-wordmark.svg
content/branding/icon16.png
content/branding/icon32.png
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/commit/8f3…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/commit/8f3…
You're receiving this email because of your account on gitlab.torproject.org.
1
0
[Git][tpo/applications/tor-browser] Pushed new branch tor-browser-140.3.0esr-15.0-1
by Pier Angelo Vendrame (@pierov) 10 Sep '25
by Pier Angelo Vendrame (@pierov) 10 Sep '25
10 Sep '25
Pier Angelo Vendrame pushed new branch tor-browser-140.3.0esr-15.0-1 at The Tor Project / Applications / Tor Browser
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/tree/tor-brows…
You're receiving this email because of your account on gitlab.torproject.org.
1
0
[Git][tpo/applications/tor-browser][tor-browser-140.2.0esr-15.0-1] 3 commits: fixup! BB 41803: Add some developer tools for working on tor-browser.
by Pier Angelo Vendrame (@pierov) 10 Sep '25
by Pier Angelo Vendrame (@pierov) 10 Sep '25
10 Sep '25
Pier Angelo Vendrame pushed to branch tor-browser-140.2.0esr-15.0-1 at The Tor Project / Applications / Tor Browser
Commits:
7495e77b by Beatriz Rizental at 2025-09-10T10:34:11+02:00
fixup! BB 41803: Add some developer tools for working on tor-browser.
This reverts commit 36c2b42c9b849182fa60f648639b4ab11b53f537.
- - - - -
72d62388 by Beatriz Rizental at 2025-09-10T10:34:11+02:00
fixup! BB 41803: Add some developer tools for working on tor-browser.
- - - - -
6b49b72e by Beatriz Rizental at 2025-09-10T10:34:11+02:00
fixup! BB 42305: Add script to combine translation files across versions.
- - - - -
0 changed files:
Changes:
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/64132f…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/64132f…
You're receiving this email because of your account on gitlab.torproject.org.
1
0
[Git][tpo/applications/mullvad-browser][mullvad-browser-140.2.0esr-15.0-1] Bug 1973265 - Put WebCodecs API behind RFP Target. r=tjr,webidl,smaug
by morgan (@morgan) 09 Sep '25
by morgan (@morgan) 09 Sep '25
09 Sep '25
morgan pushed to branch mullvad-browser-140.2.0esr-15.0-1 at The Tor Project / Applications / Mullvad Browser
Commits:
6452d953 by Fatih Kilic at 2025-09-09T17:43:19+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
=====================================
@@ -2690,3 +2690,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/mullvad-browser/-/commit/645…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/commit/645…
You're receiving this email because of your account on gitlab.torproject.org.
1
0
[Git][tpo/applications/tor-browser][base-browser-140.2.0esr-15.0-1] Bug 1973265 - Put WebCodecs API behind RFP Target. r=tjr,webidl,smaug
by morgan (@morgan) 09 Sep '25
by morgan (@morgan) 09 Sep '25
09 Sep '25
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
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
=====================================
@@ -2690,3 +2690,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/9ae87bd…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/9ae87bd…
You're receiving this email because of your account on gitlab.torproject.org.
1
0
[Git][tpo/applications/tor-browser][tor-browser-140.2.0esr-15.0-1] Bug 1973265 - Put WebCodecs API behind RFP Target. r=tjr,webidl,smaug
by morgan (@morgan) 09 Sep '25
by morgan (@morgan) 09 Sep '25
09 Sep '25
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/64132f0…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/64132f0…
You're receiving this email because of your account on gitlab.torproject.org.
1
0
[Git][tpo/applications/mullvad-browser][mullvad-browser-140.2.0esr-15.0-1] fixup! BB 40925: Implemented the Security Level component
by morgan (@morgan) 09 Sep '25
by morgan (@morgan) 09 Sep '25
09 Sep '25
morgan pushed to branch mullvad-browser-140.2.0esr-15.0-1 at The Tor Project / Applications / Mullvad Browser
Commits:
cb4959c6 by Henry Wilkes at 2025-09-09T17:34:59+00:00
fixup! BB 40925: Implemented the Security Level component
TB 43966: Add a warning notification for users when they enter the
custom security level state.
- - - - -
4 changed files:
- browser/components/BrowserComponents.manifest
- browser/modules/SecurityLevelRestartNotification.sys.mjs → browser/modules/SecurityLevelNotification.sys.mjs
- browser/modules/moz.build
- toolkit/components/securitylevel/SecurityLevel.sys.mjs
Changes:
=====================================
browser/components/BrowserComponents.manifest
=====================================
@@ -51,7 +51,7 @@ category browser-first-window-ready resource://gre/modules/SandboxUtils.sys.mjs
#endif
#endif
category browser-first-window-ready moz-src:///browser/modules/ClipboardPrivacy.sys.mjs ClipboardPrivacy.init
-category browser-first-window-ready moz-src:///browser/modules/SecurityLevelRestartNotification.sys.mjs SecurityLevelRestartNotification.ready
+category browser-first-window-ready moz-src:///browser/modules/SecurityLevelNotification.sys.mjs SecurityLevelNotification.ready
category browser-idle-startup resource:///modules/PlacesUIUtils.sys.mjs PlacesUIUtils.unblockToolbars
category browser-idle-startup resource:///modules/BuiltInThemes.sys.mjs BuiltInThemes.ensureBuiltInThemes
=====================================
browser/modules/SecurityLevelRestartNotification.sys.mjs → browser/modules/SecurityLevelNotification.sys.mjs
=====================================
@@ -15,7 +15,7 @@ ChromeUtils.defineLazyGetter(lazy, "NotificationStrings", function () {
/**
* Interface for showing the security level restart notification on desktop.
*/
-export const SecurityLevelRestartNotification = {
+export const SecurityLevelNotification = {
/**
* Whether we have already been initialised.
*
@@ -31,11 +31,13 @@ export const SecurityLevelRestartNotification = {
return;
}
this._initialized = true;
- lazy.SecurityLevelPrefs.setRestartNotificationHandler(this);
+ lazy.SecurityLevelPrefs.setNotificationHandler(this);
},
/**
* Show the restart notification, and perform the restart if the user agrees.
+ *
+ * @returns {boolean} - Whether we are restarting the browser.
*/
async tryRestartBrowser() {
const [titleText, bodyText, primaryButtonText, secondaryButtonText] =
@@ -69,6 +71,49 @@ export const SecurityLevelRestartNotification = {
Services.startup.quit(
Services.startup.eAttemptQuit | Services.startup.eRestart
);
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Show or re-show the custom security notification.
+ *
+ * @param {Function} userDismissedCallback - The callback for when the user
+ * dismisses the notification.
+ */
+ async showCustomWarning(userDismissedCallback) {
+ const win = lazy.BrowserWindowTracker.getTopWindow();
+ if (!win) {
+ return;
+ }
+ const typeName = "security-level-custom";
+ const existing = win.gNotificationBox.getNotificationWithValue(typeName);
+ if (existing) {
+ win.gNotificationBox.removeNotification(existing);
}
+
+ const buttons = [
+ {
+ "l10n-id": "security-level-panel-open-settings-button",
+ callback() {
+ win.openPreferences("privacy-securitylevel");
+ },
+ },
+ ];
+
+ win.gNotificationBox.appendNotification(
+ typeName,
+ {
+ label: { "l10n-id": "security-level-summary-custom" },
+ priority: win.gNotificationBox.PRIORITY_WARNING_HIGH,
+ eventCallback: event => {
+ if (event === "dismissed") {
+ userDismissedCallback();
+ }
+ },
+ },
+ buttons
+ );
},
};
=====================================
browser/modules/moz.build
=====================================
@@ -149,7 +149,7 @@ EXTRA_JS_MODULES += [
MOZ_SRC_FILES += [
"ContextId.sys.mjs",
"ClipboardPrivacy.sys.mjs",
- "SecurityLevelRestartNotification.sys.mjs",
+ "SecurityLevelNotification.sys.mjs",
]
if CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows":
=====================================
toolkit/components/securitylevel/SecurityLevel.sys.mjs
=====================================
@@ -238,7 +238,7 @@ var initializeNoScriptControl = () => {
} catch (e) {
logger.error("Could not apply NoScript settings", e);
// Treat as a custom security level for the rest of the session.
- Services.prefs.setBoolPref(kCustomPref, true);
+ SecurityLevelPrefs.setCustomAndWarn();
}
};
waitForExtensionMessage(noscriptID, a => a.__meta.name === "started").then(
@@ -248,7 +248,7 @@ var initializeNoScriptControl = () => {
} catch (e) {
logger.exception(e);
// Treat as a custom security level for the rest of the session.
- Services.prefs.setBoolPref(kCustomPref, true);
+ SecurityLevelPrefs.setCustomAndWarn();
}
};
@@ -401,7 +401,12 @@ var initializeSecurityPrefs = function () {
// particular, for the NoScript addon.
Services.prefs.setBoolPref(kCustomPref, false);
Services.prefs.setIntPref(kSliderPref, effectiveIndex);
- } else if (!wasCustom && effectiveIndex !== desiredIndex) {
+ }
+ // Warn the user if they have booted the browser in a custom state, and have
+ // not yet acknowledged it in a previous session.
+ SecurityLevelPrefs.maybeWarnCustom();
+
+ if (!wasCustom && effectiveIndex !== desiredIndex) {
// NOTE: We assume all our controlled preferences require a restart.
// In practice, only a subset of these preferences may actually require a
// restart, so we could switch their values. But we treat them all the same
@@ -448,7 +453,7 @@ var initializeSecurityPrefs = function () {
// properly applied. See tor-browser#43783.
// In the case where it does match a pre-set security level, the custom
// flag will be cleared at the next startup.
- Services.prefs.setBoolPref(kCustomPref, true);
+ SecurityLevelPrefs.setCustomAndWarn();
}),
});
}
@@ -517,12 +522,27 @@ export class SecurityLevel {
}
/**
- * @typedef {object} SecurityLevelRestartNotificationHandler
+ * @callback SecurityLevelTryRestartBrowserCallback
+ *
+ * @returns {Promise<boolean>} - A promise that resolves when the user has made
+ * a decision. Should return `true` when the browser is now restarting.
+ */
+/**
+ * @callback SecurityLevelShowCustomWarningCallback
*
- * An object that can serve the user a restart notification.
+ * @param {Function} userDismissedCallback - A callback that should be called
+ * if the user has acknowledged and dismissed the notification.
+ */
+/**
+ * @typedef {object} SecurityLevelNotificationHandler
*
- * @property {Function} tryRestartBrowser - The method that should be called to
- * ask the user to restart the browser.
+ * An object that can serve the user notifications.
+ *
+ * @property {SecurityLevelTryRestartBrowserCallback} tryRestartBrowser - The
+ * method that should be called to ask the user to restart the browser.
+ * @property {SecurityLevelShowCustomWarningCallback} showCustomWarning - The
+ * method that should be called to let the user know they have a custom
+ * security level.
*/
/*
@@ -538,6 +558,8 @@ export const SecurityLevelPrefs = {
}),
security_slider_pref: "browser.security_level.security_slider",
security_custom_pref: "browser.security_level.security_custom",
+ _customWarningDismissedPref:
+ "browser.security_level.custom_warning_dismissed",
/**
* The current security level preference.
@@ -593,18 +615,18 @@ export const SecurityLevelPrefs = {
},
/**
- * Whether the browser should be restarted to apply the security level.
+ * The external handler that can show a notification to the user, if any.
*
- * @type {boolean}
+ * @type {?SecurityLevelNotificationHandler}
*/
- _needRestart: false,
+ _notificationHandler: null,
/**
- * The external handler that can show a notification to the user, if any.
+ * The notifications we are waiting for a handler to show.
*
- * @type {?SecurityLevelRestartNotificationHandler}
+ * @type {Set}
*/
- _restartNotificationHandler: null,
+ _pendingNotifications: {},
/**
* Set the external handler for showing notifications to the user.
@@ -612,49 +634,73 @@ export const SecurityLevelPrefs = {
* This should only be called once per session once the handler is ready to
* show a notification, which may occur immediately during this call.
*
- * @param {SecurityLevelRestartNotificationHandler} handler - The new handler
- * to use.
+ * @param {SecurityLevelNotificationHandler} handler - The new handler to use.
*/
- setRestartNotificationHandler(handler) {
+ setNotificationHandler(handler) {
logger.info("Restart notification handler is set");
- this._restartNotificationHandler = handler;
- if (this._needRestart) {
- // Show now using the new handler.
- this._tryShowRestartNotification();
- }
+ this._notificationHandler = handler;
+ this._tryShowNotifications(this._pendingNotifications);
},
/**
* A promise for any ongoing notification prompt task.
*
- * @type {Promise}
+ * Resolves with whether the browser is restarting.
+ *
+ * @type {Promise<boolean>}
*/
_restartNotificationPromise: null,
/**
- * Try show a notification to the user.
+ * Try show notifications to the user.
+ *
+ * If no notification handler has been attached yet, this will queue the
+ * notification for when it is added, if ever.
*
- * If no notification handler has been attached yet, this will do nothing.
+ * @param {object} notifications - The notifications to try and show.
+ * @param {boolean} notifications.restart - Whether to show the restart
+ * notification.
+ * @param {boolean} notifications.custom - Whether to show the custom security
+ * level notification.
*/
- async _tryShowRestartNotification() {
- if (!this._restartNotificationHandler) {
- logger.info("Missing a restart notification handler");
+ async _tryShowNotifications(notifications) {
+ if (!this._notificationHandler) {
+ logger.info("Missing a notification handler", notifications);
// This may be added later in the session.
+ if (notifications.custom) {
+ this._pendingNotifications.custom = true;
+ }
+ if (notifications.restart) {
+ this._pendingNotifications.restart = true;
+ }
return;
}
- const prevPromise = this._restartNotificationPromise;
- let resolve;
- ({ promise: this._restartNotificationPromise, resolve } =
- Promise.withResolvers());
- await prevPromise;
-
- try {
- await this._restartNotificationHandler?.tryRestartBrowser();
- } finally {
- // Allow the notification to be shown again.
- resolve();
+ let isRestarting = false;
+ if (notifications.restart) {
+ const prevPromise = this._restartNotificationPromise;
+ let resolve;
+ ({ promise: this._restartNotificationPromise, resolve } =
+ Promise.withResolvers());
+ await prevPromise;
+
+ try {
+ isRestarting = await this._notificationHandler?.tryRestartBrowser();
+ } finally {
+ // Allow the notification to be shown again.
+ resolve();
+ }
+ }
+ // NOTE: We wait for the restart notification to resolve before showing the
+ // custom warning. We do not show the warning if we are already restarting.
+ if (!isRestarting && notifications.custom) {
+ this._notificationHandler?.showCustomWarning(() => {
+ // User has acknowledged and dismissed the notification.
+ Services.prefs.setBoolPref(this._customWarningDismissedPref, true);
+ });
}
+
+ this._pendingNotifications = {};
},
/**
@@ -670,7 +716,6 @@ export const SecurityLevelPrefs = {
// At the next startup, the custom flag may be cleared if the settings are
// as expected.
Services.prefs.setBoolPref(kCustomPref, true);
- this._needRestart = true;
// NOTE: We need to change the controlled security level preferences in
// response to the desired change in security level. We could either:
@@ -696,6 +741,39 @@ export const SecurityLevelPrefs = {
// still be marked as "custom" because:
// 1. Some preferences require a browser restart to be applied.
// 2. NoScript has not been updated with the new settings.
- this._tryShowRestartNotification();
+
+ this._tryShowNotifications({ restart: true, custom: true });
+ },
+
+ /**
+ * Put the user in the custom security level state and show them a warning
+ * about this state.
+ */
+ setCustomAndWarn() {
+ Services.prefs.setBoolPref(kCustomPref, true);
+ // NOTE: We clear _customWarningDismissedPref because the entry points
+ // for this method imply we should re-warn the user each time.
+ Services.prefs.clearUserPref(this._customWarningDismissedPref);
+ this._tryShowNotifications({ custom: true });
+ },
+
+ /**
+ * If the user is in a custom state, try and notify them of this state.
+ */
+ maybeWarnCustom() {
+ const isCustom = Services.prefs.getBoolPref(kCustomPref, false);
+ if (!isCustom) {
+ // Clear the dismissed preference so the user will be re-shown the
+ // notification when they re-enter the custom state.
+ Services.prefs.clearUserPref(this._customWarningDismissedPref);
+ return;
+ }
+ if (Services.prefs.getBoolPref(this._customWarningDismissedPref, false)) {
+ // Do not warn the user of the custom state if they have already
+ // acknowledged and dismissed this in a previous session.
+ return;
+ }
+
+ this._tryShowNotifications({ custom: true });
},
}; /* Security Level Prefs */
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/commit/cb4…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/commit/cb4…
You're receiving this email because of your account on gitlab.torproject.org.
1
0
[Git][tpo/applications/tor-browser][base-browser-140.2.0esr-15.0-1] fixup! BB 40925: Implemented the Security Level component
by morgan (@morgan) 09 Sep '25
by morgan (@morgan) 09 Sep '25
09 Sep '25
morgan pushed to branch base-browser-140.2.0esr-15.0-1 at The Tor Project / Applications / Tor Browser
Commits:
f8f4dbce by Henry Wilkes at 2025-09-09T17:33:53+00:00
fixup! BB 40925: Implemented the Security Level component
TB 43966: Add a warning notification for users when they enter the
custom security level state.
- - - - -
4 changed files:
- browser/components/BrowserComponents.manifest
- browser/modules/SecurityLevelRestartNotification.sys.mjs → browser/modules/SecurityLevelNotification.sys.mjs
- browser/modules/moz.build
- toolkit/components/securitylevel/SecurityLevel.sys.mjs
Changes:
=====================================
browser/components/BrowserComponents.manifest
=====================================
@@ -51,7 +51,7 @@ category browser-first-window-ready resource://gre/modules/SandboxUtils.sys.mjs
#endif
#endif
category browser-first-window-ready moz-src:///browser/modules/ClipboardPrivacy.sys.mjs ClipboardPrivacy.init
-category browser-first-window-ready moz-src:///browser/modules/SecurityLevelRestartNotification.sys.mjs SecurityLevelRestartNotification.ready
+category browser-first-window-ready moz-src:///browser/modules/SecurityLevelNotification.sys.mjs SecurityLevelNotification.ready
category browser-idle-startup resource:///modules/PlacesUIUtils.sys.mjs PlacesUIUtils.unblockToolbars
category browser-idle-startup resource:///modules/BuiltInThemes.sys.mjs BuiltInThemes.ensureBuiltInThemes
=====================================
browser/modules/SecurityLevelRestartNotification.sys.mjs → browser/modules/SecurityLevelNotification.sys.mjs
=====================================
@@ -15,7 +15,7 @@ ChromeUtils.defineLazyGetter(lazy, "NotificationStrings", function () {
/**
* Interface for showing the security level restart notification on desktop.
*/
-export const SecurityLevelRestartNotification = {
+export const SecurityLevelNotification = {
/**
* Whether we have already been initialised.
*
@@ -31,11 +31,13 @@ export const SecurityLevelRestartNotification = {
return;
}
this._initialized = true;
- lazy.SecurityLevelPrefs.setRestartNotificationHandler(this);
+ lazy.SecurityLevelPrefs.setNotificationHandler(this);
},
/**
* Show the restart notification, and perform the restart if the user agrees.
+ *
+ * @returns {boolean} - Whether we are restarting the browser.
*/
async tryRestartBrowser() {
const [titleText, bodyText, primaryButtonText, secondaryButtonText] =
@@ -69,6 +71,49 @@ export const SecurityLevelRestartNotification = {
Services.startup.quit(
Services.startup.eAttemptQuit | Services.startup.eRestart
);
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Show or re-show the custom security notification.
+ *
+ * @param {Function} userDismissedCallback - The callback for when the user
+ * dismisses the notification.
+ */
+ async showCustomWarning(userDismissedCallback) {
+ const win = lazy.BrowserWindowTracker.getTopWindow();
+ if (!win) {
+ return;
+ }
+ const typeName = "security-level-custom";
+ const existing = win.gNotificationBox.getNotificationWithValue(typeName);
+ if (existing) {
+ win.gNotificationBox.removeNotification(existing);
}
+
+ const buttons = [
+ {
+ "l10n-id": "security-level-panel-open-settings-button",
+ callback() {
+ win.openPreferences("privacy-securitylevel");
+ },
+ },
+ ];
+
+ win.gNotificationBox.appendNotification(
+ typeName,
+ {
+ label: { "l10n-id": "security-level-summary-custom" },
+ priority: win.gNotificationBox.PRIORITY_WARNING_HIGH,
+ eventCallback: event => {
+ if (event === "dismissed") {
+ userDismissedCallback();
+ }
+ },
+ },
+ buttons
+ );
},
};
=====================================
browser/modules/moz.build
=====================================
@@ -149,7 +149,7 @@ EXTRA_JS_MODULES += [
MOZ_SRC_FILES += [
"ContextId.sys.mjs",
"ClipboardPrivacy.sys.mjs",
- "SecurityLevelRestartNotification.sys.mjs",
+ "SecurityLevelNotification.sys.mjs",
]
if CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows":
=====================================
toolkit/components/securitylevel/SecurityLevel.sys.mjs
=====================================
@@ -226,7 +226,7 @@ var initializeNoScriptControl = () => {
} catch (e) {
logger.error("Could not apply NoScript settings", e);
// Treat as a custom security level for the rest of the session.
- Services.prefs.setBoolPref(kCustomPref, true);
+ SecurityLevelPrefs.setCustomAndWarn();
}
};
waitForExtensionMessage(noscriptID, a => a.__meta.name === "started").then(
@@ -236,7 +236,7 @@ var initializeNoScriptControl = () => {
} catch (e) {
logger.exception(e);
// Treat as a custom security level for the rest of the session.
- Services.prefs.setBoolPref(kCustomPref, true);
+ SecurityLevelPrefs.setCustomAndWarn();
}
};
@@ -389,7 +389,12 @@ var initializeSecurityPrefs = function () {
// particular, for the NoScript addon.
Services.prefs.setBoolPref(kCustomPref, false);
Services.prefs.setIntPref(kSliderPref, effectiveIndex);
- } else if (!wasCustom && effectiveIndex !== desiredIndex) {
+ }
+ // Warn the user if they have booted the browser in a custom state, and have
+ // not yet acknowledged it in a previous session.
+ SecurityLevelPrefs.maybeWarnCustom();
+
+ if (!wasCustom && effectiveIndex !== desiredIndex) {
// NOTE: We assume all our controlled preferences require a restart.
// In practice, only a subset of these preferences may actually require a
// restart, so we could switch their values. But we treat them all the same
@@ -436,7 +441,7 @@ var initializeSecurityPrefs = function () {
// properly applied. See tor-browser#43783.
// In the case where it does match a pre-set security level, the custom
// flag will be cleared at the next startup.
- Services.prefs.setBoolPref(kCustomPref, true);
+ SecurityLevelPrefs.setCustomAndWarn();
}),
});
}
@@ -505,12 +510,27 @@ export class SecurityLevel {
}
/**
- * @typedef {object} SecurityLevelRestartNotificationHandler
+ * @callback SecurityLevelTryRestartBrowserCallback
+ *
+ * @returns {Promise<boolean>} - A promise that resolves when the user has made
+ * a decision. Should return `true` when the browser is now restarting.
+ */
+/**
+ * @callback SecurityLevelShowCustomWarningCallback
*
- * An object that can serve the user a restart notification.
+ * @param {Function} userDismissedCallback - A callback that should be called
+ * if the user has acknowledged and dismissed the notification.
+ */
+/**
+ * @typedef {object} SecurityLevelNotificationHandler
*
- * @property {Function} tryRestartBrowser - The method that should be called to
- * ask the user to restart the browser.
+ * An object that can serve the user notifications.
+ *
+ * @property {SecurityLevelTryRestartBrowserCallback} tryRestartBrowser - The
+ * method that should be called to ask the user to restart the browser.
+ * @property {SecurityLevelShowCustomWarningCallback} showCustomWarning - The
+ * method that should be called to let the user know they have a custom
+ * security level.
*/
/*
@@ -526,6 +546,8 @@ export const SecurityLevelPrefs = {
}),
security_slider_pref: "browser.security_level.security_slider",
security_custom_pref: "browser.security_level.security_custom",
+ _customWarningDismissedPref:
+ "browser.security_level.custom_warning_dismissed",
/**
* The current security level preference.
@@ -581,18 +603,18 @@ export const SecurityLevelPrefs = {
},
/**
- * Whether the browser should be restarted to apply the security level.
+ * The external handler that can show a notification to the user, if any.
*
- * @type {boolean}
+ * @type {?SecurityLevelNotificationHandler}
*/
- _needRestart: false,
+ _notificationHandler: null,
/**
- * The external handler that can show a notification to the user, if any.
+ * The notifications we are waiting for a handler to show.
*
- * @type {?SecurityLevelRestartNotificationHandler}
+ * @type {Set}
*/
- _restartNotificationHandler: null,
+ _pendingNotifications: {},
/**
* Set the external handler for showing notifications to the user.
@@ -600,49 +622,73 @@ export const SecurityLevelPrefs = {
* This should only be called once per session once the handler is ready to
* show a notification, which may occur immediately during this call.
*
- * @param {SecurityLevelRestartNotificationHandler} handler - The new handler
- * to use.
+ * @param {SecurityLevelNotificationHandler} handler - The new handler to use.
*/
- setRestartNotificationHandler(handler) {
+ setNotificationHandler(handler) {
logger.info("Restart notification handler is set");
- this._restartNotificationHandler = handler;
- if (this._needRestart) {
- // Show now using the new handler.
- this._tryShowRestartNotification();
- }
+ this._notificationHandler = handler;
+ this._tryShowNotifications(this._pendingNotifications);
},
/**
* A promise for any ongoing notification prompt task.
*
- * @type {Promise}
+ * Resolves with whether the browser is restarting.
+ *
+ * @type {Promise<boolean>}
*/
_restartNotificationPromise: null,
/**
- * Try show a notification to the user.
+ * Try show notifications to the user.
+ *
+ * If no notification handler has been attached yet, this will queue the
+ * notification for when it is added, if ever.
*
- * If no notification handler has been attached yet, this will do nothing.
+ * @param {object} notifications - The notifications to try and show.
+ * @param {boolean} notifications.restart - Whether to show the restart
+ * notification.
+ * @param {boolean} notifications.custom - Whether to show the custom security
+ * level notification.
*/
- async _tryShowRestartNotification() {
- if (!this._restartNotificationHandler) {
- logger.info("Missing a restart notification handler");
+ async _tryShowNotifications(notifications) {
+ if (!this._notificationHandler) {
+ logger.info("Missing a notification handler", notifications);
// This may be added later in the session.
+ if (notifications.custom) {
+ this._pendingNotifications.custom = true;
+ }
+ if (notifications.restart) {
+ this._pendingNotifications.restart = true;
+ }
return;
}
- const prevPromise = this._restartNotificationPromise;
- let resolve;
- ({ promise: this._restartNotificationPromise, resolve } =
- Promise.withResolvers());
- await prevPromise;
-
- try {
- await this._restartNotificationHandler?.tryRestartBrowser();
- } finally {
- // Allow the notification to be shown again.
- resolve();
+ let isRestarting = false;
+ if (notifications.restart) {
+ const prevPromise = this._restartNotificationPromise;
+ let resolve;
+ ({ promise: this._restartNotificationPromise, resolve } =
+ Promise.withResolvers());
+ await prevPromise;
+
+ try {
+ isRestarting = await this._notificationHandler?.tryRestartBrowser();
+ } finally {
+ // Allow the notification to be shown again.
+ resolve();
+ }
+ }
+ // NOTE: We wait for the restart notification to resolve before showing the
+ // custom warning. We do not show the warning if we are already restarting.
+ if (!isRestarting && notifications.custom) {
+ this._notificationHandler?.showCustomWarning(() => {
+ // User has acknowledged and dismissed the notification.
+ Services.prefs.setBoolPref(this._customWarningDismissedPref, true);
+ });
}
+
+ this._pendingNotifications = {};
},
/**
@@ -658,7 +704,6 @@ export const SecurityLevelPrefs = {
// At the next startup, the custom flag may be cleared if the settings are
// as expected.
Services.prefs.setBoolPref(kCustomPref, true);
- this._needRestart = true;
// NOTE: We need to change the controlled security level preferences in
// response to the desired change in security level. We could either:
@@ -684,6 +729,39 @@ export const SecurityLevelPrefs = {
// still be marked as "custom" because:
// 1. Some preferences require a browser restart to be applied.
// 2. NoScript has not been updated with the new settings.
- this._tryShowRestartNotification();
+
+ this._tryShowNotifications({ restart: true, custom: true });
+ },
+
+ /**
+ * Put the user in the custom security level state and show them a warning
+ * about this state.
+ */
+ setCustomAndWarn() {
+ Services.prefs.setBoolPref(kCustomPref, true);
+ // NOTE: We clear _customWarningDismissedPref because the entry points
+ // for this method imply we should re-warn the user each time.
+ Services.prefs.clearUserPref(this._customWarningDismissedPref);
+ this._tryShowNotifications({ custom: true });
+ },
+
+ /**
+ * If the user is in a custom state, try and notify them of this state.
+ */
+ maybeWarnCustom() {
+ const isCustom = Services.prefs.getBoolPref(kCustomPref, false);
+ if (!isCustom) {
+ // Clear the dismissed preference so the user will be re-shown the
+ // notification when they re-enter the custom state.
+ Services.prefs.clearUserPref(this._customWarningDismissedPref);
+ return;
+ }
+ if (Services.prefs.getBoolPref(this._customWarningDismissedPref, false)) {
+ // Do not warn the user of the custom state if they have already
+ // acknowledged and dismissed this in a previous session.
+ return;
+ }
+
+ this._tryShowNotifications({ custom: true });
},
}; /* Security Level Prefs */
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/f8f4dbc…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/f8f4dbc…
You're receiving this email because of your account on gitlab.torproject.org.
1
0
[Git][tpo/applications/tor-browser][tor-browser-140.2.0esr-15.0-1] fixup! BB 40925: Implemented the Security Level component
by morgan (@morgan) 09 Sep '25
by morgan (@morgan) 09 Sep '25
09 Sep '25
morgan pushed to branch tor-browser-140.2.0esr-15.0-1 at The Tor Project / Applications / Tor Browser
Commits:
c1b85777 by Henry Wilkes at 2025-09-09T17:27:56+00:00
fixup! BB 40925: Implemented the Security Level component
TB 43966: Add a warning notification for users when they enter the
custom security level state.
- - - - -
4 changed files:
- browser/components/BrowserComponents.manifest
- browser/modules/SecurityLevelRestartNotification.sys.mjs → browser/modules/SecurityLevelNotification.sys.mjs
- browser/modules/moz.build
- toolkit/components/securitylevel/SecurityLevel.sys.mjs
Changes:
=====================================
browser/components/BrowserComponents.manifest
=====================================
@@ -52,7 +52,7 @@ category browser-first-window-ready resource://gre/modules/SandboxUtils.sys.mjs
#endif
#endif
category browser-first-window-ready moz-src:///browser/modules/ClipboardPrivacy.sys.mjs ClipboardPrivacy.init
-category browser-first-window-ready moz-src:///browser/modules/SecurityLevelRestartNotification.sys.mjs SecurityLevelRestartNotification.ready
+category browser-first-window-ready moz-src:///browser/modules/SecurityLevelNotification.sys.mjs SecurityLevelNotification.ready
category browser-first-window-ready moz-src:///toolkit/modules/DragDropFilter.sys.mjs DragDropFilter.init
category browser-first-window-ready moz-src:///browser/modules/TorSettingsNotification.sys.mjs TorSettingsNotification.ready
category browser-first-window-ready moz-src:///browser/components/onionservices/OnionAliasStore.sys.mjs OnionAliasStore.init
=====================================
browser/modules/SecurityLevelRestartNotification.sys.mjs → browser/modules/SecurityLevelNotification.sys.mjs
=====================================
@@ -15,7 +15,7 @@ ChromeUtils.defineLazyGetter(lazy, "NotificationStrings", function () {
/**
* Interface for showing the security level restart notification on desktop.
*/
-export const SecurityLevelRestartNotification = {
+export const SecurityLevelNotification = {
/**
* Whether we have already been initialised.
*
@@ -31,11 +31,13 @@ export const SecurityLevelRestartNotification = {
return;
}
this._initialized = true;
- lazy.SecurityLevelPrefs.setRestartNotificationHandler(this);
+ lazy.SecurityLevelPrefs.setNotificationHandler(this);
},
/**
* Show the restart notification, and perform the restart if the user agrees.
+ *
+ * @returns {boolean} - Whether we are restarting the browser.
*/
async tryRestartBrowser() {
const [titleText, bodyText, primaryButtonText, secondaryButtonText] =
@@ -69,6 +71,49 @@ export const SecurityLevelRestartNotification = {
Services.startup.quit(
Services.startup.eAttemptQuit | Services.startup.eRestart
);
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Show or re-show the custom security notification.
+ *
+ * @param {Function} userDismissedCallback - The callback for when the user
+ * dismisses the notification.
+ */
+ async showCustomWarning(userDismissedCallback) {
+ const win = lazy.BrowserWindowTracker.getTopWindow();
+ if (!win) {
+ return;
+ }
+ const typeName = "security-level-custom";
+ const existing = win.gNotificationBox.getNotificationWithValue(typeName);
+ if (existing) {
+ win.gNotificationBox.removeNotification(existing);
}
+
+ const buttons = [
+ {
+ "l10n-id": "security-level-panel-open-settings-button",
+ callback() {
+ win.openPreferences("privacy-securitylevel");
+ },
+ },
+ ];
+
+ win.gNotificationBox.appendNotification(
+ typeName,
+ {
+ label: { "l10n-id": "security-level-summary-custom" },
+ priority: win.gNotificationBox.PRIORITY_WARNING_HIGH,
+ eventCallback: event => {
+ if (event === "dismissed") {
+ userDismissedCallback();
+ }
+ },
+ },
+ buttons
+ );
},
};
=====================================
browser/modules/moz.build
=====================================
@@ -150,7 +150,7 @@ EXTRA_JS_MODULES += [
MOZ_SRC_FILES += [
"ContextId.sys.mjs",
"ClipboardPrivacy.sys.mjs",
- "SecurityLevelRestartNotification.sys.mjs",
+ "SecurityLevelNotification.sys.mjs",
"TorSettingsNotification.sys.mjs",
]
=====================================
toolkit/components/securitylevel/SecurityLevel.sys.mjs
=====================================
@@ -226,7 +226,7 @@ var initializeNoScriptControl = () => {
} catch (e) {
logger.error("Could not apply NoScript settings", e);
// Treat as a custom security level for the rest of the session.
- Services.prefs.setBoolPref(kCustomPref, true);
+ SecurityLevelPrefs.setCustomAndWarn();
}
};
waitForExtensionMessage(noscriptID, a => a.__meta.name === "started").then(
@@ -236,7 +236,7 @@ var initializeNoScriptControl = () => {
} catch (e) {
logger.exception(e);
// Treat as a custom security level for the rest of the session.
- Services.prefs.setBoolPref(kCustomPref, true);
+ SecurityLevelPrefs.setCustomAndWarn();
}
};
@@ -389,7 +389,12 @@ var initializeSecurityPrefs = function () {
// particular, for the NoScript addon.
Services.prefs.setBoolPref(kCustomPref, false);
Services.prefs.setIntPref(kSliderPref, effectiveIndex);
- } else if (!wasCustom && effectiveIndex !== desiredIndex) {
+ }
+ // Warn the user if they have booted the browser in a custom state, and have
+ // not yet acknowledged it in a previous session.
+ SecurityLevelPrefs.maybeWarnCustom();
+
+ if (!wasCustom && effectiveIndex !== desiredIndex) {
// NOTE: We assume all our controlled preferences require a restart.
// In practice, only a subset of these preferences may actually require a
// restart, so we could switch their values. But we treat them all the same
@@ -436,7 +441,7 @@ var initializeSecurityPrefs = function () {
// properly applied. See tor-browser#43783.
// In the case where it does match a pre-set security level, the custom
// flag will be cleared at the next startup.
- Services.prefs.setBoolPref(kCustomPref, true);
+ SecurityLevelPrefs.setCustomAndWarn();
}),
});
}
@@ -505,12 +510,27 @@ export class SecurityLevel {
}
/**
- * @typedef {object} SecurityLevelRestartNotificationHandler
+ * @callback SecurityLevelTryRestartBrowserCallback
+ *
+ * @returns {Promise<boolean>} - A promise that resolves when the user has made
+ * a decision. Should return `true` when the browser is now restarting.
+ */
+/**
+ * @callback SecurityLevelShowCustomWarningCallback
*
- * An object that can serve the user a restart notification.
+ * @param {Function} userDismissedCallback - A callback that should be called
+ * if the user has acknowledged and dismissed the notification.
+ */
+/**
+ * @typedef {object} SecurityLevelNotificationHandler
*
- * @property {Function} tryRestartBrowser - The method that should be called to
- * ask the user to restart the browser.
+ * An object that can serve the user notifications.
+ *
+ * @property {SecurityLevelTryRestartBrowserCallback} tryRestartBrowser - The
+ * method that should be called to ask the user to restart the browser.
+ * @property {SecurityLevelShowCustomWarningCallback} showCustomWarning - The
+ * method that should be called to let the user know they have a custom
+ * security level.
*/
/*
@@ -526,6 +546,8 @@ export const SecurityLevelPrefs = {
}),
security_slider_pref: "browser.security_level.security_slider",
security_custom_pref: "browser.security_level.security_custom",
+ _customWarningDismissedPref:
+ "browser.security_level.custom_warning_dismissed",
/**
* The current security level preference.
@@ -581,18 +603,18 @@ export const SecurityLevelPrefs = {
},
/**
- * Whether the browser should be restarted to apply the security level.
+ * The external handler that can show a notification to the user, if any.
*
- * @type {boolean}
+ * @type {?SecurityLevelNotificationHandler}
*/
- _needRestart: false,
+ _notificationHandler: null,
/**
- * The external handler that can show a notification to the user, if any.
+ * The notifications we are waiting for a handler to show.
*
- * @type {?SecurityLevelRestartNotificationHandler}
+ * @type {Set}
*/
- _restartNotificationHandler: null,
+ _pendingNotifications: {},
/**
* Set the external handler for showing notifications to the user.
@@ -600,49 +622,73 @@ export const SecurityLevelPrefs = {
* This should only be called once per session once the handler is ready to
* show a notification, which may occur immediately during this call.
*
- * @param {SecurityLevelRestartNotificationHandler} handler - The new handler
- * to use.
+ * @param {SecurityLevelNotificationHandler} handler - The new handler to use.
*/
- setRestartNotificationHandler(handler) {
+ setNotificationHandler(handler) {
logger.info("Restart notification handler is set");
- this._restartNotificationHandler = handler;
- if (this._needRestart) {
- // Show now using the new handler.
- this._tryShowRestartNotification();
- }
+ this._notificationHandler = handler;
+ this._tryShowNotifications(this._pendingNotifications);
},
/**
* A promise for any ongoing notification prompt task.
*
- * @type {Promise}
+ * Resolves with whether the browser is restarting.
+ *
+ * @type {Promise<boolean>}
*/
_restartNotificationPromise: null,
/**
- * Try show a notification to the user.
+ * Try show notifications to the user.
+ *
+ * If no notification handler has been attached yet, this will queue the
+ * notification for when it is added, if ever.
*
- * If no notification handler has been attached yet, this will do nothing.
+ * @param {object} notifications - The notifications to try and show.
+ * @param {boolean} notifications.restart - Whether to show the restart
+ * notification.
+ * @param {boolean} notifications.custom - Whether to show the custom security
+ * level notification.
*/
- async _tryShowRestartNotification() {
- if (!this._restartNotificationHandler) {
- logger.info("Missing a restart notification handler");
+ async _tryShowNotifications(notifications) {
+ if (!this._notificationHandler) {
+ logger.info("Missing a notification handler", notifications);
// This may be added later in the session.
+ if (notifications.custom) {
+ this._pendingNotifications.custom = true;
+ }
+ if (notifications.restart) {
+ this._pendingNotifications.restart = true;
+ }
return;
}
- const prevPromise = this._restartNotificationPromise;
- let resolve;
- ({ promise: this._restartNotificationPromise, resolve } =
- Promise.withResolvers());
- await prevPromise;
-
- try {
- await this._restartNotificationHandler?.tryRestartBrowser();
- } finally {
- // Allow the notification to be shown again.
- resolve();
+ let isRestarting = false;
+ if (notifications.restart) {
+ const prevPromise = this._restartNotificationPromise;
+ let resolve;
+ ({ promise: this._restartNotificationPromise, resolve } =
+ Promise.withResolvers());
+ await prevPromise;
+
+ try {
+ isRestarting = await this._notificationHandler?.tryRestartBrowser();
+ } finally {
+ // Allow the notification to be shown again.
+ resolve();
+ }
+ }
+ // NOTE: We wait for the restart notification to resolve before showing the
+ // custom warning. We do not show the warning if we are already restarting.
+ if (!isRestarting && notifications.custom) {
+ this._notificationHandler?.showCustomWarning(() => {
+ // User has acknowledged and dismissed the notification.
+ Services.prefs.setBoolPref(this._customWarningDismissedPref, true);
+ });
}
+
+ this._pendingNotifications = {};
},
/**
@@ -658,7 +704,6 @@ export const SecurityLevelPrefs = {
// At the next startup, the custom flag may be cleared if the settings are
// as expected.
Services.prefs.setBoolPref(kCustomPref, true);
- this._needRestart = true;
// NOTE: We need to change the controlled security level preferences in
// response to the desired change in security level. We could either:
@@ -684,6 +729,39 @@ export const SecurityLevelPrefs = {
// still be marked as "custom" because:
// 1. Some preferences require a browser restart to be applied.
// 2. NoScript has not been updated with the new settings.
- this._tryShowRestartNotification();
+
+ this._tryShowNotifications({ restart: true, custom: true });
+ },
+
+ /**
+ * Put the user in the custom security level state and show them a warning
+ * about this state.
+ */
+ setCustomAndWarn() {
+ Services.prefs.setBoolPref(kCustomPref, true);
+ // NOTE: We clear _customWarningDismissedPref because the entry points
+ // for this method imply we should re-warn the user each time.
+ Services.prefs.clearUserPref(this._customWarningDismissedPref);
+ this._tryShowNotifications({ custom: true });
+ },
+
+ /**
+ * If the user is in a custom state, try and notify them of this state.
+ */
+ maybeWarnCustom() {
+ const isCustom = Services.prefs.getBoolPref(kCustomPref, false);
+ if (!isCustom) {
+ // Clear the dismissed preference so the user will be re-shown the
+ // notification when they re-enter the custom state.
+ Services.prefs.clearUserPref(this._customWarningDismissedPref);
+ return;
+ }
+ if (Services.prefs.getBoolPref(this._customWarningDismissedPref, false)) {
+ // Do not warn the user of the custom state if they have already
+ // acknowledged and dismissed this in a previous session.
+ return;
+ }
+
+ this._tryShowNotifications({ custom: true });
},
}; /* Security Level Prefs */
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/c1b8577…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/c1b8577…
You're receiving this email because of your account on gitlab.torproject.org.
1
0
[Git][tpo/applications/tor-browser] Pushed new tag FIREFOX_140_3_0esr_BUILD1
by Pier Angelo Vendrame (@pierov) 09 Sep '25
by Pier Angelo Vendrame (@pierov) 09 Sep '25
09 Sep '25
Pier Angelo Vendrame pushed new tag FIREFOX_140_3_0esr_BUILD1 at The Tor Project / Applications / Tor Browser
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/tree/FIREFOX_1…
You're receiving this email because of your account on gitlab.torproject.org.
1
0