1// -*- indent-tabs-mode: nil; js-indent-level: 2 -*- 2/* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 4 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 5"use strict"; 6 7var EXPORTED_SYMBOLS = ["LanguagePrompt"]; 8 9ChromeUtils.import("resource://gre/modules/Services.jsm"); 10ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); 11 12const kPrefResistFingerprinting = "privacy.resistFingerprinting"; 13const kPrefSpoofEnglish = "privacy.spoof_english"; 14const kTopicHttpOnModifyRequest = "http-on-modify-request"; 15 16class _LanguagePrompt { 17 constructor() { 18 this._initialized = false; 19 } 20 21 init() { 22 if (this._initialized) { 23 return; 24 } 25 this._initialized = true; 26 27 Services.prefs.addObserver(kPrefResistFingerprinting, this); 28 this._handleResistFingerprintingChanged(); 29 } 30 31 uninit() { 32 if (!this._initialized) { 33 return; 34 } 35 this._initialized = false; 36 37 Services.prefs.removeObserver(kPrefResistFingerprinting, this); 38 this._removeObservers(); 39 } 40 41 observe(subject, topic, data) { 42 switch (topic) { 43 case "nsPref:changed": 44 this._handlePrefChanged(data); 45 break; 46 case kTopicHttpOnModifyRequest: 47 this._handleHttpOnModifyRequest(subject, data); 48 break; 49 default: 50 break; 51 } 52 } 53 54 _removeObservers() { 55 try { 56 Services.pref.removeObserver(kPrefSpoofEnglish, this); 57 } catch (e) { 58 // do nothing 59 } 60 try { 61 Services.obs.removeObserver(this, kTopicHttpOnModifyRequest); 62 } catch (e) { 63 // do nothing 64 } 65 } 66 67 _shouldPromptForLanguagePref() { 68 return (Services.locale.getAppLocaleAsLangTag().substr(0, 2) !== "en") 69 && (Services.prefs.getIntPref(kPrefSpoofEnglish) === 0); 70 } 71 72 _handlePrefChanged(data) { 73 switch (data) { 74 case kPrefResistFingerprinting: 75 this._handleResistFingerprintingChanged(); 76 break; 77 case kPrefSpoofEnglish: 78 this._handleSpoofEnglishChanged(); 79 break; 80 default: 81 break; 82 } 83 } 84 85 _handleResistFingerprintingChanged() { 86 if (Services.prefs.getBoolPref(kPrefResistFingerprinting)) { 87 Services.prefs.addObserver(kPrefSpoofEnglish, this); 88 if (this._shouldPromptForLanguagePref()) { 89 Services.obs.addObserver(this, kTopicHttpOnModifyRequest); 90 } 91 } else { 92 this._removeObservers(); 93 Services.prefs.setIntPref(kPrefSpoofEnglish, 0); 94 } 95 } 96 97 _handleSpoofEnglishChanged() { 98 switch (Services.prefs.getIntPref(kPrefSpoofEnglish)) { 99 case 0: // will prompt 100 // This should only happen when turning privacy.resistFingerprinting off. 101 // Works like disabling accept-language spoofing. 102 case 1: // don't spoof 103 if (Services.prefs.prefHasUserValue("javascript.use_us_english_locale")) { 104 Services.prefs.clearUserPref("javascript.use_us_english_locale"); 105 } 106 // We don't reset intl.accept_languages. Instead, setting 107 // privacy.spoof_english to 1 allows user to change preferred language 108 // settings through Preferences UI. 109 break; 110 case 2: // spoof 111 Services.prefs.setCharPref("intl.accept_languages", "en-US, en"); 112 Services.prefs.setBoolPref("javascript.use_us_english_locale", true); 113 break; 114 default: 115 break; 116 } 117 } 118 119 _handleHttpOnModifyRequest(subject, data) { 120 // If we are loading an HTTP page from content, show the 121 // "request English language web pages?" prompt. 122 let httpChannel; 123 try { 124 httpChannel = subject.QueryInterface(Ci.nsIHttpChannel); 125 } catch (e) { 126 return; 127 } 128 129 if (!httpChannel) { 130 return; 131 } 132 133 let notificationCallbacks = httpChannel.notificationCallbacks; 134 if (!notificationCallbacks) { 135 return; 136 } 137 138 let loadContext = notificationCallbacks.getInterface(Ci.nsILoadContext); 139 if (!loadContext || !loadContext.isContent) { 140 return; 141 } 142 143 if (!subject.URI.schemeIs("http") && !subject.URI.schemeIs("https")) { 144 return; 145 } 146 // The above QI did not throw, the scheme is http[s], and we know the 147 // load context is content, so we must have a true HTTP request from content. 148 // Stop the observer and display the prompt if another window has 149 // not already done so. 150 Services.obs.removeObserver(this, kTopicHttpOnModifyRequest); 151 152 if (!this._shouldPromptForLanguagePref()) { 153 return; 154 } 155 156 this._promptForLanguagePreference(); 157 158 // The Accept-Language header for this request was set when the 159 // channel was created. Reset it to match the value that will be 160 // used for future requests. 161 let val = this._getCurrentAcceptLanguageValue(subject.URI); 162 if (val) { 163 httpChannel.setRequestHeader("Accept-Language", val, false); 164 } 165 } 166 167 _promptForLanguagePreference() { 168 // Display two buttons, both with string titles. 169 let flags = Services.prompt.STD_YES_NO_BUTTONS; 170 let brandBundle = Services.strings.createBundle( 171 "chrome://branding/locale/brand.properties"); 172 let brandShortName = brandBundle.GetStringFromName("brandShortName"); 173 let navigatorBundle = Services.strings.createBundle( 174 "chrome://browser/locale/browser.properties"); 175 let message = navigatorBundle.formatStringFromName( 176 "privacy.spoof_english", [brandShortName], 1); 177 let response = Services.prompt.confirmEx( 178 null, "", message, flags, null, null, null, null, {value: false}); 179 180 // Update preferences to reflect their response and to prevent the prompt 181 // from being displayed again. 182 Services.prefs.setIntPref(kPrefSpoofEnglish, (response == 0) ? 2 : 1); 183 } 184 185 _getCurrentAcceptLanguageValue(uri) { 186 let channel = Services.io.newChannelFromURI2( 187 uri, 188 null, // aLoadingNode 189 Services.scriptSecurityManager.getSystemPrincipal(), 190 null, // aTriggeringPrincipal 191 Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, 192 Ci.nsIContentPolicy.TYPE_OTHER); 193 let httpChannel; 194 try { 195 httpChannel = channel.QueryInterface(Ci.nsIHttpChannel); 196 } catch (e) { 197 return null; 198 } 199 return httpChannel.getRequestHeader("Accept-Language"); 200 } 201} 202 203let LanguagePrompt = new _LanguagePrompt(); 204