A followup for bug 617528 - implement the HTML5 "context menu" feature (contextmenu attribute). Merged "generated" and "ident" XUL attribute into "generateditemid" and renamed PageMenu.init() to PageMenu.maybeBuildAndAttachMenu(). r=enn
authorJan Varga <jan.varga@gmail.com>
Thu, 18 Aug 2011 18:37:26 +0200
changeset 75469 cbb901789b3b7b301271540daeaf68c9bc10d9d1
parent 75468 b904a9d949d6e0990a6888b22d823839f34b456f
child 75477 932c8414512f32c41e0d3eb9c9e483658ed7ab27
push id2
push userbsmedberg@mozilla.com
push dateFri, 19 Aug 2011 14:38:13 +0000
reviewersenn
bugs617528
milestone9.0a1
A followup for bug 617528 - implement the HTML5 "context menu" feature (contextmenu attribute). Merged "generated" and "ident" XUL attribute into "generateditemid" and renamed PageMenu.init() to PageMenu.maybeBuildAndAttachMenu(). r=enn
browser/base/content/nsContextMenu.js
browser/base/content/test/test_contextmenu.html
content/xul/content/public/nsIXULContextMenuBuilder.idl
content/xul/content/src/nsXULContextMenuBuilder.cpp
content/xul/content/src/nsXULContextMenuBuilder.h
toolkit/content/PageMenu.jsm
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -74,17 +74,18 @@ nsContextMenu.prototype = {
                    document.popupRangeOffset);
     if (!this.shouldDisplay)
       return;
 
     this.browser = aBrowser;
 
     this.hasPageMenu = false;
     if (!aIsShift) {
-      this.hasPageMenu = PageMenu.init(this.target, aXulMenu);
+      this.hasPageMenu = PageMenu.maybeBuildAndAttachMenu(this.target,
+                                                          aXulMenu);
     }
 
     this.isFrameImage = document.getElementById("isFrameImage");
     this.ellipsis = "\u2026";
     try {
       this.ellipsis = gPrefService.getComplexValue("intl.ellipsis",
                                                    Ci.nsIPrefLocalizedString).data;
     } catch (e) { }
--- a/browser/base/content/test/test_contextmenu.html
+++ b/browser/base/content/test/test_contextmenu.html
@@ -44,19 +44,20 @@ 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 invokeItemAction(ident)
+function invokeItemAction(generatedItemId)
 {
-  var item = contextMenu.getElementsByAttribute("ident", ident)[0];
+  var item = contextMenu.getElementsByAttribute("generateditemid",
+                                                generatedItemId)[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 = {};
@@ -64,17 +65,17 @@ function getVisibleMenuItems(aMenu, aDat
         var item = aMenu.childNodes[i];
         if (item.hidden)
             continue;
 
         var key = item.accessKey;
         if (key)
             key = key.toLowerCase();
 
-        var isGenerated = item.hasAttribute("generated");
+        var isGenerated = item.hasAttribute("generateditemid");
 
         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 {
--- a/content/xul/content/public/nsIXULContextMenuBuilder.idl
+++ b/content/xul/content/public/nsIXULContextMenuBuilder.idl
@@ -38,36 +38,32 @@
 
 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)]
+[scriptable, uuid(eb6b42c0-2f1c-4760-b5ca-bdc9b3ec77d4)]
 interface nsIXULContextMenuBuilder : nsISupports
 {
 
   /**
    * Initialize builder before building.
    *
    * @param aDocumentFragment the fragment that will be used to append top
    *        level elements
    *
-   * @param 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.
+   * @param aGeneratedItemIdAttrName the name of the attribute that will be
+   *        used to mark elements as generated and for menuitem identification
    */
   void init(in nsIDOMDocumentFragment aDocumentFragment,
-            in AString aGeneratedAttrName,
-            in AString aIdentAttrName);
+            in AString aGeneratedItemIdAttrName);
 
   /**
-   * Invoke the action of the menuitem with assigned identifier aIdent.
+   * Invoke the action of the menuitem with assigned id aGeneratedItemId.
    *
-   * @param aIdent the menuitem identifier
+   * @param aGeneratedItemId the menuitem id
    */
-  void click(in DOMString aIdent);
+  void click(in DOMString aGeneratedItemId);
 
 };
--- a/content/xul/content/src/nsXULContextMenuBuilder.cpp
+++ b/content/xul/content/src/nsXULContextMenuBuilder.cpp
@@ -36,17 +36,17 @@
 
 #include "nsContentCreatorFunctions.h"
 #include "nsIDOMHTMLElement.h"
 #include "nsIDOMHTMLMenuItemElement.h"
 #include "nsXULContextMenuBuilder.h"
 
 
 nsXULContextMenuBuilder::nsXULContextMenuBuilder()
-  : mCurrentIdent(0)
+  : mCurrentGeneratedItemId(0)
 {
 }
 
 nsXULContextMenuBuilder::~nsXULContextMenuBuilder()
 {
 }
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsXULContextMenuBuilder)
@@ -80,23 +80,24 @@ nsXULContextMenuBuilder::OpenContainer(c
   if (!mFragment) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   if (!mCurrentNode) {
     mCurrentNode = mFragment;
   } else {
     nsCOMPtr<nsIContent> menu;
-    nsresult rv = CreateElement(nsGkAtoms::menu, getter_AddRefs(menu));
+    nsresult rv = CreateElement(nsGkAtoms::menu, nsnull, 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));
+    rv = CreateElement(nsGkAtoms::menupopup, nsnull,
+                       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);
 
@@ -110,17 +111,18 @@ 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));
+  nsresult rv = CreateElement(nsGkAtoms::menuitem, aElement,
+                              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,
@@ -149,38 +151,28 @@ nsXULContextMenuBuilder::AddItemFor(nsID
 
   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;
+  return mCurrentNode->AppendChildTo(menuitem, PR_FALSE);
 }
 
 NS_IMETHODIMP
 nsXULContextMenuBuilder::AddSeparator()
 {
   if (!mFragment) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   nsCOMPtr<nsIContent> menuseparator;
-  nsresult rv = CreateElement(nsGkAtoms::menuseparator,
+  nsresult rv = CreateElement(nsGkAtoms::menuseparator, nsnull,
                               getter_AddRefs(menuseparator));
   NS_ENSURE_SUCCESS(rv, rv);
 
   return mCurrentNode->AppendChildTo(menuseparator, PR_FALSE);
 }
 
 NS_IMETHODIMP
 nsXULContextMenuBuilder::UndoAddSeparator()
@@ -213,56 +205,63 @@ nsXULContextMenuBuilder::CloseContainer(
   }
 
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsXULContextMenuBuilder::Init(nsIDOMDocumentFragment* aDocumentFragment,
-                              const nsAString& aGeneratedAttrName,
-                              const nsAString& aIdentAttrName)
+                              const nsAString& aGeneratedItemIdAttrName)
 {
   NS_ENSURE_ARG_POINTER(aDocumentFragment);
 
   mFragment = do_QueryInterface(aDocumentFragment);
   mDocument = mFragment->GetOwnerDocument();
-  mGeneratedAttr = do_GetAtom(aGeneratedAttrName);
-  mIdentAttr = do_GetAtom(aIdentAttrName);
+  mGeneratedItemIdAttr = do_GetAtom(aGeneratedItemIdAttrName);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsXULContextMenuBuilder::Click(const nsAString& aIdent)
+nsXULContextMenuBuilder::Click(const nsAString& aGeneratedItemId)
 {
   PRInt32 rv;
-  PRInt32 idx = nsString(aIdent).ToInteger(&rv);
+  PRInt32 idx = nsString(aGeneratedItemId).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)
+nsXULContextMenuBuilder::CreateElement(nsIAtom* aTag,
+                                       nsIDOMHTMLElement* aHTMLElement,
+                                       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(),
+  nsAutoString generateditemid;
+
+  if (aHTMLElement) {
+    mElements.AppendObject(aHTMLElement);
+    generateditemid.AppendInt(mCurrentGeneratedItemId++);
+  }
+
+  (*aResult)->SetAttr(kNameSpaceID_None, mGeneratedItemIdAttr, generateditemid,
                       PR_FALSE);
 
   return NS_OK;
 }
--- a/content/xul/content/src/nsXULContextMenuBuilder.h
+++ b/content/xul/content/src/nsXULContextMenuBuilder.h
@@ -52,20 +52,21 @@ public:
   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);
+  nsresult CreateElement(nsIAtom* aTag,
+                         nsIDOMHTMLElement* aHTMLElement,
+                         nsIContent** aResult);
 
   nsCOMPtr<nsIContent>          mFragment;
   nsCOMPtr<nsIDocument>         mDocument;
-  nsCOMPtr<nsIAtom>             mGeneratedAttr;
-  nsCOMPtr<nsIAtom>             mIdentAttr;
+  nsCOMPtr<nsIAtom>             mGeneratedItemIdAttr;
 
   nsCOMPtr<nsIContent>          mCurrentNode;
-  PRInt32                       mCurrentIdent;
+  PRInt32                       mCurrentGeneratedItemId;
 
   nsCOMArray<nsIDOMHTMLElement> mElements;
 };
--- a/toolkit/content/PageMenu.jsm
+++ b/toolkit/content/PageMenu.jsm
@@ -35,23 +35,22 @@
 
 let EXPORTED_SYMBOLS = ["PageMenu"];
 
 function PageMenu() {
 }
 
 PageMenu.prototype = {
   PAGEMENU_ATTR: "pagemenu",
-  GENERATED_ATTR: "generated",
-  IDENT_ATTR: "ident",
+  GENERATEDITEMID_ATTR: "generateditemid",
 
   popup: null,
   builder: null,
 
-  init: function(aTarget, aPopup) {
+  maybeBuildAndAttachMenu: function(aTarget, aPopup) {
     var pageMenu = null;
     var target = aTarget;
     while (target) {
       var contextMenu = target.contextMenu;
       if (contextMenu) {
         pageMenu = contextMenu;
         break;
       }
@@ -73,42 +72,42 @@ PageMenu.prototype = {
 
     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);
+    builder.init(fragment, this.GENERATEDITEMID_ATTR);
 
     pageMenu.build(builder);
 
     var pos = insertionPoint.getAttribute(this.PAGEMENU_ATTR);
-    if (pos == "end") {
-      insertionPoint.appendChild(fragment);
-    } else {
+    if (pos == "start") {
       insertionPoint.insertBefore(fragment,
                                   insertionPoint.firstChild);
+    } else {
+      insertionPoint.appendChild(fragment);
     }
 
     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));
+    if (type == "command" && target.hasAttribute(this.GENERATEDITEMID_ATTR)) {
+      this.builder.click(target.getAttribute(this.GENERATEDITEMID_ATTR));
     } else if (type == "popuphidden" && this.popup == target) {
       this.removeGeneratedContent(this.popup);
 
       this.popup.removeEventListener("popuphidden", this);
       this.popup.removeEventListener("command", this);
 
       this.popup = null;
       this.builder = null;
@@ -155,17 +154,17 @@ PageMenu.prototype = {
     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)) {
+        if (!child.hasAttribute(this.GENERATEDITEMID_ATTR)) {
           ungenerated.push(child);
           continue;
         }
         element.removeChild(child);
       }
     }
   }
 }