Bug 1335171 - Implement Chrome-parity support for a few theme properties. r=mattw,mikedeboer
authorJared Wein <jwein@mozilla.com>
Tue, 31 Jan 2017 17:32:54 -0500
changeset 380871 4e2ef9e6332f186cfface7c5b04ae88ffa2bfaf0
parent 380870 7f00eae7c948a6fae3e86a18ab094038732331f2
child 380872 064f8ea8ee11044850ed0c39de4af835cc55edd7
push id1468
push userasasaki@mozilla.com
push dateMon, 05 Jun 2017 19:31:07 +0000
treeherdermozilla-release@0641fc6ee9d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmattw, mikedeboer
bugs1335171
milestone54.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 1335171 - Implement Chrome-parity support for a few theme properties. r=mattw,mikedeboer MozReview-Commit-ID: C244c3Oh4bC
browser/components/extensions/ext-theme.js
browser/components/extensions/schemas/theme.json
browser/components/extensions/test/browser/browser-common.ini
browser/components/extensions/test/browser/browser_ext_themes_chromeparity.js
--- a/browser/components/extensions/ext-theme.js
+++ b/browser/components/extensions/ext-theme.js
@@ -7,20 +7,24 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 
 // WeakMap[Extension -> Theme]
 let themeMap = new WeakMap();
 
 /** Class representing a theme. */
 class Theme {
   /**
    * Creates a theme instance.
+   *
+   * @param {string} baseURI The base URI of the extension, used to
+   *   resolve relative filepaths.
    */
-  constructor() {
+  constructor(baseURI) {
     // A dictionary of light weight theme styles.
     this.lwtStyles = {};
+    this.baseURI = baseURI;
   }
 
   /**
    * 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.
@@ -45,37 +49,63 @@ class Theme {
   }
 
   /**
    * Helper method for loading colors found in the extension's manifest.
    *
    * @param {Object} colors Dictionary mapping color properties to values.
    */
   loadColors(colors) {
-    let {accentcolor, textcolor} = colors;
+    for (let color of Object.keys(colors)) {
+      Services.console.logStringMessage(`parsing color=${color}`);
+      let val = colors[color];
+
+      if (!val) {
+        continue;
+      }
 
-    if (accentcolor) {
-      this.lwtStyles.accentcolor = accentcolor;
-    }
+      let cssColor = val;
+      if (Array.isArray(val)) {
+        cssColor = "rgb" + (val.length > 3 ? "a" : "") + "(" + val.join(",") + ")";
+      }
 
-    if (textcolor) {
-      this.lwtStyles.textcolor = textcolor;
+      switch (color) {
+        case "accentcolor":
+        case "frame":
+          this.lwtStyles.accentcolor = cssColor;
+          break;
+        case "textcolor":
+        case "tab_text":
+          this.lwtStyles.textcolor = cssColor;
+          break;
+      }
     }
   }
 
   /**
    * Helper method for loading images found in the extension's manifest.
    *
    * @param {Object} images Dictionary mapping image properties to values.
    */
   loadImages(images) {
-    let {headerURL} = images;
+    for (let image of Object.keys(images)) {
+      let val = images[image];
+
+      if (!val) {
+        continue;
+      }
 
-    if (headerURL) {
-      this.lwtStyles.headerURL = headerURL;
+      switch (image) {
+        case "headerURL":
+        case "theme_frame": {
+          let resolvedURL = this.baseURI.resolve(val);
+          this.lwtStyles.headerURL = resolvedURL;
+          break;
+        }
+      }
     }
   }
 
   /**
    * Unloads the currently applied theme.
    */
   unload() {
     Services.obs.notifyObservers(null,
@@ -86,17 +116,17 @@ class Theme {
 
 /* eslint-disable mozilla/balanced-listeners */
 extensions.on("manifest_theme", (type, directive, extension, manifest) => {
   if (!Preferences.get("extensions.webextensions.themes.enabled")) {
     // Return early if themes are disabled.
     return;
   }
 
-  let theme = new Theme();
+  let theme = new Theme(extension.baseURI);
   theme.load(manifest.theme);
   themeMap.set(extension, theme);
 });
 
 extensions.on("shutdown", (type, extension) => {
   let theme = themeMap.get(extension);
 
   // We won't have a theme if theme's aren't enabled.
--- a/browser/components/extensions/schemas/theme.json
+++ b/browser/components/extensions/schemas/theme.json
@@ -12,27 +12,45 @@
         "properties": {
           "images": {
             "type": "object",
             "optional": true,
             "properties": {
               "headerURL": {
                 "type": "string",
                 "optional": true
+              },
+              "theme_frame": {
+                "type": "string",
+                "optional": true
               }
             }
           },
           "colors": {
             "type": "object",
             "optional": true,
             "properties": {
               "accentcolor": {
                 "type": "string",
                 "optional": true
               },
+              "frame": {
+                "type": "array",
+                "items": {
+                  "type": "number"
+                },
+                "optional": true
+              },
+              "tab_text": {
+                "type": "array",
+                "items": {
+                  "type": "number"
+                },
+                "optional": true
+              },
               "textcolor": {
                 "type": "string",
                 "optional": true
               }
             }
           }
         }
       },
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -102,16 +102,17 @@ support-files =
 [browser_ext_tabs_query.js]
 [browser_ext_tabs_reload.js]
 [browser_ext_tabs_reload_bypass_cache.js]
 [browser_ext_tabs_sendMessage.js]
 [browser_ext_tabs_cookieStoreId.js]
 [browser_ext_tabs_update.js]
 [browser_ext_tabs_zoom.js]
 [browser_ext_tabs_update_url.js]
+[browser_ext_themes_chromeparity.js]
 [browser_ext_themes_dynamic_updates.js]
 [browser_ext_themes_lwtsupport.js]
 [browser_ext_topwindowid.js]
 [browser_ext_url_overrides.js]
 [browser_ext_webRequest.js]
 [browser_ext_webNavigation_frameId0.js]
 [browser_ext_webNavigation_getFrames.js]
 [browser_ext_webNavigation_urlbar_transitions.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_themes_chromeparity.js
@@ -0,0 +1,55 @@
+"use strict";
+
+const ENCODED_IMAGE_DATA = "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";
+
+function imageBufferFromDataURI(encodedImageData) {
+  let decodedImageData = atob(encodedImageData);
+  return Uint8Array.from(decodedImageData, byte => byte.charCodeAt(0)).buffer;
+}
+
+add_task(function* setup() {
+  yield SpecialPowers.pushPrefEnv({
+    set: [["extensions.webextensions.themes.enabled", true]],
+  });
+});
+
+add_task(function* test_support_theme_frame() {
+  const FRAME_COLOR = [71, 105, 91];
+  const TAB_TEXT_COLOR = [207, 221, 192, .9];
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "theme": {
+        "images": {
+          "theme_frame": "face.png",
+        },
+        "colors": {
+          "frame": FRAME_COLOR,
+          "tab_text": TAB_TEXT_COLOR,
+        },
+      },
+    },
+    files: {
+      "face.png": imageBufferFromDataURI(ENCODED_IMAGE_DATA),
+    },
+  });
+
+  yield 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}`);
+  Assert.equal(style.backgroundColor, "rgb(" + FRAME_COLOR.join(", ") + ")",
+    "Expected correct background color");
+  Assert.equal(style.color, "rgba(" + TAB_TEXT_COLOR.join(", ") + ")",
+    "Expected correct text color");
+
+  yield extension.unload();
+
+  Assert.ok(!docEl.hasAttribute("lwtheme"), "LWT attribute should not be set");
+});