Bug 1525762: Part 5 - Fix FOUC at startup when non-default theme is used. r=aswan
authorKris Maglione <maglione.k@gmail.com>
Thu, 21 Mar 2019 13:31:59 -0700
changeset 466982 1b30a2c10d998efaf789db9cf66c14b5a965e13d
parent 466981 238bd73cdf0b759712c6b5ae8a73d77a930a0663
child 466983 5d58601921993a4ae6bf87849eb295b6ffe389ee
push id35789
push userbtara@mozilla.com
push dateSun, 31 Mar 2019 09:00:52 +0000
treeherdermozilla-central@c06dfc552c64 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaswan
bugs1525762
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 1525762: Part 5 - Fix FOUC at startup when non-default theme is used. r=aswan The static theme startup code is both super asynchronous and super inefficient. It currently takes a noticeable amount of time after startup to finish its work and apply its theme, which results in the user seeing a flash of the default theme before their selected them is applied. This is particularly noticeable when dark mode themes are enabled. This patch caches the fully-processed theme data in the addonStartup cache, and applies it immediately after extension startup begins, if it's available.
toolkit/components/extensions/Extension.jsm
toolkit/components/extensions/parent/ext-theme.js
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -44,16 +44,17 @@ XPCOMUtils.defineLazyModuleGetters(this,
   AsyncShutdown: "resource://gre/modules/AsyncShutdown.jsm",
   ExtensionPermissions: "resource://gre/modules/ExtensionPermissions.jsm",
   ExtensionProcessScript: "resource://gre/modules/ExtensionProcessScript.jsm",
   ExtensionStorage: "resource://gre/modules/ExtensionStorage.jsm",
   ExtensionStorageIDB: "resource://gre/modules/ExtensionStorageIDB.jsm",
   ExtensionTelemetry: "resource://gre/modules/ExtensionTelemetry.jsm",
   FileSource: "resource://gre/modules/L10nRegistry.jsm",
   L10nRegistry: "resource://gre/modules/L10nRegistry.jsm",
+  LightweightThemeManager: "resource://gre/modules/LightweightThemeManager.jsm",
   Log: "resource://gre/modules/Log.jsm",
   MessageChannel: "resource://gre/modules/MessageChannel.jsm",
   NetUtil: "resource://gre/modules/NetUtil.jsm",
   OS: "resource://gre/modules/osfile.jsm",
   PluralForm: "resource://gre/modules/PluralForm.jsm",
   Schemas: "resource://gre/modules/Schemas.jsm",
   XPIProvider: "resource://gre/modules/addons/XPIProvider.jsm",
 });
@@ -1436,16 +1437,20 @@ class Extension extends ExtensionData {
     this.whiteListedHosts = null;
     this._optionalOrigins = null;
     this.webAccessibleResources = null;
 
     this.registeredContentScripts = new Map();
 
     this.emitter = new EventEmitter();
 
+    if (this.startupData.lwtData && this.startupReason == "APP_STARTUP") {
+      LightweightThemeManager.fallbackThemeData = this.startupData.lwtData;
+    }
+
     /* eslint-disable mozilla/balanced-listeners */
     this.on("add-permissions", (ignoreEvent, permissions) => {
       for (let perm of permissions.permissions) {
         this.permissions.add(perm);
       }
 
       if (permissions.origins.length > 0) {
         let patterns = this.whiteListedHosts.patterns.map(host => host.pattern);
--- a/toolkit/components/extensions/parent/ext-theme.js
+++ b/toolkit/components/extensions/parent/ext-theme.js
@@ -33,89 +33,103 @@ let windowOverrides = new Map();
  */
 class Theme {
   /**
    * Creates a theme instance.
    *
    * @param {string} extension Extension that created the theme.
    * @param {Integer} windowId The windowId where the theme is applied.
    */
-  constructor({extension, details, darkDetails, windowId, experiment}) {
+  constructor({extension, details, darkDetails, windowId, experiment, startupData}) {
     this.extension = extension;
     this.details = details;
     this.darkDetails = darkDetails;
     this.windowId = windowId;
 
-    this.lwtStyles = {
-      icons: {},
-    };
-    this.lwtDarkStyles = null;
+    if (startupData && startupData.lwtData) {
+      Object.assign(this, startupData);
+    } else {
+      this.lwtStyles = {
+        icons: {},
+      };
+      this.lwtDarkStyles = null;
 
-    if (experiment) {
-      const canRunExperiment = AppConstants.MOZ_ALLOW_LEGACY_EXTENSIONS &&
-        Services.prefs.getBoolPref("extensions.legacy.enabled");
-      if (canRunExperiment) {
-        this.lwtStyles.experimental = {
-          colors: {},
-          images: {},
-          properties: {},
-        };
-        const {baseURI} = this.extension;
-        if (experiment.stylesheet) {
-          experiment.stylesheet = baseURI.resolve(experiment.stylesheet);
+      if (experiment) {
+        const canRunExperiment = AppConstants.MOZ_ALLOW_LEGACY_EXTENSIONS &&
+          Services.prefs.getBoolPref("extensions.legacy.enabled");
+        if (canRunExperiment) {
+          this.lwtStyles.experimental = {
+            colors: {},
+            images: {},
+            properties: {},
+          };
+          const {baseURI} = this.extension;
+          if (experiment.stylesheet) {
+            experiment.stylesheet = baseURI.resolve(experiment.stylesheet);
+          }
+          this.experiment = experiment;
+        } else {
+          const {logger} = this.extension;
+          logger.warn("This extension is not allowed to run theme experiments");
+          return;
         }
-        this.experiment = experiment;
-      } else {
-        const {logger} = this.extension;
-        logger.warn("This extension is not allowed to run theme experiments");
-        return;
       }
     }
     this.load();
   }
 
   /**
    * Loads a theme by reading the properties from the extension's manifest.
    * This method will override any currently applied theme.
    *
    * @param {Object} details Theme part of the manifest. Supported
    *   properties can be found in the schema under ThemeType.
    */
   load() {
-    this.loadDetails(this.details, this.lwtStyles);
-    if (this.darkDetails) {
-      this.lwtDarkStyles = {
-        icons: {},
-      };
-      this.loadDetails(this.darkDetails, this.lwtDarkStyles);
-    }
+    if (!this.lwtData) {
+      this.loadDetails(this.details, this.lwtStyles);
+      if (this.darkDetails) {
+        this.lwtDarkStyles = {
+          icons: {},
+        };
+        this.loadDetails(this.darkDetails, this.lwtDarkStyles);
+      }
 
-    let lwtData = {
-      theme: this.lwtStyles,
-      darkTheme: this.lwtDarkStyles,
-    };
+      this.lwtData = {
+        theme: this.lwtStyles,
+        darkTheme: this.lwtDarkStyles,
+      };
 
-    if (this.experiment) {
-      lwtData.experiment = this.experiment;
+      if (this.experiment) {
+        this.lwtData.experiment = this.experiment;
+      }
+
+      this.extension.startupData = {
+        lwtData: this.lwtData,
+        lwtStyles: this.lwtStyles,
+        lwtDarkStyles: this.lwtDarkStyles,
+        experiment: this.experiment,
+      };
+      this.extension.saveStartupData();
     }
 
     if (this.windowId) {
-      lwtData.window =
+      this.lwtData.window =
         getWinUtils(windowTracker.getWindow(this.windowId)).outerWindowID;
       windowOverrides.set(this.windowId, this);
     } else {
       windowOverrides.clear();
       defaultTheme = this;
-      LightweightThemeManager.fallbackThemeData = lwtData;
+      LightweightThemeManager.fallbackThemeData = this.lwtData;
     }
     onUpdatedEmitter.emit("theme-updated", this.details, this.windowId);
 
     Services.obs.notifyObservers(null,
                                  "lightweight-theme-styling-update",
-                                 JSON.stringify(lwtData));
+                                 JSON.stringify(this.lwtData));
   }
 
   /**
    * @param {Object} details Details
    * @param {Object} styles Styles object in which to store the colors.
    */
   loadDetails(details, styles) {
     if (details.colors) {
@@ -399,16 +413,17 @@ this.theme = class extends ExtensionAPI 
     let {extension} = this;
     let {manifest} = extension;
 
     defaultTheme = new Theme({
       extension,
       details: manifest.theme,
       darkDetails: manifest.dark_theme,
       experiment: manifest.theme_experiment,
+      startupData: extension.startupData,
     });
   }
 
   onShutdown(reason) {
     if (reason === "APP_SHUTDOWN") {
       return;
     }