morgan pushed to branch mullvad-browser-153.2.0esr-16.0-1 at The Tor Project / Applications / Mullvad Browser Commits: b861f2e0 by Pier Angelo Vendrame at 2026-09-08T14:48:02+02:00 fixup! MB 419: Mullvad Browser migration procedures. BB 45039: Make it easier to test ProfileDataUpgrader customization. - - - - - 1ce322f2 by Pier Angelo Vendrame at 2026-09-08T14:48:02+02:00 fixup! MB 38: Mullvad Browser configuration MB 537: Migrate from Mullvad to Quad9 DoH. - - - - - d1954a60 by Pier Angelo Vendrame at 2026-09-08T14:48:58+02:00 fixup! MB 419: Mullvad Browser migration procedures. MB 537: Implement DoH migration. - - - - - ae13e203 by Henry Wilkes at 2026-09-08T14:49:02+02:00 MB 538: Mullvad DoH discontinued notification. - - - - - a9478885 by Henry Wilkes at 2026-09-08T14:49:02+02:00 fixup! Mullvad Browser strings MB 538: Strings for the DoH notification. - - - - - ab6a2587 by Pier Angelo Vendrame at 2026-09-08T14:49:03+02:00 MB 549: Customize the DoH preferences UI. - - - - - a16dc06a by Pier Angelo Vendrame at 2026-09-08T14:49:03+02:00 fixup! Mullvad Browser strings MB 549: Customize the DoH preferences UI. - - - - - 10 changed files: - browser/app/profile/000-mullvad-browser.js - browser/base/content/browser-main.js - browser/base/content/browser.xhtml - + browser/base/content/mullvadDoHNotification.js - browser/base/jar.mn - browser/components/ProfileDataUpgrader.sys.mjs - browser/components/preferences/config/privacy.mjs - + browser/components/tests/unit/test_profileDataUpgrade_mullvad_doh.js - browser/components/tests/unit/xpcshell.toml - toolkit/locales/en-US/toolkit/global/mullvad-browser.ftl Changes: ===================================== browser/app/profile/000-mullvad-browser.js ===================================== @@ -11,12 +11,14 @@ pref("browser.base-browser-support-url", "https://mullvad.net/en/help/"); pref("browser.toolbars.bookmarks.visibility", "never"); // mullvad-browser#19: Enable Mullvad's DOH -pref("network.trr.uri", "https://dns.mullvad.net/dns-query"); -pref("network.trr.default_provider_uri", "https://dns.mullvad.net/dns-query"); +pref("network.trr.uri", "https://doh-mullvad.quad9.net/dns-query"); +pref("network.trr.default_provider_uri", "https://doh-mullvad.quad9.net/dns-query"); pref("network.trr.mode", 3); -pref("doh-rollout.provider-list", "[{\"UIName\":\"Mullvad\",\"autoDefault\":true,\"canonicalName\":\"\",\"id\":\"mullvad\",\"last_modified\":0,\"schema\":0,\"uri\":\"https://dns.mullvad.net/dns-query\"},{\"UIName\":\"Mullvad (Ad-blocking)\",\"autoDefault\":false,\"canonicalName\":\"\",\"id\":\"mullvad\",\"last_modified\":0,\"schema\":0,\"uri\":\"https://adblock.dns.mullvad.net/dns-query\"}]"); +pref("doh-rollout.provider-list", "[{\"uri\":\"https://doh-mullvad.quad9.net/dns-query\",\"UIName\":\"Quad9 - no threat blocking\",\"schema\":0,\"autoDefault\":false,\"canonicalName\":\"\",\"id\":\"quad9-no-threat-blocking\",\"last_modified\":0},{\"uri\":\"https://dns.quad9.net/dns-query\",\"UIName\":\"Quad9 - secure: threat blocking\",\"schema\":0,\"autoDefault\":true,\"canonicalName\":\"\",\"id\":\"quad9-threat-blocking\",\"last_modified\":0}]"); // mullvad-browser#122: Audit DoH heuristics pref("doh-rollout.disable-heuristics", true); +// mullvad-browser#537: migrate users from Mullvad DoH service. +pref("mullvadbrowser.migration.show_doh_notification", false); // mullvad-browser#87: Windows and Linux need additional work to make the // default browser choice working. ===================================== browser/base/content/browser-main.js ===================================== @@ -30,6 +30,7 @@ Services.scriptloader.loadSubScript("chrome://browser/content/places/places-menupopup.js", this); Services.scriptloader.loadSubScript("chrome://browser/content/search/autocomplete-popup.js", this); Services.scriptloader.loadSubScript("chrome://browser/content/search/searchbar.js", this); + Services.scriptloader.loadSubScript("chrome://browser/content/mullvadDoHNotification.js", this); Services.scriptloader.loadSubScript("chrome://browser/content/languageNotification.js", this); if (AIWindow.isOpeningAIWindow(window)) { ChromeUtils.importESModule("chrome://browser/content/urlbar/SmartbarInput.mjs", { global: "current" }); ===================================== browser/base/content/browser.xhtml ===================================== @@ -93,6 +93,7 @@ <link rel="localization" href="browser/syncedTabs.ftl"/> <link rel="localization" href="browser/profiles.ftl"/> <link rel="localization" href="toolkit/global/base-browser.ftl"/> + <link rel="localization" href="toolkit/global/mullvad-browser.ftl"/> <!-- Untranslated FTL files --> <link rel="localization" href="preview/credentialChooser.ftl" /> <link rel="localization" href="preview/enUS-searchFeatures.ftl" /> ===================================== browser/base/content/mullvadDoHNotification.js ===================================== @@ -0,0 +1,62 @@ +"use strict"; + +window.addEventListener("load", () => { + // A notification to let users known that their DNS over HTTPS settings have + // changed because the mullvad DoH services has been discontinued and + // replaced. tor-browser#538. + const notification = { + // ProfileDataUpgrader.upgradeMB migration will set the `showPref` + // preference conditionally on whether the user is effected. We wait for + // this preference for confirmation. + showPref: "mullvadbrowser.migration.show_doh_notification", + shown: false, + + init() { + Services.prefs.addObserver(this.showPref, this); + this.maybeShow(); + }, + + observe() { + this.maybeShow(); + }, + + async maybeShow() { + if (this.shown || !Services.prefs.getBoolPref(this.showPref, false)) { + return; + } + this.shown = true; + Services.prefs.removeObserver(this.showPref, this); + + gNotificationBox.appendNotification( + "mullvad-doh-notification", + { + label: { "l10n-id": "mullvad-doh-notification-body" }, + priority: gNotificationBox.PRIORITY_INFO_HIGH, + eventCallback: name => { + if (name === "dismissed") { + Services.prefs.clearUserPref(this.showPref); + } + }, + }, + [ + { + "l10n-id": "moz-support-link-text", + link: "https://mullvad.net/en/blog/shutting-down-our-public-encrypted-dns-servers-a...", + }, + { + "l10n-id": "mullvad-doh-notification-settings-button", + primary: true, + callback: () => { + window.openPreferences("privacy-doh"); + Services.prefs.clearUserPref(this.showPref); + // Keep the notification open in case the user wants to click + // "Learn more". + return true; + }, + }, + ] + ); + }, + }; + notification.init(); +}); ===================================== browser/base/jar.mn ===================================== @@ -101,4 +101,5 @@ browser.jar: content/browser/spotlight.js (content/spotlight.js) * content/browser/default-bookmarks.html (content/default-bookmarks.html) + content/browser/mullvadDoHNotification.js (content/mullvadDoHNotification.js) content/browser/languageNotification.js (content/languageNotification.js) ===================================== browser/components/ProfileDataUpgrader.sys.mjs ===================================== @@ -1191,12 +1191,21 @@ export let ProfileDataUpgrader = { Services.prefs.setIntPref(MIGRATION_PREF, MIGRATION_VERSION); }, - async upgradeMB(isNewProfile) { + /** + * Run the profile data migration for Tor Browser if needed. + * + * @param {boolean} isNewProfile When true, just set the migration version + * without actually changing anything. + * @param {number} [currentVersion] The version to migrating from. To be used + * only by tests. + */ + async upgradeMB(isNewProfile, currentVersion) { // Version 1: Mullvad Browser 14.5a6: Clear home page update url preference // (mullvad-browser#411). // Version 2: Mullvad Browser 15.0a2: Remove legacy search addons // (tor-browser#43111). - const MB_MIGRATION_VERSION = 2; + // Version 3: Mullvad Browser 16.0a12: DoH migration (mullvad-browser#537). + const MB_MIGRATION_VERSION = 3; const MIGRATION_PREF = "mullvadbrowser.migration.version"; if (isNewProfile) { @@ -1209,11 +1218,14 @@ export let ProfileDataUpgrader = { console.error("upgradeTB: isNewProfile is undefined."); } - const currentVersion = Services.prefs.getIntPref(MIGRATION_PREF, 0); + if (currentVersion === undefined) { + currentVersion = Services.prefs.getIntPref(MIGRATION_PREF, 0); + } if (currentVersion < 1) { Services.prefs.clearUserPref("mullvadbrowser.post_update.url"); } + const dropAddons = async list => { for (const id of list) { try { @@ -1222,6 +1234,7 @@ export let ProfileDataUpgrader = { } catch {} } }; + if (currentVersion < 2) { await dropAddons([ "brave@search.mozilla.org", @@ -1234,6 +1247,36 @@ export let ProfileDataUpgrader = { ]); } + if (currentVersion < 3) { + this.upgradeDoH(); + } + Services.prefs.setIntPref(MIGRATION_PREF, MB_MIGRATION_VERSION); }, + + upgradeDoH() { + const DOH_NOTIFICATION_PREF = + "mullvadbrowser.migration.show_doh_notification"; + const DOH_URI_PREF = "network.trr.uri"; + const DOH_MODE_PREF = "network.trr.mode"; + const MULLVAD_ADBLOCK_URL = "https://adblock.dns.mullvad.net/dns-query"; + + const dohMode = Services.prefs.getIntPref(DOH_MODE_PREF, -1); + const usesAdBlock = + Services.prefs.getStringPref(DOH_URI_PREF, "") === MULLVAD_ADBLOCK_URL; + const usesCustomUri = + Services.prefs.prefHasUserValue(DOH_URI_PREF) && !usesAdBlock; + if ( + dohMode === Ci.nsIDNSService.MODE_TRROFF || + (dohMode === Ci.nsIDNSService.MODE_TRRONLY && usesCustomUri) + ) { + return; + } + + Services.prefs.setIntPref(DOH_MODE_PREF, Ci.nsIDNSService.MODE_TRRONLY); + if (usesAdBlock) { + Services.prefs.clearUserPref(DOH_URI_PREF); + } + Services.prefs.setBoolPref(DOH_NOTIFICATION_PREF, true); + }, }; ===================================== browser/components/preferences/config/privacy.mjs ===================================== @@ -1103,15 +1103,10 @@ SettingGroupManager.registerGroups({ id: "dohRadioGroup", control: "moz-radio-group", options: [ - { - id: "dohRadioDefault", - value: "default", - l10nId: "preferences-doh-radio-default", - }, { id: "dohRadioCustom", value: "custom", - l10nId: "preferences-doh-radio-custom", + l10nId: "mullvad-preferences-doh-radio-always-on", items: [ { id: "dohFallbackIfCustom", @@ -1132,7 +1127,7 @@ SettingGroupManager.registerGroups({ { id: "dohRadioOff", value: "off", - l10nId: "preferences-doh-radio-off", + l10nId: "mullvad-preferences-doh-radio-off", }, ], }, @@ -3210,14 +3205,13 @@ Preferences.addSetting({ id: "dohModeBoxItem", deps: ["dohMode"], getControlConfig: (config, deps) => { - let l10nId = "preferences-doh-overview-off"; + let l10nId = "mullvad-preferences-doh-overview-off"; if (deps.dohMode.value == Ci.nsIDNSService.MODE_NATIVEONLY) { l10nId = "preferences-doh-overview-default"; - } else if ( - deps.dohMode.value == Ci.nsIDNSService.MODE_TRRFIRST || - deps.dohMode.value == Ci.nsIDNSService.MODE_TRRONLY - ) { + } else if (deps.dohMode.value == Ci.nsIDNSService.MODE_TRRFIRST) { l10nId = "preferences-doh-overview-custom"; + } else if (deps.dohMode.value == Ci.nsIDNSService.MODE_TRRONLY) { + l10nId = "mullvad-preferences-doh-overview-always-on"; } return { ...config, @@ -3428,6 +3422,7 @@ Preferences.addSetting({ // for the mismatch of control-to-pref. deps: ["dohMode"], disabled: ({ dohMode }) => dohMode.locked, + visible: () => false, onUserChange: val => { if (val) { Glean.securityDohSettings.modeChangedButton.record({ ===================================== browser/components/tests/unit/test_profileDataUpgrade_mullvad_doh.js ===================================== @@ -0,0 +1,115 @@ +/* Any copyright is dedicated to the Public Domain. +https://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { ProfileDataUpgrader } = ChromeUtils.importESModule( + "moz-src:///browser/components/ProfileDataUpgrader.sys.mjs" +); + +const DOH_NOTIFICATION_PREF = "mullvadbrowser.migration.show_doh_notification"; +const DOH_URI_PREF = "network.trr.uri"; +const DOH_MODE_PREF = "network.trr.mode"; +const MULLVAD_ADBLOCK_URL = "https://adblock.dns.mullvad.net/dns-query"; + +/** + * Run a test case. + * + * The test takes for granted the final mode can be either off (only if it was + * already) or TRR-only in all other cases. + * The caller need to supply the expected outcomes for the other prefs. + * + * @param {number} mode The mode to set before the migration + * @param {string?} oldUri The URI to set before the migration, or null to clear + * the related preference. + * @param {string?} newUri The expected new value for the preference, or null if + * the preference should have its default value. + * @param {boolean} showNotification The expected value for the preference that + * signals that the change notification should be shown to the UI. + */ +async function runTest(mode, oldUri, newUri, showNotification) { + info(`Running test for mode ${mode} and old uri ${oldUri}.`); + + Services.prefs.setIntPref(DOH_MODE_PREF, mode); + if (oldUri !== null) { + Services.prefs.setStringPref(DOH_URI_PREF, oldUri); + } else { + Services.prefs.clearUserPref(DOH_URI_PREF); + } + Services.prefs.clearUserPref(DOH_NOTIFICATION_PREF); + + await ProfileDataUpgrader.upgradeMB(false, 2); + + if (newUri) { + Assert.equal(Services.prefs.getStringPref(DOH_URI_PREF), newUri); + } else { + Assert.ok( + !Services.prefs.prefHasUserValue(DOH_URI_PREF), + "The migration was supposed to clear the custom URI and so it did." + ); + } + + if (mode === Ci.nsIDNSService.MODE_TRROFF) { + Assert.equal( + Services.prefs.getIntPref(DOH_MODE_PREF, -1), + Ci.nsIDNSService.MODE_TRROFF, + "The migration did not turn on DoH." + ); + } else { + Assert.equal( + Services.prefs.getIntPref(DOH_MODE_PREF, -1), + Ci.nsIDNSService.MODE_TRRONLY, + "Regardless of the starting mode, after the migration we are on TRR-only." + ); + } + + Assert.equal( + Services.prefs.getBoolPref(DOH_NOTIFICATION_PREF, false), + showNotification, + `The value for ${DOH_NOTIFICATION_PREF} is correct after the migration` + ); +} + +add_task(async function test_default_uri() { + // Max protection (our default): inherits the URI change, so we need to show + // the notification. + await runTest(Ci.nsIDNSService.MODE_TRRONLY, null, null, true); + // We move default and increased protection to maximum protection, and they + // inherit the default URI: show the notification. + await runTest(Ci.nsIDNSService.MODE_NATIVEONLY, null, null, true); + await runTest(Ci.nsIDNSService.MODE_TRRFIRST, null, null, true); +}); + +add_task(async function test_mullvad_adblock() { + // Mullvad Adblock was retired, so move to the default URI with maximum + // protection. Show the notification. + // This option could be set only with TRR first and TRR only, so test only + // those. + await runTest( + Ci.nsIDNSService.MODE_TRRFIRST, + MULLVAD_ADBLOCK_URL, + null, + true + ); + await runTest(Ci.nsIDNSService.MODE_TRRONLY, MULLVAD_ADBLOCK_URL, null, true); +}); + +add_task(async function test_custom_provider() { + const CUSTOM_URL = "https://example.org/dns"; + // Custom provider + TRR-only: nothing should change, do not show the + // notification. + await runTest(Ci.nsIDNSService.MODE_TRRONLY, CUSTOM_URL, CUSTOM_URL, false); + // Custom provider but TRR-first: we change it to TRR-only, so we should show + // a notification. + await runTest(Ci.nsIDNSService.MODE_TRRFIRST, CUSTOM_URL, CUSTOM_URL, true); + // Firefox's "default" mode and off automatically reset the provider URI, so + // we do not test them here. +}); + +add_task(async function test_update_doh_disabled() { + // No DoH: the migration should not turn it on. I.e., no changes, therefore no + // notification as well. + // Firefox resets the URL to the default one from the UI, so test only the + // default URI case. + await runTest(Ci.nsIDNSService.MODE_TRROFF, null, null, false); +}); ===================================== browser/components/tests/unit/xpcshell.toml ===================================== @@ -37,6 +37,9 @@ skip-if = [ ["test_distribution_observer.js"] +["test_profileDataUpgrade_mullvad_doh.js"] +tags = "mullvad-browser" + ["test_profileDataUpgrade_smartwindow.js"] ["test_profileDataUpgrade_smartwindow_semanticHistory.js"] ===================================== toolkit/locales/en-US/toolkit/global/mullvad-browser.ftl ===================================== @@ -32,3 +32,26 @@ home-mode-choice-mullvad = telemetry-title = Telemetry Information telemetry-description = Telemetry is disabled in { -brand-short-name }. + +## DNS over HTTPS settings customization. + +mullvad-preferences-doh-overview-always-on = + .label = Always on + .description = Always use secure DNS. +mullvad-preferences-doh-overview-off = + .label = Off + .description = Use your default DNS resolver. +mullvad-preferences-doh-radio-always-on = + .label = Always on + .description = Always use secure DNS +mullvad-preferences-doh-radio-off = + .label = Off (recommended when using a VPN) + .description = Use your default DNS resolver + +## DNS over HTTPS provider changed notification. + +# The first sentence should be wrapped in '<strong>' and '</strong>'. +# "Mullvad" is an organization name. +mullvad-doh-notification-body = <strong>You default DNS Over HTTPS settings have been changed.</strong> Mullvad secure DNS service is being discontinued. +mullvad-doh-notification-settings-button = + .label = Review settings View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/compare/a7a... -- View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/compare/a7a... You're receiving this email because of your account on gitlab.torproject.org. Manage all notifications: https://gitlab.torproject.org/-/profile/notifications | Help: https://gitlab.torproject.org/help
participants (1)
-
morgan (@morgan)