summaryrefslogtreecommitdiffstats
path: root/toolkit/modules/LightweightThemeConsumer.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/modules/LightweightThemeConsumer.jsm')
-rw-r--r--toolkit/modules/LightweightThemeConsumer.jsm180
1 files changed, 180 insertions, 0 deletions
diff --git a/toolkit/modules/LightweightThemeConsumer.jsm b/toolkit/modules/LightweightThemeConsumer.jsm
new file mode 100644
index 000000000..1dd4c976a
--- /dev/null
+++ b/toolkit/modules/LightweightThemeConsumer.jsm
@@ -0,0 +1,180 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+this.EXPORTED_SYMBOLS = ["LightweightThemeConsumer"];
+
+const {utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/AppConstants.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeImageOptimizer",
+ "resource://gre/modules/addons/LightweightThemeImageOptimizer.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+this.LightweightThemeConsumer =
+ function LightweightThemeConsumer(aDocument) {
+ this._doc = aDocument;
+ this._win = aDocument.defaultView;
+ this._footerId = aDocument.documentElement.getAttribute("lightweightthemesfooter");
+
+ if (PrivateBrowsingUtils.isWindowPrivate(this._win) &&
+ !PrivateBrowsingUtils.permanentPrivateBrowsing) {
+ return;
+ }
+
+ let screen = this._win.screen;
+ this._lastScreenWidth = screen.width;
+ this._lastScreenHeight = screen.height;
+
+ Services.obs.addObserver(this, "lightweight-theme-styling-update", false);
+
+ var temp = {};
+ Cu.import("resource://gre/modules/LightweightThemeManager.jsm", temp);
+ this._update(temp.LightweightThemeManager.currentThemeForDisplay);
+ this._win.addEventListener("resize", this);
+}
+
+LightweightThemeConsumer.prototype = {
+ _lastData: null,
+ _lastScreenWidth: null,
+ _lastScreenHeight: null,
+ // Whether the active lightweight theme should be shown on the window.
+ _enabled: true,
+ // Whether a lightweight theme is enabled.
+ _active: false,
+
+ enable: function() {
+ this._enabled = true;
+ this._update(this._lastData);
+ },
+
+ disable: function() {
+ // Dance to keep the data, but reset the applied styles:
+ let lastData = this._lastData
+ this._update(null);
+ this._enabled = false;
+ this._lastData = lastData;
+ },
+
+ getData: function() {
+ return this._enabled ? Cu.cloneInto(this._lastData, this._win) : null;
+ },
+
+ observe: function (aSubject, aTopic, aData) {
+ if (aTopic != "lightweight-theme-styling-update")
+ return;
+
+ this._update(JSON.parse(aData));
+ },
+
+ handleEvent: function (aEvent) {
+ let {width, height} = this._win.screen;
+
+ if (this._lastScreenWidth != width || this._lastScreenHeight != height) {
+ this._lastScreenWidth = width;
+ this._lastScreenHeight = height;
+ if (!this._active)
+ return;
+ this._update(this._lastData);
+ Services.obs.notifyObservers(this._win, "lightweight-theme-optimized",
+ JSON.stringify(this._lastData));
+ }
+ },
+
+ destroy: function () {
+ if (!PrivateBrowsingUtils.isWindowPrivate(this._win) ||
+ PrivateBrowsingUtils.permanentPrivateBrowsing) {
+ Services.obs.removeObserver(this, "lightweight-theme-styling-update");
+
+ this._win.removeEventListener("resize", this);
+ }
+
+ this._win = this._doc = null;
+ },
+
+ _update: function (aData) {
+ if (!aData) {
+ aData = { headerURL: "", footerURL: "", textcolor: "", accentcolor: "" };
+ this._lastData = aData;
+ } else {
+ this._lastData = aData;
+ aData = LightweightThemeImageOptimizer.optimize(aData, this._win.screen);
+ }
+ if (!this._enabled)
+ return;
+
+ let root = this._doc.documentElement;
+ let active = !!aData.headerURL;
+ let stateChanging = (active != this._active);
+
+ // We need to clear these either way: either because the theme is being removed,
+ // or because we are applying a new theme and the data might be bogus CSS,
+ // so if we don't reset first, it'll keep the old value.
+ root.style.removeProperty("color");
+ root.style.removeProperty("background-color");
+ if (active) {
+ root.style.color = aData.textcolor || "black";
+ root.style.backgroundColor = aData.accentcolor || "white";
+ let [r, g, b] = _parseRGB(this._doc.defaultView.getComputedStyle(root, "").color);
+ let luminance = 0.2125 * r + 0.7154 * g + 0.0721 * b;
+ root.setAttribute("lwthemetextcolor", luminance <= 110 ? "dark" : "bright");
+ root.setAttribute("lwtheme", "true");
+ } else {
+ root.removeAttribute("lwthemetextcolor");
+ root.removeAttribute("lwtheme");
+ }
+
+ this._active = active;
+
+ _setImage(root, active, aData.headerURL);
+ if (this._footerId) {
+ let footer = this._doc.getElementById(this._footerId);
+ footer.style.backgroundColor = active ? aData.accentcolor || "white" : "";
+ _setImage(footer, active, aData.footerURL);
+ if (active && aData.footerURL)
+ footer.setAttribute("lwthemefooter", "true");
+ else
+ footer.removeAttribute("lwthemefooter");
+ }
+
+ // On OS X, we extend the lightweight theme into the titlebar, which means setting
+ // the chromemargin attribute. Some XUL applications already draw in the titlebar,
+ // so we need to save the chromemargin value before we overwrite it with the value
+ // that lets us draw in the titlebar. We stash this value on the root attribute so
+ // that XUL applications have the ability to invalidate the saved value.
+ if (AppConstants.platform == "macosx" && stateChanging) {
+ if (!root.hasAttribute("chromemargin-nonlwtheme")) {
+ root.setAttribute("chromemargin-nonlwtheme", root.getAttribute("chromemargin"));
+ }
+
+ if (active) {
+ root.setAttribute("chromemargin", "0,-1,-1,-1");
+ } else {
+ let defaultChromemargin = root.getAttribute("chromemargin-nonlwtheme");
+ if (defaultChromemargin) {
+ root.setAttribute("chromemargin", defaultChromemargin);
+ } else {
+ root.removeAttribute("chromemargin");
+ }
+ }
+ }
+ Services.obs.notifyObservers(this._win, "lightweight-theme-window-updated",
+ JSON.stringify(aData));
+ }
+}
+
+function _setImage(aElement, aActive, aURL) {
+ aElement.style.backgroundImage =
+ (aActive && aURL) ? 'url("' + aURL.replace(/"/g, '\\"') + '")' : "";
+}
+
+function _parseRGB(aColorString) {
+ var rgb = aColorString.match(/^rgba?\((\d+), (\d+), (\d+)/);
+ rgb.shift();
+ return rgb.map(x => parseInt(x));
+}