Bug 515512 - Text urls that don't have the leading protocol should have the link context menu options when selected. r=dao
authorNochum Sossonko <highmind63@gmail.com>
Sat, 31 Oct 2009 08:59:23 +0100
changeset 34437 c0dbbcfdb583a9061b2f88286668564863c90638
parent 34436 8132c52d4dee2361f3457996ccf83dbfdd2ee1d7
child 34438 7e0336a4f6fdfd8a911c9a0f19777de9799dc868
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)
reviewersdao
bugs515512
milestone1.9.3a1pre
Bug 515512 - Text urls that don't have the leading protocol should have the link context menu options when selected. r=dao
browser/base/content/nsContextMenu.js
browser/base/content/test/Makefile.in
browser/base/content/test/browser_plainTextLinks.js
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -139,28 +139,74 @@ nsContextMenu.prototype = {
       var mailtoHandler = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
                           getService(Ci.nsIExternalProtocolService).
                           getProtocolHandlerInfo("mailto");
       isMailtoInternal = (!mailtoHandler.alwaysAskBeforeHandling &&
                           mailtoHandler.preferredAction == Ci.nsIHandlerInfo.useHelperApp &&
                           (mailtoHandler.preferredApplicationHandler instanceof Ci.nsIWebHandlerApp));
     }
 
-    // time to do some bad things and see if we've highlighted a URL that isn't actually linked
+    // Time to do some bad things and see if we've highlighted a URL that
+    // isn't actually linked.
+    var onPlainTextLink = false;
     if (this.isTextSelected) {
-      // ok, we have some text, let's figure out if it looks like a URL
-      var someText = document.commandDispatcher.focusedWindow
-                             .getSelection().toString();
-      try {
-       var uri = makeURI(someText);
+      // Ok, we have some text, let's figure out if it looks like a URL.
+      let selection =  document.commandDispatcher.focusedWindow
+                               .getSelection();
+      let linkText = selection.toString().trim();
+      let uri;
+      if (/^(?:https?|ftp):/i.test(linkText)) {
+        try {
+          uri = makeURI(linkText);
+        } catch (ex) {}
       }
-      catch (ex) { }
- 
-      var onPlainTextLink = false;
-      if (uri && /^(https?|ftp)$/i.test(uri.scheme) && uri.host) {
+      // Check if this could be a valid url, just missing the protocol.
+      else if (/^(?:\w+\.)+\D\S*$/.test(linkText)) {
+        // Now let's see if this is an intentional link selection. Our guess is
+        // based on whether the selection begins/ends with whitespace or is
+        // preceded/followed by a non-word character.
+
+        // selection.toString() trims trailing whitespace, so we look for
+        // that explicitly in the first and last ranges.
+        let beginRange = selection.getRangeAt(0);
+        let delimitedAtStart = /^\s/.test(beginRange);
+        if (!delimitedAtStart) {
+          let container = beginRange.startContainer;
+          let offset = beginRange.startOffset;
+          if (container.nodeType == Node.TEXT_NODE && offset > 0)
+            delimitedAtStart = /\W/.test(container.textContent[offset - 1]);
+          else
+            delimitedAtStart = true;
+        }
+
+        let delimitedAtEnd = false;
+        if (delimitedAtStart) {
+          let endRange = selection.getRangeAt(selection.rangeCount - 1);
+          delimitedAtEnd = /\s$/.test(endRange);
+          if (!delimitedAtEnd) {
+            let container = endRange.endContainer;
+            let offset = endRange.endOffset;
+            if (container.nodeType == Node.TEXT_NODE &&
+                offset < container.textContent.length)
+              delimitedAtEnd = /\W/.test(container.textContent[offset]);
+            else
+              delimitedAtEnd = true;
+          }
+        }
+
+        if (delimitedAtStart && delimitedAtEnd) {
+          let uriFixup = Cc["@mozilla.org/docshell/urifixup;1"]
+                           .getService(Ci.nsIURIFixup);
+          try {
+            uri = uriFixup.createFixupURI(linkText, uriFixup.FIXUP_FLAG_NONE);
+          } catch (ex) {}
+        }
+      }
+
+      if (uri && uri.host) {
         this.linkURI = uri;
         this.linkURL = this.linkURI.spec;
         onPlainTextLink = true;
       }
     }
  
     var shouldShow = this.onSaveableLink || isMailtoInternal || onPlainTextLink;
     this.showItem("context-openlink", shouldShow);
--- a/browser/base/content/test/Makefile.in
+++ b/browser/base/content/test/Makefile.in
@@ -134,16 +134,17 @@ include $(topsrcdir)/config/rules.mk
                  browser_scope.js \
                  browser_overflowScroll.js \
                  browser_sanitizeDialog.js \
                  browser_tabs_owner.js \
                  browser_bug491431.js \
                  browser_bug304198.js \
                  browser_drag.js \
                  browser_relatedTabs.js \
+                 browser_plainTextLinks.js \
                  browser_contextSearchTabPosition.js \
                  browser_NetworkPrioritizer.js \
     $(NULL)
 
 ifneq (cocoa,$(MOZ_WIDGET_TOOLKIT))
 _BROWSER_FILES += browser_bug462289.js
 else
 _BROWSER_FILES += browser_customize.js
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/browser_plainTextLinks.js
@@ -0,0 +1,95 @@
+let doc, range, selection;
+function setSelection(el1, el2, index1, index2) {
+  selection.removeAllRanges();
+  range.setStart(el1, index1);
+  range.setEnd(el2, index2);
+  selection.addRange(range);
+}
+
+function initContextMenu() {
+  document.popupNode = doc.getElementsByTagName("DIV")[0];
+  let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
+  let contextMenu = new nsContextMenu(contentAreaContextMenu, gBrowser);
+  return contextMenu;
+}
+
+function testExpected(expected, msg) {
+  initContextMenu();
+  let linkMenuItem = document.getElementById("context-openlinkincurrent");
+  is(linkMenuItem.hidden, expected, msg);
+}
+
+function testLinkExpected(expected, msg) {
+  let contextMenu = initContextMenu();
+  is(contextMenu.linkURL, expected, msg);
+}
+
+function runSelectionTests() {
+  let mainDiv = doc.createElement("div");
+  let div = doc.createElement("div");
+  let div2 = doc.createElement("div");
+  let span1 = doc.createElement("span");
+  let span2 = doc.createElement("span");
+  let span3 = doc.createElement("span");
+  let p1 = doc.createElement("p");
+  let p2 = doc.createElement("p");
+  span1.textContent = "http://index.";
+  span2.textContent = "example.com example.com";
+  span3.textContent = " - Test";
+  p1.textContent = "mailto:test.com ftp.example.com";
+  p2.textContent = "example.com   -";
+  div.appendChild(span1);
+  div.appendChild(span2);
+  div.appendChild(span3);
+  div.appendChild(p1);
+  div.appendChild(p2);
+  let p3 = doc.createElement("p");
+  p3.textContent = "main.example.com";
+  div2.appendChild(p3);
+  mainDiv.appendChild(div);
+  mainDiv.appendChild(div2);
+  doc.body.appendChild(mainDiv);
+  setSelection(span1.firstChild, span2.firstChild, 0, 11);
+  testExpected(false, "The link context menu should show for http://www.example.com");
+  setSelection(span1.firstChild, span2.firstChild, 7, 11);
+  testExpected(false, "The link context menu should show for www.example.com");
+  setSelection(span1.firstChild, span2.firstChild, 8, 11);
+  testExpected(true, "The link context menu should not show for ww.example.com");
+  setSelection(span2.firstChild, span2.firstChild, 0, 11);
+  testExpected(false, "The link context menu should show for example.com");
+  testLinkExpected("http://example.com/", "url for example.com selection should not prepend www");
+  setSelection(span2.firstChild, span2.firstChild, 11, 23);
+  testExpected(false, "The link context menu should show for example.com");
+  setSelection(span2.firstChild, span2.firstChild, 0, 10);
+  testExpected(true, "Link options should not show for selection that's not at a word boundary");
+  setSelection(span2.firstChild, span3.firstChild, 12, 7);
+  testExpected(true, "Link options should not show for selection that has whitespace");
+  setSelection(span2.firstChild, span2.firstChild, 12, 19);
+  testExpected(true, "Link options should not show unless a url is selected");
+  setSelection(p1.firstChild, p1.firstChild, 0, 15);
+  testExpected(true, "Link options should not show for mailto: links");
+  setSelection(p1.firstChild, p1.firstChild, 16, 31);
+  testExpected(false, "Link options should show for ftp.example.com");
+  testLinkExpected("ftp://ftp.example.com/", "ftp.example.com should be preceeded with ftp://");
+  setSelection(p2.firstChild, p2.firstChild, 0, 14);
+  testExpected(false, "Link options should show for www.example.com  ");
+  selection.selectAllChildren(div2);
+  testExpected(false, "Link options should show for triple-click selections");
+  gBrowser.removeCurrentTab();
+  finish();
+}
+
+function test() {
+  waitForExplicitFinish();
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function() {
+    gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+    doc = content.document;
+    range = doc.createRange();
+    selection = content.getSelection();
+    waitForFocus(runSelectionTests, content);
+  }, true);
+
+  content.location =
+    "data:text/html,Test For Non-Hyperlinked url selection";
+}