Bug 1474163 - Make use of sharedData for content theme data. r=mconley
☠☠ backed out by b964d8badd9b ☠ ☠
authorTim Nguyen <ntim.bugs@gmail.com>
Fri, 13 Jul 2018 16:21:34 +0100
changeset 427812 7fd40900b7c1d576d7ff3f1c7bfda330ca740ca8
parent 427811 5865394c3ef194daf2e5ee4c252e302e5f99872b
child 427813 e1e741df87f2e86c7ccc25c21172083e0900d2d0
push id66725
push userntim.bugs@gmail.com
push dateMon, 23 Jul 2018 17:07:07 +0000
treeherderautoland@7fd40900b7c1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmconley
bugs1474163
milestone63.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 1474163 - Make use of sharedData for content theme data. r=mconley MozReview-Commit-ID: Etz8huX2YCt
browser/base/content/contentTheme.js
browser/base/content/tab-content.js
browser/base/content/test/performance/browser_startup_content.js
browser/modules/LightweightThemeChildHelper.jsm
browser/modules/LightweightThemeChildListener.jsm
browser/modules/moz.build
toolkit/components/extensions/test/browser/browser_ext_themes_ntp_colors.js
toolkit/components/extensions/test/browser/browser_ext_themes_ntp_colors_perwindow.js
toolkit/modules/LightweightThemeConsumer.jsm
--- a/browser/base/content/contentTheme.js
+++ b/browser/base/content/contentTheme.js
@@ -41,27 +41,24 @@ const inContentVariableMap = [
  */
 const ContentThemeController = {
   /**
    * Tell the frame script that the page supports theming, and watch for updates
    * from the frame script.
    */
   init() {
     addEventListener("LightweightTheme:Set", this);
-
-    const event = new CustomEvent("LightweightTheme:Support", {bubbles: true});
-    document.dispatchEvent(event);
   },
 
   /**
    * Handle theme updates from the frame script.
    * @param {Object} event object containing the theme update.
    */
-  handleEvent({ detail }) {
-    if (detail.type == "LightweightTheme:Update") {
+  handleEvent({ type, detail }) {
+    if (type == "LightweightTheme:Set") {
       let {data} = detail;
       if (!data) {
         data = {};
       }
       this._setProperties(document.body, data);
     }
   },
 
--- a/browser/base/content/tab-content.js
+++ b/browser/base/content/tab-content.js
@@ -19,18 +19,16 @@ ChromeUtils.defineModuleGetter(this, "Ut
 ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "AboutReader",
   "resource://gre/modules/AboutReader.jsm");
 ChromeUtils.defineModuleGetter(this, "ReaderMode",
   "resource://gre/modules/ReaderMode.jsm");
 ChromeUtils.defineModuleGetter(this, "PageStyleHandler",
   "resource:///modules/PageStyleHandler.jsm");
-ChromeUtils.defineModuleGetter(this, "LightweightThemeChildListener",
-  "resource:///modules/LightweightThemeChildListener.jsm");
 
 // TabChildGlobal
 var global = this;
 
 
 addEventListener("MozDOMPointerLock:Entered", function(aEvent) {
   sendAsyncMessage("PointerLock:Entered", {
     originNoSuffix: aEvent.target.nodePrincipal.originNoSuffix
@@ -84,43 +82,31 @@ addMessageListener("Browser:Reload", fun
   } catch (e) {
   }
 });
 
 addMessageListener("MixedContent:ReenableProtection", function() {
   docShell.mixedContentChannel = null;
 });
 
-var LightweightThemeChildListenerStub = {
-  _childListener: null,
-  get childListener() {
-    if (!this._childListener) {
-      this._childListener = new LightweightThemeChildListener();
-    }
-    return this._childListener;
-  },
+XPCOMUtils.defineLazyProxy(this, "LightweightThemeChildHelper",
+  "resource:///modules/LightweightThemeChildHelper.jsm");
 
-  init() {
-    addEventListener("LightweightTheme:Support", this, false, true);
-    addMessageListener("LightweightTheme:Update", this);
-    sendAsyncMessage("LightweightTheme:Request");
-    this.init = null;
-  },
+let themeablePagesWhitelist = new Set([
+  "about:home",
+  "about:newtab",
+  "about:welcome",
+]);
 
-  handleEvent(event) {
-    return this.childListener.handleEvent(event);
-  },
-
-  receiveMessage(msg) {
-    return this.childListener.receiveMessage(msg);
-  },
-};
-
-LightweightThemeChildListenerStub.init();
-
+addEventListener("pageshow", function({ originalTarget }) {
+  if (originalTarget.defaultView == content && themeablePagesWhitelist.has(content.document.documentURI)) {
+    LightweightThemeChildHelper.listen(themeablePagesWhitelist);
+    LightweightThemeChildHelper.update(chromeOuterWindowID, content);
+  }
+}, false, true);
 
 var AboutReaderListener = {
 
   _articlePromise: null,
 
   _isLeavingReaderableReaderMode: false,
 
   init() {
--- a/browser/base/content/test/performance/browser_startup_content.js
+++ b/browser/base/content/test/performance/browser_startup_content.js
@@ -51,17 +51,16 @@ const whitelist = {
     // Forms and passwords
     "resource://formautofill/FormAutofill.jsm",
     "resource://formautofill/FormAutofillContent.jsm",
 
     // Browser front-end
     "resource:///modules/ContentLinkHandler.jsm",
     "resource:///modules/ContentMetaHandler.jsm",
     "resource:///modules/PageStyleHandler.jsm",
-    "resource:///modules/LightweightThemeChildListener.jsm",
     "resource://gre/modules/BrowserUtils.jsm",
     "resource://gre/modules/E10SUtils.jsm",
     "resource://gre/modules/PrivateBrowsingUtils.jsm",
     "resource://gre/modules/ReaderMode.jsm",
     "resource://gre/modules/RemotePageManager.jsm",
 
     // Pocket
     "chrome://pocket/content/AboutPocket.jsm",
rename from browser/modules/LightweightThemeChildListener.jsm
rename to browser/modules/LightweightThemeChildHelper.jsm
--- a/browser/modules/LightweightThemeChildListener.jsm
+++ b/browser/modules/LightweightThemeChildHelper.jsm
@@ -1,82 +1,74 @@
 /* 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";
 
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+var EXPORTED_SYMBOLS = ["LightweightThemeChildHelper"];
+
 /**
- * LightweightThemeChildListener forwards theme updates from LightweightThemeConsumer to
- * the whitelisted in-content pages
+ * LightweightThemeChildHelper forwards theme data to in-content pages.
  */
-class LightweightThemeChildListener {
-  constructor() {
-    /**
-     * The pages that will receive theme updates
-     */
-    this.whitelist = new Set([
-      "about:home",
-      "about:newtab",
-      "about:welcome",
-    ]);
-
-    /**
-     * The last theme data received from LightweightThemeConsumer
-     */
-    this._lastData = null;
-  }
+var LightweightThemeChildHelper = {
+  listener: null,
+  whitelist: [],
 
   /**
-   * Handles theme updates from the parent process
-   * @param {Object} message from the parent process.
+   * Listen to theme updates for the current process
+   * @param {Array} whitelist The pages that can receive theme updates.
    */
-  receiveMessage({ name, data, target }) {
-    if (name == "LightweightTheme:Update") {
-      this._lastData = data;
-      this._update(data, target.content);
+  listen(whitelist) {
+    if (!this.listener) {
+      // Clone the whitelist to avoid leaking the global the whitelist
+      // originates from.
+      this.whitelist = new Set([...whitelist]);
+      this.listener = ({ changedKeys }) => {
+        if (changedKeys.find(change => change.startsWith("theme/"))) {
+          this._updateProcess(changedKeys);
+        }
+      };
+      Services.cpmm.sharedData.addEventListener("change", this.listener);
+      Services.obs.addObserver(() => {
+        Services.cpmm.sharedData.removeEventListener("change", this.listener);
+      }, "xpcom-shutdown");
     }
-  }
+  },
 
   /**
-   * Handles events from the content scope.
-   * @param {Object} event The received event.
+   * Update the theme data for the whole process
+   * @param {Array} changedKeys The sharedData keys that were changed.
    */
-  handleEvent(event) {
-    const content = event.originalTarget.defaultView;
-    if (content != content.top) {
-      return;
+  _updateProcess(changedKeys) {
+    const windowEnumerator = Services.ww.getWindowEnumerator();
+    while (windowEnumerator.hasMoreElements()) {
+      const window = windowEnumerator.getNext().QueryInterface(Ci.nsIDOMWindow);
+      const tabChildGlobal = window.QueryInterface(Ci.nsIInterfaceRequestor)
+                                   .getInterface(Ci.nsIDocShell)
+                                   .sameTypeRootTreeItem
+                                   .QueryInterface(Ci.nsIInterfaceRequestor)
+                                   .getInterface(Ci.nsIContentFrameMessageManager);
+      const {chromeOuterWindowID, content} = tabChildGlobal;
+      if (changedKeys.includes(`theme/${chromeOuterWindowID}`) &&
+          content && this.whitelist.has(content.document.documentURI)) {
+        this.update(chromeOuterWindowID, content);
+      }
     }
-
-    if (event.type == "LightweightTheme:Support") {
-      this._update(this._lastData, content);
-    }
-  }
-
-  /**
-   * Checks if a given global is allowed to receive theme updates
-   * @param {Object} content The global to check against.
-   * @returns {boolean} Whether the global is allowed to receive updates.
-   */
-  _isContentWhitelisted(content) {
-    return this.whitelist.has(content.document.documentURI);
-  }
+  },
 
   /**
    * Forward the theme data to the page.
-   * @param {Object} data The theme data to forward
+   * @param {Object} outerWindowID The outerWindowID the parent process window has.
    * @param {Object} content The receiving global
    */
-  _update(data, content) {
-    if (this._isContentWhitelisted(content)) {
-      const event = Cu.cloneInto({
-        detail: {
-          type: "LightweightTheme:Update",
-          data,
-        },
-      }, content);
-      content.dispatchEvent(new content.CustomEvent("LightweightTheme:Set",
-                                                    event));
-    }
-  }
-}
-
-var EXPORTED_SYMBOLS = ["LightweightThemeChildListener"];
+  update(outerWindowID, content) {
+    const event = Cu.cloneInto({
+      detail: {
+        data: Services.cpmm.sharedData.get(`theme/${outerWindowID}`)
+      },
+    }, content);
+    content.dispatchEvent(new content.CustomEvent("LightweightTheme:Set",
+                                                  event));
+  },
+};
--- a/browser/modules/moz.build
+++ b/browser/modules/moz.build
@@ -65,17 +65,17 @@ with Files("ContentWebRTC.jsm"):
     BUG_COMPONENT = ("Firefox", "Device Permissions")
 
 with Files("ExtensionsUI.jsm"):
     BUG_COMPONENT = ("WebExtensions", "General")
 
 with Files("LaterRun.jsm"):
     BUG_COMPONENT = ("Firefox", "Tours")
 
-with Files("LightweightThemeChildListener.jsm"):
+with Files("LightweightThemeChildHelper.jsm"):
     BUG_COMPONENT = ("WebExtensions", "Themes")
 
 with Files("LightWeightThemeWebInstallListener.jsm"):
     BUG_COMPONENT = ("Firefox", "Theme")
 
 with Files("OpenInTabsUtils.jsm"):
     BUG_COMPONENT = ("Firefox", "Tabbed Browser")
 
@@ -155,17 +155,17 @@ EXTRA_JS_MODULES += [
     'ContentWebRTC.jsm',
     'ContextMenu.jsm',
     'ExtensionsUI.jsm',
     'Feeds.jsm',
     'FormSubmitObserver.jsm',
     'FormValidationHandler.jsm',
     'HomePage.jsm',
     'LaterRun.jsm',
-    'LightweightThemeChildListener.jsm',
+    'LightweightThemeChildHelper.jsm',
     'LightWeightThemeWebInstallListener.jsm',
     'NetErrorContent.jsm',
     'OpenInTabsUtils.jsm',
     'PageActions.jsm',
     'PageInfoListener.jsm',
     'PageStyleHandler.jsm',
     'PermissionUI.jsm',
     'PingCentre.jsm',
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_ntp_colors.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_ntp_colors.js
@@ -29,16 +29,18 @@ async function test_ntp_theme(theme, isB
     return {
       originalBackground: content.getComputedStyle(doc.body).backgroundColor,
       originalColor: content.getComputedStyle(doc.querySelector(".outer-wrapper")).color,
     };
   });
 
   await extension.startup();
 
+  Services.ppmm.sharedData.flush();
+
   await ContentTask.spawn(browser, {
     isBrightText,
     background: hexToCSS(theme.colors.ntp_background),
     color: hexToCSS(theme.colors.ntp_text),
   }, function({isBrightText, background, color}) {
     let doc = content.document;
     ok(doc.body.hasAttribute("lwt-newtab"),
        "New tab page should have lwt-newtab attribute");
@@ -48,16 +50,18 @@ async function test_ntp_theme(theme, isB
     is(content.getComputedStyle(doc.body).backgroundColor, background,
        "New tab page background should be set.");
     is(content.getComputedStyle(doc.querySelector(".outer-wrapper")).color, color,
        "New tab page text color should be set.");
   });
 
   await extension.unload();
 
+  Services.ppmm.sharedData.flush();
+
   await ContentTask.spawn(browser, {
     originalBackground,
     originalColor,
   }, function({originalBackground, originalColor}) {
     let doc = content.document;
     ok(!doc.body.hasAttribute("lwt-newtab"),
        "New tab page should not have lwt-newtab attribute");
     ok(!doc.body.hasAttribute("lwt-newtab-brighttext"),
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_ntp_colors_perwindow.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_ntp_colors_perwindow.js
@@ -5,16 +5,17 @@
 /**
  * Test whether a given browser has the new tab page theme applied
  * @param {Object} browser to test against
  * @param {Object} theme that is applied
  * @param {boolean} isBrightText whether the brighttext attribute should be set
  * @returns {Promise} The task as a promise
  */
 function test_ntp_theme(browser, theme, isBrightText) {
+  Services.ppmm.sharedData.flush();
   return ContentTask.spawn(browser, {
     isBrightText,
     background: hexToCSS(theme.colors.ntp_background),
     color: hexToCSS(theme.colors.ntp_text),
   }, function({isBrightText, background, color}) {
     let doc = content.document;
     ok(doc.body.hasAttribute("lwt-newtab"),
        "New tab page should have lwt-newtab attribute");
@@ -29,16 +30,17 @@ function test_ntp_theme(browser, theme, 
 }
 
 /**
  * Test whether a given browser has the default theme applied
  * @param {Object} browser to test against
  * @returns {Promise} The task as a promise
  */
 function test_ntp_default_theme(browser) {
+  Services.ppmm.sharedData.flush();
   return ContentTask.spawn(browser, {
     background: hexToCSS("#F9F9FA"),
     color: hexToCSS("#0C0C0D"),
   }, function({background, color}) {
     let doc = content.document;
     ok(!doc.body.hasAttribute("lwt-newtab"),
        "New tab page should not have lwt-newtab attribute");
     ok(!doc.body.hasAttribute("lwt-newtab-brighttext"),
--- a/toolkit/modules/LightweightThemeConsumer.jsm
+++ b/toolkit/modules/LightweightThemeConsumer.jsm
@@ -107,82 +107,69 @@ ChromeUtils.defineModuleGetter(this, "Th
 ChromeUtils.defineModuleGetter(this, "ThemeVariableMap",
   "resource:///modules/ThemeVariableMap.jsm");
 ChromeUtils.defineModuleGetter(this, "LightweightThemeImageOptimizer",
   "resource://gre/modules/addons/LightweightThemeImageOptimizer.jsm");
 
 function LightweightThemeConsumer(aDocument) {
   this._doc = aDocument;
   this._win = aDocument.defaultView;
+  this._winId = this._win
+    .QueryInterface(Ci.nsIInterfaceRequestor)
+    .getInterface(Ci.nsIDOMWindowUtils)
+    .outerWindowID;
 
   Services.obs.addObserver(this, "lightweight-theme-styling-update");
 
   var temp = {};
   ChromeUtils.import("resource://gre/modules/LightweightThemeManager.jsm", temp);
   this._update(temp.LightweightThemeManager.currentThemeForDisplay);
 
   this._win.addEventListener("resolutionchange", this);
   this._win.addEventListener("unload", this, { once: true });
-  this._win.addEventListener("EndSwapDocShells", this, true);
-  this._win.messageManager.addMessageListener("LightweightTheme:Request", this);
 
   let darkThemeMediaQuery = this._win.matchMedia("(-moz-system-dark-theme)");
   darkThemeMediaQuery.addListener(temp.LightweightThemeManager);
   temp.LightweightThemeManager.systemThemeChanged(darkThemeMediaQuery);
 }
 
 LightweightThemeConsumer.prototype = {
   _lastData: null,
   // Whether a lightweight theme is enabled.
   _active: false,
 
   observe(aSubject, aTopic, aData) {
     if (aTopic != "lightweight-theme-styling-update")
       return;
 
-    const { outerWindowID } = this._win
-      .QueryInterface(Ci.nsIInterfaceRequestor)
-      .getInterface(Ci.nsIDOMWindowUtils);
-
     let parsedData = JSON.parse(aData);
     if (!parsedData) {
       parsedData = { theme: null };
     }
 
-    if (parsedData.window && parsedData.window !== outerWindowID) {
+    if (parsedData.window && parsedData.window !== this._winId) {
       return;
     }
 
     this._update(parsedData.theme);
   },
 
-  receiveMessage({ name, target }) {
-    if (name == "LightweightTheme:Request") {
-      let contentThemeData = _getContentProperties(this._doc, this._active, this._lastData);
-      target.messageManager.sendAsyncMessage("LightweightTheme:Update", contentThemeData);
-    }
-  },
-
   handleEvent(aEvent) {
     switch (aEvent.type) {
       case "resolutionchange":
         if (this._active) {
           this._update(this._lastData);
         }
         break;
       case "unload":
         Services.obs.removeObserver(this, "lightweight-theme-styling-update");
+        Services.ppmm.sharedData.delete(`theme/${this._winId}`);
         this._win.removeEventListener("resolutionchange", this);
-        this._win.removeEventListener("EndSwapDocShells", this, true);
         this._win = this._doc = null;
         break;
-      case "EndSwapDocShells":
-        let contentThemeData = _getContentProperties(this._doc, this._active, this._lastData);
-        aEvent.target.messageManager.sendAsyncMessage("LightweightTheme:Update", contentThemeData);
-        break;
     }
   },
 
   _update(aData) {
     this._lastData = aData;
     if (aData) {
       aData = LightweightThemeImageOptimizer.optimize(aData, this._win.screen);
     }
@@ -226,21 +213,17 @@ LightweightThemeConsumer.prototype = {
     }
 
     if (active && aData.footerURL)
       root.setAttribute("lwthemefooter", "true");
     else
       root.removeAttribute("lwthemefooter");
 
     let contentThemeData = _getContentProperties(this._doc, active, aData);
-
-    let browserMessageManager = this._win.getGroupMessageManager("browsers");
-    browserMessageManager.broadcastAsyncMessage(
-      "LightweightTheme:Update", contentThemeData
-    );
+    Services.ppmm.sharedData.set(`theme/${this._winId}`, contentThemeData);
   }
 };
 
 function _getContentProperties(doc, active, data) {
   if (!active) {
     return {};
   }
   let properties = {};