diff --git a/browser/base/content/browser-context.inc b/browser/base/content/browser-context.inc index 6bfe0e02ffba..c69d7bd0f0fd 100644 --- a/browser/base/content/browser-context.inc +++ b/browser/base/content/browser-context.inc @@ -36,6 +36,7 @@ # # ***** END LICENSE BLOCK ***** + diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index 3478c212edcb..81be94c47644 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -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. diff --git a/browser/base/content/browser.xul b/browser/base/content/browser.xul index 5355ed502ff0..1afc00a121f8 100644 --- a/browser/base/content/browser.xul +++ b/browser/base/content/browser.xul @@ -273,10 +273,10 @@ oncommand="BrowserFullScreen();"/> - chssseesbbbie
chssseefsbbbie
+
+

I've got a context menu!

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/browser/base/content/test/test_contextmenu.html b/browser/base/content/test/test_contextmenu.html index 0d842ea6e448..87917954d4c5 100644 --- a/browser/base/content/test/test_contextmenu.html +++ b/browser/base/content/test/test_contextmenu.html @@ -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); } - items.push(!item.disabled); + 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"); - 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; + 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"); } @@ -408,9 +466,70 @@ function runTest(testNum) { openContextMenuFor(link); // Invoke context menu for next test. break; - case 15: + 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); diff --git a/browser/base/content/web-panels.xul b/browser/base/content/web-panels.xul index 93630bf84769..f5515240a28d 100644 --- a/browser/base/content/web-panels.xul +++ b/browser/base/content/web-panels.xul @@ -80,10 +80,10 @@ - . + */ + void build(in nsIMenuBuilder aBuilder); + +}; diff --git a/content/html/content/public/nsIMenuBuilder.idl b/content/html/content/public/nsIMenuBuilder.idl new file mode 100644 index 000000000000..09bae6db1d57 --- /dev/null +++ b/content/html/content/public/nsIMenuBuilder.idl @@ -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 + */ + +[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(); + +}; diff --git a/content/html/content/src/Makefile.in b/content/html/content/src/Makefile.in index 3ab42a0b52ce..71f54bf4e1b4 100644 --- a/content/html/content/src/Makefile.in +++ b/content/html/content/src/Makefile.in @@ -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 \ diff --git a/content/html/content/src/nsGenericHTMLElement.cpp b/content/html/content/src/nsGenericHTMLElement.cpp index b26e36e26934..9d529f350a0b 100644 --- a/content/html/content/src/nsGenericHTMLElement.cpp +++ b/content/html/content/src/nsGenericHTMLElement.cpp @@ -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 element = + nsHTMLMenuElement::FromContent(doc->GetElementById(value)); + element.forget(aContextMenu); + } + + return NS_OK; +} + //---------------------------------------------------------------------- NS_IMPL_INT_ATTR(nsGenericHTMLFrameElement, TabIndex, tabindex) diff --git a/content/html/content/src/nsGenericHTMLElement.h b/content/html/content/src/nsGenericHTMLElement.h index af428736e755..daaca7356dd5 100644 --- a/content/html/content/src/nsGenericHTMLElement.h +++ b/content/html/content/src/nsGenericHTMLElement.h @@ -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 @@ -1471,22 +1478,22 @@ protected: NS_INTERFACE_TABLE_ENTRY(_class, _i10) \ NS_OFFSET_AND_INTERFACE_TABLE_END -/* Use this macro to declare functions that forward the behavior of this interface to another object. - This macro doesn't forward Focus or Click because sometimes elements will want to override them. */ -#define NS_FORWARD_NSIDOMHTMLELEMENT_NOFOCUSCLICK(_to) \ - NS_SCRIPTABLE NS_IMETHOD GetId(nsAString & aId) { return _to GetId(aId); } \ - NS_SCRIPTABLE NS_IMETHOD SetId(const nsAString & aId) { return _to SetId(aId); } \ - NS_SCRIPTABLE NS_IMETHOD GetTitle(nsAString & aTitle) { return _to GetTitle(aTitle); } \ - NS_SCRIPTABLE NS_IMETHOD SetTitle(const nsAString & aTitle) { return _to SetTitle(aTitle); } \ - NS_SCRIPTABLE NS_IMETHOD GetLang(nsAString & aLang) { return _to GetLang(aLang); } \ - NS_SCRIPTABLE NS_IMETHOD SetLang(const nsAString & aLang) { return _to SetLang(aLang); } \ - NS_SCRIPTABLE NS_IMETHOD GetDir(nsAString & aDir) { return _to GetDir(aDir); } \ - NS_SCRIPTABLE NS_IMETHOD SetDir(const nsAString & aDir) { return _to SetDir(aDir); } \ - NS_SCRIPTABLE NS_IMETHOD GetClassName(nsAString & aClassName) { return _to GetClassName(aClassName); } \ - NS_SCRIPTABLE NS_IMETHOD SetClassName(const nsAString & aClassName) { return _to SetClassName(aClassName); } \ - NS_SCRIPTABLE NS_IMETHOD GetAccessKey(nsAString & aAccessKey) { return _to GetAccessKey(aAccessKey); } \ - NS_SCRIPTABLE NS_IMETHOD SetAccessKey(const nsAString & aAccessKey) { return _to SetAccessKey(aAccessKey); } \ - NS_SCRIPTABLE NS_IMETHOD GetAccessKeyLabel(nsAString & aLabel) { return _to GetAccessKeyLabel(aLabel); } \ +/* Use this macro to declare functions that forward the behavior of this interface to another object. + This macro doesn't forward Focus or Click because sometimes elements will want to override them. */ +#define NS_FORWARD_NSIDOMHTMLELEMENT_NOFOCUSCLICK(_to) \ + NS_SCRIPTABLE NS_IMETHOD GetId(nsAString & aId) { return _to GetId(aId); } \ + NS_SCRIPTABLE NS_IMETHOD SetId(const nsAString & aId) { return _to SetId(aId); } \ + NS_SCRIPTABLE NS_IMETHOD GetTitle(nsAString & aTitle) { return _to GetTitle(aTitle); } \ + NS_SCRIPTABLE NS_IMETHOD SetTitle(const nsAString & aTitle) { return _to SetTitle(aTitle); } \ + NS_SCRIPTABLE NS_IMETHOD GetLang(nsAString & aLang) { return _to GetLang(aLang); } \ + NS_SCRIPTABLE NS_IMETHOD SetLang(const nsAString & aLang) { return _to SetLang(aLang); } \ + NS_SCRIPTABLE NS_IMETHOD GetDir(nsAString & aDir) { return _to GetDir(aDir); } \ + NS_SCRIPTABLE NS_IMETHOD SetDir(const nsAString & aDir) { return _to SetDir(aDir); } \ + NS_SCRIPTABLE NS_IMETHOD GetClassName(nsAString & aClassName) { return _to GetClassName(aClassName); } \ + NS_SCRIPTABLE NS_IMETHOD SetClassName(const nsAString & aClassName) { return _to SetClassName(aClassName); } \ + NS_SCRIPTABLE NS_IMETHOD GetAccessKey(nsAString & aAccessKey) { return _to GetAccessKey(aAccessKey); } \ + NS_SCRIPTABLE NS_IMETHOD SetAccessKey(const nsAString & aAccessKey) { return _to SetAccessKey(aAccessKey); } \ + NS_SCRIPTABLE NS_IMETHOD GetAccessKeyLabel(nsAString & aLabel) { return _to GetAccessKeyLabel(aLabel); } \ NS_SCRIPTABLE NS_IMETHOD Blur(void) { return _to Blur(); } /** @@ -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) diff --git a/content/html/content/src/nsHTMLMenuElement.cpp b/content/html/content/src/nsHTMLMenuElement.cpp new file mode 100644 index 000000000000..1921ac4dd06a --- /dev/null +++ b/content/html/content/src/nsHTMLMenuElement.cpp @@ -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 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 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 shell = document->GetShell(); + if (!shell) { + return NS_ERROR_FAILURE; + } + + nsRefPtr presContext = shell->GetPresContext(); + nsEventStatus status = nsEventStatus_eIgnore; + nsEventDispatcher::Dispatch(static_cast(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 baseURI = aContent->GetBaseURI(); + nsCOMPtr 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 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; +} diff --git a/content/html/content/src/nsHTMLMenuElement.h b/content/html/content/src/nsHTMLMenuElement.h new file mode 100644 index 000000000000..a44ef7dea865 --- /dev/null +++ b/content/html/content/src/nsHTMLMenuElement.h @@ -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 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(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; +}; diff --git a/content/html/content/src/nsHTMLMenuItemElement.cpp b/content/html/content/src/nsHTMLMenuItemElement.cpp new file mode 100644 index 000000000000..e456bb8b4c2c --- /dev/null +++ b/content/html/content/src/nsHTMLMenuItemElement.cpp @@ -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 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 ni = aNodeInfo; + nsHTMLMenuItemElement *it = new nsHTMLMenuItemElement(ni.forget(), + NOT_FROM_PARSER); + if (!it) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsCOMPtr 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 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 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); + } +} diff --git a/content/html/content/src/nsHTMLMenuItemElement.h b/content/html/content/src/nsHTMLMenuItemElement.h new file mode 100644 index 000000000000..f7e781dd37c4 --- /dev/null +++ b/content/html/content/src/nsHTMLMenuItemElement.h @@ -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 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(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; +}; diff --git a/content/html/content/src/nsHTMLSharedElement.cpp b/content/html/content/src/nsHTMLSharedElement.cpp index a8304e28eada..23ef1a797e75 100644 --- a/content/html/content/src/nsHTMLSharedElement.cpp +++ b/content/html/content/src/nsHTMLSharedElement.cpp @@ -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(); diff --git a/content/html/content/test/Makefile.in b/content/html/content/test/Makefile.in index 2ab4592a9ee1..0722b7f6398c 100644 --- a/content/html/content/test/Makefile.in +++ b/content/html/content/test/Makefile.in @@ -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) diff --git a/content/html/content/test/test_bug617528.html b/content/html/content/test/test_bug617528.html new file mode 100644 index 000000000000..1d0005cba63b --- /dev/null +++ b/content/html/content/test/test_bug617528.html @@ -0,0 +1,74 @@ + + + + + Test for Bug 617528 + + + + + +Mozilla Bug 617528 +

+
+ + + + + +
+
+
+
+ + diff --git a/content/html/content/test/test_bug418756.html b/content/html/content/test/test_checked.html similarity index 90% rename from content/html/content/test/test_bug418756.html rename to content/html/content/test/test_checked.html index e8fc0ff20200..d024ef0c077d 100644 --- a/content/html/content/test/test_bug418756.html +++ b/content/html/content/test/test_checked.html @@ -2,26 +2,41 @@ - Test for Bug 418756 + Test for Bug 418756 and 617528 -Mozilla Bug 418756 +Mozilla bug +418756 +and +617528

+ + + +
 
 
diff --git a/content/html/document/src/nsHTMLContentSink.cpp b/content/html/document/src/nsHTMLContentSink.cpp index d3786da46d67..9b074263c683 100644 --- a/content/html/document/src/nsHTMLContentSink.cpp +++ b/content/html/document/src/nsHTMLContentSink.cpp @@ -1021,7 +1021,10 @@ SinkContext::AddLeaf(const nsIParserNode& aNode) case eHTMLTag_input: content->DoneCreatingElement(); + break; + case eHTMLTag_menuitem: + content->DoneCreatingElement(); break; default: diff --git a/content/xml/document/src/nsXMLContentSink.cpp b/content/xml/document/src/nsXMLContentSink.cpp index 0c00c811ae8e..7ade5adfcc40 100644 --- a/content/xml/document/src/nsXMLContentSink.cpp +++ b/content/xml/document/src/nsXMLContentSink.cpp @@ -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; diff --git a/content/xslt/src/xslt/txMozillaXMLOutput.cpp b/content/xslt/src/xslt/txMozillaXMLOutput.cpp index 202edf48c8e7..35140ab8dc5a 100644 --- a/content/xslt/src/xslt/txMozillaXMLOutput.cpp +++ b/content/xslt/src/xslt/txMozillaXMLOutput.cpp @@ -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(); } } diff --git a/content/xul/content/Makefile.in b/content/xul/content/Makefile.in index 9f33894a6465..e5e3a3fc283a 100644 --- a/content/xul/content/Makefile.in +++ b/content/xul/content/Makefile.in @@ -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 diff --git a/content/xul/content/public/Makefile.in b/content/xul/content/public/Makefile.in new file mode 100644 index 000000000000..1c9bda3b5e42 --- /dev/null +++ b/content/xul/content/public/Makefile.in @@ -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 diff --git a/content/xul/content/public/nsIXULContextMenuBuilder.idl b/content/xul/content/public/nsIXULContextMenuBuilder.idl new file mode 100644 index 000000000000..b14ba38fabb4 --- /dev/null +++ b/content/xul/content/public/nsIXULContextMenuBuilder.idl @@ -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); + +}; diff --git a/content/xul/content/src/Makefile.in b/content/xul/content/src/Makefile.in index f5df02234078..99c87adbfaf6 100644 --- a/content/xul/content/src/Makefile.in +++ b/content/xul/content/src/Makefile.in @@ -54,6 +54,7 @@ ifdef MOZ_XUL CPPSRCS += \ nsXULElement.cpp \ nsXULPopupListener.cpp \ + nsXULContextMenuBuilder.cpp \ $(NULL) endif diff --git a/content/xul/content/src/nsXULContextMenuBuilder.cpp b/content/xul/content/src/nsXULContextMenuBuilder.cpp new file mode 100644 index 000000000000..3c660ad355ce --- /dev/null +++ b/content/xul/content/src/nsXULContextMenuBuilder.cpp @@ -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 menu; + nsresult rv = CreateElement(nsGkAtoms::menu, getter_AddRefs(menu)); + NS_ENSURE_SUCCESS(rv, rv); + + menu->SetAttr(kNameSpaceID_None, nsGkAtoms::label, aLabel, PR_FALSE); + + nsCOMPtr 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 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 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 element = mElements.SafeObjectAt(idx); + if (element) { + element->Click(); + } + } + + return NS_OK; +} + +nsresult +nsXULContextMenuBuilder::CreateElement(nsIAtom* aTag, nsIContent** aResult) +{ + *aResult = nsnull; + + nsCOMPtr 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; +} diff --git a/content/xul/content/src/nsXULContextMenuBuilder.h b/content/xul/content/src/nsXULContextMenuBuilder.h new file mode 100644 index 000000000000..2b8c2bdc9224 --- /dev/null +++ b/content/xul/content/src/nsXULContextMenuBuilder.h @@ -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 mFragment; + nsCOMPtr mDocument; + nsCOMPtr mGeneratedAttr; + nsCOMPtr mIdentAttr; + + nsCOMPtr mCurrentNode; + PRInt32 mCurrentIdent; + + nsCOMArray mElements; +}; diff --git a/dom/base/nsDOMClassInfo.cpp b/dom/base/nsDOMClassInfo.cpp index 46c3d501f765..69df178ed7b8 100644 --- a/dom/base/nsDOMClassInfo.cpp +++ b/dom/base/nsDOMClassInfo.cpp @@ -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 || diff --git a/dom/base/nsDOMClassInfoClasses.h b/dom/base/nsDOMClassInfoClasses.h index c2cf31260f99..b6517a1e9539 100644 --- a/dom/base/nsDOMClassInfoClasses.h +++ b/dom/base/nsDOMClassInfoClasses.h @@ -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) diff --git a/dom/interfaces/html/Makefile.in b/dom/interfaces/html/Makefile.in index e0b55765e91c..1814bafc3dcf 100644 --- a/dom/interfaces/html/Makefile.in +++ b/dom/interfaces/html/Makefile.in @@ -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 \ diff --git a/dom/interfaces/html/nsIDOMHTMLCommandElement.idl b/dom/interfaces/html/nsIDOMHTMLCommandElement.idl new file mode 100644 index 000000000000..3b5ebbd5e019 --- /dev/null +++ b/dom/interfaces/html/nsIDOMHTMLCommandElement.idl @@ -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 + * 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; +}; diff --git a/dom/interfaces/html/nsIDOMHTMLMenuElement.idl b/dom/interfaces/html/nsIDOMHTMLMenuElement.idl index 2b0cc66b4c2c..55e0c6f14249 100644 --- a/dom/interfaces/html/nsIDOMHTMLMenuElement.idl +++ b/dom/interfaces/html/nsIDOMHTMLMenuElement.idl @@ -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; }; diff --git a/dom/interfaces/html/nsIDOMHTMLMenuItemElement.idl b/dom/interfaces/html/nsIDOMHTMLMenuItemElement.idl new file mode 100644 index 000000000000..7152c412ec7c --- /dev/null +++ b/dom/interfaces/html/nsIDOMHTMLMenuItemElement.idl @@ -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 + * element. + * + * @status UNDER_DEVELOPMENT + */ + +[scriptable, uuid(613f28ee-01f5-42dc-8224-161f85f0f20b)] +interface nsIDOMHTMLMenuItemElement : nsIDOMHTMLCommandElement +{ +}; diff --git a/dom/interfaces/html/nsIDOMNSHTMLElement.idl b/dom/interfaces/html/nsIDOMNSHTMLElement.idl index 0000b619d2ec..08d85f553b12 100644 --- a/dom/interfaces/html/nsIDOMNSHTMLElement.idl +++ b/dom/interfaces/html/nsIDOMNSHTMLElement.idl @@ -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,7 +72,8 @@ interface nsIDOMNSHTMLElement : nsISupports [optional_argc] void scrollIntoView([optional] in boolean top); + readonly attribute nsIDOMHTMLMenuElement contextMenu; attribute boolean spellcheck; - + readonly attribute nsIDOMDOMStringMap dataset; }; diff --git a/editor/libeditor/base/nsEditPropertyAtomList.h b/editor/libeditor/base/nsEditPropertyAtomList.h index adbbbfe833fb..2ebe92365e89 100644 --- a/editor/libeditor/base/nsEditPropertyAtomList.h +++ b/editor/libeditor/base/nsEditPropertyAtomList.h @@ -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") diff --git a/editor/libeditor/html/nsHTMLEditUtils.cpp b/editor/libeditor/html/nsHTMLEditUtils.cpp index 333c8a34352a..dfc6433ed034 100644 --- a/editor/libeditor/html/nsHTMLEditUtils.cpp +++ b/editor/libeditor/html/nsHTMLEditUtils.cpp @@ -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), diff --git a/js/src/xpconnect/src/dom_quickstubs.qsconf b/js/src/xpconnect/src/dom_quickstubs.qsconf index 72bca2d20e45..8c1f7e3110dc 100644 --- a/js/src/xpconnect/src/dom_quickstubs.qsconf +++ b/js/src/xpconnect/src/dom_quickstubs.qsconf @@ -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', diff --git a/layout/style/html.css b/layout/style/html.css index 13c6c1b7c93d..6c0b0bc4d51b 100644 --- a/layout/style/html.css +++ b/layout/style/html.css @@ -573,6 +573,10 @@ ul, menu, dir { -moz-padding-start: 40px; } +menu[type="context"] { + display: none !important; +} + ol { display: block; list-style-type: decimal; diff --git a/mobile/installer/package-manifest.in b/mobile/installer/package-manifest.in index be8f1d431c25..a681359de581 100644 --- a/mobile/installer/package-manifest.in +++ b/mobile/installer/package-manifest.in @@ -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 diff --git a/parser/html/nsHtml5TreeBuilderCppSupplement.h b/parser/html/nsHtml5TreeBuilderCppSupplement.h index 6153c883acda..be60266a9c5a 100644 --- a/parser/html/nsHtml5TreeBuilderCppSupplement.h +++ b/parser/html/nsHtml5TreeBuilderCppSupplement.h @@ -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 diff --git a/parser/htmlparser/public/nsHTMLTagList.h b/parser/htmlparser/public/nsHTMLTagList.h index 1dcccfc38db7..2fc4301c9f90 100644 --- a/parser/htmlparser/public/nsHTMLTagList.h +++ b/parser/htmlparser/public/nsHTMLTagList.h @@ -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) diff --git a/parser/htmlparser/src/nsElementTable.cpp b/parser/htmlparser/src/nsElementTable.cpp index fc8bc76c5862..15062f965e6f 100644 --- a/parser/htmlparser/src/nsElementTable.cpp +++ b/parser/htmlparser/src/nsElementTable.cpp @@ -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, diff --git a/parser/htmlparser/src/nsHTMLTags.cpp b/parser/htmlparser/src/nsHTMLTags.cpp index e672382c35f1..e42c591e2234 100644 --- a/parser/htmlparser/src/nsHTMLTags.cpp +++ b/parser/htmlparser/src/nsHTMLTags.cpp @@ -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[] = diff --git a/toolkit/content/Makefile.in b/toolkit/content/Makefile.in index 88c0295206b4..8696a4b56d64 100644 --- a/toolkit/content/Makefile.in +++ b/toolkit/content/Makefile.in @@ -93,6 +93,7 @@ EXTRA_PP_JS_MODULES = \ LightweightThemeConsumer.jsm \ Services.jsm \ WindowDraggingUtils.jsm \ + PageMenu.jsm \ $(NULL) include $(topsrcdir)/config/rules.mk diff --git a/toolkit/content/PageMenu.jsm b/toolkit/content/PageMenu.jsm new file mode 100644 index 000000000000..808b7be6c113 --- /dev/null +++ b/toolkit/content/PageMenu.jsm @@ -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); + } + } + } +} diff --git a/widget/public/nsGUIEvent.h b/widget/public/nsGUIEvent.h index 1e206006803c..fc7dd523c9f1 100644 --- a/widget/public/nsGUIEvent.h +++ b/widget/public/nsGUIEvent.h @@ -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.