2016-05-02 03:01:47 +03:00
|
|
|
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
|
|
|
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
|
|
|
/* 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/. */
|
|
|
|
|
2016-05-03 17:42:20 +03:00
|
|
|
"use strict";
|
|
|
|
|
2018-03-14 19:31:12 +03:00
|
|
|
const EventEmitter = require("devtools/shared/event-emitter");
|
2018-06-28 08:55:48 +03:00
|
|
|
const { getCurrentZoom } = require("devtools/shared/layout/utils");
|
2016-05-02 03:01:47 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* A partial implementation of the Menu API provided by electron:
|
|
|
|
* https://github.com/electron/electron/blob/master/docs/api/menu.md.
|
|
|
|
*
|
|
|
|
* Extra features:
|
|
|
|
* - Emits an 'open' and 'close' event when the menu is opened/closed
|
|
|
|
|
|
|
|
* @param String id (non standard)
|
|
|
|
* Needed so tests can confirm the XUL implementation is working
|
|
|
|
*/
|
2016-05-03 17:42:20 +03:00
|
|
|
function Menu({ id = null } = {}) {
|
2016-05-02 03:01:47 +03:00
|
|
|
this.menuitems = [];
|
|
|
|
this.id = id;
|
|
|
|
|
|
|
|
Object.defineProperty(this, "items", {
|
|
|
|
get() {
|
|
|
|
return this.menuitems;
|
2018-10-19 15:55:39 +03:00
|
|
|
},
|
2016-05-02 03:01:47 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
EventEmitter.decorate(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add an item to the end of the Menu
|
|
|
|
*
|
|
|
|
* @param {MenuItem} menuItem
|
|
|
|
*/
|
2018-04-05 00:10:09 +03:00
|
|
|
Menu.prototype.append = function(menuItem) {
|
2016-05-02 03:01:47 +03:00
|
|
|
this.menuitems.push(menuItem);
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add an item to a specified position in the menu
|
|
|
|
*
|
|
|
|
* @param {int} pos
|
|
|
|
* @param {MenuItem} menuItem
|
|
|
|
*/
|
2018-04-05 00:10:09 +03:00
|
|
|
Menu.prototype.insert = function(pos, menuItem) {
|
2016-05-03 17:42:20 +03:00
|
|
|
throw Error("Not implemented");
|
2016-05-02 03:01:47 +03:00
|
|
|
};
|
|
|
|
|
2018-05-11 03:08:09 +03:00
|
|
|
/**
|
|
|
|
* Show the Menu with anchor element's coordinate.
|
|
|
|
* For example, In the case of zoom in/out the devtool panel, we should multiply
|
|
|
|
* element's position to zoom value.
|
|
|
|
* If you know the screen coodinate of display position, you should use Menu.pop().
|
|
|
|
*
|
|
|
|
* @param {int} x
|
|
|
|
* @param {int} y
|
|
|
|
* @param Toolbox toolbox
|
|
|
|
*/
|
|
|
|
Menu.prototype.popupWithZoom = function(x, y, toolbox) {
|
2018-06-28 08:55:48 +03:00
|
|
|
const zoom = getCurrentZoom(toolbox.doc);
|
2018-05-11 03:08:09 +03:00
|
|
|
this.popup(x * zoom, y * zoom, toolbox);
|
|
|
|
};
|
|
|
|
|
2016-05-02 03:01:47 +03:00
|
|
|
/**
|
|
|
|
* Show the Menu at a specified location on the screen
|
|
|
|
*
|
|
|
|
* Missing features:
|
|
|
|
* - browserWindow - BrowserWindow (optional) - Default is null.
|
|
|
|
* - positioningItem Number - (optional) OS X
|
|
|
|
*
|
|
|
|
* @param {int} screenX
|
|
|
|
* @param {int} screenY
|
|
|
|
* @param Toolbox toolbox (non standard)
|
|
|
|
* Needed so we in which window to inject XUL
|
|
|
|
*/
|
2018-04-05 00:10:09 +03:00
|
|
|
Menu.prototype.popup = function(screenX, screenY, toolbox) {
|
2018-06-01 13:36:09 +03:00
|
|
|
const doc = toolbox.doc;
|
2018-07-05 20:56:15 +03:00
|
|
|
|
|
|
|
let popupset = doc.querySelector("popupset");
|
|
|
|
if (!popupset) {
|
2018-08-14 19:22:44 +03:00
|
|
|
popupset = doc.createXULElement("popupset");
|
2018-07-05 20:56:15 +03:00
|
|
|
doc.documentElement.appendChild(popupset);
|
|
|
|
}
|
2016-07-14 01:44:20 +03:00
|
|
|
// See bug 1285229, on Windows, opening the same popup multiple times in a
|
|
|
|
// row ends up duplicating the popup. The newly inserted popup doesn't
|
|
|
|
// dismiss the old one. So remove any previously displayed popup before
|
|
|
|
// opening a new one.
|
|
|
|
let popup = popupset.querySelector("menupopup[menu-api=\"true\"]");
|
|
|
|
if (popup) {
|
|
|
|
popup.hidePopup();
|
|
|
|
}
|
|
|
|
|
2018-08-14 19:22:44 +03:00
|
|
|
popup = doc.createXULElement("menupopup");
|
2016-05-02 03:01:47 +03:00
|
|
|
popup.setAttribute("menu-api", "true");
|
2018-05-08 04:59:19 +03:00
|
|
|
popup.setAttribute("consumeoutsideclicks", "true");
|
2016-05-02 03:01:47 +03:00
|
|
|
|
|
|
|
if (this.id) {
|
|
|
|
popup.id = this.id;
|
|
|
|
}
|
|
|
|
this._createMenuItems(popup);
|
|
|
|
|
|
|
|
// Remove the menu from the DOM once it's hidden.
|
|
|
|
popup.addEventListener("popuphidden", (e) => {
|
|
|
|
if (e.target === popup) {
|
|
|
|
popup.remove();
|
|
|
|
this.emit("close");
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
popup.addEventListener("popupshown", (e) => {
|
|
|
|
if (e.target === popup) {
|
|
|
|
this.emit("open");
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2016-07-14 01:44:20 +03:00
|
|
|
popupset.appendChild(popup);
|
2016-05-02 03:01:47 +03:00
|
|
|
popup.openPopupAtScreen(screenX, screenY, true);
|
|
|
|
};
|
|
|
|
|
2018-04-05 00:10:09 +03:00
|
|
|
Menu.prototype._createMenuItems = function(parent) {
|
2018-06-01 13:36:09 +03:00
|
|
|
const doc = parent.ownerDocument;
|
2016-05-02 03:01:47 +03:00
|
|
|
this.menuitems.forEach(item => {
|
2016-05-03 17:40:33 +03:00
|
|
|
if (!item.visible) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-05-02 03:01:47 +03:00
|
|
|
if (item.submenu) {
|
2018-08-14 19:22:44 +03:00
|
|
|
const menupopup = doc.createXULElement("menupopup");
|
2016-05-02 03:01:47 +03:00
|
|
|
item.submenu._createMenuItems(menupopup);
|
|
|
|
|
2018-08-14 19:22:44 +03:00
|
|
|
const menu = doc.createXULElement("menu");
|
2016-05-02 03:01:47 +03:00
|
|
|
menu.appendChild(menupopup);
|
2018-07-17 21:55:49 +03:00
|
|
|
applyItemAttributesToNode(item, menu);
|
2016-05-02 03:01:47 +03:00
|
|
|
parent.appendChild(menu);
|
|
|
|
} else if (item.type === "separator") {
|
2018-08-14 19:22:44 +03:00
|
|
|
const menusep = doc.createXULElement("menuseparator");
|
2016-05-02 03:01:47 +03:00
|
|
|
parent.appendChild(menusep);
|
|
|
|
} else {
|
2018-08-14 19:22:44 +03:00
|
|
|
const menuitem = doc.createXULElement("menuitem");
|
2018-07-17 21:55:49 +03:00
|
|
|
applyItemAttributesToNode(item, menuitem);
|
|
|
|
|
2016-05-02 03:01:47 +03:00
|
|
|
menuitem.addEventListener("command", () => {
|
|
|
|
item.click();
|
|
|
|
});
|
2017-09-25 16:03:11 +03:00
|
|
|
menuitem.addEventListener("DOMMenuItemActive", () => {
|
|
|
|
item.hover();
|
|
|
|
});
|
2016-05-02 03:01:47 +03:00
|
|
|
|
|
|
|
parent.appendChild(menuitem);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
Menu.setApplicationMenu = () => {
|
2016-05-03 17:42:20 +03:00
|
|
|
throw Error("Not implemented");
|
2016-05-02 03:01:47 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
Menu.sendActionToFirstResponder = () => {
|
2016-05-03 17:42:20 +03:00
|
|
|
throw Error("Not implemented");
|
2016-05-02 03:01:47 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
Menu.buildFromTemplate = () => {
|
2016-05-03 17:42:20 +03:00
|
|
|
throw Error("Not implemented");
|
2016-05-02 03:01:47 +03:00
|
|
|
};
|
|
|
|
|
2018-07-17 21:55:49 +03:00
|
|
|
function applyItemAttributesToNode(item, node) {
|
|
|
|
if (item.l10nID) {
|
|
|
|
node.setAttribute("data-l10n-id", item.l10nID);
|
|
|
|
} else {
|
|
|
|
node.setAttribute("label", item.label);
|
|
|
|
if (item.accelerator) {
|
|
|
|
node.setAttribute("acceltext", item.accelerator);
|
|
|
|
}
|
|
|
|
if (item.accesskey) {
|
|
|
|
node.setAttribute("accesskey", item.accesskey);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (item.type === "checkbox") {
|
|
|
|
node.setAttribute("type", "checkbox");
|
|
|
|
}
|
|
|
|
if (item.type === "radio") {
|
|
|
|
node.setAttribute("type", "radio");
|
|
|
|
}
|
|
|
|
if (item.disabled) {
|
|
|
|
node.setAttribute("disabled", "true");
|
|
|
|
}
|
|
|
|
if (item.checked) {
|
|
|
|
node.setAttribute("checked", "true");
|
|
|
|
}
|
|
|
|
if (item.id) {
|
|
|
|
node.id = item.id;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-02 03:01:47 +03:00
|
|
|
module.exports = Menu;
|