123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239 |
- /* 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/. */
- this.EXPORTED_SYMBOLS = ["PageMenu"];
- this.PageMenu = function PageMenu() {
- }
- PageMenu.prototype = {
- PAGEMENU_ATTR: "pagemenu",
- GENERATEDITEMID_ATTR: "generateditemid",
- _popup: null,
- _builder: null,
- // Given a target node, get the context menu for it or its ancestor.
- getContextMenu: function(aTarget) {
- let target = aTarget;
- while (target) {
- let contextMenu = target.contextMenu;
- if (contextMenu) {
- return contextMenu;
- }
- target = target.parentNode;
- }
- return null;
- },
- // Given a target node, generate a JSON object for any context menu
- // associated with it, or null if there is no context menu.
- maybeBuild: function(aTarget) {
- let pageMenu = this.getContextMenu(aTarget);
- if (!pageMenu) {
- return null;
- }
- pageMenu.QueryInterface(Components.interfaces.nsIHTMLMenu);
- pageMenu.sendShowEvent();
- // the show event is not cancelable, so no need to check a result here
- this._builder = pageMenu.createBuilder();
- if (!this._builder) {
- return null;
- }
- pageMenu.build(this._builder);
- // This serializes then parses again, however this could be avoided in
- // the single-process case with further improvement.
- let menuString = this._builder.toJSONString();
- if (!menuString) {
- return null;
- }
- return JSON.parse(menuString);
- },
- // Given a JSON menu object and popup, add the context menu to the popup.
- buildAndAttachMenuWithObject: function(aMenu, aBrowser, aPopup) {
- if (!aMenu) {
- return false;
- }
- let insertionPoint = this.getInsertionPoint(aPopup);
- if (!insertionPoint) {
- return false;
- }
- let fragment = aPopup.ownerDocument.createDocumentFragment();
- this.buildXULMenu(aMenu, fragment);
- let pos = insertionPoint.getAttribute(this.PAGEMENU_ATTR);
- if (pos == "start") {
- insertionPoint.insertBefore(fragment,
- insertionPoint.firstChild);
- } else if (pos.startsWith("#")) {
- insertionPoint.insertBefore(fragment, insertionPoint.querySelector(pos));
- } else {
- insertionPoint.appendChild(fragment);
- }
- this._popup = aPopup;
- this._popup.addEventListener("command", this);
- this._popup.addEventListener("popuphidden", this);
- return true;
- },
- // Construct the XUL menu structure for a given JSON object.
- buildXULMenu: function(aNode, aElementForAppending) {
- let document = aElementForAppending.ownerDocument;
- let children = aNode.children;
- for (let child of children) {
- let menuitem;
- switch (child.type) {
- case "menuitem":
- if (!child.id) {
- continue; // Ignore children without ids
- }
- menuitem = document.createElement("menuitem");
- if (child.checkbox) {
- menuitem.setAttribute("type", "checkbox");
- if (child.checked) {
- menuitem.setAttribute("checked", "true");
- }
- }
- if (child.label) {
- menuitem.setAttribute("label", child.label);
- }
- if (child.icon) {
- menuitem.setAttribute("image", child.icon);
- menuitem.className = "menuitem-iconic";
- }
- if (child.disabled) {
- menuitem.setAttribute("disabled", true);
- }
- break;
- case "separator":
- menuitem = document.createElement("menuseparator");
- break;
- case "menu":
- menuitem = document.createElement("menu");
- if (child.label) {
- menuitem.setAttribute("label", child.label);
- }
- let menupopup = document.createElement("menupopup");
- menuitem.appendChild(menupopup);
- this.buildXULMenu(child, menupopup);
- break;
- }
- menuitem.setAttribute(this.GENERATEDITEMID_ATTR, child.id ? child.id : 0);
- aElementForAppending.appendChild(menuitem);
- }
- },
- // Called when the generated menuitem is executed.
- handleEvent: function(event) {
- let type = event.type;
- let target = event.target;
- if (type == "command" && target.hasAttribute(this.GENERATEDITEMID_ATTR)) {
- // If a builder is assigned, call click on it directly. Otherwise, this is
- // likely a menu with data from another process, so send a message to the
- // browser to execute the menuitem.
- if (this._builder) {
- this._builder.click(target.getAttribute(this.GENERATEDITEMID_ATTR));
- }
- } else if (type == "popuphidden" && this._popup == target) {
- this.removeGeneratedContent(this._popup);
- this._popup.removeEventListener("popuphidden", this);
- this._popup.removeEventListener("command", this);
- this._popup = null;
- this._builder = null;
- }
- },
- // Get the first child of the given element with the given tag name.
- getImmediateChild: function(element, tag) {
- let child = element.firstChild;
- while (child) {
- if (child.localName == tag) {
- return child;
- }
- child = child.nextSibling;
- }
- return null;
- },
- // Return the location where the generated items should be inserted into the
- // given popup. They should be inserted as the next sibling of the returned
- // element.
- getInsertionPoint: function(aPopup) {
- if (aPopup.hasAttribute(this.PAGEMENU_ATTR))
- return aPopup;
- let element = aPopup.firstChild;
- while (element) {
- if (element.localName == "menu") {
- let popup = this.getImmediateChild(element, "menupopup");
- if (popup) {
- let result = this.getInsertionPoint(popup);
- if (result) {
- return result;
- }
- }
- }
- element = element.nextSibling;
- }
- return null;
- },
- // Returns true if custom menu items were present.
- maybeBuildAndAttachMenu: function(aTarget, aPopup) {
- let menuObject = this.maybeBuild(aTarget);
- if (!menuObject) {
- return false;
- }
- return this.buildAndAttachMenuWithObject(menuObject, null, aPopup);
- },
- // Remove the generated content from the given popup.
- removeGeneratedContent: function(aPopup) {
- let ungenerated = [];
- ungenerated.push(aPopup);
- let count;
- while (0 != (count = ungenerated.length)) {
- let last = count - 1;
- let element = ungenerated[last];
- ungenerated.splice(last, 1);
- let i = element.childNodes.length;
- while (i-- > 0) {
- let child = element.childNodes[i];
- if (!child.hasAttribute(this.GENERATEDITEMID_ATTR)) {
- ungenerated.push(child);
- continue;
- }
- element.removeChild(child);
- }
- }
- }
- }
|