3 var torbutton_new_circuit;
4 var torbutton_new_identity;
7 // Bug 1506 P1-P5: This is the main Torbutton overlay file. Much needs to be
8 // preserved here, but in an ideal world, most of this code should perhaps be
9 // moved into an XPCOM service, and much can also be tossed. See also
10 // individual 1506 comments for details.
12 // TODO: check for leaks: http://www.mozilla.org/scriptable/avoiding-leaks.html
13 // TODO: Double-check there are no strange exploits to defeat:
14 // http://kb.mozillazine.org/Links_to_local_pages_don%27t_work
16 /* global gBrowser, CustomizableUI,
17 createTorCircuitDisplay, gFindBarInitialized,
18 gFindBar, OpenBrowserWindow, PrivateBrowsingUtils,
19 Services, AppConstants
23 show_torbrowser_manual,
29 torbutton_get_property_string,
30 } = ChromeUtils.import("resource://torbutton/modules/utils.js", {});
31 let { configureControlPortModule } = Cu.import("resource://torbutton/modules/tor-control-port.js", {});
33 const k_tb_tor_check_failed_topic = "Torbutton:TorCheckFailed";
35 var m_tb_prefs = Services.prefs;
38 var m_tb_wasinited = false;
39 var m_tb_is_main_window = false;
41 var m_tb_confirming_plugins = false;
43 var m_tb_control_ipc_file = null; // Set if using IPC (UNIX domain socket).
44 var m_tb_control_port = null; // Set if using TCP.
45 var m_tb_control_host = null; // Set if using TCP.
46 var m_tb_control_pass = null;
47 var m_tb_control_desc = null; // For logging.
49 var m_tb_domWindowUtils = window.windowUtils;
51 async function clearData(flags) {
52 return new Promise((resolve, reject) => {
53 Services.clearData.deleteData(flags, {
55 if (code === Cr.NS_OK) {
58 reject(new Error(`Error deleting data with flags ${flags}: ${code}`));
65 // Bug 1506 P2: This object keeps Firefox prefs in sync with Torbutton prefs.
66 // It probably could stand some simplification (See #3100). It also belongs
67 // in a component, not the XUL overlay.
68 var torbutton_unique_pref_observer =
72 this.forced_ua = false;
73 m_tb_prefs.addObserver("extensions.torbutton", this, false);
74 m_tb_prefs.addObserver("browser.privatebrowsing.autostart", this, false);
75 m_tb_prefs.addObserver("javascript", this, false);
76 m_tb_prefs.addObserver("plugin.disable", this, false);
77 m_tb_prefs.addObserver("privacy.resistFingerprinting", this, false);
78 m_tb_prefs.addObserver("privacy.resistFingerprinting.letterboxing", this, false);
80 // We observe xpcom-category-entry-added for plugins w/ Gecko-Content-Viewers
81 var observerService = Services.obs;
82 observerService.addObserver(this, "xpcom-category-entry-added");
85 unregister: function()
87 m_tb_prefs.removeObserver("extensions.torbutton", this);
88 m_tb_prefs.removeObserver("browser.privatebrowsing.autostart", this);
89 m_tb_prefs.removeObserver("javascript", this);
90 m_tb_prefs.removeObserver("plugin.disable", this);
91 m_tb_prefs.removeObserver("privacy.resistFingerprinting", this);
92 m_tb_prefs.removeObserver("privacy.resistFingerprinting.letterboxing", this);
94 var observerService = Services.obs;
95 observerService.removeObserver(this, "xpcom-category-entry-added");
98 // topic: what event occurred
99 // subject: what nsIPrefBranch we're observing
100 // data: which pref has been changed (relative to subject)
101 observe: function(subject, topic, data)
103 if (topic == "xpcom-category-entry-added") {
104 // Hrmm. should we inspect subject too? it's just mime type..
105 subject.QueryInterface(Ci.nsISupportsCString);
106 if (data == "Gecko-Content-Viewers" &&
107 !m_tb_prefs.getBoolPref("extensions.torbutton.startup") &&
108 m_tb_prefs.getBoolPref("extensions.torbutton.confirm_plugins")) {
109 torbutton_log(3, "Got plugin enabled notification: "+subject);
111 /* We need to protect this call with a flag becuase we can
112 * get multiple observer events for each mime type a plugin
113 * registers. Thankfully, these notifications arrive only on
114 * the main thread, *however*, our confirmation dialog suspends
115 * execution and allows more events to arrive until it is answered
117 if (!m_tb_confirming_plugins) {
118 m_tb_confirming_plugins = true;
119 torbutton_confirm_plugins();
120 m_tb_confirming_plugins = false;
122 torbutton_log(3, "Skipping notification for mime type: "+subject);
128 if (topic != "nsPref:changed") return;
131 case "plugin.disable":
132 torbutton_toggle_plugins(
133 m_tb_prefs.getBoolPref("plugin.disable"));
135 case "browser.privatebrowsing.autostart":
136 torbutton_update_disk_prefs();
138 case "extensions.torbutton.use_nontor_proxy":
139 torbutton_use_nontor_proxy();
141 case "privacy.resistFingerprinting":
142 case "privacy.resistFingerprinting.letterboxing":
143 torbutton_update_fingerprinting_prefs();
149 var torbutton_tor_check_observer = {
151 this._obsSvc = Services.obs;
152 this._obsSvc.addObserver(this, k_tb_tor_check_failed_topic);
155 unregister: function()
158 this._obsSvc.removeObserver(this, k_tb_tor_check_failed_topic);
161 observe: function(subject, topic, data)
163 if (topic == k_tb_tor_check_failed_topic) {
164 // Update all open about:tor pages.
165 torbutton_abouttor_message_handler.updateAllOpenPages();
167 // If the user does not have an about:tor tab open in the front most
169 var wm = Services.wm;
170 var win = wm.getMostRecentWindow("navigator:browser");
172 let foundTab = false;
173 let tabBrowser = top.gBrowser;
174 for (let i = 0; !foundTab && (i < tabBrowser.browsers.length); ++i) {
175 let b = tabBrowser.getBrowserAtIndex(i);
176 foundTab = (b.currentURI.spec.toLowerCase() == "about:tor");
180 gBrowser.selectedTab = gBrowser.addTrustedTab("about:tor");
187 function torbutton_is_mobile() {
188 return Services.appinfo.OS === "Android";
191 // Bug 1506 P2-P4: This code sets some version variables that are irrelevant.
192 // It does read out some important environment variables, though. It is
193 // called once per browser window.. This might belong in a component.
194 torbutton_init = function() {
195 torbutton_log(3, 'called init()');
197 if (m_tb_wasinited) {
200 m_tb_wasinited = true;
204 tlps = Cc["@torproject.org/torlauncher-protocol-service;1"]
205 .getService(Ci.nsISupports).wrappedJSObject;
208 // Bug 1506 P4: These vars are very important for New Identity
209 var environ = Cc["@mozilla.org/process/environment;1"]
210 .getService(Ci.nsIEnvironment);
212 if (environ.exists("TOR_CONTROL_PASSWD")) {
213 m_tb_control_pass = environ.get("TOR_CONTROL_PASSWD");
214 } else if (environ.exists("TOR_CONTROL_COOKIE_AUTH_FILE")) {
215 var cookie_path = environ.get("TOR_CONTROL_COOKIE_AUTH_FILE");
217 if ("" != cookie_path) {
218 m_tb_control_pass = torbutton_read_authentication_cookie(cookie_path);
221 torbutton_log(4, 'unable to read authentication cookie');
225 // Try to get password from Tor Launcher.
226 m_tb_control_pass = tlps.TorGetPassword(false);
230 // Try to get the control port IPC file (an nsIFile) from Tor Launcher,
231 // since Tor Launcher knows how to handle its own preferences and how to
232 // resolve relative paths.
234 m_tb_control_ipc_file = tlps.TorGetControlIPCFile();
237 if (m_tb_control_ipc_file) {
238 m_tb_control_desc = m_tb_control_ipc_file.path;
240 if (environ.exists("TOR_CONTROL_PORT")) {
241 m_tb_control_port = environ.get("TOR_CONTROL_PORT");
244 const kTLControlPortPref = "extensions.torlauncher.control_port";
245 m_tb_control_port = m_tb_prefs.getIntPref(kTLControlPortPref);
247 // Since we want to disable some features when Tor Launcher is
248 // not installed (e.g., New Identity), we do not set a default
253 if (m_tb_control_port) {
254 m_tb_control_desc = "" + m_tb_control_port;
257 if (environ.exists("TOR_CONTROL_HOST")) {
258 m_tb_control_host = environ.get("TOR_CONTROL_HOST");
261 const kTLControlHostPref = "extensions.torlauncher.control_host";
262 m_tb_control_host = m_tb_prefs.getCharPref(kTLControlHostPref);
264 m_tb_control_host = "127.0.0.1";
269 configureControlPortModule(m_tb_control_ipc_file, m_tb_control_host,
270 m_tb_control_port, m_tb_control_pass);
272 // Add about:tor IPC message listener.
273 window.messageManager.addMessageListener("AboutTor:Loaded",
274 torbutton_abouttor_message_handler);
276 setupPreferencesForMobile();
278 torbutton_log(1, "registering Tor check observer");
279 torbutton_tor_check_observer.register();
282 createTorCircuitDisplay("extensions.torbutton.display_circuit");
284 torbutton_log(4, "Error creating the tor circuit display " + e);
288 torbutton_init_user_manual_links();
290 torbutton_log(4, "Error loading the user manual " + e);
293 // Arrange for our about:tor content script to be loaded in each frame.
294 window.messageManager.loadFrameScript(
295 "chrome://torbutton/content/aboutTor/aboutTor-content.js", true);
297 torbutton_log(3, 'init completed');
300 var torbutton_abouttor_message_handler = {
301 // Receive IPC messages from the about:tor content script.
302 receiveMessage: function(aMessage) {
303 switch(aMessage.name) {
304 case "AboutTor:Loaded":
305 aMessage.target.messageManager.sendAsyncMessage("AboutTor:ChromeData",
306 this.getChromeData(true));
311 // Send privileged data to all of the about:tor content scripts.
312 updateAllOpenPages: function() {
313 window.messageManager.broadcastAsyncMessage("AboutTor:ChromeData",
314 this.getChromeData(false));
317 // The chrome data contains all of the data needed by the about:tor
318 // content process that is only available here (in the chrome process).
319 // It is sent to the content process when an about:tor window is opened
320 // and in response to events such as the browser noticing that Tor is
322 getChromeData: function(aIsRespondingToPageLoad) {
324 mobile: torbutton_is_mobile(),
325 updateChannel: AppConstants.MOZ_UPDATE_CHANNEL,
326 torOn: torbutton_tor_check_ok()
329 if (aIsRespondingToPageLoad) {
330 const kShouldNotifyPref = "torbrowser.post_update.shouldNotify";
331 if (m_tb_prefs.getBoolPref(kShouldNotifyPref, false)) {
332 m_tb_prefs.clearUserPref(kShouldNotifyPref);
333 dataObj.hasBeenUpdated = true;
334 dataObj.updateMoreInfoURL = this.getUpdateMoreInfoURL();
341 getUpdateMoreInfoURL: function() {
343 return Services.prefs.getCharPref("torbrowser.post_update.url");
346 // Use the default URL as a fallback.
347 return Services.urlFormatter.formatURLPref("startup.homepage_override_url");
351 function torbutton_confirm_plugins() {
352 var any_plugins_enabled = false;
353 var PH=Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
354 var P=PH.getPluginTags({});
355 for(var i=0; i<P.length; i++) {
357 any_plugins_enabled = true;
360 if (!any_plugins_enabled) {
361 torbutton_log(3, "False positive on plugin notification. Ignoring");
365 torbutton_log(3, "Confirming plugin usage.");
367 var prompts = Services.prompt;
369 // Display two buttons, both with string titles.
370 var flags = prompts.STD_YES_NO_BUTTONS + prompts.BUTTON_DELAY_ENABLE;
372 var message = torbutton_get_property_string("torbutton.popup.confirm_plugins");
373 var askAgainText = torbutton_get_property_string("torbutton.popup.never_ask_again");
374 var askAgain = {value: false};
376 var wm = Services.wm;
377 var win = wm.getMostRecentWindow("navigator:browser");
378 var no_plugins = (prompts.confirmEx(win, "", message, flags, null, null, null,
379 askAgainText, askAgain) == 1);
381 m_tb_prefs.setBoolPref("extensions.torbutton.confirm_plugins", !askAgain.value);
383 // The pref observer for "plugin.disable" will set the appropriate plugin state.
384 // So, we only touch the pref if it has changed.
386 m_tb_prefs.getBoolPref("plugin.disable"))
387 m_tb_prefs.setBoolPref("plugin.disable", no_plugins);
389 torbutton_toggle_plugins(no_plugins);
391 // Now, if any tabs were open to about:addons, reload them. Our popup
392 // messed up that page.
393 var browserEnumerator = wm.getEnumerator("navigator:browser");
395 // Check each browser instance for our URL
396 while (browserEnumerator.hasMoreElements()) {
397 var browserWin = browserEnumerator.getNext();
398 var tabbrowser = browserWin.gBrowser;
400 // Check each tab of this browser instance
401 var numTabs = tabbrowser.browsers.length;
402 for (var index = 0; index < numTabs; index++) {
403 var currentBrowser = tabbrowser.getBrowserAtIndex(index);
404 if ("about:addons" == currentBrowser.currentURI.spec) {
405 torbutton_log(3, "Got browser: "+currentBrowser.currentURI.spec);
406 currentBrowser.reload();
412 // Bug 1506 P4: Control port interaction. Needed for New Identity.
413 function torbutton_socket_readline(input) {
416 while((bytes = input.readBytes(1)) != "\n") {
423 // Bug 1506 P4: Control port interaction. Needed for New Identity.
424 function torbutton_read_authentication_cookie(path) {
425 var file = Cc["@mozilla.org/file/local;1"]
426 .createInstance(Ci.nsIFile);
427 file.initWithPath(path);
428 var fileStream = Cc["@mozilla.org/network/file-input-stream;1"]
429 .createInstance(Ci.nsIFileInputStream);
430 fileStream.init(file, 1, 0, false);
431 var binaryStream = Cc["@mozilla.org/binaryinputstream;1"]
432 .createInstance(Ci.nsIBinaryInputStream);
433 binaryStream.setInputStream(fileStream);
434 var array = binaryStream.readByteArray(fileStream.available());
435 binaryStream.close();
437 return torbutton_array_to_hexdigits(array);
440 // Bug 1506 P4: Control port interaction. Needed for New Identity.
441 function torbutton_array_to_hexdigits(array) {
442 return array.map(function(c) {
443 return String("0" + c.toString(16)).slice(-2)
447 // Bug 1506 P4: Control port interaction. Needed for New Identity.
449 // Executes a command on the control port.
450 // Return a string response upon success and null upon error.
451 function torbutton_send_ctrl_cmd(command) {
453 // We spin the event queue until it is empty and we can be sure that sending
454 // NEWNYM is not leading to a deadlock (see bug 9531 comment 23 for an
455 // invstigation on why and when this may happen). This is surrounded by
456 // suppressing/unsuppressing user initiated events in a window's document to
457 // be sure that these events are not interfering with processing events being
458 // in the event queue.
459 var thread = Services.tm.currentThread;
460 m_tb_domWindowUtils.suppressEventHandling(true);
461 while (thread.processNextEvent(false)) {}
462 m_tb_domWindowUtils.suppressEventHandling(false);
465 let sts = Cc["@mozilla.org/network/socket-transport-service;1"]
466 .getService(Ci.nsISocketTransportService);
468 if (m_tb_control_ipc_file) {
469 socket = sts.createUnixDomainTransport(m_tb_control_ipc_file);
471 socket = sts.createTransport([], m_tb_control_host,
472 m_tb_control_port, null);
475 // If we don't get a response from the control port in 2 seconds, someting is wrong..
476 socket.setTimeout(Ci.nsISocketTransport.TIMEOUT_READ_WRITE, 2);
478 var input = socket.openInputStream(3, 1, 1); // 3 == OPEN_BLOCKING|OPEN_UNBUFFERED
479 var output = socket.openOutputStream(3, 1, 1); // 3 == OPEN_BLOCKING|OPEN_UNBUFFERED
481 var inputStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIBinaryInputStream);
482 var outputStream = Cc["@mozilla.org/binaryoutputstream;1"].createInstance(Ci.nsIBinaryOutputStream);
484 inputStream.setInputStream(input);
485 outputStream.setOutputStream(output);
487 var auth_cmd = "AUTHENTICATE "+m_tb_control_pass+"\r\n";
488 outputStream.writeBytes(auth_cmd, auth_cmd.length);
490 var bytes = torbutton_socket_readline(inputStream);
492 if (bytes.indexOf("250") != 0) {
493 torbutton_safelog(4, "Unexpected auth response on control port "+m_tb_control_desc+":", bytes);
497 outputStream.writeBytes(command, command.length);
498 bytes = torbutton_socket_readline(inputStream);
499 if(bytes.indexOf("250") != 0) {
500 torbutton_safelog(4, "Unexpected command response on control port "+m_tb_control_desc+":", bytes);
504 // Closing these streams prevents a shutdown hang on Mac OS. See bug 10201.
506 outputStream.close();
507 socket.close(Cr.NS_OK);
508 return bytes.substr(4);
510 torbutton_log(4, "Exception on control port "+e);
515 // Bug 1506 P4: Needed for New IP Address
516 torbutton_new_circuit = function() {
517 let firstPartyDomain = getDomainForBrowser(gBrowser.selectedBrowser);
519 let domainIsolator = Cc["@torproject.org/domain-isolator;1"]
520 .getService(Ci.nsISupports).wrappedJSObject;
522 domainIsolator.newCircuitForDomain(firstPartyDomain);
524 gBrowser.reloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE);
527 let newIdentityInProgress = false;
529 // Bug 1506 P4: Needed for New Identity.
530 torbutton_new_identity = async function() {
532 // Ignore if there's a New Identity in progress to avoid race
533 // conditions leading to failures (see bug 11783 for an example).
534 if (newIdentityInProgress) {
538 newIdentityInProgress = true;
540 let shouldConfirm = m_tb_prefs.getBoolPref("extensions.torbutton.confirm_newnym");
543 let prompts = Services.prompt;
545 // Display two buttons, both with string titles.
546 let flags = prompts.STD_YES_NO_BUTTONS;
548 let message = torbutton_get_property_string("torbutton.popup.confirm_newnym");
549 let askAgainText = torbutton_get_property_string("torbutton.popup.never_ask_again");
550 let askAgain = {value: false};
552 let confirmed = (prompts.confirmEx(null, "", message, flags, null, null, null,
553 askAgainText, askAgain) == 0);
555 m_tb_prefs.setBoolPref("extensions.torbutton.confirm_newnym", !askAgain.value);
558 await torbutton_do_new_identity();
561 await torbutton_do_new_identity();
564 // If something went wrong make sure we have the New Identity button
566 // TODO: Remove the Torbutton menu entry again once we have done our
567 // security control redesign.
568 torbutton_log(5, "Unexpected error on new identity: " + e);
569 window.alert("Torbutton: Unexpected error on new identity: " + e);
571 newIdentityInProgress = false;
575 /* The "New Identity" implementation does the following:
576 * 1. Disables Javascript and plugins on all tabs
579 * b. Cache + image cache
580 * c. Site-specific zoom
581 * d. Cookies+DOM Storage+safe browsing key
582 * e. google wifi geolocation token
585 * h. last open location url
586 * i. clear content prefs
588 * k. site security settings (e.g. HSTS)
589 * l. IndexedDB and other DOM storage
592 * o. predictor network data
593 * 3. Sends tor the NEWNYM signal to get a new circuit
594 * 4. Opens a new window with the default homepage
595 * 5. Closes this window
597 * XXX: intermediate SSL certificates are not cleared.
599 // Bug 1506 P4: Needed for New Identity.
600 async function torbutton_do_new_identity() {
601 var obsSvc = Services.obs;
602 torbutton_log(3, "New Identity: Disabling JS");
603 torbutton_disable_all_js();
605 m_tb_prefs.setBoolPref("browser.zoom.siteSpecific",
606 !m_tb_prefs.getBoolPref("browser.zoom.siteSpecific"));
607 m_tb_prefs.setBoolPref("browser.zoom.siteSpecific",
608 !m_tb_prefs.getBoolPref("browser.zoom.siteSpecific"));
611 if(m_tb_prefs.prefHasUserValue("geo.wifi.access_token")) {
612 m_tb_prefs.clearUserPref("geo.wifi.access_token");
615 torbutton_log(3, "Exception on wifi token clear: "+e);
619 if(m_tb_prefs.prefHasUserValue("general.open_location.last_url")) {
620 m_tb_prefs.clearUserPref("general.open_location.last_url");
623 torbutton_log(3, "Exception on clearing last opened location: "+e);
626 torbutton_log(3, "New Identity: Closing tabs and clearing searchbox");
628 torbutton_close_tabs_on_new_identity();
630 // Bug #10800: Trying to clear search/find can cause exceptions
631 // in unknown cases. Just log for now.
633 var searchBar = window.document.getElementById("searchbar");
635 searchBar.textbox.reset();
637 torbutton_log(5, "New Identity: Exception on clearing search box: "+e);
641 if (gFindBarInitialized) {
642 var findbox = gFindBar.getElement("findbar-textbox");
647 torbutton_log(5, "New Identity: Exception on clearing find bar: "+e);
650 torbutton_log(3, "New Identity: Emitting Private Browsing Session clear event");
651 obsSvc.notifyObservers(null, "browser:purge-session-history", "");
653 torbutton_log(3, "New Identity: Clearing HTTP Auth");
655 if (m_tb_prefs.getBoolPref("extensions.torbutton.clear_http_auth")) {
656 var auth = Cc["@mozilla.org/network/http-auth-manager;1"].
657 getService(Ci.nsIHttpAuthManager);
661 torbutton_log(3, "New Identity: Clearing Crypto Tokens");
663 // Clear all crypto auth tokens. This includes calls to PK11_LogoutAll(),
664 // nsNSSComponent::LogoutAuthenticatedPK11() and clearing the SSL session
666 let sdr = Cc["@mozilla.org/security/sdr;1"].
667 getService(Ci.nsISecretDecoderRing);
668 sdr.logoutAndTeardown();
670 // This clears the OCSP cache.
672 // nsNSSComponent::Observe() watches security.OCSP.enabled, which calls
673 // setValidationOptions(), which in turn calls setNonPkixOcspEnabled() which,
674 // if security.OCSP.enabled is set to 0, calls CERT_DisableOCSPChecking(),
675 // which calls CERT_ClearOCSPCache().
676 // See: https://mxr.mozilla.org/comm-esr24/source/mozilla/security/manager/ssl/src/nsNSSComponent.cpp
677 var ocsp = m_tb_prefs.getIntPref("security.OCSP.enabled");
678 m_tb_prefs.setIntPref("security.OCSP.enabled", 0);
679 m_tb_prefs.setIntPref("security.OCSP.enabled", ocsp);
681 // This clears the site permissions on Tor Browser
682 // XXX: Tie to some kind of disk-ok pref?
684 Services.perms.removeAll();
686 // Actually, this catch does not appear to be needed. Leaving it in for
688 torbutton_log(3, "Can't clear permissions: Not Tor Browser: "+e);
691 // Clear site security settings
692 let sss = Cc["@mozilla.org/ssservice;1"].
693 getService(Ci.nsISiteSecurityService);
696 // This clears the undo tab history.
697 var tabs = m_tb_prefs.getIntPref("browser.sessionstore.max_tabs_undo");
698 m_tb_prefs.setIntPref("browser.sessionstore.max_tabs_undo", 0);
699 m_tb_prefs.setIntPref("browser.sessionstore.max_tabs_undo", tabs);
701 torbutton_log(3, "New Identity: Clearing Image Cache");
702 torbutton_clear_image_caches();
704 torbutton_log(3, "New Identity: Clearing Offline Cache");
707 const LoadContextInfo = Services.loadContextInfo;
709 for (let contextInfo of [LoadContextInfo.default, LoadContextInfo.private]) {
710 let appCacheStorage = Services.cache2.appCacheStorage(contextInfo, null);
711 // The following call (asyncEvictStorage) is actually synchronous, either
712 // if we have pref "browser.cache.use_new_backend" -> 1 or
713 // "browser.cache.use_new_backend_temp" -> true,
714 // then we are using the new cache (cache2) which operates synchronously.
715 // If we are using the old cache, then the tor-browser.git patch for
716 // #5715 also makes this synchronous. So we pass a null callback.
718 appCacheStorage.asyncEvictStorage(null);
720 // We ignore "not available" errors because they occur if a cache
721 // has not been used, e.g., if no browsing has been done.
722 if (err.name !== 'NS_ERROR_NOT_AVAILABLE') {
728 torbutton_log(5, "Exception on cache clearing: "+e);
729 window.alert("Torbutton: Unexpected error during offline cache clearing: "+e);
732 torbutton_log(3, "New Identity: Clearing Disk and Memory Caches");
735 Services.cache2.clear();
737 torbutton_log(5, "Exception on cache clearing: "+e);
738 window.alert("Torbutton: Unexpected error during cache clearing: "+e);
741 torbutton_log(3, "New Identity: Clearing storage");
742 torbutton_log(3, "New Identity: Clearing plugin data");
743 torbutton_log(3, "New Identity: Clearing media devices");
744 torbutton_log(3, "New Identity: Clearing predictor network data");
748 Services.clearData.CLEAR_DOM_STORAGES |
749 Services.clearData.CLEAR_PLUGIN_DATA |
750 Services.clearData.CLEAR_MEDIA_DEVICES |
751 Services.clearData.CLEAR_PREDICTOR_NETWORK_DATA
754 torbutton_log(5, "Exception on storage clearing: " + e);
755 window.alert("Torbutton: Unexpected error during storage clearing: " + e);
758 torbutton_log(3, "New Identity: Clearing Cookies and DOM Storage");
760 torbutton_clear_cookies();
762 torbutton_log(3, "New Identity: Closing open connections");
765 obsSvc.notifyObservers(this, "net:prune-all-connections", null);
767 torbutton_log(3, "New Identity: Clearing Content Preferences");
769 // XXX: This may not clear zoom site-specific
770 // browser.content.full-zoom
771 ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
772 "resource://gre/modules/PrivateBrowsingUtils.jsm");
773 var pbCtxt = PrivateBrowsingUtils.privacyContextFromWindow(window);
774 var cps = Cc["@mozilla.org/content-pref/service;1"]
775 .getService(Ci.nsIContentPrefService2);
776 cps.removeAllDomains(pbCtxt);
778 torbutton_log(3, "New Identity: Syncing prefs");
780 // Force prefs to be synced to disk
781 Services.prefs.savePrefFile(null);
783 torbutton_log(3, "New Identity: Clearing permissions");
785 let pm = Services.perms;
788 // Clear the domain isolation state.
789 torbutton_log(3, "New Identity: Clearing domain isolator");
791 let domainIsolator = Cc["@torproject.org/domain-isolator;1"]
792 .getService(Ci.nsISupports).wrappedJSObject;
793 domainIsolator.clearIsolation();
795 torbutton_log(3, "New Identity: Sending NEWNYM");
797 // We only support TBB for newnym.
798 if (!m_tb_control_pass || (!m_tb_control_ipc_file && !m_tb_control_port)) {
799 var warning = torbutton_get_property_string("torbutton.popup.no_newnym");
800 torbutton_log(5, "Torbutton cannot safely newnym. It does not have access to the Tor Control Port.");
801 window.alert(warning);
803 if (!torbutton_send_ctrl_cmd("SIGNAL NEWNYM\r\n")) {
804 var warning = torbutton_get_property_string("torbutton.popup.no_newnym");
805 torbutton_log(5, "Torbutton was unable to request a new circuit from Tor");
806 window.alert(warning);
810 torbutton_log(3, "Ending any remaining private browsing sessions.");
811 obsSvc.notifyObservers(null, "last-pb-context-exited", "");
813 torbutton_log(3, "New Identity: Opening a new browser window");
815 // Open a new window with the TBB check homepage
816 // In Firefox >=19, can pass {private: true} but we do not need it because
817 // we have browser.privatebrowsing.autostart = true
820 torbutton_log(3, "New identity successful");
822 // Run garbage collection and cycle collection after window is gone.
823 // This ensures that blob URIs are forgotten.
824 window.addEventListener("unload", function (event) {
825 torbutton_log(3, "Initiating New Identity GC pass");
826 // Clear out potential pending sInterSliceGCTimer:
827 m_tb_domWindowUtils.runNextCollectorTimer();
829 // Clear out potential pending sICCTimer:
830 m_tb_domWindowUtils.runNextCollectorTimer();
832 // Schedule a garbage collection in 4000-1000ms...
833 m_tb_domWindowUtils.garbageCollect();
835 // To ensure the GC runs immediately instead of 4-10s from now, we need
836 // to poke it at least 11 times.
837 // We need 5 pokes for GC, 1 poke for the interSliceGC, and 5 pokes for CC.
838 // See nsJSContext::RunNextCollectorTimer() in
839 // https://mxr.mozilla.org/mozilla-central/source/dom/base/nsJSEnvironment.cpp#1970.
840 // XXX: We might want to make our own method for immediate full GC...
841 for (let poke = 0; poke < 11; poke++) {
842 m_tb_domWindowUtils.runNextCollectorTimer();
845 // And now, since the GC probably actually ran *after* the CC last time,
846 // run the whole thing again.
847 m_tb_domWindowUtils.garbageCollect();
848 for (let poke = 0; poke < 11; poke++) {
849 m_tb_domWindowUtils.runNextCollectorTimer();
852 torbutton_log(3, "Completed New Identity GC pass");
855 // Close the current window for added safety
859 function torbutton_clear_image_caches()
863 let imgTools = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools);
864 if (!("getImgCacheForDocument" in imgTools)) {
865 // In Firefox 17 and older, there is one global image cache. Clear it.
866 imgCache = Cc["@mozilla.org/image/cache;1"].getService(Ci.imgICache);
867 imgCache.clearCache(false); // evict all but chrome cache
869 // In Firefox 18 and newer, there are two image caches: one that is
870 // used for regular browsing and one that is used for private browsing.
872 // Clear the non-private browsing image cache.
873 imgCache = imgTools.getImgCacheForDocument(null);
874 imgCache.clearCache(false); // evict all but chrome cache
876 // Try to clear the private browsing cache. To do so, we must locate
877 // a content document that is contained within a private browsing window.
878 let didClearPBCache = false;
879 let wm = Services.wm;
880 let enumerator = wm.getEnumerator("navigator:browser");
881 while (!didClearPBCache && enumerator.hasMoreElements()) {
882 let win = enumerator.getNext();
883 let browserDoc = win.document.documentElement;
884 if (!browserDoc.hasAttribute("privatebrowsingmode"))
887 let tabbrowser = win.gBrowser;
891 var tabCount = tabbrowser.browsers.length;
892 for (var i = 0; i < tabCount; i++) {
893 let doc = tabbrowser.browsers[i].contentDocument;
895 imgCache = imgTools.getImgCacheForDocument(doc);
896 imgCache.clearCache(false); // evict all but chrome cache
897 didClearPBCache = true;
904 // FIXME: This can happen in some rare cases involving XULish image data
905 // in combination with our image cache isolation patch. Sure isn't
906 // a good thing, but it's not really a super-cookie vector either.
907 // We should fix it eventually.
908 torbutton_log(4, "Exception on image cache clearing: "+e);
912 /* Called when we switch the use_nontor_proxy pref in either direction.
914 * Enables/disables domain isolation and then does new identity
916 function torbutton_use_nontor_proxy()
918 let domainIsolator = Cc["@torproject.org/domain-isolator;1"]
919 .getService(Ci.nsISupports).wrappedJSObject;
921 if (m_tb_prefs.getBoolPref("extensions.torbutton.use_nontor_proxy")) {
922 // Disable domain isolation
923 domainIsolator.disableIsolation();
925 domainIsolator.enableIsolation();
928 // Always reset our identity if the proxy has changed from tor
930 torbutton_do_new_identity();
933 function torbutton_do_tor_check()
935 let checkSvc = Cc["@torproject.org/torbutton-torCheckService;1"]
936 .getService(Ci.nsISupports).wrappedJSObject;
937 if (m_tb_prefs.getBoolPref("extensions.torbutton.use_nontor_proxy") ||
938 !m_tb_prefs.getBoolPref("extensions.torbutton.test_enabled"))
939 return; // Only do the check once.
941 // If we have a tor control port and transparent torification is off,
942 // perform a check via the control port.
943 const kEnvSkipControlPortTest = "TOR_SKIP_CONTROLPORTTEST";
944 const kEnvUseTransparentProxy = "TOR_TRANSPROXY";
945 var env = Cc["@mozilla.org/process/environment;1"]
946 .getService(Ci.nsIEnvironment);
947 if ((m_tb_control_ipc_file || m_tb_control_port) &&
948 !env.exists(kEnvUseTransparentProxy) &&
949 !env.exists(kEnvSkipControlPortTest) &&
950 m_tb_prefs.getBoolPref("extensions.torbutton.local_tor_check")) {
951 if (torbutton_local_tor_check())
952 checkSvc.statusOfTorCheck = checkSvc.kCheckSuccessful;
954 // The check failed. Update toolbar icon and tooltip.
955 checkSvc.statusOfTorCheck = checkSvc.kCheckFailed;
959 // A local check is not possible, so perform a remote check.
960 torbutton_initiate_remote_tor_check();
964 function torbutton_local_tor_check()
966 let didLogError = false;
968 let proxyType = m_tb_prefs.getIntPref("network.proxy.type");
972 // Ask tor for its SOCKS listener address and port and compare to the
973 // browser preferences.
974 const kCmdArg = "net/listeners/socks";
975 let resp = torbutton_send_ctrl_cmd("GETINFO " + kCmdArg + "\r\n");
979 function logUnexpectedResponse()
983 torbutton_log(5, "Local Tor check: unexpected GETINFO response: " + resp);
987 function removeBrackets(aStr)
989 // Remove enclosing square brackets if present.
990 if (aStr.startsWith('[') && aStr.endsWith(']'))
991 return aStr.substr(1, aStr.length - 2);
996 // Sample response: net/listeners/socks="127.0.0.1:9149" "127.0.0.1:9150"
997 // First, check for and remove the command argument prefix.
998 if (0 != resp.indexOf(kCmdArg + '=')) {
999 logUnexpectedResponse();
1002 resp = resp.substr(kCmdArg.length + 1);
1004 // Retrieve configured proxy settings and check each listener against them.
1005 // When the SOCKS prefs are set to use IPC (e.g., a Unix domain socket), a
1006 // file URL should be present in network.proxy.socks.
1007 // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1211567
1008 let socksAddr = m_tb_prefs.getCharPref("network.proxy.socks");
1009 let socksPort = m_tb_prefs.getIntPref("network.proxy.socks_port");
1011 if (socksAddr && socksAddr.startsWith("file:")) {
1012 // Convert the file URL to a file path.
1014 let ioService = Services.io;
1015 let fph = ioService.getProtocolHandler("file")
1016 .QueryInterface(Ci.nsIFileProtocolHandler);
1017 socksIPCPath = fph.getFileFromURLSpec(socksAddr).path;
1019 torbutton_log(5, "Local Tor check: IPC file error: " + e);
1023 socksAddr = removeBrackets(socksAddr);
1026 // Split into quoted strings. This code is adapted from utils.splitAtSpaces()
1027 // within tor-control-port.js; someday this code should use the entire
1028 // tor-control-port.js framework.
1030 resp.replace(/((\S*?"(.*?)")+\S*|\S+)/g, function (a, captured) {
1031 addrArray.push(captured);
1034 let foundSocksListener = false;
1035 for (let i = 0; !foundSocksListener && (i < addrArray.length); ++i) {
1037 try { addr = unescapeTorString(addrArray[i]); } catch (e) {}
1041 // Remove double quotes if present.
1042 let len = addr.length;
1043 if ((len > 2) && ('"' == addr.charAt(0)) && ('"' == addr.charAt(len - 1)))
1044 addr = addr.substring(1, len - 1);
1046 if (addr.startsWith("unix:")) {
1050 // Check against the configured UNIX domain socket proxy.
1051 let path = addr.substring(5);
1052 torbutton_log(2, "Tor socks listener (Unix domain socket): " + path);
1053 foundSocksListener = (socksIPCPath === path);
1054 } else if (!socksIPCPath) {
1055 // Check against the configured TCP proxy. We expect addr:port where addr
1056 // may be an IPv6 address; that is, it may contain colon characters.
1057 // Also, we remove enclosing square brackets before comparing addresses
1058 // because tor requires them but Firefox does not.
1059 let idx = addr.lastIndexOf(':');
1061 logUnexpectedResponse();
1063 let torSocksAddr = removeBrackets(addr.substring(0, idx));
1064 let torSocksPort = parseInt(addr.substring(idx + 1), 10);
1065 if ((torSocksAddr.length < 1) || isNaN(torSocksPort)) {
1066 logUnexpectedResponse();
1068 torbutton_log(2, "Tor socks listener: " + torSocksAddr + ':'
1070 foundSocksListener = ((socksAddr === torSocksAddr) &&
1071 (socksPort === torSocksPort));
1077 return foundSocksListener;
1078 } // torbutton_local_tor_check
1081 function torbutton_initiate_remote_tor_check() {
1082 let obsSvc = Services.obs;
1084 let checkSvc = Cc["@torproject.org/torbutton-torCheckService;1"]
1085 .getService(Ci.nsISupports).wrappedJSObject;
1086 let req = checkSvc.createCheckRequest(true); // async
1087 req.onreadystatechange = function (aEvent) {
1088 if (req.readyState === 4) {
1089 let ret = checkSvc.parseCheckResponse(req);
1091 // If we received an error response from check.torproject.org,
1092 // set the status of the tor check to failure (we don't want
1093 // to indicate failure if we didn't receive a response).
1094 if (ret == 2 || ret == 3 || ret == 5 || ret == 6
1095 || ret == 7 || ret == 8) {
1096 checkSvc.statusOfTorCheck = checkSvc.kCheckFailed;
1097 obsSvc.notifyObservers(null, k_tb_tor_check_failed_topic, null);
1098 } else if (ret == 4) {
1099 checkSvc.statusOfTorCheck = checkSvc.kCheckSuccessful;
1100 } // Otherwise, redo the check later
1102 torbutton_log(3, "Tor remote check done. Result: " + ret);
1106 torbutton_log(3, "Sending async Tor remote check");
1109 if (e.result == 0x80004005) // NS_ERROR_FAILURE
1110 torbutton_log(5, "Tor check failed! Is tor running?");
1112 torbutton_log(5, "Tor check failed! Tor internal error: "+e);
1114 checkSvc.statusOfTorCheck = checkSvc.kCheckFailed;
1115 obsSvc.notifyObservers(null, k_tb_tor_check_failed_topic, null);
1117 } // torbutton_initiate_remote_tor_check()
1119 function torbutton_tor_check_ok()
1121 torbutton_do_tor_check();
1122 let checkSvc = Cc["@torproject.org/torbutton-torCheckService;1"]
1123 .getService(Ci.nsISupports).wrappedJSObject;
1124 return (checkSvc.kCheckFailed != checkSvc.statusOfTorCheck);
1127 // Bug 1506 P5: Despite the name, this is the way we disable
1128 // plugins for Tor Browser, too.
1130 // toggles plugins: true for disabled, false for enabled
1131 function torbutton_toggle_plugins(disable_plugins) {
1132 var PH=Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
1133 var P=PH.getPluginTags({});
1134 for(var i=0; i<P.length; i++) {
1135 if ("enabledState" in P[i]) { // FF24
1136 // FIXME: DOCDOC the reasoning for the isDisabled check, or remove it.
1137 var isDisabled = (P[i].enabledState == Ci.nsIPluginTag.STATE_DISABLED);
1138 if (!isDisabled && disable_plugins)
1139 P[i].enabledState = Ci.nsIPluginTag.STATE_DISABLED;
1140 else if (isDisabled && !disable_plugins)
1141 P[i].enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
1142 } else if (P[i].disabled != disable_plugins) { // FF17
1143 P[i].disabled=disable_plugins;
1148 function torbutton_update_disk_prefs() {
1149 var mode = m_tb_prefs.getBoolPref("browser.privatebrowsing.autostart");
1151 m_tb_prefs.setBoolPref("browser.cache.disk.enable", !mode);
1152 m_tb_prefs.setBoolPref("places.history.enabled", !mode);
1154 m_tb_prefs.setBoolPref("security.nocertdb", mode);
1156 // No way to clear this beast during New Identity. Leave it off.
1157 //m_tb_prefs.setBoolPref("dom.indexedDB.enabled", !mode);
1159 m_tb_prefs.setBoolPref("permissions.memory_only", mode);
1161 // Third party abuse. Leave it off for now.
1162 //m_tb_prefs.setBoolPref("browser.cache.offline.enable", !mode);
1164 // Force prefs to be synced to disk
1165 Services.prefs.savePrefFile(null);
1168 function torbutton_update_fingerprinting_prefs() {
1169 var mode = m_tb_prefs.getBoolPref("privacy.resistFingerprinting");
1170 var letterboxing = m_tb_prefs.getBoolPref("privacy.resistFingerprinting.letterboxing", false);
1171 m_tb_prefs.setBoolPref("extensions.torbutton.resize_new_windows", mode && !letterboxing);
1173 // Force prefs to be synced to disk
1174 Services.prefs.savePrefFile(null);
1177 // This function closes all XUL browser windows except this one. For this
1178 // window, it closes all existing tabs and creates one about:blank tab.
1179 function torbutton_close_tabs_on_new_identity() {
1180 if (!m_tb_prefs.getBoolPref("extensions.torbutton.close_newnym")) {
1181 torbutton_log(3, "Not closing tabs");
1185 // TODO: muck around with browser.tabs.warnOnClose.. maybe..
1186 torbutton_log(3, "Closing tabs...");
1187 let wm = Services.wm;
1188 let enumerator = wm.getEnumerator("navigator:browser");
1189 let windowsToClose = [];
1190 while (enumerator.hasMoreElements()) {
1191 let win = enumerator.getNext();
1192 let browser = win.gBrowser;
1194 torbutton_log(5, "No browser for possible closed window");
1198 let tabCount = browser.browsers.length;
1199 torbutton_log(3, "Tab count for window: " + tabCount);
1200 let tabsToRemove = [];
1201 for (let i = 0; i < tabCount; i++) {
1202 let tab = browser.getTabForBrowser(browser.browsers[i]);
1204 torbutton_log(5, "No tab for browser");
1206 tabsToRemove.push(tab);
1210 if (win == window) {
1211 browser.addWebTab("about:blank");
1213 // It is a bad idea to alter the window list while iterating
1214 // over it, so add this window to an array and close it later.
1215 windowsToClose.push(win);
1218 // Close each tab except the new blank one that we created.
1219 tabsToRemove.forEach(aTab => browser.removeTab(aTab));
1222 // Close all XUL windows except this one.
1223 torbutton_log(2, "Closing windows...");
1224 windowsToClose.forEach(aWin => aWin.close());
1226 torbutton_log(3, "Closed all tabs");
1229 // -------------- HISTORY & COOKIES ---------------------
1231 // Bug 1506 P4: Used by New Identity if cookie protections are
1233 function torbutton_clear_cookies() {
1234 torbutton_log(2, "called torbutton_clear_cookies");
1235 var cm = Services.cookies;
1240 // -------------- JS/PLUGIN HANDLING CODE ---------------------
1241 // Bug 1506 P3: Defense in depth. Disables JS and events for New Identity.
1242 function torbutton_disable_browser_js(browser) {
1243 var eventSuppressor = null;
1245 /* Solution from: https://bugzilla.mozilla.org/show_bug.cgi?id=409737 */
1246 // XXX: This kills the entire window. We need to redirect
1247 // focus and inform the user via a lightbox.
1249 if (!browser.contentWindow)
1250 torbutton_log(3, "No content window to disable JS events.");
1252 eventSuppressor = browser.contentWindow.windowUtils;
1254 torbutton_log(4, "Failed to disable JS events: "+e)
1257 if (browser.docShell)
1258 browser.docShell.allowJavascript = false;
1261 // My estimation is that this does not get the inner iframe windows,
1262 // but that does not matter, because iframes should be destroyed
1263 // on the next load.
1264 browser.contentWindow.name = null;
1265 browser.contentWindow.window.name = null;
1267 torbutton_log(4, "Failed to reset window.name: "+e)
1270 if (eventSuppressor)
1271 eventSuppressor.suppressEventHandling(true);
1274 // Bug 1506 P3: The JS-killing bits of this are used by
1275 // New Identity as a defense-in-depth measure.
1276 function torbutton_disable_window_js(win) {
1277 var browser = win.gBrowser;
1279 torbutton_log(5, "No browser for plugin window...");
1282 var browsers = browser.browsers;
1283 torbutton_log(1, "Toggle window plugins");
1285 for (var i = 0; i < browsers.length; ++i) {
1286 var b = browser.browsers[i];
1287 if (b && !b.docShell) {
1290 torbutton_log(5, "DocShell is null for: "+b.currentURI.spec);
1292 torbutton_log(5, "DocShell is null for unknown URL");
1294 torbutton_log(5, "DocShell is null for unparsable URL: "+e);
1297 if (b && b.docShell) {
1298 torbutton_disable_browser_js(b);
1300 // kill meta-refresh and existing page loading
1301 // XXX: Despite having JUST checked b.docShell, it can
1302 // actually end up NULL here in some cases?
1304 if (b.docShell && b.webNavigation)
1305 b.webNavigation.stop(b.webNavigation.STOP_ALL);
1307 torbutton_log(4, "DocShell error: "+e);
1313 // Bug 1506 P3: The JS-killing bits of this are used by
1314 // New Identity as a defense-in-depth measure.
1316 // This is an ugly beast.. But unfortunately it has to be so..
1317 // Looping over all tabs twice is not somethign we wanna do..
1318 function torbutton_disable_all_js() {
1319 var wm = Services.wm;
1320 var enumerator = wm.getEnumerator("navigator:browser");
1321 while(enumerator.hasMoreElements()) {
1322 var win = enumerator.getNext();
1323 torbutton_disable_window_js(win);
1327 // Bug 1506 P1: This function just cleans up prefs that got set badly in previous releases
1328 function torbutton_fixup_old_prefs()
1330 if(m_tb_prefs.getIntPref('extensions.torbutton.pref_fixup_version') < 1) {
1331 // TBB 5.0a3 had bad Firefox code that silently flipped this pref on us
1332 if (m_tb_prefs.prefHasUserValue("browser.newtabpage.enhanced")) {
1333 m_tb_prefs.clearUserPref("browser.newtabpage.enhanced");
1334 // TBB 5.0a3 users had all the necessary data cached in
1335 // directoryLinks.json. This meant that resetting the pref above
1336 // alone was not sufficient as the tiles features uses the cache
1337 // even if the pref indicates that feature should be disabled.
1338 // We flip the preference below as this forces a refetching which
1339 // effectively results in an empty JSON file due to our spoofed
1341 let matchOS = m_tb_prefs.getBoolPref("intl.locale.matchOS");
1342 m_tb_prefs.setBoolPref("intl.locale.matchOS", !matchOS);
1343 m_tb_prefs.setBoolPref("intl.locale.matchOS", matchOS);
1346 // For some reason, the Share This Page button also survived the
1347 // TBB 5.0a4 update's attempt to remove it.
1348 if (m_tb_prefs.prefHasUserValue("browser.uiCustomization.state")) {
1349 m_tb_prefs.clearUserPref("browser.uiCustomization.state");
1352 m_tb_prefs.setIntPref('extensions.torbutton.pref_fixup_version', 1);
1356 // ---------------------- Event handlers -----------------
1358 // Bug 1506 P1-P3: Most of these observers aren't very important.
1359 // See their comments for details
1360 function torbutton_do_main_window_startup()
1362 torbutton_log(3, "Torbutton main window startup");
1363 m_tb_is_main_window = true;
1364 torbutton_unique_pref_observer.register();
1367 // Bug 1506 P4: Most of this function is now useless, save
1368 // for the very important SOCKS environment vars at the end.
1369 // Those could probably be rolled into a function with the
1370 // control port vars, though. See 1506 comments inside.
1371 function torbutton_do_startup()
1373 if(m_tb_prefs.getBoolPref("extensions.torbutton.startup")) {
1374 // Bug 1506: Still want to do this
1375 torbutton_toggle_plugins(
1376 m_tb_prefs.getBoolPref("plugin.disable"));
1378 // Bug 1506: Should probably be moved to an XPCOM component
1379 torbutton_do_main_window_startup();
1382 torbutton_update_fingerprinting_prefs();
1384 // Bug 30565: sync browser.privatebrowsing.autostart with security.nocertdb
1385 torbutton_update_disk_prefs();
1387 // For general pref fixups to handle pref damage in older versions
1388 torbutton_fixup_old_prefs();
1390 m_tb_prefs.setBoolPref("extensions.torbutton.startup", false);
1394 // Bug 1506 P3: Used to decide if we should resize the window.
1396 // Returns true if the window wind is neither maximized, full screen,
1397 // ratpoisioned/evilwmed, nor minimized.
1398 function torbutton_is_windowed(wind) {
1399 torbutton_log(3, "Window: (" + wind.outerWidth + "," + wind.outerHeight + ") ?= ("
1400 + wind.screen.availWidth + "," + wind.screen.availHeight + ")");
1401 if (wind.windowState == Ci.nsIDOMChromeWindow.STATE_MINIMIZED
1402 || wind.windowState == Ci.nsIDOMChromeWindow.STATE_MAXIMIZED) {
1403 torbutton_log(2, "Window is minimized/maximized");
1406 if ("fullScreen" in wind && wind.fullScreen) {
1407 torbutton_log(2, "Window is fullScreen");
1410 if(wind.outerHeight == wind.screen.availHeight
1411 && wind.outerWidth == wind.screen.availWidth) {
1412 torbutton_log(3, "Window is ratpoisoned/evilwm'ed");
1416 torbutton_log(2, "Window is normal");
1420 function showSecurityPreferencesPanel(chromeWindow) {
1421 const tabBrowser = chromeWindow.BrowserApp;
1422 let settingsTab = null;
1424 const SECURITY_PREFERENCES_URI = 'chrome://torbutton/content/preferences.xhtml';
1426 tabBrowser.tabs.some(function (tab) {
1427 // If the security prefs tab is opened, send the user to it
1428 if (tab.browser.currentURI.spec === SECURITY_PREFERENCES_URI) {
1435 if (settingsTab === null) {
1436 // Open up the settings panel in a new tab.
1437 tabBrowser.addTab(SECURITY_PREFERENCES_URI, {
1439 "parentId": tabBrowser.selectedTab.id,
1440 triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
1443 // Activate an existing settings panel tab.
1444 tabBrowser.selectTab(settingsTab);
1448 function setupPreferencesForMobile() {
1449 if (!torbutton_is_mobile()) {
1453 torbutton_log(4, "Setting up settings preferences for Android.");
1455 const chromeWindow = Services.wm.getMostRecentWindow('navigator:browser');
1457 // Add the extension's chrome menu item to the main browser menu.
1458 chromeWindow.NativeWindow.menu.add({
1459 'name': torbutton_get_property_string("torbutton.security_settings.menu.title"),
1460 'callback': showSecurityPreferencesPanel.bind(this, chromeWindow)
1464 // Bug 1506 P3: This is needed pretty much only for the window resizing.
1465 // See comments for individual functions for details
1466 function torbutton_new_window(event)
1468 torbutton_log(3, "New window");
1469 var browser = window.gBrowser;
1472 torbutton_log(5, "No browser for new window.");
1476 if (!m_tb_wasinited) {
1480 torbutton_do_startup();
1482 let progress = Cc["@mozilla.org/docloaderservice;1"]
1483 .getService(Ci.nsIWebProgress);
1485 if (torbutton_is_windowed(window)) {
1486 progress.addProgressListener(torbutton_resizelistener,
1487 Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
1490 torbutton_do_tor_check();
1493 // Bug 1506 P2: This is only needed because we have observers
1494 // in XUL that should be in an XPCOM component
1495 function torbutton_close_window(event) {
1496 torbutton_tor_check_observer.unregister();
1498 window.removeEventListener("sizemodechange", m_tb_resize_handler,
1501 // TODO: This is a real ghetto hack.. When the original window
1502 // closes, we need to find another window to handle observing
1503 // unique events... The right way to do this is to move the
1504 // majority of torbutton functionality into a XPCOM component..
1505 // But that is a major overhaul..
1506 if (m_tb_is_main_window) {
1507 torbutton_log(3, "Original window closed. Searching for another");
1508 var wm = Services.wm;
1509 var enumerator = wm.getEnumerator("navigator:browser");
1510 while(enumerator.hasMoreElements()) {
1511 var win = enumerator.getNext();
1512 // For some reason, when New Identity is called from a pref
1513 // observer (ex: torbutton_use_nontor_proxy) on an ASAN build,
1514 // we sometimes don't have this symbol set in the new window yet.
1515 // However, the new window will run this init later in that case,
1516 // as it does in the OSX case.
1517 if(win != window && "torbutton_do_main_window_startup" in win) {
1518 torbutton_log(3, "Found another window");
1519 win.torbutton_do_main_window_startup();
1520 m_tb_is_main_window = false;
1525 torbutton_unique_pref_observer.unregister();
1527 if(m_tb_is_main_window) { // main window not reset above
1528 // This happens on Mac OS because they allow firefox
1529 // to still persist without a navigator window
1530 torbutton_log(3, "Last window closed. None remain.");
1531 m_tb_prefs.setBoolPref("extensions.torbutton.startup", true);
1532 m_tb_is_main_window = false;
1537 window.addEventListener('load',torbutton_new_window,false);
1538 window.addEventListener('unload', torbutton_close_window, false);
1540 var m_tb_resize_handler = null;
1541 var m_tb_resize_date = null;
1543 // Bug 1506 P1/P3: Setting a fixed window size is important, but
1544 // probably not for android.
1545 var torbutton_resizelistener =
1547 QueryInterface: ChromeUtils.generateQI(["nsIWebProgressListener", "nsISupportsWeakReference"]),
1549 onLocationChange: function(aProgress, aRequest, aURI) {},
1550 onStateChange: function(aProgress, aRequest, aFlag, aStatus) {
1551 if (aFlag & Ci.nsIWebProgressListener.STATE_STOP) {
1552 m_tb_resize_handler = async function() {
1553 // Wait for end of execution queue to ensure we have correct windowState.
1554 await new Promise(resolve => setTimeout(resolve, 0));
1555 if (window.windowState === window.STATE_MAXIMIZED ||
1556 window.windowState === window.STATE_FULLSCREEN) {
1557 if (m_tb_prefs.getBoolPref("extensions.torbutton.resize_new_windows") &&
1558 m_tb_prefs.getIntPref("extensions.torbutton.maximize_warnings_remaining") > 0) {
1560 // Do not add another notification if one is already showing.
1561 const kNotificationName = "torbutton-maximize-notification";
1562 let box = gBrowser.getNotificationBox();
1563 if (box.getNotificationWithValue(kNotificationName))
1566 // Rate-limit showing our notification if needed.
1567 if (m_tb_resize_date === null) {
1568 m_tb_resize_date = Date.now();
1570 // We wait at least another second before we show a new
1571 // notification. Should be enough to rule out OSes that call our
1572 // handler rapidly due to internal workings.
1573 if (Date.now() - m_tb_resize_date < 1000) {
1576 // Resizing but we need to reset |m_tb_resize_date| now.
1577 m_tb_resize_date = Date.now();
1580 // No need to get "OK" translated again.
1581 let sbSvc = Services.strings;
1583 createBundle("chrome://global/locale/commonDialogs.properties");
1584 let button_label = bundle.GetStringFromName("OK");
1587 label: button_label,
1592 m_tb_prefs.setIntPref("extensions.torbutton.maximize_warnings_remaining",
1593 m_tb_prefs.getIntPref("extensions.torbutton.maximize_warnings_remaining") - 1);
1597 let priority = box.PRIORITY_WARNING_LOW;
1599 torbutton_get_property_string("torbutton.maximize_warning");
1601 box.appendNotification(message, kNotificationName, null,
1606 }; // m_tb_resize_handler
1608 // We need to handle OSes that auto-maximize windows depending on user
1609 // settings and/or screen resolution in the start-up phase and users that
1610 // try to shoot themselves in the foot by maximizing the window manually.
1611 // We add a listener which is triggerred as soon as the window gets
1612 // maximized (windowState = 1). We are resizing during start-up but not
1613 // later as the user should see only a warning there as a stopgap before
1615 // Alas, the Firefox window code is handling the event not itself:
1616 // "// Note the current implementation of SetSizeMode just stores
1617 // // the new state; it doesn't actually resize. So here we store
1618 // // the state and pass the event on to the OS."
1619 // (See: https://mxr.mozilla.org/mozilla-esr31/source/xpfe/appshell/src/
1620 // nsWebShellWindow.cpp#348)
1621 // This means we have to cope with race conditions and resizing in the
1622 // sizemodechange listener is likely to fail. Thus, we add a specific
1623 // resize listener that is doing the work for us. It seems (at least on
1624 // Ubuntu) to be the case that maximizing (and then again normalizing) of
1625 // the window triggers more than one resize event the first being not the
1626 // one we need. Thus we can't remove the listener after the first resize
1627 // event got fired. Thus, we have the rather klunky setTimeout() call.
1628 window.addEventListener("sizemodechange", m_tb_resize_handler, false);
1630 let progress = Cc["@mozilla.org/docloaderservice;1"]
1631 .getService(Ci.nsIWebProgress);
1632 progress.removeProgressListener(this);
1636 onProgressChange: function(aProgress, aRequest, curSelfProgress,
1637 maxSelfProgress, curTotalProgress,
1638 maxTotalProgress) {},
1639 onStatusChange: function(aProgress, aRequest, stat, message) {},
1640 onSecurityChange: function() {}
1643 // Makes sure the item in the Help Menu and the link in about:tor
1644 // for the Tor Browser User Manual are only visible when
1645 // show_torbrowser_manual() returns true.
1646 function torbutton_init_user_manual_links() {
1647 let menuitem = document.getElementById("torBrowserUserManual");
1648 bindPrefAndInit("intl.locale.requested", val => {
1649 menuitem.hidden = !show_torbrowser_manual();
1650 torbutton_abouttor_message_handler.updateAllOpenPages();