Bug 1289319 - Add a test framework for the first party isolation tests. draft
authorTim Huang <tihuang@mozilla.com>
Tue, 26 Jul 2016 15:54:22 +0800
changeset 396022 01dfc69f98521676fc95a2fe6fa3ac0909d63cd0
parent 393484 db3ed1fdbbeaf5ab1e8fe454780146e7499be3db
child 527105 0482a02e4e7b5bb82c0db63ee103101e4d69103d
push id24897
push userbmo:tihuang@mozilla.com
push dateWed, 03 Aug 2016 08:00:03 +0000
bugs1289319
milestone50.0a1
Bug 1289319 - Add a test framework for the first party isolation tests.
browser/components/firstpartyisolation/moz.build
browser/components/firstpartyisolation/test/browser/.eslintrc
browser/components/firstpartyisolation/test/browser/browser.ini
browser/components/firstpartyisolation/test/browser/browser_dummy.js
browser/components/firstpartyisolation/test/browser/file_firstPartyBasic.html
browser/components/firstpartyisolation/test/browser/head.js
browser/components/moz.build
new file mode 100644
--- /dev/null
+++ b/browser/components/firstpartyisolation/moz.build
@@ -0,0 +1,12 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+BROWSER_CHROME_MANIFESTS += [
+    'test/browser/browser.ini',
+]
+
+with Files('**'):
+    BUG_COMPONENT = ('Firefox', 'First Party Isolation')
new file mode 100644
--- /dev/null
+++ b/browser/components/firstpartyisolation/test/browser/.eslintrc
@@ -0,0 +1,5 @@
+{
+  "extends": [
+    "../../../../../testing/mochitest/browser.eslintrc"
+  ]
+}
new file mode 100644
--- /dev/null
+++ b/browser/components/firstpartyisolation/test/browser/browser.ini
@@ -0,0 +1,9 @@
+[DEFAULT]
+skip-if = buildapp == "mulet"
+tags = firstpartyisolation
+support-files =
+  file_firstPartyBasic.html
+  head.js
+
+[browser_dummy.js]
+skip-if = true
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/browser/components/firstpartyisolation/test/browser/browser_dummy.js
@@ -0,0 +1,10 @@
+/*
+ * This is a dummy test case which makes this could be built.
+ * Should be removed after actual tests landed.
+ */
+
+"use strict";
+
+add_task(function* () {
+  ok(true, "Make this test pass anyway.");
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/firstpartyisolation/test/browser/file_firstPartyBasic.html
@@ -0,0 +1,8 @@
+<html>
+  <head>
+    <meta charset="UTF-8">
+    <title>First Party Isolation Tests</title>
+  </head>
+  <body>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/components/firstpartyisolation/test/browser/head.js
@@ -0,0 +1,282 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+'use strict';
+
+const TEST_URL_PATH = "browser/browser/components/firstpartyisolation/test/browser/";
+
+const TEST_MODE_FIRSTPARTY   = 0;
+const TEST_MODE_NO_ISOLATION = 1;
+const TEST_MODE_CONTAINERS   = 2;
+
+let gFirstPartyBasicPage = TEST_URL_PATH + "file_firstPartyBasic.html";
+
+// Redefine the ContentTask.spwan for allowing the 'content' can br directly
+// accessed as the window of the iframe in first party isoloation tests.
+ContentTask.spawnNative = ContentTask.spawn;
+
+ContentTask.spawn = function (aBrowser, aArg, aTask) {
+
+  const funcHeader = `
+  {
+    this.iframeWindow = content.document.querySelector('iframe').contentWindow;
+    Object.defineProperty(this, "content", {
+      get: () => this.iframeWindow
+    });`;
+
+  let runnableStr = aTask.toString();
+
+  // Replace the first left curly bracket with the function header which
+  // alters the 'content' in the ContentTask to the iframe's window.
+  if (IsolationTestTools.isolationTestMode === TEST_MODE_FIRSTPARTY) {
+    runnableStr = runnableStr.replace("{", funcHeader);
+  }
+
+  return ContentTask.spawnNative(aBrowser, aArg, runnableStr);
+};
+
+function addIsolationTask(task) {
+  add_task(function* () {
+    yield task();
+  });
+}
+
+/**
+ * Add a tab for the given url with the specific user context id.
+ *
+ * @param aURL The url of the page.
+ * @param aUserContextId The user context id for this tab.
+ *
+ * @return tab The tab object of this tab.
+ *         browser The browser object of this tab.
+ */
+function* openTabInUserContext(aURL, aUserContextId) {
+  // Open the tab in the correct userContextId.
+  let tab = gBrowser.addTab(aURL, {userContextId: aUserContextId});
+
+  // Select tab and make sure its browser is focused.
+  gBrowser.selectedTab = tab;
+  tab.ownerGlobal.focus();
+
+  let browser = gBrowser.getBrowserForTab(tab);
+  yield BrowserTestUtils.browserLoaded(browser);
+  return {tab, browser};
+}
+
+/**
+ * Add a tab for a page with the given first party domain. This page will have
+ * an iframe which is loaded with the given url.
+ *
+ * @param aURL The url of the iframe.
+ * @param aFirstPartyDomain The first party domain.
+ *
+ * @return tab The tab object of this tab.
+ *         browser The browser object of this tab.
+ */
+function* openTabInFirstParty(aURL, aFirstPartyDomain) {
+
+  if (!aFirstPartyDomain.endsWith('/')) {
+    aFirstPartyDomain += "/";
+  }
+
+  // Open the tab for the basic first party page.
+  let tab = gBrowser.addTab(aFirstPartyDomain + gFirstPartyBasicPage);
+
+  // Select tab and make sure its browser is focused.
+  gBrowser.selectedTab = tab;
+  tab.ownerGlobal.focus();
+
+  let browser = gBrowser.getBrowserForTab(tab);
+  yield BrowserTestUtils.browserLoaded(browser);
+
+  // Create the iframe.
+  yield ContentTask.spawnNative(browser, { url: aURL }, function* (arg) {
+    let document = content.document;
+
+    let iframe = document.createElement('iframe');
+    iframe.setAttribute("src", arg.url);
+
+    // Wait the iframe to be loaded.
+    yield new Promise(done => {
+      iframe.addEventListener("load", function loadEnd() {
+        iframe.removeEventListener("load", loadEnd, true);
+        done();
+      }, true);
+
+      document.body.appendChild(iframe);
+    });
+
+  });
+
+  return {tab, browser};
+}
+
+this.IsolationTestTools = {
+  _isolationTestMode: TEST_MODE_FIRSTPARTY,
+  _profiles:          [],
+  _skipTestFlags:     [false, false, false],
+
+  /**
+   * Adds isolation tests for the first party isolation, the no isolation
+   * and the containers respectively.
+   *
+   * @param aTask The testing task which will be run in different settings.
+   */
+  add_task(aTask) {
+    add_task(function* addTaskForIsolationTests() {
+      // There should be one profile at least.
+      if (this._profiles.length === 0) {
+        ok(false, "It should be one profile at least.");
+        return;
+      }
+
+      // Add the test task for the first party isolation.
+      if (!this._skipTestFlags[TEST_MODE_FIRSTPARTY]) {
+        add_task(function* () {
+          info("Starting the test for the first party isolation.");
+          this._isolationTestMode = TEST_MODE_FIRSTPARTY;
+
+          // Before run thes task, reset the preferences first.
+          yield new Promise(resolve => {
+            SpecialPowers.flushPrefEnv(resolve);
+          });
+
+          // Enable the first party isolation.
+          yield new Promise(resolve => {
+            SpecialPowers.pushPrefEnv({"set": [
+              ["privacy.firstparty.isolate", true]
+            ]}, resolve);
+          });
+          yield aTask();
+        }.bind(this));
+      }
+
+      // Add the test task for disabling isolation.
+      if (!this._skipTestFlags[TEST_MODE_NO_ISOLATION]) {
+        add_task(function* () {
+          info("Starting the test for disabling isolation.");
+          this._isolationTestMode = TEST_MODE_NO_ISOLATION;
+
+          // Before run thes task, reset the preferences first.
+          yield new Promise(resolve => {
+            SpecialPowers.flushPrefEnv(resolve);
+          });
+
+          yield aTask();
+        }.bind(this));
+      }
+
+      // Add the test task for containers.
+      if (!this._skipTestFlags[TEST_MODE_CONTAINERS]) {
+        add_task(function* () {
+          info("Starting the test for Containers.");
+          this._isolationTestMode = TEST_MODE_CONTAINERS;
+
+          // Before run thes task, reset the preferences first.
+          yield new Promise(resolve => {
+            SpecialPowers.flushPrefEnv(resolve);
+          });
+
+          // Make sure userContext is enabled.
+          yield new Promise(resolve => {
+            SpecialPowers.pushPrefEnv({"set": [
+              ["privacy.userContext.enabled", true]
+            ]}, resolve);
+          });
+          yield aTask();
+        }.bind(this));
+      }
+    }.bind(this));
+  },
+
+  /**
+   * Add a tab with the given profile, this will open different types of tabs
+   * according to the current running test mode. A profile means a isolation
+   * target in different test mode; a profile indicates a first party domain
+   * when testing the first party isolation, it is a user context id when
+   * testing containers.
+   *
+   * @param aURL The url which is going to open.
+   * @param aProfileId The profile id which will apply to this tab.
+   *
+   * @return tab The tab object of this tab.
+   *         browser The browser object of this tab.
+   */
+  addTab(aURL, aProfileId) {
+    let testMode = this._isolationTestMode;
+
+    if (testMode === TEST_MODE_CONTAINERS) {
+      return openTabInUserContext(aURL, this.getProfile(aProfileId).userContextId);
+    } else {
+      return openTabInFirstParty(aURL, this.getProfile(aProfileId).firstPartyDomain);
+    }
+  },
+
+  /**
+   * Register an isolation profile.
+   *
+   * @param aFirstPartyDomain The first party domain of this profile.
+   * @param aUserContextId The user context id of this profile.
+   *
+   * return An profile id which starts from 0 and increases by one as new
+   * profild added.
+   */
+  registerProfile(aFirstPartyDomain, aUserContextId = 0) {
+    let profile = { firstPartyDomain: aFirstPartyDomain,
+                    userContextId: aUserContextId };
+
+    let length = this._profiles.push(profile);
+
+    return length - 1;
+  },
+
+  /**
+   * Get the isolation profile.
+   *
+   * @param aProfileId The id of the profile.
+   * return firstPartyDomain The first party domain of this profile.
+   *        userContextId The user context id of this profile.
+   */
+  getProfile(aProfileId) {
+    return this._profiles[aProfileId];
+  },
+
+  /**
+   * Tell the test framework to skip all first party isolation tests.
+   */
+  skipTestFirstParty() {
+    this._skipTestFlags[TEST_MODE_FIRSTPARTY] = true;
+  },
+
+  /**
+   * Tell the test framework to skip all no isolation tests.
+   */
+  skipTestNoIsolation() {
+    this._skipTestFlags[TEST_MODE_NO_ISOLATION] = true;
+  },
+
+  /**
+   * Tell the test framework to skip all containers tests.
+   */
+  skipTestContainers() {
+    this._skipTestFlags[TEST_MODE_CONTAINERS] = true;
+  },
+
+  /**
+   * Get current test mode.
+   *
+   * return The current test mode.
+   */
+  get isolationTestMode() {
+    return this._isolationTestMode;
+  },
+
+  /**
+   * Return a boolean to indicate that should the test check the isolation is
+   * working or not.
+   */
+  get shouldCheckIsolation() {
+    return this._isolationTestMode !== TEST_MODE_NO_ISOLATION;
+  }
+};
--- a/browser/components/moz.build
+++ b/browser/components/moz.build
@@ -7,16 +7,17 @@
 DIRS += [
     'about',
     'contextualidentity',
     'customizableui',
     'dirprovider',
     'downloads',
     'extensions',
     'feeds',
+    'firstpartyisolation',
     'migration',
     'newtab',
     'places',
     'preferences',
     'privatebrowsing',
     'search',
     'sessionstore',
     'shell',