Bug 617528 Part 2 - Core implementation r=smaug
authorJan Varga <jan.varga@gmail.com>
Mon, 08 Aug 2011 19:31:32 +0200
changeset 74003 561821863607ac216fbf273475dfde8d57fa7f2d
parent 74002 36989c74b287e7f7d132e3ecb80c02b54a88c6bd
child 74004 95feef73a8bac20dfd25cdc59e364d337e0ebb38
push id2
push userbsmedberg@mozilla.com
push dateFri, 19 Aug 2011 14:38:13 +0000
reviewerssmaug
bugs617528
milestone8.0a1
Bug 617528 Part 2 - Core implementation r=smaug
browser/base/content/browser-context.inc
browser/base/content/browser.js
browser/base/content/browser.xul
browser/base/content/nsContextMenu.js
browser/base/content/test/subtst_contextmenu.html
browser/base/content/test/test_contextmenu.html
browser/base/content/web-panels.xul
browser/installer/package-manifest.in
content/base/src/nsGkAtomList.h
content/events/public/nsEventNameList.h
content/events/src/nsDOMEvent.cpp
content/events/src/nsDOMEvent.h
content/html/content/public/Makefile.in
content/html/content/public/nsIHTMLMenu.idl
content/html/content/public/nsIMenuBuilder.idl
content/html/content/src/Makefile.in
content/html/content/src/nsGenericHTMLElement.cpp
content/html/content/src/nsGenericHTMLElement.h
content/html/content/src/nsHTMLMenuElement.cpp
content/html/content/src/nsHTMLMenuElement.h
content/html/content/src/nsHTMLMenuItemElement.cpp
content/html/content/src/nsHTMLMenuItemElement.h
content/html/content/src/nsHTMLSharedElement.cpp
content/html/content/test/Makefile.in
content/html/content/test/test_bug418756.html
content/html/content/test/test_bug617528.html
content/html/content/test/test_checked.html
content/html/document/src/nsHTMLContentSink.cpp
content/xml/document/src/nsXMLContentSink.cpp
content/xslt/src/xslt/txMozillaXMLOutput.cpp
content/xul/content/Makefile.in
content/xul/content/public/Makefile.in
content/xul/content/public/nsIXULContextMenuBuilder.idl
content/xul/content/src/Makefile.in
content/xul/content/src/nsXULContextMenuBuilder.cpp
content/xul/content/src/nsXULContextMenuBuilder.h
dom/base/nsDOMClassInfo.cpp
dom/base/nsDOMClassInfoClasses.h
dom/interfaces/html/Makefile.in
dom/interfaces/html/nsIDOMHTMLCommandElement.idl
dom/interfaces/html/nsIDOMHTMLMenuElement.idl
dom/interfaces/html/nsIDOMHTMLMenuItemElement.idl
dom/interfaces/html/nsIDOMNSHTMLElement.idl
editor/libeditor/base/nsEditPropertyAtomList.h
editor/libeditor/html/nsHTMLEditUtils.cpp
js/src/xpconnect/src/dom_quickstubs.qsconf
layout/style/html.css
mobile/installer/package-manifest.in
parser/html/nsHtml5TreeBuilderCppSupplement.h
parser/htmlparser/public/nsHTMLTagList.h
parser/htmlparser/src/nsElementTable.cpp
parser/htmlparser/src/nsHTMLTags.cpp
toolkit/content/Makefile.in
toolkit/content/PageMenu.jsm
widget/public/nsGUIEvent.h
--- a/browser/base/content/browser-context.inc
+++ b/browser/base/content/browser-context.inc
@@ -31,16 +31,17 @@
 # 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 *****
 
+      <menuseparator id="page-menu-separator"/>
       <menuitem id="spell-no-suggestions"
                 disabled="true"
                 label="&spellNoSuggestions.label;"/>
       <menuitem id="spell-add-to-dictionary"
                 label="&spellAddToDictionary.label;"
                 accesskey="&spellAddToDictionary.accesskey;"
                 oncommand="InlineSpellCheckerUI.addToDictionary();"/>
       <menuseparator id="spell-suggestions-separator"/>
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -211,16 +211,22 @@ XPCOMUtils.defineLazyGetter(this, "Win7F
 });
 
 #ifdef MOZ_CRASHREPORTER
 XPCOMUtils.defineLazyServiceGetter(this, "gCrashReporter",
                                    "@mozilla.org/xre/app-info;1",
                                    "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.
 */
 function pageShowEventHandlers(event) {
   // Filter out events that are not about the document load we are interested in
   if (event.originalTarget == content.document) {
     charsetLoadListener(event);
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -268,20 +268,20 @@
                 accesskey="&fullScreenAutohide.accesskey;"
                 oncommand="FullScreen.setAutohide();"/>
       <menuseparator/>
       <menuitem label="&fullScreenExit.label;"
                 accesskey="&fullScreenExit.accesskey;"
                 oncommand="BrowserFullScreen();"/>
     </menupopup>
 
-    <menupopup id="contentAreaContextMenu"
+    <menupopup id="contentAreaContextMenu" pagemenu="start"
                onpopupshowing="if (event.target != this)
                                  return true;
-                               gContextMenu = new nsContextMenu(this, gBrowser);
+                               gContextMenu = new nsContextMenu(this, gBrowser, event.shiftKey);
                                if (gContextMenu.shouldDisplay)
                                  updateEditUIVisibility();
                                return gContextMenu.shouldDisplay;"
                onpopuphiding="if (event.target == this) { gContextMenu = null; updateEditUIVisibility(); }">
 #include browser-context.inc
     </menupopup>
 
     <menupopup id="placesContext"/>
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -56,55 +56,66 @@
 # 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 *****
 
-function nsContextMenu(aXulMenu, aBrowser) {
+function nsContextMenu(aXulMenu, aBrowser, aIsShift) {
   this.shouldDisplay = true;
-  this.initMenu(aBrowser);
+  this.initMenu(aBrowser, aXulMenu, aIsShift);
 }
 
 // Prototype for nsContextMenu "class."
 nsContextMenu.prototype = {
-  initMenu: function CM_initMenu(aBrowser) {
+  initMenu: function CM_initMenu(aBrowser, aXulMenu, aIsShift) {
     // Get contextual info.
     this.setTarget(document.popupNode, document.popupRangeParent,
                    document.popupRangeOffset);
     if (!this.shouldDisplay)
       return;
 
     this.browser = aBrowser;
+
+    this.hasPageMenu = false;
+    if (!aIsShift) {
+      this.hasPageMenu = PageMenu.init(this.target, aXulMenu);
+    }
+
     this.isFrameImage = document.getElementById("isFrameImage");
     this.ellipsis = "\u2026";
     try {
       this.ellipsis = gPrefService.getComplexValue("intl.ellipsis",
                                                    Ci.nsIPrefLocalizedString).data;
     } catch (e) { }
     this.isTextSelected = this.isTextSelection();
     this.isContentSelected = this.isContentSelection();
 
     // Initialize (disable/remove) menu items.
     this.initItems();
   },
 
   initItems: function CM_initItems() {
+    this.initPageMenuSeparator();
     this.initOpenItems();
     this.initNavigationItems();
     this.initViewItems();
     this.initMiscItems();
     this.initSpellingItems();
     this.initSaveItems();
     this.initClipboardItems();
     this.initMediaPlayerItems();
   },
 
+  initPageMenuSeparator: function CM_initPageMenuSeparator() {
+    this.showItem("page-menu-separator", this.hasPageMenu);
+  },
+
   initOpenItems: function CM_initOpenItems() {
     var isMailtoInternal = false;
     if (this.onMailtoLink) {
       var mailtoHandler = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
                           getService(Ci.nsIExternalProtocolService).
                           getProtocolHandlerInfo("mailto");
       isMailtoInternal = (!mailtoHandler.alwaysAskBeforeHandling &&
                           mailtoHandler.preferredAction == Ci.nsIHandlerInfo.useHelperApp &&
--- a/browser/base/content/test/subtst_contextmenu.html
+++ b/browser/base/content/test/subtst_contextmenu.html
@@ -16,11 +16,45 @@ Browser context menu subtest.
 <video id="test-video-bad" src="bogus.duh" width="100" height="100" style="background-color: orange"></video>
 <video id="test-video-bad2" width="100" height="100" style="background-color: yellow">
   <source src="bogus.duh" type="video/durrrr;">
 </video>
 <iframe id="test-iframe" width="98"  height="98" style="border: 1px solid black"></iframe>
 <textarea id="test-textarea">chssseesbbbie</textarea> <!-- a weird word which generates only one suggestion -->
 <div id="test-contenteditable" contenteditable="true">chssseefsbbbie</div> <!-- a more weird word which generates no suggestions -->
 <input id="test-input-spellcheck" type="text" spellcheck="true" autofocus value="prodkjfgigrty"> <!-- this one also generates one suggestion -->
+<div contextmenu="myMenu">
+  <p id="test-pagemenu" hopeless="true">I've got a context menu!</p>
+  <menu id="myMenu" type="context">
+    <menuitem label="Plain item" onclick="document.getElementById('test-pagemenu').removeAttribute('hopeless');"></menuitem>
+    <menuitem label="Disabled item" disabled></menuitem>
+    <menu>
+      <menuitem type="checkbox" label="Checkbox" checked></menuitem>
+    </menu>
+    <menu>
+      <menuitem type="radio" label="Radio1" checked></menuitem>
+      <menuitem type="radio" label="Radio2"></menuitem>
+      <menuitem type="radio" label="Radio3"></menuitem>
+    </menu>
+    <menu>
+      <menuitem label="Item w/ icon" icon="favicon.ico"></menuitem>
+      <menuitem label="Item w/ bad icon" icon="data://www.mozilla.org/favicon.ico"></menuitem>
+    </menu>
+    <menu label="Submenu">
+      <menuitem type="radio" label="Radio1" radiogroup="rg"></menuitem>
+      <menuitem type="radio" label="Radio2" checked radiogroup="rg"></menuitem>
+      <menuitem type="radio" label="Radio3" radiogroup="rg"></menuitem>
+      <menu>
+        <menuitem type="checkbox" label="Checkbox"></menuitem>
+      </menu>
+    </menu>
+    <menu hidden>
+      <menuitem label="Bogus item"></menuitem>
+    </menu>
+    <menu>
+    </menu>
+    <menuitem label="Hidden item" hidden></menuitem>
+    <menuitem></menuitem>
+  </menu>
+</div>
 
 </body>
 </html>
--- a/browser/base/content/test/test_contextmenu.html
+++ b/browser/base/content/test/test_contextmenu.html
@@ -19,21 +19,21 @@ Browser context menu tests.
 
 /** Test for Login Manager: multiple login autocomplete. **/
 
 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);
 }
 
 function closeContextMenu() {
     contextMenu.hidePopup();
 }
 
 function executeCopyCommand(command, expectedValue)
@@ -45,113 +45,171 @@ function executeCopyCommand(command, exp
   // The easiest way to check the clipboard is to paste the contents into a
   // textbox
   input.focus();
   input.value = "";
   input.controllers.getControllerForCommand("cmd_paste").doCommand("cmd_paste");
   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++) {
         var item = aMenu.childNodes[i];
         if (item.hidden)
             continue;
 
         var key = item.accessKey;
         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");
             }
             var label = item.getAttribute("label");
             ok(label.length, "menuitem " + item.id + " has a label");
             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");
               if (accessKeys[key])
                   ok(false, "menuitem " + item.id + " has same accesskey as " + accessKeys[key]);
               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
             // for expectedItems and actualItems.
             items.push([]);
             items.push(null);
         } else {
             ok(false, "child #" + i + " of menu ID " + aMenu.id +
                       " has an unknown type (" + item.nodeName + ")");
         }
     }
     return items;
 }
 
 function checkContextMenu(expectedItems) {
     is(contextMenu.state, "open", "checking if popup is open");
-    checkMenu(contextMenu, expectedItems);
+    var data = { generatedSubmenuId: 1 };
+    checkMenu(contextMenu, expectedItems, data);
 }
 
 /*
  * checkMenu - checks to see if the specified <menupopup> contains the
  * expected items and state.
  * expectedItems is a array of (1) item IDs and (2) a boolean specifying if
  * the item is enabled or not (or null to ignore it). Submenus can be checked
  * by providing a nested array entry after the expected <menu> ID.
  * For example: ["blah", true,              // item enabled
  *               "submenu", null,           // submenu
  *                   ["sub1", true,         // submenu contents
  *                    "sub2", false], null, // submenu contents
  *               "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];
         var actualEnabled = actualItems[i + 1];
         var expectedItem = expectedItems[i];
         var expectedEnabled = expectedItems[i + 1];
         if (expectedItem instanceof Array) {
             ok(true, "Checking submenu...");
             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");
         }
     }
     // Could find unexpected extra items at the end...
     is(actualItems.length, expectedItems.length, "checking expected number of menu entries");
 }
 
@@ -403,19 +461,80 @@ function runTest(testNum) {
                               ["spell-check-dictionary-en-US", true,
                                "---",                          null,
                                "spell-add-dictionaries",       true], null]);
 
         closeContextMenu();
         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();
         return;
 
     /*
      * Other things that would be nice to test:
      *  - selected text
@@ -432,17 +551,17 @@ 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');
     chromeWin = subwindow
                     .QueryInterface(Ci.nsIInterfaceRequestor)
                     .getInterface(Ci.nsIWebNavigation)
                     .QueryInterface(Ci.nsIDocShellTreeItem)
                     .rootTreeItem
@@ -465,16 +584,17 @@ function startTest() {
     canvas = subwindow.document.getElementById("test-canvas");
     video_ok   = subwindow.document.getElementById("test-video-ok");
     video_bad  = subwindow.document.getElementById("test-video-bad");
     video_bad2 = subwindow.document.getElementById("test-video-bad2");
     iframe = subwindow.document.getElementById("test-iframe");
     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);
 }
 
 // We open this in a separate window, because the Mochitests run inside a frame.
 // The frame causes an extra menu item, and prevents running the test
 // standalone (ie, clicking the test name in the Mochitest window) to see
--- a/browser/base/content/web-panels.xul
+++ b/browser/base/content/web-panels.xul
@@ -75,20 +75,20 @@
              oncommand="getPanelBrowser().webNavigation.goForward();"
              disabled="true"/>
     <command id="Browser:Stop" oncommand="PanelBrowserStop();"/>
     <command id="Browser:Reload" oncommand="PanelBrowserReload();"/>
   </commandset>
 
   <popupset id="mainPopupSet">
     <tooltip id="aHTMLTooltip" onpopupshowing="return FillInHTMLTooltip(document.tooltipNode);"/>
-    <menupopup id="contentAreaContextMenu"
+    <menupopup id="contentAreaContextMenu" pagemenu="start"
                onpopupshowing="if (event.target != this)
                                  return true;
-                               gContextMenu = new nsContextMenu(this, getPanelBrowser());
+                               gContextMenu = new nsContextMenu(this, getPanelBrowser(), event.shiftKey);
                                if (gContextMenu.shouldDisplay)
                                  document.popupNode = this.triggerNode;
                                return gContextMenu.shouldDisplay;"
                onpopuphiding="if (event.target == this)
                                 gContextMenu = null;">
 #include browser-context.inc
     </menupopup>
   </popupset>
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -251,16 +251,17 @@
 @BINPATH@/components/xpcom_system.xpt
 @BINPATH@/components/xpcom_components.xpt
 @BINPATH@/components/xpcom_ds.xpt
 @BINPATH@/components/xpcom_io.xpt
 @BINPATH@/components/xpcom_threads.xpt
 @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
 @BINPATH@/components/telemetry.xpt
 
 ; JavaScript components
 @BINPATH@/components/ConsoleAPI.manifest
 @BINPATH@/components/ConsoleAPI.js
--- a/content/base/src/nsGkAtomList.h
+++ b/content/base/src/nsGkAtomList.h
@@ -562,16 +562,17 @@ GK_ATOM(mediaType, "media-type")
 GK_ATOM(member, "member")
 GK_ATOM(menu, "menu")
 GK_ATOM(menubar, "menubar")
 GK_ATOM(menubutton, "menubutton")
 GK_ATOM(menuButton, "menu-button")
 GK_ATOM(menuitem, "menuitem")
 GK_ATOM(menulist, "menulist")
 GK_ATOM(menupopup, "menupopup")
+GK_ATOM(menuseparator, "menuseparator")
 GK_ATOM(message, "message")
 GK_ATOM(meta, "meta")
 GK_ATOM(meter, "meter")
 GK_ATOM(method, "method")
 GK_ATOM(middle, "middle")
 GK_ATOM(min, "min")
 GK_ATOM(minheight, "minheight")
 GK_ATOM(minimum_scale, "minimum-scale")
@@ -718,16 +719,17 @@ GK_ATOM(onpopupshown, "onpopupshown")
 GK_ATOM(onreadystatechange, "onreadystatechange")
 GK_ATOM(onRequest, "onRequest")
 GK_ATOM(onreset, "onreset")
 GK_ATOM(onMozBeforeResize, "onMozBeforeResize")
 GK_ATOM(onresize, "onresize")
 GK_ATOM(onscroll, "onscroll")
 GK_ATOM(onselect, "onselect")
 GK_ATOM(onset, "onset")
+GK_ATOM(onshow, "onshow")
 GK_ATOM(onsubmit, "onsubmit")
 GK_ATOM(ontext, "ontext")
 GK_ATOM(ontouchstart, "ontouchstart")
 GK_ATOM(ontouchend, "ontouchend")
 GK_ATOM(ontouchmove, "ontouchmove")
 GK_ATOM(ontouchenter, "ontouchenter")
 GK_ATOM(ontouchleave, "ontouchleave")
 GK_ATOM(ontouchcancel, "ontouchcancel")
--- a/content/events/public/nsEventNameList.h
+++ b/content/events/public/nsEventNameList.h
@@ -280,18 +280,20 @@ EVENT(seeked,
 EVENT(seeking,
       NS_SEEKING,
       EventNameType_HTML,
       NS_EVENT_NULL)
 EVENT(select,
       NS_FORM_SELECTED,
       EventNameType_HTMLXUL,
       NS_EVENT)
-// Not supported yet
-// EVENT(show)
+EVENT(show,
+      NS_SHOW_EVENT,
+      EventNameType_HTML,
+      NS_EVENT)
 EVENT(stalled,
       NS_STALLED,
       EventNameType_HTML,
       NS_EVENT_NULL)
 EVENT(submit,
       NS_FORM_SUBMIT,
       EventNameType_HTMLXUL,
       NS_EVENT)
--- a/content/events/src/nsDOMEvent.cpp
+++ b/content/events/src/nsDOMEvent.cpp
@@ -72,17 +72,17 @@ static const char* const sEventNames[] =
   "dragenter", "dragover", "dragexit", "dragdrop", "draggesture",
   "drag", "dragend", "dragstart", "dragleave", "drop", "resize",
   "scroll", "overflow", "underflow", "overflowchanged",
   "DOMSubtreeModified", "DOMNodeInserted", "DOMNodeRemoved", 
   "DOMNodeRemovedFromDocument", "DOMNodeInsertedIntoDocument",
   "DOMAttrModified", "DOMCharacterDataModified",
   "DOMActivate", "DOMFocusIn", "DOMFocusOut",
   "pageshow", "pagehide", "DOMMouseScroll", "MozMousePixelScroll",
-  "offline", "online", "copy", "cut", "paste", "open", "message",
+  "offline", "online", "copy", "cut", "paste", "open", "message", "show",
   "SVGLoad", "SVGUnload", "SVGAbort", "SVGError", "SVGResize", "SVGScroll",
   "SVGZoom",
 #ifdef MOZ_SMIL
   "beginEvent", "endEvent", "repeatEvent",
 #endif // MOZ_SMIL
 #ifdef MOZ_MEDIA
   "loadstart", "progress", "suspend", "emptied", "stalled", "play", "pause",
   "loadedmetadata", "loadeddata", "waiting", "playing", "canplay",
@@ -1252,16 +1252,18 @@ const char* nsDOMEvent::GetEventName(PRU
   case NS_CUT:
     return sEventNames[eDOMEvents_cut];
   case NS_PASTE:
     return sEventNames[eDOMEvents_paste];
   case NS_OPEN:
     return sEventNames[eDOMEvents_open];
   case NS_MESSAGE:
     return sEventNames[eDOMEvents_message];
+  case NS_SHOW_EVENT:
+    return sEventNames[eDOMEvents_show];
   case NS_SVG_LOAD:
     return sEventNames[eDOMEvents_SVGLoad];
   case NS_SVG_UNLOAD:
     return sEventNames[eDOMEvents_SVGUnload];
   case NS_SVG_ABORT:
     return sEventNames[eDOMEvents_SVGAbort];
   case NS_SVG_ERROR:
     return sEventNames[eDOMEvents_SVGError];
--- a/content/events/src/nsDOMEvent.h
+++ b/content/events/src/nsDOMEvent.h
@@ -133,16 +133,17 @@ public:
     eDOMEvents_MozMousePixelScroll,
     eDOMEvents_offline,
     eDOMEvents_online,
     eDOMEvents_copy,
     eDOMEvents_cut,
     eDOMEvents_paste,
     eDOMEvents_open,
     eDOMEvents_message,
+    eDOMEvents_show,
     eDOMEvents_SVGLoad,
     eDOMEvents_SVGUnload,
     eDOMEvents_SVGAbort,
     eDOMEvents_SVGError,
     eDOMEvents_SVGResize,
     eDOMEvents_SVGScroll,
     eDOMEvents_SVGZoom,
 #ifdef MOZ_SMIL
--- a/content/html/content/public/Makefile.in
+++ b/content/html/content/public/Makefile.in
@@ -43,16 +43,18 @@ VPATH     = @srcdir@
 include $(DEPTH)/config/autoconf.mk
 
 MODULE       = content
 XPIDL_MODULE = content_html
 
 XPIDLSRCS = \
 		nsIFormSubmitObserver.idl \
 		nsIPhonetic.idl \
+		nsIHTMLMenu.idl \
+		nsIMenuBuilder.idl \
 		$(NULL)
 
 EXPORTS = \
 		nsIConstraintValidation.h \
 		nsIFormControl.h \
 		nsIForm.h \
 		nsIFormProcessor.h \
 		nsILink.h \
new file mode 100644
--- /dev/null
+++ b/content/html/content/public/nsIHTMLMenu.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 nsIMenuBuilder;
+
+/**
+ * A private interface.
+ * All methods throw NS_ERROR_DOM_SECURITY_ERR if the caller is not chrome.
+ */
+
+[scriptable, uuid(d3d068d8-e223-4228-ba39-4d6df21ba616)]
+interface nsIHTMLMenu : nsISupports
+{
+  /**
+   * Creates and dispatches a trusted event named "show".
+   * The event is not cancelable and does not bubble.
+   * See http://www.whatwg.org/specs/web-apps/current-work/multipage/interactive-elements.html#context-menus
+   */
+  void sendShowEvent();
+
+  /**
+   * Creates a native menu builder. The builder type is dependent on menu type.
+   * Currently, it returns nsXULContextMenuBuilder for context menus.
+   * Toolbar menus are not yet supported (the method returns null).
+   */
+  nsIMenuBuilder createBuilder();
+
+  /*
+   * Builds a menu by iterating over menu children.
+   * See http://www.whatwg.org/specs/web-apps/current-work/multipage/interactive-elements.html#building-menus-and-toolbars
+   * The caller can use a native builder by calling createBuilder() or provide
+   * a custom builder that implements the nsIMenuBuilder interface.
+   * A custom builder can be used for example to build native context menus
+   * that are not defined using <menupopup>.
+   */
+  void build(in nsIMenuBuilder aBuilder);
+
+};
new file mode 100644
--- /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 <menu>
+ */
+
+[scriptable, uuid(12724737-f7db-43b4-94ab-708a7b86e115)]
+interface nsIMenuBuilder : nsISupports
+{
+
+  /**
+   * Create the top level menu or a submenu. The implementation should create
+   * a new context for this menu, so all subsequent methods will add new items
+   * to this newly created menu.
+   */
+  void openContainer(in DOMString aLabel);
+
+  /**
+   * Add a new menu item. All menu item details can be obtained from
+   * the element. This method is not called for hidden elements or elements
+   * with no or empty label. The icon should be loaded only if aCanLoadIcon
+   * is true.
+   */
+  void addItemFor(in nsIDOMHTMLMenuItemElement aElement,
+                  in boolean aCanLoadIcon);
+
+  /**
+   * Create a new separator.
+   */
+  void addSeparator();
+
+  /**
+   * Remove last added separator.
+   * Sometimes it's needed to remove last added separator, otherwise it's not
+   * possible to implement the postprocessing in one pass.
+   * See http://www.whatwg.org/specs/web-apps/current-work/multipage/interactive-elements.html#building-menus-and-toolbars
+   */
+  void undoAddSeparator();
+
+  /**
+   * Set the context to the parent menu.
+   */
+  void closeContainer();
+
+};
--- a/content/html/content/src/Makefile.in
+++ b/content/html/content/src/Makefile.in
@@ -77,16 +77,18 @@ CPPSRCS		= \
 		nsHTMLIFrameElement.cpp \
 		nsHTMLImageElement.cpp \
 		nsHTMLInputElement.cpp \
 		nsHTMLLIElement.cpp \
 		nsHTMLLabelElement.cpp \
 		nsHTMLLegendElement.cpp \
 		nsHTMLLinkElement.cpp \
 		nsHTMLMapElement.cpp \
+		nsHTMLMenuElement.cpp \
+		nsHTMLMenuItemElement.cpp \
 		nsHTMLMetaElement.cpp \
 		nsHTMLModElement.cpp \
 		nsHTMLObjectElement.cpp \
 		nsHTMLOListElement.cpp \
 		nsHTMLSharedObjectElement.cpp \
 		nsHTMLOptionElement.cpp \
 		nsHTMLOptGroupElement.cpp \
 		nsHTMLOutputElement.cpp \
@@ -129,16 +131,17 @@ FORCE_STATIC_LIB = 1
 
 include $(topsrcdir)/config/rules.mk
 include $(topsrcdir)/ipc/chromium/chromium-config.mk
 
 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 \
 		-I$(srcdir)/../../../../layout/xul/base/src \
 		-I$(srcdir)/../../../../layout/generic \
 		-I$(srcdir)/../../../../dom/base \
 		-I$(srcdir)/../../../../editor/libeditor/base \
 		-I$(srcdir)/../../../../editor/libeditor/text \
--- a/content/html/content/src/nsGenericHTMLElement.cpp
+++ b/content/html/content/src/nsGenericHTMLElement.cpp
@@ -45,16 +45,17 @@
 #include "mozilla/css/StyleRule.h"
 #include "nsIDocument.h"
 #include "nsIDocumentEncoder.h"
 #include "nsIDOMHTMLBodyElement.h"
 #include "nsIDOMHTMLDocument.h"
 #include "nsIDOMAttr.h"
 #include "nsIDOMDocumentFragment.h"
 #include "nsIDOMNSHTMLElement.h"
+#include "nsIDOMHTMLMenuElement.h"
 #include "nsIDOMElementCSSInlineStyle.h"
 #include "nsIDOMWindow.h"
 #include "nsIDOMDocument.h"
 #include "nsEventListenerManager.h"
 #include "nsMappedAttributes.h"
 #include "nsHTMLStyleSheet.h"
 #include "nsIHTMLDocument.h"
 #include "nsILink.h"
@@ -110,16 +111,17 @@
 #include "nsEventDispatcher.h"
 #include "nsLayoutUtils.h"
 #include "nsContentCreatorFunctions.h"
 #include "mozAutoDocUpdate.h"
 #include "nsHtml5Module.h"
 #include "nsITextControlElement.h"
 #include "mozilla/dom/Element.h"
 #include "nsHTMLFieldSetElement.h"
+#include "nsHTMLMenuElement.h"
 
 #include "mozilla/Preferences.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 #include "nsThreadUtils.h"
 
@@ -2446,16 +2448,38 @@ nsGenericHTMLElement::GetIsContentEditab
       }
     }
   }
 
   *aContentEditable = PR_FALSE;
   return NS_OK;
 }
 
+nsresult
+nsGenericHTMLElement::GetContextMenu(nsIDOMHTMLMenuElement** aContextMenu)
+{
+  *aContextMenu = nsnull;
+
+  nsAutoString value;
+  GetAttr(kNameSpaceID_None, nsGkAtoms::contextmenu, value);
+
+  if (value.IsEmpty()) {
+    return NS_OK;
+  }
+
+  nsIDocument* doc = GetCurrentDoc();
+  if (doc) {
+    nsRefPtr<nsHTMLMenuElement> element =
+      nsHTMLMenuElement::FromContent(doc->GetElementById(value));
+    element.forget(aContextMenu);
+  }
+
+  return NS_OK;
+}
+
 //----------------------------------------------------------------------
 
 NS_IMPL_INT_ATTR(nsGenericHTMLFrameElement, TabIndex, tabindex)
 
 nsGenericHTMLFormElement::nsGenericHTMLFormElement(already_AddRefed<nsINodeInfo> aNodeInfo)
   : nsGenericHTMLElement(aNodeInfo)
   , mForm(nsnull)
   , mFieldSet(nsnull)
--- a/content/html/content/src/nsGenericHTMLElement.h
+++ b/content/html/content/src/nsGenericHTMLElement.h
@@ -61,16 +61,17 @@ class nsIFormControlFrame;
 class nsIForm;
 class nsPresState;
 class nsILayoutHistoryState;
 class nsIEditor;
 struct nsRect;
 struct nsSize;
 class nsHTMLFormElement;
 class nsIDOMDOMStringMap;
+class nsIDOMHTMLMenuElement;
 
 typedef nsMappedAttributeElement nsGenericHTMLElementBase;
 
 /**
  * A common superclass for HTML elements
  */
 class nsGenericHTMLElement : public nsGenericHTMLElementBase
 {
@@ -156,16 +157,17 @@ public:
   NS_IMETHOD SetAccessKey(const nsAString& aAccessKey);
   NS_IMETHOD GetAccessKeyLabel(nsAString& aLabel);
   nsresult GetContentEditable(nsAString& aContentEditable);
   nsresult GetIsContentEditable(PRBool* aContentEditable);
   nsresult SetContentEditable(const nsAString &aContentEditable);
   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,
                               nsIContent* aBindingParent,
                               PRBool aCompileEventHandlers);
   virtual void UnbindFromTree(PRBool aDeep = PR_TRUE,
                               PRBool aNullParent = PR_TRUE);
   nsresult SetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
@@ -528,16 +530,21 @@ public:
 
   /**
    * Returns the current disabled state of the element.
    */
   virtual bool IsDisabled() const {
     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
    */
   void AddToNameTable(nsIAtom* aName) {
     NS_ASSERTION(HasName(), "Node doesn't have name?");
     nsIDocument* doc = GetCurrentDoc();
     if (doc && !IsInAnonymousSubtree()) {
@@ -1466,32 +1473,32 @@ protected:
     NS_INTERFACE_TABLE_ENTRY(_class, _i5)                                     \
     NS_INTERFACE_TABLE_ENTRY(_class, _i6)                                     \
     NS_INTERFACE_TABLE_ENTRY(_class, _i7)                                     \
     NS_INTERFACE_TABLE_ENTRY(_class, _i8)                                     \
     NS_INTERFACE_TABLE_ENTRY(_class, _i9)                                     \
     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(); }
 
 /**
  * A macro to declare the NS_NewHTMLXXXElement() functions.
  */
 #define NS_DECLARE_NS_NEW_HTML_ELEMENT(_elementName)                       \
 nsGenericHTMLElement*                                                      \
 NS_NewHTML##_elementName##Element(already_AddRefed<nsINodeInfo> aNodeInfo, \
@@ -1558,16 +1565,18 @@ NS_DECLARE_NS_NEW_HTML_ELEMENT_AS_SHARED
 NS_DECLARE_NS_NEW_HTML_ELEMENT(IFrame)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(Image)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(Input)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(LI)
 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)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(Option)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(Output)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(Paragraph)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(Pre)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(Progress)
new file mode 100644
--- /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<nsINodeInfo> aNodeInfo)
+  : nsGenericHTMLElement(aNodeInfo), mType(MENU_TYPE_LIST)
+{
+}
+
+nsHTMLMenuElement::~nsHTMLMenuElement()
+{
+}
+
+
+NS_IMPL_ADDREF_INHERITED(nsHTMLMenuElement, nsGenericElement)
+NS_IMPL_RELEASE_INHERITED(nsHTMLMenuElement, nsGenericElement)
+
+
+DOMCI_NODE_DATA(HTMLMenuElement, nsHTMLMenuElement)
+
+// QueryInterface implementation for nsHTMLMenuElement
+NS_INTERFACE_TABLE_HEAD(nsHTMLMenuElement)
+  NS_HTML_CONTENT_INTERFACE_TABLE2(nsHTMLMenuElement,
+                                   nsIDOMHTMLMenuElement,
+                                   nsIHTMLMenu)
+  NS_HTML_CONTENT_INTERFACE_TABLE_TO_MAP_SEGUE(nsHTMLMenuElement,
+                                               nsGenericHTMLElement)
+NS_HTML_CONTENT_INTERFACE_TABLE_TAIL_CLASSINFO(HTMLMenuElement)
+
+NS_IMPL_ELEMENT_CLONE(nsHTMLMenuElement)
+
+NS_IMPL_BOOL_ATTR(nsHTMLMenuElement, Compact, compact)
+NS_IMPL_ENUM_ATTR_DEFAULT_VALUE(nsHTMLMenuElement, Type, type,
+                                kMenuDefaultType->tag)
+NS_IMPL_STRING_ATTR(nsHTMLMenuElement, Label, label)
+
+
+NS_IMETHODIMP
+nsHTMLMenuElement::SendShowEvent()
+{
+  NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_DOM_SECURITY_ERR);
+
+  nsCOMPtr<nsIDocument> document = GetCurrentDoc();
+  if (!document) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsEvent event(PR_TRUE, NS_SHOW_EVENT);
+  event.flags |= NS_EVENT_FLAG_CANT_CANCEL | NS_EVENT_FLAG_CANT_BUBBLE;
+
+  nsCOMPtr<nsIPresShell> shell = document->GetShell();
+  if (!shell) {
+    return NS_ERROR_FAILURE;
+  }
+ 
+  nsRefPtr<nsPresContext> presContext = shell->GetPresContext();
+  nsEventStatus status = nsEventStatus_eIgnore;
+  nsEventDispatcher::Dispatch(static_cast<nsIContent*>(this), presContext,
+                              &event, nsnull, &status);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHTMLMenuElement::CreateBuilder(nsIMenuBuilder** _retval)
+{
+  NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_DOM_SECURITY_ERR);
+
+  *_retval = nsnull;
+
+  if (mType == MENU_TYPE_CONTEXT) {
+    NS_ADDREF(*_retval = new nsXULContextMenuBuilder());
+  }
+
+  return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsHTMLMenuElement::Build(nsIMenuBuilder* aBuilder)
+{
+  NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_DOM_SECURITY_ERR);
+
+  if (!aBuilder) {
+    return NS_OK;
+  }
+
+  BuildSubmenu(EmptyString(), this, aBuilder);
+
+  return NS_OK;
+}
+
+
+PRBool
+nsHTMLMenuElement::ParseAttribute(PRInt32 aNamespaceID,
+                                  nsIAtom* aAttribute,
+                                  const nsAString& aValue,
+                                  nsAttrValue& aResult)
+{
+  if (aNamespaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::type) {
+    PRBool success = aResult.ParseEnumValue(aValue, kMenuTypeTable,
+                                            PR_FALSE);
+    if (success) {
+      mType = aResult.GetEnumValue();
+    } else {
+      mType = kMenuDefaultType->value;
+    }
+
+    return success;
+  }
+
+  return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+                                              aResult);
+}
+
+void
+nsHTMLMenuElement::BuildSubmenu(const nsAString& aLabel,
+                                nsIContent* aContent,
+                                nsIMenuBuilder* aBuilder)
+{
+  aBuilder->OpenContainer(aLabel);
+
+  PRInt8 separator = ST_TRUE_INIT;
+  TraverseContent(aContent, aBuilder, separator);
+
+  if (separator == ST_TRUE) {
+    aBuilder->UndoAddSeparator();
+  }
+
+  aBuilder->CloseContainer();
+}
+
+// static
+PRBool
+nsHTMLMenuElement::CanLoadIcon(nsIContent* aContent, const nsAString& aIcon)
+{
+  if (aIcon.IsEmpty()) {
+    return PR_FALSE;
+  }
+
+  nsIDocument* doc = aContent->GetOwnerDoc();
+  if (!doc) {
+    return PR_FALSE;
+  }
+
+  nsCOMPtr<nsIURI> baseURI = aContent->GetBaseURI();
+  nsCOMPtr<nsIURI> uri;
+  nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri), aIcon, doc,
+                                            baseURI);
+
+  if (!uri) {
+    return PR_FALSE;
+  }
+
+  return nsContentUtils::CanLoadImage(uri, aContent, doc,
+                                      aContent->NodePrincipal());
+}
+
+void
+nsHTMLMenuElement::TraverseContent(nsIContent* aContent,
+                                   nsIMenuBuilder* aBuilder,
+                                   PRInt8& aSeparator)
+{
+  nsCOMPtr<nsIContent> child;
+  for (child = aContent->GetFirstChild(); child;
+       child = child->GetNextSibling()) {
+    nsGenericHTMLElement* element = nsGenericHTMLElement::FromContent(child);
+    if (!element) {
+      continue;
+    }
+
+    nsIAtom* tag = child->Tag();
+
+    if (tag == nsGkAtoms::menuitem) {
+      nsHTMLMenuItemElement* menuitem =
+        nsHTMLMenuItemElement::FromContent(child);
+
+      if (menuitem->IsHidden()) {
+        continue;
+      }
+
+      nsAutoString label;
+      menuitem->GetLabel(label);
+      if (label.IsEmpty()) {
+        continue;
+      }
+
+      nsAutoString icon;
+      menuitem->GetIcon(icon);
+
+      aBuilder->AddItemFor(menuitem, CanLoadIcon(child, icon));
+
+      aSeparator = ST_FALSE;
+    } else if (tag == nsGkAtoms::menu && !element->IsHidden()) {
+      if (child->HasAttr(kNameSpaceID_None, nsGkAtoms::label)) {
+        nsAutoString label;
+        child->GetAttr(kNameSpaceID_None, nsGkAtoms::label, label);
+
+        BuildSubmenu(label, child, aBuilder);
+
+        aSeparator = ST_FALSE;
+      } else {
+        AddSeparator(aBuilder, aSeparator);
+
+        TraverseContent(child, aBuilder, aSeparator);
+
+        AddSeparator(aBuilder, aSeparator);
+      }
+    }
+  }
+}
+
+inline void
+nsHTMLMenuElement::AddSeparator(nsIMenuBuilder* aBuilder, PRInt8& aSeparator)
+{
+  if (aSeparator) {
+    return;
+  }
+ 
+  aBuilder->AddSeparator();
+  aSeparator = ST_TRUE;
+}
new file mode 100644
--- /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<nsINodeInfo> aNodeInfo);
+  virtual ~nsHTMLMenuElement();
+
+  /** Typesafe, non-refcounting cast from nsIContent.  Cheaper than QI. **/
+  static nsHTMLMenuElement* FromContent(nsIContent* aContent)
+  {
+    if (aContent && aContent->IsHTML(nsGkAtoms::menu))
+      return static_cast<nsHTMLMenuElement*>(aContent);
+    return nsnull;
+  }
+
+  // nsISupports
+  NS_DECL_ISUPPORTS_INHERITED
+
+  // nsIDOMNode
+  NS_FORWARD_NSIDOMNODE(nsGenericHTMLElement::)
+
+  // nsIDOMElement
+  NS_FORWARD_NSIDOMELEMENT(nsGenericHTMLElement::)
+
+  // nsIDOMHTMLElement
+  NS_FORWARD_NSIDOMHTMLELEMENT(nsGenericHTMLElement::)
+
+  // nsIDOMHTMLMenuElement
+  NS_DECL_NSIDOMHTMLMENUELEMENT
+
+  // nsIHTMLMenu
+  NS_DECL_NSIHTMLMENU
+
+  virtual PRBool ParseAttribute(PRInt32 aNamespaceID,
+                                nsIAtom* aAttribute,
+                                const nsAString& aValue,
+                                nsAttrValue& aResult);
+
+  virtual nsresult Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const;
+
+  virtual nsXPCClassInfo* GetClassInfo();
+
+  PRUint8 GetType() const { return mType; }
+
+protected:
+  static PRBool CanLoadIcon(nsIContent* aContent, const nsAString& aIcon);
+
+  void BuildSubmenu(const nsAString& aLabel,
+                    nsIContent* aContent,
+                    nsIMenuBuilder* aBuilder);
+
+  void TraverseContent(nsIContent* aContent,
+                       nsIMenuBuilder* aBuilder,
+                       PRInt8& aSeparator);
+
+  void AddSeparator(nsIMenuBuilder* aBuilder, PRInt8& aSeparator);
+
+  PRUint8 mType;
+};
new file mode 100644
--- /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<nsINodeInfo> aNodeInfo, FromParser aFromParser)
+  : nsGenericHTMLElement(aNodeInfo),
+    mType(kMenuItemDefaultType->value),
+    mParserCreating(false),
+    mShouldInitChecked(false),
+    mCheckedDirty(false),
+    mChecked(false)
+{
+  mParserCreating = aFromParser;
+}
+
+nsHTMLMenuItemElement::~nsHTMLMenuItemElement()
+{
+}
+
+
+NS_IMPL_ADDREF_INHERITED(nsHTMLMenuItemElement, nsGenericElement)
+NS_IMPL_RELEASE_INHERITED(nsHTMLMenuItemElement, nsGenericElement)
+
+
+DOMCI_NODE_DATA(HTMLMenuItemElement, nsHTMLMenuItemElement)
+
+// QueryInterface implementation for nsHTMLMenuItemElement
+NS_INTERFACE_TABLE_HEAD(nsHTMLMenuItemElement)
+  NS_HTML_CONTENT_INTERFACE_TABLE2(nsHTMLMenuItemElement,
+                                   nsIDOMHTMLCommandElement,
+                                   nsIDOMHTMLMenuItemElement)
+  NS_HTML_CONTENT_INTERFACE_TABLE_TO_MAP_SEGUE(nsHTMLMenuItemElement,
+                                               nsGenericHTMLElement)
+NS_HTML_CONTENT_INTERFACE_TABLE_TAIL_CLASSINFO(HTMLMenuItemElement)
+
+//NS_IMPL_ELEMENT_CLONE(nsHTMLMenuItemElement)
+nsresult
+nsHTMLMenuItemElement::Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const
+{
+  *aResult = nsnull;
+  nsCOMPtr<nsINodeInfo> ni = aNodeInfo;
+  nsHTMLMenuItemElement *it = new nsHTMLMenuItemElement(ni.forget(),
+                                                        NOT_FROM_PARSER);
+  if (!it) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  nsCOMPtr<nsINode> kungFuDeathGrip = it;
+  nsresult rv = CopyInnerTo(it);
+  if (NS_SUCCEEDED(rv)) {
+    switch (mType) {
+      case CMD_TYPE_CHECKBOX:
+      case CMD_TYPE_RADIO:
+        if (mCheckedDirty) {
+          // We no longer have our original checked state.  Set our
+          // checked state on the clone.
+          it->mCheckedDirty = true;
+          it->mChecked = mChecked;
+        }
+        break;
+    }
+
+    kungFuDeathGrip.swap(*aResult);
+  }
+
+  return rv;
+}
+
+
+NS_IMPL_ENUM_ATTR_DEFAULT_VALUE(nsHTMLMenuItemElement, Type, type,
+                                kMenuItemDefaultType->tag)
+NS_IMPL_STRING_ATTR(nsHTMLMenuItemElement, Label, label)
+NS_IMPL_URI_ATTR(nsHTMLMenuItemElement, Icon, icon)
+NS_IMPL_BOOL_ATTR(nsHTMLMenuItemElement, Disabled, disabled)
+NS_IMPL_BOOL_ATTR(nsHTMLMenuItemElement, DefaultChecked, checked)
+//NS_IMPL_BOOL_ATTR(nsHTMLMenuItemElement, Checked, checked)
+NS_IMPL_STRING_ATTR(nsHTMLMenuItemElement, Radiogroup, radiogroup)
+
+NS_IMETHODIMP
+nsHTMLMenuItemElement::GetChecked(PRBool* aChecked)
+{
+  *aChecked = mChecked;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHTMLMenuItemElement::SetChecked(PRBool aChecked)
+{
+  PRBool checkedChanged = mChecked != aChecked;
+
+  mChecked = aChecked;
+
+  if (mType == CMD_TYPE_RADIO) {
+    if (checkedChanged) {
+      if (mCheckedDirty) {
+        ClearCheckedVisitor visitor(this);
+        WalkRadioGroup(&visitor);
+      } else {
+        ClearCheckedVisitor visitor1(this);
+        SetCheckedDirtyVisitor visitor2;
+        CombinedVisitor visitor(&visitor1, &visitor2);
+        WalkRadioGroup(&visitor);
+      }
+    } else if (!mCheckedDirty) {
+      SetCheckedDirtyVisitor visitor;
+      WalkRadioGroup(&visitor);
+    }
+  } else {
+    mCheckedDirty = true;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+nsHTMLMenuItemElement::PreHandleEvent(nsEventChainPreVisitor& aVisitor)
+{
+  if (aVisitor.mEvent->message == NS_MOUSE_CLICK) {
+
+    PRBool originalCheckedValue = PR_FALSE;
+    switch (mType) {
+      case CMD_TYPE_CHECKBOX:
+        originalCheckedValue = mChecked;
+        SetChecked(!originalCheckedValue);
+        aVisitor.mItemFlags |= NS_CHECKED_IS_TOGGLED;
+        break;
+      case CMD_TYPE_RADIO:
+        nsCOMPtr<nsIDOMHTMLMenuItemElement> selectedRadio = GetSelectedRadio();
+        aVisitor.mItemData = selectedRadio;
+
+        originalCheckedValue = mChecked;
+        if (!originalCheckedValue) {
+          SetChecked(PR_TRUE);
+          aVisitor.mItemFlags |= NS_CHECKED_IS_TOGGLED;
+        }
+        break;
+    }
+
+    if (originalCheckedValue) {
+      aVisitor.mItemFlags |= NS_ORIGINAL_CHECKED_VALUE;
+    }
+
+    // We must cache type because mType may change during JS event.
+    aVisitor.mItemFlags |= mType;
+  }
+
+  return nsGenericHTMLElement::PreHandleEvent(aVisitor);
+}
+
+nsresult
+nsHTMLMenuItemElement::PostHandleEvent(nsEventChainPostVisitor& aVisitor)
+{
+  // Check to see if the event was cancelled.
+  if (aVisitor.mEvent->message == NS_MOUSE_CLICK &&
+      aVisitor.mItemFlags & NS_CHECKED_IS_TOGGLED &&
+      aVisitor.mEventStatus == nsEventStatus_eConsumeNoDefault) {
+    PRBool originalCheckedValue =
+      !!(aVisitor.mItemFlags & NS_ORIGINAL_CHECKED_VALUE);
+    PRUint8 oldType = NS_MENUITEM_TYPE(aVisitor.mItemFlags);
+
+    nsCOMPtr<nsIDOMHTMLMenuItemElement> selectedRadio =
+      do_QueryInterface(aVisitor.mItemData);
+    if (selectedRadio) {
+      selectedRadio->SetChecked(PR_TRUE);
+      if (mType != CMD_TYPE_RADIO) {
+        SetChecked(PR_FALSE);
+      }
+    } else if (oldType == CMD_TYPE_CHECKBOX) {
+      SetChecked(originalCheckedValue);
+    }
+  }
+
+  return NS_OK;
+}
+
+nsresult
+nsHTMLMenuItemElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+                                  nsIContent* aBindingParent,
+                                  PRBool aCompileEventHandlers)
+{
+  nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent,
+                                                 aBindingParent,
+                                                 aCompileEventHandlers);
+
+  if (NS_SUCCEEDED(rv) && aDocument && mType == CMD_TYPE_RADIO) {
+    AddedToRadioGroup();
+  }
+
+  return rv;
+}
+
+PRBool
+nsHTMLMenuItemElement::ParseAttribute(PRInt32 aNamespaceID,
+                                      nsIAtom* aAttribute,
+                                      const nsAString& aValue,
+                                      nsAttrValue& aResult)
+{
+  if (aNamespaceID == kNameSpaceID_None) {
+    if (aAttribute == nsGkAtoms::type) {
+      PRBool success = aResult.ParseEnumValue(aValue, kMenuItemTypeTable,
+                                              PR_FALSE);
+      if (success) {
+        mType = aResult.GetEnumValue();
+      } else {
+        mType = kMenuItemDefaultType->value;
+      }
+
+      return success;
+    }
+
+    if (aAttribute == nsGkAtoms::radiogroup) {
+      aResult.ParseAtom(aValue);
+      return PR_TRUE;
+    }
+  }
+
+  return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+                                              aResult);
+}
+
+void
+nsHTMLMenuItemElement::DoneCreatingElement()
+{
+  mParserCreating = false;
+
+  if (mShouldInitChecked) {
+    InitChecked();
+    mShouldInitChecked = false;
+  }
+}
+
+nsresult
+nsHTMLMenuItemElement::AfterSetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
+                                    const nsAString* aValue, PRBool aNotify)
+{
+  if (aNameSpaceID == kNameSpaceID_None) {
+    if ((aName == nsGkAtoms::radiogroup || aName == nsGkAtoms::type) &&
+        mType == CMD_TYPE_RADIO &&
+        !mParserCreating) {
+      if (IsInDoc() && GetParent()) {
+        AddedToRadioGroup();
+      }
+    }
+
+    // Checked must be set no matter what type of menuitem it is, since
+    // GetChecked() must reflect the new value
+    if (aName == nsGkAtoms::checked &&
+        !mCheckedDirty) {
+      if (mParserCreating) {
+        mShouldInitChecked = true;
+      } else {
+        InitChecked();
+      }
+    }
+  }
+
+  return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName, aValue,
+                                            aNotify);
+}
+
+void
+nsHTMLMenuItemElement::WalkRadioGroup(Visitor* aVisitor)
+{
+  nsIContent* parent = GetParent();
+  if (!parent) {
+    aVisitor->Visit(this);
+    return;
+  }
+
+  nsAttrInfo info1(GetAttrInfo(kNameSpaceID_None,
+                               nsGkAtoms::radiogroup));
+  PRBool info1Empty = !info1.mValue || info1.mValue->IsEmptyString();
+
+  for (nsIContent* cur = parent->GetFirstChild();
+       cur;
+       cur = cur->GetNextSibling()) {
+    nsHTMLMenuItemElement* menuitem = nsHTMLMenuItemElement::FromContent(cur);
+
+    if (!menuitem || menuitem->GetType() != CMD_TYPE_RADIO) {
+      continue;
+    }
+
+    nsAttrInfo info2(menuitem->GetAttrInfo(kNameSpaceID_None,
+                                           nsGkAtoms::radiogroup));
+    PRBool info2Empty = !info2.mValue || info2.mValue->IsEmptyString();
+
+    if (info1Empty != info2Empty ||
+        info1.mValue && info2.mValue && !info1.mValue->Equals(*info2.mValue)) {
+      continue;
+    }
+
+    if (!aVisitor->Visit(menuitem)) {
+      break;
+    }
+  }
+}
+
+nsHTMLMenuItemElement*
+nsHTMLMenuItemElement::GetSelectedRadio()
+{
+  nsHTMLMenuItemElement* result = nsnull;
+
+  GetCheckedVisitor visitor(&result);
+  WalkRadioGroup(&visitor);
+
+  return result;
+}
+
+void
+nsHTMLMenuItemElement::AddedToRadioGroup()
+{
+  PRBool checkedDirty = mCheckedDirty;
+  if (mChecked) {
+    ClearCheckedVisitor visitor1(this);
+    GetCheckedDirtyVisitor visitor2(&checkedDirty, this);
+    CombinedVisitor visitor(&visitor1, &visitor2);
+    WalkRadioGroup(&visitor);
+  } else {
+    GetCheckedDirtyVisitor visitor(&checkedDirty, this);
+    WalkRadioGroup(&visitor);
+  }
+  mCheckedDirty = checkedDirty;
+}
+
+void
+nsHTMLMenuItemElement::InitChecked()
+{
+  PRBool defaultChecked;
+  GetDefaultChecked(&defaultChecked);
+  mChecked = defaultChecked;
+  if (mType == CMD_TYPE_RADIO) {
+    ClearCheckedVisitor visitor(this);
+    WalkRadioGroup(&visitor);
+  }
+}
new file mode 100644
--- /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<nsINodeInfo> aNodeInfo,
+                        mozilla::dom::FromParser aFromParser);
+  virtual ~nsHTMLMenuItemElement();
+
+  /** Typesafe, non-refcounting cast from nsIContent.  Cheaper than QI. **/
+  static nsHTMLMenuItemElement* FromContent(nsIContent* aContent)
+  {
+    if (aContent && aContent->IsHTML(nsGkAtoms::menuitem)) {
+      return static_cast<nsHTMLMenuItemElement*>(aContent);
+    }
+    return nsnull;
+  }
+
+  // nsISupports
+  NS_DECL_ISUPPORTS_INHERITED
+
+  // nsIDOMNode
+  NS_FORWARD_NSIDOMNODE(nsGenericHTMLElement::)
+
+  // nsIDOMElement
+  NS_FORWARD_NSIDOMELEMENT(nsGenericHTMLElement::)
+
+  // nsIDOMHTMLElement
+  NS_FORWARD_NSIDOMHTMLELEMENT(nsGenericHTMLElement::)
+
+  // nsIDOMHTMLCommandElement
+  NS_DECL_NSIDOMHTMLCOMMANDELEMENT
+
+  // nsIDOMHTMLMenuItemElement
+  NS_DECL_NSIDOMHTMLMENUITEMELEMENT
+
+  virtual nsresult PreHandleEvent(nsEventChainPreVisitor& aVisitor);
+  virtual nsresult PostHandleEvent(nsEventChainPostVisitor& aVisitor);
+
+  virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+                              nsIContent* aBindingParent,
+                              PRBool aCompileEventHandlers);
+
+  virtual PRBool ParseAttribute(PRInt32 aNamespaceID,
+                                nsIAtom* aAttribute,
+                                const nsAString& aValue,
+                                nsAttrValue& aResult);
+
+  virtual void DoneCreatingElement();
+
+  virtual nsresult Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const;
+
+  virtual nsXPCClassInfo* GetClassInfo();
+
+  PRUint8 GetType() const { return mType; }
+
+  /**
+   * Syntax sugar to make it easier to check for checked and checked dirty
+   */
+  PRBool IsChecked() const { return mChecked; }
+  PRBool IsCheckedDirty() const { return mCheckedDirty; }
+
+protected:
+  virtual nsresult AfterSetAttr(PRInt32 aNamespaceID, nsIAtom* aName,
+                                const nsAString* aValue, PRBool aNotify);
+
+  void WalkRadioGroup(Visitor* aVisitor);
+
+  nsHTMLMenuItemElement* GetSelectedRadio();
+
+  void AddedToRadioGroup();
+
+  void InitChecked();
+
+  friend class ClearCheckedVisitor;
+  friend class SetCheckedDirtyVisitor;
+
+  void ClearChecked() { mChecked = false; }
+  void SetCheckedDirty() { mCheckedDirty = true; }
+
+private:
+  PRUint8 mType : 2;
+  bool mParserCreating : 1;
+  bool mShouldInitChecked : 1;
+  bool mCheckedDirty : 1;
+  bool mChecked : 1;
+};
--- a/content/html/content/src/nsHTMLSharedElement.cpp
+++ b/content/html/content/src/nsHTMLSharedElement.cpp
@@ -33,17 +33,16 @@
  * 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 "nsIDOMHTMLParamElement.h"
 #include "nsIDOMHTMLBaseElement.h"
 #include "nsIDOMHTMLDirectoryElement.h"
-#include "nsIDOMHTMLMenuElement.h"
 #include "nsIDOMHTMLQuoteElement.h"
 #include "nsIDOMHTMLHeadElement.h"
 #include "nsIDOMHTMLHtmlElement.h"
 #include "nsGenericHTMLElement.h"
 #include "nsGkAtoms.h"
 #include "nsStyleConsts.h"
 #include "nsRuleData.h"
 #include "nsMappedAttributes.h"
@@ -54,17 +53,16 @@
 
 // XXX nav4 has type= start= (same as OL/UL)
 extern nsAttrValue::EnumTable kListTypeTable[];
 
 class nsHTMLSharedElement : public nsGenericHTMLElement,
                             public nsIDOMHTMLParamElement,
                             public nsIDOMHTMLBaseElement,
                             public nsIDOMHTMLDirectoryElement,
-                            public nsIDOMHTMLMenuElement,
                             public nsIDOMHTMLQuoteElement,
                             public nsIDOMHTMLHeadElement,
                             public nsIDOMHTMLHtmlElement
 {
 public:
   nsHTMLSharedElement(already_AddRefed<nsINodeInfo> aNodeInfo);
   virtual ~nsHTMLSharedElement();
 
@@ -84,19 +82,16 @@ public:
   NS_DECL_NSIDOMHTMLPARAMELEMENT
 
   // nsIDOMHTMLBaseElement
   NS_DECL_NSIDOMHTMLBASEELEMENT
 
   // nsIDOMHTMLDirectoryElement
   NS_DECL_NSIDOMHTMLDIRECTORYELEMENT
 
-  // nsIDOMHTMLMenuElement
-  // Same as directoryelement
-
   // nsIDOMHTMLQuoteElement
   NS_DECL_NSIDOMHTMLQUOTEELEMENT
 
   // nsIDOMHTMLHeadElement
   NS_DECL_NSIDOMHTMLHEADELEMENT
 
   // nsIDOMHTMLHtmlElement
   NS_DECL_NSIDOMHTMLHTMLELEMENT
@@ -152,36 +147,32 @@ nsHTMLSharedElement::~nsHTMLSharedElemen
 
 NS_IMPL_ADDREF_INHERITED(nsHTMLSharedElement, nsGenericElement)
 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)
 
 nsIClassInfo*
 nsHTMLSharedElement::GetClassInfoInternal()
 {
   if (mNodeInfo->Equals(nsGkAtoms::param)) {
     return NS_GetDOMClassInfoInstance(eDOMClassInfo_HTMLParamElement_id);
   }
   if (mNodeInfo->Equals(nsGkAtoms::base)) {
     return NS_GetDOMClassInfoInstance(eDOMClassInfo_HTMLBaseElement_id);
   }
   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);
   }
   if (mNodeInfo->Equals(nsGkAtoms::blockquote)) {
     return NS_GetDOMClassInfoInstance(eDOMClassInfo_HTMLQuoteElement_id);
   }
   if (mNodeInfo->Equals(nsGkAtoms::head)) {
     return NS_GetDOMClassInfoInstance(eDOMClassInfo_HTMLHeadElement_id);
@@ -198,17 +189,16 @@ NS_INTERFACE_TABLE_HEAD(nsHTMLSharedElem
                                                   nsIDOMHTMLParamElement)
   NS_OFFSET_AND_INTERFACE_TABLE_END
   NS_HTML_CONTENT_INTERFACE_TABLE_TO_MAP_SEGUE_AMBIGUOUS(nsHTMLSharedElement,
                                                          nsGenericHTMLElement,
                                                          nsIDOMHTMLParamElement)
   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)
   NS_INTERFACE_MAP_ENTRY_IF_TAG(nsIDOMHTMLHtmlElement, html)
 
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO_GETTER(GetClassInfoInternal)
 NS_HTML_CONTENT_INTERFACE_MAP_END
 
@@ -219,19 +209,16 @@ NS_IMPL_ELEMENT_CLONE(nsHTMLSharedElemen
 NS_IMPL_STRING_ATTR(nsHTMLSharedElement, Name, name)
 NS_IMPL_STRING_ATTR(nsHTMLSharedElement, Type, type)
 NS_IMPL_STRING_ATTR(nsHTMLSharedElement, Value, value)
 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)
 
 // nsIDOMHTMLHeadElement
 // Empty
 
 // nsIDOMHTMLHtmlElement
 NS_IMPL_STRING_ATTR(nsHTMLSharedElement, Version, version)
@@ -270,32 +257,31 @@ nsHTMLSharedElement::SetHref(const nsASt
 
 PRBool
 nsHTMLSharedElement::ParseAttribute(PRInt32 aNamespaceID,
                                     nsIAtom* aAttribute,
                                     const nsAString& aValue,
                                     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);
     }
     if (aAttribute == nsGkAtoms::start) {
       return aResult.ParseIntWithBounds(aValue, 1);
     }
   }
 
   return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
                                               aResult);
 }
 
 static void
-DirectoryMenuMapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+DirectoryMapAttributesIntoRule(const nsMappedAttributes* aAttributes,
                                nsRuleData* aData)
 {
   if (aData->mSIDs & NS_STYLE_INHERIT_BIT(List)) {
     nsCSSValue* listStyleType = aData->ValueForListStyleType();
     if (listStyleType->GetUnit() == eCSSUnit_Null) {
       // type: enum
       const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::type);
       if (value) {
@@ -481,14 +467,14 @@ nsHTMLSharedElement::UnbindFromTree(PRBo
       SetBaseTargetUsingFirstBaseWithTarget(doc, nsnull);
     }
   }
 }
 
 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();
 }
--- a/content/html/content/test/Makefile.in
+++ b/content/html/content/test/Makefile.in
@@ -141,17 +141,16 @@ include $(topsrcdir)/config/rules.mk
 		test_bug406596.html \
 		test_bug408231.html \
 		test_bug417760.html \
 		file_bug417760.png \
 		test_formSubmission.html \
 		test_formSubmission2.html \
 		file_formSubmission_text.txt \
 		file_formSubmission_img.jpg \
-		test_bug418756.html \
 		test_bug421640.html \
 		test_bug424698.html \
 		test_bug428135.xhtml \
 		test_bug430351.html \
 		test_bug430392.html \
 		bug441930_iframe.html \
 		test_bug441930.html \
 		test_bug442801.html \
@@ -275,12 +274,14 @@ include $(topsrcdir)/config/rules.mk
 		test_bug659743.xml \
 		test_bug660663.html \
 		test_bug664299.html \
 		test_bug666200.html \
 		test_bug666666.html \
 		test_bug674558.html \
 		test_bug583533.html \
 		test_restore_from_parser_fragment.html \
+		test_bug617528.html \
+		test_checked.html \
 		$(NULL)
 
 libs:: $(_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/content/html/content/test/test_bug617528.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=617528
+-->
+<head>
+  <title>Test for Bug 617528</title>
+  <script type="application/javascript" src="/MochiKit/packed.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=617528">Mozilla Bug 617528</a>
+<p id="display"></p>
+<div id="content">
+  <menu>
+    <menuitem id="checkbox" type="checkbox" label="Checkbox" checked></menuitem>
+    <menuitem id="radio1" type="radio" label="Radio1" checked></menuitem>
+    <menuitem id="radio2" type="radio" label="Radio2"></menuitem>
+  </menu>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 617528 **/
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+  function click(element, preventDefault, checked) {
+    function handleClick(event) {
+      is(this.checked, checked,
+         "checking .checked (" + this.id + ")");
+      if (preventDefault)
+        event.preventDefault();
+    }
+    element.addEventListener("click", handleClick);
+    element.click();
+    element.removeEventListener("click", handleClick);
+  }
+
+  function verify(elements, data) {
+    for (var i = 0; i < elements.length; i++) {
+      var element = elements[i];
+      is(element.checked, data[i*2],
+         "checking .checked (" + element.id + ")");
+      is(element.defaultChecked, data[i*2+1],
+         'checking .defaultChecked (' + element.id + ")");
+    }
+  }
+
+  var checkbox = document.getElementById("checkbox");
+  click(checkbox, false, false);
+  verify([checkbox], [false, true]);
+
+  click(checkbox, true, true);
+  verify([checkbox], [false, true]);
+
+  var radio1 = document.getElementById("radio1");
+  var radio2 = document.getElementById("radio2");
+  click(radio2, false, true);
+  verify([radio1, radio2], [false, true,
+                            true, false]);
+
+  click(radio1, true, true);
+  verify([radio1, radio2], [false, true,
+                            true, false]);
+
+  SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
rename from content/html/content/test/test_bug418756.html
rename to content/html/content/test/test_checked.html
--- a/content/html/content/test/test_bug418756.html
+++ b/content/html/content/test/test_checked.html
@@ -1,32 +1,47 @@
 <!DOCTYPE HTML>
 <html>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=418756
+https://bugzilla.mozilla.org/show_bug.cgi?id=617528
 -->
 <head>
-  <title>Test for Bug 418756</title>
+  <title>Test for Bug 418756 and 617528</title>
   <script type="text/javascript" src="/MochiKit/packed.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=418756">Mozilla Bug 418756</a>
+Mozilla bug
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=418756">418756</a>
+and
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=617528">617528</a>
 <p id="display"></p>
 <div id="content">
   <form id="f1">
   </form>
   <form id="f2">
   </form>
+  <menu id="m1">
+  </menu>
+  <menu id="m2">
+  </menu>
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript; version=1.7">
 
-/** Test for Bug 418756 **/
+/** Test for Bug 418756 and 617528 **/
+let group1;
+let group2;
+let group3;
+
+let tags = ["input", "menuitem"];
+for each (let tag in tags) {
+
 function bounce(node) {
   let n = node.nextSibling;
   let p = node.parentNode;
   p.removeChild(node);
   p.insertBefore(node, n);
 }
 
 let createdNodes = [];
@@ -45,23 +60,23 @@ let typeMapper = {
  'c': 'checkbox',
  'r': 'radio'
 };
 
 let id = 0;
 
 // type can be 'c' for 'checkbox' and 'r' for 'radio'
 function createNode(type, name, checked) {
-  let node = document.createElement("input");
+  let node = document.createElement(tag);
   node.setAttribute("type",  typeMapper[type]);
   if (checked) {
     node.setAttribute("checked", "checked");
   }
   node.setAttribute("id", type + (++id));
-  node.setAttribute("name", name);
+  node.setAttribute(tag == "input" ? "name" : "radiogroup", name);
   createdNodes.push(node);
   return node;
 }
 
 let types = ['c', 'r'];
 
 // First make sure that setting .checked makes .defaultChecked changes no
 // longer affect .checked.
@@ -92,45 +107,45 @@ for each (let type in types) {
 }
 
 cleanup();
 
 // Now check that bouncing a control that's the only one of its kind has no
 // effect
 for each (let type in types) {
   let n = createNode(type, 'test1', true);
-  $("f1").appendChild(n);
+  $(tag == "input" ? "f1" : "m1").appendChild(n);
   n.checked = false;
   n.defaultChecked = false;
   bounce(n);
   n.defaultChecked = true;
   is(n.checked, false, "We set .checked on this " + typeMapper[type]);
 }
 
 cleanup();
 
 // Now check that playing with a single radio in a group affects all
 // other radios in the group (but not radios not in that group)
-let group1 = [ createNode('r', 'g1', false),
-               createNode('r', 'g1', false),
-               createNode('r', 'g1', false) ];
-let group2 = [ createNode('r', 'g2', false),
-               createNode('r', 'g2', false),
-               createNode('r', 'g2', false) ];
-let group3 = [ createNode('r', 'g1', false),
-               createNode('r', 'g1', false),
-               createNode('r', 'g1', false) ];
+group1 = [ createNode('r', 'g1', false),
+           createNode('r', 'g1', false),
+           createNode('r', 'g1', false) ];
+group2 = [ createNode('r', 'g2', false),
+           createNode('r', 'g2', false),
+           createNode('r', 'g2', false) ];
+group3 = [ createNode('r', 'g1', false),
+           createNode('r', 'g1', false),
+           createNode('r', 'g1', false) ];
 for each (let g in group1) {
-  $("f1").appendChild(g);
+  $(tag == "input" ? "f1" : "m1").appendChild(g);
 }
 for each (let g in group2) {
-  $("f1").appendChild(g);
+  $(tag == "input" ? "f1" : "m1").appendChild(g);
 }
 for each (let g in group3) {
-  $("f2").appendChild(g);
+  $(tag == "input" ? "f2" : "m2").appendChild(g);
 }
 
 for each (let n in [1, 2, 3]) {
   for each (let g in window["group"+n]) {
     is(g.defaultChecked, false,
        "group" + n + "[" + window["group"+n].indexOf(g) +
        "] defaultChecked wrong pass 1");
     is(g.checked, false,
@@ -330,13 +345,15 @@ for each (let n in [1, 2, 3]) {
                               group1.indexOf(g) == 0)) ||
                   (n == 2 && group2.indexOf(g) == 0), 
        "group" + n + "[" + window["group"+n].indexOf(g) +
        "] checked wrong pass 15");
   }
 }
 
 cleanup();
+
+}
 </script>
 </pre>
 </body>
 </html>
 
--- a/content/html/document/src/nsHTMLContentSink.cpp
+++ b/content/html/document/src/nsHTMLContentSink.cpp
@@ -1016,17 +1016,20 @@ SinkContext::AddLeaf(const nsIParserNode
         // Bug 40072: Don't evaluate METAs after FRAMESET.
         if (!mSink->mInsideNoXXXTag && !mSink->mFrameset) {
           rv = mSink->ProcessMETATag(content);
         }
         break;
 
       case eHTMLTag_input:
         content->DoneCreatingElement();
-
+        break;
+
+      case eHTMLTag_menuitem:
+        content->DoneCreatingElement();
         break;
 
       default:
         break;
       }
     }
     break;
 
--- a/content/xml/document/src/nsXMLContentSink.cpp
+++ b/content/xml/document/src/nsXMLContentSink.cpp
@@ -1065,17 +1065,18 @@ nsXMLContentSink::HandleStartElement(con
       parent->AppendChildTo(content, PR_FALSE);
     }
   }
 
   // Some HTML nodes need DoneCreatingElement() called to initialize
   // 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;
     }
   }
 
   if (IsMonolithicContainer(nodeInfo)) {
     mInMonolithicContainer++;
--- a/content/xslt/src/xslt/txMozillaXMLOutput.cpp
+++ b/content/xslt/src/xslt/txMozillaXMLOutput.cpp
@@ -338,17 +338,18 @@ txMozillaXMLOutput::endElement()
             // Else, add this script element to the array of loading scripts.
             if (rv == NS_ERROR_HTMLPARSER_BLOCK) {
                 nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(element);
                 rv = mNotifier->AddScriptElement(sele);
                 NS_ENSURE_SUCCESS(rv, rv);
             }
         } else if (ns == kNameSpaceID_XHTML &&
                    (localName == nsGkAtoms::input ||
-                    localName == nsGkAtoms::button)) {
+                    localName == nsGkAtoms::button ||
+                    localName == nsGkAtoms::menuitem)) {
           element->DoneCreatingElement();
         }
     }
 
     if (mCreatingNewDocument) {
         // Handle all sorts of stylesheets
         nsCOMPtr<nsIStyleSheetLinkingElement> ssle =
             do_QueryInterface(mCurrentNode);
--- a/content/xul/content/Makefile.in
+++ b/content/xul/content/Makefile.in
@@ -38,16 +38,16 @@
 DEPTH		= ../../..
 topsrcdir	= @top_srcdir@
 srcdir		= @srcdir@
 VPATH		= @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 MODULE		= xul
-PARALLEL_DIRS	= src
+PARALLEL_DIRS	= public src
 
 ifdef ENABLE_TESTS
 PARALLEL_DIRS	+= test
 endif
 
 include $(topsrcdir)/config/rules.mk
 
new file mode 100644
--- /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
new file mode 100644
--- /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);
+
+};
--- a/content/xul/content/src/Makefile.in
+++ b/content/xul/content/src/Makefile.in
@@ -49,16 +49,17 @@ LIBRARY_NAME	= gkconxulcon_s
 LIBXUL_LIBRARY	= 1
 endif
 
 
 ifdef MOZ_XUL
 CPPSRCS		+= \
 		nsXULElement.cpp \
 		nsXULPopupListener.cpp \
+		nsXULContextMenuBuilder.cpp \
 		$(NULL)
 endif
 
 # we don't want the shared lib, but we want to force the creation of a
 # static lib.
 FORCE_STATIC_LIB = 1
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /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<nsIContent> menu;
+    nsresult rv = CreateElement(nsGkAtoms::menu, getter_AddRefs(menu));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    menu->SetAttr(kNameSpaceID_None, nsGkAtoms::label, aLabel, PR_FALSE);
+
+    nsCOMPtr<nsIContent> menuPopup;
+    rv = CreateElement(nsGkAtoms::menupopup, getter_AddRefs(menuPopup));
+    NS_ENSURE_SUCCESS(rv, rv);
+        
+    rv = menu->AppendChildTo(menuPopup, PR_FALSE);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = mCurrentNode->AppendChildTo(menu, PR_FALSE);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    mCurrentNode = menuPopup;
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULContextMenuBuilder::AddItemFor(nsIDOMHTMLMenuItemElement* aElement,
+                                    PRBool aCanLoadIcon)
+{
+  if (!mFragment) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
+  nsCOMPtr<nsIContent> menuitem;
+  nsresult rv = CreateElement(nsGkAtoms::menuitem, getter_AddRefs(menuitem));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsAutoString type;
+  aElement->GetType(type);
+  if (type.EqualsLiteral("checkbox") || type.EqualsLiteral("radio")) {
+    // The menu is only temporary, so we don't need to handle
+    // the radio type precisely.
+    menuitem->SetAttr(kNameSpaceID_None, nsGkAtoms::type,
+                      NS_LITERAL_STRING("checkbox"), PR_FALSE);
+    PRBool checked;
+    aElement->GetChecked(&checked);
+    if (checked) {
+      menuitem->SetAttr(kNameSpaceID_None, nsGkAtoms::checked,
+                        NS_LITERAL_STRING("true"), PR_FALSE);
+    }
+  }
+
+  nsAutoString label;
+  aElement->GetLabel(label);
+  menuitem->SetAttr(kNameSpaceID_None, nsGkAtoms::label, label, PR_FALSE);
+
+  nsAutoString icon;
+  aElement->GetIcon(icon);
+  if (!icon.IsEmpty()) {
+    menuitem->SetAttr(kNameSpaceID_None, nsGkAtoms::_class,
+                      NS_LITERAL_STRING("menuitem-iconic"), PR_FALSE);
+    if (aCanLoadIcon) {
+      menuitem->SetAttr(kNameSpaceID_None, nsGkAtoms::image, icon, PR_FALSE);
+    }
+  }
+
+  PRBool disabled;
+  aElement->GetDisabled(&disabled);
+  if (disabled) {
+    menuitem->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled,
+                      NS_LITERAL_STRING("true"), PR_FALSE);
+  }
+
+  nsAutoString ident;
+  ident.AppendInt(mCurrentIdent++);
+
+  menuitem->SetAttr(kNameSpaceID_None, mIdentAttr, ident, PR_FALSE);
+
+  rv = mCurrentNode->AppendChildTo(menuitem, PR_FALSE);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mElements.AppendObject(aElement);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULContextMenuBuilder::AddSeparator()
+{
+  if (!mFragment) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
+  nsCOMPtr<nsIContent> menuseparator;
+  nsresult rv = CreateElement(nsGkAtoms::menuseparator,
+                              getter_AddRefs(menuseparator));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return mCurrentNode->AppendChildTo(menuseparator, PR_FALSE);
+}
+
+NS_IMETHODIMP
+nsXULContextMenuBuilder::UndoAddSeparator()
+{
+  if (!mFragment) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
+  PRUint32 count = mCurrentNode->GetChildCount();
+  if (!count ||
+      mCurrentNode->GetChildAt(count - 1)->Tag() != nsGkAtoms::menuseparator) {
+    return NS_OK;
+  }
+
+  return mCurrentNode->RemoveChildAt(count - 1, PR_FALSE);
+}
+
+NS_IMETHODIMP
+nsXULContextMenuBuilder::CloseContainer()
+{
+  if (!mFragment) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
+  if (mCurrentNode == mFragment) {
+    mCurrentNode = nsnull;
+  } else {
+    nsIContent* parent = mCurrentNode->GetParent();
+    mCurrentNode = parent->GetParent();
+  }
+
+  return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsXULContextMenuBuilder::Init(nsIDOMDocumentFragment* aDocumentFragment,
+                              const nsAString& aGeneratedAttrName,
+                              const nsAString& aIdentAttrName)
+{
+  NS_ENSURE_ARG_POINTER(aDocumentFragment);
+
+  mFragment = do_QueryInterface(aDocumentFragment);
+  mDocument = mFragment->GetOwnerDocument();
+  mGeneratedAttr = do_GetAtom(aGeneratedAttrName);
+  mIdentAttr = do_GetAtom(aIdentAttrName);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULContextMenuBuilder::Click(const nsAString& aIdent)
+{
+  PRInt32 rv;
+  PRInt32 idx = nsString(aIdent).ToInteger(&rv);
+  if (NS_SUCCEEDED(rv)) {
+    nsCOMPtr<nsIDOMHTMLElement> element = mElements.SafeObjectAt(idx);
+    if (element) {
+      element->Click();
+    }
+  }
+
+  return NS_OK;
+}
+
+nsresult
+nsXULContextMenuBuilder::CreateElement(nsIAtom* aTag, nsIContent** aResult)
+{
+  *aResult = nsnull;
+
+  nsCOMPtr<nsINodeInfo> nodeInfo = mDocument->NodeInfoManager()->GetNodeInfo(
+    aTag, nsnull, kNameSpaceID_XUL, nsIDOMNode::ELEMENT_NODE);
+  NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
+
+  nsresult rv = NS_NewElement(aResult, kNameSpaceID_XUL, nodeInfo.forget(),
+                              mozilla::dom::NOT_FROM_PARSER);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  (*aResult)->SetAttr(kNameSpaceID_None, mGeneratedAttr, EmptyString(),
+                      PR_FALSE);
+
+  return NS_OK;
+}
new file mode 100644
--- /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<nsIContent>          mFragment;
+  nsCOMPtr<nsIDocument>         mDocument;
+  nsCOMPtr<nsIAtom>             mGeneratedAttr;
+  nsCOMPtr<nsIAtom>             mIdentAttr;
+
+  nsCOMPtr<nsIContent>          mCurrentNode;
+  PRInt32                       mCurrentIdent;
+
+  nsCOMArray<nsIDOMHTMLElement> mElements;
+};
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -280,16 +280,17 @@
 #include "nsIDOMHTMLImageElement.h"
 #include "nsIDOMHTMLInputElement.h"
 #include "nsIDOMHTMLLIElement.h"
 #include "nsIDOMHTMLLabelElement.h"
 #include "nsIDOMHTMLLegendElement.h"
 #include "nsIDOMHTMLLinkElement.h"
 #include "nsIDOMHTMLMapElement.h"
 #include "nsIDOMHTMLMenuElement.h"
+#include "nsIDOMHTMLMenuItemElement.h"
 #include "nsIDOMHTMLMetaElement.h"
 #include "nsIDOMHTMLModElement.h"
 #include "nsIDOMHTMLOListElement.h"
 #include "nsIDOMHTMLObjectElement.h"
 #include "nsIDOMHTMLOptGroupElement.h"
 #include "nsIDOMHTMLOutputElement.h"
 #include "nsIDOMHTMLParagraphElement.h"
 #include "nsIDOMHTMLParamElement.h"
@@ -831,16 +832,18 @@ static nsDOMClassInfoData sClassInfoData
   NS_DEFINE_CLASSINFO_DATA(HTMLLegendElement, nsElementSH,
                            ELEMENT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(HTMLLinkElement, nsElementSH,
                            ELEMENT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(HTMLMapElement, nsElementSH,
                            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,
                            ELEMENT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(HTMLOListElement, nsElementSH,
                            ELEMENT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(HTMLObjectElement, nsHTMLPluginObjElementSH,
                            EXTERNAL_OBJ_SCRIPTABLE_FLAGS)
@@ -2690,16 +2693,21 @@ nsDOMClassInfo::Init()
     DOM_CLASSINFO_GENERIC_HTML_MAP_ENTRIES
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(HTMLMenuElement, nsIDOMHTMLMenuElement)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMHTMLMenuElement)
     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
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(HTMLModElement, nsIDOMHTMLModElement)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMHTMLModElement)
     DOM_CLASSINFO_GENERIC_HTML_MAP_ENTRIES
@@ -7488,16 +7496,17 @@ nsEventReceiverSH::ReallyIsEventName(jsi
             id == sOnprogress_id);
   case 'r' :
     return (id == sOnreadystatechange_id ||
             id == sOnreset_id            ||
             id == sOnresize_id           ||
             id == sOnratechange_id);
   case 's' :
     return (id == sOnscroll_id       ||
+            id == sOnshow_id         ||
             id == sOnselect_id       ||
             id == sOnsubmit_id       || 
             id == sOnseeked_id       ||
             id == sOnseeking_id      ||
             id == sOnstalled_id      ||
             id == sOnsuspend_id);
   case 't':
     return id == sOntimeupdate_id ||
--- a/dom/base/nsDOMClassInfoClasses.h
+++ b/dom/base/nsDOMClassInfoClasses.h
@@ -115,16 +115,17 @@ DOMCI_CLASS(HTMLIFrameElement)
 DOMCI_CLASS(HTMLImageElement)
 DOMCI_CLASS(HTMLInputElement)
 DOMCI_CLASS(HTMLLIElement)
 DOMCI_CLASS(HTMLLabelElement)
 DOMCI_CLASS(HTMLLegendElement)
 DOMCI_CLASS(HTMLLinkElement)
 DOMCI_CLASS(HTMLMapElement)
 DOMCI_CLASS(HTMLMenuElement)
+DOMCI_CLASS(HTMLMenuItemElement)
 DOMCI_CLASS(HTMLMetaElement)
 DOMCI_CLASS(HTMLModElement)
 DOMCI_CLASS(HTMLOListElement)
 DOMCI_CLASS(HTMLObjectElement)
 DOMCI_CLASS(HTMLOptGroupElement)
 DOMCI_CLASS(HTMLOptionElement)
 DOMCI_CLASS(HTMLOutputElement)
 DOMCI_CLASS(HTMLParagraphElement)
--- a/dom/interfaces/html/Makefile.in
+++ b/dom/interfaces/html/Makefile.in
@@ -50,16 +50,17 @@ SDK_XPIDLSRCS =					\
 	nsIDOMHTMLAnchorElement.idl		\
 	nsIDOMHTMLAppletElement.idl		\
 	nsIDOMHTMLAreaElement.idl		\
 	nsIDOMHTMLBRElement.idl			\
 	nsIDOMHTMLBaseElement.idl		\
 	nsIDOMHTMLBodyElement.idl		\
 	nsIDOMHTMLButtonElement.idl		\
 	nsIDOMHTMLCollection.idl		\
+	nsIDOMHTMLCommandElement.idl		\
 	nsIDOMHTMLDataListElement.idl		\
 	nsIDOMHTMLDListElement.idl		\
 	nsIDOMHTMLDirectoryElement.idl		\
 	nsIDOMHTMLDivElement.idl		\
 	nsIDOMHTMLDocument.idl			\
 	nsIDOMHTMLElement.idl			\
 	nsIDOMHTMLEmbedElement.idl		\
 	nsIDOMHTMLFieldSetElement.idl		\
@@ -75,16 +76,17 @@ SDK_XPIDLSRCS =					\
 	nsIDOMHTMLImageElement.idl		\
 	nsIDOMHTMLInputElement.idl		\
 	nsIDOMHTMLLIElement.idl			\
 	nsIDOMHTMLLabelElement.idl		\
 	nsIDOMHTMLLegendElement.idl		\
 	nsIDOMHTMLLinkElement.idl		\
 	nsIDOMHTMLMapElement.idl		\
 	nsIDOMHTMLMenuElement.idl		\
+	nsIDOMHTMLMenuItemElement.idl		\
 	nsIDOMHTMLMetaElement.idl		\
 	nsIDOMHTMLModElement.idl		\
 	nsIDOMHTMLOListElement.idl		\
 	nsIDOMHTMLObjectElement.idl		\
 	nsIDOMHTMLOptGroupElement.idl		\
 	nsIDOMHTMLOptionElement.idl		\
 	nsIDOMHTMLOptionsCollection.idl		\
 	nsIDOMHTMLOutputElement.idl		\
new file mode 100644
--- /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
+ * <command> element.
+ *
+ * For more information on this interface, please see
+ * http://www.whatwg.org/specs/web-apps/current-work/#the-command-element
+ *
+ * @status UNDER_DEVELOPMENT
+ */
+
+[scriptable, uuid(df4a19b4-81f1-412e-a971-fcbe7312a9b6)]
+interface nsIDOMHTMLCommandElement : nsIDOMHTMLElement
+{
+           attribute DOMString        type;
+           attribute DOMString        label;
+           attribute DOMString        icon;
+           attribute boolean          disabled;
+           attribute boolean          defaultChecked;
+           attribute boolean          checked;
+           attribute DOMString        radiogroup;
+};
--- a/dom/interfaces/html/nsIDOMHTMLMenuElement.idl
+++ b/dom/interfaces/html/nsIDOMHTMLMenuElement.idl
@@ -45,13 +45,16 @@
  *
  * This interface is trying to follow the DOM Level 2 HTML specification:
  * http://www.w3.org/TR/DOM-Level-2-HTML/
  *
  * with changes from the work-in-progress WHATWG HTML specification:
  * 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;
 };
new file mode 100644
--- /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
+ * <menuitem> element.
+ *
+ * @status UNDER_DEVELOPMENT
+ */
+
+[scriptable, uuid(613f28ee-01f5-42dc-8224-161f85f0f20b)]
+interface nsIDOMHTMLMenuItemElement : nsIDOMHTMLCommandElement
+{
+};
--- a/dom/interfaces/html/nsIDOMNSHTMLElement.idl
+++ b/dom/interfaces/html/nsIDOMNSHTMLElement.idl
@@ -34,18 +34,19 @@
  * 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 "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;
   readonly attribute long             offsetLeft;
   readonly attribute long             offsetWidth;
   readonly attribute long             offsetHeight;
   readonly attribute nsIDOMElement    offsetParent;
            attribute DOMString        innerHTML;
@@ -66,12 +67,13 @@ interface nsIDOMNSHTMLElement : nsISuppo
            attribute boolean          draggable;
 
   void insertAdjacentHTML(in DOMString position,
                           in DOMString text)
                           raises(DOMException);
 
   [optional_argc] void scrollIntoView([optional] in boolean top);
 
+  readonly attribute nsIDOMHTMLMenuElement contextMenu;
            attribute boolean         spellcheck;
-           
+
   readonly attribute nsIDOMDOMStringMap dataset;
 };
--- a/editor/libeditor/base/nsEditPropertyAtomList.h
+++ b/editor/libeditor/base/nsEditPropertyAtomList.h
@@ -142,16 +142,17 @@ EDITOR_ATOM(img, "img")
 EDITOR_ATOM(input, "input")
 EDITOR_ATOM(kbd, "kbd")
 EDITOR_ATOM(keygen, "keygen")
 EDITOR_ATOM(label, "label")
 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")
 EDITOR_ATOM(nav, "nav")
 EDITOR_ATOM(noscript, "noscript")
 EDITOR_ATOM(object, "object")
 EDITOR_ATOM(ol, "ol")
 EDITOR_ATOM(output, "output")
--- a/editor/libeditor/html/nsHTMLEditUtils.cpp
+++ b/editor/libeditor/html/nsHTMLEditUtils.cpp
@@ -657,17 +657,18 @@ static const nsElementInfo kElements[eHT
   ELEM(label, PR_TRUE, PR_FALSE, GROUP_FORMCONTROL, GROUP_INLINE_ELEMENT),
   ELEM(legend, PR_TRUE, PR_TRUE, GROUP_NONE, GROUP_INLINE_ELEMENT),
   ELEM(li, PR_TRUE, PR_FALSE, GROUP_LI, GROUP_FLOW_ELEMENT),
   ELEM(link, PR_FALSE, PR_FALSE, GROUP_HEAD_CONTENT, GROUP_NONE),
   ELEM(listing, PR_FALSE, PR_FALSE, GROUP_NONE, GROUP_NONE),
   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),
   ELEM(nobr, PR_FALSE, PR_FALSE, GROUP_NONE, GROUP_NONE),
   ELEM(noembed, PR_FALSE, PR_FALSE, GROUP_NONE, GROUP_NONE),
   ELEM(noframes, PR_TRUE, PR_TRUE, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
   ELEM(noscript, PR_TRUE, PR_TRUE, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
   ELEM(object, PR_TRUE, PR_TRUE, GROUP_SPECIAL | GROUP_BLOCK,
--- a/js/src/xpconnect/src/dom_quickstubs.qsconf
+++ b/js/src/xpconnect/src/dom_quickstubs.qsconf
@@ -208,16 +208,17 @@ members = [
     'nsIDOMHTMLButtonElement.form',
     'nsIDOMHTMLButtonElement.value',
     'nsIDOMHTMLButtonElement.disabled',
     'nsIDOMHTMLCollection.item',
     # This is shadowed by nsIDOMHTMLOptionsCollection.length,
     # 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',
     'nsIDOMHTMLDocument.links',
     'nsIDOMHTMLDocument.URL',
     'nsIDOMHTMLDocument.forms',
     'nsIDOMHTMLDocument.cookie',
     'nsIDOMHTMLDocument.images',
@@ -256,16 +257,18 @@ members = [
     'nsIDOMHTMLInputElement.value',
     'nsIDOMHTMLInputElement.files',
     'nsIDOMHTMLInputElement.textLength',
     'nsIDOMHTMLInputElement.selectionStart',
     'nsIDOMHTMLInputElement.selectionEnd',
     'nsIDOMHTMLInputElement.selectionDirection',
     'nsIDOMHTMLInputElement.setSelectionRange',
     'nsIDOMHTMLLinkElement.disabled',
+    'nsIDOMHTMLMenuElement.*',
+    'nsIDOMHTMLMenuItemElement.*',
     'nsIDOMHTMLOptionElement.index',
     'nsIDOMHTMLOptionElement.selected',
     'nsIDOMHTMLOptionElement.form',
     'nsIDOMHTMLOptionElement.text',
     'nsIDOMHTMLOptionElement.defaultSelected',
     'nsIDOMHTMLOptionElement.value',
     'nsIDOMHTMLOptionElement.label',
     'nsIDOMHTMLOptionElement.disabled',
--- a/layout/style/html.css
+++ b/layout/style/html.css
@@ -568,16 +568,20 @@ abbr[title], acronym[title] {
 
 ul, menu, dir {
   display: block;
   list-style-type: disc;
   margin: 1em 0;
   -moz-padding-start: 40px;
 }
 
+menu[type="context"] {
+  display: none !important;
+}
+
 ol {
   display: block;
   list-style-type: decimal;
   margin: 1em 0;
   -moz-padding-start: 40px;
 }
 
 li {
--- a/mobile/installer/package-manifest.in
+++ b/mobile/installer/package-manifest.in
@@ -264,16 +264,17 @@
 @BINPATH@/components/xpcom_system.xpt
 @BINPATH@/components/xpcom_components.xpt
 @BINPATH@/components/xpcom_ds.xpt
 @BINPATH@/components/xpcom_io.xpt
 @BINPATH@/components/xpcom_threads.xpt
 @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
 @BINPATH@/components/webapps.xpt
 
 ; JavaScript components
 @BINPATH@/components/ConsoleAPI.manifest
 @BINPATH@/components/ConsoleAPI.js
--- a/parser/html/nsHtml5TreeBuilderCppSupplement.h
+++ b/parser/html/nsHtml5TreeBuilderCppSupplement.h
@@ -539,17 +539,18 @@ nsHtml5TreeBuilder::elementPopped(PRInt3
       treeOp->Init(eTreeOpFlushPendingAppendNotifications);
     }
     nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
     NS_ASSERTION(treeOp, "Tree op allocation failed.");
     treeOp->Init(eTreeOpDoneAddingChildren, aElement);
     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 
       // point. See bug 497861.
       nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
       NS_ASSERTION(treeOp, "Tree op allocation failed.");
       treeOp->Init(eTreeOpFlushPendingAppendNotifications);
     }
--- a/parser/htmlparser/public/nsHTMLTagList.h
+++ b/parser/htmlparser/public/nsHTMLTagList.h
@@ -134,17 +134,18 @@ HTML_TAG(keygen, Span)
 HTML_TAG(label, Label)
 HTML_TAG(legend, Legend)
 HTML_TAG(li, LI)
 HTML_TAG(link, Link)
 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)
 HTML_HTMLELEMENT_TAG(nobr)
 HTML_HTMLELEMENT_TAG(noembed)
 HTML_HTMLELEMENT_TAG(noframes)
 HTML_HTMLELEMENT_TAG(noscript)
 HTML_TAG(object, Object)
--- a/parser/htmlparser/src/nsElementTable.cpp
+++ b/parser/htmlparser/src/nsElementTable.cpp
@@ -852,16 +852,25 @@ const nsHTMLElement gHTMLElements[] = {
     /*req-parent excl-parent*/          eHTMLTag_unknown,eHTMLTag_unknown,
     /*rootnodes,endrootnodes*/          &gRootTags,&gRootTags,
     /*autoclose starttags and endtags*/ 0,0,0,0,
     /*parent,incl,exclgroups*/          kList, (kSelf|kFlowEntity), kNone,
     /*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,
     /*rootnodes,endrootnodes*/          &gInHead,&gInHead,
     /*autoclose starttags and endtags*/ 0,0,0,0,
     /*parent,incl,exclgroups*/          kHeadContent, kNone, kNone,
     /*special props, prop-range*/       kNoStyleLeaksIn|kNonContainer, kDefaultPropRange,
     /*special parents,kids*/            &gInHead,0,
   },
--- a/parser/htmlparser/src/nsHTMLTags.cpp
+++ b/parser/htmlparser/src/nsHTMLTags.cpp
@@ -190,16 +190,18 @@ static const PRUnichar sHTMLTagUnicodeNa
 static const PRUnichar sHTMLTagUnicodeName_map[] =
   {'m', 'a', 'p', '\0'};
 static const PRUnichar sHTMLTagUnicodeName_mark[] =
   {'m', 'a', 'r', 'k', '\0'};
 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[] =
   {'m', 'u', 'l', 't', 'i', 'c', 'o', 'l', '\0'};
 static const PRUnichar sHTMLTagUnicodeName_nav[] =
   {'n', 'a', 'v', '\0'};
 static const PRUnichar sHTMLTagUnicodeName_nobr[] =
   {'n', 'o', 'b', 'r', '\0'};
--- a/toolkit/content/Makefile.in
+++ b/toolkit/content/Makefile.in
@@ -88,11 +88,12 @@ EXTRA_JS_MODULES = \
   Dict.jsm \
   $(NULL)
 
 EXTRA_PP_JS_MODULES = \
   debug.js \
   LightweightThemeConsumer.jsm \
   Services.jsm \
   WindowDraggingUtils.jsm \
+  PageMenu.jsm \
   $(NULL)
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /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);
+      }
+    }
+  }
+}
--- a/widget/public/nsGUIEvent.h
+++ b/widget/public/nsGUIEvent.h
@@ -531,16 +531,18 @@ class nsHashKey;
 #define NS_OPEN                      (NS_OPENCLOSE_EVENT_START)
 #define NS_CLOSE                     (NS_OPENCLOSE_EVENT_START+1)
 
 // Device motion and orientation
 #define NS_DEVICE_ORIENTATION_START  4900
 #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.
  */
 
 /**
  * different types of (top-level) window z-level positioning
  */