Bug 1223573 - Part 7. Add support in bootstrap.js for starting Loop and displaying the button. Also get all tests passing again. r=mikedeboer
☠☠ backed out by c95f8e8955b0 ☠ ☠
authorMark Banner <standard8@mozilla.com>
Fri, 27 Nov 2015 18:57:40 +0000
changeset 274463 14251062e347b5109ec8c694e7cc6ef3d976d3c8
parent 274462 081b0af71d6e4fb7d3152a814b972861451a13d3
child 274464 19876a153a009b457900c82f27efbc398ad19413
push id16429
push usermbanner@mozilla.com
push dateFri, 27 Nov 2015 18:58:34 +0000
treeherderfx-team@19876a153a00 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmikedeboer
bugs1223573
milestone45.0a1
Bug 1223573 - Part 7. Add support in bootstrap.js for starting Loop and displaying the button. Also get all tests passing again. r=mikedeboer
browser/components/about/AboutRedirector.cpp
browser/components/customizableui/CustomizableWidgets.jsm
browser/extensions/loop/.eslintrc-gecko
browser/extensions/loop/bootstrap.js
testing/profiles/prefs_general.js
--- a/browser/components/about/AboutRedirector.cpp
+++ b/browser/components/about/AboutRedirector.cpp
@@ -103,22 +103,22 @@ static RedirEntry kRedirMap[] = {
 #endif
   { "accounts", "chrome://browser/content/aboutaccounts/aboutaccounts.xhtml",
     nsIAboutModule::ALLOW_SCRIPT },
   { "customizing", "chrome://browser/content/customizableui/aboutCustomizing.xul",
     nsIAboutModule::ALLOW_SCRIPT },
   {
     "debugging", "chrome://devtools/content/aboutdebugging/aboutdebugging.xhtml",
     nsIAboutModule::ALLOW_SCRIPT },
-  { "loopconversation", "chrome://browser/content/loop/conversation.html",
+  { "loopconversation", "chrome://loop/content/panels/conversation.html",
     nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
     nsIAboutModule::ALLOW_SCRIPT |
     nsIAboutModule::HIDE_FROM_ABOUTABOUT |
     nsIAboutModule::ENABLE_INDEXED_DB },
-  { "looppanel", "chrome://browser/content/loop/panel.html",
+  { "looppanel", "chrome://loop/content/panels/panel.html",
     nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
     nsIAboutModule::ALLOW_SCRIPT |
     nsIAboutModule::HIDE_FROM_ABOUTABOUT |
     nsIAboutModule::ENABLE_INDEXED_DB,
     // Shares an IndexedDB origin with about:loopconversation.
     "loopconversation" },
   { "reader", "chrome://global/content/reader/aboutReader.html",
     nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
--- a/browser/components/customizableui/CustomizableWidgets.jsm
+++ b/browser/components/customizableui/CustomizableWidgets.jsm
@@ -946,52 +946,16 @@ const CustomizableWidgets = [
   }, {
     id: "email-link-button",
     tooltiptext: "email-link-button.tooltiptext3",
     onCommand: function(aEvent) {
       let win = aEvent.view;
       win.MailIntegration.sendLinkForBrowser(win.gBrowser.selectedBrowser)
     }
   }, {
-    id: "loop-button",
-    type: "custom",
-    label: "loop-call-button3.label",
-    tooltiptext: "loop-call-button3.tooltiptext2",
-    privateBrowsingTooltiptext: "loop-call-button3-pb.tooltiptext",
-    defaultArea: CustomizableUI.AREA_NAVBAR,
-    introducedInVersion: 4,
-    onBuild: function(aDocument) {
-      // If we're not supposed to see the button, return zip.
-      if (!Services.prefs.getBoolPref("loop.enabled")) {
-        return null;
-      }
-
-      let isWindowPrivate = PrivateBrowsingUtils.isWindowPrivate(aDocument.defaultView);
-
-      let node = aDocument.createElementNS(kNSXUL, "toolbarbutton");
-      node.setAttribute("id", this.id);
-      node.classList.add("toolbarbutton-1");
-      node.classList.add("chromeclass-toolbar-additional");
-      node.classList.add("badged-button");
-      node.setAttribute("label", CustomizableUI.getLocalizedProperty(this, "label"));
-      if (isWindowPrivate)
-        node.setAttribute("disabled", "true");
-      let tooltiptext = isWindowPrivate ?
-        CustomizableUI.getLocalizedProperty(this, "privateBrowsingTooltiptext",
-          [CustomizableUI.getLocalizedProperty(this, "label")]) :
-        CustomizableUI.getLocalizedProperty(this, "tooltiptext");
-      node.setAttribute("tooltiptext", tooltiptext);
-      node.setAttribute("removable", "true");
-      node.addEventListener("command", function(event) {
-        aDocument.defaultView.LoopUI.togglePanel(event);
-      });
-
-      return node;
-    }
-  }, {
     id: "web-apps-button",
     label: "web-apps-button.label",
     tooltiptext: "web-apps-button.tooltiptext",
     onCommand: function(aEvent) {
       let win = aEvent.target &&
                 aEvent.target.ownerDocument &&
                 aEvent.target.ownerDocument.defaultView;
       if (win && typeof win.BrowserOpenApps == "function") {
--- a/browser/extensions/loop/.eslintrc-gecko
+++ b/browser/extensions/loop/.eslintrc-gecko
@@ -37,16 +37,17 @@
     "LoopAPI": true,
     "LoopCalls": true,
     "loopCrypto": false,
     "LoopRooms": true,
     "LoopRoomsCache": true,
     "MozLoopPushHandler": true,
     "MozLoopService": true,
     "OS": false,
+    "PrivateBrowsingUtils": false,
     "roomsPushNotification": true,
     "Services": false,
     "Social": false,
     "SocialShare": false,
     "Task": false,
     "UITour": false,
     "WebChannel": false,
     "XPCOMUtils": false,
--- a/browser/extensions/loop/bootstrap.js
+++ b/browser/extensions/loop/bootstrap.js
@@ -1,28 +1,40 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
-const { utils: Cu, classes: Cc } = Components;
+const { interfaces: Ci, utils: Cu, classes: Cc } = Components;
+
+const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+const kBrowserSharingNotificationId = "loop-sharing-notification";
+const kPrefBrowserSharingInfoBar = "browserSharing.showInfoBar";
 
 Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+  "resource://gre/modules/PrivateBrowsingUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
+  "resource:///modules/CustomizableUI.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+  "resource://gre/modules/Task.jsm");
+
+/**
+ * This window listener gets loaded into each browser.xul window and is used
+ * to provide the required loop functions for the window.
+ */
 var WindowListener = {
   /**
    * Sets up the chrome integration within browser windows for Loop.
    *
    * @param {Object} window The window to inject the integration into.
    */
   setupBrowserUI: function(window) {
-    const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
-    const kBrowserSharingNotificationId = "loop-sharing-notification";
-    const kPrefBrowserSharingInfoBar = "browserSharing.showInfoBar";
-
     let document = window.document;
     let gBrowser = window.gBrowser;
     let xhrClass = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"];
     let FileReader = window.FileReader;
 
     // the "exported" symbols
     var LoopUI = {
       /**
@@ -249,16 +261,21 @@ var WindowListener = {
         });
       },
 
       /**
        * Triggers the initialization of the loop service.  Called by
        * delayedStartup.
        */
       init: function() {
+        // Cleanup when the window unloads.
+        window.addEventListener("unload", () => {
+          this.uninit();
+        });
+
         // Add observer notifications before the service is initialized
         Services.obs.addObserver(this, "loop-status-changed", false);
 
         // This is a promise for test purposes, but we don't want to be logging
         // expected errors to the console, so we catch them here.
         this.MozLoopService.initialize().catch(ex => {
           if (!ex.message ||
               (!ex.message.contains("not enabled") &&
@@ -592,17 +609,17 @@ var WindowListener = {
         if (!/^https?:/.test(pageURI)) {
           callback();
           return;
         }
 
         this.PlacesUtils.promiseFaviconLinkUrl(pageURI).then(uri => {
           // We XHR the favicon to get a File object, which we can pass to the FileReader
           // object. The FileReader turns the File object into a data-uri.
-          let xhr = new XMLHttpRequest();
+          let xhr = xhrClass.createInstance(Ci.nsIXMLHttpRequest);
           xhr.open("get", uri.spec, true);
           xhr.responseType = "blob";
           xhr.overrideMimeType("image/x-icon");
           xhr.onload = () => {
             if (xhr.status != 200) {
               callback(new Error("Invalid status code received for favicon XHR: " + xhr.status));
               return;
             }
@@ -621,38 +638,208 @@ var WindowListener = {
     };
 
     XPCOMUtils.defineLazyModuleGetter(LoopUI, "hookWindowCloseForPanelClose", "resource://gre/modules/MozSocialAPI.jsm");
     XPCOMUtils.defineLazyModuleGetter(LoopUI, "LoopAPI", "chrome://loop/content/modules/MozLoopAPI.jsm");
     XPCOMUtils.defineLazyModuleGetter(LoopUI, "LoopRooms", "chrome://loop/content/modules/LoopRooms.jsm");
     XPCOMUtils.defineLazyModuleGetter(LoopUI, "MozLoopService", "chrome://loop/content/modules/MozLoopService.jsm");
     XPCOMUtils.defineLazyModuleGetter(LoopUI, "PanelFrame", "resource:///modules/PanelFrame.jsm");
     XPCOMUtils.defineLazyModuleGetter(LoopUI, "PlacesUtils", "resource://gre/modules/PlacesUtils.jsm");
+
+    LoopUI.init();
+    window.LoopUI = LoopUI;
+  },
+
+  tearDownBrowserUI: function(window) {
+    let document = window.document;
+
+    // Take any steps to remove UI or anything from the browser window
+    // document.getElementById() etc. will work here
+    // XXX Add in tear-down of the panel.
+  },
+
+  // nsIWindowMediatorListener functions.
+  onOpenWindow: function(xulWindow) {
+    // A new window has opened.
+    let domWindow = xulWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+                             .getInterface(Ci.nsIDOMWindow);
+
+    // Wait for it to finish loading.
+    domWindow.addEventListener("load", function listener() {
+      domWindow.removeEventListener("load", listener, false);
+
+      // If this is a browser window then setup its UI.
+      if (domWindow.document.documentElement.getAttribute("windowtype") == "navigator:browser") {
+        WindowListener.setupBrowserUI(domWindow);
+      }
+    }, false);
+  },
+
+  onCloseWindow: function(xulWindow) {
+  },
+
+  onWindowTitleChange: function(xulWindow, newTitle) {
   }
 };
 
 /**
+ * Creates the loop button on the toolbar. Due to loop being a system-addon
+ * CustomizableUI already has a placement location for the button, so that
+ * we can be on the toolbar.
+ */
+function createLoopButton() {
+  CustomizableUI.createWidget({
+    id: "loop-button",
+    type: "custom",
+    label: "loop-call-button3.label",
+    tooltiptext: "loop-call-button3.tooltiptext2",
+    privateBrowsingTooltiptext: "loop-call-button3-pb.tooltiptext",
+    defaultArea: CustomizableUI.AREA_NAVBAR,
+    removable: true,
+    onBuild: function(aDocument) {
+      // If we're not supposed to see the button, return zip.
+      if (!Services.prefs.getBoolPref("loop.enabled")) {
+        return null;
+      }
+
+      let isWindowPrivate = PrivateBrowsingUtils.isWindowPrivate(aDocument.defaultView);
+
+      let node = aDocument.createElementNS(kNSXUL, "toolbarbutton");
+      node.setAttribute("id", this.id);
+      node.classList.add("toolbarbutton-1");
+      node.classList.add("chromeclass-toolbar-additional");
+      node.classList.add("badged-button");
+      node.setAttribute("label", CustomizableUI.getLocalizedProperty(this, "label"));
+      if (isWindowPrivate) {
+        node.setAttribute("disabled", "true");
+      }
+      let tooltiptext = isWindowPrivate ?
+        CustomizableUI.getLocalizedProperty(this, "privateBrowsingTooltiptext",
+          [CustomizableUI.getLocalizedProperty(this, "label")]) :
+        CustomizableUI.getLocalizedProperty(this, "tooltiptext");
+      node.setAttribute("tooltiptext", tooltiptext);
+      node.setAttribute("removable", "true");
+      node.addEventListener("command", function(event) {
+        aDocument.defaultView.LoopUI.togglePanel(event);
+      });
+
+      return node;
+    }
+  });
+}
+
+/**
  * Loads the default preferences from the prefs file. This loads the preferences
  * into the default branch, so they don't appear as user preferences.
  */
 function loadDefaultPrefs() {
   var branch = Services.prefs.getDefaultBranch("");
   Services.scriptloader.loadSubScript("chrome://loop/content/preferences/prefs.js", {
     pref: (key, val) => {
       switch (typeof val) {
         case "boolean":
           branch.setBoolPref(key, val);
           break;
         case "number":
           branch.setIntPref(key, val);
           break;
         case "string":
           branch.setCharPref(key, val);
-         break;
+          break;
       }
     }
   });
 }
 
+/**
+ * Called when the add-on is started, e.g. when installed or when Firefox starts.
+ */
+function startup() {
+  loadDefaultPrefs();
 
-function startup(data, reason) {
-  loadDefaultPrefs();
+  createLoopButton();
+
+  // Attach to hidden window (for OS X).
+  try {
+    WindowListener.setupBrowserUI(Services.appShell.hiddenDOMWindow);
+  } catch (ex) {
+    // Hidden window didn't exist, so wait until startup is done.
+    let topic = "browser-delayed-startup-finished";
+    Services.obs.addObserver(function observer() {
+      Services.obs.removeObserver(observer, topic);
+      WindowListener.setupBrowserUI(Services.appShell.hiddenDOMWindow);
+    }, topic, false);
+  }
+
+  // Attach to existing browser windows, for modifying UI.
+  let wm = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);
+  let windows = wm.getEnumerator("navigator:browser");
+  while (windows.hasMoreElements()) {
+    let domWindow = windows.getNext().QueryInterface(Ci.nsIDOMWindow);
+    WindowListener.setupBrowserUI(domWindow);
+  }
+
+  // Wait for any new browser windows to open.
+  wm.addListener(WindowListener);
+
+  // Load our stylesheets.
+  let styleSheetService = Cc["@mozilla.org/content/style-sheet-service;1"]
+    .getService(Components.interfaces.nsIStyleSheetService);
+  let sheets = ["chrome://loop-shared/skin/loop.css",
+                "chrome://loop/skin/platform.css"];
+  for (let sheet of sheets) {
+    let styleSheetURI = Services.io.newURI(sheet, null, null);
+    styleSheetService.loadAndRegisterSheet(styleSheetURI,
+                                           styleSheetService.AUTHOR_SHEET);
+  }
 }
+
+/**
+ * Called when the add-on is shutting down, could be for re-installation
+ * or just uninstall.
+ */
+function shutdown() {
+  // Close any open chat windows
+  Cu.import("resource:///modules/Chat.jsm");
+  let isLoopURL = ({ src }) => /^about:loopconversation#/.test(src);
+  [...Chat.chatboxes].filter(isLoopURL).forEach(chatbox => {
+    chatbox.content.contentWindow.close();
+  });
+
+  // Detach from hidden window (for OS X).
+  WindowListener.tearDownBrowserUI(Services.appShell.hiddenDOMWindow);
+
+  // Detach from browser windows.
+  let wm = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);
+  let windows = wm.getEnumerator("navigator:browser");
+  while (windows.hasMoreElements()) {
+    let domWindow = windows.getNext().QueryInterface(Ci.nsIDOMWindow);
+    WindowListener.tearDownBrowserUI(domWindow);
+  }
+
+  // Stop waiting for browser windows to open.
+  wm.removeListener(WindowListener);
+
+  CustomizableUI.destroyWidget("loop-button");
+
+  // Unload stylesheets.
+  let styleSheetService = Cc["@mozilla.org/content/style-sheet-service;1"]
+    .getService(Components.interfaces.nsIStyleSheetService);
+  let sheets = ["chrome://loop/content/addon/css/loop.css",
+                "chrome://loop/skin/platform.css"];
+  for (let sheet of sheets) {
+    let styleSheetURI = Services.io.newURI(sheet, null, null);
+    if (styleSheetService.sheetRegistered(styleSheetURI,
+                                          styleSheetService.USER_SHEET)) {
+      styleSheetService.unregisterSheet(styleSheetURI,
+                                        styleSheetService.USER_SHEET);
+    }
+  }
+
+  // Unload modules.
+  Cu.unload("chrome://loop/content/modules/MozLoopAPI.jsm");
+  Cu.unload("chrome://loop/content/modules/LoopRooms.jsm");
+  Cu.unload("chrome://loop/content/modules/MozLoopService.jsm");
+}
+
+function install() {}
+
+function uninstall() {}
--- a/testing/profiles/prefs_general.js
+++ b/testing/profiles/prefs_general.js
@@ -281,17 +281,17 @@ user_pref("dom.apps.customization.enable
 // Don't fetch or send directory tiles data from real servers
 user_pref("browser.newtabpage.directory.source", 'data:application/json,{"testing":1}');
 user_pref("browser.newtabpage.directory.ping", "");
 
 // Enable Loop
 user_pref("loop.debug.loglevel", "All");
 user_pref("loop.enabled", true);
 user_pref("loop.throttled", false);
-user_pref("loop.server", "http://%(server)s/browser/browser/components/loop/test/mochitest/loop_fxa.sjs?");
+user_pref("loop.server", "http://%(server)s/browser/browser/extensions/loop/test/mochitest/loop_fxa.sjs?");
 user_pref("loop.CSP","default-src 'self' about: file: chrome: data: wss://* http://* https://*");
 
 // Ensure UITour won't hit the network
 user_pref("browser.uitour.pinnedTabUrl", "http://%(server)s/uitour-dummy/pinnedTab");
 user_pref("browser.uitour.url", "http://%(server)s/uitour-dummy/tour");
 
 // Tell the search service we are running in the US.  This also has the desired
 // side-effect of preventing our geoip lookup.