mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-14 03:07:31 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
320
toolkit/modules/PageMenu.jsm
Normal file
320
toolkit/modules/PageMenu.jsm
Normal file
|
|
@ -0,0 +1,320 @@
|
|||
/* 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 = ["PageMenuParent", "PageMenuChild"];
|
||||
|
||||
var {interfaces: Ci} = Components;
|
||||
|
||||
this.PageMenu = function PageMenu() {
|
||||
}
|
||||
|
||||
PageMenu.prototype = {
|
||||
PAGEMENU_ATTR: "pagemenu",
|
||||
GENERATEDITEMID_ATTR: "generateditemid",
|
||||
|
||||
_popup: null,
|
||||
|
||||
// Only one of builder or browser will end up getting set.
|
||||
_builder: null,
|
||||
_browser: null,
|
||||
|
||||
// Given a target node, get the context menu for it or its ancestor.
|
||||
getContextMenu: function(aTarget) {
|
||||
let pageMenu = null;
|
||||
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._browser = aBrowser;
|
||||
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 (this._browser) {
|
||||
let win = target.ownerDocument.defaultView;
|
||||
let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
this._browser.messageManager.sendAsyncMessage("ContextMenu:DoCustomCommand", {
|
||||
generatedItemId: target.getAttribute(this.GENERATEDITEMID_ATTR),
|
||||
handlingUserInput: windowUtils.isHandlingUserInput
|
||||
});
|
||||
}
|
||||
} 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;
|
||||
this._browser = 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;
|
||||
},
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This object is expected to be used from a parent process.
|
||||
this.PageMenuParent = function PageMenuParent() {
|
||||
}
|
||||
|
||||
PageMenuParent.prototype = {
|
||||
__proto__ : PageMenu.prototype,
|
||||
|
||||
/*
|
||||
* Given a target node and popup, add the context menu to the popup. This is
|
||||
* intended to be called when a single process is used. This is equivalent to
|
||||
* calling PageMenuChild.build and PageMenuParent.addToPopup in sequence.
|
||||
*
|
||||
* Returns true if custom menu items were present.
|
||||
*/
|
||||
buildAndAddToPopup: function(aTarget, aPopup) {
|
||||
let menuObject = this.maybeBuild(aTarget);
|
||||
if (!menuObject) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.buildAndAttachMenuWithObject(menuObject, null, aPopup);
|
||||
},
|
||||
|
||||
/*
|
||||
* Given a JSON menu object and popup, add the context menu to the popup. This
|
||||
* is intended to be called when the child page is in a different process.
|
||||
* aBrowser should be the browser containing the page the context menu is
|
||||
* displayed for, which may be null.
|
||||
*
|
||||
* Returns true if custom menu items were present.
|
||||
*/
|
||||
addToPopup: function(aMenu, aBrowser, aPopup) {
|
||||
return this.buildAndAttachMenuWithObject(aMenu, aBrowser, aPopup);
|
||||
}
|
||||
}
|
||||
|
||||
// This object is expected to be used from a child process.
|
||||
this.PageMenuChild = function PageMenuChild() {
|
||||
}
|
||||
|
||||
PageMenuChild.prototype = {
|
||||
__proto__ : PageMenu.prototype,
|
||||
|
||||
/*
|
||||
* Given a target node, return a JSON object for the custom menu commands. The
|
||||
* object will consist of a hierarchical structure of menus, menuitems or
|
||||
* separators. Supported properties of each are:
|
||||
* Menu: children, label, type="menu"
|
||||
* Menuitems: checkbox, checked, disabled, icon, label, type="menuitem"
|
||||
* Separators: type="separator"
|
||||
*
|
||||
* In addition, the id of each item will be used to identify the item
|
||||
* when it is executed. The type will either be 'menu', 'menuitem' or
|
||||
* 'separator'. The toplevel node will be a menu with a children property. The
|
||||
* children property of a menu is an array of zero or more other items.
|
||||
*
|
||||
* If there is no menu associated with aTarget, null will be returned.
|
||||
*/
|
||||
build: function(aTarget) {
|
||||
return this.maybeBuild(aTarget);
|
||||
},
|
||||
|
||||
/*
|
||||
* Given the id of a menu, execute the command associated with that menu. It
|
||||
* is assumed that only one command will be executed so the builder is
|
||||
* cleared afterwards.
|
||||
*/
|
||||
executeMenu: function(aId) {
|
||||
if (this._builder) {
|
||||
this._builder.click(aId);
|
||||
this._builder = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue