Bug 1311722 - Add support for dynamic theme updates r?jaws
MozReview-Commit-ID: 206GJJ3J9DL
--- a/browser/components/extensions/ext-theme.js
+++ b/browser/components/extensions/ext-theme.js
@@ -5,72 +5,93 @@ Cu.import("resource://gre/modules/Servic
// 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);
-}
+class Theme {
+ constructor(manifest) {
+ this.userSheetURI = null;
+ this.LWTStyles = {};
+ this.cssVars = {};
+ this.load(manifest.theme);
+ this.render();
+ }
-Theme.prototype = {
- loadThemeFromManifest(manifest) {
- // A temporary element is created to filter the CSS values that
- // themes can provide.
- let temp = WindowManager.topWindow.document.createElement("temp");
- let cssVars = "";
- let LWTStyles = this.LWTStyles = { headerURL: "", footerURL: "", textcolor: "", accentcolor: "" };
+ load(theme) {
+ if (theme.images) {
+ this.loadImages(theme.images);
+ }
+ if (theme.colors) {
+ this.loadColors(theme.colors);
+ }
+ }
- 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 = cssURL;
- cssVars += `--page-background: ${temp.style.background} !important;`;
- } else if (image == "theme_frame" || image == "headerURL") {
- LWTStyles.headerURL = manifest.theme.images[image];
+ loadImages(images) {
+ // Use a temporary element to filter the CSS values that themes can provide.
+ if (WindowManager.topWindow) {
+ let temp = WindowManager.topWindow.document.createElement("temp");
+ for (let image of Object.getOwnPropertyNames(images)) {
+ if (images[image]) {
+ let cssURL = 'url("' + images[image].replace(/"/g, '\\"') + '")';
+ if (image == "theme_ntp_background") {
+ temp.style.background = cssURL;
+ this.cssVars["--page-background"] = `${temp.style.background} !important;`;
+ } else if (image == "theme_frame" || image == "headerURL") {
+ this.LWTStyles.headerURL = images[image];
+ }
+ }
}
}
+ }
- for (let color of Object.getOwnPropertyNames(manifest.theme.colors || {})) {
- let val = manifest.theme.colors[color];
+ loadColors(colors) {
+ for (let color of Object.getOwnPropertyNames(colors)) {
+ let val = details.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;
+ this.LWTStyles.accentcolor = cssColor;
break;
case "tab_text":
case "textcolor":
- LWTStyles.textcolor = cssColor;
+ this.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);
+ render() {
+ if (this.cssVars) {
+ let aboutHomeStyles = `
+ @-moz-document url("about:home"),
+ url("chrome://browser/content/abouthome/aboutHome.xhtml") {
+ :root {
+ --page-background: ${this.cssVars["--page-background"]}
+ }
+ }`;
+ let dataURL = `data:text/css,${aboutHomeStyles}`;
- if (LWTStyles.headerURL) {
+ if (this.userSheetURI) {
+ styleSheetService.unregisterSheet(this.userSheetURI, styleSheetService.USER_SHEET);
+ }
+
+ this.userSheetURI = ioService.newURI(dataURL, null, null);
+ styleSheetService.loadAndRegisterSheet(this.userSheetURI, styleSheetService.USER_SHEET);
+ }
+
+ if (this.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);
}
@@ -86,12 +107,19 @@ extensions.on("shutdown", (type, extensi
if (themeMap.has(extension)) {
themeMap.get(extension).shutdown();
themeMap.delete(extension);
}
});
/* eslint-enable mozilla/balanced-listeners */
extensions.registerSchemaAPI("theme", "addon_parent", context => {
+ let {extension} = context;
return {
- theme: {},
+ theme: {
+ update(details) {
+ let theme = themeMap.get(extension);
+ theme.load(details);
+ theme.render();
+ }
+ },
};
});
--- a/browser/components/extensions/schemas/theme.json
+++ b/browser/components/extensions/schemas/theme.json
@@ -2,74 +2,91 @@
// 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/.
[
{
"namespace": "manifest",
"types": [
{
- "$extend": "WebExtensionManifest",
+ "id": "ThemeType",
+ "type": "object",
"properties": {
- "theme": {
+ "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": {
- "images": {
- "type": "object",
- "optional": true,
- "properties": {
- "headerURL": {
- "type": "string",
- "optional": true
- },
- "theme_frame": {
- "type": "string",
- "optional": true
- },
- "theme_ntp_background": {
- "type": "string",
- "optional": true
- }
- }
+ "accentcolor": {
+ "type": "string",
+ "optional": true
+ },
+ "frame": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "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
- }
- }
+ "tab_text": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "optional": true
+ },
+ "textcolor": {
+ "type": "string",
+ "optional": true
}
}
}
}
+ },
+ {
+ "$extend": "WebExtensionManifest",
+ "properties": {
+ "theme": {
+ "$ref": "ThemeType",
+ "optional": true
+ }
+ }
}
]
},
{
"namespace": "theme",
"description": "The theme API allows customizing of visual elements of the browser.",
"permissions": ["manifest:theme"],
"functions": [
{
- "name": "dummyFunction",
+ "name": "update",
"type": "function",
"async": true,
- "description": "Dummy function, necessary to register the 'theme' namespace."
+ "description": "Update the theme",
+ "parameters": [
+ {
+ "name": "details",
+ "$ref": "manifest.ThemeType",
+ "description": "Update the existing theme."
+ }
+ ]
}
]
}
]
--- a/browser/components/extensions/test/browser/browser_ext_theme_abouthomebackground.js
+++ b/browser/components/extensions/test/browser/browser_ext_theme_abouthomebackground.js
@@ -1,33 +1,55 @@
/* -*- 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==";
+const kBackgrounds = [
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==",
+ "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDE2IDE2Ij4KICA8cGF0aCBkPSJNOCwxMkwzLDcsNCw2bDQsNCw0LTQsMSwxWiIgZmlsbD0iIzZBNkE2QSIgLz4KPC9zdmc+Cg==",
+];
add_task(function* testAboutHomeBackground() {
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home", true);
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"theme": {
"images": {
- "theme_ntp_background": kBackground
+ "theme_ntp_background": kBackgrounds[0]
}
}
+ },
+ background() {
+ browser.test.onMessage.addListener((message, data) => {
+ if (message === "update-theme") {
+ browser.theme.update(data);
+ browser.test.sendMessage("theme-updated");
+ }
+ });
}
});
yield extension.startup();
let win = tab.linkedBrowser.contentWindow;
let docEl = win.document.documentElement;
let background = win.getComputedStyle(docEl).backgroundImage;
- is(background, `url("` + kBackground + `")`, "Expected background image");
+ is(background, `url("` + kBackgrounds[0] + `")`, "Expected background image");
+
+ extension.sendMessage("update-theme", {
+ "images": {
+ "theme_ntp_background": kBackgrounds[1],
+ }
+ });
+
+ yield extension.awaitMessage("theme-updated");
+
+ background = win.getComputedStyle(docEl).backgroundImage;
+ is(background, `url("` + kBackgrounds[1] + `")`, "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);
});