Bug 617528 Part 2 - Core implementation r=smaug
authorJan Varga <jan.varga@gmail.com>
Mon, 08 Aug 2011 19:31:32 +0200
changeset 74062 561821863607ac216fbf273475dfde8d57fa7f2d
parent 74032 36989c74b287e7f7d132e3ecb80c02b54a88c6bd
child 74063 95feef73a8bac20dfd25cdc59e364d337e0ebb38
push id1072
push userCallek@gmail.com
push dateTue, 09 Aug 2011 10:30:47 +0000
treeherdermozilla-inbound@cee834f57d42 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs617528
milestone8.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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
  */