Bug 1113431 - Tests for referrer in click, middle click, and open new link in new {tab, window}. r=gijskruitbosch, a=sledru
authorChristoph Kerschbaumer <mozilla@christophkerschbaumer.com>
Fri, 20 Feb 2015 16:18:05 -0800
changeset 248310 7670a26203b6f092cd651ae2c49140d379b92b00
parent 248309 5aaef73ac73e2fe4ef6a584633d0b7404a3c94bb
child 248311 514b921f8f3a6966f862151561e78afc1e36a4a2
push id7810
push userryanvm@gmail.com
push dateTue, 24 Mar 2015 16:58:47 +0000
treeherdermozilla-aurora@6f0afb5065a8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgijskruitbosch, sledru
bugs1113431
milestone38.0a2
Bug 1113431 - Tests for referrer in click, middle click, and open new link in new {tab, window}. r=gijskruitbosch, a=sledru
browser/base/content/test/referrer/browser.ini
browser/base/content/test/referrer/browser_referrer_middle_click.js
browser/base/content/test/referrer/browser_referrer_open_link_in_private.js
browser/base/content/test/referrer/browser_referrer_open_link_in_tab.js
browser/base/content/test/referrer/browser_referrer_open_link_in_window.js
browser/base/content/test/referrer/browser_referrer_simple_click.js
browser/base/content/test/referrer/file_referrer_policyserver.sjs
browser/base/content/test/referrer/file_referrer_testserver.sjs
browser/base/content/test/referrer/head.js
browser/base/moz.build
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/referrer/browser.ini
@@ -0,0 +1,11 @@
+[DEFAULT]
+support-files =
+  file_referrer_policyserver.sjs
+  file_referrer_testserver.sjs
+  head.js
+
+[browser_referrer_middle_click.js]
+[browser_referrer_open_link_in_private.js]
+[browser_referrer_open_link_in_tab.js]
+[browser_referrer_open_link_in_window.js]
+[browser_referrer_simple_click.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/referrer/browser_referrer_middle_click.js
@@ -0,0 +1,18 @@
+// Tests referrer on middle-click navigation.
+// Middle-clicks on the link, which opens it in a new tab.
+
+function startMiddleClickTestCase(aTestNumber) {
+  info("browser_referrer_middle_click: " +
+       getReferrerTestDescription(aTestNumber));
+  someTabLoaded(gTestWindow).then(function(aNewTab) {
+    gTestWindow.gBrowser.selectedTab = aNewTab;
+    checkReferrerAndStartNextTest(aTestNumber, null, aNewTab,
+                                  startMiddleClickTestCase);
+  });
+
+  clickTheLink(gTestWindow, "testlink", {button: 1});
+}
+
+function test() {
+  startReferrerTest(startMiddleClickTestCase);
+}
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/referrer/browser_referrer_open_link_in_private.js
@@ -0,0 +1,22 @@
+// Tests referrer on context menu navigation - open link in new private window.
+// Selects "open link in new private window" from the context menu.
+
+function startNewPrivateWindowTestCase(aTestNumber) {
+  info("browser_referrer_open_link_in_private: " +
+       getReferrerTestDescription(aTestNumber));
+  contextMenuOpened(gTestWindow, "testlink").then(function(aContextMenu) {
+    newWindowOpened().then(function(aNewWindow) {
+      someTabLoaded(aNewWindow).then(function() {
+        checkReferrerAndStartNextTest(aTestNumber, aNewWindow, null,
+                                      startNewPrivateWindowTestCase);
+      });
+    });
+
+    doContextMenuCommand(gTestWindow, aContextMenu, "context-openlinkprivate");
+  });
+}
+
+function test() {
+  requestLongerTimeout(5);  // slowwww shutdown on e10s
+  startReferrerTest(startNewPrivateWindowTestCase);
+}
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/referrer/browser_referrer_open_link_in_tab.js
@@ -0,0 +1,20 @@
+// Tests referrer on context menu navigation - open link in new tab.
+// Selects "open link in new tab" from the context menu.
+
+function startNewTabTestCase(aTestNumber) {
+  info("browser_referrer_open_link_in_tab: " +
+       getReferrerTestDescription(aTestNumber));
+  contextMenuOpened(gTestWindow, "testlink").then(function(aContextMenu) {
+    someTabLoaded(gTestWindow).then(function(aNewTab) {
+      gTestWindow.gBrowser.selectedTab = aNewTab;
+      checkReferrerAndStartNextTest(aTestNumber, null, aNewTab,
+                                    startNewTabTestCase);
+    });
+
+    doContextMenuCommand(gTestWindow, aContextMenu, "context-openlinkintab");
+  });
+}
+
+function test() {
+  startReferrerTest(startNewTabTestCase);
+}
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/referrer/browser_referrer_open_link_in_window.js
@@ -0,0 +1,22 @@
+// Tests referrer on context menu navigation - open link in new window.
+// Selects "open link in new window" from the context menu.
+
+function startNewWindowTestCase(aTestNumber) {
+  info("browser_referrer_open_link_in_window: " +
+       getReferrerTestDescription(aTestNumber));
+  contextMenuOpened(gTestWindow, "testlink").then(function(aContextMenu) {
+    newWindowOpened().then(function(aNewWindow) {
+      someTabLoaded(aNewWindow).then(function() {
+        checkReferrerAndStartNextTest(aTestNumber, aNewWindow, null,
+                                      startNewWindowTestCase);
+      });
+    });
+
+    doContextMenuCommand(gTestWindow, aContextMenu, "context-openlink");
+  });
+}
+
+function test() {
+  requestLongerTimeout(5);  // slowwww shutdown on e10s
+  startReferrerTest(startNewWindowTestCase);
+}
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/referrer/browser_referrer_simple_click.js
@@ -0,0 +1,17 @@
+// Tests referrer on simple click navigation.
+// Clicks on the link, which opens it in the same tab.
+
+function startSimpleClickTestCase(aTestNumber) {
+  info("browser_referrer_simple_click: " +
+       getReferrerTestDescription(aTestNumber));
+  BrowserTestUtils.browserLoaded(gTestWindow.gBrowser.selectedBrowser).then(function() {
+    checkReferrerAndStartNextTest(aTestNumber, null, null,
+                                  startSimpleClickTestCase);
+  });
+
+  clickTheLink(gTestWindow, "testlink", {});
+};
+
+function test() {
+  startReferrerTest(startSimpleClickTestCase);
+}
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/referrer/file_referrer_policyserver.sjs
@@ -0,0 +1,37 @@
+/**
+ * Renders a link with the provided referrer policy.
+ * Used in browser_referrer_*.js, bug 1113431.
+ * Arguments: ?scheme=http://&policy=origin&rel=noreferrer
+ */
+function handleRequest(request, response)
+{
+  Components.utils.importGlobalProperties(["URLSearchParams"]);
+  let query = new URLSearchParams(request.queryString);
+
+  let scheme = query.get("scheme");
+  let policy = query.get("policy");
+  let rel = query.get("rel");
+
+  let linkUrl = scheme +
+                "test1.example.com/browser/browser/base/content/test/referrer/" +
+                "file_referrer_testserver.sjs";
+  let metaReferrerTag =
+      policy ? `<meta name='referrer' content='${policy}'>` : "";
+
+  let html = `<!DOCTYPE HTML>
+              <html>
+              <head>
+              <meta charset='utf-8'>
+              ${metaReferrerTag}
+              <title>Test referrer</title>
+              </head>
+              <body>
+              <a id='testlink' href='${linkUrl}' ${rel ? ` rel='${rel}'` : ""}>
+              referrer test link</a>
+              </body>
+              </html>`;
+
+  response.setHeader("Cache-Control", "no-cache", false);
+  response.setHeader("Content-Type", "text/html", false);
+  response.write(html);
+}
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/referrer/file_referrer_testserver.sjs
@@ -0,0 +1,31 @@
+/**
+ * Renders the HTTP Referer header up to the second path slash.
+ * Used in browser_referrer_*.js, bug 1113431.
+ */
+function handleRequest(request, response)
+{
+  let referrer = "";
+  try {
+    referrer = request.getHeader("referer");
+  } catch (e) {
+    referrer = "";
+  }
+
+  // Strip it past the first path slash. Makes tests easier to read.
+  referrer = referrer.split("/").slice(0, 4).join("/");
+
+  let html = `<!DOCTYPE HTML>
+             <html>
+             <head>
+             <meta charset='utf-8'>
+             <title>Test referrer</title>
+             </head>
+             <body>
+             <div id='testdiv'>${referrer}</div>
+             </body>
+             </html>`;
+
+  response.setHeader("Cache-Control", "no-cache", false);
+  response.setHeader("Content-Type", "text/html", false);
+  response.write(html);
+}
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/referrer/head.js
@@ -0,0 +1,299 @@
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+  "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserTestUtils",
+  "resource://testing-common/BrowserTestUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ContentTask",
+  "resource://testing-common/ContentTask.jsm");
+
+const REFERRER_URL_BASE = "/browser/browser/base/content/test/referrer/";
+const REFERRER_POLICYSERVER_URL =
+  "test1.example.com" + REFERRER_URL_BASE + "file_referrer_policyserver.sjs";
+
+let gTestWindow = null;
+
+// We test that the UI code propagates three pieces of state - the referrer URI
+// itself, the referrer policy, and the triggering principal. After that, we
+// trust nsIWebNavigation to do the right thing with the info it's given, which
+// is covered more exhaustively by dom/base/test/test_bug704320.html (which is
+// a faster content-only test). So, here, we limit ourselves to cases that
+// would break when the UI code drops either of these pieces; we don't try to
+// duplicate the entire cross-product test in bug 704320 - that would be slow,
+// especially when we're opening a new window for each case.
+let _referrerTests = [
+  // 1. Normal cases - no referrer policy, no special attributes.
+  //    We expect a full referrer normally, and no referrer on downgrade.
+  {
+    fromScheme: "http://",
+    toScheme: "http://",
+    result: "http://test1.example.com/browser"  // full referrer
+  },
+  {
+    fromScheme: "https://",
+    toScheme: "http://",
+    result: ""  // no referrer when downgrade
+  },
+  // 2. Origin referrer policy - we expect an origin referrer,
+  //    even on downgrade.  But rel=noreferrer trumps this.
+  {
+    fromScheme: "https://",
+    toScheme: "http://",
+    policy: "origin",
+    result: "https://test1.example.com"  // origin, even on downgrade
+  },
+  {
+    fromScheme: "https://",
+    toScheme: "http://",
+    policy: "origin",
+    rel: "noreferrer",
+    result: ""  // rel=noreferrer trumps meta-referrer
+  },
+  // 3. Origin-when-crossorigin policy - this depends on the triggering
+  //    principal.  We expect full referrer for same-origin requests,
+  //    and origin referrer for cross-origin requests.
+  {
+    fromScheme: "https://",
+    toScheme: "https://",
+    policy: "origin-when-crossorigin",
+    result: "https://test1.example.com/browser"  // same origin
+  },
+  {
+    fromScheme: "http://",
+    toScheme: "https://",
+    policy: "origin-when-crossorigin",
+    result: "http://test1.example.com"  // cross origin
+  },
+];
+
+/**
+ * Returns the test object for a given test number.
+ * @param aTestNumber The test number - 0, 1, 2, ...
+ * @return The test object, or undefined if the number is out of range.
+ */
+function getReferrerTest(aTestNumber) {
+  return _referrerTests[aTestNumber];
+}
+
+/**
+ * Returns a brief summary of the test, for logging.
+ * @param aTestNumber The test number - 0, 1, 2...
+ * @return The test description.
+ */
+function getReferrerTestDescription(aTestNumber) {
+  let test = getReferrerTest(aTestNumber);
+  return "policy=[" + test.policy + "] " +
+         "rel=[" + test.rel + "] " +
+         test.fromScheme + " -> " + test.toScheme;
+}
+
+/**
+ * Clicks the link.
+ * @param aWindow The window to click the link in.
+ * @param aLinkId The id of the link element.
+ * @param aOptions The options for synthesizeMouseAtCenter.
+ */
+function clickTheLink(aWindow, aLinkId, aOptions) {
+  ContentTask.spawn(aWindow.gBrowser.selectedBrowser,
+                    {id: aLinkId, options: aOptions},
+                    function(data) {
+    let element = content.document.getElementById(data.id);
+    let options = data.options;
+    element.focus();
+
+    // EventUtils.synthesizeMouseAtCenter(element, options, content);
+    // Alas, EventUtils doesn't work in the content task environment.
+    var domWindowUtils =
+        content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+        .getInterface(Components.interfaces.nsIDOMWindowUtils);
+    var rect = element.getBoundingClientRect();
+    var left = rect.left + rect.width / 2;
+    var top = rect.top + rect.height / 2;
+    var button = options.button || 0;
+    function sendMouseEvent(type) {
+      domWindowUtils.sendMouseEvent(type, left, top, button,
+                                    1, 0, false, 0, 0, true);
+    }
+    if ("type" in options) {
+      sendMouseEvent(options.type);  // e.g., "contextmenu"
+    } else {
+      sendMouseEvent("mousedown");
+      sendMouseEvent("mouseup");
+    }
+  });
+}
+
+/**
+ * Extracts the referrer result from the target window.
+ * @param aWindow The window where the referrer target has loaded.
+ * @return {Promise}
+ * @resolves When extacted, with the text of the (trimmed) referrer.
+ */
+function referrerResultExtracted(aWindow) {
+  return ContentTask.spawn(aWindow.gBrowser.selectedBrowser, {}, function() {
+    return content.document.getElementById("testdiv").textContent;
+  });
+}
+
+/**
+ * Waits for browser delayed startup to finish.
+ * @param aWindow The window to wait for.
+ * @return {Promise}
+ * @resolves When the window is loaded.
+ */
+function delayedStartupFinished(aWindow) {
+  return new Promise(function(resolve) {
+    Services.obs.addObserver(function observer(aSubject, aTopic) {
+      if (aWindow == aSubject) {
+        Services.obs.removeObserver(observer, aTopic);
+        resolve();
+      }
+    }, "browser-delayed-startup-finished", false);
+  });
+}
+
+/**
+ * Waits for some (any) tab to load. The caller triggers the load.
+ * @param aWindow The window where to wait for a tab to load.
+ * @return {Promise}
+ * @resolves With the tab once it's loaded.
+ */
+function someTabLoaded(aWindow) {
+  return new Promise(function(resolve) {
+    aWindow.gBrowser.addEventListener("load", function onLoad(aEvent) {
+      let tab = aWindow.gBrowser._getTabForContentWindow(
+          aEvent.target.defaultView.top);
+      if (tab) {
+        aWindow.gBrowser.removeEventListener("load", onLoad, true);
+        resolve(tab);
+      }
+    }, true);
+  });
+}
+
+/**
+ * Waits for a new window to open and load. The caller triggers the open.
+ * @return {Promise}
+ * @resolves With the new window once it's open and loaded.
+ */
+function newWindowOpened() {
+  return new Promise(function(resolve) {
+    Services.wm.addListener({
+      onOpenWindow: function(aXULWindow) {
+        Services.wm.removeListener(this);
+        var newWindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+                                  .getInterface(Ci.nsIDOMWindow);
+        delayedStartupFinished(newWindow).then(function() {
+          resolve(newWindow);
+        });
+      },
+      onCloseWindow: function(aXULWindow) { },
+      onWindowTitleChange: function(aXULWindow, aNewTitle) { }
+    });
+  });
+}
+
+/**
+ * Opens the context menu.
+ * @param aWindow The window to open the context menu in.
+ * @param aLinkId The id of the link to open the context menu on.
+ * @return {Promise}
+ * @resolves With the menu popup when the context menu is open.
+ */
+function contextMenuOpened(aWindow, aLinkId) {
+  return new Promise(function(resolve) {
+    aWindow.document.addEventListener("popupshown",
+                                      function handleMenu(aEvent) {
+      aWindow.document.removeEventListener("popupshown", handleMenu, false);
+      resolve(aEvent.target);
+    }, false);
+
+    // Simulate right-click that opens the context menu.
+    clickTheLink(aWindow, aLinkId, {type: "contextmenu", button: 2});
+  });
+}
+
+/**
+ * Performs a context menu command.
+ * @param aWindow The window with the already open context menu.
+ * @param aMenu The menu popup to hide.
+ * @param aItemId The id of the menu item to activate.
+ */
+function doContextMenuCommand(aWindow, aMenu, aItemId) {
+  let command = aWindow.document.getElementById(aItemId);
+  command.doCommand();
+  aMenu.hidePopup();
+}
+
+/**
+ * Loads a single test case, i.e., a source url into gTestWindow.
+ * @param aTestNumber The test case number - 0, 1, 2...
+ * @return {Promise}
+ * @resolves When the source url for this test case is loaded.
+ */
+function referrerTestCaseLoaded(aTestNumber) {
+  let test = getReferrerTest(aTestNumber);
+  let url = test.fromScheme + REFERRER_POLICYSERVER_URL +
+            "?scheme=" + escape(test.toScheme) +
+            "&policy=" + escape(test.policy || "") +
+            "&rel=" + escape(test.rel || "");
+  var browser = gTestWindow.gBrowser;
+  browser.selectedTab = browser.addTab(url);
+  return BrowserTestUtils.browserLoaded(browser.selectedBrowser);
+}
+
+/**
+ * Checks the result of the referrer test, and moves on to the next test.
+ * @param aTestNumber The test number - 0, 1, 2, ...
+ * @param aNewWindow The new window where the referrer target opened, or null.
+ * @param aNewTab The new tab where the referrer target opened, or null.
+ * @param aStartTestCase The callback to start the next test, aTestNumber + 1.
+ */
+function checkReferrerAndStartNextTest(aTestNumber, aNewWindow, aNewTab,
+                                       aStartTestCase) {
+  referrerResultExtracted(aNewWindow || gTestWindow).then(function(result) {
+    // Compare the actual result against the expected one.
+    let test = getReferrerTest(aTestNumber);
+    let desc = getReferrerTestDescription(aTestNumber);
+    is(result, test.result, desc);
+
+    // Clean up - close new tab / window, and then the source tab.
+    aNewTab && (aNewWindow || gTestWindow).gBrowser.removeTab(aNewTab);
+    aNewWindow && aNewWindow.close();
+    is(gTestWindow.gBrowser.tabs.length, 2, "two tabs open");
+    gTestWindow.gBrowser.removeTab(gTestWindow.gBrowser.tabs[1]);
+
+    // Move on to the next test.  Or finish if we're done.
+    var nextTestNumber = aTestNumber + 1;
+    if (getReferrerTest(nextTestNumber)) {
+      referrerTestCaseLoaded(nextTestNumber).then(function() {
+        aStartTestCase(nextTestNumber);
+      });
+    } else {
+      finish();
+    }
+  });
+}
+
+/**
+ * Fires up the complete referrer test.
+ * @param aStartTestCase The callback to start a single test case, called with
+ * the test number - 0, 1, 2... Needs to trigger the navigation from the source
+ * page, and call checkReferrerAndStartNextTest() when the target is loaded.
+ */
+function startReferrerTest(aStartTestCase) {
+  waitForExplicitFinish();
+
+  // Open the window where we'll load the source URLs.
+  gTestWindow = openDialog(location, "", "chrome,all,dialog=no", "about:blank");
+  registerCleanupFunction(function() {
+    gTestWindow && gTestWindow.close();
+  });
+
+  // Load and start the first test.
+  delayedStartupFinished(gTestWindow).then(function() {
+    referrerTestCaseLoaded(0).then(function() {
+      aStartTestCase(0);
+    });
+  });
+}
--- a/browser/base/moz.build
+++ b/browser/base/moz.build
@@ -15,16 +15,17 @@ MOCHITEST_CHROME_MANIFESTS += [
 ]
 
 BROWSER_CHROME_MANIFESTS += [
     'content/test/chat/browser.ini',
     'content/test/general/browser.ini',
     'content/test/newtab/browser.ini',
     'content/test/plugins/browser.ini',
     'content/test/popupNotifications/browser.ini',
+    'content/test/referrer/browser.ini',
     'content/test/social/browser.ini',
 ]
 
 DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
 
 DEFINES['APP_LICENSE_BLOCK'] = '%s/content/overrides/app-license.html' % SRCDIR
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'gtk2', 'gtk3', 'cocoa'):