Bug 782810 - Implement Yuan's new context menu spec. r=mbrubeck
authorJim Mathies <jmathies@mozilla.com>
Tue, 19 Feb 2013 19:51:02 -0600
changeset 122441 d3ee65f9f8c80f3d5cb7872b18d26b73df9be616
parent 122440 8b45c32b6028a944c3dddd538d5d9320c089c306
child 122442 17b80182ebd8b4502d1bef94b487018631794620
push id24342
push userryanvm@gmail.com
push dateThu, 21 Feb 2013 13:05:06 +0000
treeherdermozilla-central@702d2814efbf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmbrubeck
bugs782810
milestone22.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 782810 - Implement Yuan's new context menu spec. r=mbrubeck
browser/metro/base/content/ContextCommands.js
browser/metro/base/content/browser.xul
browser/metro/base/content/contenthandlers/ContextMenuHandler.js
browser/metro/base/content/contenthandlers/SelectionHandler.js
browser/metro/base/content/helperui/MenuUI.js
browser/metro/base/content/helperui/SelectionHelperUI.js
browser/metro/base/content/input.js
browser/metro/locales/en-US/chrome/browser.dtd
browser/metro/locales/en-US/chrome/browser.properties
--- a/browser/metro/base/content/ContextCommands.js
+++ b/browser/metro/base/content/ContextCommands.js
@@ -18,73 +18,215 @@ var ContextCommands = {
   get docRef() {
     return Browser.selectedBrowser.contentWindow.document;
   },
 
   /*
    * Context menu handlers
    */
 
+  // Text specific
+
   copy: function cc_copy() {
     let target = ContextMenuUI.popupState.target;
     if (target.localName == "browser") {
+      // content
       if (ContextMenuUI.popupState.string != "undefined") {
         this.clipboard.copyString(ContextMenuUI.popupState.string,
                                   this.docRef);
+        this.showToast(Strings.browser.GetStringFromName("selectionHelper.textCopied"));
       } else {
         let x = ContextMenuUI.popupState.x;
         let y = ContextMenuUI.popupState.y;
         let json = {x: x, y: y, command: "copy" };
         target.messageManager.sendAsyncMessage("Browser:ContextCommand", json);
       }
     } else {
+      // chrome
       target.editor.copy();
+      this.showToast(Strings.browser.GetStringFromName("selectionHelper.textCopied"));
     }
 
     if (target)
       target.focus();
   },
 
   paste: function cc_paste() {
     let target = ContextMenuUI.popupState.target;
     if (target.localName == "browser") {
+      // content
       let x = ContextMenuUI.popupState.x;
       let y = ContextMenuUI.popupState.y;
       let json = {x: x, y: y, command: "paste" };
       target.messageManager.sendAsyncMessage("Browser:ContextCommand", json);
     } else {
+      // chrome
       target.editor.paste(Ci.nsIClipboard.kGlobalClipboard);
       target.focus();
     }
   },
 
   pasteAndGo: function cc_pasteAndGo() {
     let target = ContextMenuUI.popupState.target;
     target.editor.selectAll();
     target.editor.paste(Ci.nsIClipboard.kGlobalClipboard);
     BrowserUI.goToURI();
   },
 
+  select: function cc_select() {
+    let contextInfo = { name: "",
+                        json: ContextMenuUI.popupState,
+                        target: ContextMenuUI.popupState.target };
+    SelectionHelperUI.openEditSession(contextInfo);
+  },
+
   selectAll: function cc_selectAll() {
     let target = ContextMenuUI.popupState.target;
     if (target.localName == "browser") {
+      // content
       let x = ContextMenuUI.popupState.x;
       let y = ContextMenuUI.popupState.y;
       let json = {x: x, y: y, command: "select-all" };
       target.messageManager.sendAsyncMessage("Browser:ContextCommand", json);
+      let contextInfo = { name: "",
+                          json: ContextMenuUI.popupState,
+                          target: ContextMenuUI.popupState.target };
+      SelectionHelperUI.attachEditSession(contextInfo);
     } else {
+      // chrome
       target.editor.selectAll();
       target.focus();
     }
   },
 
-  openInNewTab: function cc_openInNewTab() {
+  // called on display of the search text menu item
+  searchTextSetup: function cc_searchTextSetup(aRichListItem, aSearchString) {
+    let defaultURI;
+    let defaultName;
+    try {
+      let defaultPB = Services.prefs.getDefaultBranch(null);
+      const nsIPLS = Ci.nsIPrefLocalizedString;
+      defaultName = defaultPB.getComplexValue("browser.search.defaultenginename", nsIPLS).data;
+      let defaultEngine = Services.search.getEngineByName(defaultName);
+      defaultURI = defaultEngine.getSubmission(aSearchString).uri.spec;
+    } catch (ex) {
+      Cu.reportError(ex);
+      return false;
+    }
+    // label child node
+    let label = Services.strings
+                        .createBundle("chrome://browser/locale/browser.properties")
+                        .formatStringFromName("browser.search.contextTextSearchLabel",
+                                              [defaultName], 1);
+    aRichListItem.childNodes[0].setAttribute("value", label);
+    aRichListItem.setAttribute("searchString", defaultURI);
+    return true;
+  },
+
+  searchText: function cc_searchText(aRichListItem) {
+    let defaultURI = aRichListItem.getAttribute("searchString");
+    aRichListItem.childNodes[0].setAttribute("value", "");
+    aRichListItem.setAttribute("searchString", "");
+    BrowserUI.newTab(defaultURI, Browser.selectedTab);
+  },
+
+  // Link specific
+
+  openLinkInNewTab: function cc_openLinkInNewTab() {
     BrowserUI.newTab(ContextMenuUI.popupState.linkURL, Browser.selectedTab);
   },
 
+  copyLink: function cc_copyLink() {
+    this.clipboard.copyString(ContextMenuUI.popupState.linkURL,
+                              this.docRef);
+    this.showToast(Strings.browser.GetStringFromName("selectionHelper.linkCopied"));
+  },
+
+  bookmarkLink: function cc_bookmarkLink() {
+    let state = ContextMenuUI.popupState;
+    let uri = Util.makeURI(state.linkURL);
+    let title = state.linkTitle || state.linkURL;
+
+    try {
+      Bookmarks.addForURI(uri, title);
+    } catch (e) {
+      return;
+    }
+
+    this.showToast(Strings.browser.GetStringFromName("alertLinkBookmarked"));
+  },
+
+  // Image specific
+
+  saveImageToLib: function cc_saveImageToLib() {
+    this.saveToWinLibrary("Pict");
+  },
+
+  copyImage: function cc_copyImage() {
+    // copy to clibboard
+    this.sendCommand("copy-image-contents");
+  },
+
+  copyImageSrc: function cc_copyImageSrc() {
+    this.clipboard.copyString(ContextMenuUI.popupState.mediaURL,
+                              this.docRef);
+    this.showToast(Strings.browser.GetStringFromName("selectionHelper.linkCopied"));
+  },
+
+  openImageInNewTab: function cc_openImageInNewTab() {
+    BrowserUI.newTab(ContextMenuUI.popupState.mediaURL, Browser.selectedTab);
+  },
+
+  // Video specific
+
+  saveVideoToLib: function cc_saveVideoToLib() {
+    this.saveToWinLibrary("Vids");
+  },
+
+  copyVideoSrc: function cc_copyVideoSrc() {
+    this.clipboard.copyString(ContextMenuUI.popupState.mediaURL,
+                              this.docRef);
+    this.showToast(Strings.browser.GetStringFromName("selectionHelper.linkCopied"));
+  },
+
+  openVideoInNewTab: function cc_openVideoInNewTab() {
+    BrowserUI.newTab(ContextMenuUI.popupState.mediaURL, Browser.selectedTab);
+  },
+
+  openVideoInFullscreen: function cc_openVideoInFullscreen() {
+    // XXX currently isn't working.
+    this.sendCommand('videotab');
+  },
+
+  // Bookmarks
+
+  editBookmark: function cc_editBookmark() {
+    let target = ContextMenuUI.popupState.target;
+    target.startEditing();
+  },
+
+  removeBookmark: function cc_removeBookmark() {
+    let target = ContextMenuUI.popupState.target;
+    target.remove();
+  },
+
+  // App bar
+
+  findInPage: function cc_findInPage() {
+    FindHelperUI.show();
+  },
+
+  viewOnDesktop: function cc_viewOnDesktop() {
+    Appbar.onViewOnDesktop();
+  },
+
+  /*
+   * Utilities
+   */
+
   saveToWinLibrary: function cc_saveToWinLibrary(aType) {
     let popupState = ContextMenuUI.popupState;
     let browser = popupState.target;
 
     // ContentAreaUtils internalSave relies on various desktop related prefs,
     // values, and functionality. We want to be more direct by saving the
     // image to the users Windows Library. If users want to specify the
     // save location they can use the context menu option 'Save (type) To'.
@@ -110,105 +252,27 @@ var ContextCommands = {
       targetFile        : saveLocationPath,
       sourceCacheKey    : null,
       sourcePostData    : null,
       bypassCache       : false,
       initiatingWindow  : this.docRef.defaultView
     });
   },
 
-  // Video specific
-
-  saveVideo: function cc_saveVideo() {
-    this.saveToWinLibrary("Vids");
-  },
-
-  saveVideoTo: function cc_saveVideoTo() {
-    this.saveFileAs(ContextMenuUI.popupState);
-  },
-
-  // Image specific
-
-  saveImageToLib: function cc_saveImageToLib() {
-    this.saveToWinLibrary("Pict");
-  },
-
-  copyImage: function cc_copyImage() {
-    // copy to clibboard
-    this.sendCommand("copy-image-contents");
-  },
-
-  copyImageLink: function cc_copyImage() {
-    this.clipboard.copyString(ContextMenuUI.popupState.mediaURL,
-                              this.docRef);
-  },
-
-  openImageInNewTab: function cc_openImageInNewTab() {
-    BrowserUI.newTab(ContextMenuUI.popupState.mediaURL, Browser.selectedTab);
-  },
-
-  copyLink: function cc_copyLink() {
-    this.clipboard.copyString(ContextMenuUI.popupState.linkURL,
-                              this.docRef);
-  },
-
-  copyEmail: function cc_copyEmail() {
-    this.clipboard.copyString(ContextMenuUI.popupState.linkURL.substr(ContextMenuUI.popupState.linkURL.indexOf(':')+1),
-                              this.docRef);
-  },
-
-  copyPhone: function cc_copyPhone() {
-    this.clipboard.copyString(ContextMenuUI.popupState.linkURL.substr(ContextMenuUI.popupState.linkURL.indexOf(':')+1),
-                              this.docRef);
-  },
-
-  bookmarkLink: function cc_bookmarkLink() {
-    let state = ContextMenuUI.popupState;
-    let uri = Util.makeURI(state.linkURL);
-    let title = state.linkTitle || state.linkURL;
-
-    try {
-      Bookmarks.addForURI(uri, title);
-    } catch (e) {
-      return;
-    }
-
-    let message = Strings.browser.GetStringFromName("alertLinkBookmarked");
+  showToast: function showToast(aString) {
     let toaster = Cc["@mozilla.org/toaster-alerts-service;1"].getService(Ci.nsIAlertsService);
-    toaster.showAlertNotification(null, message, "", false, "", null);
+    toaster.showAlertNotification(null, aString, "", false, "", null);
   },
 
   sendCommand: function cc_playVideo(aCommand) {
     // Send via message manager over to ContextMenuHandler
     let browser = ContextMenuUI.popupState.target;
     browser.messageManager.sendAsyncMessage("Browser:ContextCommand", { command: aCommand });
   },
 
-  editBookmark: function cc_editBookmark() {
-    let target = ContextMenuUI.popupState.target;
-    target.startEditing();
-  },
-
-  removeBookmark: function cc_removeBookmark() {
-    let target = ContextMenuUI.popupState.target;
-    target.remove();
-  },
-
-  findInPage: function cc_findInPage() {
-    FindHelperUI.show();
-  },
-
-  viewOnDesktop: function cc_viewOnDesktop() {
-    Appbar.onViewOnDesktop();
-  },
-
-  /*
-   * Utilities
-   */
-
   /*
    * isAccessibleDirectory
    *
    * Test to see if the directory exists and is writable.
    */
   isAccessibleDirectory: function isAccessibleDirectory(aDirectory) {
     return aDirectory && aDirectory.exists() && aDirectory.isDirectory() &&
            aDirectory.isWritable();
--- a/browser/metro/base/content/browser.xul
+++ b/browser/metro/base/content/browser.xul
@@ -52,31 +52,31 @@
   </broadcasterset>
 
   <observerset id="observerset">
     <observes id="observe_contentShowing" element="bcast_contentShowing" attribute="disabled" onbroadcast="BrowserUI.updateUIFocus();"/>
   </observerset>
 
   <commandset id="mainCommandSet">
     <!-- basic navigation -->
-    <command id="cmd_back" label="&back.label;" disabled="true" oncommand="CommandUpdater.doCommand(this.id);"/>
-    <command id="cmd_forward" label="&forward.label;" disabled="true" oncommand="CommandUpdater.doCommand(this.id);" observes="bcast_urlbarState"/>
+    <command id="cmd_back" disabled="true" oncommand="CommandUpdater.doCommand(this.id);"/>
+    <command id="cmd_forward" disabled="true" oncommand="CommandUpdater.doCommand(this.id);" observes="bcast_urlbarState"/>
     <command id="cmd_handleBackspace" oncommand="BrowserUI.handleBackspace();" />
     <command id="cmd_handleShiftBackspace" oncommand="BrowserUI.handleShiftBackspace();" />
-    <command id="cmd_reload" label="&reload.label;" oncommand="CommandUpdater.doCommand(this.id);"/>
+    <command id="cmd_reload" oncommand="CommandUpdater.doCommand(this.id);"/>
     <command id="cmd_forceReload" oncommand="CommandUpdater.doCommand(this.id);"/>
-    <command id="cmd_stop" label="&stop.label;" oncommand="CommandUpdater.doCommand(this.id);"/>
-    <command id="cmd_go" label="&go.label;" oncommand="CommandUpdater.doCommand(this.id);"/>
+    <command id="cmd_stop" oncommand="CommandUpdater.doCommand(this.id);"/>
+    <command id="cmd_go" oncommand="CommandUpdater.doCommand(this.id);"/>
     <command id="cmd_openLocation" oncommand="CommandUpdater.doCommand(this.id);"/>
     <command id="cmd_home" oncommand="CommandUpdater.doCommand(this.id);"/>
 
     <!-- tabs -->
-    <command id="cmd_newTab" label="&newtab.label;" oncommand="CommandUpdater.doCommand(this.id);"/>
-    <command id="cmd_closeTab" label="&closetab.label;" oncommand="CommandUpdater.doCommand(this.id);"/>
-    <command id="cmd_undoCloseTab" label="&undoclosetab.label;" oncommand="CommandUpdater.doCommand(this.id);"/>
+    <command id="cmd_newTab" oncommand="CommandUpdater.doCommand(this.id);"/>
+    <command id="cmd_closeTab" oncommand="CommandUpdater.doCommand(this.id);"/>
+    <command id="cmd_undoCloseTab" oncommand="CommandUpdater.doCommand(this.id);"/>
 #ifdef MOZ_SERVICES_SYNC
     <command id="cmd_remoteTabs" oncommand="CommandUpdater.doCommand(this.id);"/>
 #endif
 
     <!-- misc -->
     <command id="cmd_close" oncommand="CommandUpdater.doCommand(this.id);"/>
     <command id="cmd_quit" oncommand="CommandUpdater.doCommand(this.id);"/>
     <command id="cmd_actions" oncommand="CommandUpdater.doCommand(this.id);"/>
@@ -96,22 +96,22 @@
     <command id="cmd_volumeLeft" observes="bcast_contentShowing" oncommand="CommandUpdater.doCommand(this.id);"/>
     <command id="cmd_volumeRight" observes="bcast_contentShowing" oncommand="CommandUpdater.doCommand(this.id);"/>
 
     <!-- scrolling -->
     <command id="cmd_scrollPageUp" oncommand="CommandUpdater.doCommand(this.id);"/>
     <command id="cmd_scrollPageDown" oncommand="CommandUpdater.doCommand(this.id);"/>
 
     <!-- editing -->
-    <command id="cmd_cut" label="&cut.label;" oncommand="CommandUpdater.doCommand(this.id);"/>
-    <command id="cmd_copy" label="&copy.label;" oncommand="CommandUpdater.doCommand(this.id);"/>
-    <command id="cmd_copylink" label="&copylink.label;" oncommand="CommandUpdater.doCommand(this.id);"/>
-    <command id="cmd_paste" label="&paste.label;" oncommand="CommandUpdater.doCommand(this.id);"/>
-    <command id="cmd_delete" label="&delete.label;" oncommand="CommandUpdater.doCommand(this.id);"/>
-    <command id="cmd_selectAll" label="&selectAll.label;" oncommand="CommandUpdater.doCommand(this.id);"/>
+    <command id="cmd_cut" oncommand="CommandUpdater.doCommand(this.id);"/>
+    <command id="cmd_copy" oncommand="CommandUpdater.doCommand(this.id);"/>
+    <command id="cmd_copylink" oncommand="CommandUpdater.doCommand(this.id);"/>
+    <command id="cmd_paste" oncommand="CommandUpdater.doCommand(this.id);"/>
+    <command id="cmd_delete" oncommand="CommandUpdater.doCommand(this.id);"/>
+    <command id="cmd_selectAll" oncommand="CommandUpdater.doCommand(this.id);"/>
 
     <!-- forms navigation -->
     <command id="cmd_formPrevious" oncommand="FormHelperUI.goToPrevious();"/>
     <command id="cmd_formNext" oncommand="FormHelperUI.goToNext();"/>
     <command id="cmd_formClose" oncommand="FormHelperUI.hide();"/>
 
     <!-- find navigation -->
     <command id="cmd_findPrevious" oncommand="FindHelperUI.goToPrevious();"/>
@@ -190,20 +190,20 @@
             <observes element="bcast_windowState" attribute="*"/>
             <observes element="bcast_urlbarState" attribute="*"/>
             <hbox id="unified-back-forward-button" class="chromeclass-toolbar-additional"
                observes="bcast_windowState"
                context="backForwardMenu" removable="true"
                forwarddisabled="true"
                title="Back/Forward">
               <toolbarbutton id="back-button" class="toolbarbutton"
-                             label="Back"
+                             label="&back.label;"
                              command="cmd_back"/>
               <toolbarbutton id="forward-button" class="toolbarbutton"
-                             label="Forward"
+                             label="&forward.label;"
                              command="cmd_forward"/>
               <dummyobservertarget hidden="true"
                                    onbroadcast="if (this.getAttribute('disabled') == 'true')
                                                   this.parentNode.setAttribute('forwarddisabled', 'true');
                                                 else
                                                   this.parentNode.removeAttribute('forwarddisabled');">
                 <observes element="cmd_forward" attribute="disabled"/>
               </dummyobservertarget>
@@ -240,17 +240,17 @@
 
             <hbox id="urlbar-icons" observes="bcast_urlbarState">
               <toolbarbutton id="tool-reload" oncommand="CommandUpdater.doCommand(event.shiftKey ? 'cmd_forceReload' : 'cmd_reload');"/>
               <toolbarbutton id="tool-stop" command="cmd_stop"/>
             </hbox>
           </toolbar>
 
           <box id="toolbar-transition" observes="bcast_windowState" >
-            <toolbarbutton id="tool-new-tab" command="cmd_newTab"/>
+            <toolbarbutton id="tool-new-tab" command="cmd_newTab" label="&newtab.label;"/>
           </box>
         </hbox>
 
         <hbox id="progress-control" layer="true"></hbox>
 
         <!-- Start UI -->
         <hbox id="start-container" flex="1" observes="bcast_windowState" class="meta content-height content-width" onclick="false;">
           <!-- portrait/landscape/filled view -->
@@ -554,88 +554,93 @@
         </vbox>
       </dialog>
     </box>
 #endif
 
     <box id="context-container" class="menu-container" hidden="true">
       <vbox id="context-popup" class="menu-popup">
         <richlistbox id="context-commands" bindingType="contextmenu" flex="1">
+          <!-- priority="low" items are hidden by default when a context is being displayed
+               for two or more media types. (e.g. a linked image) -->
+          <!-- Note the order of richlistitem here is important as it is reflected in the
+               menu itself. -->
+          <!-- ux spec: https://bug782810.bugzilla.mozilla.org/attachment.cgi?id=714804 -->
+
           <!-- Text related -->
-          <richlistitem id="context-copy" type="copy" onclick="ContextCommands.copy();">
-            <label value="&copy.label;"/>
+          <!-- for text inputs, this will copy selected text, or if no text is selected, copy all -->
+          <richlistitem id="context-copy" type="copy,selectable" onclick="ContextCommands.copy();">
+            <label value="&contextTextCopy.label;"/>
           </richlistitem>
-          <richlistitem id="context-copy-all" type="copy-all" onclick="ContextCommands.copy();">
-            <label value="&copyAll.label;"/>
-          </richlistitem>
+          <!-- only displayed if there is text on the clipboard -->
           <richlistitem id="context-paste" type="paste" onclick="ContextCommands.paste();">
-            <label value="&paste.label;"/>
+            <label value="&contextTextPaste.label;"/>
           </richlistitem>
+          <!-- Search Bing for "..." -->
+          <richlistitem id="context-search" type="copy,selected-text" onclick="ContextCommands.searchText(this);">
+            <label id="context-search-label" value=""/>
+          </richlistitem>
+          <!-- only display if there is text on the clipboard and the target is the urlbar -->
           <richlistitem id="context-paste-n-go" type="paste-url" onclick="ContextCommands.pasteAndGo();">
-            <label value="&pasteAndGo.label;"/>
+            <label value="&contextTextPasteAndGo.label;"/>
           </richlistitem>
-          <richlistitem id="context-select-all" type="select-all" onclick="ContextCommands.selectAll();">
-            <label value="&selectAll.label;"/>
+          <!-- only displayed in inputs with text that do not have selection -->
+          <richlistitem id="context-select" type="selectable" onclick="ContextCommands.select();">
+            <label value="&contextTextSelect.label;"/>
+          </richlistitem>
+          <!-- only displayed in inputs with text that do not have selection -->
+          <richlistitem id="context-select-all" type="selectable" onclick="ContextCommands.selectAll();">
+            <label value="&contextTextSelectAll.label;"/>
           </richlistitem>
 
           <!-- Image related -->
+          <!-- save image to user pictures library -->
           <richlistitem id="context-save-image-lib" type="image" onclick="ContextCommands.saveImageToLib();">
             <label value="&contextSaveImageLib.label;"/>
           </richlistitem>
+          <!-- copy image data to clipboard -->
           <richlistitem id="context-copy-image" type="image" onclick="ContextCommands.copyImage();">
             <label value="&contextCopyImage.label;"/>
           </richlistitem>
-          <richlistitem id="context-copy-image-loc" type="image" onclick="ContextCommands.copyImageLink();">
+          <!-- copy the uri of the image src -->
+          <richlistitem id="context-copy-image-loc" type="image" onclick="ContextCommands.copyImageSrc();">
             <label value="&contextCopyImageLocation.label;"/>
           </richlistitem>
-          <richlistitem id="context-open-image-tab" type="image" onclick="ContextCommands.openImageInNewTab();">
+          <!-- open the uri of the image src in a new tab -->
+          <richlistitem id="context-open-image-tab" type="image" priority="low" onclick="ContextCommands.openImageInNewTab();">
             <label value="&contextOpenImageTab.label;"/>
           </richlistitem>
 
+          <!-- Video related -->
+          <!-- save video to user videos library -->
+          <richlistitem id="context-save-video-lib" type="video" onclick="ContextCommands.saveVideoToLib();">
+            <label value="&contextSaveVideoLib.label;"/>
+          </richlistitem>
+          <!-- copy the uri of the video src -->
+          <richlistitem id="context-copy-video-loc" type="video" onclick="ContextCommands.copyVideoSrc();">
+            <label value="&contextCopyVideoLocation.label;"/>
+          </richlistitem>
+          <!-- open the uri of the video src in a new tab -->
+          <richlistitem id="context-open-video-tab" type="video" priority="low" onclick="ContextCommands.openVideoInNewTab();">
+            <label value="&contextOpenVideoTab.label;"/>
+          </richlistitem>
+
           <!-- Link related -->
-          <richlistitem id="context-openinnewtab" type="link-openable" onclick="ContextCommands.openInNewTab();">
-            <label value="&contextOpenInNewTab.label;"/>
-          </richlistitem>
-          <richlistitem id="context-bookmark-link" type="link" onclick="ContextCommands.bookmarkLink();">
-            <label value="&contextBookmarkLink.label;"/>
+          <!-- all of these apply to underlying link href values -->
+          <richlistitem id="context-open-in-new-tab" type="link" onclick="ContextCommands.openLinkInNewTab();">
+            <label value="&contextOpenLinkTab.label;"/>
           </richlistitem>
           <richlistitem id="context-copy-link" type="link" onclick="ContextCommands.copyLink();">
-            <label value="&contextCopyLink.label;"/>
+            <label value="&contextCopyLinkHref.label;"/>
           </richlistitem>
-          <richlistitem id="context-copy-email" type="mailto" onclick="ContextCommands.copyEmail();">
-            <label value="&contextCopyEmail.label;"/>
-          </richlistitem>
-          <richlistitem id="context-copy-phone" type="callto" onclick="ContextCommands.copyPhone();">
-            <label value="&contextCopyPhone.label;"/>
+          <richlistitem id="context-bookmark-link" type="link" priority="low" onclick="ContextCommands.bookmarkLink();">
+            <label value="&contextBookmarkLinkHref.label;"/>
           </richlistitem>
 
-          <!-- Video related -->
-          <richlistitem id="context-play-media" type="media-paused" onclick="ContextCommands.sendCommand('play');">
-            <label value="&contextPlayMedia.label;"/>
-          </richlistitem>
-          <richlistitem id="context-pause-video" type="media-playing" onclick="ContextCommands.sendCommand('pause');">
-            <label value="&contextPauseMedia.label;"/>
-          </richlistitem>
-          <richlistitem id="context-videotab" type="video" onclick="ContextCommands.sendCommand('videotab');">
-            <label value="&contextVideoTab.label;"/>
-          </richlistitem>
-          <richlistitem id="context-save-video" type="video" onclick="ContextCommands.saveVideo();">
-            <label value="&contextSaveVideo.label;"/>
-          </richlistitem>
-          <richlistitem id="context-save-video-to" type="video" onclick="ContextCommands.saveVideoTo();">
-            <label value="&contextSaveVideoTo.label;"/>
-          </richlistitem>
-
-          <!-- Misc. related -->
-          <richlistitem id="context-editbookmark" type="edit-bookmark" onclick="ContextCommands.editBookmark();">
-            <label value="&contextEditBookmark.label;"/>
-          </richlistitem>
-          <richlistitem id="context-removebookmark" type="edit-bookmark" onclick="ContextCommands.removeBookmark();">
-            <label value="&contextRemoveBookmark.label;"/>
-          </richlistitem>
+          <!-- App bar: 'more' button context menu -->
           <richlistitem id="context-findinpage" type="find-in-page" onclick="ContextCommands.findInPage();">
           <label value="&appbarFindInPage.label;"/>
           </richlistitem>
           <richlistitem id="context-viewondesktop" type="view-on-desktop" onclick="ContextCommands.viewOnDesktop();">
           <label value="&appbarViewOnDesktop.label;"/>
           </richlistitem>
         </richlistbox>
       </vbox>
--- a/browser/metro/base/content/contenthandlers/ContextMenuHandler.js
+++ b/browser/metro/base/content/contenthandlers/ContextMenuHandler.js
@@ -170,39 +170,38 @@ var ContextMenuHandler = {
             continue;
           }
 
           state.types.push("link");
           state.label = state.linkURL = this._getLinkURL(elem);
           linkUrl = state.linkURL;
           state.linkTitle = popupNode.textContent || popupNode.title;
           state.linkProtocol = this._getProtocol(this._getURI(state.linkURL));
+          // mark as text so we can pickup on selection below
+          isText = true;
           break;
         } else if (this._isTextInput(elem)) {
           let selectionStart = elem.selectionStart;
           let selectionEnd = elem.selectionEnd;
 
           state.types.push("input-text");
           this._target = elem;
 
           // Don't include "copy" for password fields.
           if (!(elem instanceof Ci.nsIDOMHTMLInputElement) || elem.mozIsTextField(true)) {
             if (selectionStart != selectionEnd) {
               state.types.push("copy");
               state.string = elem.value.slice(selectionStart, selectionEnd);
-            } else if (elem.value) {
-              state.types.push("copy-all");
+            }
+            if (elem.value && (selectionStart > 0 || selectionEnd < elem.textLength)) {
+              state.types.push("selectable");
               state.string = elem.value;
             }
           }
 
-          if (selectionStart > 0 || selectionEnd < elem.textLength) {
-            state.types.push("select-all");
-          }
-
           if (!elem.textLength) {
             state.types.push("input-empty");
           }
 
           let flavors = ["text/unicode"];
           let cb = Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard);
           let hasData = cb.hasDataMatchingFlavors(flavors,
                                                   flavors.length,
@@ -222,16 +221,17 @@ var ContextMenuHandler = {
             state.types.push("video");
           }
         }
       }
 
       elem = elem.parentNode;
     }
 
+    // Over arching text tests
     if (isText) {
       // If this is text and has a selection, we want to bring
       // up the copy option on the context menu.
       if (content && content.getSelection() &&
           content.getSelection().toString().length > 0) {
         state.string = content.getSelection().toString();
         state.types.push("copy");
         state.types.push("selected-text");
@@ -329,41 +329,8 @@ var ContextMenuHandler = {
 
   /** Remove all handlers registered for a given type. */
   unregisterType: function unregisterType(aName) {
     this._types = this._types.filter(function(type) type.name != aName);
   }
 };
 
 ContextMenuHandler.init();
-
-ContextMenuHandler.registerType("mailto", function(aState, aElement) {
-  return aState.linkProtocol == "mailto";
-});
-
-ContextMenuHandler.registerType("callto", function(aState, aElement) {
-  let protocol = aState.linkProtocol;
-  return protocol == "tel" || protocol == "callto" || protocol == "sip" || protocol == "voipto";
-});
-
-ContextMenuHandler.registerType("link-openable", function(aState, aElement) {
-  return Util.isOpenableScheme(aState.linkProtocol);
-});
-
-["image", "video"].forEach(function(aType) {
-  ContextMenuHandler.registerType(aType+"-shareable", function(aState, aElement) {
-    if (aState.types.indexOf(aType) == -1)
-      return false;
-
-    let protocol = ContextMenuHandler._getProtocol(ContextMenuHandler._getURI(aState.mediaURL));
-    return Util.isShareableScheme(protocol);
-  });
-});
-
-ContextMenuHandler.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;
-});
-
--- a/browser/metro/base/content/contenthandlers/SelectionHandler.js
+++ b/browser/metro/base/content/contenthandlers/SelectionHandler.js
@@ -50,29 +50,31 @@ var SelectionHandler = {
   _frameOffset: { x:0, y:0 },
   _domWinUtils: null,
   _selectionMoveActive: false,
   _lastMarker: "",
   _debugOptions: { dumpRanges: false, displayRanges: false },
 
   init: function init() {
     addMessageListener("Browser:SelectionStart", this);
+    addMessageListener("Browser:SelectionAttach", this);
     addMessageListener("Browser:SelectionEnd", this);
     addMessageListener("Browser:SelectionMoveStart", this);
     addMessageListener("Browser:SelectionMove", this);
     addMessageListener("Browser:SelectionMoveEnd", this);
     addMessageListener("Browser:SelectionUpdate", this);
     addMessageListener("Browser:SelectionClose", this);
     addMessageListener("Browser:SelectionClear", this);
     addMessageListener("Browser:SelectionCopy", this);
     addMessageListener("Browser:SelectionDebug", this);
   },
 
   shutdown: function shutdown() {
     removeMessageListener("Browser:SelectionStart", this);
+    removeMessageListener("Browser:SelectionAttach", this);
     removeMessageListener("Browser:SelectionEnd", this);
     removeMessageListener("Browser:SelectionMoveStart", this);
     removeMessageListener("Browser:SelectionMove", this);
     removeMessageListener("Browser:SelectionMoveEnd", this);
     removeMessageListener("Browser:SelectionUpdate", this);
     removeMessageListener("Browser:SelectionClose", this);
     removeMessageListener("Browser:SelectionClear", this);
     removeMessageListener("Browser:SelectionCopy", this);
@@ -109,16 +111,27 @@ var SelectionHandler = {
       this._onFail("failed to set selection at point");
       return;
     }
 
     // Update the position of our selection monocles
     this._updateSelectionUI(true, true);
   },
 
+  _onSelectionAttach: function _onSelectionAttach(aX, aY) {
+    // Init content window information
+    if (!this._initTargetInfo(aX, aY)) {
+      this._onFail("failed to get frame offset");
+      return;
+    }
+
+    // Update the position of our selection monocles
+    this._updateSelectionUI(true, true);
+  },
+
   /*
    * Selection monocle start move event handler
    */
   _onSelectionMoveStart: function _onSelectionMoveStart(aMsg) {
     if (!this._contentWindow) {
       this._onFail("_onSelectionMoveStart was called without proper view set up");
       return;
     }
@@ -889,16 +902,20 @@ var SelectionHandler = {
       Util.dumpLn("SelectionHandler:", aMessage.name);
     }
     let json = aMessage.json;
     switch (aMessage.name) {
       case "Browser:SelectionStart":
         this._onSelectionStart(json.xPos, json.yPos);
         break;
 
+      case "Browser:SelectionAttach":
+        this._onSelectionAttach(json.xPos, json.yPos);
+      break;
+
       case "Browser:SelectionClose":
         this._onSelectionClose();
         break;
 
       case "Browser:SelectionMoveStart":
         this._onSelectionMoveStart(json);
         break;
 
--- a/browser/metro/base/content/helperui/MenuUI.js
+++ b/browser/metro/base/content/helperui/MenuUI.js
@@ -113,37 +113,76 @@ var ContextMenuUI = {
    *
    * json: TBD
    */
   showContextMenu: function ch_showContextMenu(aMessage) {
     this._popupState = aMessage.json;
     this._popupState.target = aMessage.target;
     let contentTypes = this._popupState.types;
 
+    /*
+     * Types in ContextMenuHandler:
+     * image
+     * link
+     * input-text     - generic form input control
+     * copy           - form input that has some selected text
+     * selectable     - form input with text that can be selected
+     * input-empty    - form input (empty)
+     * paste          - form input and there's text on the clipboard
+     * selected-text  - generic content text that is selected
+     * content-text   - generic content text
+     * video
+     * media-paused, media-playing
+     * paste-url      - url bar w/text on the clipboard
+     */
+
+    Util.dumpLn("contentTypes:", contentTypes);
+
+    // Defines whether or not low priority items in images, text, and
+    // links are displayed.
+    let multipleMediaTypes = false;
+    if (contentTypes.indexOf("link") != -1 &&
+        (contentTypes.indexOf("image") != -1  ||
+         contentTypes.indexOf("video") != -1 ||
+         contentTypes.indexOf("selected-text") != -1))
+      multipleMediaTypes = true;
+
+    for (let command of Array.slice(this._commands.childNodes)) {
+      command.hidden = true;
+    }
+
     let optionsAvailable = false;
-    for (let i = 0; i < this._commands.childElementCount; i++) {
-      let command = this._commands.childNodes[i];
-      command.hidden = true;
+    for (let command of Array.slice(this._commands.childNodes)) {
+      let types = command.getAttribute("type").split(",");
+      let lowPriority = (command.hasAttribute("priority") &&
+        command.getAttribute("priority") == "low");
+      let searchTextItem = (command.id == "context-search");
 
-      let types = command.getAttribute("type").split(/\s+/);
+      // filter low priority items if we have more than one media type.
+      if (multipleMediaTypes && lowPriority)
+        continue;
+
       for (let i = 0; i < types.length; i++) {
         if (contentTypes.indexOf(types[i]) != -1) {
+          // If this is the special search text item, we need to set its label dynamically.
+          if (searchTextItem && !ContextCommands.searchTextSetup(command, this._popupState.string)) {
+            break;
+          }
           optionsAvailable = true;
           command.hidden = false;
           break;
         }
       }
     }
 
     if (!optionsAvailable) {
       this._popupState = null;
       return false;
     }
 
-
     this._menuPopup.show(this._popupState);
 
     let event = document.createEvent("Events");
     event.initEvent("CancelTouchSequence", true, false);
     if (this._popupState.target) {
       this._popupState.target.dispatchEvent(event);
     }
     return true;
--- a/browser/metro/base/content/helperui/SelectionHelperUI.js
+++ b/browser/metro/base/content/helperui/SelectionHelperUI.js
@@ -221,19 +221,16 @@ var SelectionHelperUI = {
 
   /*
    * openEditSession
    * 
    * Attempts to select underlying text at a point and begins editing
    * the section.
    */
   openEditSession: function openEditSession(aMessage) {
-    if (!this.canHandle(aMessage))
-      return;
-
      /*
      * aMessage - from _onContentContextMenu in ContextMenuHandler
      *  name: aMessage.name,
      *  target: aMessage.target
      *  json:
      *   types: [],
      *   label: "",
      *   linkURL: "",
@@ -261,31 +258,51 @@ var SelectionHelperUI = {
       "Browser:SelectionStart",
       { xPos: this._popupState.xPos,
         yPos: this._popupState.yPos });
 
     this._setupDebugOptions();
   },
 
   /*
+   * attachEditSession
+   * 
+   * Attaches to existing selection and begins editing.
+   */
+  attachEditSession: function attachEditSession(aMessage) {
+    this._popupState = aMessage.json;
+    this._popupState._target = aMessage.target;
+
+    this._init();
+
+    Util.dumpLn("enableSelection target:", this._popupState._target);
+
+    // Set the track bounds for each marker NIY
+    this.startMark.setTrackBounds(this._popupState.xPos, this._popupState.yPos);
+    this.endMark.setTrackBounds(this._popupState.xPos, this._popupState.yPos);
+
+    // Send this over to SelectionHandler in content, they'll message us
+    // back with information on the current selection.
+    this._popupState._target.messageManager.sendAsyncMessage(
+      "Browser:SelectionAttach",
+      { xPos: this._popupState.xPos,
+        yPos: this._popupState.yPos });
+
+    this._setupDebugOptions();
+  },
+
+  /*
    * canHandle
    *
    * Determines if we can handle a ContextMenuHandler message.
    */
   canHandle: function canHandle(aMessage) {
-    // Reject empty text inputs, nothing to do here
-    if (aMessage.json.types.indexOf("input-empty") != -1) {
-      return false;
-    }
-    // Input with text or general text in a page
-    if (aMessage.json.types.indexOf("content-text") == -1 &&
-        aMessage.json.types.indexOf("input-text") == -1) {
-      return false;
-    }
-    return true;
+    if (aMessage.json.types.indexOf("content-text") != -1)
+      return true;
+    return false;
   },
 
   /*
    * isActive
    *
    * Determines if an edit session is currently active.
    */
   isActive: function isActive() {
--- a/browser/metro/base/content/input.js
+++ b/browser/metro/base/content/input.js
@@ -160,18 +160,16 @@ var TouchModule = {
     this._doDragStop();
 
     // Kinetic panning may have already been active or drag stop above may have
     // made kinetic panning active.
     this._kinetic.end();
 
     this._targetScrollbox = null;
     this._targetScrollInterface = null;
-
-    this._cleanClickBuffer();
   },
 
   _onContextMenu: function _onContextMenu(aEvent) {
     // Special case when running on the desktop, fire off
     // a edge ui event when we get the contextmenu event.
     if (this._treatMouseAsTouch) {
       let event = document.createEvent("Events");
       event.initEvent("MozEdgeUIGesture", true, false);
--- a/browser/metro/locales/en-US/chrome/browser.dtd
+++ b/browser/metro/locales/en-US/chrome/browser.dtd
@@ -2,49 +2,33 @@
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <!ENTITY urlbar.emptytext      "Enter Search or Address">
 <!ENTITY urlbar.accesskey      "d">
 
 <!ENTITY back.label            "Back">
 <!ENTITY forward.label         "Forward">
-<!ENTITY reload.label          "Reload">
-<!ENTITY stop.label            "Stop">
-<!ENTITY go.label              "Go">
-<!ENTITY star.label            "Star">
-
 <!ENTITY toggleTabsOnly.label  "Always show tabs">
 <!ENTITY showTabs.label        "Show Tabs">
 <!ENTITY newtab.label          "New Tab">
 <!ENTITY closetab.label        "Close Tab">
 <!ENTITY undoclosetab.label    "Undo Close Tab">
 
-<!ENTITY cut.label             "Cut">
-<!ENTITY copy.label            "Copy">
-<!ENTITY copyAll.label         "Copy All">
-<!ENTITY copylink.label        "Copy Link Location">
-<!ENTITY paste.label           "Paste">
-<!ENTITY pasteAndGo.label      "Paste &amp; Go">
-<!ENTITY delete.label          "Delete">
-<!ENTITY selectAll.label       "Select All">
-
 <!ENTITY appbarFindInPage.label    "Find in Page">
 <!ENTITY appbarViewOnDesktop.label "View on Desktop">
 
 <!ENTITY startTopSitesHeader.label        "Top Sites">
 <!ENTITY startBookmarksHeader.label       "Bookmarks">
 <!ENTITY startHistoryHeader.label         "Recent History">
 <!ENTITY startRemoteTabsHeader.label      "Tabs from Other Devices">
 
 <!ENTITY autocompleteResultsHeader.label  "Your Results">
 <!ENTITY autocompleteSearchesHeader.label "Internet Searches"> 
 
-<!ENTITY selectHelper.done         "Done">
-
 <!ENTITY downloadsHeader.label     "Downloads">
 <!ENTITY downloadShowPage.label    "Go to Page">
 <!ENTITY downloadShow2.label       "Find">
 <!ENTITY downloadOpen2.label       "Open">
 <!ENTITY downloadCancel.label      "Cancel">
 <!ENTITY downloadPause.label       "Pause">
 <!ENTITY downloadResume.label      "Resume">
 <!ENTITY downloadRetry.label       "Retry">
@@ -71,38 +55,37 @@
 <!ENTITY consoleMessages.label     "Messages">
 <!ENTITY consoleCodeEval.label     "Code:">
 <!ENTITY consoleClear.label        "Clear">
 <!ENTITY consoleEvaluate.label     "…">
 <!ENTITY consoleErrFile.label      "Source File:">
 <!ENTITY consoleErrLine.label      "Line:">
 <!ENTITY consoleErrColumn.label    "Column:">
 
-<!ENTITY contextOpenInNewTab.label    "Open Link in New Tab">
-<!ENTITY contextViewInNewTab.label    "View in New Tab">
-<!ENTITY contextCopyLink.label        "Copy Link">
-<!ENTITY contextCopyEmail.label       "Copy Email Address">
-<!ENTITY contextCopyPhone.label       "Copy Phone Number">
-<!ENTITY contextCopyImage.label       "Copy Image">
-<!ENTITY contextShareLink.label       "Share Link">
-<!ENTITY contextShareImage.label      "Share Image">
-<!ENTITY contextBookmarkLink.label    "Bookmark Link">
-<!ENTITY contextSaveVideo.label       "Save Video">
-<!ENTITY contextSaveVideoTo.label       "Save Video To">
-<!ENTITY contextShareVideo.label      "Share Video">
-<!ENTITY contextPlayMedia.label       "Play">
-<!ENTITY contextPauseMedia.label      "Pause">
-<!ENTITY contextVideoTab.label        "Open In New Tab">
-<!ENTITY contextEditBookmark.label    "Edit">
-<!ENTITY contextRemoveBookmark.label  "Remove">
-<!ENTITY contextShortcutBookmark.label "Add to Home Screen">
+<!--  TEXT CONTEXT MENU -->
+<!ENTITY contextTextCopy.label             "Copy">
+<!ENTITY contextTextPaste.label            "Paste">
+<!-- unique item that is only added to the url bar context menu -->
+<!ENTITY contextTextPasteAndGo.label       "Paste &amp; go">
+<!ENTITY contextTextSelect.label           "Select">
+<!ENTITY contextTextSelectAll.label        "Select all">
 
-<!-- * image context menu * -->
+<!-- LINK CONTEXT MENU -->
+<!ENTITY contextOpenLinkTab.label          "Open link in new tab">
+<!ENTITY contextCopyLinkHref.label         "Copy link">
+<!ENTITY contextBookmarkLinkHref.label     "Bookmark link">
+
+<!-- IMAGE CONTEXT MENU -->
 <!-- l10n: contextSaveImageLib saves an image to the users "pictures library"
      (Explorer -> left hand side navigation ppane -> Libraries -> Pictures) -->
-<!ENTITY contextSaveImageLib.label        "Save to pictures library">
-<!ENTITY contextCopyImage.label           "Copy image">
-<!ENTITY contextCopyImageLocation.label   "Copy image location">
-<!ENTITY contextOpenImageTab.label        "Open image in new tab">
+<!ENTITY contextSaveImageLib.label         "Save to picture library">
+<!ENTITY contextCopyImage.label            "Copy image">
+<!ENTITY contextCopyImageLocation.label    "Copy image location">
+<!ENTITY contextOpenImageTab.label         "Open image in new tab">
+
+<!-- VIDEO CONTEXT MENU -->
+<!ENTITY contextSaveVideoLib.label         "Save to video library">
+<!ENTITY contextCopyVideoLocation.label    "Copy video location">
+<!ENTITY contextOpenVideoTab.label         "Open video in new tab">
 
 <!ENTITY pageactions.password.forget "Forget Password">
 <!ENTITY pageactions.reset           "Clear Site Preferences">
 <!ENTITY pageactions.charEncoding    "Character Encoding">
--- a/browser/metro/locales/en-US/chrome/browser.properties
+++ b/browser/metro/locales/en-US/chrome/browser.properties
@@ -5,16 +5,19 @@
 # Default search engine
 browser.search.defaultenginename=Bing
 
 # Search engine order (order displayed in the search bar dropdown)s
 browser.search.order.1=Bing
 browser.search.order.2=Google
 browser.search.order.3=Yahoo
 
+# l10n: search context menu item text will be: |Search (browser.search.defaultenginename) for ".."
+browser.search.contextTextSearchLabel=Search %S for ".."
+
 # Settings Charms
 aboutCharm1=About
 optionsCharm=Options
 helpOnlineCharm=Help (online)
 
 # General
 browserForSaveLocation=Save Location
 
@@ -130,10 +133,11 @@ clearPrivateData.message=Delete your bro
 # This is not a string to translate. If users frequently use the "Character Encoding"
 # menu, set this to "true". Otherwise, you can leave it as "false".
 browser.menu.showCharacterEncoding=false
 
 # LOCALIZATION NOTE (intl.charsetmenu.browser.static): Set to a series of comma separated
 # values for charsets that the user can select from in the Character Encoding menu.
 intl.charsetmenu.browser.static=iso-8859-1,utf-8,x-gbk,big5,iso-2022-jp,shift_jis,euc-jp
 
-#Text Selection
+# Selection alerts
 selectionHelper.textCopied=Text copied to clipboard
+selectionHelper.linkCopied=Link copied to clipboard