Bug 617528 Part 2 - Core implementation r=smaug

This commit is contained in:
Jan Varga
2011-08-08 19:31:32 +02:00
parent 2b91286805
commit a76cfe8db7
54 changed files with 2363 additions and 89 deletions

View File

@@ -36,6 +36,7 @@
#
# ***** END LICENSE BLOCK *****
<menuseparator id="page-menu-separator"/>
<menuitem id="spell-no-suggestions"
disabled="true"
label="&spellNoSuggestions.label;"/>

View File

@@ -216,6 +216,12 @@ XPCOMUtils.defineLazyServiceGetter(this, "gCrashReporter",
"nsICrashReporter");
#endif
XPCOMUtils.defineLazyGetter(this, "PageMenu", function() {
let tmp = {};
Cu.import("resource://gre/modules/PageMenu.jsm", tmp);
return new tmp.PageMenu();
});
/**
* We can avoid adding multiple load event listeners and save some time by adding
* one listener that calls all real handlers.

View File

@@ -273,10 +273,10 @@
oncommand="BrowserFullScreen();"/>
</menupopup>
<menupopup id="contentAreaContextMenu"
<menupopup id="contentAreaContextMenu" pagemenu="start"
onpopupshowing="if (event.target != this)
return true;
gContextMenu = new nsContextMenu(this, gBrowser);
gContextMenu = new nsContextMenu(this, gBrowser, event.shiftKey);
if (gContextMenu.shouldDisplay)
updateEditUIVisibility();
return gContextMenu.shouldDisplay;"

View File

@@ -61,14 +61,14 @@
#
# ***** END LICENSE BLOCK *****
function nsContextMenu(aXulMenu, aBrowser) {
function nsContextMenu(aXulMenu, aBrowser, aIsShift) {
this.shouldDisplay = true;
this.initMenu(aBrowser);
this.initMenu(aBrowser, aXulMenu, aIsShift);
}
// Prototype for nsContextMenu "class."
nsContextMenu.prototype = {
initMenu: function CM_initMenu(aBrowser) {
initMenu: function CM_initMenu(aBrowser, aXulMenu, aIsShift) {
// Get contextual info.
this.setTarget(document.popupNode, document.popupRangeParent,
document.popupRangeOffset);
@@ -76,6 +76,12 @@ nsContextMenu.prototype = {
return;
this.browser = aBrowser;
this.hasPageMenu = false;
if (!aIsShift) {
this.hasPageMenu = PageMenu.init(this.target, aXulMenu);
}
this.isFrameImage = document.getElementById("isFrameImage");
this.ellipsis = "\u2026";
try {
@@ -90,6 +96,7 @@ nsContextMenu.prototype = {
},
initItems: function CM_initItems() {
this.initPageMenuSeparator();
this.initOpenItems();
this.initNavigationItems();
this.initViewItems();
@@ -100,6 +107,10 @@ nsContextMenu.prototype = {
this.initMediaPlayerItems();
},
initPageMenuSeparator: function CM_initPageMenuSeparator() {
this.showItem("page-menu-separator", this.hasPageMenu);
},
initOpenItems: function CM_initOpenItems() {
var isMailtoInternal = false;
if (this.onMailtoLink) {

View File

@@ -21,6 +21,40 @@ Browser context menu subtest.
<textarea id="test-textarea">chssseesbbbie</textarea> <!-- a weird word which generates only one suggestion -->
<div id="test-contenteditable" contenteditable="true">chssseefsbbbie</div> <!-- a more weird word which generates no suggestions -->
<input id="test-input-spellcheck" type="text" spellcheck="true" autofocus value="prodkjfgigrty"> <!-- this one also generates one suggestion -->
<div contextmenu="myMenu">
<p id="test-pagemenu" hopeless="true">I've got a context menu!</p>
<menu id="myMenu" type="context">
<menuitem label="Plain item" onclick="document.getElementById('test-pagemenu').removeAttribute('hopeless');"></menuitem>
<menuitem label="Disabled item" disabled></menuitem>
<menu>
<menuitem type="checkbox" label="Checkbox" checked></menuitem>
</menu>
<menu>
<menuitem type="radio" label="Radio1" checked></menuitem>
<menuitem type="radio" label="Radio2"></menuitem>
<menuitem type="radio" label="Radio3"></menuitem>
</menu>
<menu>
<menuitem label="Item w/ icon" icon="favicon.ico"></menuitem>
<menuitem label="Item w/ bad icon" icon="data://www.mozilla.org/favicon.ico"></menuitem>
</menu>
<menu label="Submenu">
<menuitem type="radio" label="Radio1" radiogroup="rg"></menuitem>
<menuitem type="radio" label="Radio2" checked radiogroup="rg"></menuitem>
<menuitem type="radio" label="Radio3" radiogroup="rg"></menuitem>
<menu>
<menuitem type="checkbox" label="Checkbox"></menuitem>
</menu>
</menu>
<menu hidden>
<menuitem label="Bogus item"></menuitem>
</menu>
<menu>
</menu>
<menuitem label="Hidden item" hidden></menuitem>
<menuitem></menuitem>
</menu>
</div>
</body>
</html>

View File

@@ -24,11 +24,11 @@ netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
const Cc = Components.classes;
const Ci = Components.interfaces;
function openContextMenuFor(element) {
function openContextMenuFor(element, shiftkey) {
// Context menu should be closed before we open it again.
is(contextMenu.state, "closed", "checking if popup is closed");
var eventDetails = { type : "contextmenu", button : 2 };
var eventDetails = { type : "contextmenu", button : 2, shiftKey : shiftkey };
synthesizeMouse(element, 2, 2, eventDetails, element.ownerDocument.defaultView);
}
@@ -50,7 +50,15 @@ function executeCopyCommand(command, expectedValue)
is(input.value, expectedValue, "paste for command " + command);
}
function getVisibleMenuItems(aMenu) {
function invokeItemAction(ident)
{
var item = contextMenu.getElementsByAttribute("ident", ident)[0];
ok(item, "Got generated XUL menu item");
item.doCommand();
is(pagemenu.hasAttribute("hopeless"), false, "attribute got removed");
}
function getVisibleMenuItems(aMenu, aData) {
var items = [];
var accessKeys = {};
for (var i = 0; i < aMenu.childNodes.length; i++) {
@@ -62,10 +70,14 @@ function getVisibleMenuItems(aMenu) {
if (key)
key = key.toLowerCase();
var isGenerated = item.hasAttribute("generated");
if (item.nodeName == "menuitem") {
var isSpellSuggestion = item.className == "spell-suggestion";
if (isSpellSuggestion) {
is(item.id, "", "child menuitem #" + i + " is a spelling suggestion");
} else if (isGenerated) {
is(item.id, "", "child menuitem #" + i + " is a generated item");
} else {
ok(item.id, "child menuitem #" + i + " has an ID");
}
@@ -74,6 +86,8 @@ function getVisibleMenuItems(aMenu) {
if (isSpellSuggestion) {
is(key, "", "Spell suggestions shouldn't have an access key");
items.push("*" + label);
} else if (isGenerated) {
items.push("+" + label);
} else if (item.id.indexOf("spell-check-dictionary-") != 0 &&
item.id != "spell-no-suggestions") {
ok(key, "menuitem " + item.id + " has an access key");
@@ -82,21 +96,35 @@ function getVisibleMenuItems(aMenu) {
else
accessKeys[key] = item.id;
}
if (!isSpellSuggestion) {
if (!isSpellSuggestion && !isGenerated) {
items.push(item.id);
}
if (isGenerated) {
var p = {};
p.type = item.getAttribute("type");
p.icon = item.getAttribute("image");
p.checked = item.hasAttribute("checked");
p.disabled = item.hasAttribute("disabled");
items.push(p);
} else {
items.push(!item.disabled);
}
} else if (item.nodeName == "menuseparator") {
ok(true, "--- seperator id is " + item.id);
items.push("---");
items.push(null);
} else if (item.nodeName == "menu") {
if (isGenerated) {
item.id = "generated-submenu-" + aData.generatedSubmenuId++;
}
ok(item.id, "child menu #" + i + " has an ID");
if (!isGenerated) {
ok(key, "menu has an access key");
if (accessKeys[key])
ok(false, "menu " + item.id + " has same accesskey as " + accessKeys[key]);
else
accessKeys[key] = item.id;
}
items.push(item.id);
items.push(!item.disabled);
// Add a dummy item to that the indexes in checkMenu are the same
@@ -113,7 +141,8 @@ function getVisibleMenuItems(aMenu) {
function checkContextMenu(expectedItems) {
is(contextMenu.state, "open", "checking if popup is open");
checkMenu(contextMenu, expectedItems);
var data = { generatedSubmenuId: 1 };
checkMenu(contextMenu, expectedItems, data);
}
/*
@@ -129,8 +158,8 @@ function checkContextMenu(expectedItems) {
* "lol", false] // item disabled
*
*/
function checkMenu(menu, expectedItems) {
var actualItems = getVisibleMenuItems(menu);
function checkMenu(menu, expectedItems, data) {
var actualItems = getVisibleMenuItems(menu, data);
//ok(false, "Items are: " + actualItems);
for (var i = 0; i < expectedItems.length; i+=2) {
var actualItem = actualItems[i];
@@ -142,11 +171,40 @@ function checkMenu(menu, expectedItems) {
var menuID = expectedItems[i - 2]; // The last item was the menu ID.
var submenu = menu.getElementsByAttribute("id", menuID)[0];
ok(submenu && submenu.nodeName == "menu", "got expected submenu element");
checkMenu(submenu.menupopup, expectedItem);
checkMenu(submenu.menupopup, expectedItem, data);
} else {
is(actualItem, expectedItem,
"checking item #" + i/2 + " (" + expectedItem + ") name");
if (expectedEnabled != null)
if (typeof expectedEnabled == "object" && expectedEnabled != null ||
typeof actualEnabled == "object" && actualEnabled != null) {
ok(!(actualEnabled == null), "actualEnabled is not null");
ok(!(expectedEnabled == null), "expectedEnabled is not null");
is(typeof actualEnabled, typeof expectedEnabled, "checking types");
if (typeof actualEnabled != typeof expectedEnabled ||
actualEnabled == null || expectedEnabled == null)
continue;
is(actualEnabled.type, expectedEnabled.type,
"checking item #" + i/2 + " (" + expectedItem + ") type attr value");
var icon = actualEnabled.icon;
if (icon) {
var tmp = "";
var j = icon.length - 1;
while (j && icon[j] != "/") {
tmp = icon[j--] + tmp;
}
icon = tmp;
}
is(icon, expectedEnabled.icon,
"checking item #" + i/2 + " (" + expectedItem + ") icon attr value");
is(actualEnabled.checked, expectedEnabled.checked,
"checking item #" + i/2 + " (" + expectedItem + ") has checked attr");
is(actualEnabled.disabled, expectedEnabled.disabled,
"checking item #" + i/2 + " (" + expectedItem + ") has disabled attr");
} else if (expectedEnabled != null)
is(actualEnabled, expectedEnabled,
"checking item #" + i/2 + " (" + expectedItem + ") enabled state");
}
@@ -411,6 +469,67 @@ function runTest(testNum) {
case 15:
executeCopyCommand("cmd_copyLink", "http://mozilla.com/");
closeContextMenu();
openContextMenuFor(pagemenu); // Invoke context menu for next test.
break;
case 16:
// Context menu for element with assigned content context menu
checkContextMenu(["+Plain item", {type: "", icon: "", checked: false, disabled: false},
"+Disabled item", {type: "", icon: "", checked: false, disabled: true},
"---", 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,
"generated-submenu-1", true,
["+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}], null,
"---", null,
"context-back", false,
"context-forward", false,
"context-reload", true,
"context-stop", false,
"---", null,
"context-bookmarkpage", true,
"context-savepage", true,
"context-sendpage", true,
"---", null,
"context-viewbgimage", false,
"context-selectall", true,
"---", null,
"context-viewsource", true,
"context-viewinfo", true]);
invokeItemAction("0");
closeContextMenu();
openContextMenuFor(pagemenu, true); // Invoke context menu for next test.
break;
case 17:
// Context menu for element with assigned content context menu
// The shift key should bypass content context menu processing
checkContextMenu(["context-back", false,
"context-forward", false,
"context-reload", true,
"context-stop", false,
"---", null,
"context-bookmarkpage", true,
"context-savepage", true,
"context-sendpage", true,
"---", null,
"context-viewbgimage", false,
"context-selectall", true,
"---", null,
"context-viewsource", true,
"context-viewinfo", true]);
subwindow.close();
SimpleTest.finish();
@@ -437,7 +556,7 @@ function runTest(testNum) {
var testNum = 1;
var subwindow, chromeWin, contextMenu;
var text, link, mailto, input, img, canvas, video_ok, video_bad, video_bad2,
iframe, textarea, contenteditable, inputspell;
iframe, textarea, contenteditable, inputspell, pagemenu;
function startTest() {
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
@@ -470,6 +589,7 @@ function startTest() {
textarea = subwindow.document.getElementById("test-textarea");
contenteditable = subwindow.document.getElementById("test-contenteditable");
inputspell = subwindow.document.getElementById("test-input-spellcheck");
pagemenu = subwindow.document.getElementById("test-pagemenu");
contextMenu.addEventListener("popupshown", function() { runTest(++testNum); }, false);
runTest(1);

View File

@@ -80,10 +80,10 @@
<popupset id="mainPopupSet">
<tooltip id="aHTMLTooltip" onpopupshowing="return FillInHTMLTooltip(document.tooltipNode);"/>
<menupopup id="contentAreaContextMenu"
<menupopup id="contentAreaContextMenu" pagemenu="start"
onpopupshowing="if (event.target != this)
return true;
gContextMenu = new nsContextMenu(this, getPanelBrowser());
gContextMenu = new nsContextMenu(this, getPanelBrowser(), event.shiftKey);
if (gContextMenu.shouldDisplay)
document.popupNode = this.triggerNode;
return gContextMenu.shouldDisplay;"

View File

@@ -256,6 +256,7 @@
@BINPATH@/components/xpcom_xpti.xpt
@BINPATH@/components/xpconnect.xpt
@BINPATH@/components/xulapp.xpt
@BINPATH@/components/xul.xpt
@BINPATH@/components/xuldoc.xpt
@BINPATH@/components/xultmpl.xpt
@BINPATH@/components/zipwriter.xpt

View File

@@ -567,6 +567,7 @@ GK_ATOM(menuButton, "menu-button")
GK_ATOM(menuitem, "menuitem")
GK_ATOM(menulist, "menulist")
GK_ATOM(menupopup, "menupopup")
GK_ATOM(menuseparator, "menuseparator")
GK_ATOM(message, "message")
GK_ATOM(meta, "meta")
GK_ATOM(meter, "meter")
@@ -723,6 +724,7 @@ GK_ATOM(onresize, "onresize")
GK_ATOM(onscroll, "onscroll")
GK_ATOM(onselect, "onselect")
GK_ATOM(onset, "onset")
GK_ATOM(onshow, "onshow")
GK_ATOM(onsubmit, "onsubmit")
GK_ATOM(ontext, "ontext")
GK_ATOM(ontouchstart, "ontouchstart")

View File

@@ -285,8 +285,10 @@ EVENT(select,
NS_FORM_SELECTED,
EventNameType_HTMLXUL,
NS_EVENT)
// Not supported yet
// EVENT(show)
EVENT(show,
NS_SHOW_EVENT,
EventNameType_HTML,
NS_EVENT)
EVENT(stalled,
NS_STALLED,
EventNameType_HTML,

View File

@@ -77,7 +77,7 @@ static const char* const sEventNames[] = {
"DOMAttrModified", "DOMCharacterDataModified",
"DOMActivate", "DOMFocusIn", "DOMFocusOut",
"pageshow", "pagehide", "DOMMouseScroll", "MozMousePixelScroll",
"offline", "online", "copy", "cut", "paste", "open", "message",
"offline", "online", "copy", "cut", "paste", "open", "message", "show",
"SVGLoad", "SVGUnload", "SVGAbort", "SVGError", "SVGResize", "SVGScroll",
"SVGZoom",
#ifdef MOZ_SMIL
@@ -1257,6 +1257,8 @@ const char* nsDOMEvent::GetEventName(PRUint32 aEventType)
return sEventNames[eDOMEvents_open];
case NS_MESSAGE:
return sEventNames[eDOMEvents_message];
case NS_SHOW_EVENT:
return sEventNames[eDOMEvents_show];
case NS_SVG_LOAD:
return sEventNames[eDOMEvents_SVGLoad];
case NS_SVG_UNLOAD:

View File

@@ -138,6 +138,7 @@ public:
eDOMEvents_paste,
eDOMEvents_open,
eDOMEvents_message,
eDOMEvents_show,
eDOMEvents_SVGLoad,
eDOMEvents_SVGUnload,
eDOMEvents_SVGAbort,

View File

@@ -48,6 +48,8 @@ XPIDL_MODULE = content_html
XPIDLSRCS = \
nsIFormSubmitObserver.idl \
nsIPhonetic.idl \
nsIHTMLMenu.idl \
nsIMenuBuilder.idl \
$(NULL)
EXPORTS = \

View File

@@ -0,0 +1,73 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla.
*
* The Initial Developer of the Original Code is Mozilla Foundation
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsISupports.idl"
interface nsIMenuBuilder;
/**
* A private interface.
* All methods throw NS_ERROR_DOM_SECURITY_ERR if the caller is not chrome.
*/
[scriptable, uuid(d3d068d8-e223-4228-ba39-4d6df21ba616)]
interface nsIHTMLMenu : nsISupports
{
/**
* Creates and dispatches a trusted event named "show".
* The event is not cancelable and does not bubble.
* See http://www.whatwg.org/specs/web-apps/current-work/multipage/interactive-elements.html#context-menus
*/
void sendShowEvent();
/**
* 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).
*/
nsIMenuBuilder createBuilder();
/*
* Builds a menu by iterating over menu children.
* See http://www.whatwg.org/specs/web-apps/current-work/multipage/interactive-elements.html#building-menus-and-toolbars
* The caller can use a native builder by calling createBuilder() or provide
* a custom builder that implements the nsIMenuBuilder interface.
* A custom builder can be used for example to build native context menus
* that are not defined using <menupopup>.
*/
void build(in nsIMenuBuilder aBuilder);
};

View File

@@ -0,0 +1,83 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla.
*
* The Initial Developer of the Original Code is Mozilla Foundation
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsISupports.idl"
interface nsIDOMHTMLMenuItemElement;
/**
* An interface used to construct native toolbar or context menus from <menu>
*/
[scriptable, uuid(12724737-f7db-43b4-94ab-708a7b86e115)]
interface nsIMenuBuilder : nsISupports
{
/**
* Create the top level menu or a submenu. The implementation should create
* a new context for this menu, so all subsequent methods will add new items
* to this newly created menu.
*/
void openContainer(in DOMString aLabel);
/**
* Add a new menu item. All menu item details can be obtained from
* the element. This method is not called for hidden elements or elements
* with no or empty label. The icon should be loaded only if aCanLoadIcon
* is true.
*/
void addItemFor(in nsIDOMHTMLMenuItemElement aElement,
in boolean aCanLoadIcon);
/**
* Create a new separator.
*/
void addSeparator();
/**
* Remove last added separator.
* Sometimes it's needed to remove last added separator, otherwise it's not
* possible to implement the postprocessing in one pass.
* See http://www.whatwg.org/specs/web-apps/current-work/multipage/interactive-elements.html#building-menus-and-toolbars
*/
void undoAddSeparator();
/**
* Set the context to the parent menu.
*/
void closeContainer();
};

View File

@@ -82,6 +82,8 @@ CPPSRCS = \
nsHTMLLegendElement.cpp \
nsHTMLLinkElement.cpp \
nsHTMLMapElement.cpp \
nsHTMLMenuElement.cpp \
nsHTMLMenuItemElement.cpp \
nsHTMLMetaElement.cpp \
nsHTMLModElement.cpp \
nsHTMLObjectElement.cpp \
@@ -134,6 +136,7 @@ INCLUDES += \
-I$(srcdir)/../../../base/src \
-I$(srcdir)/../../../events/src \
-I$(srcdir)/../../../xbl/src \
-I$(srcdir)/../../../xul/content/src \
-I$(srcdir)/../../../../layout/forms \
-I$(srcdir)/../../../../layout/style \
-I$(srcdir)/../../../../layout/tables \

View File

@@ -50,6 +50,7 @@
#include "nsIDOMAttr.h"
#include "nsIDOMDocumentFragment.h"
#include "nsIDOMNSHTMLElement.h"
#include "nsIDOMHTMLMenuElement.h"
#include "nsIDOMElementCSSInlineStyle.h"
#include "nsIDOMWindow.h"
#include "nsIDOMDocument.h"
@@ -115,6 +116,7 @@
#include "nsITextControlElement.h"
#include "mozilla/dom/Element.h"
#include "nsHTMLFieldSetElement.h"
#include "nsHTMLMenuElement.h"
#include "mozilla/Preferences.h"
@@ -2451,6 +2453,28 @@ nsGenericHTMLElement::GetIsContentEditable(PRBool* aContentEditable)
return NS_OK;
}
nsresult
nsGenericHTMLElement::GetContextMenu(nsIDOMHTMLMenuElement** aContextMenu)
{
*aContextMenu = nsnull;
nsAutoString value;
GetAttr(kNameSpaceID_None, nsGkAtoms::contextmenu, value);
if (value.IsEmpty()) {
return NS_OK;
}
nsIDocument* doc = GetCurrentDoc();
if (doc) {
nsRefPtr<nsHTMLMenuElement> element =
nsHTMLMenuElement::FromContent(doc->GetElementById(value));
element.forget(aContextMenu);
}
return NS_OK;
}
//----------------------------------------------------------------------
NS_IMPL_INT_ATTR(nsGenericHTMLFrameElement, TabIndex, tabindex)

View File

@@ -66,6 +66,7 @@ struct nsRect;
struct nsSize;
class nsHTMLFormElement;
class nsIDOMDOMStringMap;
class nsIDOMHTMLMenuElement;
typedef nsMappedAttributeElement nsGenericHTMLElementBase;
@@ -161,6 +162,7 @@ public:
nsresult GetDataset(nsIDOMDOMStringMap** aDataset);
// Callback for destructor of of dataset to ensure to null out weak pointer.
nsresult ClearDataset();
nsresult GetContextMenu(nsIDOMHTMLMenuElement** aContextMenu);
// Implementation for nsIContent
virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
@@ -533,6 +535,11 @@ public:
return HasAttr(kNameSpaceID_None, nsGkAtoms::disabled);
}
PRBool IsHidden() const
{
return HasAttr(kNameSpaceID_None, nsGkAtoms::hidden);
}
protected:
/**
* Add/remove this element to the documents name cache
@@ -1563,6 +1570,8 @@ NS_DECLARE_NS_NEW_HTML_ELEMENT(Label)
NS_DECLARE_NS_NEW_HTML_ELEMENT(Legend)
NS_DECLARE_NS_NEW_HTML_ELEMENT(Link)
NS_DECLARE_NS_NEW_HTML_ELEMENT(Map)
NS_DECLARE_NS_NEW_HTML_ELEMENT(Menu)
NS_DECLARE_NS_NEW_HTML_ELEMENT(MenuItem)
NS_DECLARE_NS_NEW_HTML_ELEMENT(Meta)
NS_DECLARE_NS_NEW_HTML_ELEMENT(Object)
NS_DECLARE_NS_NEW_HTML_ELEMENT(OptGroup)

View File

@@ -0,0 +1,289 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla.
*
* The Initial Developer of the Original Code is Mozilla Foundation
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsIDOMNSHTMLElement.h"
#include "nsIDOMHTMLMenuItemElement.h"
#include "nsXULContextMenuBuilder.h"
#include "nsGUIEvent.h"
#include "nsEventDispatcher.h"
#include "nsHTMLMenuItemElement.h"
#include "nsHTMLMenuElement.h"
enum MenuType
{
MENU_TYPE_CONTEXT = 1,
MENU_TYPE_TOOLBAR,
MENU_TYPE_LIST
};
static const nsAttrValue::EnumTable kMenuTypeTable[] = {
{ "context", MENU_TYPE_CONTEXT },
{ "toolbar", MENU_TYPE_TOOLBAR },
{ "list", MENU_TYPE_LIST },
{ 0 }
};
static const nsAttrValue::EnumTable* kMenuDefaultType =
&kMenuTypeTable[2];
enum SeparatorType
{
ST_TRUE_INIT = -1,
ST_FALSE = 0,
ST_TRUE = 1
};
NS_IMPL_NS_NEW_HTML_ELEMENT(Menu)
nsHTMLMenuElement::nsHTMLMenuElement(already_AddRefed<nsINodeInfo> aNodeInfo)
: nsGenericHTMLElement(aNodeInfo), mType(MENU_TYPE_LIST)
{
}
nsHTMLMenuElement::~nsHTMLMenuElement()
{
}
NS_IMPL_ADDREF_INHERITED(nsHTMLMenuElement, nsGenericElement)
NS_IMPL_RELEASE_INHERITED(nsHTMLMenuElement, nsGenericElement)
DOMCI_NODE_DATA(HTMLMenuElement, nsHTMLMenuElement)
// QueryInterface implementation for nsHTMLMenuElement
NS_INTERFACE_TABLE_HEAD(nsHTMLMenuElement)
NS_HTML_CONTENT_INTERFACE_TABLE2(nsHTMLMenuElement,
nsIDOMHTMLMenuElement,
nsIHTMLMenu)
NS_HTML_CONTENT_INTERFACE_TABLE_TO_MAP_SEGUE(nsHTMLMenuElement,
nsGenericHTMLElement)
NS_HTML_CONTENT_INTERFACE_TABLE_TAIL_CLASSINFO(HTMLMenuElement)
NS_IMPL_ELEMENT_CLONE(nsHTMLMenuElement)
NS_IMPL_BOOL_ATTR(nsHTMLMenuElement, Compact, compact)
NS_IMPL_ENUM_ATTR_DEFAULT_VALUE(nsHTMLMenuElement, Type, type,
kMenuDefaultType->tag)
NS_IMPL_STRING_ATTR(nsHTMLMenuElement, Label, label)
NS_IMETHODIMP
nsHTMLMenuElement::SendShowEvent()
{
NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_DOM_SECURITY_ERR);
nsCOMPtr<nsIDocument> document = GetCurrentDoc();
if (!document) {
return NS_ERROR_FAILURE;
}
nsEvent event(PR_TRUE, NS_SHOW_EVENT);
event.flags |= NS_EVENT_FLAG_CANT_CANCEL | NS_EVENT_FLAG_CANT_BUBBLE;
nsCOMPtr<nsIPresShell> shell = document->GetShell();
if (!shell) {
return NS_ERROR_FAILURE;
}
nsRefPtr<nsPresContext> presContext = shell->GetPresContext();
nsEventStatus status = nsEventStatus_eIgnore;
nsEventDispatcher::Dispatch(static_cast<nsIContent*>(this), presContext,
&event, nsnull, &status);
return NS_OK;
}
NS_IMETHODIMP
nsHTMLMenuElement::CreateBuilder(nsIMenuBuilder** _retval)
{
NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_DOM_SECURITY_ERR);
*_retval = nsnull;
if (mType == MENU_TYPE_CONTEXT) {
NS_ADDREF(*_retval = new nsXULContextMenuBuilder());
}
return NS_OK;
}
NS_IMETHODIMP
nsHTMLMenuElement::Build(nsIMenuBuilder* aBuilder)
{
NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_DOM_SECURITY_ERR);
if (!aBuilder) {
return NS_OK;
}
BuildSubmenu(EmptyString(), this, aBuilder);
return NS_OK;
}
PRBool
nsHTMLMenuElement::ParseAttribute(PRInt32 aNamespaceID,
nsIAtom* aAttribute,
const nsAString& aValue,
nsAttrValue& aResult)
{
if (aNamespaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::type) {
PRBool success = aResult.ParseEnumValue(aValue, kMenuTypeTable,
PR_FALSE);
if (success) {
mType = aResult.GetEnumValue();
} else {
mType = kMenuDefaultType->value;
}
return success;
}
return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
aResult);
}
void
nsHTMLMenuElement::BuildSubmenu(const nsAString& aLabel,
nsIContent* aContent,
nsIMenuBuilder* aBuilder)
{
aBuilder->OpenContainer(aLabel);
PRInt8 separator = ST_TRUE_INIT;
TraverseContent(aContent, aBuilder, separator);
if (separator == ST_TRUE) {
aBuilder->UndoAddSeparator();
}
aBuilder->CloseContainer();
}
// static
PRBool
nsHTMLMenuElement::CanLoadIcon(nsIContent* aContent, const nsAString& aIcon)
{
if (aIcon.IsEmpty()) {
return PR_FALSE;
}
nsIDocument* doc = aContent->GetOwnerDoc();
if (!doc) {
return PR_FALSE;
}
nsCOMPtr<nsIURI> baseURI = aContent->GetBaseURI();
nsCOMPtr<nsIURI> uri;
nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri), aIcon, doc,
baseURI);
if (!uri) {
return PR_FALSE;
}
return nsContentUtils::CanLoadImage(uri, aContent, doc,
aContent->NodePrincipal());
}
void
nsHTMLMenuElement::TraverseContent(nsIContent* aContent,
nsIMenuBuilder* aBuilder,
PRInt8& aSeparator)
{
nsCOMPtr<nsIContent> child;
for (child = aContent->GetFirstChild(); child;
child = child->GetNextSibling()) {
nsGenericHTMLElement* element = nsGenericHTMLElement::FromContent(child);
if (!element) {
continue;
}
nsIAtom* tag = child->Tag();
if (tag == nsGkAtoms::menuitem) {
nsHTMLMenuItemElement* menuitem =
nsHTMLMenuItemElement::FromContent(child);
if (menuitem->IsHidden()) {
continue;
}
nsAutoString label;
menuitem->GetLabel(label);
if (label.IsEmpty()) {
continue;
}
nsAutoString icon;
menuitem->GetIcon(icon);
aBuilder->AddItemFor(menuitem, CanLoadIcon(child, icon));
aSeparator = ST_FALSE;
} else if (tag == nsGkAtoms::menu && !element->IsHidden()) {
if (child->HasAttr(kNameSpaceID_None, nsGkAtoms::label)) {
nsAutoString label;
child->GetAttr(kNameSpaceID_None, nsGkAtoms::label, label);
BuildSubmenu(label, child, aBuilder);
aSeparator = ST_FALSE;
} else {
AddSeparator(aBuilder, aSeparator);
TraverseContent(child, aBuilder, aSeparator);
AddSeparator(aBuilder, aSeparator);
}
}
}
}
inline void
nsHTMLMenuElement::AddSeparator(nsIMenuBuilder* aBuilder, PRInt8& aSeparator)
{
if (aSeparator) {
return;
}
aBuilder->AddSeparator();
aSeparator = ST_TRUE;
}

View File

@@ -0,0 +1,101 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla.
*
* The Initial Developer of the Original Code is Mozilla Foundation
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsIDOMHTMLMenuElement.h"
#include "nsIHTMLMenu.h"
#include "nsGenericHTMLElement.h"
#include "nsIDOMNSHTMLElement.h"
class nsHTMLMenuElement : public nsGenericHTMLElement,
public nsIDOMHTMLMenuElement,
public nsIHTMLMenu
{
public:
nsHTMLMenuElement(already_AddRefed<nsINodeInfo> aNodeInfo);
virtual ~nsHTMLMenuElement();
/** Typesafe, non-refcounting cast from nsIContent. Cheaper than QI. **/
static nsHTMLMenuElement* FromContent(nsIContent* aContent)
{
if (aContent && aContent->IsHTML(nsGkAtoms::menu))
return static_cast<nsHTMLMenuElement*>(aContent);
return nsnull;
}
// nsISupports
NS_DECL_ISUPPORTS_INHERITED
// nsIDOMNode
NS_FORWARD_NSIDOMNODE(nsGenericHTMLElement::)
// nsIDOMElement
NS_FORWARD_NSIDOMELEMENT(nsGenericHTMLElement::)
// nsIDOMHTMLElement
NS_FORWARD_NSIDOMHTMLELEMENT(nsGenericHTMLElement::)
// nsIDOMHTMLMenuElement
NS_DECL_NSIDOMHTMLMENUELEMENT
// nsIHTMLMenu
NS_DECL_NSIHTMLMENU
virtual PRBool ParseAttribute(PRInt32 aNamespaceID,
nsIAtom* aAttribute,
const nsAString& aValue,
nsAttrValue& aResult);
virtual nsresult Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const;
virtual nsXPCClassInfo* GetClassInfo();
PRUint8 GetType() const { return mType; }
protected:
static PRBool CanLoadIcon(nsIContent* aContent, const nsAString& aIcon);
void BuildSubmenu(const nsAString& aLabel,
nsIContent* aContent,
nsIMenuBuilder* aBuilder);
void TraverseContent(nsIContent* aContent,
nsIMenuBuilder* aBuilder,
PRInt8& aSeparator);
void AddSeparator(nsIMenuBuilder* aBuilder, PRInt8& aSeparator);
PRUint8 mType;
};

View File

@@ -0,0 +1,514 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla.
*
* The Initial Developer of the Original Code is Mozilla Foundation
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsGUIEvent.h"
#include "nsEventDispatcher.h"
#include "nsHTMLMenuItemElement.h"
using namespace mozilla::dom;
// First bits are needed for the menuitem type.
#define NS_CHECKED_IS_TOGGLED (1 << 2)
#define NS_ORIGINAL_CHECKED_VALUE (1 << 3)
#define NS_MENUITEM_TYPE(bits) ((bits) & ~( \
NS_CHECKED_IS_TOGGLED | NS_ORIGINAL_CHECKED_VALUE))
enum CmdType
{
CMD_TYPE_MENUITEM = 1,
CMD_TYPE_CHECKBOX,
CMD_TYPE_RADIO
};
static const nsAttrValue::EnumTable kMenuItemTypeTable[] = {
{ "menuitem", CMD_TYPE_MENUITEM },
{ "checkbox", CMD_TYPE_CHECKBOX },
{ "radio", CMD_TYPE_RADIO },
{ 0 }
};
static const nsAttrValue::EnumTable* kMenuItemDefaultType =
&kMenuItemTypeTable[0];
// A base class inherited by all radio visitors.
class Visitor
{
public:
Visitor() { }
virtual ~Visitor() { }
/**
* Visit a node in the tree. This is meant to be called on all radios in a
* group, sequentially. If the method returns false then the iteration is
* stopped.
*/
virtual PRBool Visit(nsHTMLMenuItemElement* aMenuItem) = 0;
};
// Find the selected radio, see GetSelectedRadio().
class GetCheckedVisitor : public Visitor
{
public:
GetCheckedVisitor(nsHTMLMenuItemElement** aResult)
: mResult(aResult)
{ }
virtual PRBool Visit(nsHTMLMenuItemElement* aMenuItem)
{
if (aMenuItem->IsChecked()) {
*mResult = aMenuItem;
return PR_FALSE;
}
return PR_TRUE;
}
protected:
nsHTMLMenuItemElement** mResult;
};
// Deselect all radios except the one passed to the constructor.
class ClearCheckedVisitor : public Visitor
{
public:
ClearCheckedVisitor(nsHTMLMenuItemElement* aExcludeMenuItem)
: mExcludeMenuItem(aExcludeMenuItem)
{ }
virtual PRBool Visit(nsHTMLMenuItemElement* aMenuItem)
{
if (aMenuItem != mExcludeMenuItem && aMenuItem->IsChecked()) {
aMenuItem->ClearChecked();
}
return PR_TRUE;
}
protected:
nsHTMLMenuItemElement* mExcludeMenuItem;
};
// Get current value of the checked dirty flag. The same value is stored on all
// radios in the group, so we need to check only the first one.
class GetCheckedDirtyVisitor : public Visitor
{
public:
GetCheckedDirtyVisitor(PRBool* aCheckedDirty,
nsHTMLMenuItemElement* aExcludeMenuItem)
: mCheckedDirty(aCheckedDirty),
mExcludeMenuItem(aExcludeMenuItem)
{ }
virtual PRBool Visit(nsHTMLMenuItemElement* aMenuItem)
{
if (aMenuItem == mExcludeMenuItem) {
return PR_TRUE;
}
*mCheckedDirty = aMenuItem->IsCheckedDirty();
return PR_FALSE;
}
protected:
PRBool* mCheckedDirty;
nsHTMLMenuItemElement* mExcludeMenuItem;
};
// Set checked dirty to true on all radios in the group.
class SetCheckedDirtyVisitor : public Visitor
{
public:
SetCheckedDirtyVisitor()
{ }
virtual PRBool Visit(nsHTMLMenuItemElement* aMenuItem)
{
aMenuItem->SetCheckedDirty();
return PR_TRUE;
}
};
// A helper visitor that is used to combine two operations (visitors) to avoid
// iterating over radios twice.
class CombinedVisitor : public Visitor
{
public:
CombinedVisitor(Visitor* aVisitor1, Visitor* aVisitor2)
: mVisitor1(aVisitor1), mVisitor2(aVisitor2),
mContinue1(PR_TRUE), mContinue2(PR_TRUE)
{ }
virtual PRBool Visit(nsHTMLMenuItemElement* aMenuItem)
{
if (mContinue1) {
mContinue1 = mVisitor1->Visit(aMenuItem);
}
if (mContinue2) {
mContinue2 = mVisitor2->Visit(aMenuItem);
}
return mContinue1 || mContinue2;
}
protected:
Visitor* mVisitor1;
Visitor* mVisitor2;
PRPackedBool mContinue1;
PRPackedBool mContinue2;
};
NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(MenuItem)
nsHTMLMenuItemElement::nsHTMLMenuItemElement(
already_AddRefed<nsINodeInfo> aNodeInfo, FromParser aFromParser)
: nsGenericHTMLElement(aNodeInfo),
mType(kMenuItemDefaultType->value),
mParserCreating(false),
mShouldInitChecked(false),
mCheckedDirty(false),
mChecked(false)
{
mParserCreating = aFromParser;
}
nsHTMLMenuItemElement::~nsHTMLMenuItemElement()
{
}
NS_IMPL_ADDREF_INHERITED(nsHTMLMenuItemElement, nsGenericElement)
NS_IMPL_RELEASE_INHERITED(nsHTMLMenuItemElement, nsGenericElement)
DOMCI_NODE_DATA(HTMLMenuItemElement, nsHTMLMenuItemElement)
// QueryInterface implementation for nsHTMLMenuItemElement
NS_INTERFACE_TABLE_HEAD(nsHTMLMenuItemElement)
NS_HTML_CONTENT_INTERFACE_TABLE2(nsHTMLMenuItemElement,
nsIDOMHTMLCommandElement,
nsIDOMHTMLMenuItemElement)
NS_HTML_CONTENT_INTERFACE_TABLE_TO_MAP_SEGUE(nsHTMLMenuItemElement,
nsGenericHTMLElement)
NS_HTML_CONTENT_INTERFACE_TABLE_TAIL_CLASSINFO(HTMLMenuItemElement)
//NS_IMPL_ELEMENT_CLONE(nsHTMLMenuItemElement)
nsresult
nsHTMLMenuItemElement::Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const
{
*aResult = nsnull;
nsCOMPtr<nsINodeInfo> ni = aNodeInfo;
nsHTMLMenuItemElement *it = new nsHTMLMenuItemElement(ni.forget(),
NOT_FROM_PARSER);
if (!it) {
return NS_ERROR_OUT_OF_MEMORY;
}
nsCOMPtr<nsINode> kungFuDeathGrip = it;
nsresult rv = CopyInnerTo(it);
if (NS_SUCCEEDED(rv)) {
switch (mType) {
case CMD_TYPE_CHECKBOX:
case CMD_TYPE_RADIO:
if (mCheckedDirty) {
// We no longer have our original checked state. Set our
// checked state on the clone.
it->mCheckedDirty = true;
it->mChecked = mChecked;
}
break;
}
kungFuDeathGrip.swap(*aResult);
}
return rv;
}
NS_IMPL_ENUM_ATTR_DEFAULT_VALUE(nsHTMLMenuItemElement, Type, type,
kMenuItemDefaultType->tag)
NS_IMPL_STRING_ATTR(nsHTMLMenuItemElement, Label, label)
NS_IMPL_URI_ATTR(nsHTMLMenuItemElement, Icon, icon)
NS_IMPL_BOOL_ATTR(nsHTMLMenuItemElement, Disabled, disabled)
NS_IMPL_BOOL_ATTR(nsHTMLMenuItemElement, DefaultChecked, checked)
//NS_IMPL_BOOL_ATTR(nsHTMLMenuItemElement, Checked, checked)
NS_IMPL_STRING_ATTR(nsHTMLMenuItemElement, Radiogroup, radiogroup)
NS_IMETHODIMP
nsHTMLMenuItemElement::GetChecked(PRBool* aChecked)
{
*aChecked = mChecked;
return NS_OK;
}
NS_IMETHODIMP
nsHTMLMenuItemElement::SetChecked(PRBool aChecked)
{
PRBool checkedChanged = mChecked != aChecked;
mChecked = aChecked;
if (mType == CMD_TYPE_RADIO) {
if (checkedChanged) {
if (mCheckedDirty) {
ClearCheckedVisitor visitor(this);
WalkRadioGroup(&visitor);
} else {
ClearCheckedVisitor visitor1(this);
SetCheckedDirtyVisitor visitor2;
CombinedVisitor visitor(&visitor1, &visitor2);
WalkRadioGroup(&visitor);
}
} else if (!mCheckedDirty) {
SetCheckedDirtyVisitor visitor;
WalkRadioGroup(&visitor);
}
} else {
mCheckedDirty = true;
}
return NS_OK;
}
nsresult
nsHTMLMenuItemElement::PreHandleEvent(nsEventChainPreVisitor& aVisitor)
{
if (aVisitor.mEvent->message == NS_MOUSE_CLICK) {
PRBool originalCheckedValue = PR_FALSE;
switch (mType) {
case CMD_TYPE_CHECKBOX:
originalCheckedValue = mChecked;
SetChecked(!originalCheckedValue);
aVisitor.mItemFlags |= NS_CHECKED_IS_TOGGLED;
break;
case CMD_TYPE_RADIO:
nsCOMPtr<nsIDOMHTMLMenuItemElement> selectedRadio = GetSelectedRadio();
aVisitor.mItemData = selectedRadio;
originalCheckedValue = mChecked;
if (!originalCheckedValue) {
SetChecked(PR_TRUE);
aVisitor.mItemFlags |= NS_CHECKED_IS_TOGGLED;
}
break;
}
if (originalCheckedValue) {
aVisitor.mItemFlags |= NS_ORIGINAL_CHECKED_VALUE;
}
// We must cache type because mType may change during JS event.
aVisitor.mItemFlags |= mType;
}
return nsGenericHTMLElement::PreHandleEvent(aVisitor);
}
nsresult
nsHTMLMenuItemElement::PostHandleEvent(nsEventChainPostVisitor& aVisitor)
{
// Check to see if the event was cancelled.
if (aVisitor.mEvent->message == NS_MOUSE_CLICK &&
aVisitor.mItemFlags & NS_CHECKED_IS_TOGGLED &&
aVisitor.mEventStatus == nsEventStatus_eConsumeNoDefault) {
PRBool originalCheckedValue =
!!(aVisitor.mItemFlags & NS_ORIGINAL_CHECKED_VALUE);
PRUint8 oldType = NS_MENUITEM_TYPE(aVisitor.mItemFlags);
nsCOMPtr<nsIDOMHTMLMenuItemElement> selectedRadio =
do_QueryInterface(aVisitor.mItemData);
if (selectedRadio) {
selectedRadio->SetChecked(PR_TRUE);
if (mType != CMD_TYPE_RADIO) {
SetChecked(PR_FALSE);
}
} else if (oldType == CMD_TYPE_CHECKBOX) {
SetChecked(originalCheckedValue);
}
}
return NS_OK;
}
nsresult
nsHTMLMenuItemElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
nsIContent* aBindingParent,
PRBool aCompileEventHandlers)
{
nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent,
aBindingParent,
aCompileEventHandlers);
if (NS_SUCCEEDED(rv) && aDocument && mType == CMD_TYPE_RADIO) {
AddedToRadioGroup();
}
return rv;
}
PRBool
nsHTMLMenuItemElement::ParseAttribute(PRInt32 aNamespaceID,
nsIAtom* aAttribute,
const nsAString& aValue,
nsAttrValue& aResult)
{
if (aNamespaceID == kNameSpaceID_None) {
if (aAttribute == nsGkAtoms::type) {
PRBool success = aResult.ParseEnumValue(aValue, kMenuItemTypeTable,
PR_FALSE);
if (success) {
mType = aResult.GetEnumValue();
} else {
mType = kMenuItemDefaultType->value;
}
return success;
}
if (aAttribute == nsGkAtoms::radiogroup) {
aResult.ParseAtom(aValue);
return PR_TRUE;
}
}
return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
aResult);
}
void
nsHTMLMenuItemElement::DoneCreatingElement()
{
mParserCreating = false;
if (mShouldInitChecked) {
InitChecked();
mShouldInitChecked = false;
}
}
nsresult
nsHTMLMenuItemElement::AfterSetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
const nsAString* aValue, PRBool aNotify)
{
if (aNameSpaceID == kNameSpaceID_None) {
if ((aName == nsGkAtoms::radiogroup || aName == nsGkAtoms::type) &&
mType == CMD_TYPE_RADIO &&
!mParserCreating) {
if (IsInDoc() && GetParent()) {
AddedToRadioGroup();
}
}
// Checked must be set no matter what type of menuitem it is, since
// GetChecked() must reflect the new value
if (aName == nsGkAtoms::checked &&
!mCheckedDirty) {
if (mParserCreating) {
mShouldInitChecked = true;
} else {
InitChecked();
}
}
}
return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName, aValue,
aNotify);
}
void
nsHTMLMenuItemElement::WalkRadioGroup(Visitor* aVisitor)
{
nsIContent* parent = GetParent();
if (!parent) {
aVisitor->Visit(this);
return;
}
nsAttrInfo info1(GetAttrInfo(kNameSpaceID_None,
nsGkAtoms::radiogroup));
PRBool info1Empty = !info1.mValue || info1.mValue->IsEmptyString();
for (nsIContent* cur = parent->GetFirstChild();
cur;
cur = cur->GetNextSibling()) {
nsHTMLMenuItemElement* menuitem = nsHTMLMenuItemElement::FromContent(cur);
if (!menuitem || menuitem->GetType() != CMD_TYPE_RADIO) {
continue;
}
nsAttrInfo info2(menuitem->GetAttrInfo(kNameSpaceID_None,
nsGkAtoms::radiogroup));
PRBool info2Empty = !info2.mValue || info2.mValue->IsEmptyString();
if (info1Empty != info2Empty ||
info1.mValue && info2.mValue && !info1.mValue->Equals(*info2.mValue)) {
continue;
}
if (!aVisitor->Visit(menuitem)) {
break;
}
}
}
nsHTMLMenuItemElement*
nsHTMLMenuItemElement::GetSelectedRadio()
{
nsHTMLMenuItemElement* result = nsnull;
GetCheckedVisitor visitor(&result);
WalkRadioGroup(&visitor);
return result;
}
void
nsHTMLMenuItemElement::AddedToRadioGroup()
{
PRBool checkedDirty = mCheckedDirty;
if (mChecked) {
ClearCheckedVisitor visitor1(this);
GetCheckedDirtyVisitor visitor2(&checkedDirty, this);
CombinedVisitor visitor(&visitor1, &visitor2);
WalkRadioGroup(&visitor);
} else {
GetCheckedDirtyVisitor visitor(&checkedDirty, this);
WalkRadioGroup(&visitor);
}
mCheckedDirty = checkedDirty;
}
void
nsHTMLMenuItemElement::InitChecked()
{
PRBool defaultChecked;
GetDefaultChecked(&defaultChecked);
mChecked = defaultChecked;
if (mType == CMD_TYPE_RADIO) {
ClearCheckedVisitor visitor(this);
WalkRadioGroup(&visitor);
}
}

View File

@@ -0,0 +1,127 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla.
*
* The Initial Developer of the Original Code is Mozilla Foundation
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsIDOMHTMLMenuItemElement.h"
#include "nsGenericHTMLElement.h"
class Visitor;
class nsHTMLMenuItemElement : public nsGenericHTMLElement,
public nsIDOMHTMLMenuItemElement
{
public:
nsHTMLMenuItemElement(already_AddRefed<nsINodeInfo> aNodeInfo,
mozilla::dom::FromParser aFromParser);
virtual ~nsHTMLMenuItemElement();
/** Typesafe, non-refcounting cast from nsIContent. Cheaper than QI. **/
static nsHTMLMenuItemElement* FromContent(nsIContent* aContent)
{
if (aContent && aContent->IsHTML(nsGkAtoms::menuitem)) {
return static_cast<nsHTMLMenuItemElement*>(aContent);
}
return nsnull;
}
// nsISupports
NS_DECL_ISUPPORTS_INHERITED
// nsIDOMNode
NS_FORWARD_NSIDOMNODE(nsGenericHTMLElement::)
// nsIDOMElement
NS_FORWARD_NSIDOMELEMENT(nsGenericHTMLElement::)
// nsIDOMHTMLElement
NS_FORWARD_NSIDOMHTMLELEMENT(nsGenericHTMLElement::)
// nsIDOMHTMLCommandElement
NS_DECL_NSIDOMHTMLCOMMANDELEMENT
// nsIDOMHTMLMenuItemElement
NS_DECL_NSIDOMHTMLMENUITEMELEMENT
virtual nsresult PreHandleEvent(nsEventChainPreVisitor& aVisitor);
virtual nsresult PostHandleEvent(nsEventChainPostVisitor& aVisitor);
virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
nsIContent* aBindingParent,
PRBool aCompileEventHandlers);
virtual PRBool ParseAttribute(PRInt32 aNamespaceID,
nsIAtom* aAttribute,
const nsAString& aValue,
nsAttrValue& aResult);
virtual void DoneCreatingElement();
virtual nsresult Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const;
virtual nsXPCClassInfo* GetClassInfo();
PRUint8 GetType() const { return mType; }
/**
* Syntax sugar to make it easier to check for checked and checked dirty
*/
PRBool IsChecked() const { return mChecked; }
PRBool IsCheckedDirty() const { return mCheckedDirty; }
protected:
virtual nsresult AfterSetAttr(PRInt32 aNamespaceID, nsIAtom* aName,
const nsAString* aValue, PRBool aNotify);
void WalkRadioGroup(Visitor* aVisitor);
nsHTMLMenuItemElement* GetSelectedRadio();
void AddedToRadioGroup();
void InitChecked();
friend class ClearCheckedVisitor;
friend class SetCheckedDirtyVisitor;
void ClearChecked() { mChecked = false; }
void SetCheckedDirty() { mCheckedDirty = true; }
private:
PRUint8 mType : 2;
bool mParserCreating : 1;
bool mShouldInitChecked : 1;
bool mCheckedDirty : 1;
bool mChecked : 1;
};

View File

@@ -38,7 +38,6 @@
#include "nsIDOMHTMLParamElement.h"
#include "nsIDOMHTMLBaseElement.h"
#include "nsIDOMHTMLDirectoryElement.h"
#include "nsIDOMHTMLMenuElement.h"
#include "nsIDOMHTMLQuoteElement.h"
#include "nsIDOMHTMLHeadElement.h"
#include "nsIDOMHTMLHtmlElement.h"
@@ -59,7 +58,6 @@ class nsHTMLSharedElement : public nsGenericHTMLElement,
public nsIDOMHTMLParamElement,
public nsIDOMHTMLBaseElement,
public nsIDOMHTMLDirectoryElement,
public nsIDOMHTMLMenuElement,
public nsIDOMHTMLQuoteElement,
public nsIDOMHTMLHeadElement,
public nsIDOMHTMLHtmlElement
@@ -89,9 +87,6 @@ public:
// nsIDOMHTMLDirectoryElement
NS_DECL_NSIDOMHTMLDIRECTORYELEMENT
// nsIDOMHTMLMenuElement
// Same as directoryelement
// nsIDOMHTMLQuoteElement
NS_DECL_NSIDOMHTMLQUOTEELEMENT
@@ -157,7 +152,6 @@ NS_IMPL_RELEASE_INHERITED(nsHTMLSharedElement, nsGenericElement)
DOMCI_DATA(HTMLParamElement, nsHTMLSharedElement)
DOMCI_DATA(HTMLBaseElement, nsHTMLSharedElement)
DOMCI_DATA(HTMLDirectoryElement, nsHTMLSharedElement)
DOMCI_DATA(HTMLMenuElement, nsHTMLSharedElement)
DOMCI_DATA(HTMLQuoteElement, nsHTMLSharedElement)
DOMCI_DATA(HTMLHeadElement, nsHTMLSharedElement)
DOMCI_DATA(HTMLHtmlElement, nsHTMLSharedElement)
@@ -174,9 +168,6 @@ nsHTMLSharedElement::GetClassInfoInternal()
if (mNodeInfo->Equals(nsGkAtoms::dir)) {
return NS_GetDOMClassInfoInstance(eDOMClassInfo_HTMLDirectoryElement_id);
}
if (mNodeInfo->Equals(nsGkAtoms::menu)) {
return NS_GetDOMClassInfoInstance(eDOMClassInfo_HTMLMenuElement_id);
}
if (mNodeInfo->Equals(nsGkAtoms::q)) {
return NS_GetDOMClassInfoInstance(eDOMClassInfo_HTMLQuoteElement_id);
}
@@ -203,7 +194,6 @@ NS_INTERFACE_TABLE_HEAD(nsHTMLSharedElement)
NS_INTERFACE_MAP_ENTRY_IF_TAG(nsIDOMHTMLParamElement, param)
NS_INTERFACE_MAP_ENTRY_IF_TAG(nsIDOMHTMLBaseElement, base)
NS_INTERFACE_MAP_ENTRY_IF_TAG(nsIDOMHTMLDirectoryElement, dir)
NS_INTERFACE_MAP_ENTRY_IF_TAG(nsIDOMHTMLMenuElement, menu)
NS_INTERFACE_MAP_ENTRY_IF_TAG(nsIDOMHTMLQuoteElement, q)
NS_INTERFACE_MAP_ENTRY_IF_TAG(nsIDOMHTMLQuoteElement, blockquote)
NS_INTERFACE_MAP_ENTRY_IF_TAG(nsIDOMHTMLHeadElement, head)
@@ -224,9 +214,6 @@ NS_IMPL_STRING_ATTR(nsHTMLSharedElement, ValueType, valuetype)
// nsIDOMHTMLDirectoryElement
NS_IMPL_BOOL_ATTR(nsHTMLSharedElement, Compact, compact)
// nsIDOMHTMLMenuElement
//NS_IMPL_BOOL_ATTR(nsHTMLSharedElement, Compact, compact)
// nsIDOMHTMLQuoteElement
NS_IMPL_URI_ATTR(nsHTMLSharedElement, Cite, cite)
@@ -275,8 +262,7 @@ nsHTMLSharedElement::ParseAttribute(PRInt32 aNamespaceID,
nsAttrValue& aResult)
{
if (aNamespaceID == kNameSpaceID_None &&
(mNodeInfo->Equals(nsGkAtoms::dir) ||
mNodeInfo->Equals(nsGkAtoms::menu))) {
mNodeInfo->Equals(nsGkAtoms::dir)) {
if (aAttribute == nsGkAtoms::type) {
return aResult.ParseEnumValue(aValue, kListTypeTable, PR_FALSE);
}
@@ -290,7 +276,7 @@ nsHTMLSharedElement::ParseAttribute(PRInt32 aNamespaceID,
}
static void
DirectoryMenuMapAttributesIntoRule(const nsMappedAttributes* aAttributes,
DirectoryMapAttributesIntoRule(const nsMappedAttributes* aAttributes,
nsRuleData* aData)
{
if (aData->mSIDs & NS_STYLE_INHERIT_BIT(List)) {
@@ -486,8 +472,8 @@ nsHTMLSharedElement::UnbindFromTree(PRBool aDeep, PRBool aNullParent)
nsMapRuleToAttributesFunc
nsHTMLSharedElement::GetAttributeMappingFunction() const
{
if (mNodeInfo->Equals(nsGkAtoms::dir) || mNodeInfo->Equals(nsGkAtoms::menu)) {
return &DirectoryMenuMapAttributesIntoRule;
if (mNodeInfo->Equals(nsGkAtoms::dir)) {
return &DirectoryMapAttributesIntoRule;
}
return nsGenericHTMLElement::GetAttributeMappingFunction();

View File

@@ -146,7 +146,6 @@ _TEST_FILES = \
test_formSubmission2.html \
file_formSubmission_text.txt \
file_formSubmission_img.jpg \
test_bug418756.html \
test_bug421640.html \
test_bug424698.html \
test_bug428135.xhtml \
@@ -280,6 +279,8 @@ _TEST_FILES = \
test_bug674558.html \
test_bug583533.html \
test_restore_from_parser_fragment.html \
test_bug617528.html \
test_checked.html \
$(NULL)
libs:: $(_TEST_FILES)

View File

@@ -0,0 +1,74 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=617528
-->
<head>
<title>Test for Bug 617528</title>
<script type="application/javascript" src="/MochiKit/packed.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=617528">Mozilla Bug 617528</a>
<p id="display"></p>
<div id="content">
<menu>
<menuitem id="checkbox" type="checkbox" label="Checkbox" checked></menuitem>
<menuitem id="radio1" type="radio" label="Radio1" checked></menuitem>
<menuitem id="radio2" type="radio" label="Radio2"></menuitem>
</menu>
</div>
<pre id="test">
<script type="application/javascript">
/** Test for Bug 617528 **/
SimpleTest.waitForExplicitFinish();
addLoadEvent(function() {
function click(element, preventDefault, checked) {
function handleClick(event) {
is(this.checked, checked,
"checking .checked (" + this.id + ")");
if (preventDefault)
event.preventDefault();
}
element.addEventListener("click", handleClick);
element.click();
element.removeEventListener("click", handleClick);
}
function verify(elements, data) {
for (var i = 0; i < elements.length; i++) {
var element = elements[i];
is(element.checked, data[i*2],
"checking .checked (" + element.id + ")");
is(element.defaultChecked, data[i*2+1],
'checking .defaultChecked (' + element.id + ")");
}
}
var checkbox = document.getElementById("checkbox");
click(checkbox, false, false);
verify([checkbox], [false, true]);
click(checkbox, true, true);
verify([checkbox], [false, true]);
var radio1 = document.getElementById("radio1");
var radio2 = document.getElementById("radio2");
click(radio2, false, true);
verify([radio1, radio2], [false, true,
true, false]);
click(radio1, true, true);
verify([radio1, radio2], [false, true,
true, false]);
SimpleTest.finish();
});
</script>
</pre>
</body>
</html>

View File

@@ -2,26 +2,41 @@
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=418756
https://bugzilla.mozilla.org/show_bug.cgi?id=617528
-->
<head>
<title>Test for Bug 418756</title>
<title>Test for Bug 418756 and 617528</title>
<script type="text/javascript" src="/MochiKit/packed.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=418756">Mozilla Bug 418756</a>
Mozilla bug
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=418756">418756</a>
and
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=617528">617528</a>
<p id="display"></p>
<div id="content">
<form id="f1">
</form>
<form id="f2">
</form>
<menu id="m1">
</menu>
<menu id="m2">
</menu>
</div>
<pre id="test">
<script class="testbody" type="text/javascript; version=1.7">
/** Test for Bug 418756 **/
/** Test for Bug 418756 and 617528 **/
let group1;
let group2;
let group3;
let tags = ["input", "menuitem"];
for each (let tag in tags) {
function bounce(node) {
let n = node.nextSibling;
let p = node.parentNode;
@@ -50,13 +65,13 @@ let id = 0;
// type can be 'c' for 'checkbox' and 'r' for 'radio'
function createNode(type, name, checked) {
let node = document.createElement("input");
let node = document.createElement(tag);
node.setAttribute("type", typeMapper[type]);
if (checked) {
node.setAttribute("checked", "checked");
}
node.setAttribute("id", type + (++id));
node.setAttribute("name", name);
node.setAttribute(tag == "input" ? "name" : "radiogroup", name);
createdNodes.push(node);
return node;
}
@@ -97,7 +112,7 @@ cleanup();
// effect
for each (let type in types) {
let n = createNode(type, 'test1', true);
$("f1").appendChild(n);
$(tag == "input" ? "f1" : "m1").appendChild(n);
n.checked = false;
n.defaultChecked = false;
bounce(n);
@@ -109,23 +124,23 @@ cleanup();
// Now check that playing with a single radio in a group affects all
// other radios in the group (but not radios not in that group)
let group1 = [ createNode('r', 'g1', false),
group1 = [ createNode('r', 'g1', false),
createNode('r', 'g1', false),
createNode('r', 'g1', false) ];
let group2 = [ createNode('r', 'g2', false),
group2 = [ createNode('r', 'g2', false),
createNode('r', 'g2', false),
createNode('r', 'g2', false) ];
let group3 = [ createNode('r', 'g1', false),
group3 = [ createNode('r', 'g1', false),
createNode('r', 'g1', false),
createNode('r', 'g1', false) ];
for each (let g in group1) {
$("f1").appendChild(g);
$(tag == "input" ? "f1" : "m1").appendChild(g);
}
for each (let g in group2) {
$("f1").appendChild(g);
$(tag == "input" ? "f1" : "m1").appendChild(g);
}
for each (let g in group3) {
$("f2").appendChild(g);
$(tag == "input" ? "f2" : "m2").appendChild(g);
}
for each (let n in [1, 2, 3]) {
@@ -335,6 +350,8 @@ for each (let n in [1, 2, 3]) {
}
cleanup();
}
</script>
</pre>
</body>

View File

@@ -1021,7 +1021,10 @@ SinkContext::AddLeaf(const nsIParserNode& aNode)
case eHTMLTag_input:
content->DoneCreatingElement();
break;
case eHTMLTag_menuitem:
content->DoneCreatingElement();
break;
default:

View File

@@ -1070,7 +1070,8 @@ nsXMLContentSink::HandleStartElement(const PRUnichar *aName,
// properly (eg form state restoration).
if (nodeInfo->NamespaceID() == kNameSpaceID_XHTML) {
if (nodeInfo->NameAtom() == nsGkAtoms::input ||
nodeInfo->NameAtom() == nsGkAtoms::button) {
nodeInfo->NameAtom() == nsGkAtoms::button ||
nodeInfo->NameAtom() == nsGkAtoms::menuitem) {
content->DoneCreatingElement();
} else if (nodeInfo->NameAtom() == nsGkAtoms::head && !mCurrentHead) {
mCurrentHead = content;

View File

@@ -343,7 +343,8 @@ txMozillaXMLOutput::endElement()
}
} else if (ns == kNameSpaceID_XHTML &&
(localName == nsGkAtoms::input ||
localName == nsGkAtoms::button)) {
localName == nsGkAtoms::button ||
localName == nsGkAtoms::menuitem)) {
element->DoneCreatingElement();
}
}

View File

@@ -43,7 +43,7 @@ VPATH = @srcdir@
include $(DEPTH)/config/autoconf.mk
MODULE = xul
PARALLEL_DIRS = src
PARALLEL_DIRS = public src
ifdef ENABLE_TESTS
PARALLEL_DIRS += test

View File

@@ -0,0 +1,51 @@
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is Mozilla code.
#
# The Initial Developer of the Original Code is Mozilla Foundation
# Portions created by the Initial Developer are Copyright (C) 2011
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
#
# Alternatively, the contents of this file may be used under the terms of
# either of the GNU General Public License Version 2 or later (the "GPL"),
# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
DEPTH = ../../../..
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@
include $(DEPTH)/config/autoconf.mk
MODULE = xul
ifdef MOZ_XUL
XPIDLSRCS = \
nsIXULContextMenuBuilder.idl \
$(NULL)
endif
include $(topsrcdir)/config/rules.mk

View File

@@ -0,0 +1,73 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla.
*
* The Initial Developer of the Original Code is Mozilla Foundation
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#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(f0c35053-14cc-4e23-a9db-f9a68fae8375)]
interface nsIXULContextMenuBuilder : nsISupports
{
/**
* Initialize builder before building.
*
* @param aDocumentFragment the fragment that will be used to append top
* level elements
*
* @param aGeneratedAttrName the name of the attribute that will be used
* to mark elements as generated.
*
* @param aIdentAttrName the name of the attribute that will be used for
* menuitem identification.
*/
void init(in nsIDOMDocumentFragment aDocumentFragment,
in AString aGeneratedAttrName,
in AString aIdentAttrName);
/**
* Invoke the action of the menuitem with assigned identifier aIdent.
*
* @param aIdent the menuitem identifier
*/
void click(in DOMString aIdent);
};

View File

@@ -54,6 +54,7 @@ ifdef MOZ_XUL
CPPSRCS += \
nsXULElement.cpp \
nsXULPopupListener.cpp \
nsXULContextMenuBuilder.cpp \
$(NULL)
endif

View File

@@ -0,0 +1,268 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla.
*
* The Initial Developer of the Original Code is Mozilla Foundation
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsContentCreatorFunctions.h"
#include "nsIDOMHTMLElement.h"
#include "nsIDOMHTMLMenuItemElement.h"
#include "nsXULContextMenuBuilder.h"
nsXULContextMenuBuilder::nsXULContextMenuBuilder()
: mCurrentIdent(0)
{
}
nsXULContextMenuBuilder::~nsXULContextMenuBuilder()
{
}
NS_IMPL_CYCLE_COLLECTION_CLASS(nsXULContextMenuBuilder)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXULContextMenuBuilder)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mFragment)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mDocument)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mCurrentNode)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mElements)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXULContextMenuBuilder)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mFragment)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mDocument)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mCurrentNode)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mElements)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
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<nsIContent> menu;
nsresult rv = CreateElement(nsGkAtoms::menu, getter_AddRefs(menu));
NS_ENSURE_SUCCESS(rv, rv);
menu->SetAttr(kNameSpaceID_None, nsGkAtoms::label, aLabel, PR_FALSE);
nsCOMPtr<nsIContent> menuPopup;
rv = CreateElement(nsGkAtoms::menupopup, getter_AddRefs(menuPopup));
NS_ENSURE_SUCCESS(rv, rv);
rv = menu->AppendChildTo(menuPopup, PR_FALSE);
NS_ENSURE_SUCCESS(rv, rv);
rv = mCurrentNode->AppendChildTo(menu, PR_FALSE);
NS_ENSURE_SUCCESS(rv, rv);
mCurrentNode = menuPopup;
}
return NS_OK;
}
NS_IMETHODIMP
nsXULContextMenuBuilder::AddItemFor(nsIDOMHTMLMenuItemElement* aElement,
PRBool aCanLoadIcon)
{
if (!mFragment) {
return NS_ERROR_NOT_INITIALIZED;
}
nsCOMPtr<nsIContent> menuitem;
nsresult rv = CreateElement(nsGkAtoms::menuitem, 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"), PR_FALSE);
PRBool checked;
aElement->GetChecked(&checked);
if (checked) {
menuitem->SetAttr(kNameSpaceID_None, nsGkAtoms::checked,
NS_LITERAL_STRING("true"), PR_FALSE);
}
}
nsAutoString label;
aElement->GetLabel(label);
menuitem->SetAttr(kNameSpaceID_None, nsGkAtoms::label, label, PR_FALSE);
nsAutoString icon;
aElement->GetIcon(icon);
if (!icon.IsEmpty()) {
menuitem->SetAttr(kNameSpaceID_None, nsGkAtoms::_class,
NS_LITERAL_STRING("menuitem-iconic"), PR_FALSE);
if (aCanLoadIcon) {
menuitem->SetAttr(kNameSpaceID_None, nsGkAtoms::image, icon, PR_FALSE);
}
}
PRBool disabled;
aElement->GetDisabled(&disabled);
if (disabled) {
menuitem->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled,
NS_LITERAL_STRING("true"), PR_FALSE);
}
nsAutoString ident;
ident.AppendInt(mCurrentIdent++);
menuitem->SetAttr(kNameSpaceID_None, mIdentAttr, ident, PR_FALSE);
rv = mCurrentNode->AppendChildTo(menuitem, PR_FALSE);
NS_ENSURE_SUCCESS(rv, rv);
mElements.AppendObject(aElement);
return NS_OK;
}
NS_IMETHODIMP
nsXULContextMenuBuilder::AddSeparator()
{
if (!mFragment) {
return NS_ERROR_NOT_INITIALIZED;
}
nsCOMPtr<nsIContent> menuseparator;
nsresult rv = CreateElement(nsGkAtoms::menuseparator,
getter_AddRefs(menuseparator));
NS_ENSURE_SUCCESS(rv, rv);
return mCurrentNode->AppendChildTo(menuseparator, PR_FALSE);
}
NS_IMETHODIMP
nsXULContextMenuBuilder::UndoAddSeparator()
{
if (!mFragment) {
return NS_ERROR_NOT_INITIALIZED;
}
PRUint32 count = mCurrentNode->GetChildCount();
if (!count ||
mCurrentNode->GetChildAt(count - 1)->Tag() != nsGkAtoms::menuseparator) {
return NS_OK;
}
return mCurrentNode->RemoveChildAt(count - 1, PR_FALSE);
}
NS_IMETHODIMP
nsXULContextMenuBuilder::CloseContainer()
{
if (!mFragment) {
return NS_ERROR_NOT_INITIALIZED;
}
if (mCurrentNode == mFragment) {
mCurrentNode = nsnull;
} else {
nsIContent* parent = mCurrentNode->GetParent();
mCurrentNode = parent->GetParent();
}
return NS_OK;
}
NS_IMETHODIMP
nsXULContextMenuBuilder::Init(nsIDOMDocumentFragment* aDocumentFragment,
const nsAString& aGeneratedAttrName,
const nsAString& aIdentAttrName)
{
NS_ENSURE_ARG_POINTER(aDocumentFragment);
mFragment = do_QueryInterface(aDocumentFragment);
mDocument = mFragment->GetOwnerDocument();
mGeneratedAttr = do_GetAtom(aGeneratedAttrName);
mIdentAttr = do_GetAtom(aIdentAttrName);
return NS_OK;
}
NS_IMETHODIMP
nsXULContextMenuBuilder::Click(const nsAString& aIdent)
{
PRInt32 rv;
PRInt32 idx = nsString(aIdent).ToInteger(&rv);
if (NS_SUCCEEDED(rv)) {
nsCOMPtr<nsIDOMHTMLElement> element = mElements.SafeObjectAt(idx);
if (element) {
element->Click();
}
}
return NS_OK;
}
nsresult
nsXULContextMenuBuilder::CreateElement(nsIAtom* aTag, nsIContent** aResult)
{
*aResult = nsnull;
nsCOMPtr<nsINodeInfo> nodeInfo = mDocument->NodeInfoManager()->GetNodeInfo(
aTag, nsnull, kNameSpaceID_XUL, nsIDOMNode::ELEMENT_NODE);
NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
nsresult rv = NS_NewElement(aResult, kNameSpaceID_XUL, nodeInfo.forget(),
mozilla::dom::NOT_FROM_PARSER);
if (NS_FAILED(rv)) {
return rv;
}
(*aResult)->SetAttr(kNameSpaceID_None, mGeneratedAttr, EmptyString(),
PR_FALSE);
return NS_OK;
}

View File

@@ -0,0 +1,71 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla.
*
* The Initial Developer of the Original Code is Mozilla Foundation
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsCOMPtr.h"
#include "nsCOMArray.h"
#include "nsIContent.h"
#include "nsIMenuBuilder.h"
#include "nsIXULContextMenuBuilder.h"
#include "nsIDOMDocumentFragment.h"
#include "nsCycleCollectionParticipant.h"
class nsXULContextMenuBuilder : public nsIMenuBuilder,
public nsIXULContextMenuBuilder
{
public:
nsXULContextMenuBuilder();
virtual ~nsXULContextMenuBuilder();
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsXULContextMenuBuilder,
nsIMenuBuilder)
NS_DECL_NSIMENUBUILDER
NS_DECL_NSIXULCONTEXTMENUBUILDER
protected:
nsresult CreateElement(nsIAtom* aTag, nsIContent** aResult);
nsCOMPtr<nsIContent> mFragment;
nsCOMPtr<nsIDocument> mDocument;
nsCOMPtr<nsIAtom> mGeneratedAttr;
nsCOMPtr<nsIAtom> mIdentAttr;
nsCOMPtr<nsIContent> mCurrentNode;
PRInt32 mCurrentIdent;
nsCOMArray<nsIDOMHTMLElement> mElements;
};

View File

@@ -285,6 +285,7 @@
#include "nsIDOMHTMLLinkElement.h"
#include "nsIDOMHTMLMapElement.h"
#include "nsIDOMHTMLMenuElement.h"
#include "nsIDOMHTMLMenuItemElement.h"
#include "nsIDOMHTMLMetaElement.h"
#include "nsIDOMHTMLModElement.h"
#include "nsIDOMHTMLOListElement.h"
@@ -836,6 +837,8 @@ static nsDOMClassInfoData sClassInfoData[] = {
ELEMENT_SCRIPTABLE_FLAGS)
NS_DEFINE_CLASSINFO_DATA(HTMLMenuElement, nsElementSH,
ELEMENT_SCRIPTABLE_FLAGS)
NS_DEFINE_CLASSINFO_DATA(HTMLMenuItemElement, nsElementSH,
ELEMENT_SCRIPTABLE_FLAGS)
NS_DEFINE_CLASSINFO_DATA(HTMLMetaElement, nsElementSH,
ELEMENT_SCRIPTABLE_FLAGS)
NS_DEFINE_CLASSINFO_DATA(HTMLModElement, nsElementSH,
@@ -2695,6 +2698,11 @@ nsDOMClassInfo::Init()
DOM_CLASSINFO_GENERIC_HTML_MAP_ENTRIES
DOM_CLASSINFO_MAP_END
DOM_CLASSINFO_MAP_BEGIN(HTMLMenuItemElement, nsIDOMHTMLMenuItemElement)
DOM_CLASSINFO_MAP_ENTRY(nsIDOMHTMLMenuItemElement)
DOM_CLASSINFO_GENERIC_HTML_MAP_ENTRIES
DOM_CLASSINFO_MAP_END
DOM_CLASSINFO_MAP_BEGIN(HTMLMetaElement, nsIDOMHTMLMetaElement)
DOM_CLASSINFO_MAP_ENTRY(nsIDOMHTMLMetaElement)
DOM_CLASSINFO_GENERIC_HTML_MAP_ENTRIES
@@ -7493,6 +7501,7 @@ nsEventReceiverSH::ReallyIsEventName(jsid id, jschar aFirstChar)
id == sOnratechange_id);
case 's' :
return (id == sOnscroll_id ||
id == sOnshow_id ||
id == sOnselect_id ||
id == sOnsubmit_id ||
id == sOnseeked_id ||

View File

@@ -120,6 +120,7 @@ DOMCI_CLASS(HTMLLegendElement)
DOMCI_CLASS(HTMLLinkElement)
DOMCI_CLASS(HTMLMapElement)
DOMCI_CLASS(HTMLMenuElement)
DOMCI_CLASS(HTMLMenuItemElement)
DOMCI_CLASS(HTMLMetaElement)
DOMCI_CLASS(HTMLModElement)
DOMCI_CLASS(HTMLOListElement)

View File

@@ -55,6 +55,7 @@ SDK_XPIDLSRCS = \
nsIDOMHTMLBodyElement.idl \
nsIDOMHTMLButtonElement.idl \
nsIDOMHTMLCollection.idl \
nsIDOMHTMLCommandElement.idl \
nsIDOMHTMLDataListElement.idl \
nsIDOMHTMLDListElement.idl \
nsIDOMHTMLDirectoryElement.idl \
@@ -80,6 +81,7 @@ SDK_XPIDLSRCS = \
nsIDOMHTMLLinkElement.idl \
nsIDOMHTMLMapElement.idl \
nsIDOMHTMLMenuElement.idl \
nsIDOMHTMLMenuItemElement.idl \
nsIDOMHTMLMetaElement.idl \
nsIDOMHTMLModElement.idl \
nsIDOMHTMLOListElement.idl \

View File

@@ -0,0 +1,59 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla.
*
* The Initial Developer of the Original Code is Mozilla Foundation
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsIDOMHTMLElement.idl"
/**
* The nsIDOMHTMLCommandElement interface is the interface to a HTML
* <command> element.
*
* For more information on this interface, please see
* http://www.whatwg.org/specs/web-apps/current-work/#the-command-element
*
* @status UNDER_DEVELOPMENT
*/
[scriptable, uuid(df4a19b4-81f1-412e-a971-fcbe7312a9b6)]
interface nsIDOMHTMLCommandElement : nsIDOMHTMLElement
{
attribute DOMString type;
attribute DOMString label;
attribute DOMString icon;
attribute boolean disabled;
attribute boolean defaultChecked;
attribute boolean checked;
attribute DOMString radiogroup;
};

View File

@@ -50,8 +50,11 @@
* http://www.whatwg.org/specs/web-apps/current-work/
*/
[scriptable, uuid(318d9314-f97b-4b7e-96ff-95f0cb203fdf)]
[scriptable, uuid(43aa6818-f67f-420c-a400-59a2668e9fe5)]
interface nsIDOMHTMLMenuElement : nsIDOMHTMLElement
{
attribute boolean compact;
attribute DOMString type;
attribute DOMString label;
};

View File

@@ -0,0 +1,49 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla.
*
* The Initial Developer of the Original Code is Mozilla Foundation
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsIDOMHTMLCommandElement.idl"
/**
* The nsIDOMHTMLMenuItemElement interface is the interface to a HTML
* <menuitem> element.
*
* @status UNDER_DEVELOPMENT
*/
[scriptable, uuid(613f28ee-01f5-42dc-8224-161f85f0f20b)]
interface nsIDOMHTMLMenuItemElement : nsIDOMHTMLCommandElement
{
};

View File

@@ -39,8 +39,9 @@
#include "domstubs.idl"
interface nsIDOMDOMStringMap;
interface nsIDOMHTMLMenuElement;
[scriptable, uuid(4012e9a9-f6fb-48b3-9a80-b096c1dcb5ba)]
[scriptable, uuid(0c3b4b63-30b2-4c93-906d-f983ee9af584)]
interface nsIDOMNSHTMLElement : nsISupports
{
readonly attribute long offsetTop;
@@ -71,6 +72,7 @@ interface nsIDOMNSHTMLElement : nsISupports
[optional_argc] void scrollIntoView([optional] in boolean top);
readonly attribute nsIDOMHTMLMenuElement contextMenu;
attribute boolean spellcheck;
readonly attribute nsIDOMDOMStringMap dataset;

View File

@@ -147,6 +147,7 @@ EDITOR_ATOM(legend, "legend")
EDITOR_ATOM(li, "li")
EDITOR_ATOM(map, "map")
EDITOR_ATOM(mark, "mark")
EDITOR_ATOM(menuitem, "menuitem")
EDITOR_ATOM(mozdirty, "_moz_dirty")
EDITOR_ATOM(mozEditorBogusNode, "_moz_editor_bogus_node")
EDITOR_ATOM(name, "name")

View File

@@ -662,7 +662,8 @@ static const nsElementInfo kElements[eHTMLTag_userdefined] = {
ELEM(map, PR_TRUE, PR_TRUE, GROUP_SPECIAL, GROUP_BLOCK | GROUP_MAP_CONTENT),
ELEM(mark, PR_TRUE, PR_TRUE, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
ELEM(marquee, PR_FALSE, PR_FALSE, GROUP_NONE, GROUP_NONE),
ELEM(menu, PR_TRUE, PR_FALSE, GROUP_BLOCK, GROUP_LI),
ELEM(menu, PR_TRUE, PR_TRUE, GROUP_BLOCK, GROUP_LI | GROUP_FLOW_ELEMENT),
ELEM(menuitem, PR_FALSE, PR_FALSE, GROUP_NONE, GROUP_NONE),
ELEM(meta, PR_FALSE, PR_FALSE, GROUP_HEAD_CONTENT, GROUP_NONE),
ELEM(multicol, PR_FALSE, PR_FALSE, GROUP_NONE, GROUP_NONE),
ELEM(nav, PR_TRUE, PR_TRUE, GROUP_BLOCK, GROUP_FLOW_ELEMENT),

View File

@@ -213,6 +213,7 @@ members = [
# but it is also present in other objects where it isn't shadowed.
# Quick stubs handle the shadowing the same as XPConnect.
'nsIDOMHTMLCollection.length',
'nsIDOMHTMLCommandElement.*',
'nsIDOMHTMLDocument.body',
'nsIDOMHTMLDocument.getElementsByName',
'nsIDOMHTMLDocument.anchors',
@@ -261,6 +262,8 @@ members = [
'nsIDOMHTMLInputElement.selectionDirection',
'nsIDOMHTMLInputElement.setSelectionRange',
'nsIDOMHTMLLinkElement.disabled',
'nsIDOMHTMLMenuElement.*',
'nsIDOMHTMLMenuItemElement.*',
'nsIDOMHTMLOptionElement.index',
'nsIDOMHTMLOptionElement.selected',
'nsIDOMHTMLOptionElement.form',

View File

@@ -573,6 +573,10 @@ ul, menu, dir {
-moz-padding-start: 40px;
}
menu[type="context"] {
display: none !important;
}
ol {
display: block;
list-style-type: decimal;

View File

@@ -269,6 +269,7 @@
@BINPATH@/components/xpcom_xpti.xpt
@BINPATH@/components/xpconnect.xpt
@BINPATH@/components/xulapp.xpt
@BINPATH@/components/xul.xpt
@BINPATH@/components/xuldoc.xpt
@BINPATH@/components/xultmpl.xpt
@BINPATH@/components/zipwriter.xpt

View File

@@ -544,7 +544,8 @@ nsHtml5TreeBuilder::elementPopped(PRInt32 aNamespace, nsIAtom* aName, nsIContent
return;
}
if (aName == nsHtml5Atoms::input ||
aName == nsHtml5Atoms::button) {
aName == nsHtml5Atoms::button ||
aName == nsHtml5Atoms::menuitem) {
if (!formPointer) {
// If form inputs don't belong to a form, their state preservation
// won't work right without an append notification flush at this

View File

@@ -139,7 +139,8 @@ HTML_HTMLELEMENT_TAG(listing)
HTML_TAG(map, Map)
HTML_HTMLELEMENT_TAG(mark)
HTML_TAG(marquee, Div)
HTML_TAG(menu, Shared)
HTML_TAG(menu, Menu)
HTML_TAG(menuitem, MenuItem)
HTML_TAG(meta, Meta)
HTML_TAG(multicol, Span)
HTML_HTMLELEMENT_TAG(nav)

View File

@@ -856,6 +856,15 @@ const nsHTMLElement gHTMLElements[] = {
/*special props, prop-range*/ 0,kDefaultPropRange,
/*special parents,kids*/ 0,&gULKids,
},
{
/*tag*/ eHTMLTag_menuitem,
/*req-parent excl-parent*/ eHTMLTag_unknown,eHTMLTag_unknown,
/*rootnodes,endrootnodes*/ &gRootTags,&gRootTags,
/*autoclose starttags and endtags*/ 0,0,0,0,
/*parent,incl,exclgroups*/ kFlowEntity, kNone, kNone,
/*special props, prop-range*/ kNonContainer,kDefaultPropRange,
/*special parents,kids*/ 0,0,
},
{
/*tag*/ eHTMLTag_meta,
/*req-parent excl-parent*/ eHTMLTag_unknown,eHTMLTag_unknown,

View File

@@ -195,6 +195,8 @@ static const PRUnichar sHTMLTagUnicodeName_marquee[] =
{'m', 'a', 'r', 'q', 'u', 'e', 'e', '\0'};
static const PRUnichar sHTMLTagUnicodeName_menu[] =
{'m', 'e', 'n', 'u', '\0'};
static const PRUnichar sHTMLTagUnicodeName_menuitem[] =
{'m', 'e', 'n', 'u', 'i', 't', 'e', 'm', '\0'};
static const PRUnichar sHTMLTagUnicodeName_meta[] =
{'m', 'e', 't', 'a', '\0'};
static const PRUnichar sHTMLTagUnicodeName_multicol[] =

View File

@@ -93,6 +93,7 @@ EXTRA_PP_JS_MODULES = \
LightweightThemeConsumer.jsm \
Services.jsm \
WindowDraggingUtils.jsm \
PageMenu.jsm \
$(NULL)
include $(topsrcdir)/config/rules.mk

View File

@@ -0,0 +1,171 @@
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is Mozilla code.
#
# The Initial Developer of the Original Code is Mozilla Foundation
# Portions created by the Initial Developer are Copyright (C) 2011
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the LGPL or the GPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK ***** -->
let EXPORTED_SYMBOLS = ["PageMenu"];
function PageMenu() {
}
PageMenu.prototype = {
PAGEMENU_ATTR: "pagemenu",
GENERATED_ATTR: "generated",
IDENT_ATTR: "ident",
popup: null,
builder: null,
init: function(aTarget, aPopup) {
var pageMenu = null;
var target = aTarget;
while (target) {
var contextMenu = target.contextMenu;
if (contextMenu) {
pageMenu = contextMenu;
break;
}
target = target.parentNode;
}
if (!pageMenu) {
return false;
}
var insertionPoint = this.getInsertionPoint(aPopup);
if (!insertionPoint) {
return false;
}
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();
var builder = pageMenu.createBuilder();
if (!builder) {
return false;
}
builder.QueryInterface(Components.interfaces.nsIXULContextMenuBuilder);
builder.init(fragment, this.GENERATED_ATTR, this.IDENT_ATTR);
pageMenu.build(builder);
var pos = insertionPoint.getAttribute(this.PAGEMENU_ATTR);
if (pos == "end") {
insertionPoint.appendChild(fragment);
} else {
insertionPoint.insertBefore(fragment,
insertionPoint.firstChild);
}
this.builder = builder;
this.popup = aPopup;
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.GENERATED_ATTR)) {
this.builder.click(target.getAttribute(this.IDENT_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;
}
},
getImmediateChild: function(element, tag) {
var child = element.firstChild;
while (child) {
if (child.localName == tag) {
return child;
}
child = child.nextSibling;
}
return null;
},
getInsertionPoint: function(aPopup) {
if (aPopup.hasAttribute(this.PAGEMENU_ATTR))
return aPopup;
var element = aPopup.firstChild;
while (element) {
if (element.localName == "menu") {
var popup = this.getImmediateChild(element, "menupopup");
if (popup) {
var result = this.getInsertionPoint(popup);
if (result) {
return result;
}
}
}
element = element.nextSibling;
}
return null;
},
removeGeneratedContent: function(aPopup) {
var ungenerated = [];
ungenerated.push(aPopup);
var count;
while (0 != (count = ungenerated.length)) {
var last = count - 1;
var element = ungenerated[last];
ungenerated.splice(last, 1);
var i = element.childNodes.length;
while (i-- > 0) {
var child = element.childNodes[i];
if (!child.hasAttribute(this.GENERATED_ATTR)) {
ungenerated.push(child);
continue;
}
element.removeChild(child);
}
}
}
}

View File

@@ -536,6 +536,8 @@ class nsHashKey;
#define NS_DEVICE_ORIENTATION (NS_DEVICE_ORIENTATION_START)
#define NS_DEVICE_MOTION (NS_DEVICE_ORIENTATION_START+1)
#define NS_SHOW_EVENT 5000
/**
* Return status for event processors, nsEventStatus, is defined in
* nsEvent.h.