Bug 1257154 - Switch to getting DOM title updates via a frame script to work better with e10s. r=mikedeboer, a=ritu
authorMark Banner <standard8@mozilla.com>
Fri, 01 Apr 2016 13:54:08 +0100
changeset 310533 4f524a974b08902305ece5c986806eeff8471eb0
parent 310532 17af4819254951af648ac7d346dccba76a384c97
child 310534 39fe246bce0f76238e7de110ad607e0019ce0550
push id9391
push usercbook@mozilla.com
push dateTue, 12 Apr 2016 09:33:44 +0000
treeherdermozilla-aurora@4f524a974b08 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmikedeboer, ritu
bugs1257154
milestone47.0a2
Bug 1257154 - Switch to getting DOM title updates via a frame script to work better with e10s. r=mikedeboer, a=ritu
browser/extensions/loop/bootstrap.js
browser/extensions/loop/chrome/content/modules/tabFrame.js
browser/extensions/loop/chrome/test/mochitest/browser.ini
browser/extensions/loop/chrome/test/mochitest/browser_sharingTitleListeners.js
--- a/browser/extensions/loop/bootstrap.js
+++ b/browser/extensions/loop/bootstrap.js
@@ -8,16 +8,19 @@
 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 CURSOR_MIN_DELTA = 3;
 const CURSOR_MIN_INTERVAL = 100;
 const CURSOR_CLICK_DELAY = 1000;
+// Due to bug 1051238 frame scripts are cached forever, so we can't update them
+// as a restartless add-on. The Math.random() is the work around for this.
+const FRAME_SCRIPT = "chrome://loop/content/modules/tabFrame.js?" + Math.random();
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/AppConstants.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
@@ -52,16 +55,17 @@ var WindowListener = {
    */
   setupBrowserUI: function(window) {
     let document = window.document;
     let gBrowser = window.gBrowser;
     let xhrClass = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"];
     let FileReader = window.FileReader;
     let menuItem = null;
     let isSlideshowOpen = false;
+    let titleChangedListener = null;
 
     // the "exported" symbols
     var LoopUI = {
       /**
        * @var {XULWidgetSingleWrapper} toolbarButton Getter for the Loop toolbarbutton
        *                                             instance for this window. This should
        *                                             not be used in the hidden window.
        */
@@ -110,16 +114,20 @@ var WindowListener = {
           }, result => {
             this._constants = result;
           });
         }
 
         return this._constants;
       },
 
+      get mm() {
+        return window.getGroupMessageManager("browsers");
+      },
+
       /**
        * @return {Promise}
        */
       promiseDocumentVisible(aDocument) {
         if (!aDocument.hidden) {
           return Promise.resolve(aDocument);
         }
 
@@ -308,16 +316,20 @@ var WindowListener = {
         this.addMenuItem();
 
         // Don't do the rest if this is for the hidden window - we don't
         // have a toolbar there.
         if (window == Services.appShell.hiddenDOMWindow) {
           return;
         }
 
+        // Load the frame script into any tab, plus any that get created in the
+        // future.
+        this.mm.loadFrameScript(FRAME_SCRIPT, true);
+
         // Cleanup when the window unloads.
         window.addEventListener("unload", () => {
           Services.obs.removeObserver(this, "loop-status-changed");
         });
 
         Services.obs.addObserver(this, "loop-status-changed", false);
 
         this.updateToolbarState();
@@ -517,19 +529,23 @@ var WindowListener = {
        * Push message parameters:
        * - {Integer} windowId  The new windowId for the browser.
        */
       startBrowserSharing: function() {
         if (!this._listeningToTabSelect) {
           gBrowser.tabContainer.addEventListener("TabSelect", this);
           this._listeningToTabSelect = true;
 
+          titleChangedListener = this.handleDOMTitleChanged.bind(this);
+
           // Watch for title changes as opposed to location changes as more
           // metadata about the page is available when this event fires.
-          gBrowser.addEventListener("DOMTitleChanged", this);
+          this.mm.addMessageListener("loop@mozilla.org:DOMTitleChanged",
+            titleChangedListener);
+
           this._browserSharePaused = false;
 
           // Add this event to the parent gBrowser to avoid adding and removing
           // it for each individual tab's browsers.
           gBrowser.addEventListener("mousemove", this);
           gBrowser.addEventListener("click", this);
         }
 
@@ -556,17 +572,22 @@ var WindowListener = {
        */
       stopBrowserSharing: function() {
         if (!this._listeningToTabSelect) {
           return;
         }
 
         this._hideBrowserSharingInfoBar();
         gBrowser.tabContainer.removeEventListener("TabSelect", this);
-        gBrowser.removeEventListener("DOMTitleChanged", this);
+
+        if (titleChangedListener) {
+          this.mm.removeMessageListener("loop@mozilla.org:DOMTitleChanged",
+            titleChangedListener);
+          titleChangedListener = null;
+        }
 
         // Remove shared pointers related events
         gBrowser.removeEventListener("mousemove", this);
         gBrowser.removeEventListener("click", this);
         this.removeRemoteCursor();
 
         this._listeningToTabSelect = false;
         this._browserSharePaused = false;
@@ -795,24 +816,36 @@ var WindowListener = {
        */
       _notifyBrowserSwitch: function() {
          // Get the first window Id for the listener.
         this.LoopAPI.broadcastPushMessage("BrowserSwitch",
           gBrowser.selectedBrowser.outerWindowID);
       },
 
       /**
+       * Handles events from the frame script.
+       *
+       * @param {Object} message The message received from the frame script.
+       */
+      handleDOMTitleChanged: function(message) {
+        if (!this._listeningToTabSelect || this._browserSharePaused) {
+          return;
+        }
+
+        if (gBrowser.selectedBrowser == message.target) {
+          // Get the new title of the shared tab
+          this._notifyBrowserSwitch();
+        }
+      },
+
+      /**
        * Handles events from gBrowser.
        */
       handleEvent: function(event) {
         switch (event.type) {
-          case "DOMTitleChanged":
-            // Get the new title of the shared tab
-            this._notifyBrowserSwitch();
-            break;
           case "TabSelect":
             let wasVisible = false;
             // Hide the infobar from the previous tab.
             if (event.detail.previousTab) {
               wasVisible = this._hideBrowserSharingInfoBar(
                             event.detail.previousTab.linkedBrowser);
               // And remove the cursor.
               this.removeRemoteCursor();
@@ -945,16 +978,20 @@ var WindowListener = {
    * document.getElementById() etc. will work here.
    *
    * @param {Object} window The window to remove the integration from.
    */
   tearDownBrowserUI: function(window) {
     if (window.LoopUI) {
       window.LoopUI.removeMenuItem();
 
+      // This stops the frame script being loaded to new tabs, but doesn't
+      // remove it from existing tabs (there's no way to do that).
+      window.LoopUI.mm.removeDelayedFrameScript(FRAME_SCRIPT);
+
       // XXX Bug 1229352 - Add in tear-down of the panel.
     }
   },
 
   // nsIWindowMediatorListener functions.
   onOpenWindow: function(xulWindow) {
     // A new window has opened.
     let domWindow = xulWindow.QueryInterface(Ci.nsIInterfaceRequestor)
new file mode 100644
--- /dev/null
+++ b/browser/extensions/loop/chrome/content/modules/tabFrame.js
@@ -0,0 +1,22 @@
+/* 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";
+
+/* global sendAsyncMessage */
+
+/**
+ * This script runs in the content process and is attached to browsers when
+ * they are created.
+ */
+
+// Listen for when the title is changed and send a message back to the chrome
+// process.
+addEventListener("DOMTitleChanged", ({ target }) => {
+  sendAsyncMessage("loop@mozilla.org:DOMTitleChanged", {
+    details: "titleChanged"
+  }, {
+     target: target
+  });
+});
--- a/browser/extensions/loop/chrome/test/mochitest/browser.ini
+++ b/browser/extensions/loop/chrome/test/mochitest/browser.ini
@@ -10,9 +10,10 @@ support-files =
 [browser_LoopRooms_channel.js]
 [browser_menuitem.js]
 [browser_mozLoop_appVersionInfo.js]
 [browser_mozLoop_chat.js]
 [browser_mozLoop_context.js]
 [browser_mozLoop_socialShare.js]
 [browser_mozLoop_sharingListeners.js]
 [browser_mozLoop_telemetry.js]
+[browser_sharingTitleListeners.js]
 [browser_toolbarbutton.js]
new file mode 100644
--- /dev/null
+++ b/browser/extensions/loop/chrome/test/mochitest/browser_sharingTitleListeners.js
@@ -0,0 +1,51 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * This file contains tests for the browser sharing document title listeners.
+ */
+"use strict";
+
+var [, gHandlers] = LoopAPI.inspect();
+
+function promiseBrowserSwitch() {
+  return new Promise(resolve => {
+    LoopAPI.stub([{
+      sendAsyncMessage: function(messageName, data) {
+        if (data[0] == "BrowserSwitch") {
+          LoopAPI.restore();
+          resolve();
+        }
+      }
+    }]);
+  });
+}
+
+add_task(function* setup() {
+  Services.prefs.setBoolPref("loop.remote.autostart", true);
+
+  gHandlers.AddBrowserSharingListener({ data: [42] }, () => {});
+
+  let newTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank", true);
+
+  registerCleanupFunction(function* () {
+    // Remove the listener.
+    gHandlers.RemoveBrowserSharingListener({ data: [42] }, function() {});
+
+    yield BrowserTestUtils.removeTab(newTab);
+
+    Services.prefs.clearUserPref("loop.remote.autostart");
+  });
+});
+
+add_task(function* test_notifyOnTitleChanged() {
+  // Hook up the async listener and wait for the BrowserSwitch to happen.
+  let browserSwitchPromise = promiseBrowserSwitch();
+
+  BrowserTestUtils.loadURI(gBrowser.selectedBrowser, "about:mozilla");
+
+  // Now check we get the notification of the browser switch.
+  yield browserSwitchPromise;
+
+  Assert.ok(true, "We got notification of the browser switch");
+});