tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 77ec8bb464f073f221643b79b0138521a67ebf67
parent 212c86a4b34424194c94cbfe4db503a07764a821
Author: Alexandra Borovova <aborovova@mozilla.com>
Date:   Thu, 30 Oct 2025 15:37:25 +0000

Bug 1994396 - Override "navigator.language/s" when "browsingContext.language" is set. r=dom-core,smaug

Differential Revision: https://phabricator.services.mozilla.com/D269934

Diffstat:
Mdocshell/base/BrowsingContext.cpp | 5+++++
Mdom/base/Navigator.cpp | 27++++++++++++++++++++++++---
Mdom/base/Navigator.h | 8+++++++-
Mdom/base/test/browser_language_override.js | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mdom/workers/RuntimeService.cpp | 4++--
Mtoolkit/components/resistfingerprinting/nsUserCharacteristics.cpp | 2+-
6 files changed, 100 insertions(+), 11 deletions(-)

diff --git a/docshell/base/BrowsingContext.cpp b/docshell/base/BrowsingContext.cpp @@ -38,6 +38,7 @@ #include "mozilla/dom/LocationBinding.h" #include "mozilla/dom/MediaDevices.h" #include "mozilla/dom/Navigation.h" +#include "mozilla/dom/Navigator.h" #include "mozilla/dom/PopupBlocker.h" #include "mozilla/dom/ReferrerInfo.h" #include "mozilla/dom/ScriptSettings.h" @@ -3333,6 +3334,10 @@ void BrowsingContext::DidSet(FieldIndex<IDX_LanguageOverride>, JS::SetRealmLocaleOverride( realm, PromiseFlatCString(languageOverride).get()); } + + if (Navigator* navigator = window->Navigator()) { + navigator->ClearLanguageCache(); + } } } }); diff --git a/dom/base/Navigator.cpp b/dom/base/Navigator.cpp @@ -338,14 +338,19 @@ void Navigator::GetAppName(nsAString& aAppName) const { * for more detail. */ /* static */ -void Navigator::GetAcceptLanguages(nsTArray<nsString>& aLanguages) { +void Navigator::GetAcceptLanguages(nsTArray<nsString>& aLanguages, + const nsCString* aLanguageOverride) { MOZ_ASSERT(NS_IsMainThread()); aLanguages.Clear(); // E.g. "de-de, en-us,en". nsAutoCString acceptLang; - intl::LocaleService::GetInstance()->GetAcceptLanguages(acceptLang); + if (aLanguageOverride) { + acceptLang.Assign(aLanguageOverride->get()); + } else { + intl::LocaleService::GetInstance()->GetAcceptLanguages(acceptLang); + } // Split values on commas. for (nsDependentCSubstring lang : @@ -397,7 +402,18 @@ void Navigator::GetLanguage(nsAString& aLanguage) { } void Navigator::GetLanguages(nsTArray<nsString>& aLanguages) { - GetAcceptLanguages(aLanguages); + BrowsingContext* bc = mWindow ? mWindow->GetBrowsingContext() : nullptr; + if (bc) { + const nsCString& languageOverride = bc->Top()->GetLanguageOverride(); + + if (!languageOverride.IsEmpty()) { + GetAcceptLanguages(aLanguages, &languageOverride); + + return; + } + } + + GetAcceptLanguages(aLanguages, nullptr); // The returned value is cached by the binding code. The window listens to the // accept languages change and will clear the cache when needed. It has to @@ -1980,6 +1996,11 @@ void Navigator::ClearPlatformCache() { Navigator_Binding::ClearCachedPlatformValue(this); } +void Navigator::ClearLanguageCache() { + Navigator_Binding::ClearCachedLanguageValue(this); + Navigator_Binding::ClearCachedLanguagesValue(this); +} + nsresult Navigator::GetPlatform(nsAString& aPlatform, Document* aCallerDoc, bool aUsePrefOverriddenValue) { MOZ_ASSERT(NS_IsMainThread()); diff --git a/dom/base/Navigator.h b/dom/base/Navigator.h @@ -147,6 +147,11 @@ class Navigator final : public nsISupports, public nsWrapperCache { Maybe<bool> aShouldResistFingerprinting, nsAString& aUserAgent); + // Clears the language cache by calling: + // Navigator_Binding::ClearCachedLanguageValue(this); and + // Navigator_Binding::ClearCachedLanguagesValue(this); + void ClearLanguageCache(); + // Clears the platform cache by calling: // Navigator_Binding::ClearCachedPlatformValue(this); void ClearPlatformCache(); @@ -220,7 +225,8 @@ class Navigator final : public nsISupports, public nsWrapperCache { StorageManager* Storage(); - static void GetAcceptLanguages(nsTArray<nsString>& aLanguages); + static void GetAcceptLanguages(nsTArray<nsString>& aLanguages, + const nsCString* aLanguageOverride); dom::MediaCapabilities* MediaCapabilities(); dom::MediaSession* MediaSession(); diff --git a/dom/base/test/browser_language_override.js b/dom/base/test/browser_language_override.js @@ -16,6 +16,7 @@ add_task(async function test_set_language_override() { info("Get default language"); const defaultLanguage = await getIntlLanguage(browser); + const defaultNavigatorLanguage = await getNavigatorLanguage(browser); const browsingContext = browser.browsingContext; @@ -36,7 +37,11 @@ add_task(async function test_set_language_override() { info("Reset language override"); browsingContext.languageOverride = ""; - await assertLanguageIsNotOverridden(browser, defaultLanguage); + await assertLanguageIsNotOverridden( + browser, + defaultLanguage, + defaultNavigatorLanguage + ); BrowserTestUtils.removeTab(tab); }); @@ -81,9 +86,11 @@ add_task(async function test_set_language_override_in_different_contexts() { info("Get default language in the first tab"); const defaultLanguage1 = await getIntlLanguage(browser1); + const defaultNavigatorLanguage1 = await getNavigatorLanguage(browser1); info("Get default language in the second tab"); const defaultLanguage2 = await getIntlLanguage(browser2); + const defaultNavigatorLanguage2 = await getNavigatorLanguage(browser2); const browsingContext1 = browser1.browsingContext; @@ -94,11 +101,19 @@ add_task(async function test_set_language_override_in_different_contexts() { await assertLanguageOverridden(browser1, languageOverride); info("Make sure that in the second tab language is not overridden"); - await assertLanguageIsNotOverridden(browser2, defaultLanguage2); + await assertLanguageIsNotOverridden( + browser2, + defaultLanguage2, + defaultNavigatorLanguage2 + ); info("Reset language override"); browsingContext1.languageOverride = ""; - await assertLanguageIsNotOverridden(browser1, defaultLanguage1); + await assertLanguageIsNotOverridden( + browser1, + defaultLanguage1, + defaultNavigatorLanguage1 + ); BrowserTestUtils.removeTab(tab1); BrowserTestUtils.removeTab(tab2); @@ -120,18 +135,60 @@ async function getIntlLanguage(browser) { }); } +async function getNavigatorLanguage(browser) { + return SpecialPowers.spawn(browser, [], () => { + return content.eval(`window.navigator.language`); + }); +} + +async function getNavigatorLanguages(browser) { + return SpecialPowers.spawn(browser, [], () => { + return content.eval(`window.navigator.languages`); + }); +} + async function assertLanguageOverridden(browser, languageOverride) { is( await getIntlLanguage(browser), languageOverride, "new Intl.DateTimeFormat().resolvedOptions().locale is overridden" ); + + is( + await getNavigatorLanguage(browser), + languageOverride, + "navigator.language is overridden" + ); + + const navigatorLanguages = await getNavigatorLanguages(browser); + is( + navigatorLanguages.includes(languageOverride), + true, + "navigator.languages is overridden" + ); } -async function assertLanguageIsNotOverridden(browser, defaultLanguage) { +async function assertLanguageIsNotOverridden( + browser, + defaultLanguage, + defaultNavigatorLanguage +) { is( await getIntlLanguage(browser), defaultLanguage, "new Intl.DateTimeFormat().resolvedOptions().locale is not overridden" ); + + is( + await getNavigatorLanguage(browser), + defaultNavigatorLanguage, + "navigator.language is not overridden" + ); + + const navigatorLanguages = await getNavigatorLanguages(browser); + is( + navigatorLanguages.includes(defaultNavigatorLanguage), + true, + "navigator.languages is not overridden" + ); } diff --git a/dom/workers/RuntimeService.cpp b/dom/workers/RuntimeService.cpp @@ -1117,7 +1117,7 @@ void PrefLanguagesChanged(const char* /* aPrefName */, void* /* aClosure */) { AssertIsOnMainThread(); nsTArray<nsString> languages; - Navigator::GetAcceptLanguages(languages); + Navigator::GetAcceptLanguages(languages, nullptr); RuntimeService* runtime = RuntimeService::GetService(); if (runtime) { @@ -1308,7 +1308,7 @@ bool RuntimeService::RegisterWorker(WorkerPrivate& aWorkerPrivate) { // The navigator overridden properties should have already been read. - Navigator::GetAcceptLanguages(mNavigatorProperties.mLanguages); + Navigator::GetAcceptLanguages(mNavigatorProperties.mLanguages, nullptr); mNavigatorPropertiesLoaded = true; } diff --git a/toolkit/components/resistfingerprinting/nsUserCharacteristics.cpp b/toolkit/components/resistfingerprinting/nsUserCharacteristics.cpp @@ -589,7 +589,7 @@ void PopulateLanguages() { // sufficient to only collect this information as the other properties are // just reformats of Navigator::GetAcceptLanguages. nsTArray<nsString> languages; - dom::Navigator::GetAcceptLanguages(languages); + dom::Navigator::GetAcceptLanguages(languages, nullptr); nsCString output = "["_ns; for (const auto& language : languages) {