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: 'use strict'; michael@0: michael@0: module.metadata = { michael@0: 'stability': 'experimental', michael@0: 'engines': { michael@0: 'Firefox': '> 28' michael@0: } michael@0: }; michael@0: michael@0: const { Cu } = require('chrome'); michael@0: const { on, off, emit } = require('../../event/core'); michael@0: michael@0: const { data } = require('sdk/self'); michael@0: michael@0: const { isObject } = require('../../lang/type'); michael@0: michael@0: const { getMostRecentBrowserWindow } = require('../../window/utils'); michael@0: const { ignoreWindow } = require('../../private-browsing/utils'); michael@0: const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {}); michael@0: const { AREA_PANEL, AREA_NAVBAR } = CustomizableUI; michael@0: michael@0: const { events: viewEvents } = require('./view/events'); michael@0: michael@0: const XUL_NS = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'; michael@0: michael@0: const views = new Map(); michael@0: const customizedWindows = new WeakMap(); michael@0: michael@0: const buttonListener = { michael@0: onCustomizeStart: window => { michael@0: for (let [id, view] of views) { michael@0: setIcon(id, window, view.icon); michael@0: setLabel(id, window, view.label); michael@0: } michael@0: michael@0: customizedWindows.set(window, true); michael@0: }, michael@0: onCustomizeEnd: window => { michael@0: customizedWindows.delete(window); michael@0: michael@0: for (let [id, ] of views) { michael@0: let placement = CustomizableUI.getPlacementOfWidget(id); michael@0: michael@0: if (placement) michael@0: emit(viewEvents, 'data', { type: 'update', target: id, window: window }); michael@0: } michael@0: }, michael@0: onWidgetAfterDOMChange: (node, nextNode, container) => { michael@0: let { id } = node; michael@0: let view = views.get(id); michael@0: let window = node.ownerDocument.defaultView; michael@0: michael@0: if (view) { michael@0: emit(viewEvents, 'data', { type: 'update', target: id, window: window }); michael@0: } michael@0: } michael@0: }; michael@0: michael@0: CustomizableUI.addListener(buttonListener); michael@0: michael@0: require('../../system/unload').when( _ => michael@0: CustomizableUI.removeListener(buttonListener) michael@0: ); michael@0: michael@0: function getNode(id, window) { michael@0: return !views.has(id) || ignoreWindow(window) michael@0: ? null michael@0: : CustomizableUI.getWidget(id).forWindow(window).node michael@0: }; michael@0: michael@0: function isInToolbar(id) { michael@0: let placement = CustomizableUI.getPlacementOfWidget(id); michael@0: michael@0: return placement && CustomizableUI.getAreaType(placement.area) === 'toolbar'; michael@0: } michael@0: michael@0: michael@0: function getImage(icon, isInToolbar, pixelRatio) { michael@0: let targetSize = (isInToolbar ? 18 : 32) * pixelRatio; michael@0: let bestSize = 0; michael@0: let image = icon; michael@0: michael@0: if (isObject(icon)) { michael@0: for (let size of Object.keys(icon)) { michael@0: size = +size; michael@0: let offset = targetSize - size; michael@0: michael@0: if (offset === 0) { michael@0: bestSize = size; michael@0: break; michael@0: } michael@0: michael@0: let delta = Math.abs(offset) - Math.abs(targetSize - bestSize); michael@0: michael@0: if (delta < 0) michael@0: bestSize = size; michael@0: } michael@0: michael@0: image = icon[bestSize]; michael@0: } michael@0: michael@0: if (image.indexOf('./') === 0) michael@0: return data.url(image.substr(2)); michael@0: michael@0: return image; michael@0: } michael@0: michael@0: function nodeFor(id, window=getMostRecentBrowserWindow()) { michael@0: return customizedWindows.has(window) ? null : getNode(id, window); michael@0: }; michael@0: exports.nodeFor = nodeFor; michael@0: michael@0: function create(options) { michael@0: let { id, label, icon, type } = options; michael@0: michael@0: if (views.has(id)) michael@0: throw new Error('The ID "' + id + '" seems already used.'); michael@0: michael@0: CustomizableUI.createWidget({ michael@0: id: id, michael@0: type: 'custom', michael@0: removable: true, michael@0: defaultArea: AREA_NAVBAR, michael@0: allowedAreas: [ AREA_PANEL, AREA_NAVBAR ], michael@0: michael@0: onBuild: function(document) { michael@0: let window = document.defaultView; michael@0: michael@0: let node = document.createElementNS(XUL_NS, 'toolbarbutton'); michael@0: michael@0: let image = getImage(icon, true, window.devicePixelRatio); michael@0: michael@0: if (ignoreWindow(window)) michael@0: node.style.display = 'none'; michael@0: michael@0: node.setAttribute('id', this.id); michael@0: node.setAttribute('class', 'toolbarbutton-1 chromeclass-toolbar-additional'); michael@0: node.setAttribute('type', type); michael@0: node.setAttribute('label', label); michael@0: node.setAttribute('tooltiptext', label); michael@0: node.setAttribute('image', image); michael@0: node.setAttribute('sdk-button', 'true'); michael@0: michael@0: views.set(id, { michael@0: area: this.currentArea, michael@0: icon: icon, michael@0: label: label michael@0: }); michael@0: michael@0: node.addEventListener('command', function(event) { michael@0: if (views.has(id)) { michael@0: emit(viewEvents, 'data', { michael@0: type: 'click', michael@0: target: id, michael@0: window: event.view, michael@0: checked: node.checked michael@0: }); michael@0: } michael@0: }); michael@0: michael@0: return node; michael@0: } michael@0: }); michael@0: }; michael@0: exports.create = create; michael@0: michael@0: function dispose(id) { michael@0: if (!views.has(id)) return; michael@0: michael@0: views.delete(id); michael@0: CustomizableUI.destroyWidget(id); michael@0: } michael@0: exports.dispose = dispose; michael@0: michael@0: function setIcon(id, window, icon) { michael@0: let node = getNode(id, window); michael@0: michael@0: if (node) { michael@0: icon = customizedWindows.has(window) ? views.get(id).icon : icon; michael@0: let image = getImage(icon, isInToolbar(id), window.devicePixelRatio); michael@0: michael@0: node.setAttribute('image', image); michael@0: } michael@0: } michael@0: exports.setIcon = setIcon; michael@0: michael@0: function setLabel(id, window, label) { michael@0: let node = nodeFor(id, window); michael@0: michael@0: if (node) { michael@0: node.setAttribute('label', label); michael@0: node.setAttribute('tooltiptext', label); michael@0: } michael@0: } michael@0: exports.setLabel = setLabel; michael@0: michael@0: function setDisabled(id, window, disabled) { michael@0: let node = nodeFor(id, window); michael@0: michael@0: if (node) michael@0: node.disabled = disabled; michael@0: } michael@0: exports.setDisabled = setDisabled; michael@0: michael@0: function setChecked(id, window, checked) { michael@0: let node = nodeFor(id, window); michael@0: michael@0: if (node) michael@0: node.checked = checked; michael@0: } michael@0: exports.setChecked = setChecked; michael@0: michael@0: function click(id) { michael@0: let node = nodeFor(id); michael@0: michael@0: if (node) michael@0: node.click(); michael@0: } michael@0: exports.click = click;