Bug 27476: misc about:torconnect fixes and polish
[torbutton.git] / chrome / content / torbutton.js
blob48539a965217e409c29fccf33a532ec0fc20c643
1 // window globals
2 var torbutton_init;
3 var torbutton_new_circuit;
4 var torbutton_new_identity;
6 (() => {
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
20  */
22 let {
23   show_torbrowser_manual,
24   unescapeTorString,
25   bindPrefAndInit,
26   getDomainForBrowser,
27   torbutton_safelog,
28   torbutton_log,
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;
37 // status
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, {
54       onDataDeleted(code) {
55         if (code === Cr.NS_OK) {
56           resolve();
57         } else {
58           reject(new Error(`Error deleting data with flags ${flags}: ${code}`));
59         }
60       },
61     });
62   });
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 =
70     register: function()
71     {
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");
83     },
85     unregister: function()
86     {
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");
96     },
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)
102     {
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
116               */
117              if (!m_tb_confirming_plugins) {
118                m_tb_confirming_plugins = true;
119                torbutton_confirm_plugins();
120                m_tb_confirming_plugins = false;
121              } else {
122                torbutton_log(3, "Skipping notification for mime type: "+subject);
123              }
124           }
125           return;
126         }
128         if (topic != "nsPref:changed") return;
130         switch (data) {
131             case "plugin.disable":
132                 torbutton_toggle_plugins(
133                         m_tb_prefs.getBoolPref("plugin.disable"));
134                 break;
135             case "browser.privatebrowsing.autostart":
136                 torbutton_update_disk_prefs();
137                 break;
138             case "extensions.torbutton.use_nontor_proxy":
139                 torbutton_use_nontor_proxy();
140                 break;
141             case "privacy.resistFingerprinting":
142             case "privacy.resistFingerprinting.letterboxing":
143                 torbutton_update_fingerprinting_prefs();
144                 break;
145         }
146     }
149 var torbutton_tor_check_observer = {
150     register() {
151         this._obsSvc = Services.obs;
152         this._obsSvc.addObserver(this, k_tb_tor_check_failed_topic);
153     },
155     unregister: function()
156     {
157         if (this._obsSvc)
158           this._obsSvc.removeObserver(this, k_tb_tor_check_failed_topic);
159     },
161     observe: function(subject, topic, data)
162     {
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
168         // window, open one.
169         var wm = Services.wm;
170         var win = wm.getMostRecentWindow("navigator:browser");
171         if (win == window) {
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");
177           }
179           if (!foundTab) {
180             gBrowser.selectedTab = gBrowser.addTrustedTab("about:tor");
181           }
182         }
183       }
184     },
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) {
198         return;
199     }
200     m_tb_wasinited = true;
202     let tlps;
203     try {
204         tlps = Cc["@torproject.org/torlauncher-protocol-service;1"]
205                  .getService(Ci.nsISupports).wrappedJSObject;
206     } catch(e) {}
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");
216         try {
217             if ("" != cookie_path) {
218                 m_tb_control_pass = torbutton_read_authentication_cookie(cookie_path);
219             }
220         } catch(e) {
221             torbutton_log(4, 'unable to read authentication cookie');
222         }
223     } else {
224       try {
225         // Try to get password from Tor Launcher.
226         m_tb_control_pass = tlps.TorGetPassword(false);
227       } catch (e) {}
228     }
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.
233     try {
234         m_tb_control_ipc_file = tlps.TorGetControlIPCFile();
235     } catch(e) {}
237     if (m_tb_control_ipc_file) {
238         m_tb_control_desc = m_tb_control_ipc_file.path;
239     } else {
240         if (environ.exists("TOR_CONTROL_PORT")) {
241             m_tb_control_port = environ.get("TOR_CONTROL_PORT");
242         } else {
243             try {
244                 const kTLControlPortPref = "extensions.torlauncher.control_port";
245                 m_tb_control_port = m_tb_prefs.getIntPref(kTLControlPortPref);
246             } catch(e) {
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
249               // port value here.
250             }
251         }
253         if (m_tb_control_port) {
254           m_tb_control_desc = "" + m_tb_control_port;
255         }
257         if (environ.exists("TOR_CONTROL_HOST")) {
258             m_tb_control_host = environ.get("TOR_CONTROL_HOST");
259         } else {
260             try {
261                 const kTLControlHostPref = "extensions.torlauncher.control_host";
262                 m_tb_control_host = m_tb_prefs.getCharPref(kTLControlHostPref);
263             } catch(e) {
264               m_tb_control_host = "127.0.0.1";
265             }
266         }
267     }
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();
281     try {
282         createTorCircuitDisplay("extensions.torbutton.display_circuit");
283     } catch(e) {
284         torbutton_log(4, "Error creating the tor circuit display " + e);
285     }
287     try {
288         torbutton_init_user_manual_links();
289     } catch(e) {
290         torbutton_log(4, "Error loading the user manual " + e);
291     }
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));
307         break;
308     }
309   },
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));
315   },
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
321   // not working.
322   getChromeData: function(aIsRespondingToPageLoad) {
323     let dataObj = {
324       mobile: torbutton_is_mobile(),
325       updateChannel: AppConstants.MOZ_UPDATE_CHANNEL,
326       torOn: torbutton_tor_check_ok()
327     };
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();
335       }
336     }
338     return dataObj;
339   },
341   getUpdateMoreInfoURL: function() {
342     try {
343       return Services.prefs.getCharPref("torbrowser.post_update.url");
344     } catch (e) {}
346     // Use the default URL as a fallback.
347     return Services.urlFormatter.formatURLPref("startup.homepage_override_url");
348   }
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++) {
356       if (!P[i].disabled)
357         any_plugins_enabled = true;
358   }
360   if (!any_plugins_enabled) {
361     torbutton_log(3, "False positive on plugin notification. Ignoring");
362     return;
363   }
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.
385   if (no_plugins !=
386       m_tb_prefs.getBoolPref("plugin.disable"))
387     m_tb_prefs.setBoolPref("plugin.disable", no_plugins);
388   else
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();
407       }
408     }
409   }
412 // Bug 1506 P4: Control port interaction. Needed for New Identity.
413 function torbutton_socket_readline(input) {
414   var str = "";
415   var bytes;
416   while((bytes = input.readBytes(1)) != "\n") {
417     if (bytes != '\r')
418       str += bytes;
419   }
420   return str;
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();
436   fileStream.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)
444                    }).join('');
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);
464   try {
465     let sts = Cc["@mozilla.org/network/socket-transport-service;1"]
466         .getService(Ci.nsISocketTransportService);
467     let socket;
468     if (m_tb_control_ipc_file) {
469       socket = sts.createUnixDomainTransport(m_tb_control_ipc_file);
470     } else {
471       socket = sts.createTransport([], m_tb_control_host,
472                                    m_tb_control_port, null);
473     }
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);
494       return null;
495     }
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);
501       return null;
502     }
504     // Closing these streams prevents a shutdown hang on Mac OS. See bug 10201.
505     inputStream.close();
506     outputStream.close();
507     socket.close(Cr.NS_OK);
508     return bytes.substr(4);
509   } catch(e) {
510     torbutton_log(4, "Exception on control port "+e);
511     return null;
512   }
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() {
531   try {
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) {
535       return;
536     }
538     newIdentityInProgress = true;
540     let shouldConfirm =  m_tb_prefs.getBoolPref("extensions.torbutton.confirm_newnym");
542     if (shouldConfirm) {
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);
557       if (confirmed) {
558         await torbutton_do_new_identity();
559       }
560     } else {
561       await torbutton_do_new_identity();
562     }
563   } catch(e) {
564     // If something went wrong make sure we have the New Identity button
565     // enabled (again).
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);
570   } finally {
571     newIdentityInProgress = false;
572   }
575 /* The "New Identity" implementation does the following:
576  *   1. Disables Javascript and plugins on all tabs
577  *   2. Clears state:
578  *      a. OCSP
579  *      b. Cache + image cache
580  *      c. Site-specific zoom
581  *      d. Cookies+DOM Storage+safe browsing key
582  *      e. google wifi geolocation token
583  *      f. http auth
584  *      g. SSL Session IDs
585  *      h. last open location url
586  *      i. clear content prefs
587  *      j. permissions
588  *      k. site security settings (e.g. HSTS)
589  *      l. IndexedDB and other DOM storage
590  *      m. plugin data
591  *      n. media devices
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.
598  */
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"));
610   try {
611       if(m_tb_prefs.prefHasUserValue("geo.wifi.access_token")) {
612           m_tb_prefs.clearUserPref("geo.wifi.access_token");
613       }
614   } catch(e) {
615       torbutton_log(3, "Exception on wifi token clear: "+e);
616   }
618   try {
619       if(m_tb_prefs.prefHasUserValue("general.open_location.last_url")) {
620           m_tb_prefs.clearUserPref("general.open_location.last_url");
621       }
622   } catch(e) {
623       torbutton_log(3, "Exception on clearing last opened location: "+e);
624   }
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.
632   try {
633     var searchBar = window.document.getElementById("searchbar");
634     if (searchBar)
635         searchBar.textbox.reset();
636   } catch(e) {
637     torbutton_log(5, "New Identity: Exception on clearing search box: "+e);
638   }
640   try {
641     if (gFindBarInitialized) {
642         var findbox = gFindBar.getElement("findbar-textbox");
643         findbox.reset();
644         gFindBar.close();
645     }
646   } catch(e) {
647     torbutton_log(5, "New Identity: Exception on clearing find bar: "+e);
648   }
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);
658       auth.clearAll();
659   }
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
665   // cache.
666   let sdr = Cc["@mozilla.org/security/sdr;1"].
667                        getService(Ci.nsISecretDecoderRing);
668   sdr.logoutAndTeardown();
670   // This clears the OCSP cache.
671   //
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?
683   try {
684       Services.perms.removeAll();
685   } catch(e) {
686       // Actually, this catch does not appear to be needed. Leaving it in for
687       // safety though.
688       torbutton_log(3, "Can't clear permissions: Not Tor Browser: "+e);
689   }
691    // Clear site security settings
692    let sss = Cc["@mozilla.org/ssservice;1"].
693      getService(Ci.nsISiteSecurityService);
694    sss.clearAll();
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");
706   try {
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.
717       try {
718         appCacheStorage.asyncEvictStorage(null);
719       } catch (err) {
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') {
723              throw err;
724          }
725       }
726     }
727   } catch(e) {
728       torbutton_log(5, "Exception on cache clearing: "+e);
729       window.alert("Torbutton: Unexpected error during offline cache clearing: "+e);
730   }
732   torbutton_log(3, "New Identity: Clearing Disk and Memory Caches");
734   try {
735       Services.cache2.clear();
736   } catch(e) {
737       torbutton_log(5, "Exception on cache clearing: "+e);
738       window.alert("Torbutton: Unexpected error during cache clearing: "+e);
739   }
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");
746   try {
747     await clearData(
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
752     );
753   } catch (e) {
754     torbutton_log(5, "Exception on storage clearing: " + e);
755     window.alert("Torbutton: Unexpected error during storage clearing: " + e);
756   }
758   torbutton_log(3, "New Identity: Clearing Cookies and DOM Storage");
760   torbutton_clear_cookies();
762   torbutton_log(3, "New Identity: Closing open connections");
764   // Clear keep-alive
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;
786   pm.removeAll();
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);
802   } else {
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);
807     }
808   }
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
818   OpenBrowserWindow();
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();
843     }
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();
850     }
852     torbutton_log(3, "Completed New Identity GC pass");
853   });
855   // Close the current window for added safety
856   window.close();
859 function torbutton_clear_image_caches()
861   try {
862     let imgCache;
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
868     } else {
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"))
885           continue;
887         let tabbrowser = win.gBrowser;
888         if (!tabbrowser)
889           continue;
891         var tabCount = tabbrowser.browsers.length;
892         for (var i = 0; i < tabCount; i++) {
893           let doc = tabbrowser.browsers[i].contentDocument;
894           if (doc) {
895             imgCache = imgTools.getImgCacheForDocument(doc);
896             imgCache.clearCache(false); // evict all but chrome cache
897             didClearPBCache = true;
898             break;
899           }
900         }
901       }
902     }
903   } catch(e) {
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);
909   }
912 /* Called when we switch the use_nontor_proxy pref in either direction.
914  * Enables/disables domain isolation and then does new identity
915  */
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();
924   } else {
925     domainIsolator.enableIsolation();
926   }
928   // Always reset our identity if the proxy has changed from tor
929   // to non-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;
953     else {
954       // The check failed.  Update toolbar icon and tooltip.
955       checkSvc.statusOfTorCheck = checkSvc.kCheckFailed;
956     }
957   }
958   else {
959     // A local check is not possible, so perform a remote check.
960     torbutton_initiate_remote_tor_check();
961   }
964 function torbutton_local_tor_check()
966   let didLogError = false;
968   let proxyType = m_tb_prefs.getIntPref("network.proxy.type");
969   if (0 == proxyType)
970     return false;
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");
976   if (!resp)
977     return false;
979   function logUnexpectedResponse()
980   {
981     if (!didLogError) {
982       didLogError = true;
983       torbutton_log(5, "Local Tor check: unexpected GETINFO response: " + resp);
984     }
985   }
987   function removeBrackets(aStr)
988   {
989     // Remove enclosing square brackets if present.
990     if (aStr.startsWith('[') && aStr.endsWith(']'))
991       return aStr.substr(1, aStr.length - 2);
993     return aStr;
994   }
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();
1000     return false;
1001   }
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");
1010   let socksIPCPath;
1011   if (socksAddr && socksAddr.startsWith("file:")) {
1012     // Convert the file URL to a file path.
1013     try {
1014       let ioService = Services.io;
1015       let fph = ioService.getProtocolHandler("file")
1016                          .QueryInterface(Ci.nsIFileProtocolHandler);
1017       socksIPCPath = fph.getFileFromURLSpec(socksAddr).path;
1018     } catch (e) {
1019       torbutton_log(5, "Local Tor check: IPC file error: " + e);
1020       return false;
1021     }
1022   } else {
1023     socksAddr = removeBrackets(socksAddr);
1024   }
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.
1029   let addrArray = [];
1030   resp.replace(/((\S*?"(.*?)")+\S*|\S+)/g, function (a, captured) {
1031     addrArray.push(captured);
1032   });
1034   let foundSocksListener = false;
1035   for (let i = 0; !foundSocksListener && (i < addrArray.length); ++i) {
1036     let addr;
1037     try { addr = unescapeTorString(addrArray[i]); } catch (e) {}
1038     if (!addr)
1039       continue;
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:")) {
1047       if (!socksIPCPath)
1048         continue;
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(':');
1060       if (idx < 0) {
1061         logUnexpectedResponse();
1062       } else {
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();
1067         } else {
1068           torbutton_log(2, "Tor socks listener: " + torSocksAddr + ':'
1069                            + torSocksPort);
1070           foundSocksListener = ((socksAddr === torSocksAddr) &&
1071                                 (socksPort === torSocksPort));
1072         }
1073       }
1074     }
1075   }
1077   return foundSocksListener;
1078 } // torbutton_local_tor_check
1081 function torbutton_initiate_remote_tor_check() {
1082   let obsSvc = Services.obs;
1083   try {
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);
1103           }
1104       };
1106       torbutton_log(3, "Sending async Tor remote check");
1107       req.send(null);
1108   } catch(e) {
1109     if (e.result == 0x80004005) // NS_ERROR_FAILURE
1110       torbutton_log(5, "Tor check failed! Is tor running?");
1111     else
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);
1116   }
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;
1144       }
1145   }
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");
1182     return;
1183   }
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;
1193     if (!browser) {
1194       torbutton_log(5, "No browser for possible closed window");
1195       continue;
1196     }
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]);
1203       if (!tab) {
1204         torbutton_log(5, "No tab for browser");
1205       } else {
1206         tabsToRemove.push(tab);
1207       }
1208     }
1210     if (win == window) {
1211       browser.addWebTab("about:blank");
1212     } else {
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);
1216     }
1218     // Close each tab except the new blank one that we created.
1219     tabsToRemove.forEach(aTab => browser.removeTab(aTab));
1220   }
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
1232 // not in use.
1233 function torbutton_clear_cookies() {
1234     torbutton_log(2, "called torbutton_clear_cookies");
1235     var cm = Services.cookies;
1237     cm.removeAll();
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.
1248     try {
1249         if (!browser.contentWindow)
1250             torbutton_log(3, "No content window to disable JS events.");
1251         else
1252             eventSuppressor = browser.contentWindow.windowUtils;
1253     } catch(e) {
1254         torbutton_log(4, "Failed to disable JS events: "+e)
1255     }
1257     if (browser.docShell)
1258       browser.docShell.allowJavascript = false;
1260     try {
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;
1266     } catch(e) {
1267         torbutton_log(4, "Failed to reset window.name: "+e)
1268     }
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;
1278     if (!browser) {
1279       torbutton_log(5, "No browser for plugin window...");
1280       return;
1281     }
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) {
1288             try {
1289                 if (b.currentURI)
1290                     torbutton_log(5, "DocShell is null for: "+b.currentURI.spec);
1291                 else
1292                     torbutton_log(5, "DocShell is null for unknown URL");
1293             } catch(e) {
1294                 torbutton_log(5, "DocShell is null for unparsable URL: "+e);
1295             }
1296         }
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?
1303             try {
1304               if (b.docShell && b.webNavigation)
1305                 b.webNavigation.stop(b.webNavigation.STOP_ALL);
1306             } catch(e) {
1307               torbutton_log(4, "DocShell error: "+e);
1308             }
1309         }
1310     }
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);
1324     }
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
1340             // URLs.
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);
1344         }
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");
1350         }
1352         m_tb_prefs.setIntPref('extensions.torbutton.pref_fixup_version', 1);
1353     }
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();
1381         // For charsets
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);
1391     }
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");
1404         return false;
1405     }
1406     if ("fullScreen" in wind && wind.fullScreen) {
1407         torbutton_log(2, "Window is fullScreen");
1408         return false;
1409     }
1410     if(wind.outerHeight == wind.screen.availHeight
1411             && wind.outerWidth == wind.screen.availWidth) {
1412         torbutton_log(3, "Window is ratpoisoned/evilwm'ed");
1413         return false;
1414     }
1416     torbutton_log(2, "Window is normal");
1417     return true;
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) {
1429           settingsTab = tab;
1430           return true;
1431       }
1432       return false;
1433   });
1435   if (settingsTab === null) {
1436       // Open up the settings panel in a new tab.
1437       tabBrowser.addTab(SECURITY_PREFERENCES_URI, {
1438           "selected": true,
1439           "parentId": tabBrowser.selectedTab.id,
1440           triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
1441       });
1442   } else {
1443       // Activate an existing settings panel tab.
1444       tabBrowser.selectTab(settingsTab);
1445   }
1448 function setupPreferencesForMobile() {
1449   if (!torbutton_is_mobile()) {
1450     return;
1451   }
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)
1461   });
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;
1471     if(!browser) {
1472       torbutton_log(5, "No browser for new window.");
1473       return;
1474     }
1476     if (!m_tb_wasinited) {
1477         torbutton_init();
1478     }
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);
1488     }
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,
1499         false);
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;
1521                 break;
1522             }
1523         }
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;
1533         }
1534     }
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))
1564               return;
1566             // Rate-limit showing our notification if needed.
1567             if (m_tb_resize_date === null) {
1568               m_tb_resize_date = Date.now();
1569             } else {
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) {
1574                 return;
1575               }
1576               // Resizing but we need to reset |m_tb_resize_date| now.
1577               m_tb_resize_date = Date.now();
1578             }
1580             // No need to get "OK" translated again.
1581             let sbSvc = Services.strings;
1582             let bundle = sbSvc.
1583               createBundle("chrome://global/locale/commonDialogs.properties");
1584             let button_label = bundle.GetStringFromName("OK");
1586             let buttons = [{
1587               label: button_label,
1588               accessKey: 'O',
1589               popup: null,
1590               callback:
1591                 function() {
1592                   m_tb_prefs.setIntPref("extensions.torbutton.maximize_warnings_remaining",
1593                   m_tb_prefs.getIntPref("extensions.torbutton.maximize_warnings_remaining") - 1);
1594                 }
1595             }];
1597             let priority = box.PRIORITY_WARNING_LOW;
1598             let message =
1599               torbutton_get_property_string("torbutton.maximize_warning");
1601             box.appendNotification(message, kNotificationName, null,
1602                                    priority, buttons);
1603             return;
1604           }
1605         }
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
1614       // #14229 lands.
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);
1633     }
1634   }, // onStateChange
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();
1651   });
1653 })();
1654 //vim:set ts=4