Bug 581999 - "Make it easier for add-ons to extend the context menu"
authorMatt Brubeck <mbrubeck@mozilla.com>
Wed, 28 Jul 2010 21:13:31 -0700
changeset 66413 c0d2d5c509846a92f59a434be5b78d71e2285f8d
parent 66412 9369c15beb2ed0a096da7127c38339963f6c1799
child 66414 c54478d9ef3b9d244b9132f7a588bb8b5147019b
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs581999
Bug 581999 - "Make it easier for add-ons to extend the context menu" [r=mark.finkle] (pt 2) missed part - forgot to qrefresh after resolving conflicts
mobile/chrome/content/browser-ui.js
mobile/chrome/content/browser.xul
mobile/chrome/content/content.js
--- a/mobile/chrome/content/browser-ui.js
+++ b/mobile/chrome/content/browser-ui.js
@@ -2039,47 +2039,41 @@ var ContextHelper = {
     this.popupState = aMessage.json;
     this.popupState.target = aMessage.target;
 
     let first = null;
     let last = null;
     let commands = document.getElementById("context-commands");
     for (let i=0; i<commands.childElementCount; i++) {
       let command = commands.children[i];
+      command.removeAttribute("selector");
+      command.hidden = true;
+
       let types = command.getAttribute("type").split(/\s+/);
-      command.removeAttribute("selector");
-      if ((types.indexOf("image") != -1 && this.popupState.onImage) ||
-         (types.indexOf("image-loaded") != -1 && this.popupState.onLoadedImage) ||
-         (types.indexOf("link") != -1 && this.popupState.onSaveableLink) ||
-         (types.indexOf("callto") != -1 && this.popupState.onVoiceLink) ||
-         (types.indexOf("mailto") != -1 && this.popupState.onLink && this.popupState.linkProtocol == "mailto") ||
-         (types.indexOf("edit-bookmark") != -1 && this.popupState.onBookmark)) {
-        first = (first ? first : command);
-        last = command;
-        command.hidden = false;
-        continue;
+      for (let i=0; i<types.length; i++) {
+        if (this.popupState.types.indexOf(types[i]) != -1) {
+          first = first || command;
+          last = command;
+          command.hidden = false;
+          break;
+        }
       }
-      command.hidden = true;
     }
 
     if (!first) {
       this.popupState = null;
       return;
     }
 
+    // Allow the first and last *non-hidden* elements to be selected in CSS.
     first.setAttribute("selector", "first-child");
     last.setAttribute("selector", "last-child");
 
     let label = document.getElementById("context-hint");
-    if (this.popupState.onImage)
-      label.value = this.popupState.mediaURL;
-    if (this.popupState.onLink)
-      label.value = this.popupState.linkURL;
-    if (this.popupState.onBookmark)
-      label.value = this.popupState.bookmarkURL;
+    label.value = this.popupState.label;
 
     this._panel.hidden = false;
     window.addEventListener("resize", this, true);
 
     this.sizeToContent();
     BrowserUI.pushPopup(this, [this._popup]);
   },
 
--- a/mobile/chrome/content/browser.xul
+++ b/mobile/chrome/content/browser.xul
@@ -510,17 +510,17 @@
     </vbox>
 
     <hbox id="context-container" class="window-width window-height context-block" top="0" left="0" hidden="true">
       <vbox id="context-popup" class="dialog-dark">
         <hbox id="context-header">
           <label id="context-hint" crop="center" flex="1"/>
         </hbox>
         <richlistbox id="context-commands" onclick="ContextHelper.hide();">
-          <richlistitem id="context-openinnewtab" type="link" onclick="ContextCommands.openInNewTab(event);">
+          <richlistitem id="context-openinnewtab" type="link-saveable" onclick="ContextCommands.openInNewTab(event);">
             <label value="&contextOpenInNewTab.label;"/>
           </richlistitem>
           <richlistitem id="context-saveimage" type="image-loaded" onclick="ContextCommands.saveImage(event);">
             <label value="&contextSaveImage.label;"/>
           </richlistitem>
           <richlistitem id="context-editbookmark" type="edit-bookmark" onclick="ContextCommands.editBookmark(event);">
             <label value="&contextEditBookmark.label;"/>
           </richlistitem>
--- a/mobile/chrome/content/content.js
+++ b/mobile/chrome/content/content.js
@@ -739,88 +739,109 @@ var ContextHandler = {
   },
 
   _getProtocol: function ch_getProtocol(aURI) {
     if (aURI)
       return aURI.scheme;
     return null;
   },
 
-  _isSaveable: function ch_isSaveable(aProtocol) {
-    // We don't do the Right Thing for news/snews yet, so turn them off until we do
-    return aProtocol && !(aProtocol == "mailto" || aProtocol == "javascript" || aProtocol == "news" || aProtocol == "snews");
-  },
-
-  _isVoice: function ch_isVoice(aProtocol) {
-    // Collection of protocols related to voice or data links
-    return aProtocol && (aProtocol == "tel" || aProtocol == "callto" || aProtocol == "sip" || aProtocol == "voipto");
-  },
-
   init: function ch_init() {
     addEventListener("contextmenu", this, false);
   },
 
   handleEvent: function ch_handleEvent(aEvent) {
     if (aEvent.getPreventDefault())
       return;
 
     let state = {
-      onLink: false,
-      onSaveableLink: false,
-      onVoiceLink: false,
-      onImage: false,
-      onLoadedImage: false,
+      types: [],
+      label: "",
       linkURL: "",
       linkProtocol: null,
       mediaURL: ""
     };
 
     let popupNode = aEvent.originalTarget;
 
     // Do checks for nodes that never have children.
     if (popupNode.nodeType == Ci.nsIDOMNode.ELEMENT_NODE) {
       // See if the user clicked on an image.
       if (popupNode instanceof Ci.nsIImageLoadingContent && popupNode.currentURI) {
-        state.onImage = true;
+        state.types.push("image");
         state.mediaURL = popupNode.currentURI.spec;
-
-        let request = popupNode.getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST);
-        if (request && (request.imageStatus & request.STATUS_SIZE_AVAILABLE))
-          state.onLoadedImage = true;
+        state.label = state.mediaURL;
       }
     }
 
     let elem = popupNode;
     while (elem) {
       if (elem.nodeType == Ci.nsIDOMNode.ELEMENT_NODE) {
         // Link?
-        if (!this.onLink &&
-             ((elem instanceof Ci.nsIDOMHTMLAnchorElement && elem.href) ||
-              (elem instanceof Ci.nsIDOMHTMLAreaElement && elem.href) ||
-              elem instanceof Ci.nsIDOMHTMLLinkElement ||
-              elem.getAttributeNS(kXLinkNamespace, "type") == "simple")) {
+        if ((elem instanceof Ci.nsIDOMHTMLAnchorElement && elem.href) ||
+            (elem instanceof Ci.nsIDOMHTMLAreaElement && elem.href) ||
+            elem instanceof Ci.nsIDOMHTMLLinkElement ||
+            elem.getAttributeNS(kXLinkNamespace, "type") == "simple") {
 
           // Target is a link or a descendant of a link.
-          state.linkURL = this._getLinkURL(elem);
+          state.types.push("link");
+          state.label = state.linkURL = this._getLinkURL(elem);
           state.linkProtocol = this._getProtocol(this._getURI(state.linkURL));
-          state.onLink = true;
-          state.onSaveableLink = this._isSaveable(state.linkProtocol);
-          state.onVoiceLink = this._isVoice(state.linkProtocol);
+          break;
         }
       }
 
       elem = elem.parentNode;
     }
+
+    for (let i = 0; i < this._types.length; i++)
+      if (this._types[i].handler(state, popupNode))
+        state.types.push(this._types[i].name);
     
     sendAsyncMessage("Browser:ContextMenu", state);
+  },
+
+  /**
+   * For add-ons to add new types and data to the ContextMenu message.
+   * 
+   * @param aName A string to identify the new type.
+   * @param aHandler A function that takes a state object and a target element.
+   *    If aHandler returns true, then aName will be added to the list of types.
+   *    The function may also modify the state object.
+   */
+  registerType: function registerType(aName, aHandler) {
+    this._types.push({name: aName, handler: aHandler});
   }
 };
 
 ContextHandler.init();
 
+ContextHandler.registerType("mailto", function(aState, aElement) {
+  return aState.linkProtocol == "mailto";
+});
+
+ContextHandler.registerType("callto", function(aState, aElement) {
+  let protocol = aState.linkProtocol;
+  return protocol == "tel" || protocol == "callto" || protocol == "sip" || protocol == "voipto";
+});
+
+ContextHandler.registerType("link-saveable", function(aState, aElement) {
+  let protocol = aState.linkProtocol;
+  dump(protocol+"\n");
+  return (protocol && protocol != "mailto" && protocol != "javascript" && protocol != "news" && protocol != "snews");
+});
+
+ContextHandler.registerType("image-loaded", function(aState, aElement) {
+  if (aState.types.indexOf("image") != -1) {
+    let request = aElement.getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST);
+    if (request && (request.imageStatus & request.STATUS_SIZE_AVAILABLE))
+      return true;
+  }
+  return false;
+});
 
 var FormSubmitObserver = {
   
   init: function init(){
     addMessageListener("Browser:TabOpen", this);
     addMessageListener("Browser:TabClose", this);
   },