Bug 1330341 - Add support for dynamic updates r=mikedeboer

MozReview-Commit-ID: 8wA7J1oX2t

--HG--
extra : rebase_source : e244e9803644676398d952057d416f2d9558e833
This commit is contained in:
Matthew Wein 2017-01-25 15:11:20 -08:00
Родитель 32d84fe236
Коммит 247590ec7d
5 изменённых файлов: 225 добавлений и 52 удалений

Просмотреть файл

@ -5,58 +5,123 @@ Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
"resource://gre/modules/Preferences.jsm");
/* eslint-disable mozilla/balanced-listeners */
extensions.on("manifest_theme", (type, directive, extension, manifest) => {
let enabled = Preferences.get("extensions.webextensions.themes.enabled");
// WeakMap[Extension -> Theme]
let themeMap = new WeakMap();
if (!enabled || !manifest || !manifest.theme) {
return;
}
// Apply theme only if themes are enabled.
let lwtStyles = {footerURL: ""};
if (manifest.theme.colors) {
let colors = manifest.theme.colors;
for (let color of Object.getOwnPropertyNames(colors)) {
let val = colors[color];
// Since values are optional, they may be `null`.
if (val === null) {
continue;
/** Class representing a theme. */
class Theme {
/**
* Creates a theme instance.
*/
constructor() {
// A dictionary of light weight theme styles.
this.lwtStyles = {};
}
if (color == "accentcolor") {
lwtStyles.accentcolor = val;
continue;
}
if (color == "textcolor") {
lwtStyles.textcolor = val;
}
}
/**
* 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(details) {
if (details.colors) {
this.loadColors(details.colors);
}
if (manifest.theme.images) {
let images = manifest.theme.images;
for (let image of Object.getOwnPropertyNames(images)) {
let val = images[image];
if (val === null) {
continue;
if (details.images) {
this.loadImages(details.images);
}
if (image == "headerURL") {
lwtStyles.headerURL = val;
}
}
}
if (lwtStyles.headerURL &&
lwtStyles.accentcolor &&
lwtStyles.textcolor) {
// Lightweight themes require all properties to be defined.
if (this.lwtStyles.headerURL &&
this.lwtStyles.accentcolor &&
this.lwtStyles.textcolor) {
Services.obs.notifyObservers(null,
"lightweight-theme-styling-update",
JSON.stringify(lwtStyles));
JSON.stringify(this.lwtStyles));
}
}
/**
* 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;
if (accentcolor) {
this.lwtStyles.accentcolor = accentcolor;
}
if (textcolor) {
this.lwtStyles.textcolor = textcolor;
}
}
/**
* 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;
if (headerURL) {
this.lwtStyles.headerURL = headerURL;
}
}
/**
* Unloads the currently applied theme.
*/
unload() {
Services.obs.notifyObservers(null,
"lightweight-theme-styling-update",
null);
}
}
/* 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();
theme.load(manifest.theme);
themeMap.set(extension, theme);
});
extensions.on("shutdown", (type, extension) => {
Services.obs.notifyObservers(null, "lightweight-theme-styling-update", null);
let theme = themeMap.get(extension);
// We won't have a theme if theme's aren't enabled.
if (!theme) {
return;
}
theme.unload();
});
/* eslint-enable mozilla/balanced-listeners */
extensions.registerSchemaAPI("theme", "addon_parent", context => {
let {extension} = context;
return {
theme: {
update(details) {
let theme = themeMap.get(extension);
// We won't have a theme if theme's aren't enabled.
if (!theme) {
return;
}
theme.load(details);
},
},
};
});

Просмотреть файл

@ -46,5 +46,25 @@
}
}
]
},
{
"namespace": "theme",
"description": "The theme API allows customizing of visual elements of the browser.",
"permissions": ["manifest:theme"],
"functions": [
{
"name": "update",
"type": "function",
"async": true,
"description": "Make complete or partial updates to the theme. Resolves when the update has completed.",
"parameters": [
{
"name": "details",
"$ref": "manifest.ThemeType",
"description": "The properties of the theme to update."
}
]
}
]
}
]

Просмотреть файл

@ -107,6 +107,7 @@ support-files =
[browser_ext_tabs_update.js]
[browser_ext_tabs_zoom.js]
[browser_ext_tabs_update_url.js]
[browser_ext_themes_dynamic_updates.js]
[browser_ext_themes_lwtsupport.js]
[browser_ext_topwindowid.js]
[browser_ext_url_overrides.js]

Просмотреть файл

@ -0,0 +1,87 @@
"use strict";
// PNG image data for a simple red dot.
const BACKGROUND_1 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";
const ACCENT_COLOR_1 = "#a14040";
const TEXT_COLOR_1 = "#fac96e";
// PNG image data for the Mozilla dino head.
const BACKGROUND_2 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg==";
const ACCENT_COLOR_2 = "#03fe03";
const TEXT_COLOR_2 = "#0ef325";
function hexToRGB(hex) {
hex = parseInt((hex.indexOf("#") > -1 ? hex.substring(1) : hex), 16);
return [hex >> 16, (hex & 0x00FF00) >> 8, (hex & 0x0000FF)];
}
function validateTheme(backgroundImage, accentColor, textColor) {
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.equal(style.backgroundImage, 'url("' + backgroundImage.replace(/"/g, '\\"') + '")',
"Expected correct background image");
Assert.equal(style.backgroundColor, "rgb(" + hexToRGB(accentColor).join(", ") + ")",
"Expected correct accent color");
Assert.equal(style.color, "rgb(" + hexToRGB(textColor).join(", ") + ")",
"Expected correct text color");
}
add_task(function* setup() {
yield SpecialPowers.pushPrefEnv({
set: [["extensions.webextensions.themes.enabled", true]],
});
});
add_task(function* test_dynamic_theme_updates() {
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"theme": {
"images": {
"headerURL": BACKGROUND_1,
},
"colors": {
"accentcolor": ACCENT_COLOR_1,
"textcolor": TEXT_COLOR_1,
},
},
},
background() {
browser.test.onMessage.addListener((msg, details) => {
if (msg != "update-theme") {
browser.test.fail("expected 'update-theme' message");
}
browser.theme.update(details);
browser.test.sendMessage("theme-updated");
});
},
});
yield extension.startup();
validateTheme(BACKGROUND_1, ACCENT_COLOR_1, TEXT_COLOR_1);
extension.sendMessage("update-theme", {
"images": {
"headerURL": BACKGROUND_2,
},
"colors": {
"accentcolor": ACCENT_COLOR_2,
"textcolor": TEXT_COLOR_2,
},
});
yield extension.awaitMessage("theme-updated");
validateTheme(BACKGROUND_2, ACCENT_COLOR_2, TEXT_COLOR_2);
yield extension.unload();
let docEl = window.document.documentElement;
Assert.ok(!docEl.hasAttribute("lwtheme"), "LWT attribute should not be set");
});

Просмотреть файл

@ -1,8 +1,8 @@
"use strict";
const kBackground = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";
const kAccentColor = "#a14040";
const kTextColor = "#fac96e";
const BACKGROUND = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";
const ACCENT_COLOR = "#a14040";
const TEXT_COLOR = "#fac96e";
function hexToRGB(hex) {
hex = parseInt((hex.indexOf("#") > -1 ? hex.substring(1) : hex), 16);
@ -15,16 +15,16 @@ add_task(function* setup() {
});
});
add_task(function* testSupportLWTProperties() {
add_task(function* test_support_LWT_properties() {
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"theme": {
"images": {
"headerURL": kBackground,
"headerURL": BACKGROUND,
},
"colors": {
"accentcolor": kAccentColor,
"textcolor": kTextColor,
"accentcolor": ACCENT_COLOR,
"textcolor": TEXT_COLOR,
},
},
},
@ -39,11 +39,11 @@ add_task(function* testSupportLWTProperties() {
Assert.equal(docEl.getAttribute("lwthemetextcolor"), "bright",
"LWT text color attribute should be set");
Assert.equal(style.backgroundImage, 'url("' + kBackground.replace(/"/g, '\\"') + '")',
Assert.equal(style.backgroundImage, 'url("' + BACKGROUND.replace(/"/g, '\\"') + '")',
"Expected background image");
Assert.equal(style.backgroundColor, "rgb(" + hexToRGB(kAccentColor).join(", ") + ")",
Assert.equal(style.backgroundColor, "rgb(" + hexToRGB(ACCENT_COLOR).join(", ") + ")",
"Expected correct background color");
Assert.equal(style.color, "rgb(" + hexToRGB(kTextColor).join(", ") + ")",
Assert.equal(style.color, "rgb(" + hexToRGB(TEXT_COLOR).join(", ") + ")",
"Expected correct text color");
yield extension.unload();
@ -51,12 +51,12 @@ add_task(function* testSupportLWTProperties() {
Assert.ok(!docEl.hasAttribute("lwtheme"), "LWT attribute should not be set");
});
add_task(function* testLWTRequiresAllPropertiesDefinedImageOnly() {
add_task(function* test_LWT_requires_all_properties_defined_image_only() {
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"theme": {
"images": {
"headerURL": kBackground,
"headerURL": BACKGROUND,
},
},
},
@ -70,13 +70,13 @@ add_task(function* testLWTRequiresAllPropertiesDefinedImageOnly() {
Assert.ok(!docEl.hasAttribute("lwtheme"), "LWT attribute should not be set");
});
add_task(function* testLWTRequiresAllPropertiesDefinedColorsOnly() {
add_task(function* test_LWT_requires_all_properties_defined_colors_only() {
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"theme": {
"colors": {
"accentcolor": kAccentColor,
"textcolor": kTextColor,
"accentcolor": ACCENT_COLOR,
"textcolor": TEXT_COLOR,
},
},
},