michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: this.EXPORTED_SYMBOLS = ["LightweightThemeConsumer"]; michael@0: michael@0: Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: Components.utils.import("resource://gre/modules/Services.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeImageOptimizer", michael@0: "resource://gre/modules/addons/LightweightThemeImageOptimizer.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", michael@0: "resource://gre/modules/PrivateBrowsingUtils.jsm"); michael@0: michael@0: this.LightweightThemeConsumer = michael@0: function LightweightThemeConsumer(aDocument) { michael@0: this._doc = aDocument; michael@0: this._win = aDocument.defaultView; michael@0: this._footerId = aDocument.documentElement.getAttribute("lightweightthemesfooter"); michael@0: michael@0: if (PrivateBrowsingUtils.isWindowPrivate(this._win) && michael@0: !PrivateBrowsingUtils.permanentPrivateBrowsing) { michael@0: return; michael@0: } michael@0: michael@0: let screen = this._win.screen; michael@0: this._lastScreenWidth = screen.width; michael@0: this._lastScreenHeight = screen.height; michael@0: michael@0: Components.classes["@mozilla.org/observer-service;1"] michael@0: .getService(Components.interfaces.nsIObserverService) michael@0: .addObserver(this, "lightweight-theme-styling-update", false); michael@0: michael@0: var temp = {}; michael@0: Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm", temp); michael@0: this._update(temp.LightweightThemeManager.currentThemeForDisplay); michael@0: this._win.addEventListener("resize", this); michael@0: } michael@0: michael@0: LightweightThemeConsumer.prototype = { michael@0: _lastData: null, michael@0: _lastScreenWidth: null, michael@0: _lastScreenHeight: null, michael@0: // Whether the active lightweight theme should be shown on the window. michael@0: _enabled: true, michael@0: // Whether a lightweight theme is enabled. michael@0: _active: false, michael@0: michael@0: enable: function() { michael@0: this._enabled = true; michael@0: this._update(this._lastData); michael@0: }, michael@0: michael@0: disable: function() { michael@0: // Dance to keep the data, but reset the applied styles: michael@0: let lastData = this._lastData michael@0: this._update(null); michael@0: this._enabled = false; michael@0: this._lastData = lastData; michael@0: }, michael@0: michael@0: observe: function (aSubject, aTopic, aData) { michael@0: if (aTopic != "lightweight-theme-styling-update") michael@0: return; michael@0: michael@0: this._update(JSON.parse(aData)); michael@0: }, michael@0: michael@0: handleEvent: function (aEvent) { michael@0: let {width, height} = this._win.screen; michael@0: michael@0: if (this._lastScreenWidth != width || this._lastScreenHeight != height) { michael@0: this._lastScreenWidth = width; michael@0: this._lastScreenHeight = height; michael@0: if (!this._active) michael@0: return; michael@0: this._update(this._lastData); michael@0: Services.obs.notifyObservers(this._win, "lightweight-theme-optimized", michael@0: JSON.stringify(this._lastData)); michael@0: } michael@0: }, michael@0: michael@0: destroy: function () { michael@0: if (!PrivateBrowsingUtils.isWindowPrivate(this._win) || michael@0: PrivateBrowsingUtils.permanentPrivateBrowsing) { michael@0: Components.classes["@mozilla.org/observer-service;1"] michael@0: .getService(Components.interfaces.nsIObserverService) michael@0: .removeObserver(this, "lightweight-theme-styling-update"); michael@0: michael@0: this._win.removeEventListener("resize", this); michael@0: } michael@0: michael@0: this._win = this._doc = null; michael@0: }, michael@0: michael@0: _update: function (aData) { michael@0: if (!aData) { michael@0: aData = { headerURL: "", footerURL: "", textcolor: "", accentcolor: "" }; michael@0: this._lastData = aData; michael@0: } else { michael@0: this._lastData = aData; michael@0: aData = LightweightThemeImageOptimizer.optimize(aData, this._win.screen); michael@0: } michael@0: if (!this._enabled) michael@0: return; michael@0: michael@0: let root = this._doc.documentElement; michael@0: let active = !!aData.headerURL; michael@0: let stateChanging = (active != this._active); michael@0: michael@0: if (active) { michael@0: root.style.color = aData.textcolor || "black"; michael@0: root.style.backgroundColor = aData.accentcolor || "white"; michael@0: let [r, g, b] = _parseRGB(this._doc.defaultView.getComputedStyle(root, "").color); michael@0: let luminance = 0.2125 * r + 0.7154 * g + 0.0721 * b; michael@0: root.setAttribute("lwthemetextcolor", luminance <= 110 ? "dark" : "bright"); michael@0: root.setAttribute("lwtheme", "true"); michael@0: } else { michael@0: root.style.color = ""; michael@0: root.style.backgroundColor = ""; michael@0: root.removeAttribute("lwthemetextcolor"); michael@0: root.removeAttribute("lwtheme"); michael@0: } michael@0: michael@0: this._active = active; michael@0: michael@0: _setImage(root, active, aData.headerURL); michael@0: if (this._footerId) { michael@0: let footer = this._doc.getElementById(this._footerId); michael@0: footer.style.backgroundColor = active ? aData.accentcolor || "white" : ""; michael@0: _setImage(footer, active, aData.footerURL); michael@0: if (active && aData.footerURL) michael@0: footer.setAttribute("lwthemefooter", "true"); michael@0: else michael@0: footer.removeAttribute("lwthemefooter"); michael@0: } michael@0: michael@0: #ifdef XP_MACOSX michael@0: // On OS X, we extend the lightweight theme into the titlebar, which means setting michael@0: // the chromemargin attribute. Some XUL applications already draw in the titlebar, michael@0: // so we need to save the chromemargin value before we overwrite it with the value michael@0: // that lets us draw in the titlebar. We stash this value on the root attribute so michael@0: // that XUL applications have the ability to invalidate the saved value. michael@0: if (stateChanging) { michael@0: if (!root.hasAttribute("chromemargin-nonlwtheme")) { michael@0: root.setAttribute("chromemargin-nonlwtheme", root.getAttribute("chromemargin")); michael@0: } michael@0: michael@0: if (active) { michael@0: root.setAttribute("chromemargin", "0,-1,-1,-1"); michael@0: } else { michael@0: let defaultChromemargin = root.getAttribute("chromemargin-nonlwtheme"); michael@0: if (defaultChromemargin) { michael@0: root.setAttribute("chromemargin", defaultChromemargin); michael@0: } else { michael@0: root.removeAttribute("chromemargin"); michael@0: } michael@0: } michael@0: } michael@0: #endif michael@0: } michael@0: } michael@0: michael@0: function _setImage(aElement, aActive, aURL) { michael@0: aElement.style.backgroundImage = michael@0: (aActive && aURL) ? 'url("' + aURL.replace(/"/g, '\\"') + '")' : ""; michael@0: } michael@0: michael@0: function _parseRGB(aColorString) { michael@0: var rgb = aColorString.match(/^rgba?\((\d+), (\d+), (\d+)/); michael@0: rgb.shift(); michael@0: return rgb.map(function (x) parseInt(x)); michael@0: }