Bug 970746 - Context menu should include option to for search link text on search engine. r=bmcbride
authorValentin Tsatskin <vtsatskin@mozilla.com>
Mon, 10 Feb 2014 18:53:00 -0500
changeset 191489 120a6f93c1d451fa51b6a187f7563bdb97e6ccde
parent 191488 c1651a5658df0990eba30aa8c1dff36db201eff7
child 191490 3a8c5cbeccea6e2581d5e2b22194f2a9f5f47d0f
push id474
push userasasaki@mozilla.com
push dateMon, 02 Jun 2014 21:01:02 +0000
treeherdermozilla-release@967f4cf1b31c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbmcbride
bugs970746
milestone30.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 970746 - Context menu should include option to for search link text on search engine. r=bmcbride
browser/base/content/browser-context.inc
browser/base/content/nsContextMenu.js
browser/base/content/test/general/browser.ini
browser/base/content/test/general/browser_bug970746.js
browser/base/content/test/general/browser_bug970746.xhtml
browser/base/content/test/general/test_contextmenu.html
--- a/browser/base/content/browser-context.inc
+++ b/browser/base/content/browser-context.inc
@@ -288,17 +288,17 @@
                 accesskey="&selectAllCmd.accesskey;"
                 command="cmd_selectAll"/>
       <menuseparator id="context-sep-selectall"/>
       <menuitem id="context-keywordfield"
                 label="&keywordfield.label;"
                 accesskey="&keywordfield.accesskey;"
                 oncommand="AddKeywordForSearchField();"/>
       <menuitem id="context-searchselect"
-                oncommand="BrowserSearch.loadSearchFromContext(getBrowserSelection());"/>
+                oncommand="BrowserSearch.loadSearchFromContext(this.searchTerms);"/>
       <menuitem id="context-shareselect"
                 label="&shareSelectCmd.label;"
                 accesskey="&shareSelectCmd.accesskey;"
                 oncommand="gContextMenu.shareSelect(getBrowserSelection());"/>
       <menuseparator id="frame-sep"/>
       <menu id="frame" label="&thisFrameMenu.label;" accesskey="&thisFrameMenu.accesskey;">
         <menupopup>
           <menuitem id="context-showonlythisframe"
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -27,17 +27,17 @@ nsContextMenu.prototype = {
     }
 
     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();
     this.onPlainTextLink = false;
 
     // Initialize (disable/remove) menu items.
     this.initItems();
   },
 
   hiding: function CM_hiding() {
@@ -263,41 +263,44 @@ nsContextMenu.prototype = {
     document.getElementById("context-viewbgimage")
             .disabled = !this.hasBGImage;
 
     this.showItem("context-viewimageinfo", this.onImage);
     this.showItem("context-viewimagedesc", this.onImage && this.imageDescURL !== "");
   },
 
   initMiscItems: function CM_initMiscItems() {
-    var isTextSelected = this.isTextSelected;
-
     // Use "Bookmark This Link" if on a link.
     this.showItem("context-bookmarkpage",
                   !(this.isContentSelected || this.onTextInput || this.onLink ||
                     this.onImage || this.onVideo || this.onAudio || this.onSocial));
     this.showItem("context-bookmarklink", (this.onLink && !this.onMailtoLink &&
                                            !this.onSocial) || this.onPlainTextLink);
-    this.showItem("context-searchselect", isTextSelected);
     this.showItem("context-keywordfield",
                   this.onTextInput && this.onKeywordField);
     this.showItem("frame", this.inFrame);
 
+    let showSearchSelect = (this.isTextSelected || this.onLink) && !this.onImage;
+    this.showItem("context-searchselect", showSearchSelect);
+    if (showSearchSelect) {
+      this.formatSearchContextItem();
+    }
+
     // srcdoc cannot be opened separately due to concerns about web
     // content with about:srcdoc in location bar masquerading as trusted
     // chrome/addon content.
     // No need to also test for this.inFrame as this is checked in the parent
     // submenu.
     this.showItem("context-showonlythisframe", !this.inSrcdocFrame);
     this.showItem("context-openframeintab", !this.inSrcdocFrame);
     this.showItem("context-openframe", !this.inSrcdocFrame);
     this.showItem("context-bookmarkframe", !this.inSrcdocFrame);
     this.showItem("open-frame-sep", !this.inSrcdocFrame);
 
-    this.showItem("frame-sep", this.inFrame && isTextSelected);
+    this.showItem("frame-sep", this.inFrame && this.isTextSelected);
 
     // Hide menu entries for images, show otherwise
     if (this.inFrame) {
       if (mimeTypeIsTextBased(this.target.ownerDocument.contentType))
         this.isFrameImage.removeAttribute('hidden');
       else
         this.isFrameImage.setAttribute('hidden', 'true');
     }
@@ -535,16 +538,18 @@ nsContextMenu.prototype = {
     this.inSrcdocFrame     = false;
     this.inSyntheticDoc    = false;
     this.hasBGImage        = false;
     this.bgImageURL        = "";
     this.onEditableArea    = false;
     this.isDesignMode      = false;
     this.onCTPPlugin       = false;
     this.canSpellCheck     = false;
+    this.textSelected      = getBrowserSelection();
+    this.isTextSelected    = this.textSelected.length != 0;
 
     // Remember the node that was clicked.
     this.target = aNode;
 
     // If this is a remote context menu event, use the information from
     // gContextMenuContentData instead.
     if (this.isRemote) {
       this.browser = gContextMenuContentData.browser;
@@ -1437,49 +1442,16 @@ nsContextMenu.prototype = {
         if (!text || !text.match(/\S/))
           text = this.linkURL;
       }
     }
 
     return text;
   },
 
-  // Get selected text. Only display the first 15 chars.
-  isTextSelection: function() {
-    // Get 16 characters, so that we can trim the selection if it's greater
-    // than 15 chars
-    var selectedText = getBrowserSelection(16);
-
-    if (!selectedText)
-      return false;
-
-    if (selectedText.length > 15)
-      selectedText = selectedText.substr(0,15) + this.ellipsis;
-
-    // Use the current engine if the search bar is visible, the default
-    // engine otherwise.
-    var engineName = "";
-    var ss = Cc["@mozilla.org/browser/search-service;1"].
-             getService(Ci.nsIBrowserSearchService);
-    if (isElementVisible(BrowserSearch.searchBar))
-      engineName = ss.currentEngine.name;
-    else
-      engineName = ss.defaultEngine.name;
-
-    // format "Search <engine> for <selection>" string to show in menu
-    var menuLabel = gNavigatorBundle.getFormattedString("contextMenuSearch",
-                                                        [engineName,
-                                                         selectedText]);
-    document.getElementById("context-searchselect").label = menuLabel;
-    document.getElementById("context-searchselect").accessKey =
-             gNavigatorBundle.getString("contextMenuSearch.accesskey"); 
-
-    return true;
-  },
-
   // Returns true if anything is selected.
   isContentSelection: function() {
     return !document.commandDispatcher.focusedWindow.getSelection().isCollapsed;
   },
 
   toString: function () {
     return "contextMenu.target     = " + this.target + "\n" +
            "contextMenu.onImage    = " + this.onImage + "\n" +
@@ -1683,10 +1655,39 @@ nsContextMenu.prototype = {
                     getService(Ci.nsIClipboardHelper);
     clipboard.copyString(this.mediaURL, document);
   },
 
   get imageURL() {
     if (this.onImage)
       return this.mediaURL;
     return "";
+  },
+
+  // Formats the 'Search <engine> for "<selection or link text>"' context menu.
+  formatSearchContextItem: function() {
+    var menuItem = document.getElementById("context-searchselect");
+    var selectedText = this.onLink ? this.linkText() : this.textSelected;
+
+    // Store searchTerms in context menu item so we know what to search onclick
+    menuItem.searchTerms = selectedText;
+
+    if (selectedText.length > 15)
+      selectedText = selectedText.substr(0,15) + this.ellipsis;
+
+    // Use the current engine if the search bar is visible, the default
+    // engine otherwise.
+    var engineName = "";
+    var ss = Cc["@mozilla.org/browser/search-service;1"].
+             getService(Ci.nsIBrowserSearchService);
+    if (isElementVisible(BrowserSearch.searchBar))
+      engineName = ss.currentEngine.name;
+    else
+      engineName = ss.defaultEngine.name;
+
+    // format "Search <engine> for <selection>" string to show in menu
+    var menuLabel = gNavigatorBundle.getFormattedString("contextMenuSearch",
+                                                        [engineName,
+                                                         selectedText]);
+    menuItem.label = menuLabel;
+    menuItem.accessKey = gNavigatorBundle.getString("contextMenuSearch.accesskey");
   }
 };
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -4,16 +4,17 @@ support-files =
   accounts_testRemoteCommands.html
   alltabslistener.html
   app_bug575561.html
   app_subframe_bug575561.html
   authenticate.sjs
   browser_bug479408_sample.html
   browser_bug678392-1.html
   browser_bug678392-2.html
+  browser_bug970746.xhtml
   browser_registerProtocolHandler_notification.html
   browser_star_hsts.sjs
   browser_tab_dragdrop2_frame1.xul
   bug564387.html
   bug564387_video1.ogv
   bug564387_video1.ogv^headers^
   bug592338.html
   bug792517-2.html
@@ -197,16 +198,17 @@ skip-if = os == "mac" # Intermittent fai
 [browser_bug817947.js]
 [browser_bug822367.js]
 [browser_bug832435.js]
 [browser_bug839103.js]
 [browser_bug880101.js]
 [browser_bug882977.js]
 [browser_bug902156.js]
 [browser_bug906190.js]
+[browser_bug970746.js]
 [browser_canonizeURL.js]
 [browser_contentAreaClick.js]
 [browser_contextSearchTabPosition.js]
 skip-if = os == "mac" # bug 967013, bug 926729
 [browser_ctrlTab.js]
 [browser_customize_popupNotification.js]
 [browser_datareporting_notification.js]
 run-if = datareporting
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug970746.js
@@ -0,0 +1,104 @@
+/* Make sure context menu includes option to search hyperlink text on search engine */
+
+function test() {
+  waitForExplicitFinish();
+
+  gBrowser.selectedTab = gBrowser.addTab();
+  
+  gBrowser.selectedBrowser.addEventListener("load", function() {
+    gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+
+    let doc = gBrowser.contentDocument;
+    let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
+    let ellipsis = "\u2026";
+
+    // Tests if the "Search <engine> for '<some terms>'" context menu item is shown for the
+    // given query string of an element. Tests to make sure label includes the proper search terms.
+    //
+    // Options:
+    // 
+    //   id: The id of the element to test.
+    //   isSelected: Flag to enable selection (text hilight) the contents of the element
+    //   shouldBeShown: The display state of the menu item
+    //   expectedLabelContents: The menu item label should contain a portion of this string.
+    //                          Will only be tested if shouldBeShown is true.
+
+    let testElement = function(opts) {
+      let element = doc.getElementById(opts.id);
+      document.popupNode = element;
+
+      let selection = content.getSelection();
+      selection.removeAllRanges();
+
+      if(opts.isSelected) {
+        selection.selectAllChildren(element);
+      }
+
+      let contextMenu = new nsContextMenu(contentAreaContextMenu);
+      let menuItem = document.getElementById("context-searchselect");
+
+      is(document.getElementById("context-searchselect").hidden, !opts.shouldBeShown, "search context menu item is shown for  '#" + opts.id + "' and selected is '" + opts.isSelected + "'");
+
+      if(opts.shouldBeShown) {
+        ok(menuItem.label.contains(opts.expectedLabelContents), "Menu item text '" + menuItem.label  + "' contains the correct search terms '" + opts.expectedLabelContents  + "'");
+      }
+    }
+
+    testElement({
+      id: "link",
+      isSelected: true,
+      shouldBeShown: true,
+      expectedLabelContents: "I'm a link!",
+    });
+    testElement({
+      id: "link",
+      isSelected: false,
+      shouldBeShown: true,
+      expectedLabelContents: "I'm a link!",
+    });
+
+    testElement({
+      id: "longLink",
+      isSelected: true,
+      shouldBeShown: true,
+      expectedLabelContents: "I'm a really lo" + ellipsis,
+    });
+    testElement({
+      id: "longLink",
+      isSelected: false,
+      shouldBeShown: true,
+      expectedLabelContents: "I'm a really lo" + ellipsis,
+    });
+
+    testElement({
+      id: "plainText",
+      isSelected: true,
+      shouldBeShown: true,
+      expectedLabelContents: "Right clicking " + ellipsis,
+    });
+    testElement({
+      id: "plainText",
+      isSelected: false,
+      shouldBeShown: false,
+    });
+
+    testElement({
+      id: "mixedContent",
+      isSelected: true,
+      shouldBeShown: true,
+      expectedLabelContents: "I'm some text, " + ellipsis,
+    });
+    testElement({
+      id: "mixedContent",
+      isSelected: false,
+      shouldBeShown: false,
+    });
+
+    // cleanup
+    document.popupNode = null;
+    gBrowser.removeCurrentTab();
+    finish();
+  }, true);
+
+  content.location = "http://mochi.test:8888/browser/browser/base/content/test/general/browser_bug970746.xhtml";
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug970746.xhtml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+	<body>
+		<a href="http://mozilla.org" id="link">I'm a link!</a>
+		<a href="http://mozilla.org" id="longLink">I'm a really long link and I should be truncated.</a>
+
+		<span id="plainText">
+			Right clicking me when I'm selected should show the menu item.
+		</span>
+		<span id="mixedContent">
+			I'm some text, and <a href="http://mozilla.org">I'm a link!</a>
+		</span>
+	</body>
+</html>
\ No newline at end of file
--- a/browser/base/content/test/general/test_contextmenu.html
+++ b/browser/base/content/test/general/test_contextmenu.html
@@ -113,34 +113,38 @@ function runTest(testNum) {
         // Context menu for text link
         if (perWindowPrivateBrowsing) {
           checkContextMenu(["context-openlinkintab", true,
                             "context-openlink",      true,
                             "context-openlinkprivate", true,
                             "---",                   null,
                             "context-bookmarklink",  true,
                             "context-savelink",      true,
-                            "context-copylink",      true
+                            "context-copylink",      true,
+                            "context-searchselect",  true
                            ].concat(inspectItems));
         } else {
           checkContextMenu(["context-openlinkintab", true,
                             "context-openlink",      true,
                             "---",                   null,
                             "context-bookmarklink",  true,
                             "context-savelink",      true,
-                            "context-copylink",      true
+                            "context-copylink",      true,
+                            "context-searchselect",  true
                            ].concat(inspectItems));
         }
         closeContextMenu();
         openContextMenuFor(mailto); // Invoke context menu for next test.
         break;
 
     case 4:
         // Context menu for text mailto-link
-        checkContextMenu(["context-copyemail", true].concat(inspectItems));
+        checkContextMenu(["context-copyemail", true,
+                          "context-searchselect", true
+                        ].concat(inspectItems));
         closeContextMenu();
         openContextMenuFor(img); // Invoke context menu for next test.
         break;
 
     case 5:
         // Context menu for an image
         checkContextMenu(["context-viewimage",            true,
                           "context-copyimage-contents",   true,