mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1066383, rework custom html menu item handling to support contextmenu attribute in separate process, r=janv,mconley,peterv
This commit is contained in:
parent
c8ce17bf8e
commit
6c8fcd5f50
@ -401,6 +401,8 @@
|
||||
@BINPATH@/components/nsSidebar.js
|
||||
@BINPATH@/components/nsAsyncShutdown.manifest
|
||||
@BINPATH@/components/nsAsyncShutdown.js
|
||||
@RESPATH@/components/htmlMenuBuilder.js
|
||||
@RESPATH@/components/htmlMenuBuilder.manifest
|
||||
|
||||
; WiFi, NetworkManager, NetworkStats
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
|
@ -259,10 +259,10 @@ XPCOMUtils.defineLazyServiceGetter(this, "gCrashReporter",
|
||||
"nsICrashReporter");
|
||||
#endif
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "PageMenu", function() {
|
||||
XPCOMUtils.defineLazyGetter(this, "PageMenuParent", function() {
|
||||
let tmp = {};
|
||||
Cu.import("resource://gre/modules/PageMenu.jsm", tmp);
|
||||
return new tmp.PageMenu();
|
||||
return new tmp.PageMenuParent();
|
||||
});
|
||||
|
||||
/**
|
||||
|
@ -43,6 +43,11 @@ XPCOMUtils.defineLazyGetter(this, "SimpleServiceDiscovery", function() {
|
||||
});
|
||||
return ssdp;
|
||||
});
|
||||
XPCOMUtils.defineLazyGetter(this, "PageMenuChild", function() {
|
||||
let tmp = {};
|
||||
Cu.import("resource://gre/modules/PageMenu.jsm", tmp);
|
||||
return new tmp.PageMenuChild();
|
||||
});
|
||||
|
||||
// TabChildGlobal
|
||||
var global = this;
|
||||
@ -102,6 +107,10 @@ addMessageListener("SecondScreen:tab-mirror", function(message) {
|
||||
}
|
||||
});
|
||||
|
||||
addMessageListener("ContextMenu:DoCustomCommand", function(message) {
|
||||
PageMenuChild.executeMenu(message.data);
|
||||
});
|
||||
|
||||
addEventListener("DOMFormHasPassword", function(event) {
|
||||
InsecurePasswordUtils.checkForInsecurePasswords(event.target);
|
||||
LoginManagerContent.onFormPassword(event);
|
||||
@ -148,7 +157,8 @@ let handleContentContextMenu = function (event) {
|
||||
InlineSpellCheckerContent.initContextMenu(event, editFlags, this);
|
||||
}
|
||||
|
||||
sendSyncMessage("contextmenu", { editFlags, spellInfo, addonInfo }, { event, popupNode: event.target });
|
||||
let customMenuItems = PageMenuChild.build(event.target);
|
||||
sendSyncMessage("contextmenu", { editFlags, spellInfo, customMenuItems, addonInfo }, { event, popupNode: event.target });
|
||||
}
|
||||
else {
|
||||
// Break out to the parent window and pass the add-on info along
|
||||
|
@ -24,10 +24,15 @@ nsContextMenu.prototype = {
|
||||
return;
|
||||
|
||||
this.hasPageMenu = false;
|
||||
// FIXME (bug 1047751) - The page menu is disabled in e10s.
|
||||
if (!aIsShift && !this.isRemote) {
|
||||
this.hasPageMenu = PageMenu.maybeBuildAndAttachMenu(this.target,
|
||||
aXulMenu);
|
||||
if (!aIsShift) {
|
||||
if (this.isRemote) {
|
||||
this.hasPageMenu =
|
||||
PageMenuParent.addToPopup(gContextMenuContentData.customMenuItems,
|
||||
this.browser, aXulMenu);
|
||||
}
|
||||
else {
|
||||
this.hasPageMenu = PageMenuParent.buildAndAddToPopup(this.target, aXulMenu);
|
||||
}
|
||||
}
|
||||
|
||||
this.isFrameImage = document.getElementById("isFrameImage");
|
||||
@ -1766,7 +1771,7 @@ nsContextMenu.prototype = {
|
||||
}
|
||||
|
||||
// Check if this is a page menu item:
|
||||
if (e.target.hasAttribute(PageMenu.GENERATEDITEMID_ATTR)) {
|
||||
if (e.target.hasAttribute(PageMenuParent.GENERATEDITEMID_ATTR)) {
|
||||
this._telemetryClickID = "custom-page-item";
|
||||
} else {
|
||||
this._telemetryClickID = (e.target.id || "unknown").replace(/^context-/i, "");
|
||||
|
@ -3175,6 +3175,7 @@
|
||||
browser: browser,
|
||||
editFlags: aMessage.data.editFlags,
|
||||
spellInfo: spellInfo,
|
||||
customMenuItems: aMessage.data.customMenuItems,
|
||||
addonInfo: aMessage.data.addonInfo };
|
||||
let popup = browser.ownerDocument.getElementById("contentAreaContextMenu");
|
||||
let event = gContextMenuContentData.event;
|
||||
|
@ -72,6 +72,7 @@ support-files =
|
||||
redirect_bug623155.sjs
|
||||
searchSuggestionEngine.sjs
|
||||
searchSuggestionEngine.xml
|
||||
subtst_contextmenu.html
|
||||
test-mixedcontent-securityerrors.html
|
||||
test_bug435035.html
|
||||
test_bug462673.html
|
||||
@ -486,4 +487,5 @@ skip-if = e10s # bug 1100687 - test directly manipulates content (content.docume
|
||||
[browser_mcb_redirect.js]
|
||||
skip-if = e10s # bug 1084504 - [e10s] Mixed content detection does not take redirection into account
|
||||
[browser_windowactivation.js]
|
||||
[browser_contextmenu_childprocess.js]
|
||||
[browser_bug963945.js]
|
||||
|
@ -0,0 +1,87 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const gBaseURL = "https://example.com/browser/browser/base/content/test/general/";
|
||||
|
||||
add_task(function *() {
|
||||
let tab = gBrowser.addTab();
|
||||
let browser = gBrowser.getBrowserForTab(tab);
|
||||
|
||||
gBrowser.selectedTab = tab;
|
||||
yield promiseTabLoadEvent(tab, gBaseURL + "subtst_contextmenu.html");
|
||||
|
||||
let popupShownPromise = promiseWaitForEvent(window, "popupshown", true);
|
||||
|
||||
// Get the point of the element with the page menu (test-pagemenu) and
|
||||
// synthesize a right mouse click there.
|
||||
let eventDetails = { type : "contextmenu", button : 2 };
|
||||
let rect = browser.contentWindow.document.getElementById("test-pagemenu").getBoundingClientRect();
|
||||
EventUtils.synthesizeMouse(browser, rect.x + rect.width / 2, rect.y + rect.height / 2, eventDetails, window);
|
||||
|
||||
let event = yield popupShownPromise;
|
||||
|
||||
let contextMenu = document.getElementById("contentAreaContextMenu");
|
||||
checkMenu(contextMenu);
|
||||
contextMenu.hidePopup();
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
||||
|
||||
function checkItems(menuitem, arr)
|
||||
{
|
||||
for (let i = 0; i < arr.length; i += 2) {
|
||||
let str = arr[i];
|
||||
let details = arr[i + 1];
|
||||
if (str == "---") {
|
||||
is(menuitem.localName, "menuseparator", "menuseparator");
|
||||
}
|
||||
else if ("children" in details) {
|
||||
is(menuitem.localName, "menu", "submenu");
|
||||
is(menuitem.getAttribute("label"), str, str + " label");
|
||||
checkItems(menuitem.firstChild.firstChild, details.children);
|
||||
}
|
||||
else {
|
||||
is(menuitem.localName, "menuitem", str + " menuitem");
|
||||
|
||||
is(menuitem.getAttribute("label"), str, str + " label");
|
||||
is(menuitem.getAttribute("type"), details.type, str + " type");
|
||||
is(menuitem.getAttribute("image"), details.icon ? gBaseURL + details.icon : "", str + " icon");
|
||||
|
||||
if (details.checked)
|
||||
is(menuitem.getAttribute("checked"), "true", str + " checked");
|
||||
else
|
||||
ok(!menuitem.hasAttribute("checked"), str + " checked");
|
||||
|
||||
if (details.disabled)
|
||||
is(menuitem.getAttribute("disabled"), "true", str + " disabled");
|
||||
else
|
||||
ok(!menuitem.hasAttribute("disabled"), str + " disabled");
|
||||
}
|
||||
|
||||
menuitem = menuitem.nextSibling;
|
||||
}
|
||||
}
|
||||
|
||||
function checkMenu(contextMenu)
|
||||
{
|
||||
let items = [ "Plain item", {type: "", icon: "", checked: false, disabled: false},
|
||||
"Disabled item", {type: "", icon: "", checked: false, disabled: true},
|
||||
"Item w/ textContent", {type: "", icon: "", checked: false, disabled: false},
|
||||
"---", null,
|
||||
"Checkbox", {type: "checkbox", icon: "", checked: true, disabled: false},
|
||||
"---", null,
|
||||
"Radio1", {type: "checkbox", icon: "", checked: true, disabled: false},
|
||||
"Radio2", {type: "checkbox", icon: "", checked: false, disabled: false},
|
||||
"Radio3", {type: "checkbox", icon: "", checked: false, disabled: false},
|
||||
"---", null,
|
||||
"Item w/ icon", {type: "", icon: "favicon.ico", checked: false, disabled: false},
|
||||
"Item w/ bad icon", {type: "", icon: "", checked: false, disabled: false},
|
||||
"---", null,
|
||||
"Submenu", { children:
|
||||
["Radio1", {type: "checkbox", icon: "", checked: false, disabled: false},
|
||||
"Radio2", {type: "checkbox", icon: "", checked: true, disabled: false},
|
||||
"Radio3", {type: "checkbox", icon: "", checked: false, disabled: false},
|
||||
"---", null,
|
||||
"Checkbox", {type: "checkbox", icon: "", checked: false, disabled: false}] }
|
||||
];
|
||||
checkItems(contextMenu.childNodes[2], items);
|
||||
}
|
@ -495,7 +495,7 @@ function runTest(testNum) {
|
||||
"context-viewinfo", true
|
||||
].concat(inspectItems));
|
||||
|
||||
invokeItemAction("0");
|
||||
invokeItemAction("1");
|
||||
closeContextMenu();
|
||||
|
||||
// run mozRequestFullScreen on the element we're testing
|
||||
|
@ -542,6 +542,8 @@
|
||||
@RESPATH@/components/Identity.manifest
|
||||
@RESPATH@/components/recording-cmdline.js
|
||||
@RESPATH@/components/recording-cmdline.manifest
|
||||
@RESPATH@/components/htmlMenuBuilder.js
|
||||
@RESPATH@/components/htmlMenuBuilder.manifest
|
||||
|
||||
@RESPATH@/components/PermissionSettings.js
|
||||
@RESPATH@/components/PermissionSettings.manifest
|
||||
|
@ -9,11 +9,13 @@
|
||||
#include "mozilla/EventDispatcher.h"
|
||||
#include "mozilla/dom/HTMLMenuElementBinding.h"
|
||||
#include "mozilla/dom/HTMLMenuItemElement.h"
|
||||
#include "nsIMenuBuilder.h"
|
||||
#include "nsAttrValueInlines.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsXULContextMenuBuilder.h"
|
||||
#include "nsIURI.h"
|
||||
|
||||
#define HTMLMENUBUILDER_CONTRACTID "@mozilla.org/content/html-menu-builder;1"
|
||||
|
||||
NS_IMPL_NS_NEW_HTML_ELEMENT(Menu)
|
||||
|
||||
namespace mozilla {
|
||||
@ -97,12 +99,8 @@ HTMLMenuElement::CreateBuilder(nsIMenuBuilder** _retval)
|
||||
{
|
||||
NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_DOM_SECURITY_ERR);
|
||||
|
||||
*_retval = nullptr;
|
||||
|
||||
if (mType == MENU_TYPE_CONTEXT) {
|
||||
NS_ADDREF(*_retval = new nsXULContextMenuBuilder());
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIMenuBuilder> builder = CreateBuilder();
|
||||
builder.swap(*_retval);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@ -113,8 +111,9 @@ HTMLMenuElement::CreateBuilder()
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIMenuBuilder> ret = new nsXULContextMenuBuilder();
|
||||
return ret.forget();
|
||||
nsCOMPtr<nsIMenuBuilder> builder = do_CreateInstance(HTMLMENUBUILDER_CONTRACTID);
|
||||
NS_WARN_IF(!builder);
|
||||
return builder.forget();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
|
132
dom/html/htmlMenuBuilder.js
Normal file
132
dom/html/htmlMenuBuilder.js
Normal file
@ -0,0 +1,132 @@
|
||||
/* 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 component is used to build the menus for the HTML contextmenu attribute.
|
||||
|
||||
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
|
||||
// A global value that is used to identify each menu item. It is
|
||||
// incremented with each one that is found.
|
||||
var gGeneratedId = 1;
|
||||
|
||||
function HTMLMenuBuilder() {
|
||||
this.currentNode = null;
|
||||
this.root = null;
|
||||
this.items = {};
|
||||
this.nestedStack = [];
|
||||
};
|
||||
|
||||
// Building is done in two steps:
|
||||
// The first generates a hierarchical JS object that contains the menu structure.
|
||||
// This object is returned by toJSONString.
|
||||
//
|
||||
// The second step can take this structure and generate a XUL menu hierarchy or
|
||||
// other UI from this object. The default UI is done in PageMenu.jsm.
|
||||
//
|
||||
// When a multi-process browser is used, the first step is performed by the child
|
||||
// process and the second step is performed by the parent process.
|
||||
|
||||
HTMLMenuBuilder.prototype =
|
||||
{
|
||||
classID: Components.ID("{51c65f5d-0de5-4edc-9058-60e50cef77f8}"),
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIMenuBuilder]),
|
||||
|
||||
currentNode: null,
|
||||
root: null,
|
||||
items: {},
|
||||
nestedStack: [],
|
||||
|
||||
toJSONString: function() {
|
||||
return JSON.stringify(this.root);
|
||||
},
|
||||
|
||||
openContainer: function(aLabel) {
|
||||
if (!this.currentNode) {
|
||||
this.root = {
|
||||
type: "menu",
|
||||
children: []
|
||||
};
|
||||
this.currentNode = this.root;
|
||||
}
|
||||
else {
|
||||
let parent = this.currentNode;
|
||||
this.currentNode = {
|
||||
type: "menu",
|
||||
label: aLabel,
|
||||
children: []
|
||||
};
|
||||
parent.children.push(this.currentNode);
|
||||
this.nestedStack.push(parent);
|
||||
}
|
||||
},
|
||||
|
||||
addItemFor: function(aElement, aCanLoadIcon) {
|
||||
if (!("children" in this.currentNode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let item = {
|
||||
type: "menuitem",
|
||||
label: aElement.label
|
||||
};
|
||||
|
||||
let elementType = aElement.type;
|
||||
if (elementType == "checkbox" || elementType == "radio") {
|
||||
item.checkbox = true;
|
||||
|
||||
if (aElement.checked) {
|
||||
item.checked = true;
|
||||
}
|
||||
}
|
||||
|
||||
let icon = aElement.icon;
|
||||
if (icon.length > 0 && aCanLoadIcon) {
|
||||
item.icon = icon;
|
||||
}
|
||||
|
||||
if (aElement.disabled) {
|
||||
item.disabled = true;
|
||||
}
|
||||
|
||||
item.id = gGeneratedId++;
|
||||
this.currentNode.children.push(item);
|
||||
|
||||
this.items[item.id] = aElement;
|
||||
},
|
||||
|
||||
addSeparator: function() {
|
||||
if (!("children" in this.currentNode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentNode.children.push({ type: "separator"});
|
||||
},
|
||||
|
||||
undoAddSeparator: function() {
|
||||
if (!("children" in this.currentNode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let children = this.currentNode.children;
|
||||
if (children.length && children[children.length - 1].type == "separator") {
|
||||
children.pop();
|
||||
}
|
||||
},
|
||||
|
||||
closeContainer: function() {
|
||||
this.currentNode = this.nestedStack.length ? this.nestedStack.pop() : this.root;
|
||||
},
|
||||
|
||||
click: function(id) {
|
||||
let item = this.items[id];
|
||||
if (item) {
|
||||
item.click();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([HTMLMenuBuilder]);
|
3
dom/html/htmlMenuBuilder.manifest
Normal file
3
dom/html/htmlMenuBuilder.manifest
Normal file
@ -0,0 +1,3 @@
|
||||
component {51c65f5d-0de5-4edc-9058-60e50cef77f8} htmlMenuBuilder.js
|
||||
contract @mozilla.org/content/html-menu-builder;1 {51c65f5d-0de5-4edc-9058-60e50cef77f8}
|
||||
|
@ -215,6 +215,11 @@ SOURCES += [
|
||||
'PluginDocument.cpp',
|
||||
]
|
||||
|
||||
EXTRA_COMPONENTS += [
|
||||
'htmlMenuBuilder.js',
|
||||
'htmlMenuBuilder.manifest'
|
||||
]
|
||||
|
||||
FAIL_ON_WARNINGS = True
|
||||
|
||||
MSVC_ENABLE_PGO = True
|
||||
|
@ -11,7 +11,7 @@ interface nsIDOMHTMLMenuItemElement;
|
||||
* An interface used to construct native toolbar or context menus from <menu>
|
||||
*/
|
||||
|
||||
[scriptable, uuid(12724737-f7db-43b4-94ab-708a7b86e115)]
|
||||
[scriptable, uuid(93F4A48F-D043-4F45-97FD-9771EA1AF976)]
|
||||
interface nsIMenuBuilder : nsISupports
|
||||
{
|
||||
|
||||
@ -49,4 +49,28 @@ interface nsIMenuBuilder : nsISupports
|
||||
*/
|
||||
void closeContainer();
|
||||
|
||||
/**
|
||||
* Returns a JSON string representing the menu hierarchy. For a context menu,
|
||||
* it will be of the form:
|
||||
* {
|
||||
* type: "menu",
|
||||
* children: [
|
||||
* {
|
||||
* type: "menuitem",
|
||||
* label: "label",
|
||||
* icon: "image.png"
|
||||
* },
|
||||
* {
|
||||
* type: "separator",
|
||||
* },
|
||||
* ];
|
||||
*/
|
||||
AString toJSONString();
|
||||
|
||||
/**
|
||||
* Invoke the action of the menuitem with assigned id aGeneratedItemId.
|
||||
*
|
||||
* @param aGeneratedItemId the menuitem id
|
||||
*/
|
||||
void click(in DOMString aGeneratedItemId);
|
||||
};
|
||||
|
@ -40,11 +40,11 @@ partial interface HTMLMenuElement {
|
||||
|
||||
/**
|
||||
* Creates a native menu builder. The builder type is dependent on menu type.
|
||||
* Currently, it returns nsXULContextMenuBuilder for context menus.
|
||||
* Toolbar menus are not yet supported (the method returns null).
|
||||
* Currently, it returns the @mozilla.org/content/html-menu-builder;1
|
||||
* component. Toolbar menus are not yet supported (the method returns null).
|
||||
*/
|
||||
[ChromeOnly]
|
||||
MenuBuilder createBuilder();
|
||||
MenuBuilder? createBuilder();
|
||||
|
||||
/*
|
||||
* Builds a menu by iterating over menu children.
|
||||
|
@ -12,7 +12,6 @@ if CONFIG['MOZ_XUL']:
|
||||
DIRS += ['templates']
|
||||
|
||||
XPIDL_SOURCES += [
|
||||
'nsIXULContextMenuBuilder.idl',
|
||||
'nsIXULOverlayProvider.idl',
|
||||
]
|
||||
|
||||
@ -23,7 +22,6 @@ if CONFIG['MOZ_XUL']:
|
||||
UNIFIED_SOURCES += [
|
||||
'nsXULCommandDispatcher.cpp',
|
||||
'nsXULContentSink.cpp',
|
||||
'nsXULContextMenuBuilder.cpp',
|
||||
'nsXULElement.cpp',
|
||||
'nsXULPopupListener.cpp',
|
||||
'nsXULPrototypeCache.cpp',
|
||||
|
@ -1,38 +0,0 @@
|
||||
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
interface nsIDOMDocumentFragment;
|
||||
|
||||
/**
|
||||
* An interface for initialization of XUL context menu builder
|
||||
* and for triggering of menuitem actions with assigned identifiers.
|
||||
*/
|
||||
|
||||
[scriptable, uuid(eb6b42c0-2f1c-4760-b5ca-bdc9b3ec77d4)]
|
||||
interface nsIXULContextMenuBuilder : nsISupports
|
||||
{
|
||||
|
||||
/**
|
||||
* Initialize builder before building.
|
||||
*
|
||||
* @param aDocumentFragment the fragment that will be used to append top
|
||||
* level elements
|
||||
*
|
||||
* @param aGeneratedItemIdAttrName the name of the attribute that will be
|
||||
* used to mark elements as generated and for menuitem identification
|
||||
*/
|
||||
void init(in nsIDOMDocumentFragment aDocumentFragment,
|
||||
in AString aGeneratedItemIdAttrName);
|
||||
|
||||
/**
|
||||
* Invoke the action of the menuitem with assigned id aGeneratedItemId.
|
||||
*
|
||||
* @param aGeneratedItemId the menuitem id
|
||||
*/
|
||||
void click(in DOMString aGeneratedItemId);
|
||||
|
||||
};
|
@ -1,230 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#include "nsContentCreatorFunctions.h"
|
||||
#include "nsIContent.h"
|
||||
#include "nsIDOMDocumentFragment.h"
|
||||
#include "nsIDOMHTMLElement.h"
|
||||
#include "nsIDOMHTMLMenuItemElement.h"
|
||||
#include "nsXULContextMenuBuilder.h"
|
||||
#include "nsIDocument.h"
|
||||
#include "mozilla/dom/Element.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::dom;
|
||||
|
||||
nsXULContextMenuBuilder::nsXULContextMenuBuilder()
|
||||
: mCurrentGeneratedItemId(0)
|
||||
{
|
||||
}
|
||||
|
||||
nsXULContextMenuBuilder::~nsXULContextMenuBuilder()
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION(nsXULContextMenuBuilder, mFragment, mDocument,
|
||||
mCurrentNode, mElements)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXULContextMenuBuilder)
|
||||
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXULContextMenuBuilder)
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXULContextMenuBuilder)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIMenuBuilder)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIXULContextMenuBuilder)
|
||||
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMenuBuilder)
|
||||
NS_INTERFACE_MAP_END
|
||||
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsXULContextMenuBuilder::OpenContainer(const nsAString& aLabel)
|
||||
{
|
||||
if (!mFragment) {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
if (!mCurrentNode) {
|
||||
mCurrentNode = mFragment;
|
||||
} else {
|
||||
nsCOMPtr<Element> menu;
|
||||
nsresult rv = CreateElement(nsGkAtoms::menu, nullptr, getter_AddRefs(menu));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
menu->SetAttr(kNameSpaceID_None, nsGkAtoms::label, aLabel, false);
|
||||
|
||||
nsCOMPtr<Element> menuPopup;
|
||||
rv = CreateElement(nsGkAtoms::menupopup, nullptr,
|
||||
getter_AddRefs(menuPopup));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = menu->AppendChildTo(menuPopup, false);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = mCurrentNode->AppendChildTo(menu, false);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
mCurrentNode = menuPopup;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsXULContextMenuBuilder::AddItemFor(nsIDOMHTMLMenuItemElement* aElement,
|
||||
bool aCanLoadIcon)
|
||||
{
|
||||
if (!mFragment) {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
nsCOMPtr<Element> menuitem;
|
||||
nsCOMPtr<nsIDOMHTMLElement> element = do_QueryInterface(aElement);
|
||||
nsresult rv = CreateElement(nsGkAtoms::menuitem, element,
|
||||
getter_AddRefs(menuitem));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsAutoString type;
|
||||
aElement->GetType(type);
|
||||
if (type.EqualsLiteral("checkbox") || type.EqualsLiteral("radio")) {
|
||||
// The menu is only temporary, so we don't need to handle
|
||||
// the radio type precisely.
|
||||
menuitem->SetAttr(kNameSpaceID_None, nsGkAtoms::type,
|
||||
NS_LITERAL_STRING("checkbox"), false);
|
||||
bool checked;
|
||||
aElement->GetChecked(&checked);
|
||||
if (checked) {
|
||||
menuitem->SetAttr(kNameSpaceID_None, nsGkAtoms::checked,
|
||||
NS_LITERAL_STRING("true"), false);
|
||||
}
|
||||
}
|
||||
|
||||
nsAutoString label;
|
||||
aElement->GetLabel(label);
|
||||
menuitem->SetAttr(kNameSpaceID_None, nsGkAtoms::label, label, false);
|
||||
|
||||
nsAutoString icon;
|
||||
aElement->GetIcon(icon);
|
||||
if (!icon.IsEmpty()) {
|
||||
menuitem->SetAttr(kNameSpaceID_None, nsGkAtoms::_class,
|
||||
NS_LITERAL_STRING("menuitem-iconic"), false);
|
||||
if (aCanLoadIcon) {
|
||||
menuitem->SetAttr(kNameSpaceID_None, nsGkAtoms::image, icon, false);
|
||||
}
|
||||
}
|
||||
|
||||
bool disabled;
|
||||
aElement->GetDisabled(&disabled);
|
||||
if (disabled) {
|
||||
menuitem->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled,
|
||||
NS_LITERAL_STRING("true"), false);
|
||||
}
|
||||
|
||||
return mCurrentNode->AppendChildTo(menuitem, false);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsXULContextMenuBuilder::AddSeparator()
|
||||
{
|
||||
if (!mFragment) {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
nsCOMPtr<Element> menuseparator;
|
||||
nsresult rv = CreateElement(nsGkAtoms::menuseparator, nullptr,
|
||||
getter_AddRefs(menuseparator));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return mCurrentNode->AppendChildTo(menuseparator, false);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsXULContextMenuBuilder::UndoAddSeparator()
|
||||
{
|
||||
if (!mFragment) {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
uint32_t count = mCurrentNode->GetChildCount();
|
||||
if (!count ||
|
||||
mCurrentNode->GetChildAt(count - 1)->Tag() != nsGkAtoms::menuseparator) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
mCurrentNode->RemoveChildAt(count - 1, false);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsXULContextMenuBuilder::CloseContainer()
|
||||
{
|
||||
if (!mFragment) {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
if (mCurrentNode == mFragment) {
|
||||
mCurrentNode = nullptr;
|
||||
} else {
|
||||
nsIContent* parent = mCurrentNode->GetParent();
|
||||
mCurrentNode = parent->GetParent();
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsXULContextMenuBuilder::Init(nsIDOMDocumentFragment* aDocumentFragment,
|
||||
const nsAString& aGeneratedItemIdAttrName)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aDocumentFragment);
|
||||
|
||||
mFragment = do_QueryInterface(aDocumentFragment);
|
||||
mDocument = mFragment->GetOwnerDocument();
|
||||
mGeneratedItemIdAttr = do_GetAtom(aGeneratedItemIdAttrName);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsXULContextMenuBuilder::Click(const nsAString& aGeneratedItemId)
|
||||
{
|
||||
nsresult rv;
|
||||
int32_t idx = nsString(aGeneratedItemId).ToInteger(&rv);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
nsCOMPtr<nsIDOMHTMLElement> element = mElements.SafeObjectAt(idx);
|
||||
if (element) {
|
||||
element->DOMClick();
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsXULContextMenuBuilder::CreateElement(nsIAtom* aTag,
|
||||
nsIDOMHTMLElement* aHTMLElement,
|
||||
Element** aResult)
|
||||
{
|
||||
*aResult = nullptr;
|
||||
|
||||
nsRefPtr<mozilla::dom::NodeInfo> nodeInfo = mDocument->NodeInfoManager()->GetNodeInfo(
|
||||
aTag, nullptr, kNameSpaceID_XUL, nsIDOMNode::ELEMENT_NODE);
|
||||
|
||||
nsresult rv = NS_NewElement(aResult, nodeInfo.forget(), NOT_FROM_PARSER);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsAutoString generateditemid;
|
||||
|
||||
if (aHTMLElement) {
|
||||
mElements.AppendObject(aHTMLElement);
|
||||
generateditemid.AppendInt(mCurrentGeneratedItemId++);
|
||||
}
|
||||
|
||||
(*aResult)->SetAttr(kNameSpaceID_None, mGeneratedItemIdAttr, generateditemid,
|
||||
false);
|
||||
|
||||
return NS_OK;
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsCOMArray.h"
|
||||
#include "nsIMenuBuilder.h"
|
||||
#include "nsIXULContextMenuBuilder.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
|
||||
class nsIAtom;
|
||||
class nsIContent;
|
||||
class nsIDocument;
|
||||
class nsIDOMHTMLElement;
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
class Element;
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
class nsXULContextMenuBuilder : public nsIMenuBuilder,
|
||||
public nsIXULContextMenuBuilder
|
||||
{
|
||||
public:
|
||||
nsXULContextMenuBuilder();
|
||||
|
||||
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsXULContextMenuBuilder,
|
||||
nsIMenuBuilder)
|
||||
NS_DECL_NSIMENUBUILDER
|
||||
|
||||
NS_DECL_NSIXULCONTEXTMENUBUILDER
|
||||
|
||||
protected:
|
||||
virtual ~nsXULContextMenuBuilder();
|
||||
|
||||
nsresult CreateElement(nsIAtom* aTag,
|
||||
nsIDOMHTMLElement* aHTMLElement,
|
||||
mozilla::dom::Element** aResult);
|
||||
|
||||
nsCOMPtr<nsIContent> mFragment;
|
||||
nsCOMPtr<nsIDocument> mDocument;
|
||||
nsCOMPtr<nsIAtom> mGeneratedItemIdAttr;
|
||||
|
||||
nsCOMPtr<nsIContent> mCurrentNode;
|
||||
int32_t mCurrentGeneratedItemId;
|
||||
|
||||
nsCOMArray<nsIDOMHTMLElement> mElements;
|
||||
};
|
@ -408,6 +408,8 @@
|
||||
@BINPATH@/components/Webapps.manifest
|
||||
@BINPATH@/components/AppsService.js
|
||||
@BINPATH@/components/AppsService.manifest
|
||||
@RESPATH@/components/htmlMenuBuilder.js
|
||||
@RESPATH@/components/htmlMenuBuilder.manifest
|
||||
|
||||
@BINPATH@/components/Activities.manifest
|
||||
@BINPATH@/components/ActivitiesGlue.js
|
||||
|
@ -2,7 +2,7 @@
|
||||
* 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.EXPORTED_SYMBOLS = ["PageMenuParent", "PageMenuChild"];
|
||||
|
||||
this.PageMenu = function PageMenu() {
|
||||
}
|
||||
@ -11,46 +11,71 @@ PageMenu.prototype = {
|
||||
PAGEMENU_ATTR: "pagemenu",
|
||||
GENERATEDITEMID_ATTR: "generateditemid",
|
||||
|
||||
popup: null,
|
||||
builder: null,
|
||||
_popup: null,
|
||||
|
||||
maybeBuildAndAttachMenu: function(aTarget, aPopup) {
|
||||
var pageMenu = null;
|
||||
var target = aTarget;
|
||||
// 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) {
|
||||
var contextMenu = target.contextMenu;
|
||||
let contextMenu = target.contextMenu;
|
||||
if (contextMenu) {
|
||||
pageMenu = contextMenu;
|
||||
break;
|
||||
return contextMenu;
|
||||
}
|
||||
target = target.parentNode;
|
||||
}
|
||||
|
||||
if (!pageMenu) {
|
||||
return false;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
var insertionPoint = this.getInsertionPoint(aPopup);
|
||||
if (!insertionPoint) {
|
||||
return false;
|
||||
// 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
|
||||
|
||||
var fragment = aPopup.ownerDocument.createDocumentFragment();
|
||||
this._builder = pageMenu.createBuilder();
|
||||
if (!this._builder) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var builder = pageMenu.createBuilder();
|
||||
if (!builder) {
|
||||
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;
|
||||
}
|
||||
builder.QueryInterface(Components.interfaces.nsIXULContextMenuBuilder);
|
||||
builder.init(fragment, this.GENERATEDITEMID_ATTR);
|
||||
|
||||
pageMenu.build(builder);
|
||||
let insertionPoint = this.getInsertionPoint(aPopup);
|
||||
if (!insertionPoint) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var pos = insertionPoint.getAttribute(this.PAGEMENU_ATTR);
|
||||
let fragment = aPopup.ownerDocument.createDocumentFragment();
|
||||
this.buildXULMenu(aMenu, fragment);
|
||||
|
||||
let pos = insertionPoint.getAttribute(this.PAGEMENU_ATTR);
|
||||
if (pos == "start") {
|
||||
insertionPoint.insertBefore(fragment,
|
||||
insertionPoint.firstChild);
|
||||
@ -60,33 +85,101 @@ PageMenu.prototype = {
|
||||
insertionPoint.appendChild(fragment);
|
||||
}
|
||||
|
||||
this.builder = builder;
|
||||
this.popup = aPopup;
|
||||
this._browser = aBrowser;
|
||||
this._popup = aPopup;
|
||||
|
||||
this.popup.addEventListener("command", this);
|
||||
this.popup.addEventListener("popuphidden", this);
|
||||
this._popup.addEventListener("command", this);
|
||||
this._popup.addEventListener("popuphidden", this);
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
handleEvent: function(event) {
|
||||
var type = event.type;
|
||||
var target = event.target;
|
||||
if (type == "command" && target.hasAttribute(this.GENERATEDITEMID_ATTR)) {
|
||||
this.builder.click(target.getAttribute(this.GENERATEDITEMID_ATTR));
|
||||
} else if (type == "popuphidden" && this.popup == target) {
|
||||
this.removeGeneratedContent(this.popup);
|
||||
// Construct the XUL menu structure for a given JSON object.
|
||||
buildXULMenu: function(aNode, aElementForAppending) {
|
||||
let document = aElementForAppending.ownerDocument;
|
||||
|
||||
this.popup.removeEventListener("popuphidden", this);
|
||||
this.popup.removeEventListener("command", this);
|
||||
let children = aNode.children;
|
||||
for (let child of children) {
|
||||
let menuitem;
|
||||
switch (child.type) {
|
||||
case "menuitem":
|
||||
if (!child.id) {
|
||||
continue; // Ignore children without ids
|
||||
}
|
||||
|
||||
this.popup = null;
|
||||
this.builder = null;
|
||||
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) {
|
||||
this._browser.messageManager.sendAsyncMessage("ContextMenu:DoCustomCommand",
|
||||
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;
|
||||
this._browser = null;
|
||||
}
|
||||
},
|
||||
|
||||
// Get the first child of the given element with the given tag name.
|
||||
getImmediateChild: function(element, tag) {
|
||||
var child = element.firstChild;
|
||||
let child = element.firstChild;
|
||||
while (child) {
|
||||
if (child.localName == tag) {
|
||||
return child;
|
||||
@ -96,16 +189,19 @@ PageMenu.prototype = {
|
||||
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;
|
||||
|
||||
var element = aPopup.firstChild;
|
||||
let element = aPopup.firstChild;
|
||||
while (element) {
|
||||
if (element.localName == "menu") {
|
||||
var popup = this.getImmediateChild(element, "menupopup");
|
||||
let popup = this.getImmediateChild(element, "menupopup");
|
||||
if (popup) {
|
||||
var result = this.getInsertionPoint(popup);
|
||||
let result = this.getInsertionPoint(popup);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
@ -117,19 +213,20 @@ PageMenu.prototype = {
|
||||
return null;
|
||||
},
|
||||
|
||||
// Remove the generated content from the given popup.
|
||||
removeGeneratedContent: function(aPopup) {
|
||||
var ungenerated = [];
|
||||
let ungenerated = [];
|
||||
ungenerated.push(aPopup);
|
||||
|
||||
var count;
|
||||
let count;
|
||||
while (0 != (count = ungenerated.length)) {
|
||||
var last = count - 1;
|
||||
var element = ungenerated[last];
|
||||
let last = count - 1;
|
||||
let element = ungenerated[last];
|
||||
ungenerated.splice(last, 1);
|
||||
|
||||
var i = element.childNodes.length;
|
||||
let i = element.childNodes.length;
|
||||
while (i-- > 0) {
|
||||
var child = element.childNodes[i];
|
||||
let child = element.childNodes[i];
|
||||
if (!child.hasAttribute(this.GENERATEDITEMID_ATTR)) {
|
||||
ungenerated.push(child);
|
||||
continue;
|
||||
@ -139,3 +236,78 @@ PageMenu.prototype = {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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…
Reference in New Issue
Block a user