Bug 1113431 - Tests for referrer in click, middle click, and open new link in new {tab, window}. r=gijskruitbosch
authorChristoph Kerschbaumer <mozilla@christophkerschbaumer.com>
Fri, 20 Feb 2015 16:18:05 -0800
changeset 234304 4b5850a205d0920f80e15333a5fc5509b4db9103
parent 234303 3019cfbbfd0db7444c7c15a914f2409c01fbb4e6
child 234305 3d5b8440fdf4a7da10e8e2eadbde6bb4c1807cf5
push id28440
push userkwierso@gmail.com
push dateWed, 18 Mar 2015 22:38:34 +0000
treeherdermozilla-central@f3d9582e34fb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgijskruitbosch
bugs1113431
milestone39.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 1113431 - Tests for referrer in click, middle click, and open new link in new {tab, window}. r=gijskruitbosch
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'):