Bug 1540984: Pre-load theme header image and share across all windows. r=aswan
☠☠ backed out by a722a039fb67 ☠ ☠
authorKris Maglione <maglione.k@gmail.com>
Wed, 01 May 2019 12:43:25 -0700
changeset 531207 6159d01c9341088edcabb318259c8ce6201c61ac
parent 531206 0c983ed54af0730f3360b76156b8e0fa41cc4ebc
child 531208 9ae64983479d4560db4d44719f1154813fdec2e0
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaswan
bugs1540984
milestone68.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 1540984: Pre-load theme header image and share across all windows. r=aswan For large header images, built-in memory caching does not work correctly, and we wind up seeing a flicker any time a new window is opened or becomes active. This patch caches the header image in an Image element at startup, and uses that element as the backing for the header image in all browser windows. Differential Revision: https://phabricator.services.mozilla.com/D29554
toolkit/components/extensions/test/browser/browser_ext_themes_chromeparity.js
toolkit/components/extensions/test/browser/browser_ext_themes_dynamic_updates.js
toolkit/components/extensions/test/browser/browser_ext_themes_lwtsupport.js
toolkit/components/extensions/test/browser/browser_ext_themes_multiple_backgrounds.js
toolkit/components/extensions/test/browser/browser_ext_themes_persistence.js
toolkit/components/extensions/test/browser/head.js
toolkit/modules/LightweightThemeConsumer.jsm
toolkit/mozapps/extensions/LightweightThemeManager.jsm
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_chromeparity.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_chromeparity.js
@@ -23,18 +23,17 @@ add_task(async function test_support_the
   await extension.startup();
 
   let docEl = window.document.documentElement;
   Assert.ok(docEl.hasAttribute("lwtheme"), "LWT attribute should be set");
   Assert.equal(docEl.getAttribute("lwthemetextcolor"), "bright",
                "LWT text color attribute should be set");
 
   let style = window.getComputedStyle(docEl);
-  Assert.ok(style.backgroundImage.includes("face.png"),
-            `The backgroundImage should use face.png. Actual value is: ${style.backgroundImage}`);
+  checkThemeHeaderImage(window, `moz-extension://${extension.uuid}/face.png`);
   Assert.equal(style.backgroundColor, "rgb(" + FRAME_COLOR.join(", ") + ")",
                "Expected correct background color");
   Assert.equal(style.color, "rgb(" + TAB_TEXT_COLOR.join(", ") + ")",
                "Expected correct text color");
 
   await extension.unload();
 
   Assert.ok(!docEl.hasAttribute("lwtheme"), "LWT attribute should not be set");
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_dynamic_updates.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_dynamic_updates.js
@@ -20,17 +20,17 @@ function validateTheme(backgroundImage, 
   let style = window.getComputedStyle(docEl);
 
   if (isLWT) {
     Assert.ok(docEl.hasAttribute("lwtheme"), "LWT attribute should be set");
     Assert.equal(docEl.getAttribute("lwthemetextcolor"), "bright",
                  "LWT text color attribute should be set");
   }
 
-  Assert.ok(style.backgroundImage.includes(backgroundImage), "Expected correct background image");
+  checkThemeHeaderImage(window, backgroundImage);
   if (accentColor.startsWith("#")) {
     accentColor = hexToRGB(accentColor);
   }
   if (textColor.startsWith("#")) {
     textColor = hexToRGB(textColor);
   }
   Assert.equal(style.backgroundColor, accentColor, "Expected correct accent color");
   Assert.equal(style.color, textColor, "Expected correct text color");
@@ -70,33 +70,33 @@ add_task(async function test_dynamic_the
     "colors": {
       "frame": ACCENT_COLOR_1,
       "tab_background_text": TEXT_COLOR_1,
     },
   });
 
   await extension.awaitMessage("theme-updated");
 
-  validateTheme("image1.png", ACCENT_COLOR_1, TEXT_COLOR_1, true);
+  validateTheme(`moz-extension://${extension.uuid}/image1.png`, ACCENT_COLOR_1, TEXT_COLOR_1, true);
 
   // Check with the LWT aliases (to update on Firefox 69, because the
   // LWT aliases are going to be removed).
   extension.sendMessage("update-theme", {
     "images": {
       "theme_frame": "image2.png",
     },
     "colors": {
       "frame": ACCENT_COLOR_2,
       "tab_background_text": TEXT_COLOR_2,
     },
   });
 
   await extension.awaitMessage("theme-updated");
 
-  validateTheme("image2.png", ACCENT_COLOR_2, TEXT_COLOR_2, true);
+  validateTheme(`moz-extension://${extension.uuid}/image2.png`, ACCENT_COLOR_2, TEXT_COLOR_2, true);
 
   extension.sendMessage("reset-theme");
 
   await extension.awaitMessage("theme-reset");
 
   let {backgroundImage, backgroundColor, color} = defaultStyle;
   validateTheme(backgroundImage, backgroundColor, color, false);
 
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_lwtsupport.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_lwtsupport.js
@@ -23,17 +23,17 @@ add_task(async function test_support_LWT
   let docEl = window.document.documentElement;
   let style = window.getComputedStyle(docEl);
 
   Assert.ok(docEl.hasAttribute("lwtheme"), "LWT attribute should be set");
   Assert.ok(docEl.hasAttribute("lwtheme-image"), "LWT image attribute should be set");
   Assert.equal(docEl.getAttribute("lwthemetextcolor"), "bright",
                "LWT text color attribute should be set");
 
-  Assert.ok(style.backgroundImage.includes("image1.png"), "Expected background image");
+  checkThemeHeaderImage(window, `moz-extension://${extension.uuid}/image1.png`);
   Assert.equal(style.backgroundColor, "rgb(" + hexToRGB(ACCENT_COLOR).join(", ") + ")",
                "Expected correct background color");
   Assert.equal(style.color, "rgb(" + hexToRGB(TEXT_COLOR).join(", ") + ")",
                "Expected correct text color");
 
   await extension.unload();
 
   Assert.ok(!docEl.hasAttribute("lwtheme"), "LWT attribute should not be set");
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_multiple_backgrounds.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_multiple_backgrounds.js
@@ -29,20 +29,19 @@ add_task(async function test_support_bac
   let toolbox = document.querySelector("#navigator-toolbox");
 
   Assert.ok(docEl.hasAttribute("lwtheme"), "LWT attribute should be set");
   Assert.equal(docEl.getAttribute("lwthemetextcolor"), "bright",
                "LWT text color attribute should be set");
 
   let toolboxCS = window.getComputedStyle(toolbox);
   let rootCS = window.getComputedStyle(docEl);
-  let rootBgImage = rootCS.backgroundImage.split(",")[0].trim();
   let bgImage = toolboxCS.backgroundImage.split(",")[0].trim();
-  Assert.ok(rootBgImage.includes("face1.png"),
-            `The backgroundImage should use face1.png. Actual value is: ${rootBgImage}`);
+
+  checkThemeHeaderImage(window, `moz-extension://${extension.uuid}/face1.png`);
   Assert.equal(toolboxCS.backgroundImage, Array(3).fill(bgImage).join(", "),
                "The backgroundImage should use face2.png three times.");
   Assert.equal(toolboxCS.backgroundPosition, "0% 0%, 50% 0%, 100% 100%",
                "The backgroundPosition should use the three values provided.");
   Assert.equal(toolboxCS.backgroundRepeat, "no-repeat",
                "The backgroundPosition should use the default value.");
 
   await extension.unload();
@@ -90,20 +89,18 @@ add_task(async function test_support_bac
   let toolbox = document.querySelector("#navigator-toolbox");
 
   Assert.ok(docEl.hasAttribute("lwtheme"), "LWT attribute should be set");
   Assert.equal(docEl.getAttribute("lwthemetextcolor"), "bright",
                "LWT text color attribute should be set");
 
   let rootCS = window.getComputedStyle(docEl);
   let toolboxCS = window.getComputedStyle(toolbox);
-  let bgImage = rootCS.backgroundImage.split(",")[0].trim();
-  Assert.ok(bgImage.includes("face0.png"),
-            `The backgroundImage should use face.png. Actual value is: ${bgImage}`);
-  Assert.equal([1, 2, 3].map(num => bgImage.replace(/face[\d]*/, `face${num}`)).join(", "),
+  checkThemeHeaderImage(window, `moz-extension://${extension.uuid}/face0.png`);
+  Assert.equal([1, 2, 3].map(num => `url("moz-extension://${extension.uuid}/face${num}.png")`).join(", "),
                toolboxCS.backgroundImage, "The backgroundImage should use face.png three times.");
   Assert.equal(rootCS.backgroundPosition, "100% 0%",
                "The backgroundPosition should use the default value for root.");
   Assert.equal(toolboxCS.backgroundPosition, "100% 0%",
                "The backgroundPosition should use the default value for navigator-toolbox.");
   Assert.equal(rootCS.backgroundRepeat, "no-repeat",
                "The backgroundRepeat should use the default values for root.");
   Assert.equal(toolboxCS.backgroundRepeat, "repeat-x, repeat-y, repeat",
@@ -141,19 +138,17 @@ add_task(async function test_additional_
   let toolbox = document.querySelector("#navigator-toolbox");
 
   Assert.ok(docEl.hasAttribute("lwtheme"), "LWT attribute should be set");
   Assert.equal(docEl.getAttribute("lwthemetextcolor"), "bright",
                "LWT text color attribute should be set");
 
   let rootCS = window.getComputedStyle(docEl);
   let toolboxCS = window.getComputedStyle(toolbox);
-  let bgImage = rootCS.backgroundImage.split(",")[0];
-  Assert.ok(bgImage.includes("face.png"),
-            `The backgroundImage should use face.png. Actual value is: ${bgImage}`);
+  checkThemeHeaderImage(window, `moz-extension://${extension.uuid}/face.png`);
   Assert.equal("none", toolboxCS.backgroundImage,
                "The backgroundImage should not use face.png.");
   Assert.equal(rootCS.backgroundPosition, "100% 0%",
                "The backgroundPosition should use the default value.");
   Assert.equal(rootCS.backgroundRepeat, "no-repeat",
                "The backgroundPosition should use only one (default) value.");
 
   await extension.unload();
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_persistence.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_persistence.js
@@ -19,28 +19,26 @@ add_task(async function test_multiple_wi
     files: {
       "image1.png": BACKGROUND,
     },
   });
 
   await extension.startup();
 
   let docEl = window.document.documentElement;
-  let style = window.getComputedStyle(docEl);
 
   Assert.ok(docEl.hasAttribute("lwtheme"), "LWT attribute should be set");
   Assert.equal(docEl.getAttribute("lwthemetextcolor"), "bright",
                "LWT text color attribute should be set");
-  Assert.ok(style.backgroundImage.includes("image1.png"), "Expected background image");
+  checkThemeHeaderImage(window, `moz-extension://${extension.uuid}/image1.png`);
 
   // Now we'll open a new window to see if the theme is also applied there.
   let window2 = await BrowserTestUtils.openNewBrowserWindow();
   docEl = window2.document.documentElement;
-  style = window2.getComputedStyle(docEl);
 
   Assert.ok(docEl.hasAttribute("lwtheme"), "LWT attribute should be set");
   Assert.equal(docEl.getAttribute("lwthemetextcolor"), "bright",
                "LWT text color attribute should be set");
-  Assert.ok(style.backgroundImage.includes("image1.png"), "Expected background image");
+  checkThemeHeaderImage(window, `moz-extension://${extension.uuid}/image1.png`);
 
   await BrowserTestUtils.closeWindow(window2);
   await extension.unload();
 });
--- a/toolkit/components/extensions/test/browser/head.js
+++ b/toolkit/components/extensions/test/browser/head.js
@@ -1,11 +1,11 @@
 /* exported ACCENT_COLOR, BACKGROUND, ENCODED_IMAGE_DATA, FRAME_COLOR, TAB_TEXT_COLOR,
    TEXT_COLOR, TAB_BACKGROUND_TEXT_COLOR, imageBufferFromDataURI, hexToCSS, hexToRGB, testBorderColor,
-   waitForTransition */
+   waitForTransition, checkThemeHeaderImage */
 
 "use strict";
 
 const BACKGROUND = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0" +
   "DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";
 const ENCODED_IMAGE_DATA = "iVBORw0KGgoAAAANSUhEUgAAABkAAAAZCAYAAADE6YVjAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0h" +
   "STQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAdhwAAHYcBj+XxZQAAB5dJREFUSMd" +
   "91vmTlEcZB/Bvd7/vO+/ce83O3gfLDUsC4VgIghBUEo2GM9GCFTaQBEISA1qIEVNQ4aggJDGIgAGTlFUKKcqKQpVHaQyny7FrCMiywp4ze+/Mzs67M/P" +
@@ -77,8 +77,25 @@ function testBorderColor(element, expect
                "Element right border color should be set.");
   Assert.equal(computedStyle.borderTopColor,
                hexToCSS(expected),
                "Element top border color should be set.");
   Assert.equal(computedStyle.borderBottomColor,
                hexToCSS(expected),
                "Element bottom border color should be set.");
 }
+
+function checkThemeHeaderImage(window, expectedURL) {
+  const {LightweightThemeManager} = ChromeUtils.import("resource://gre/modules/LightweightThemeManager.jsm");
+
+  let root = window.document.documentElement;
+  if (expectedURL === "none") {
+    Assert.equal(window.getComputedStyle(root).backgroundImage,
+                 "none", "Should have no background image");
+  } else {
+    Assert.equal(window.getComputedStyle(root).backgroundImage,
+                 "-moz-element(#lwt-header-image)",
+                 "Should have -moz-element background image");
+
+    Assert.equal(LightweightThemeManager.themeData.theme.headerImage.src,
+                 expectedURL, "Theme image has expected source");
+  }
+}
--- a/toolkit/modules/LightweightThemeConsumer.jsm
+++ b/toolkit/modules/LightweightThemeConsumer.jsm
@@ -215,17 +215,25 @@ LightweightThemeConsumer.prototype = {
     }
 
     for (let icon of ICONS) {
       let value = theme.icons ? theme.icons[`--${icon}-icon`] : null;
       _setImage(root, active, `--${icon}-icon`, value);
     }
 
     this._setExperiment(active, themeData.experiment, theme.experimental);
-    _setImage(root, active, "--lwt-header-image", theme.headerURL);
+
+    if (theme.headerImage) {
+      this._doc.mozSetImageElement("lwt-header-image", theme.headerImage);
+      root.style.setProperty("--lwt-header-image", "-moz-element(#lwt-header-image)");
+    } else {
+      this._doc.mozSetImageElement("lwt-header-image", null);
+      root.style.removeProperty("--lwt-header-image");
+    }
+
     _setImage(root, active, "--lwt-additional-images", theme.additionalBackgrounds);
     _setProperties(root, active, theme);
 
     if (theme.id != DEFAULT_THEME_ID || this.darkMode) {
       root.setAttribute("lwtheme", "true");
     } else {
       root.removeAttribute("lwtheme");
       root.removeAttribute("lwthemetextcolor");
--- a/toolkit/mozapps/extensions/LightweightThemeManager.jsm
+++ b/toolkit/mozapps/extensions/LightweightThemeManager.jsm
@@ -1,24 +1,47 @@
 /* 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";
 
 var EXPORTED_SYMBOLS = ["LightweightThemeManager"];
 
-// Holds optional fallback theme data that will be returned when no data for an
-// active theme can be found. This the case for WebExtension Themes, for example.
+const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
+const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "hiddenWindow", () => {
+  const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+  let browser = Services.appShell.createWindowlessBrowser(true);
+  let principal = Services.scriptSecurityManager.getSystemPrincipal();
+  browser.docShell.createAboutBlankContentViewer(principal);
+
+  return browser.document.ownerGlobal;
+});
+
+if (AppConstants.DEBUG) {
+  void hiddenWindow;
+}
+
 var _fallbackThemeData = null;
 
 var LightweightThemeManager = {
   set fallbackThemeData(data) {
     if (data && Object.getOwnPropertyNames(data).length) {
       _fallbackThemeData = Object.assign({}, data);
+
+      for (let key of ["theme", "darkTheme"]) {
+        let data = _fallbackThemeData[key] || null;
+        if (data && typeof data.headerURL === "string") {
+          data.headerImage = new hiddenWindow.Image();
+          data.headerImage.src = data.headerURL;
+        }
+      }
     } else {
       _fallbackThemeData = null;
     }
   },
 
   /*
    * Returns the currently active theme, taking the fallback theme into account
    * if we'd be using the default theme otherwise.