tor-browser

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

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:
Mnetwerk/protocol/http/HttpBaseChannel.cpp | 10+++++++++-
Mnetwerk/protocol/http/nsHttpHandler.cpp | 31++++++++++++++++++++-----------
Mnetwerk/protocol/http/nsHttpHandler.h | 4++--
Mnetwerk/test/browser/browser.toml | 3+++
Anetwerk/test/browser/browser_accept_language_override.js | 152+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Anetwerk/test/browser/request_accept_language.sjs | 7+++++++
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)); +}