Bug 469434 - Links in "view source" should have "copy link location" in context menu; r=dao
authorGeoff Lankow <geoff@darktrojan.net>
Sun, 19 Feb 2012 22:47:06 +1300
changeset 88627 5c99485521cd650ac874b1734701c8b3aa2ef771
parent 88626 ab7d4cb6fb4382aec29ba10bbb25ca962fec548c
child 88628 f21351397aa76d275e67b0f708e6799fa8e82819
push idunknown
push userunknown
push dateunknown
reviewersdao
bugs469434
milestone13.0a1
Bug 469434 - Links in "view source" should have "copy link location" in context menu; r=dao
toolkit/components/viewsource/content/viewPartialSource.xul
toolkit/components/viewsource/content/viewSource.js
toolkit/components/viewsource/content/viewSource.xul
toolkit/components/viewsource/test/browser/Makefile.in
toolkit/components/viewsource/test/browser/browser_contextmenu.js
toolkit/components/viewsource/test/browser/head.js
toolkit/locales/en-US/chrome/global/viewSource.dtd
--- a/toolkit/components/viewsource/content/viewPartialSource.xul
+++ b/toolkit/components/viewsource/content/viewPartialSource.xul
@@ -106,20 +106,29 @@
     <key id="key_textZoomEnlarge2" key="&textEnlarge.commandkey2;" command="cmd_textZoomEnlarge" modifiers="accel"/>
     <key id="key_textZoomEnlarge3" key="&textEnlarge.commandkey3;" command="cmd_textZoomEnlarge" modifiers="accel"/>
     <key id="key_textZoomReduce"  key="&textReduce.commandkey;" command="cmd_textZoomReduce" modifiers="accel"/>
     <key id="key_textZoomReduce2"  key="&textReduce.commandkey2;" command="cmd_textZoomReduce" modifiers="accel"/>
     <key id="key_textZoomReset" key="&textReset.commandkey;" command="cmd_textZoomReset" modifiers="accel"/>
     <key id="key_textZoomReset2" key="&textReset.commandkey2;" command="cmd_textZoomReset" modifiers="accel"/>
   </keyset>
 
-  <menupopup id="viewSourceContextMenu">
+  <menupopup id="viewSourceContextMenu"
+             onpopupshowing="contextMenuShowing();">
     <menuitem id="cMenu_findAgain"/>
     <menuseparator/>
     <menuitem id="cMenu_copy"/>
+    <menuitem id="context-copyLink"
+              label="&copyLinkCmd.label;"
+              accesskey="&copyLinkCmd.accesskey;"
+              oncommand="contextMenuCopyLinkOrEmail();"/>
+    <menuitem id="context-copyEmail"
+              label="&copyEmailCmd.label;"
+              accesskey="&copyEmailCmd.accesskey;"
+              oncommand="contextMenuCopyLinkOrEmail();"/>
     <menuseparator/>
     <menuitem id="cMenu_selectAll"/>
   </menupopup>
 
   <!-- Menu --> 
   <toolbox id="viewSource-toolbox">
     <menubar id="viewSource-main-menubar">
 
--- a/toolkit/components/viewsource/content/viewSource.js
+++ b/toolkit/components/viewsource/content/viewSource.js
@@ -42,17 +42,18 @@ const Cc = Components.classes;
 const Ci = Components.interfaces;
 var gPrefs = null;
 
 var gLastLineFound = '';
 var gGoToLine = 0;
 
 [
   ["gBrowser",          "content"],
-  ["gViewSourceBundle", "viewSourceBundle"]
+  ["gViewSourceBundle", "viewSourceBundle"],
+  ["gContextMenu",      "viewSourceContextMenu"]
 ].forEach(function ([name, id]) {
   window.__defineGetter__(name, function () {
     var element = document.getElementById(id);
     if (!element)
       return null;
     delete window[name];
     return window[name] = element;
   });
@@ -792,8 +793,30 @@ function FillInHTMLTooltip(tipElement)
     titleText = titleText.replace(/\r\n/g, '\n');
     titleText = titleText.replace(/\r/g, '\n');
     tipNode.setAttribute("label", titleText);
     retVal = true;
   }
   return retVal;
 }
 
+function contextMenuShowing() {
+  var isLink = false;
+  var isEmail = false;
+  if (gContextMenu.triggerNode && gContextMenu.triggerNode.localName == 'a') {
+    if (gContextMenu.triggerNode.href.indexOf('view-source:') == 0)
+      isLink = true;
+    if (gContextMenu.triggerNode.href.indexOf('mailto:') == 0)
+      isEmail = true;
+  }
+  document.getElementById('context-copyLink').hidden = !isLink;
+  document.getElementById('context-copyEmail').hidden = !isEmail;
+}
+
+function contextMenuCopyLinkOrEmail() {
+  if (!gContextMenu.triggerNode)
+    return;
+
+  var href = gContextMenu.triggerNode.href;
+  var clipboard = Cc['@mozilla.org/widget/clipboardhelper;1'].
+                  getService(Ci.nsIClipboardHelper);
+  clipboard.copyString(href.substring(href.indexOf(':') + 1));
+}
--- a/toolkit/components/viewsource/content/viewSource.xul
+++ b/toolkit/components/viewsource/content/viewSource.xul
@@ -139,31 +139,40 @@
     <key id="goBackKb2" key="&goBackCmd.commandKey;" command="Browser:Back" modifiers="accel"/>
     <key id="goForwardKb2" key="&goForwardCmd.commandKey;" command="Browser:Forward" modifiers="accel"/>
 #endif
 
   </keyset>
   
   <tooltip id="aHTMLTooltip" onpopupshowing="return FillInHTMLTooltip(document.tooltipNode);"/>
 
-  <menupopup id="viewSourceContextMenu">
+  <menupopup id="viewSourceContextMenu"
+             onpopupshowing="contextMenuShowing();">
     <menuitem id="context-back"
               label="&backCmd.label;"
               accesskey="&backCmd.accesskey;"
               command="Browser:Back"
               observes="viewSourceNavigation"/>
     <menuitem id="context-forward"
               label="&forwardCmd.label;"
               accesskey="&forwardCmd.accesskey;"
               command="Browser:Forward"
               observes="viewSourceNavigation"/>
     <menuseparator observes="viewSourceNavigation"/>
     <menuitem id="cMenu_findAgain"/>
     <menuseparator/>
     <menuitem id="cMenu_copy"/>
+    <menuitem id="context-copyLink"
+              label="&copyLinkCmd.label;"
+              accesskey="&copyLinkCmd.accesskey;"
+              oncommand="contextMenuCopyLinkOrEmail();"/>
+    <menuitem id="context-copyEmail"
+              label="&copyEmailCmd.label;"
+              accesskey="&copyEmailCmd.accesskey;"
+              oncommand="contextMenuCopyLinkOrEmail();"/>
     <menuseparator/>
     <menuitem id="cMenu_selectAll"/>
   </menupopup>
 
   <!-- Menu --> 
   <toolbox id="viewSource-toolbox">
     <menubar id="viewSource-main-menubar">
 
--- a/toolkit/components/viewsource/test/browser/Makefile.in
+++ b/toolkit/components/viewsource/test/browser/Makefile.in
@@ -42,15 +42,16 @@ VPATH     = @srcdir@
 relativesrcdir  = toolkit/components/viewsource/test/browser
 
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _BROWSER_CHROME_FILES   = \
                 browser_bug699356.js \
                 browser_bug713810.js \
+                browser_contextmenu.js \
                 browser_viewsourceprefs.js \
                 browser_viewsourceprefs_nonhtml.js \
                 head.js \
                 $(NULL)
 
 libs:: $(_BROWSER_CHROME_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/toolkit/components/viewsource/test/browser/browser_contextmenu.js
@@ -0,0 +1,83 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+let source = "data:text/html,text<link%20href='http://example.com/'%20/>more%20text<a%20href='mailto:abc@def.ghi'>email</a>";
+let gViewSourceWindow, gContextMenu, gCopyLinkMenuItem, gCopyEmailMenuItem;
+
+let expectedData = [];
+let currentTest = 0;
+let partialTestRunning = false;
+
+function test() {
+  waitForExplicitFinish();
+  openViewSourceWindow(source, onViewSourceWindowOpen);
+}
+
+function onViewSourceWindowOpen(aWindow) {
+  gViewSourceWindow = aWindow;
+
+  gContextMenu = aWindow.document.getElementById("viewSourceContextMenu");
+  gCopyLinkMenuItem = aWindow.document.getElementById("context-copyLink");
+  gCopyEmailMenuItem = aWindow.document.getElementById("context-copyEmail");
+
+  let aTags = aWindow.gBrowser.contentDocument.querySelectorAll("a[href]");
+  is(aTags[0].href, "view-source:http://example.com/", "Link has correct href");
+  is(aTags[1].href, "mailto:abc@def.ghi", "Link has correct href");
+  let spanTag = aWindow.gBrowser.contentDocument.querySelector("span");
+
+  expectedData.push([aTags[0], true, false, "http://example.com/"]);
+  expectedData.push([aTags[1], false, true, "abc@def.ghi"]);
+  expectedData.push([spanTag, false, false, null]);
+
+  waitForFocus(runNextTest, aWindow);
+}
+
+function runNextTest() {
+  if (currentTest == expectedData.length) {
+    closeViewSourceWindow(gViewSourceWindow, function() {
+      if (partialTestRunning) {
+        finish();
+        return;
+      }
+      partialTestRunning = true;
+      currentTest = 0;
+      expectedData = [];
+      openDocumentSelect(source, "body", onViewSourceWindowOpen);
+    });
+    return;
+  }
+  let test = expectedData[currentTest++];
+  checkMenuItems(test[0], test[1], test[2], test[3]);
+}
+
+function checkMenuItems(popupNode, copyLinkExpected, copyEmailExpected, expectedClipboardContent) {
+  popupNode.scrollIntoView();
+
+  let cachedEvent = null;
+  let mouseFn = function(event) {
+    cachedEvent = event;
+  };
+
+  gViewSourceWindow.gBrowser.contentWindow.addEventListener("mousedown", mouseFn, false);
+  EventUtils.synthesizeMouseAtCenter(popupNode, { button: 2 }, gViewSourceWindow.gBrowser.contentWindow);
+  gViewSourceWindow.gBrowser.contentWindow.removeEventListener("mousedown", mouseFn, false);
+
+  gContextMenu.openPopup(popupNode, "after_start", 0, 0, false, false, cachedEvent);
+
+  is(gCopyLinkMenuItem.hidden, !copyLinkExpected, "Copy link menuitem is " + (copyLinkExpected ? "not hidden" : "hidden"));
+  is(gCopyEmailMenuItem.hidden, !copyEmailExpected, "Copy email menuitem is " + (copyEmailExpected ? "not hidden" : "hidden"));
+
+  if (!copyLinkExpected && !copyEmailExpected) {
+    runNextTest();
+    return;
+  }
+
+  waitForClipboard(expectedClipboardContent, function() {
+    if (copyLinkExpected)
+      gCopyLinkMenuItem.doCommand();
+    else
+      gCopyEmailMenuItem.doCommand();
+    gContextMenu.hidePopup();
+  }, runNextTest, runNextTest);
+}
--- a/toolkit/components/viewsource/test/browser/head.js
+++ b/toolkit/components/viewsource/test/browser/head.js
@@ -40,24 +40,24 @@ function openViewPartialSourceWindow(aRe
 registerCleanupFunction(function() {
   var windows = Services.wm.getEnumerator("navigator:view-source");
   ok(!windows.hasMoreElements(), "No remaining view source windows still open");
   while (windows.hasMoreElements())
     windows.getNext().close();
 });
 
 function openDocument(aURI, aCallback) {
-  let tab = window.gBrowser.addTab(aURI);
+  let tab = gBrowser.addTab(aURI);
   let browser = tab.linkedBrowser;
   browser.addEventListener("DOMContentLoaded", function pageLoadedListener() {
     browser.removeEventListener("DOMContentLoaded", pageLoadedListener, false);
     aCallback(tab);
   }, false);
   registerCleanupFunction(function() {
-    window.gBrowser.removeTab(tab);
+    gBrowser.removeTab(tab);
   });
 }
 
 function openDocumentSelect(aURI, aCSSSelector, aCallback) {
   openDocument(aURI, function(aTab) {
     let element = aTab.linkedBrowser.contentDocument.querySelector(aCSSSelector);
     let selection = aTab.linkedBrowser.contentWindow.getSelection();
     selection.selectAllChildren(element);
--- a/toolkit/locales/en-US/chrome/global/viewSource.dtd
+++ b/toolkit/locales/en-US/chrome/global/viewSource.dtd
@@ -69,8 +69,13 @@ you can use these alternative items. Oth
 <!ENTITY findAgainCmd.commandkey2 "VK_F3">
 
 <!ENTITY backCmd.label "Back">
 <!ENTITY backCmd.accesskey "B">
 <!ENTITY forwardCmd.label "Forward">
 <!ENTITY forwardCmd.accesskey "F">
 <!ENTITY goBackCmd.commandKey "[">
 <!ENTITY goForwardCmd.commandKey "]">
+
+<!ENTITY copyLinkCmd.label "Copy Link Location">
+<!ENTITY copyLinkCmd.accesskey "L">
+<!ENTITY copyEmailCmd.label "Copy Email Address">
+<!ENTITY copyEmailCmd.accesskey "E">