view.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this
  3. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. 'use strict';
  5. module.metadata = {
  6. 'stability': 'experimental',
  7. 'engines': {
  8. 'Firefox': '> 28'
  9. }
  10. };
  11. const { Cu } = require('chrome');
  12. const { on, off, emit } = require('../../event/core');
  13. const { id: addonID, data } = require('sdk/self');
  14. const buttonPrefix =
  15. 'button--' + addonID.toLowerCase().replace(/[^a-z0-9-_]/g, '');
  16. const { isObject } = require('../../lang/type');
  17. const { getMostRecentBrowserWindow } = require('../../window/utils');
  18. const { ignoreWindow } = require('../../private-browsing/utils');
  19. const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {});
  20. const { AREA_PANEL, AREA_NAVBAR } = CustomizableUI;
  21. const { events: viewEvents } = require('./view/events');
  22. const XUL_NS = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
  23. const toWidgetID = id => buttonPrefix + '-' + id;
  24. const toButtonID = id => id.substr(buttonPrefix.length + 1);
  25. const views = new Map();
  26. const customizedWindows = new WeakMap();
  27. const buttonListener = {
  28. onCustomizeStart: window => {
  29. for (let [id, view] of views) {
  30. setIcon(id, window, view.icon);
  31. setLabel(id, window, view.label);
  32. }
  33. customizedWindows.set(window, true);
  34. },
  35. onCustomizeEnd: window => {
  36. customizedWindows.delete(window);
  37. for (let [id, ] of views) {
  38. let placement = CustomizableUI.getPlacementOfWidget(toWidgetID(id));
  39. if (placement)
  40. emit(viewEvents, 'data', { type: 'update', target: id, window: window });
  41. }
  42. },
  43. onWidgetAfterDOMChange: (node, nextNode, container) => {
  44. let id = toButtonID(node.id);
  45. let view = views.get(id);
  46. let window = node.ownerDocument.defaultView;
  47. if (view) {
  48. emit(viewEvents, 'data', { type: 'update', target: id, window: window });
  49. }
  50. }
  51. };
  52. CustomizableUI.addListener(buttonListener);
  53. require('../../system/unload').when( _ =>
  54. CustomizableUI.removeListener(buttonListener)
  55. );
  56. function getNode(id, window) {
  57. return !views.has(id) || ignoreWindow(window)
  58. ? null
  59. : CustomizableUI.getWidget(toWidgetID(id)).forWindow(window).node
  60. };
  61. function isInToolbar(id) {
  62. let placement = CustomizableUI.getPlacementOfWidget(toWidgetID(id));
  63. return placement && CustomizableUI.getAreaType(placement.area) === 'toolbar';
  64. }
  65. function getImage(icon, isInToolbar, pixelRatio) {
  66. let targetSize = (isInToolbar ? 18 : 32) * pixelRatio;
  67. let bestSize = 0;
  68. let image = icon;
  69. if (isObject(icon)) {
  70. for (let size of Object.keys(icon)) {
  71. size = +size;
  72. let offset = targetSize - size;
  73. if (offset === 0) {
  74. bestSize = size;
  75. break;
  76. }
  77. let delta = Math.abs(offset) - Math.abs(targetSize - bestSize);
  78. if (delta < 0)
  79. bestSize = size;
  80. }
  81. image = icon[bestSize];
  82. }
  83. if (image.indexOf('./') === 0)
  84. return data.url(image.substr(2));
  85. return image;
  86. }
  87. function create(options) {
  88. let { id, label, icon, type } = options;
  89. if (views.has(id))
  90. throw new Error('The ID "' + id + '" seems already used.');
  91. CustomizableUI.createWidget({
  92. id: toWidgetID(id),
  93. type: 'custom',
  94. removable: true,
  95. defaultArea: AREA_NAVBAR,
  96. allowedAreas: [ AREA_PANEL, AREA_NAVBAR ],
  97. onBuild: function(document) {
  98. let window = document.defaultView;
  99. let node = document.createElementNS(XUL_NS, 'toolbarbutton');
  100. let image = getImage(icon, false, window.devicePixelRatio);
  101. if (ignoreWindow(window))
  102. node.style.display = 'none';
  103. node.setAttribute('id', this.id);
  104. node.setAttribute('class', 'toolbarbutton-1 chromeclass-toolbar-additional');
  105. node.setAttribute('type', type);
  106. node.setAttribute('label', label);
  107. node.setAttribute('tooltiptext', label);
  108. node.setAttribute('image', image);
  109. node.setAttribute('sdk-button', 'true');
  110. views.set(id, {
  111. area: this.currentArea,
  112. icon: icon,
  113. label: label
  114. });
  115. node.addEventListener('command', function(event) {
  116. if (views.has(id)) {
  117. emit(viewEvents, 'data', {
  118. type: 'click',
  119. target: id,
  120. window: event.view,
  121. checked: node.checked
  122. });
  123. }
  124. });
  125. return node;
  126. }
  127. });
  128. };
  129. exports.create = create;
  130. function dispose(id) {
  131. if (!views.has(id)) return;
  132. views.delete(id);
  133. CustomizableUI.destroyWidget(toWidgetID(id));
  134. }
  135. exports.dispose = dispose;
  136. function setIcon(id, window, icon) {
  137. let node = getNode(id, window);
  138. if (node) {
  139. icon = customizedWindows.has(window) ? views.get(id).icon : icon;
  140. let image = getImage(icon, isInToolbar(id), window.devicePixelRatio);
  141. node.setAttribute('image', image);
  142. }
  143. }
  144. exports.setIcon = setIcon;
  145. function setLabel(id, window, label) {
  146. let node = customizedWindows.has(window) ? null : getNode(id, window);
  147. if (node) {
  148. node.setAttribute('label', label);
  149. node.setAttribute('tooltiptext', label);
  150. }
  151. }
  152. exports.setLabel = setLabel;
  153. function setDisabled(id, window, disabled) {
  154. let node = customizedWindows.has(window) ? null : getNode(id, window);
  155. if (node)
  156. node.disabled = disabled;
  157. }
  158. exports.setDisabled = setDisabled;
  159. function setChecked(id, window, checked) {
  160. let node = customizedWindows.has(window) ? null : getNode(id, window);
  161. if (node)
  162. node.checked = checked;
  163. }
  164. exports.setChecked = setChecked;
  165. function click(id) {
  166. let window = getMostRecentBrowserWindow();
  167. let node = customizedWindows.has(window) ? null : getNode(id, window);
  168. if (node)
  169. node.click();
  170. }
  171. exports.click = click;