Bug 1039069 - Provide a popup about English for international users. r=arthuredelstein,mconley
MozReview-Commit-ID: IL8i4vzjWQd
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -577,16 +577,22 @@ pref("privacy.sanitize.sanitizeOnShutdow
pref("privacy.sanitize.migrateFx3Prefs", false);
pref("privacy.panicButton.enabled", true);
// Time until temporary permissions expire, in ms
pref("privacy.temporary_permission_expire_time_ms", 3600000);
+// If Accept-Language should be spoofed by en-US
+// 0 - will prompt
+// 1 - don't spoof
+// 2 - spoof
+pref("privacy.spoof_english", 0);
+
pref("network.proxy.share_proxy_settings", false); // use the same proxy settings for all protocols
// simple gestures support
pref("browser.gesture.swipe.left", "Browser:BackOrBackDuplicate");
pref("browser.gesture.swipe.right", "Browser:ForwardOrForwardDuplicate");
pref("browser.gesture.swipe.up", "cmd_scrollTop");
pref("browser.gesture.swipe.down", "cmd_scrollBottom");
#ifdef XP_MACOSX
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -29,16 +29,17 @@ XPCOMUtils.defineLazyModuleGetters(this,
ContentSearch: "resource:///modules/ContentSearch.jsm",
ContextualIdentityService: "resource://gre/modules/ContextualIdentityService.jsm",
CustomizableUI: "resource:///modules/CustomizableUI.jsm",
Deprecated: "resource://gre/modules/Deprecated.jsm",
DownloadsCommon: "resource:///modules/DownloadsCommon.jsm",
E10SUtils: "resource:///modules/E10SUtils.jsm",
ExtensionsUI: "resource:///modules/ExtensionsUI.jsm",
FormValidationHandler: "resource:///modules/FormValidationHandler.jsm",
+ LanguagePrompt: "resource://gre/modules/LanguagePrompt.jsm",
LightweightThemeManager: "resource://gre/modules/LightweightThemeManager.jsm",
Log: "resource://gre/modules/Log.jsm",
LoginManagerParent: "resource://gre/modules/LoginManagerParent.jsm",
NewTabUtils: "resource://gre/modules/NewTabUtils.jsm",
PageActions: "resource:///modules/PageActions.jsm",
PageThumbs: "resource://gre/modules/PageThumbs.jsm",
PluralForm: "resource://gre/modules/PluralForm.jsm",
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
@@ -1816,16 +1817,18 @@ var gBrowserInit = {
CaptivePortalWatcher.uninit();
SidebarUI.uninit();
DownloadsButton.uninit();
gAccessibilityServiceIndicator.uninit();
+ LanguagePrompt.uninit();
+
// Now either cancel delayedStartup, or clean up the services initialized from
// it.
if (this._boundDelayedStartup) {
this._cancelDelayedStartup();
} else {
if (Win7Features)
Win7Features.onCloseWindow();
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -36,16 +36,17 @@ XPCOMUtils.defineLazyModuleGetters(this,
DirectoryLinksProvider: "resource:///modules/DirectoryLinksProvider.jsm",
ExtensionsUI: "resource:///modules/ExtensionsUI.jsm",
Feeds: "resource:///modules/Feeds.jsm",
FileUtils: "resource://gre/modules/FileUtils.jsm",
FileSource: "resource://gre/modules/L10nRegistry.jsm",
FormValidationHandler: "resource:///modules/FormValidationHandler.jsm",
Integration: "resource://gre/modules/Integration.jsm",
L10nRegistry: "resource://gre/modules/L10nRegistry.jsm",
+ LanguagePrompt: "resource://gre/modules/LanguagePrompt.jsm",
LightweightThemeManager: "resource://gre/modules/LightweightThemeManager.jsm",
LoginHelper: "resource://gre/modules/LoginHelper.jsm",
LoginManagerParent: "resource://gre/modules/LoginManagerParent.jsm",
NetUtil: "resource://gre/modules/NetUtil.jsm",
NewTabUtils: "resource://gre/modules/NewTabUtils.jsm",
OS: "resource://gre/modules/osfile.jsm",
PageActions: "resource:///modules/PageActions.jsm",
PageThumbs: "resource://gre/modules/PageThumbs.jsm",
@@ -1161,16 +1162,20 @@ BrowserGlue.prototype = {
handlerService.asyncInit();
});
if (AppConstants.platform == "win") {
Services.tm.idleDispatchToMainThread(() => {
JawsScreenReaderVersionCheck.onWindowsRestored();
});
}
+
+ Services.tm.idleDispatchToMainThread(() => {
+ LanguagePrompt.init();
+ });
},
/**
* Use this function as an entry point to schedule tasks that need
* to run once per session, at any arbitrary point in time.
* This function will be called from an idle observer. Check the value of
* LATE_TASKS_IDLE_TIME_SEC to see the current value for this idle
* observer.
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -492,16 +492,19 @@ offlineApps.manageUsageAccessKey=S
# LOCALIZATION NOTE (canvas.siteprompt): %S is hostname
canvas.siteprompt=Will you allow %S to use your HTML5 canvas image data? This may be used to uniquely identify your computer.
canvas.notAllow=Don’t Allow
canvas.notAllow.accesskey=n
canvas.allow=Allow Data Access
canvas.allow.accesskey=A
canvas.remember=Always remember my decision
+# Spoof Accept-Language prompt
+privacy.spoof_english=Changing your language setting to English will make you more difficult to identify and enhance your privacy. Do you want to request English language versions of web pages?
+
identity.identified.verifier=Verified by: %S
identity.identified.verified_by_you=You have added a security exception for this site.
identity.identified.state_and_country=%S, %S
identity.icon.tooltip=Show site information
identity.extension.label=Extension (%S)
identity.extension.tooltip=Loaded by extension: %S
identity.showDetails.tooltip=Show connection details
new file mode 100644
--- /dev/null
+++ b/toolkit/components/resistfingerprinting/LanguagePrompt.jsm
@@ -0,0 +1,205 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+/* 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";
+
+this.EXPORTED_SYMBOLS = ["LanguagePrompt"];
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const kPrefResistFingerprinting = "privacy.resistFingerprinting";
+const kPrefSpoofEnglish = "privacy.spoof_english";
+const kTopicHttpOnModifyRequest = "http-on-modify-request";
+
+class _LanguagePrompt {
+ constructor() {
+ this._initialized = false;
+ }
+
+ init() {
+ if (this._initialized) {
+ return;
+ }
+ this._initialized = true;
+
+ Services.prefs.addObserver(kPrefResistFingerprinting, this);
+ this._handleResistFingerprintingChanged();
+ }
+
+ uninit() {
+ if (!this._initialized) {
+ return;
+ }
+ this._initialized = false;
+
+ Services.prefs.removeObserver(kPrefResistFingerprinting, this);
+ this._removeObservers();
+ }
+
+ observe(subject, topic, data) {
+ switch (topic) {
+ case "nsPref:changed":
+ this._handlePrefChanged(data);
+ break;
+ case kTopicHttpOnModifyRequest:
+ this._handleHttpOnModifyRequest(subject, data);
+ break;
+ default:
+ break;
+ }
+ }
+
+ _removeObservers() {
+ try {
+ Services.pref.removeObserver(kPrefSpoofEnglish, this);
+ } catch (e) {
+ // do nothing
+ }
+ try {
+ Services.obs.removeObserver(this, kTopicHttpOnModifyRequest);
+ } catch (e) {
+ // do nothing
+ }
+ }
+
+ _shouldPromptForLanguagePref() {
+ return (Services.locale.getAppLocaleAsLangTag().substr(0, 2) !== "en")
+ && (Services.prefs.getIntPref(kPrefSpoofEnglish) === 0);
+ }
+
+ _handlePrefChanged(data) {
+ switch (data) {
+ case kPrefResistFingerprinting:
+ this._handleResistFingerprintingChanged();
+ break;
+ case kPrefSpoofEnglish:
+ this._handleSpoofEnglishChanged();
+ break;
+ default:
+ break;
+ }
+ }
+
+ _handleResistFingerprintingChanged() {
+ if (Services.prefs.getBoolPref(kPrefResistFingerprinting)) {
+ Services.prefs.addObserver(kPrefSpoofEnglish, this);
+ if (this._shouldPromptForLanguagePref()) {
+ Services.obs.addObserver(this, kTopicHttpOnModifyRequest);
+ }
+ } else {
+ this._removeObservers();
+ Services.prefs.setIntPref(kPrefSpoofEnglish, 0);
+ }
+ }
+
+ _handleSpoofEnglishChanged() {
+ switch (Services.prefs.getIntPref(kPrefSpoofEnglish)) {
+ case 0: // will prompt
+ // This should only happen when turning privacy.resistFingerprinting off.
+ // Works like disabling accept-language spoofing.
+ case 1: // don't spoof
+ if (Services.prefs.prefHasUserValue("javascript.use_us_english_locale")) {
+ Services.prefs.clearUserPref("javascript.use_us_english_locale");
+ }
+ // We don't reset intl.accept_languages. Instead, setting
+ // privacy.spoof_english to 1 allows user to change preferred language
+ // settings through Preferences UI.
+ break;
+ case 2: // spoof
+ Services.prefs.setCharPref("intl.accept_languages", "en-US, en");
+ Services.prefs.setBoolPref("javascript.use_us_english_locale", true);
+ break;
+ default:
+ break;
+ }
+ }
+
+ _handleHttpOnModifyRequest(subject, data) {
+ // If we are loading an HTTP page from content, show the
+ // "request English language web pages?" prompt.
+ let httpChannel;
+ try {
+ httpChannel = subject.QueryInterface(Ci.nsIHttpChannel);
+ } catch (e) {
+ return;
+ }
+
+ if (!httpChannel) {
+ return;
+ }
+
+ let notificationCallbacks = httpChannel.notificationCallbacks;
+ if (!notificationCallbacks) {
+ return;
+ }
+
+ let loadContext = notificationCallbacks.getInterface(Ci.nsILoadContext);
+ if (!loadContext || !loadContext.isContent) {
+ return;
+ }
+
+ if (!subject.URI.schemeIs("http") && !subject.URI.schemeIs("https")) {
+ return;
+ }
+ // The above QI did not throw, the scheme is http[s], and we know the
+ // load context is content, so we must have a true HTTP request from content.
+ // Stop the observer and display the prompt if another window has
+ // not already done so.
+ Services.obs.removeObserver(this, kTopicHttpOnModifyRequest);
+
+ if (!this._shouldPromptForLanguagePref()) {
+ return;
+ }
+
+ this._promptForLanguagePreference();
+
+ // The Accept-Language header for this request was set when the
+ // channel was created. Reset it to match the value that will be
+ // used for future requests.
+ let val = this._getCurrentAcceptLanguageValue(subject.URI);
+ if (val) {
+ httpChannel.setRequestHeader("Accept-Language", val, false);
+ }
+ }
+
+ _promptForLanguagePreference() {
+ // Display two buttons, both with string titles.
+ let flags = Services.prompt.STD_YES_NO_BUTTONS;
+ let brandBundle = Services.strings.createBundle(
+ "chrome://branding/locale/brand.properties");
+ let brandShortName = brandBundle.GetStringFromName("brandShortName");
+ let navigatorBundle = Services.strings.createBundle(
+ "chrome://browser/locale/browser.properties");
+ let message = navigatorBundle.formatStringFromName(
+ "privacy.spoof_english", [brandShortName], 1);
+ let response = Services.prompt.confirmEx(
+ null, "", message, flags, null, null, null, null, {value: false});
+
+ // Update preferences to reflect their response and to prevent the prompt
+ // from being displayed again.
+ Services.prefs.setIntPref(kPrefSpoofEnglish, (response == 0) ? 2 : 1);
+ }
+
+ _getCurrentAcceptLanguageValue(uri) {
+ let channel = Services.io.newChannelFromURI2(
+ uri,
+ null, // aLoadingNode
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_OTHER);
+ let httpChannel;
+ try {
+ httpChannel = channel.QueryInterface(Ci.nsIHttpChannel);
+ } catch (e) {
+ return null;
+ }
+ return httpChannel.getRequestHeader("Accept-Language");
+ }
+}
+
+let LanguagePrompt = new _LanguagePrompt();
--- a/toolkit/components/resistfingerprinting/moz.build
+++ b/toolkit/components/resistfingerprinting/moz.build
@@ -8,8 +8,12 @@ UNIFIED_SOURCES += [
'nsRFPService.cpp',
]
FINAL_LIBRARY = 'xul'
EXPORTS += [
'nsRFPService.h',
]
+
+EXTRA_JS_MODULES += [
+ 'LanguagePrompt.jsm',
+]