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