Bug 1289319 - Add a test framework for the first party isolation tests.
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',