Bug 1474163 - Make use of sharedData for content theme data. r=mconley draft
authorTim Nguyen <ntim.bugs@gmail.com>
Fri, 13 Jul 2018 16:21:34 +0100
changeset 823572 913275a0002531ddefce19308fc13b3cfbf5efec
parent 823477 c8e816e887e4389e13abae4c273bc7cf134cce10
push id117729
push userbmo:ntim.bugs@gmail.com
push dateFri, 27 Jul 2018 16:34:27 +0000
reviewersmconley
bugs1474163
milestone63.0a1
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",
 
     // 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,71 @@
 /* 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);
     }
-  }
+  },
 
   /**
-   * 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
@@ -59,17 +59,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")
 
@@ -148,17 +148,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
@@ -105,80 +105,66 @@ 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.windowUtils.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.windowUtils;
-
     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);
     }
@@ -222,21 +208,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 = {};