menu.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
  2. /* This Source Code Form is subject to the terms of the Mozilla Public
  3. * License, v. 2.0. If a copy of the MPL was not distributed with this
  4. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  5. "use strict";
  6. const EventEmitter = require("devtools/shared/event-emitter");
  7. /**
  8. * A partial implementation of the Menu API provided by electron:
  9. * https://github.com/electron/electron/blob/master/docs/api/menu.md.
  10. *
  11. * Extra features:
  12. * - Emits an 'open' and 'close' event when the menu is opened/closed
  13. * @param String id (non standard)
  14. * Needed so tests can confirm the XUL implementation is working
  15. */
  16. function Menu({ id = null } = {}) {
  17. this.menuitems = [];
  18. this.id = id;
  19. Object.defineProperty(this, "items", {
  20. get() {
  21. return this.menuitems;
  22. }
  23. });
  24. EventEmitter.decorate(this);
  25. }
  26. /**
  27. * Add an item to the end of the Menu
  28. *
  29. * @param {MenuItem} menuItem
  30. */
  31. Menu.prototype.append = function (menuItem) {
  32. this.menuitems.push(menuItem);
  33. };
  34. /**
  35. * Add an item to a specified position in the menu
  36. *
  37. * @param {int} pos
  38. * @param {MenuItem} menuItem
  39. */
  40. Menu.prototype.insert = function (pos, menuItem) {
  41. throw Error("Not implemented");
  42. };
  43. /**
  44. * Show the Menu at a specified location on the screen
  45. *
  46. * Missing features:
  47. * - browserWindow - BrowserWindow (optional) - Default is null.
  48. * - positioningItem Number - (optional) OS X
  49. *
  50. * @param {int} screenX
  51. * @param {int} screenY
  52. * @param Toolbox toolbox (non standard)
  53. * Needed so we in which window to inject XUL
  54. */
  55. Menu.prototype.popup = function (screenX, screenY, toolbox) {
  56. let doc = toolbox.doc;
  57. let popupset = doc.querySelector("popupset");
  58. // See bug 1285229, on Windows, opening the same popup multiple times in a
  59. // row ends up duplicating the popup. The newly inserted popup doesn't
  60. // dismiss the old one. So remove any previously displayed popup before
  61. // opening a new one.
  62. let popup = popupset.querySelector("menupopup[menu-api=\"true\"]");
  63. if (popup) {
  64. popup.hidePopup();
  65. }
  66. popup = doc.createElement("menupopup");
  67. popup.setAttribute("menu-api", "true");
  68. if (this.id) {
  69. popup.id = this.id;
  70. }
  71. this._createMenuItems(popup);
  72. // Remove the menu from the DOM once it's hidden.
  73. popup.addEventListener("popuphidden", (e) => {
  74. if (e.target === popup) {
  75. popup.remove();
  76. this.emit("close");
  77. }
  78. });
  79. popup.addEventListener("popupshown", (e) => {
  80. if (e.target === popup) {
  81. this.emit("open");
  82. }
  83. });
  84. popupset.appendChild(popup);
  85. popup.openPopupAtScreen(screenX, screenY, true);
  86. };
  87. Menu.prototype._createMenuItems = function (parent) {
  88. let doc = parent.ownerDocument;
  89. this.menuitems.forEach(item => {
  90. if (!item.visible) {
  91. return;
  92. }
  93. if (item.submenu) {
  94. let menupopup = doc.createElement("menupopup");
  95. item.submenu._createMenuItems(menupopup);
  96. let menu = doc.createElement("menu");
  97. menu.appendChild(menupopup);
  98. menu.setAttribute("label", item.label);
  99. if (item.disabled) {
  100. menu.setAttribute("disabled", "true");
  101. }
  102. if (item.accesskey) {
  103. menu.setAttribute("accesskey", item.accesskey);
  104. }
  105. if (item.id) {
  106. menu.id = item.id;
  107. }
  108. parent.appendChild(menu);
  109. } else if (item.type === "separator") {
  110. let menusep = doc.createElement("menuseparator");
  111. parent.appendChild(menusep);
  112. } else {
  113. let menuitem = doc.createElement("menuitem");
  114. menuitem.setAttribute("label", item.label);
  115. menuitem.addEventListener("command", () => {
  116. item.click();
  117. });
  118. if (item.type === "checkbox") {
  119. menuitem.setAttribute("type", "checkbox");
  120. }
  121. if (item.type === "radio") {
  122. menuitem.setAttribute("type", "radio");
  123. }
  124. if (item.disabled) {
  125. menuitem.setAttribute("disabled", "true");
  126. }
  127. if (item.checked) {
  128. menuitem.setAttribute("checked", "true");
  129. }
  130. if (item.accesskey) {
  131. menuitem.setAttribute("accesskey", item.accesskey);
  132. }
  133. if (item.id) {
  134. menuitem.id = item.id;
  135. }
  136. parent.appendChild(menuitem);
  137. }
  138. });
  139. };
  140. Menu.setApplicationMenu = () => {
  141. throw Error("Not implemented");
  142. };
  143. Menu.sendActionToFirstResponder = () => {
  144. throw Error("Not implemented");
  145. };
  146. Menu.buildFromTemplate = () => {
  147. throw Error("Not implemented");
  148. };
  149. module.exports = Menu;