commit 391b807db33093138a3f9af9bc749e7d019dac10
parent 0c1d344289f40da67094dc51825724b3378ba80f
Author: Alexandra Borovova <aborovova@mozilla.com>
Date: Tue, 11 Nov 2025 14:31:26 +0000
Bug 1995691 - Override "Accept-Language" header when browsingContext.languageOverride is set. r=necko-reviewers,jesup
Differential Revision: https://phabricator.services.mozilla.com/D270815
Diffstat:
6 files changed, 193 insertions(+), 14 deletions(-)
diff --git a/netwerk/protocol/http/HttpBaseChannel.cpp b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -394,10 +394,18 @@ nsresult HttpBaseChannel::Init(nsIURI* aURI, uint32_t aCaps,
}
}
+ RefPtr<mozilla::dom::BrowsingContext> browsingContext;
+ mLoadInfo->GetBrowsingContext(getter_AddRefs(browsingContext));
+
+ const nsCString& languageOverride =
+ browsingContext ? browsingContext->Top()->GetLanguageOverride()
+ : EmptyCString();
+
rv = gHttpHandler->AddStandardRequestHeaders(
&mRequestHead, aURI, isHTTPS, contentPolicyType,
nsContentUtils::ShouldResistFingerprinting(this,
- RFPTarget::HttpUserAgent));
+ RFPTarget::HttpUserAgent),
+ languageOverride);
if (NS_FAILED(rv)) return rv;
nsAutoCString type;
diff --git a/netwerk/protocol/http/nsHttpHandler.cpp b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -733,7 +733,8 @@ nsresult nsHttpHandler::AddAcceptAndDictionaryHeaders(
nsresult nsHttpHandler::AddStandardRequestHeaders(
nsHttpRequestHead* request, nsIURI* aURI, bool aIsHTTPS,
- ExtContentPolicyType aContentPolicyType, bool aShouldResistFingerprinting) {
+ ExtContentPolicyType aContentPolicyType, bool aShouldResistFingerprinting,
+ const nsCString& aLanguageOverride) {
nsresult rv;
// Add the "User-Agent" header
@@ -765,18 +766,26 @@ nsresult nsHttpHandler::AddStandardRequestHeaders(
nsHttpHeaderArray::eVarietyRequestOverride);
if (NS_FAILED(rv)) return rv;
- // Add the "Accept-Language" header. This header is also exposed to the
- // service worker.
- if (mAcceptLanguagesIsDirty) {
- rv = SetAcceptLanguages();
- MOZ_ASSERT(NS_SUCCEEDED(rv));
- }
-
- // Add the "Accept-Language" header
- if (!mAcceptLanguages.IsEmpty()) {
- rv = request->SetHeader(nsHttp::Accept_Language, mAcceptLanguages, false,
+ if (!aLanguageOverride.IsEmpty()) {
+ nsAutoCString acceptLanguage;
+ acceptLanguage.Assign(aLanguageOverride.get());
+ rv = request->SetHeader(nsHttp::Accept_Language, acceptLanguage, false,
nsHttpHeaderArray::eVarietyRequestOverride);
if (NS_FAILED(rv)) return rv;
+ } else {
+ // Add the "Accept-Language" header. This header is also exposed to the
+ // service worker.
+ if (mAcceptLanguagesIsDirty) {
+ rv = SetAcceptLanguages();
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ // Add the "Accept-Language" header
+ if (!mAcceptLanguages.IsEmpty()) {
+ rv = request->SetHeader(nsHttp::Accept_Language, mAcceptLanguages, false,
+ nsHttpHeaderArray::eVarietyRequestOverride);
+ if (NS_FAILED(rv)) return rv;
+ }
}
// add the "Send Hint" header
diff --git a/netwerk/protocol/http/nsHttpHandler.h b/netwerk/protocol/http/nsHttpHandler.h
@@ -123,8 +123,8 @@ class nsHttpHandler final : public nsIHttpProtocolHandler,
const std::function<bool(bool, DictionaryCacheEntry*)>& aCallback);
[[nodiscard]] nsresult AddStandardRequestHeaders(
nsHttpRequestHead*, nsIURI* aURI, bool aIsHTTPS,
- ExtContentPolicyType aContentPolicyType,
- bool aShouldResistFingerprinting);
+ ExtContentPolicyType aContentPolicyType, bool aShouldResistFingerprinting,
+ const nsCString& aLanguageOverride);
[[nodiscard]] nsresult AddConnectionHeader(nsHttpRequestHead*, uint32_t caps);
bool IsAcceptableEncoding(const char* encoding, bool isSecure);
diff --git a/netwerk/test/browser/browser.toml b/netwerk/test/browser/browser.toml
@@ -13,6 +13,7 @@ support-files = [
"early_hint_asset_html.sjs",
"early_hint_csp_options_html.sjs",
"early_hint_preconnect_html.sjs",
+ "request_accept_language.sjs",
"file_channel.html",
"post.html",
"res.css",
@@ -150,6 +151,8 @@ support-files = ["early_hint_preload_test_helper.sys.mjs",]
["browser_about_cache.js"]
skip-if = ["os == 'linux' && os_version == '24.04' && processor == 'x86_64' && display == 'x11'"] # Bug 1970244
+["browser_accept_language_override.js"]
+
["browser_backgroundtask_purgeHTTPCache.js"]
["browser_bug968273.js"]
diff --git a/netwerk/test/browser/browser_accept_language_override.js b/netwerk/test/browser/browser_accept_language_override.js
@@ -0,0 +1,152 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+const PAGE_URL = "https://example.com/browser/netwerk/test/browser/dummy.html";
+const SCRIPT_URL =
+ "https://example.com/browser/netwerk/test/browser/request_accept_language.sjs";
+const languages = ["en-US", "es-ES", "fr-FR"];
+
+add_task(async function test_set_language_override() {
+ const tab = BrowserTestUtils.addTab(gBrowser, PAGE_URL);
+ const browser = gBrowser.getBrowserForTab(tab);
+
+ await BrowserTestUtils.browserLoaded(browser);
+
+ info("Get default language");
+ const defaultLanguage = await getAcceptLanguageHeader(browser);
+ const browsingContext = browser.browsingContext;
+ const languageOverride = getLanguageToOverride(defaultLanguage);
+
+ info("Set language override");
+ browser.browsingContext.languageOverride = languageOverride;
+ is(
+ await getAcceptLanguageHeader(browser),
+ languageOverride,
+ '"Accept-Language" header is overridden'
+ );
+
+ const secondLanguageOverride = getSecondLanguageToOverride(
+ defaultLanguage,
+ languageOverride
+ );
+
+ info("Set language override again");
+ browsingContext.languageOverride = secondLanguageOverride;
+ is(
+ await getAcceptLanguageHeader(browser),
+ secondLanguageOverride,
+ '"Accept-Language" header is overridden'
+ );
+
+ info("Reset language override");
+ browsingContext.languageOverride = "";
+ is(
+ await getAcceptLanguageHeader(browser),
+ defaultLanguage,
+ '"Accept-Language" header is not overridden'
+ );
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function test_set_language_override_and_navigate() {
+ const tab = BrowserTestUtils.addTab(gBrowser, PAGE_URL);
+ const browser = gBrowser.getBrowserForTab(tab);
+
+ await BrowserTestUtils.browserLoaded(browser);
+
+ info("Get default language");
+ const defaultLanguage = await getAcceptLanguageHeader(browser);
+ const browsingContext = browser.browsingContext;
+ const languageOverride = getLanguageToOverride(defaultLanguage);
+
+ info("Set language override");
+ browsingContext.languageOverride = languageOverride;
+ is(
+ await getAcceptLanguageHeader(browser),
+ languageOverride,
+ '"Accept-Language" header is overridden'
+ );
+
+ info("Navigate browsing context");
+ const url = "https://example.com/chrome/dom/base/test/dummy.html";
+ const loaded = BrowserTestUtils.browserLoaded(browser, false, url, false);
+ BrowserTestUtils.startLoadingURIString(browser, url);
+ await loaded;
+ is(
+ await getAcceptLanguageHeader(browser),
+ languageOverride,
+ '"Accept-Language" header is overridden'
+ );
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function test_set_language_override_in_different_contexts() {
+ const tab1 = BrowserTestUtils.addTab(gBrowser, PAGE_URL);
+ const browser1 = gBrowser.getBrowserForTab(tab1);
+
+ await BrowserTestUtils.browserLoaded(browser1);
+
+ const tab2 = BrowserTestUtils.addTab(gBrowser, PAGE_URL);
+ const browser2 = gBrowser.getBrowserForTab(tab2);
+
+ await BrowserTestUtils.browserLoaded(browser2);
+
+ info("Get default language in the first tab");
+ const defaultLanguage1 = await getAcceptLanguageHeader(browser1);
+
+ info("Get default language in the second tab");
+ const defaultLanguage2 = await getAcceptLanguageHeader(browser2);
+
+ const browsingContext1 = browser1.browsingContext;
+ const languageOverride = getLanguageToOverride(defaultLanguage1);
+
+ info("Set language override to the first tab");
+ browsingContext1.languageOverride = languageOverride;
+ is(
+ await getAcceptLanguageHeader(browser1),
+ languageOverride,
+ '"Accept-Language" header is overridden'
+ );
+
+ info("Make sure that in the second tab language is not overridden");
+ browsingContext1.languageOverride = languageOverride;
+ is(
+ await getAcceptLanguageHeader(browser2),
+ defaultLanguage2,
+ '"Accept-Language" header is not overridden'
+ );
+
+ info("Reset language override");
+ browsingContext1.languageOverride = "";
+ is(
+ await getAcceptLanguageHeader(browser1),
+ defaultLanguage1,
+ '"Accept-Language" header is not overridden'
+ );
+
+ BrowserTestUtils.removeTab(tab1);
+ BrowserTestUtils.removeTab(tab2);
+});
+
+function getLanguageToOverride(defaultLanguage) {
+ return languages.find(lang => lang !== defaultLanguage);
+}
+
+function getSecondLanguageToOverride(defaultLanguage, secondLanguage) {
+ return languages.find(
+ lang => lang !== defaultLanguage && lang !== secondLanguage
+ );
+}
+
+async function getAcceptLanguageHeader(browser) {
+ return SpecialPowers.spawn(browser, [SCRIPT_URL], async url => {
+ const response = await content.fetch(url);
+ return response.json();
+ });
+}
diff --git a/netwerk/test/browser/request_accept_language.sjs b/netwerk/test/browser/request_accept_language.sjs
@@ -0,0 +1,7 @@
+"use strict";
+
+function handleRequest(request, response) {
+ const headers = request.getHeader("Accept-Language");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.write(JSON.stringify(headers));
+}