3 var torbutton_new_circuit;
6 // Bug 1506 P1-P5: This is the main Torbutton overlay file. Much needs to be
7 // preserved here, but in an ideal world, most of this code should perhaps be
8 // moved into an XPCOM service, and much can also be tossed. See also
9 // individual 1506 comments for details.
11 // TODO: check for leaks: http://www.mozilla.org/scriptable/avoiding-leaks.html
12 // TODO: Double-check there are no strange exploits to defeat:
13 // http://kb.mozillazine.org/Links_to_local_pages_don%27t_work
15 /* global gBrowser, CustomizableUI,
16 createTorCircuitDisplay, gFindBarInitialized,
17 gFindBar, OpenBrowserWindow, PrivateBrowsingUtils,
18 Services, AppConstants
26 torbutton_get_property_string,
27 } = ChromeUtils.import("resource://torbutton/modules/utils.js");
28 let { configureControlPortModule, wait_for_controller } = ChromeUtils.import(
29 "resource://torbutton/modules/tor-control-port.js"
32 const { TorProtocolService } = ChromeUtils.import(
33 "resource://gre/modules/TorProtocolService.jsm"
36 const k_tb_tor_check_failed_topic = "Torbutton:TorCheckFailed";
38 var m_tb_prefs = Services.prefs;
41 var m_tb_wasinited = false;
42 var m_tb_is_main_window = false;
44 var m_tb_control_ipc_file = null; // Set if using IPC (UNIX domain socket).
45 var m_tb_control_port = null; // Set if using TCP.
46 var m_tb_control_host = null; // Set if using TCP.
47 var m_tb_control_pass = null;
49 // Bug 1506 P2: This object keeps Firefox prefs in sync with Torbutton prefs.
50 // It probably could stand some simplification (See #3100). It also belongs
51 // in a component, not the XUL overlay.
52 var torbutton_unique_pref_observer = {
54 this.forced_ua = false;
55 m_tb_prefs.addObserver("extensions.torbutton", this);
56 m_tb_prefs.addObserver("browser.privatebrowsing.autostart", this);
57 m_tb_prefs.addObserver("javascript", this);
58 m_tb_prefs.addObserver("privacy.resistFingerprinting", this);
59 m_tb_prefs.addObserver("privacy.resistFingerprinting.letterboxing", this);
63 m_tb_prefs.removeObserver("extensions.torbutton", this);
64 m_tb_prefs.removeObserver("browser.privatebrowsing.autostart", this);
65 m_tb_prefs.removeObserver("javascript", this);
66 m_tb_prefs.removeObserver("privacy.resistFingerprinting", this);
67 m_tb_prefs.removeObserver(
68 "privacy.resistFingerprinting.letterboxing",
73 // topic: what event occurred
74 // subject: what nsIPrefBranch we're observing
75 // data: which pref has been changed (relative to subject)
76 observe(subject, topic, data) {
77 if (topic !== "nsPref:changed") {
81 case "browser.privatebrowsing.autostart":
82 torbutton_update_disk_prefs();
84 case "extensions.torbutton.use_nontor_proxy":
85 torbutton_use_nontor_proxy();
87 case "privacy.resistFingerprinting":
88 case "privacy.resistFingerprinting.letterboxing":
89 torbutton_update_fingerprinting_prefs();
95 var torbutton_tor_check_observer = {
97 this._obsSvc = Services.obs;
98 this._obsSvc.addObserver(this, k_tb_tor_check_failed_topic);
103 this._obsSvc.removeObserver(this, k_tb_tor_check_failed_topic);
107 observe(subject, topic, data) {
108 if (topic === k_tb_tor_check_failed_topic) {
109 // Update all open about:tor pages.
110 torbutton_abouttor_message_handler.updateAllOpenPages();
112 // If the user does not have an about:tor tab open in the front most
114 var wm = Services.wm;
115 var win = wm.getMostRecentWindow("navigator:browser");
117 let foundTab = false;
118 let tabBrowser = top.gBrowser;
119 for (let i = 0; !foundTab && i < tabBrowser.browsers.length; ++i) {
120 let b = tabBrowser.getBrowserAtIndex(i);
121 foundTab = b.currentURI.spec.toLowerCase() == "about:tor";
125 gBrowser.selectedTab = gBrowser.addTrustedTab("about:tor");
132 var torbutton_new_identity_observers = {
134 Services.obs.addObserver(this, "new-identity-requested");
137 observe(aSubject, aTopic, aData) {
138 if (aTopic !== "new-identity-requested") {
142 // Clear the domain isolation state.
143 torbutton_log(3, "Clearing domain isolator");
144 const domainIsolator = Cc["@torproject.org/domain-isolator;1"].getService(
147 domainIsolator.clearIsolation();
149 torbutton_log(3, "New Identity: Sending NEWNYM");
150 // We only support TBB for newnym.
152 !m_tb_control_pass ||
153 (!m_tb_control_ipc_file && !m_tb_control_port)
155 const warning = torbutton_get_property_string(
156 "torbutton.popup.no_newnym"
160 "Torbutton cannot safely newnym. It does not have access to the Tor Control Port."
162 window.alert(warning);
164 const warning = torbutton_get_property_string(
165 "torbutton.popup.no_newnym"
167 torbutton_send_ctrl_cmd("SIGNAL NEWNYM")
172 "Torbutton was unable to request a new circuit from Tor"
174 window.alert(warning);
180 "Torbutton was unable to request a new circuit from Tor " + e
182 window.alert(warning);
188 function torbutton_is_mobile() {
189 return Services.appinfo.OS === "Android";
192 // Bug 1506 P2-P4: This code sets some version variables that are irrelevant.
193 // It does read out some important environment variables, though. It is
194 // called once per browser window.. This might belong in a component.
195 torbutton_init = function() {
196 torbutton_log(3, "called init()");
198 if (m_tb_wasinited) {
201 m_tb_wasinited = true;
203 // Bug 1506 P4: These vars are very important for New Identity
204 var environ = Cc["@mozilla.org/process/environment;1"].getService(
208 if (environ.exists("TOR_CONTROL_PASSWD")) {
209 m_tb_control_pass = environ.get("TOR_CONTROL_PASSWD");
210 } else if (environ.exists("TOR_CONTROL_COOKIE_AUTH_FILE")) {
211 var cookie_path = environ.get("TOR_CONTROL_COOKIE_AUTH_FILE");
213 if ("" != cookie_path) {
214 m_tb_control_pass = torbutton_read_authentication_cookie(cookie_path);
217 torbutton_log(4, "unable to read authentication cookie");
221 // Try to get password from Tor Launcher.
222 m_tb_control_pass = TorProtocolService.torGetPassword(false);
226 // Try to get the control port IPC file (an nsIFile) from Tor Launcher,
227 // since Tor Launcher knows how to handle its own preferences and how to
228 // resolve relative paths.
230 m_tb_control_ipc_file = TorProtocolService.torGetControlIPCFile();
233 if (!m_tb_control_ipc_file) {
234 if (environ.exists("TOR_CONTROL_PORT")) {
235 m_tb_control_port = environ.get("TOR_CONTROL_PORT");
238 const kTLControlPortPref = "extensions.torlauncher.control_port";
239 m_tb_control_port = m_tb_prefs.getIntPref(kTLControlPortPref);
241 // Since we want to disable some features when Tor Launcher is
242 // not installed (e.g., New Identity), we do not set a default
247 if (environ.exists("TOR_CONTROL_HOST")) {
248 m_tb_control_host = environ.get("TOR_CONTROL_HOST");
251 const kTLControlHostPref = "extensions.torlauncher.control_host";
252 m_tb_control_host = m_tb_prefs.getCharPref(kTLControlHostPref);
254 m_tb_control_host = "127.0.0.1";
259 configureControlPortModule(
260 m_tb_control_ipc_file,
266 // Add about:tor IPC message listener.
267 window.messageManager.addMessageListener(
269 torbutton_abouttor_message_handler
272 setupPreferencesForMobile();
274 torbutton_log(1, "registering Tor check observer");
275 torbutton_tor_check_observer.register();
277 // Create the circuit display even though the control port might not be
278 // ready yet, as the circuit display will wait for the controller to be
281 createTorCircuitDisplay("extensions.torbutton.display_circuit");
282 circuitDisplayCreated = true;
284 torbutton_log(4, "Error creating the tor circuit display " + e);
287 // Arrange for our about:tor content script to be loaded in each frame.
288 window.messageManager.loadFrameScript(
289 "chrome://torbutton/content/aboutTor/aboutTor-content.js",
293 torbutton_new_identity_observers.register();
295 torbutton_log(3, "init completed");
298 var torbutton_abouttor_message_handler = {
299 // Receive IPC messages from the about:tor content script.
300 async receiveMessage(aMessage) {
301 switch (aMessage.name) {
302 case "AboutTor:Loaded":
303 aMessage.target.messageManager.sendAsyncMessage(
304 "AboutTor:ChromeData",
305 await this.getChromeData(true)
311 // Send privileged data to all of the about:tor content scripts.
312 async updateAllOpenPages() {
313 window.messageManager.broadcastAsyncMessage(
314 "AboutTor:ChromeData",
315 await this.getChromeData(false)
319 // The chrome data contains all of the data needed by the about:tor
320 // content process that is only available here (in the chrome process).
321 // It is sent to the content process when an about:tor window is opened
322 // and in response to events such as the browser noticing that Tor is
324 async getChromeData(aIsRespondingToPageLoad) {
326 mobile: torbutton_is_mobile(),
327 updateChannel: AppConstants.MOZ_UPDATE_CHANNEL,
328 torOn: await torbutton_tor_check_ok(),
331 if (aIsRespondingToPageLoad) {
332 const kShouldNotifyPref = "torbrowser.post_update.shouldNotify";
333 if (m_tb_prefs.getBoolPref(kShouldNotifyPref, false)) {
334 m_tb_prefs.clearUserPref(kShouldNotifyPref);
335 dataObj.hasBeenUpdated = true;
336 dataObj.updateMoreInfoURL = this.getUpdateMoreInfoURL();
343 getUpdateMoreInfoURL() {
345 return Services.prefs.getCharPref("torbrowser.post_update.url");
348 // Use the default URL as a fallback.
349 return Services.urlFormatter.formatURLPref(
350 "startup.homepage_override_url"
355 // Bug 1506 P4: Control port interaction. Needed for New Identity.
356 function torbutton_read_authentication_cookie(path) {
357 var file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
358 file.initWithPath(path);
360 "@mozilla.org/network/file-input-stream;1"
361 ].createInstance(Ci.nsIFileInputStream);
362 fileStream.init(file, 1, 0, false);
363 var binaryStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(
364 Ci.nsIBinaryInputStream
366 binaryStream.setInputStream(fileStream);
367 var array = binaryStream.readByteArray(fileStream.available());
368 binaryStream.close();
370 return torbutton_array_to_hexdigits(array);
373 // Bug 1506 P4: Control port interaction. Needed for New Identity.
374 function torbutton_array_to_hexdigits(array) {
377 return String("0" + c.toString(16)).slice(-2);
382 // Bug 1506 P4: Control port interaction. Needed for New Identity.
384 // Asynchronously executes a command on the control port.
385 // returns the response as a string, or null on error
386 async function torbutton_send_ctrl_cmd(command) {
387 const getErrorMessage = e => (e && (e.torMessage || e.message)) || "";
390 const avoidCache = true;
391 let torController = await wait_for_controller(avoidCache);
393 let bytes = await torController.sendCommand(command);
394 if (!bytes.startsWith("250")) {
396 `Unexpected command response on control port '${bytes}'`
399 response = bytes.slice(4);
401 torController.close();
403 let msg = getErrorMessage(err);
404 torbutton_log(4, `Error: ${msg}`);
409 // Bug 1506 P4: Needed for New IP Address
410 torbutton_new_circuit = function() {
411 let firstPartyDomain = getDomainForBrowser(gBrowser.selectedBrowser);
413 let domainIsolator = Cc["@torproject.org/domain-isolator;1"].getService(
417 domainIsolator.newCircuitForDomain(firstPartyDomain);
419 gBrowser.reloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE);
422 /* Called when we switch the use_nontor_proxy pref in either direction.
424 * Enables/disables domain isolation and then does new identity
426 function torbutton_use_nontor_proxy() {
427 let domainIsolator = Cc["@torproject.org/domain-isolator;1"].getService(
431 if (m_tb_prefs.getBoolPref("extensions.torbutton.use_nontor_proxy")) {
432 // Disable domain isolation
433 domainIsolator.disableIsolation();
435 domainIsolator.enableIsolation();
439 async function torbutton_do_tor_check() {
440 let checkSvc = Cc["@torproject.org/torbutton-torCheckService;1"].getService(
444 m_tb_prefs.getBoolPref("extensions.torbutton.use_nontor_proxy") ||
445 !m_tb_prefs.getBoolPref("extensions.torbutton.test_enabled")
448 } // Only do the check once.
450 // If we have a tor control port and transparent torification is off,
451 // perform a check via the control port.
452 const kEnvSkipControlPortTest = "TOR_SKIP_CONTROLPORTTEST";
453 const kEnvUseTransparentProxy = "TOR_TRANSPROXY";
454 var env = Cc["@mozilla.org/process/environment;1"].getService(
458 (m_tb_control_ipc_file || m_tb_control_port) &&
459 !env.exists(kEnvUseTransparentProxy) &&
460 !env.exists(kEnvSkipControlPortTest) &&
461 m_tb_prefs.getBoolPref("extensions.torbutton.local_tor_check")
463 if (await torbutton_local_tor_check()) {
464 checkSvc.statusOfTorCheck = checkSvc.kCheckSuccessful;
466 // The check failed. Update toolbar icon and tooltip.
467 checkSvc.statusOfTorCheck = checkSvc.kCheckFailed;
470 // A local check is not possible, so perform a remote check.
471 torbutton_initiate_remote_tor_check();
475 async function torbutton_local_tor_check() {
476 let didLogError = false;
478 let proxyType = m_tb_prefs.getIntPref("network.proxy.type");
479 if (0 == proxyType) {
483 // Ask tor for its SOCKS listener address and port and compare to the
484 // browser preferences.
485 const kCmdArg = "net/listeners/socks";
486 let resp = await torbutton_send_ctrl_cmd("GETINFO " + kCmdArg);
491 function logUnexpectedResponse() {
496 "Local Tor check: unexpected GETINFO response: " + resp
501 function removeBrackets(aStr) {
502 // Remove enclosing square brackets if present.
503 if (aStr.startsWith("[") && aStr.endsWith("]")) {
504 return aStr.substr(1, aStr.length - 2);
510 // Sample response: net/listeners/socks="127.0.0.1:9149" "127.0.0.1:9150"
511 // First, check for and remove the command argument prefix.
512 if (0 != resp.indexOf(kCmdArg + "=")) {
513 logUnexpectedResponse();
516 resp = resp.substr(kCmdArg.length + 1);
518 // Retrieve configured proxy settings and check each listener against them.
519 // When the SOCKS prefs are set to use IPC (e.g., a Unix domain socket), a
520 // file URL should be present in network.proxy.socks.
521 // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1211567
522 let socksAddr = m_tb_prefs.getCharPref("network.proxy.socks");
523 let socksPort = m_tb_prefs.getIntPref("network.proxy.socks_port");
525 if (socksAddr && socksAddr.startsWith("file:")) {
526 // Convert the file URL to a file path.
528 let ioService = Services.io;
530 .getProtocolHandler("file")
531 .QueryInterface(Ci.nsIFileProtocolHandler);
532 socksIPCPath = fph.getFileFromURLSpec(socksAddr).path;
534 torbutton_log(5, "Local Tor check: IPC file error: " + e);
538 socksAddr = removeBrackets(socksAddr);
541 // Split into quoted strings. This code is adapted from utils.splitAtSpaces()
542 // within tor-control-port.js; someday this code should use the entire
543 // tor-control-port.js framework.
545 resp.replace(/((\S*?"(.*?)")+\S*|\S+)/g, function(a, captured) {
546 addrArray.push(captured);
549 let foundSocksListener = false;
550 for (let i = 0; !foundSocksListener && i < addrArray.length; ++i) {
553 addr = unescapeTorString(addrArray[i]);
559 // Remove double quotes if present.
560 let len = addr.length;
561 if (len > 2 && '"' == addr.charAt(0) && '"' == addr.charAt(len - 1)) {
562 addr = addr.substring(1, len - 1);
565 if (addr.startsWith("unix:")) {
570 // Check against the configured UNIX domain socket proxy.
571 let path = addr.substring(5);
572 torbutton_log(2, "Tor socks listener (Unix domain socket): " + path);
573 foundSocksListener = socksIPCPath === path;
574 } else if (!socksIPCPath) {
575 // Check against the configured TCP proxy. We expect addr:port where addr
576 // may be an IPv6 address; that is, it may contain colon characters.
577 // Also, we remove enclosing square brackets before comparing addresses
578 // because tor requires them but Firefox does not.
579 let idx = addr.lastIndexOf(":");
581 logUnexpectedResponse();
583 let torSocksAddr = removeBrackets(addr.substring(0, idx));
584 let torSocksPort = parseInt(addr.substring(idx + 1), 10);
585 if (torSocksAddr.length < 1 || isNaN(torSocksPort)) {
586 logUnexpectedResponse();
590 "Tor socks listener: " + torSocksAddr + ":" + torSocksPort
593 socksAddr === torSocksAddr && socksPort === torSocksPort;
599 return foundSocksListener;
600 } // torbutton_local_tor_check
602 function torbutton_initiate_remote_tor_check() {
603 let obsSvc = Services.obs;
606 "@torproject.org/torbutton-torCheckService;1"
607 ].getService(Ci.nsISupports).wrappedJSObject;
608 let req = checkSvc.createCheckRequest(true); // async
609 req.onreadystatechange = function(aEvent) {
610 if (req.readyState === 4) {
611 let ret = checkSvc.parseCheckResponse(req);
613 // If we received an error response from check.torproject.org,
614 // set the status of the tor check to failure (we don't want
615 // to indicate failure if we didn't receive a response).
624 checkSvc.statusOfTorCheck = checkSvc.kCheckFailed;
625 obsSvc.notifyObservers(null, k_tb_tor_check_failed_topic);
626 } else if (ret == 4) {
627 checkSvc.statusOfTorCheck = checkSvc.kCheckSuccessful;
628 } // Otherwise, redo the check later
630 torbutton_log(3, "Tor remote check done. Result: " + ret);
634 torbutton_log(3, "Sending async Tor remote check");
637 if (e.result == 0x80004005) {
639 torbutton_log(5, "Tor check failed! Is tor running?");
641 torbutton_log(5, "Tor check failed! Tor internal error: " + e);
644 obsSvc.notifyObservers(null, k_tb_tor_check_failed_topic);
646 } // torbutton_initiate_remote_tor_check()
648 async function torbutton_tor_check_ok() {
649 await torbutton_do_tor_check();
650 let checkSvc = Cc["@torproject.org/torbutton-torCheckService;1"].getService(
653 return checkSvc.kCheckFailed != checkSvc.statusOfTorCheck;
656 function torbutton_update_disk_prefs() {
657 var mode = m_tb_prefs.getBoolPref("browser.privatebrowsing.autostart");
659 m_tb_prefs.setBoolPref("browser.cache.disk.enable", !mode);
660 m_tb_prefs.setBoolPref("places.history.enabled", !mode);
662 m_tb_prefs.setBoolPref("security.nocertdb", mode);
664 // No way to clear this beast during New Identity. Leave it off.
665 //m_tb_prefs.setBoolPref("dom.indexedDB.enabled", !mode);
667 m_tb_prefs.setBoolPref("permissions.memory_only", mode);
669 // Third party abuse. Leave it off for now.
670 //m_tb_prefs.setBoolPref("browser.cache.offline.enable", !mode);
672 // Force prefs to be synced to disk
673 Services.prefs.savePrefFile(null);
676 function torbutton_update_fingerprinting_prefs() {
677 var mode = m_tb_prefs.getBoolPref("privacy.resistFingerprinting");
678 var letterboxing = m_tb_prefs.getBoolPref(
679 "privacy.resistFingerprinting.letterboxing",
682 m_tb_prefs.setBoolPref(
683 "extensions.torbutton.resize_new_windows",
684 mode && !letterboxing
687 // Force prefs to be synced to disk
688 Services.prefs.savePrefFile(null);
691 // Bug 1506 P1: This function just cleans up prefs that got set badly in previous releases
692 function torbutton_fixup_old_prefs() {
693 if (m_tb_prefs.getIntPref("extensions.torbutton.pref_fixup_version") < 1) {
694 // TBB 5.0a3 had bad Firefox code that silently flipped this pref on us
695 if (m_tb_prefs.prefHasUserValue("browser.newtabpage.enhanced")) {
696 m_tb_prefs.clearUserPref("browser.newtabpage.enhanced");
697 // TBB 5.0a3 users had all the necessary data cached in
698 // directoryLinks.json. This meant that resetting the pref above
699 // alone was not sufficient as the tiles features uses the cache
700 // even if the pref indicates that feature should be disabled.
701 // We flip the preference below as this forces a refetching which
702 // effectively results in an empty JSON file due to our spoofed
704 let matchOS = m_tb_prefs.getBoolPref("intl.locale.matchOS");
705 m_tb_prefs.setBoolPref("intl.locale.matchOS", !matchOS);
706 m_tb_prefs.setBoolPref("intl.locale.matchOS", matchOS);
709 // For some reason, the Share This Page button also survived the
710 // TBB 5.0a4 update's attempt to remove it.
711 if (m_tb_prefs.prefHasUserValue("browser.uiCustomization.state")) {
712 m_tb_prefs.clearUserPref("browser.uiCustomization.state");
715 m_tb_prefs.setIntPref("extensions.torbutton.pref_fixup_version", 1);
719 // ---------------------- Event handlers -----------------
721 // Bug 1506 P1-P3: Most of these observers aren't very important.
722 // See their comments for details
723 function torbutton_do_main_window_startup() {
724 torbutton_log(3, "Torbutton main window startup");
725 m_tb_is_main_window = true;
726 torbutton_unique_pref_observer.register();
729 // Bug 1506 P4: Most of this function is now useless, save
730 // for the very important SOCKS environment vars at the end.
731 // Those could probably be rolled into a function with the
732 // control port vars, though. See 1506 comments inside.
733 function torbutton_do_startup() {
734 if (m_tb_prefs.getBoolPref("extensions.torbutton.startup")) {
735 // Bug 1506: Should probably be moved to an XPCOM component
736 torbutton_do_main_window_startup();
739 torbutton_update_fingerprinting_prefs();
741 // Bug 30565: sync browser.privatebrowsing.autostart with security.nocertdb
742 torbutton_update_disk_prefs();
744 // For general pref fixups to handle pref damage in older versions
745 torbutton_fixup_old_prefs();
747 m_tb_prefs.setBoolPref("extensions.torbutton.startup", false);
751 // Bug 1506 P3: Used to decide if we should resize the window.
753 // Returns true if the window wind is neither maximized, full screen,
754 // ratpoisioned/evilwmed, nor minimized.
755 function torbutton_is_windowed(wind) {
763 wind.screen.availWidth +
765 wind.screen.availHeight +
769 wind.windowState == Ci.nsIDOMChromeWindow.STATE_MINIMIZED ||
770 wind.windowState == Ci.nsIDOMChromeWindow.STATE_MAXIMIZED
772 torbutton_log(2, "Window is minimized/maximized");
775 if ("fullScreen" in wind && wind.fullScreen) {
776 torbutton_log(2, "Window is fullScreen");
780 wind.outerHeight == wind.screen.availHeight &&
781 wind.outerWidth == wind.screen.availWidth
783 torbutton_log(3, "Window is ratpoisoned/evilwm'ed");
787 torbutton_log(2, "Window is normal");
791 function showSecurityPreferencesPanel(chromeWindow) {
792 const tabBrowser = chromeWindow.BrowserApp;
793 let settingsTab = null;
795 const SECURITY_PREFERENCES_URI =
796 "chrome://torbutton/content/preferences.xhtml";
798 tabBrowser.tabs.some(function(tab) {
799 // If the security prefs tab is opened, send the user to it
800 if (tab.browser.currentURI.spec === SECURITY_PREFERENCES_URI) {
807 if (settingsTab === null) {
808 // Open up the settings panel in a new tab.
809 tabBrowser.addTab(SECURITY_PREFERENCES_URI, {
811 parentId: tabBrowser.selectedTab.id,
812 triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
815 // Activate an existing settings panel tab.
816 tabBrowser.selectTab(settingsTab);
820 function setupPreferencesForMobile() {
821 if (!torbutton_is_mobile()) {
825 torbutton_log(4, "Setting up settings preferences for Android.");
827 const chromeWindow = Services.wm.getMostRecentWindow("navigator:browser");
829 // Add the extension's chrome menu item to the main browser menu.
830 chromeWindow.NativeWindow.menu.add({
831 name: torbutton_get_property_string(
832 "torbutton.security_settings.menu.title"
834 callback: showSecurityPreferencesPanel.bind(this, chromeWindow),
838 // Bug 1506 P3: This is needed pretty much only for the window resizing.
839 // See comments for individual functions for details
840 function torbutton_new_window(event) {
841 torbutton_log(3, "New window");
842 var browser = window.gBrowser;
845 torbutton_log(5, "No browser for new window.");
849 if (!m_tb_wasinited) {
853 torbutton_do_startup();
855 let progress = Cc["@mozilla.org/docloaderservice;1"].getService(
859 if (torbutton_is_windowed(window)) {
860 progress.addProgressListener(
861 torbutton_resizelistener,
862 Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT
867 // Bug 1506 P2: This is only needed because we have observers
868 // in XUL that should be in an XPCOM component
869 function torbutton_close_window(event) {
870 torbutton_tor_check_observer.unregister();
872 window.removeEventListener("sizemodechange", m_tb_resize_handler);
874 // TODO: This is a real ghetto hack.. When the original window
875 // closes, we need to find another window to handle observing
876 // unique events... The right way to do this is to move the
877 // majority of torbutton functionality into a XPCOM component..
878 // But that is a major overhaul..
879 if (m_tb_is_main_window) {
880 torbutton_log(3, "Original window closed. Searching for another");
881 var wm = Services.wm;
882 var enumerator = wm.getEnumerator("navigator:browser");
883 while (enumerator.hasMoreElements()) {
884 var win = enumerator.getNext();
885 // For some reason, when New Identity is called from a pref
886 // observer (ex: torbutton_use_nontor_proxy) on an ASAN build,
887 // we sometimes don't have this symbol set in the new window yet.
888 // However, the new window will run this init later in that case,
889 // as it does in the OSX case.
890 if (win != window && "torbutton_do_main_window_startup" in win) {
891 torbutton_log(3, "Found another window");
892 win.torbutton_do_main_window_startup();
893 m_tb_is_main_window = false;
898 torbutton_unique_pref_observer.unregister();
900 if (m_tb_is_main_window) {
901 // main window not reset above
902 // This happens on Mac OS because they allow firefox
903 // to still persist without a navigator window
904 torbutton_log(3, "Last window closed. None remain.");
905 m_tb_prefs.setBoolPref("extensions.torbutton.startup", true);
906 m_tb_is_main_window = false;
911 window.addEventListener("load", torbutton_new_window);
912 window.addEventListener("unload", torbutton_close_window);
914 var m_tb_resize_handler = null;
915 var m_tb_resize_date = null;
917 // Bug 1506 P1/P3: Setting a fixed window size is important, but
918 // probably not for android.
919 var torbutton_resizelistener = {
920 QueryInterface: ChromeUtils.generateQI([
921 "nsIWebProgressListener",
922 "nsISupportsWeakReference",
925 onLocationChange(aProgress, aRequest, aURI) {},
926 onStateChange(aProgress, aRequest, aFlag, aStatus) {
927 if (aFlag & Ci.nsIWebProgressListener.STATE_STOP) {
928 m_tb_resize_handler = async function() {
929 // Wait for end of execution queue to ensure we have correct windowState.
930 await new Promise(resolve => setTimeout(resolve, 0));
932 window.windowState === window.STATE_MAXIMIZED ||
933 window.windowState === window.STATE_FULLSCREEN
936 m_tb_prefs.getBoolPref(
937 "extensions.torbutton.resize_new_windows"
939 m_tb_prefs.getIntPref(
940 "extensions.torbutton.maximize_warnings_remaining"
943 // Do not add another notification if one is already showing.
944 const kNotificationName = "torbutton-maximize-notification";
945 let box = gBrowser.getNotificationBox();
946 if (box.getNotificationWithValue(kNotificationName)) {
950 // Rate-limit showing our notification if needed.
951 if (m_tb_resize_date === null) {
952 m_tb_resize_date = Date.now();
954 // We wait at least another second before we show a new
955 // notification. Should be enough to rule out OSes that call our
956 // handler rapidly due to internal workings.
957 if (Date.now() - m_tb_resize_date < 1000) {
960 // Resizing but we need to reset |m_tb_resize_date| now.
961 m_tb_resize_date = Date.now();
964 // No need to get "OK" translated again.
965 let sbSvc = Services.strings;
966 let bundle = sbSvc.createBundle(
967 "chrome://global/locale/commonDialogs.properties"
969 let button_label = bundle.GetStringFromName("OK");
977 m_tb_prefs.setIntPref(
978 "extensions.torbutton.maximize_warnings_remaining",
979 m_tb_prefs.getIntPref(
980 "extensions.torbutton.maximize_warnings_remaining"
987 let priority = box.PRIORITY_WARNING_LOW;
988 let message = torbutton_get_property_string(
989 "torbutton.maximize_warning"
992 box.appendNotification(
1001 }; // m_tb_resize_handler
1003 // We need to handle OSes that auto-maximize windows depending on user
1004 // settings and/or screen resolution in the start-up phase and users that
1005 // try to shoot themselves in the foot by maximizing the window manually.
1006 // We add a listener which is triggerred as soon as the window gets
1007 // maximized (windowState = 1). We are resizing during start-up but not
1008 // later as the user should see only a warning there as a stopgap before
1010 // Alas, the Firefox window code is handling the event not itself:
1011 // "// Note the current implementation of SetSizeMode just stores
1012 // // the new state; it doesn't actually resize. So here we store
1013 // // the state and pass the event on to the OS."
1014 // (See: https://mxr.mozilla.org/mozilla-esr31/source/xpfe/appshell/src/
1015 // nsWebShellWindow.cpp#348)
1016 // This means we have to cope with race conditions and resizing in the
1017 // sizemodechange listener is likely to fail. Thus, we add a specific
1018 // resize listener that is doing the work for us. It seems (at least on
1019 // Ubuntu) to be the case that maximizing (and then again normalizing) of
1020 // the window triggers more than one resize event the first being not the
1021 // one we need. Thus we can't remove the listener after the first resize
1022 // event got fired. Thus, we have the rather klunky setTimeout() call.
1023 window.addEventListener("sizemodechange", m_tb_resize_handler);
1025 let progress = Cc["@mozilla.org/docloaderservice;1"].getService(
1028 progress.removeProgressListener(this);
1040 onStatusChange(aProgress, aRequest, stat, message) {},
1041 onSecurityChange() {},