Bug 1223573 - Part 7. Add support in bootstrap.js for starting Loop and displaying the button. Also get all tests passing again. r=mikedeboer
authorMark Banner <standard8@mozilla.com>
Sun, 29 Nov 2015 17:08:35 +0000
changeset 274524 722d3a01dcfcb37f57e9e2a299bef5c0c1a528c9
parent 274523 117061bb2218b158627a4182742c0cb0598edd3e
child 274525 ab436aa1b1edb17da9c004063b1101efb97e0fcc
push id29733
push usercbook@mozilla.com
push dateMon, 30 Nov 2015 12:16:19 +0000
treeherdermozilla-central@2d385f1302a2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmikedeboer
bugs1223573
milestone45.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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.