Bug 1309908 - support the Lightweight Themes feature set in the themes API. r?k-9 draft
authorMike de Boer <mdeboer@mozilla.com>
Fri, 14 Oct 2016 11:38:11 +0200
changeset 425141 04252205173191585775ae8ca279066ff475aa30
parent 424826 446f5bcdeba7b9e4c8c6ce082d81d66c4bbbe343
child 533861 8a0ef4545bf6d0f1b23124eae7f7f5d704b27aec
push id32363
push usermdeboer@mozilla.com
push dateFri, 14 Oct 2016 09:38:34 +0000
reviewersk-9
bugs1309908
milestone52.0a1
Bug 1309908 - support the Lightweight Themes feature set in the themes API. r?k-9 MozReview-Commit-ID: CxNmI55IZWN
browser/components/extensions/ext-theme.js
browser/components/extensions/schemas/theme.json
browser/components/extensions/test/browser/browser.ini
browser/components/extensions/test/browser/browser_ext_theme_abouthomebackground.js
browser/components/extensions/test/browser/browser_ext_theme_lwtsupport.js
--- a/browser/components/extensions/ext-theme.js
+++ b/browser/components/extensions/ext-theme.js
@@ -1,56 +1,84 @@
 "use strict";
 
+Cu.import("resource://gre/modules/Services.jsm");
+
 // WeakMap[Extension -> Theme]
 let themeMap = new WeakMap();
 
 let styleSheetService = Components.classes["@mozilla.org/content/style-sheet-service;1"]
                                   .getService(Components.interfaces.nsIStyleSheetService);
 let ioService = Components.classes["@mozilla.org/network/io-service;1"]
                           .getService(Components.interfaces.nsIIOService);
 
 function Theme(manifest) {
   this.userSheetURI = null;
+  this.LWTStyles = null;
 
   this.loadThemeFromManifest(manifest);
 }
 
 Theme.prototype = {
   loadThemeFromManifest(manifest) {
-    if (!manifest || !manifest.theme || !manifest.theme.images) {
-      return;
-    }
-
     // A temporary element is created to filter the CSS values that
     // themes can provide.
     let temp = WindowManager.topWindow.document.createElement("temp");
     let cssVars = "";
-    for (let image of Object.keys(manifest.theme.images)) {
+    let LWTStyles = this.LWTStyles = { headerURL: "", footerURL: "", textcolor: "", accentcolor: "" };
+
+    for (let image of Object.getOwnPropertyNames(manifest.theme.images || {})) {
+      let cssURL = 'url("' + manifest.theme.images[image].replace(/"/g, '\\"') + '")';
       if (image == "theme_ntp_background") {
-        temp.style.background = manifest.theme.images.theme_ntp_background;
+        temp.style.background = cssURL;
         cssVars += `--page-background: ${temp.style.background} !important;`;
+      } else if (image == "theme_frame" || image == "headerURL") {
+        LWTStyles.headerURL = manifest.theme.images[image];
       }
     }
+
+    for (let color of Object.getOwnPropertyNames(manifest.theme.colors || {})) {
+      let val = manifest.theme.colors[color];
+      let cssColor = Array.isArray(val) ?
+        "rgb" + (val.length > 3 ? "a" : "") + "(" + val.join(",") + ")" : val;
+      switch (color) {
+        case "accentcolor":
+        case "frame":
+          LWTStyles.accentcolor = cssColor;
+          break;
+        case "tab_text":
+        case "textcolor":
+          LWTStyles.textcolor = cssColor;
+          break;
+      }
+    }
+
     let aboutHomeStyles = `
       @-moz-document url("about:home"),
                      url("chrome://browser/content/abouthome/aboutHome.xhtml") {
         :root {
           ${cssVars}
         }
       }`;
     let dataURL = `data:text/css,${aboutHomeStyles}`;
     this.userSheetURI = ioService.newURI(dataURL, null, null);
     styleSheetService.loadAndRegisterSheet(this.userSheetURI, styleSheetService.USER_SHEET);
+
+    if (LWTStyles.headerURL) {
+      Services.obs.notifyObservers(null, "lightweight-theme-styling-update", JSON.stringify(LWTStyles));
+    }
   },
 
   shutdown() {
     if (this.userSheetURI) {
       styleSheetService.unregisterSheet(this.userSheetURI, styleSheetService.USER_SHEET);
     }
+    if (this.LWTStyles.headerURL) {
+      Services.obs.notifyObservers(null, "lightweight-theme-styling-update", null);
+    }
   }
 }
 
 /* eslint-disable mozilla/balanced-listeners */
 extensions.on("manifest_theme", (type, directive, extension, manifest) => {
   themeMap.set(extension, new Theme(manifest));
 });
 
--- a/browser/components/extensions/schemas/theme.json
+++ b/browser/components/extensions/schemas/theme.json
@@ -12,21 +12,51 @@
           "theme": {
             "type": "object",
             "optional": true,
             "properties": {
               "images": {
                 "type": "object",
                 "optional": true,
                 "properties": {
+                  "headerURL": {
+                    "type": "string",
+                    "optional": true
+                  },
+                  "theme_frame": {
+                    "type": "string",
+                    "optional": true
+                  },
                   "theme_ntp_background": {
                     "type": "string",
                     "optional": true
                   }
                 }
+              },
+              "colors": {
+                "type": "object",
+                "optional": true,
+                "properties": {
+                  "accentcolor": {
+                    "type": "string",
+                    "optional": true
+                  },
+                  "frame": {
+                    "type": "array",
+                    "optional": true
+                  },
+                  "tab_text": {
+                    "type": "array",
+                    "optional": true
+                  },
+                  "textcolor": {
+                    "type": "string",
+                    "optional": true
+                  }
+                }
               }
             }
           }
         }
       }
     ]
   },
   {
--- a/browser/components/extensions/test/browser/browser.ini
+++ b/browser/components/extensions/test/browser/browser.ini
@@ -81,16 +81,17 @@ tags = webextensions
 [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_update.js]
 [browser_ext_tabs_zoom.js]
 [browser_ext_tabs_update_url.js]
 [browser_ext_theme_abouthomebackground.js]
+[browser_ext_theme_lwtsupport.js]
 [browser_ext_topwindowid.js]
 [browser_ext_webNavigation_frameId0.js]
 [browser_ext_webNavigation_getFrames.js]
 [browser_ext_webNavigation_urlbar_transitions.js]
 [browser_ext_windows.js]
 [browser_ext_windows_create.js]
 tags = fullscreen
 [browser_ext_windows_create_tabId.js]
--- a/browser/components/extensions/test/browser/browser_ext_theme_abouthomebackground.js
+++ b/browser/components/extensions/test/browser/browser_ext_theme_abouthomebackground.js
@@ -1,13 +1,13 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
-const kBackground = `url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==")`;
+const kBackground = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";
 
 add_task(function* testAboutHomeBackground() {
   let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home", true);
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "theme": {
         "images": {
@@ -17,17 +17,17 @@ add_task(function* testAboutHomeBackgrou
     }
   });
 
   yield extension.startup();
 
   let win = tab.linkedBrowser.contentWindow;
   let docEl = win.document.documentElement;
   let background = win.getComputedStyle(docEl).backgroundImage;
-  is(background, kBackground, "Expected background image");
+  is(background, `url("` + kBackground + `")`, "Expected background image");
 
   yield extension.unload();
 
   background = win.getComputedStyle(docEl).backgroundImage;
   is(background, "none", "The background image should be cleared when the extension is unloaded");
 
   yield BrowserTestUtils.removeTab(tab);
 });
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_theme_lwtsupport.js
@@ -0,0 +1,98 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+const kBackground = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";
+
+function hexToRGB(hex) {
+  hex = parseInt((hex.indexOf("#") > -1 ? hex.substring(1) : hex), 16);
+  return [ hex >> 16, (hex & 0x00FF00) >> 8, (hex & 0x0000FF) ];
+}
+
+add_task(function* testLWTSupportNewProperties() {
+  const kFrameColor = [71, 105, 91];
+  const kTabTextColor = [207, 221, 192, .9];
+
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home", true);
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "theme": {
+        "images": {
+          "theme_frame": kBackground
+        },
+        "colors": {
+          "frame": kFrameColor,
+          "tab_text": kTabTextColor
+        }
+      }
+    }
+  });
+
+  yield extension.startup();
+
+  let win = tab.ownerDocument.defaultView;
+  let docEl = tab.ownerDocument.documentElement;
+  let style = win.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.equal(style.backgroundImage, 'url("' + kBackground.replace(/"/g, '\\"') + '")',
+    "Expected background image");
+  Assert.equal(style.backgroundColor, "rgb(" + kFrameColor.join(", ") + ")",
+    "Expected correct background color");
+  Assert.equal(style.color, "rgba(" + kTabTextColor.join(", ") + ")",
+    "Expected correct text color");
+
+  yield extension.unload();
+
+  Assert.ok(!docEl.hasAttribute("lwtheme"), "LWT attribute should not be set");
+
+  yield BrowserTestUtils.removeTab(tab);
+});
+
+add_task(function* testLWTSupportLegacyProperties() {
+  const kAccentColor = "#a14040";
+  const kTextColor = "#fac96e";
+
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home", true);
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "theme": {
+        "images": {
+          "headerURL": kBackground
+        },
+        "colors": {
+          "accentcolor": kAccentColor,
+          "textcolor": kTextColor
+        }
+      }
+    }
+  });
+
+  yield extension.startup();
+
+  let win = tab.ownerDocument.defaultView;
+  let docEl = tab.ownerDocument.documentElement;
+  let style = win.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.equal(style.backgroundImage, 'url("' + kBackground.replace(/"/g, '\\"') + '")',
+    "Expected background image");
+  Assert.equal(style.backgroundColor, "rgb(" + hexToRGB(kAccentColor).join(", ") + ")",
+    "Expected correct background color");
+  Assert.equal(style.color, "rgb(" + hexToRGB(kTextColor).join(", ") + ")",
+    "Expected correct text color");
+
+  yield extension.unload();
+
+  Assert.ok(!docEl.hasAttribute("lwtheme"), "LWT attribute should not be set");
+
+  yield BrowserTestUtils.removeTab(tab);
+});