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:
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) {