[tor-commits] [torbutton/master] Bug 18913: about:tor should not have chrome privileges

gk at torproject.org gk at torproject.org
Wed Jul 5 15:38:11 UTC 2017


commit 32f9cf56c89ccd2e24924975dc5515e4198d28c3
Author: Kathy Brade <brade at pearlcrescent.com>
Date:   Sat Jun 24 08:52:19 2017 -0400

    Bug 18913: about:tor should not have chrome privileges
    
    Rearchitect our implementation so that about:tor pages are always
    loaded in a content process. This also fixes:
      Bug 22535: Searching brings me to duckduckgo but my query is discarded.
      Bug 21948: Going back to about:tor page gives "Address isn't valid" error.
    
    Most of the code that initializes and updates about:tor content has
    been moved to a content script. When necessary, IPC is used to pass
    data from the chrome process to the content script.
    
    Removed old, no-longer-used m_tb_orig_BrowserOnAboutPageLoad variable
    from torbutton.js.
    
    Also, update the about:tor newChannel() implementation to accept an
    nsILoadInfo parameter.
---
 src/chrome.manifest                             |   3 -
 src/chrome/content/aboutTor/aboutTor-content.js | 328 ++++++++++++++++++++++++
 src/chrome/content/aboutTor/aboutTor.xhtml      | 143 +----------
 src/chrome/content/torbutton.js                 | 257 ++++++-------------
 src/chrome/skin/aboutTor.css                    |  11 +-
 src/components/aboutTor.js                      |  21 +-
 src/components/startup-observer.js              |   7 +-
 7 files changed, 441 insertions(+), 329 deletions(-)

diff --git a/src/chrome.manifest b/src/chrome.manifest
index 75bef4e..272401b 100644
--- a/src/chrome.manifest
+++ b/src/chrome.manifest
@@ -152,9 +152,6 @@ contract @torproject.org/startup-observer;1 {06322def-6fde-4c06-aef6-47ae8e79962
 component {e6204253-b690-4159-bfe8-d4eedab6b3be} components/cookie-jar-selector.js
 contract @torproject.org/cookie-jar-selector;1 {e6204253-b690-4159-bfe8-d4eedab6b3be}
 
-component {84d47da6-79c3-4661-aa9f-8049476f7bf5} components/aboutTor.js
-contract @mozilla.org/network/protocol/about;1?what=tor {84d47da6-79c3-4661-aa9f-8049476f7bf5}
-
 component {5d57312b-5d8c-4169-b4af-e80d6a28a72e} components/torCheckService.js
 contract @torproject.org/torbutton-torCheckService;1 {5d57312b-5d8c-4169-b4af-e80d6a28a72e}
 
diff --git a/src/chrome/content/aboutTor/aboutTor-content.js b/src/chrome/content/aboutTor/aboutTor-content.js
new file mode 100644
index 0000000..ec515bb
--- /dev/null
+++ b/src/chrome/content/aboutTor/aboutTor-content.js
@@ -0,0 +1,328 @@
+/*************************************************************************
+ * Copyright (c) 2017, The Tor Project, Inc.
+ * See LICENSE for licensing information.
+ *
+ * vim: set sw=2 sts=2 ts=8 et syntax=javascript:
+ *
+ * about:tor content script
+ *************************************************************************/
+
+/*
+ * The following about:tor IPC messages are exchanged by this code and
+ * the code in torbutton.js:
+ *   AboutTor:Loaded          page loaded            content -> chrome
+ *   AboutTor:ChromeData      privileged data        chrome -> content
+ *   AboutTor:GetToolbarData  request toolbar info   content -> chrome
+ *   AboutTor:ToolbarData     toolbar info           chrome -> content
+ */
+
+var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+
+Cu.import("resource://gre/modules/Services.jsm");
+let { bindPrefAndInit } = Cu.import("resource://torbutton/modules/utils.js", {});
+
+
+var AboutTorListener = {
+  kAboutTorMessages: [ "AboutTor:ChromeData", "AboutTor:ToolbarData" ],
+
+  get isAboutTor() {
+    return content.document.documentURI.toLowerCase() == "about:tor";
+  },
+
+  init: function(aChromeGlobal) {
+    aChromeGlobal.addEventListener("AboutTorLoad", this, false, true);
+  },
+
+  handleEvent: function(aEvent) {
+    if (!this.isAboutTor)
+      return;
+
+    switch (aEvent.type) {
+      case "AboutTorLoad":
+        this.onPageLoad();
+        break;
+      case "pagehide":
+        this.onPageHide();
+        break;
+      case "resize":
+        sendAsyncMessage("AboutTor:GetToolbarData");
+        break;
+    }
+  },
+
+  receiveMessage: function(aMessage) {
+    if (!this.isAboutTor)
+      return;
+
+    switch (aMessage.name) {
+      case "AboutTor:ChromeData":
+        this.onChromeDataUpdate(aMessage.data);
+        break;
+      case "AboutTor:ToolbarData":
+        this.onToolbarDataUpdate(aMessage.data);
+        break;
+    }
+  },
+
+  onPageLoad: function() {
+    // Arrange to update localized text and links.
+    bindPrefAndInit("general.useragent.locale", aNewVal => {
+      this.onLocaleChange(aNewVal);
+    });
+
+    // Add message and event listeners.
+    this.kAboutTorMessages.forEach(aMsg => addMessageListener(aMsg, this));
+    addMessageListener("AboutTor:ChromeData", this);
+    addEventListener("pagehide", this, false);
+    addEventListener("resize", this, false);
+
+    sendAsyncMessage("AboutTor:Loaded");
+  },
+
+  onPageHide: function() {
+    removeEventListener("resize", this, false);
+    removeEventListener("pagehide", this, false);
+    this.kAboutTorMessages.forEach(aMsg => removeMessageListener(aMsg, this));
+  },
+
+  onChromeDataUpdate: function(aData) {
+    let body = content.document.body;
+
+    // Update status: tor on/off, update needed, Tor Browser manual shown.
+    if (aData.torOn)
+      body.setAttribute("toron", "yes");
+    else
+      body.removeAttribute("toron");
+
+    if (aData.updateNeeded)
+      body.setAttribute("torNeedsUpdate", "yes");
+    else
+      body.removeAttribute("torNeedsUpdate");
+
+    if (aData.showManual)
+      body.setAttribute("showmanual", "yes");
+    else
+      body.removeAttribute("showmanual");
+
+    // Setting body.initialized="yes" displays the body, which must be done
+    // at this point because our remaining initialization depends on elements
+    // being visible so that their size and position are accurate.
+    body.setAttribute("initialized", "yes");
+
+    let containerName = "torstatus-" + (aData.torOn ? "on" : "off") +
+                        "-container";
+    this.adjustFontSizes(containerName);
+
+    this.onToolbarDataUpdate(aData);
+  },
+
+  onToolbarDataUpdate: function(aData) {
+    this.adjustArrow(aData.toolbarButtonXPos);
+  },
+
+  onLocaleChange: function(aLocale) {
+    this.insertPropertyStrings();
+
+    // Set Tor Browser manual link.
+    content.document.getElementById("manualLink").href =
+                            "https://tb-manual.torproject.org/" + aLocale;
+
+    // Insert "Test Tor Network Settings" url.
+    let elem = content.document.getElementById("testTorSettings");
+    if (elem) {
+      let url = Services.prefs.getCharPref(
+                      "extensions.torbutton.test_url_interactive");
+      elem.href = url.replace(/__LANG__/g, aLocale.replace(/-/g, '_'));
+    }
+
+    // Display the Tor Browser product name and version.
+    try {
+      const kBrandBundle = "chrome://branding/locale/brand.properties";
+      let brandBundle = Cc["@mozilla.org/intl/stringbundle;1"]
+                          .getService(Ci.nsIStringBundleService)
+                          .createBundle(kBrandBundle);
+      let productName = brandBundle.GetStringFromName("brandFullName");
+      let tbbVersion = Services.prefs.getCharPref("torbrowser.version");
+      elem = content.document.getElementById("torstatus-version");
+
+      while (elem.firstChild)
+        elem.removeChild(elem.firstChild);
+      elem.appendChild(content.document.createTextNode(productName + '\n'
+                       + tbbVersion));
+    } catch (e) {}
+  },
+
+  insertPropertyStrings: function() {
+    try {
+      let kPropertiesURL = "chrome://torbutton/locale/aboutTor.properties";
+
+      let stringBundle = Services.strings.createBundle(kPropertiesURL);
+      let s1 = stringBundle.GetStringFromName("aboutTor.searchDDG.privacy.link");
+      let s2 = stringBundle.GetStringFromName("aboutTor.searchDDG.search.link");
+      let result = stringBundle.formatStringFromName(
+                                  "aboutTor.searchDDG.privacy", [s1, s2], 2);
+      if (result) {
+        let elem = content.document.getElementById("searchProviderInfo");
+        if (elem)
+          elem.innerHTML = result;
+      }
+    } catch(e) {}
+  },
+
+  // Ensure that text in top area does not overlap the tor on/off (onion) image.
+  // This is done by reducing the font sizes as necessary.
+  adjustFontSizes: function(aContainerName)
+  {
+    let imgElem = content.document.getElementById("torstatus-image");
+    let containerElem = content.document.getElementById(aContainerName);
+    if (!imgElem || !containerElem)
+      return;
+
+    try
+    {
+      let imgRect = imgElem.getBoundingClientRect();
+
+      for (let textElem = containerElem.firstChild; textElem;
+           textElem = textElem.nextSibling)
+      {
+        if ((textElem.nodeType != textElem.ELEMENT_NODE) ||
+            (textElem.nodeName.toLowerCase() == "br"))
+        {
+          continue;
+        }
+
+        let textRect = textElem.getBoundingClientRect();
+        if (0 == textRect.width)
+          continue;
+
+        // Reduce font to 90% of previous size, repeating the process up to 7
+        // times.  This allows for a maximum reduction to just less than 50% of
+        // the original size.
+        let maxTries = 7;
+        while ((textRect.left < imgRect.right) && (--maxTries >= 0))
+        {
+          let style = content.document.defaultView
+                             .getComputedStyle(textElem, null);
+          let fontSize = parseFloat(style.getPropertyValue("font-size"));
+          textElem.style.fontSize = (fontSize * 0.9) + "px";
+          textRect = textElem.getBoundingClientRect();
+        }
+      }
+
+    } catch (e) {}
+  },
+
+  adjustArrow: function(aToolbarButtonXPos)
+  {
+    let win = content;
+    let doc = content.document;
+    let textElem = doc.getElementById("updatePrompt");
+    let arrowHeadDiv = doc.getElementById("toolbarIconArrowHead");
+    let vertExtDiv = doc.getElementById("toolbarIconArrowVertExtension");
+    let bendDiv = doc.getElementById("toolbarIconArrowBend");
+    let horzExtDiv = doc.getElementById("toolbarIconArrowHorzExtension");
+    if (!textElem || !arrowHeadDiv || !vertExtDiv || !bendDiv || !horzExtDiv)
+      return;
+
+    let arrowTailElems = [ vertExtDiv, bendDiv, horzExtDiv ];
+    if (!aToolbarButtonXPos || isNaN(aToolbarButtonXPos) ||
+        (aToolbarButtonXPos < 0))
+    {
+      arrowHeadDiv.style.display = "none";
+      for (let elem of arrowTailElems)
+        elem.style.display = "none";
+      return;
+    }
+
+    const kArrowMargin = 6;          // Horizontal margin between line and text.
+    const kArrowHeadExtraWidth = 9;  // Horizontal margin to the line.
+    const kArrowLineThickness = 11;
+    const kBendWidth = 22;
+    const kBendHeight = 22;
+
+    try {
+      // Compensate for any content zoom that may be in effect on about:tor.
+      // Because window.devicePixelRatio always returns 1.0 for non-Chrome
+      // windows (see bug 13875), we use screenPixelsPerCSSPixel for the
+      // content window.
+      let pixRatio = content.QueryInterface(Ci.nsIInterfaceRequestor)
+                             .getInterface(Ci.nsIDOMWindowUtils)
+                             .screenPixelsPerCSSPixel;
+      let tbXpos = Math.round(aToolbarButtonXPos / pixRatio);
+
+      arrowHeadDiv.style.display = "block";  // Must be visible to get offsetWidth.
+      let arrowHalfWidth = Math.round(arrowHeadDiv.offsetWidth / 2);
+      let leftAnchor = textElem.offsetLeft - kArrowMargin
+                        - kBendWidth + Math.round(kArrowLineThickness / 2);
+      let rightAnchor = textElem.offsetLeft + textElem.offsetWidth
+                        + kArrowMargin + arrowHalfWidth;
+
+      let isArrowOnLeft = (tbXpos < leftAnchor);
+      let isArrowOnRight = (tbXpos > rightAnchor) &&
+                           (tbXpos < (win.innerWidth - arrowHalfWidth));
+      let isArrowInMiddle = (tbXpos >= leftAnchor) && (tbXpos <= rightAnchor);
+
+      if (isArrowOnLeft || isArrowOnRight || isArrowInMiddle)
+      {
+        // Position the arrow head.
+        let arrowHeadLeft = tbXpos - arrowHalfWidth;
+        arrowHeadDiv.style.left = arrowHeadLeft + "px";
+        if (isArrowOnLeft || isArrowOnRight)
+        {
+          let horzExtBottom = textElem.offsetTop +
+                Math.round((textElem.offsetHeight + kArrowLineThickness) / 2);
+
+          // Position the vertical (extended) line.
+          let arrowHeadBottom = arrowHeadDiv.offsetTop +
+                                arrowHeadDiv.offsetHeight;
+          vertExtDiv.style.top = arrowHeadBottom + "px";
+          vertExtDiv.style.left = (arrowHeadLeft + kArrowHeadExtraWidth) + "px";
+          let ht = horzExtBottom - kBendHeight - arrowHeadBottom;
+          vertExtDiv.style.height = ht + "px";
+
+          // Position the bend (elbow).
+          bendDiv.style.top = (horzExtBottom - kBendHeight) + "px";
+          let bendDivLeft;
+          if (isArrowOnLeft)
+          {
+            bendDiv.setAttribute("pos", "left");
+            bendDivLeft = arrowHeadLeft + kArrowHeadExtraWidth;
+          }
+          else if (isArrowOnRight)
+          {
+            bendDiv.setAttribute("pos", "right");
+            bendDivLeft = arrowHeadLeft + kArrowHeadExtraWidth
+                          + kArrowLineThickness - kBendWidth;
+          }
+          bendDiv.style.left = bendDivLeft + "px";
+
+          // Position the horizontal (extended) line.
+          horzExtDiv.style.top = (horzExtBottom - kArrowLineThickness) + "px";
+          let horzExtLeft, w;
+          if (isArrowOnLeft)
+          {
+            horzExtLeft = bendDivLeft + kBendWidth;
+            w = (textElem.offsetLeft - horzExtLeft - kArrowMargin);
+          }
+          else
+          {
+            horzExtLeft = rightAnchor - arrowHalfWidth;
+            w = tbXpos - arrowHalfWidth - horzExtLeft;
+          }
+          horzExtDiv.style.left = horzExtLeft + "px";
+          horzExtDiv.style.width = w + "px";
+        }
+      }
+
+      let headDisplay = (isArrowOnLeft || isArrowInMiddle || isArrowOnRight)
+                          ? "block" : "none";
+      arrowHeadDiv.style.display = headDisplay;
+      let tailDisplay = (isArrowOnLeft || isArrowOnRight) ? "block" : "none";
+      for (let elem of arrowTailElems)
+        elem.style.display = tailDisplay;
+    } catch (e) {}
+  }
+};
+
+AboutTorListener.init(this);
diff --git a/src/chrome/content/aboutTor/aboutTor.xhtml b/src/chrome/content/aboutTor/aboutTor.xhtml
index e4050fc..7ae4b8b 100644
--- a/src/chrome/content/aboutTor/aboutTor.xhtml
+++ b/src/chrome/content/aboutTor/aboutTor.xhtml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!--
-   - Copyright (c) 2016, The Tor Project, Inc.
+   - Copyright (c) 2017, The Tor Project, Inc.
    - See LICENSE for licensing information.
    - vim: set sw=2 sts=2 ts=8 et syntax=xml:
   -->
@@ -20,154 +20,17 @@
 <head>
   <title>&aboutTor.title;</title>
   <link rel="stylesheet" type="text/css" media="all"
-        href="chrome://torbutton/skin/aboutTor.css"/>
+        href="resource://torbutton/chrome/skin/aboutTor.css"/>
 <script type="text/javascript;version=1.7">
  <![CDATA[
-Components.utils.import("resource://gre/modules/Services.jsm");
-
-function onLoad()
-{
-  insertPropertyStrings();
-
-  let locale = Services.prefs.getCharPref("general.useragent.locale");
-  document.getElementById("manualLink").href = "https://tb-manual.torproject.org/" + locale;
-
-  document.addEventListener("AboutTorAdjustArrow", function() {
-    adjustToolbarIconArrow();
-  }, false);
-
-  window.setTimeout( function() {
-    adjustToolbarIconArrow();
-  }, 0);
-}
-
-function adjustToolbarIconArrow()
-{
-  let textElem = document.getElementById("updatePrompt");
-  let arrowHeadDiv = document.getElementById("toolbarIconArrowHead");
-  let vertExtDiv = document.getElementById("toolbarIconArrowVertExtension");
-  let bendDiv = document.getElementById("toolbarIconArrowBend");
-  let horzExtDiv = document.getElementById("toolbarIconArrowHorzExtension");
-  if (!textElem || !arrowHeadDiv || !vertExtDiv || !bendDiv || !horzExtDiv)
-    return;
-
-  let arrowTailElems = [ vertExtDiv, bendDiv, horzExtDiv ];
-  let tbXpos;
-  if (document.body.hasAttribute("torbutton-xpos"))
-    tbXpos = parseInt(document.body.getAttribute("torbutton-xpos"), 10);
-
-  if (!tbXpos || isNaN(tbXpos) || (tbXpos < 0))
-  {
-    arrowHeadDiv.style.display = "none";
-    for (let elem of arrowTailElems)
-      elem.style.display = "none";
-    return;
-  }
-
-  const kArrowMargin = 6;          // Horizontal margin between line and text.
-  const kArrowHeadExtraWidth = 9;  // Horizontal margin to the line.
-  const kArrowLineThickness = 11;
-  const kBendWidth = 22;
-  const kBendHeight = 22;
-
-  arrowHeadDiv.style.display = "block";  // Must be visible to get offsetWidth.
-  let arrowHalfWidth = Math.round(arrowHeadDiv.offsetWidth / 2);
-  let leftAnchor = textElem.offsetLeft - kArrowMargin
-                    - kBendWidth + Math.round(kArrowLineThickness / 2);
-  let rightAnchor = textElem.offsetLeft + textElem.offsetWidth
-                    + kArrowMargin + arrowHalfWidth;
-
-  let isArrowOnLeft = (tbXpos < leftAnchor);
-  let isArrowOnRight = (tbXpos > rightAnchor) &&
-                       (tbXpos < (window.innerWidth - arrowHalfWidth));
-  let isArrowInMiddle = (tbXpos >= leftAnchor) && (tbXpos <= rightAnchor);
-
-  if (isArrowOnLeft || isArrowOnRight || isArrowInMiddle)
-  {
-    // Position the arrow head.
-    let arrowHeadLeft = tbXpos - arrowHalfWidth;
-    arrowHeadDiv.style.left = arrowHeadLeft + "px";
-    if (isArrowOnLeft || isArrowOnRight)
-    {
-      let horzExtBottom = textElem.offsetTop +
-               Math.round((textElem.offsetHeight + kArrowLineThickness) / 2);
-
-      // Position the vertical (extended) line.
-      let arrowHeadBottom = arrowHeadDiv.offsetTop + arrowHeadDiv.offsetHeight;
-      vertExtDiv.style.top = arrowHeadBottom + "px";
-      vertExtDiv.style.left = (arrowHeadLeft + kArrowHeadExtraWidth) + "px";
-      let ht = horzExtBottom - kBendHeight - arrowHeadBottom;
-      vertExtDiv.style.height = ht + "px";
-
-      // Position the bend (elbow).
-      bendDiv.style.top = (horzExtBottom - kBendHeight) + "px";
-      let bendDivLeft;
-      if (isArrowOnLeft)
-      {
-        bendDiv.setAttribute("pos", "left");
-        bendDivLeft = arrowHeadLeft + kArrowHeadExtraWidth;
-      }
-      else if (isArrowOnRight)
-      {
-        bendDiv.setAttribute("pos", "right");
-        bendDivLeft = arrowHeadLeft + kArrowHeadExtraWidth
-                      + kArrowLineThickness - kBendWidth;
-      }
-      bendDiv.style.left = bendDivLeft + "px";
-
-      // Position the horizontal (extended) line.
-      horzExtDiv.style.top = (horzExtBottom - kArrowLineThickness) + "px";
-      let horzExtLeft, w;
-      if (isArrowOnLeft)
-      {
-        horzExtLeft = bendDivLeft + kBendWidth;
-        w = (textElem.offsetLeft - horzExtLeft - kArrowMargin);
-      }
-      else
-      {
-        horzExtLeft = rightAnchor - arrowHalfWidth;
-        w = tbXpos - arrowHalfWidth - horzExtLeft;
-      }
-      horzExtDiv.style.left = horzExtLeft + "px";
-      horzExtDiv.style.width = w + "px";
-    }
-  }
-
-  let headDisplay = (isArrowOnLeft || isArrowInMiddle || isArrowOnRight)
-                      ? "block" : "none";
-  arrowHeadDiv.style.display = headDisplay;
-  let tailDisplay = (isArrowOnLeft || isArrowOnRight) ? "block" : "none";
-  for (let elem of arrowTailElems)
-    elem.style.display = tailDisplay;
-}
-
-function insertPropertyStrings()
-{
-  try {
-    let kPropertiesURL = "chrome://torbutton/locale/aboutTor.properties";
-
-    let gStringBundle = Services.strings.createBundle(kPropertiesURL);
-    let s1 = gStringBundle.GetStringFromName("aboutTor.searchDDG.privacy.link");
-    let s2 = gStringBundle.GetStringFromName("aboutTor.searchDDG.search.link");
-    let result = gStringBundle.formatStringFromName("aboutTor.searchDDG.privacy",
-                  [s1, s2], 2);
-    if (result) {
-      let elem = document.getElementById("searchProviderInfo");
-      if (elem)
-        elem.innerHTML = result;
-    }
-  } catch(e) {};
-}
-
 window.addEventListener("pageshow", function() {
   let evt = new CustomEvent("AboutTorLoad", { bubbles: true });
   document.dispatchEvent(evt);
 });
-
 ]]>
 </script>
 </head>
-<body dir="&locale.dir;" onload="onLoad();">
+<body dir="&locale.dir;">
 <div id="torstatus" class="top">
   <div id="torstatus-version"/>
   <div id="torstatus-image"/>
diff --git a/src/chrome/content/torbutton.js b/src/chrome/content/torbutton.js
index ae705a3..3999bd4 100644
--- a/src/chrome/content/torbutton.js
+++ b/src/chrome/content/torbutton.js
@@ -40,8 +40,6 @@ var m_tb_control_host = null;        // Set if using TCP.
 var m_tb_control_pass = null;
 var m_tb_control_desc = null;        // For logging.
 
-var m_tb_orig_BrowserOnAboutPageLoad = null;
-
 var m_tb_domWindowUtils = window.QueryInterface(Ci.nsIInterfaceRequestor).
                           getInterface(Ci.nsIDOMWindowUtils);
 
@@ -203,15 +201,24 @@ var torbutton_tor_check_observer = {
         // Update toolbar icon and tooltip.
         torbutton_update_toolbutton();
 
-        // Update all open about:tor pages. If the user does not have an
-        // about:tor page open in the front most window, open one.
-        if (torbutton_update_all_abouttor_pages(undefined, false) < 1) {
-          var wm = Cc["@mozilla.org/appshell/window-mediator;1"]
+        // Update all open about:tor pages.
+        torbutton_abouttor_message_handler.updateAllOpenPages();
+
+        // If the user does not have an about:tor tab open in the front most
+        // window, open one.
+        var wm = Cc["@mozilla.org/appshell/window-mediator;1"]
               .getService(Components.interfaces.nsIWindowMediator);
-          var win = wm.getMostRecentWindow("navigator:browser");
-          if (win == window) {
-            gBrowser.selectedTab = gBrowser.addTab("about:tor");
+        var win = wm.getMostRecentWindow("navigator:browser");
+        if (win == window) {
+          let foundTab = false;
+          let tabBrowser = top.getBrowser();
+          for (let i = 0; !foundTab && (i < tabBrowser.browsers.length); ++i) {
+            let b = tabBrowser.getBrowserAtIndex(i);
+            foundTab = (b.currentURI.spec.toLowerCase() == "about:tor");
           }
+
+          if (!foundTab)
+            gBrowser.selectedTab = gBrowser.addTab("about:tor");
         }
       }
     }
@@ -332,10 +339,10 @@ function torbutton_init() {
         }
     }
 
-    // Add event listener for about:tor page loads.
-    document.addEventListener("AboutTorLoad", function(aEvent) {
-      torbutton_on_abouttor_load(aEvent.target);
-    }, false, true);
+    // Add about:tor IPC message listeners.
+    let aboutTorMessages = [ "AboutTor:Loaded", "AboutTor:GetToolbarData" ];
+    aboutTorMessages.forEach(aMsg => window.messageManager.addMessageListener(
+                                   aMsg, torbutton_abouttor_message_handler));
 
     // XXX: Get rid of the cached asmjs (or IndexedDB) files on disk in case we
     // don't allow things saved to disk. This is an ad-hoc fix to work around
@@ -365,7 +372,7 @@ function torbutton_init() {
 
     // Detect toolbar customization and update arrow on about:tor pages.
     window.addEventListener("aftercustomization", function() {
-      torbutton_update_all_abouttor_pages(undefined, undefined);
+      torbutton_abouttor_message_handler.updateAllOpenPages();
     }, false);
 
     //setting up context menu
@@ -404,9 +411,58 @@ function torbutton_init() {
 
     torbutton_init_user_manual_links();
 
+    // Arrange for our about:tor content script to be loaded in each frame.
+    window.messageManager.loadFrameScript(
+              "chrome://torbutton/content/aboutTor/aboutTor-content.js", true);
+
     torbutton_log(3, 'init completed');
 }
 
+var torbutton_abouttor_message_handler = {
+  // Receive IPC messages from the about:tor content script.
+  receiveMessage: function(aMessage) {
+    switch(aMessage.name) {
+      case "AboutTor:Loaded":
+        torbutton_show_sec_slider_notification();
+        aMessage.target.messageManager.sendAsyncMessage("AboutTor:ChromeData",
+                                                        this.chromeData);
+        break;
+      case "AboutTor:GetToolbarData":
+        aMessage.target.messageManager.sendAsyncMessage("AboutTor:ToolbarData",
+                                                        this.toolbarData);
+        break;
+    }
+  },
+
+  // Send privileged data to all of the about:tor content scripts.
+  updateAllOpenPages: function() {
+    window.messageManager.broadcastAsyncMessage("AboutTor:ChromeData",
+                                                this.chromeData);
+  },
+
+  // The chrome data contains all of the data needed by the about:tor
+  // content process that is only available here (in the chrome process).
+  // It is sent to the content process when an about:tor window is opened
+  // and in response to events such as the browser noticing that an update
+  // is available.
+  get chromeData() {
+    return {
+      torOn: torbutton_tor_check_ok(),
+      updateNeeded: torbutton_update_is_needed(),
+      showManual: torbutton_show_torbrowser_manual(),
+      toolbarButtonXPos: torbutton_get_toolbarbutton_xpos()
+    };
+  },
+
+  // The toolbar data only contains the x coordinate of Torbutton's toolbar
+  // item; it is sent back to the content process as the about:tor window
+  // is resized.
+  get toolbarData() {
+    return {
+      toolbarButtonXPos: torbutton_get_toolbarbutton_xpos()
+    };
+  }
+};
 
 function torbutton_should_prompt_for_language_preference() {
   return torbutton_get_general_useragent_locale().substring(0, 2) != "en" &&
@@ -570,7 +626,7 @@ function torbutton_notify_if_update_needed() {
     setOrClearAttribute(btn, "tbUpdateNeeded", updateNeeded);
 
     // Update all open about:tor pages.
-    torbutton_update_all_abouttor_pages(updateNeeded, undefined);
+    torbutton_abouttor_message_handler.updateAllOpenPages();
 
     // Make the "check for update" menu item bold if an update is needed.
     var item = document.getElementById("torbutton-checkForUpdate");
@@ -597,117 +653,11 @@ function torbutton_check_for_update() {
         prompter.checkForUpdates();
 }
 
-// Pass undefined for a parameter to have this function determine it.
-// Returns a count of open pages that were updated,
-function torbutton_update_all_abouttor_pages(aUpdateNeeded, aTorIsOn) {
-  if (aUpdateNeeded === undefined)
-    aUpdateNeeded = torbutton_update_is_needed();
-  if (aTorIsOn === undefined)
-    aTorIsOn = torbutton_tor_check_ok();
-
-  var count = 0;
-  var tabBrowser = top.getBrowser();
-  var tabs = tabBrowser.mTabs;
-  if (tabs && (tabs.length > 0)) {
-    for (var tab = tabs[0]; tab != null; tab = tab.nextSibling) {
-      try {
-        let doc = tabBrowser.getBrowserForTab(tab).contentDocument;
-        if (torbutton_update_abouttor_doc(doc, aTorIsOn, aUpdateNeeded))
-          ++count;
-      } catch(e) {}
-    }
-  }
-
-  return count;
-}
-
-// Returns true if aDoc is an about:tor page.
-function torbutton_update_abouttor_doc(aDoc, aTorOn, aUpdateNeeded) {
-  var isAboutTor = torbutton_is_abouttor_doc(aDoc);
-  if (isAboutTor) {
-    if (aTorOn)
-      aDoc.body.setAttribute("toron", "yes");
-    else
-      aDoc.body.removeAttribute("toron");
-
-    if (aUpdateNeeded)
-      aDoc.body.setAttribute("torNeedsUpdate", "yes"); 
-    else
-      aDoc.body.removeAttribute("torNeedsUpdate");
-
-    if (torbutton_show_torbrowser_manual())
-      aDoc.body.setAttribute("showmanual", "yes");
-    else
-      aDoc.body.removeAttribute("showmanual");
-
-    // Display product name and TBB version.
-    try {
-      const kBrandBundle = "chrome://branding/locale/brand.properties";
-      let brandBundle = Cc["@mozilla.org/intl/stringbundle;1"]
-                          .getService(Ci.nsIStringBundleService)
-                          .createBundle(kBrandBundle);
-      let productName = brandBundle.GetStringFromName("brandFullName");
-      let tbbVersion = m_tb_prefs.getCharPref("torbrowser.version");
-      let e = aDoc.getElementById("torstatus-version");
-
-      while (e.firstChild)
-        e.removeChild(e.firstChild);
-      e.appendChild(aDoc.createTextNode(productName + '\n' + tbbVersion));
-    } catch (e) {}
-
-    let containerName = "torstatus-" + (aTorOn ? "on" : "off") + "-container";
-    torbutton_adjust_abouttor_fontsizes(aDoc, containerName);
-    torbutton_update_abouttor_arrow(aDoc);
-  }
-
-  return isAboutTor;
-}
-
-// Ensure that text in top area does not overlap the tor on/off (onion) image.
-// This is done by reducing the font sizes as necessary.
-function torbutton_adjust_abouttor_fontsizes(aDoc, aContainerName)
-{
-  let imgElem = aDoc.getElementById("torstatus-image");
-  let containerElem = aDoc.getElementById(aContainerName);
-  if (!imgElem || !containerElem)
-    return;
-
-  try
-  {
-    let imgRect = imgElem.getBoundingClientRect();
-
-    for (let textElem = containerElem.firstChild; textElem;
-         textElem = textElem.nextSibling)
-    {
-      if ((textElem.nodeType != textElem.ELEMENT_NODE) ||
-          (textElem.nodeName.toLowerCase() == "br"))
-      {
-        continue;
-      }
-
-      let textRect = textElem.getBoundingClientRect();
-      if (0 == textRect.width)
-        continue;
-
-      // Reduce font to 90% of previous size, repeating the process up to 7
-      // times.  This allows for a maximum reduction to just less than 50% of
-      // the original size.
-      let maxTries = 7;
-      while ((textRect.left < imgRect.right) && (--maxTries >= 0))
-      {
-        let style = aDoc.defaultView.getComputedStyle(textElem, null);
-        let fontSize = parseFloat(style.getPropertyValue("font-size"));
-        textElem.style.fontSize = (fontSize * 0.9) + "px";
-        textRect = textElem.getBoundingClientRect();
-      }
-    }
-  } catch (e) {}
-}
-
-// Determine X position of torbutton toolbar item and pass it through
-// to the xhtml document by setting a torbutton-xpos attribute on the body.
-// The value that is set takes retina displays and content zoom into account.
-function torbutton_update_abouttor_arrow(aDoc) {
+// Determine X position of Torbutton toolbar item. The value returned
+// accounts for retina but not content zoom.
+// undefined is returned if the value cannot be determined (e.g., if the
+// toolbar item is not on the toolbar).
+function torbutton_get_toolbarbutton_xpos() {
   try {
     let tbXpos = -1;
     let tbItem = torbutton_get_toolbutton();
@@ -721,55 +671,16 @@ function torbutton_update_abouttor_arrow(aDoc) {
     }
 
     if (tbXpos >= 0) {
-      // Convert to device-independent units, compensating for retina display
-      // and content zoom that may be in effect on the about:tor page.
-      // Because window.devicePixelRatio always returns 1.0 for non-Chrome
-      // windows (see bug 13875), we use screenPixelsPerCSSPixel for the
-      // content window.
+      // Convert to device-independent units, compensating for retina display.
       tbXpos *= window.devicePixelRatio;
-      let pixRatio = gBrowser.contentWindow
-                             .QueryInterface(Ci.nsIInterfaceRequestor)
-                             .getInterface(Ci.nsIDOMWindowUtils)
-                             .screenPixelsPerCSSPixel;
-      tbXpos = Math.round(tbXpos / pixRatio);
-      aDoc.body.setAttribute("torbutton-xpos", tbXpos);
-    } else {
-      aDoc.body.removeAttribute("torbutton-xpos");
+      return tbXpos;
     }
-
-    let evt = new Event("AboutTorAdjustArrow");
-    aDoc.dispatchEvent(evt);
   } catch(e) {}
-}
-
-function torbutton_on_abouttor_load(aDoc) {
-  if (torbutton_is_abouttor_doc(aDoc) &&
-      !aDoc.documentElement.hasAttribute("aboutTorLoaded")) {
-    aDoc.documentElement.setAttribute("aboutTorLoaded", true);
-
-    // Show correct Tor on/off and "update needed" status.
-    let torOn = torbutton_tor_check_ok();
-    let needsUpdate = torbutton_update_is_needed();
-    torbutton_update_abouttor_doc(aDoc, torOn, needsUpdate);
-
-    aDoc.defaultView.addEventListener("resize",
-                      function() { torbutton_update_abouttor_arrow(aDoc); },
-                      false);
-
-    // Insert "Test Tor Network Settings" url.
-    let elem = aDoc.getElementById("testTorSettings");
-    if (elem) {
-      let locale = m_tb_prefs.getCharPref("general.useragent.locale");
-      locale = locale.replace(/-/g, '_');
-      let url = m_tb_prefs.getCharPref(
-                      "extensions.torbutton.test_url_interactive");
-      elem.href = url.replace(/__LANG__/g, locale);
-    }
-  }
 
-  if (m_tb_orig_BrowserOnAboutPageLoad)
-    m_tb_orig_BrowserOnAboutPageLoad(aDoc);
+  return undefined;
+}
 
+function torbutton_show_sec_slider_notification() {
   // Show the notification about the new security slider.
   if (m_tb_prefs.
       getBoolPref("extensions.torbutton.show_slider_notification")) {
@@ -797,10 +708,6 @@ function torbutton_on_abouttor_load(aDoc) {
   }
 }
 
-function torbutton_is_abouttor_doc(aDoc) {
-  return (aDoc && /^about:tor$/i.test(aDoc.documentURI.toLowerCase()));
-}
-
 // Bug 1506 P4: Checking for Tor Browser updates is pretty important,
 // probably even as a fallback if we ever do get a working updater.
 function torbutton_do_async_versioncheck() {
@@ -2449,7 +2356,7 @@ function torbutton_init_user_manual_links() {
   let menuitem = document.getElementById("torBrowserUserManual");
   bindPrefAndInit("general.useragent.locale", val => {
     menuitem.hidden = !torbutton_show_torbrowser_manual();
-    torbutton_update_all_abouttor_pages(undefined, undefined);
+    torbutton_abouttor_message_handler.updateAllOpenPages();
   });
 }
 
diff --git a/src/chrome/skin/aboutTor.css b/src/chrome/skin/aboutTor.css
index 3f747a9..c0d2ebc 100644
--- a/src/chrome/skin/aboutTor.css
+++ b/src/chrome/skin/aboutTor.css
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, The Tor Project, Inc.
+ * Copyright (c) 2017, The Tor Project, Inc.
  * See LICENSE for licensing information.
  *
  * vim: set sw=2 sts=2 ts=8 et syntax=css:
@@ -34,6 +34,15 @@ body[toron] {
   background-image: -moz-linear-gradient(top, #ffffff, #ffffff 10%, #d5ffd5 50%, #d5ffd5);
 }
 
+/* Hide the entire document by default to avoid showing the incorrect
+ * Tor on / off status (that info must be retrieved from the chrome
+ * process, which involves IPC when multiprocess mode is enabled). An
+ * initialized attribute will be added as soon as the status is known.
+ */
+body:not([initialized]) {
+  display: none;
+}
+
 #torstatus-version {
   position: fixed;
   top: 6px;
diff --git a/src/components/aboutTor.js b/src/components/aboutTor.js
index 2a3431f..e3ee03d 100644
--- a/src/components/aboutTor.js
+++ b/src/components/aboutTor.js
@@ -1,9 +1,9 @@
 /*************************************************************************
- * Copyright (c) 2013, The Tor Project, Inc.
+ * Copyright (c) 2017, The Tor Project, Inc.
  * See LICENSE for licensing information.
  *
  * vim: set sw=2 sts=2 ts=8 et syntax=javascript:
- * 
+ *
  * about:tor component
  *************************************************************************/
 
@@ -17,9 +17,9 @@ const kAboutTorURL = "chrome://torbutton/content/aboutTor/aboutTor.xhtml";
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
- 
+
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
- 
+
 function AboutTor()
 {
 }
@@ -35,11 +35,12 @@ AboutTor.prototype =
   contractID: kMODULE_CONTRACTID,
 
   // nsIAboutModule implementation:
-  newChannel: function(aURI)
+  newChannel: function(aURI, aLoadInfo)
   {
     let ioSvc = Cc["@mozilla.org/network/io-service;1"]
                   .getService(Ci.nsIIOService);
-    let channel = ioSvc.newChannel(kAboutTorURL, null, null);
+    let uri = ioSvc.newURI(kAboutTorURL, null, null);
+    let channel = ioSvc.newChannelFromURIWithLoadInfo(uri, aLoadInfo);
     channel.originalURI = aURI;
 
     return channel;
@@ -47,9 +48,13 @@ AboutTor.prototype =
 
   getURIFlags: function(aURI)
   {
-    return Ci.nsIAboutModule.ALLOW_SCRIPT;
+    return Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT |
+           Ci.nsIAboutModule.URI_MUST_LOAD_IN_CHILD |
+           Ci.nsIAboutModule.ALLOW_SCRIPT;
   }
 };
 
 
-const NSGetFactory = XPCOMUtils.generateNSGetFactory([AboutTor]);
+let factory = XPCOMUtils._getFactory(AboutTor);
+let reg = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+reg.registerFactory(kMODULE_CID, "", kMODULE_CONTRACTID, factory);
diff --git a/src/components/startup-observer.js b/src/components/startup-observer.js
index 65ca659..dac7aff 100644
--- a/src/components/startup-observer.js
+++ b/src/components/startup-observer.js
@@ -61,12 +61,15 @@ function StartupObserver() {
       this.logger.log(4, "Early proxy change failed. Will try again at profile load. Error: "+e);
     }
 
-    // Arrange for our nsIContentPolicy filter to be loaded in the
-    // default (chrome) process as well as in each content process.
+    // Arrange for our nsIContentPolicy filter and about:tor handler to be
+    // loaded in the default (chrome) process as well as in each content
+    // process.
     let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
                  .getService(Ci.nsIProcessScriptLoader);
     ppmm.loadProcessScript("resource://torbutton/components/content-policy.js",
                            true);
+    ppmm.loadProcessScript("resource://torbutton/components/aboutTor.js",
+                            true);
 }
 
 StartupObserver.prototype = {



More information about the tor-commits mailing list