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 87524 5c99485521cd650ac874b1734701c8b3aa2ef771
parent 87523 ab7d4cb6fb4382aec29ba10bbb25ca962fec548c
child 87525 f21351397aa76d275e67b0f708e6799fa8e82819
push id22130
push userrnewman@mozilla.com
push dateFri, 24 Feb 2012 02:35:54 +0000
treeherdermozilla-central@d23600a1d4a7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdao
bugs469434
milestone13.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 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">