summaryrefslogtreecommitdiffstats
path: root/browser/components/originattributes
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/originattributes')
-rw-r--r--browser/components/originattributes/moz.build14
-rw-r--r--browser/components/originattributes/test/browser/blobify.worker.js47
-rw-r--r--browser/components/originattributes/test/browser/browser.toml120
-rw-r--r--browser/components/originattributes/test/browser/browser_blobURLIsolation.js123
-rw-r--r--browser/components/originattributes/test/browser/browser_broadcastChannel.js55
-rw-r--r--browser/components/originattributes/test/browser/browser_cache.js350
-rw-r--r--browser/components/originattributes/test/browser/browser_cacheAPI.js24
-rw-r--r--browser/components/originattributes/test/browser/browser_clientAuth.js49
-rw-r--r--browser/components/originattributes/test/browser/browser_cookieIsolation.js43
-rw-r--r--browser/components/originattributes/test/browser/browser_favicon_firstParty.js511
-rw-r--r--browser/components/originattributes/test/browser/browser_favicon_userContextId.js406
-rw-r--r--browser/components/originattributes/test/browser/browser_firstPartyIsolation.js511
-rw-r--r--browser/components/originattributes/test/browser/browser_firstPartyIsolation_aboutPages.js258
-rw-r--r--browser/components/originattributes/test/browser/browser_firstPartyIsolation_about_newtab.js55
-rw-r--r--browser/components/originattributes/test/browser/browser_firstPartyIsolation_blobURI.js116
-rw-r--r--browser/components/originattributes/test/browser/browser_firstPartyIsolation_js_uri.js90
-rw-r--r--browser/components/originattributes/test/browser/browser_firstPartyIsolation_saveAs.js326
-rw-r--r--browser/components/originattributes/test/browser/browser_httpauth.js79
-rw-r--r--browser/components/originattributes/test/browser/browser_imageCacheIsolation.js92
-rw-r--r--browser/components/originattributes/test/browser/browser_localStorageIsolation.js33
-rw-r--r--browser/components/originattributes/test/browser/browser_permissions.js91
-rw-r--r--browser/components/originattributes/test/browser/browser_postMessage.js121
-rw-r--r--browser/components/originattributes/test/browser/browser_sanitize.js92
-rw-r--r--browser/components/originattributes/test/browser/browser_sharedworker.js30
-rw-r--r--browser/components/originattributes/test/browser/browser_windowOpenerRestriction.js113
-rw-r--r--browser/components/originattributes/test/browser/dummy.html9
-rw-r--r--browser/components/originattributes/test/browser/file_broadcastChannel.html16
-rw-r--r--browser/components/originattributes/test/browser/file_broadcastChanneliFrame.html15
-rw-r--r--browser/components/originattributes/test/browser/file_cache.html33
-rw-r--r--browser/components/originattributes/test/browser/file_favicon.html11
-rw-r--r--browser/components/originattributes/test/browser/file_favicon.pngbin0 -> 344 bytes
-rw-r--r--browser/components/originattributes/test/browser/file_favicon.png^headers^1
-rw-r--r--browser/components/originattributes/test/browser/file_favicon_cache.html11
-rw-r--r--browser/components/originattributes/test/browser/file_favicon_cache.pngbin0 -> 344 bytes
-rw-r--r--browser/components/originattributes/test/browser/file_favicon_thirdParty.html11
-rw-r--r--browser/components/originattributes/test/browser/file_firstPartyBasic.html8
-rw-r--r--browser/components/originattributes/test/browser/file_postMessage.html27
-rw-r--r--browser/components/originattributes/test/browser/file_postMessageSender.html16
-rw-r--r--browser/components/originattributes/test/browser/file_saveAs.sjs38
-rw-r--r--browser/components/originattributes/test/browser/file_shared.worker.js7
-rw-r--r--browser/components/originattributes/test/browser/file_sharedworker.html10
-rw-r--r--browser/components/originattributes/test/browser/file_thirdPartyChild.audio.oggbin0 -> 2603 bytes
-rw-r--r--browser/components/originattributes/test/browser/file_thirdPartyChild.embed.pngbin0 -> 95 bytes
-rw-r--r--browser/components/originattributes/test/browser/file_thirdPartyChild.favicon.pngbin0 -> 95 bytes
-rw-r--r--browser/components/originattributes/test/browser/file_thirdPartyChild.fetch.html8
-rw-r--r--browser/components/originattributes/test/browser/file_thirdPartyChild.font.woffbin0 -> 1112 bytes
-rw-r--r--browser/components/originattributes/test/browser/file_thirdPartyChild.iframe.html18
-rw-r--r--browser/components/originattributes/test/browser/file_thirdPartyChild.img.pngbin0 -> 95 bytes
-rw-r--r--browser/components/originattributes/test/browser/file_thirdPartyChild.import.js1
-rw-r--r--browser/components/originattributes/test/browser/file_thirdPartyChild.link.css1
-rw-r--r--browser/components/originattributes/test/browser/file_thirdPartyChild.object.pngbin0 -> 95 bytes
-rw-r--r--browser/components/originattributes/test/browser/file_thirdPartyChild.request.html8
-rw-r--r--browser/components/originattributes/test/browser/file_thirdPartyChild.script.js1
-rw-r--r--browser/components/originattributes/test/browser/file_thirdPartyChild.sharedworker.js1
-rw-r--r--browser/components/originattributes/test/browser/file_thirdPartyChild.track.vtt13
-rw-r--r--browser/components/originattributes/test/browser/file_thirdPartyChild.video.ogvbin0 -> 16049 bytes
-rw-r--r--browser/components/originattributes/test/browser/file_thirdPartyChild.worker.fetch.html8
-rw-r--r--browser/components/originattributes/test/browser/file_thirdPartyChild.worker.js20
-rw-r--r--browser/components/originattributes/test/browser/file_thirdPartyChild.worker.request.html8
-rw-r--r--browser/components/originattributes/test/browser/file_thirdPartyChild.worker.xhr.html8
-rw-r--r--browser/components/originattributes/test/browser/file_thirdPartyChild.xhr.html8
-rw-r--r--browser/components/originattributes/test/browser/file_windowOpenerRestriction.html10
-rw-r--r--browser/components/originattributes/test/browser/file_windowOpenerRestrictionTarget.html33
-rw-r--r--browser/components/originattributes/test/browser/head.js463
-rw-r--r--browser/components/originattributes/test/browser/test.html20
-rw-r--r--browser/components/originattributes/test/browser/test.js1
-rw-r--r--browser/components/originattributes/test/browser/test.js^headers^1
-rw-r--r--browser/components/originattributes/test/browser/test2.html12
-rw-r--r--browser/components/originattributes/test/browser/test2.js1
-rw-r--r--browser/components/originattributes/test/browser/test2.js^headers^1
-rw-r--r--browser/components/originattributes/test/browser/test_firstParty.html15
-rw-r--r--browser/components/originattributes/test/browser/test_firstParty_cookie.html13
-rw-r--r--browser/components/originattributes/test/browser/test_firstParty_html_redirect.html9
-rw-r--r--browser/components/originattributes/test/browser/test_firstParty_http_redirect.html9
-rw-r--r--browser/components/originattributes/test/browser/test_firstParty_http_redirect.html^headers^2
-rw-r--r--browser/components/originattributes/test/browser/test_firstParty_http_redirect_to_same_domain.html9
-rw-r--r--browser/components/originattributes/test/browser/test_firstParty_http_redirect_to_same_domain.html^headers^2
-rw-r--r--browser/components/originattributes/test/browser/test_firstParty_iframe_http_redirect.html13
-rw-r--r--browser/components/originattributes/test/browser/test_firstParty_postMessage.html28
-rw-r--r--browser/components/originattributes/test/browser/test_form.html14
-rw-r--r--browser/components/originattributes/test/browser/window.html11
-rw-r--r--browser/components/originattributes/test/browser/window2.html11
-rw-r--r--browser/components/originattributes/test/browser/window3.html11
-rw-r--r--browser/components/originattributes/test/browser/window_redirect.html12
-rw-r--r--browser/components/originattributes/test/mochitest/file_empty.html2
-rw-r--r--browser/components/originattributes/test/mochitest/mochitest.toml6
-rw-r--r--browser/components/originattributes/test/mochitest/test_permissions_api.html176
87 files changed, 4950 insertions, 0 deletions
diff --git a/browser/components/originattributes/moz.build b/browser/components/originattributes/moz.build
new file mode 100644
index 0000000000..21b004095e
--- /dev/null
+++ b/browser/components/originattributes/moz.build
@@ -0,0 +1,14 @@
+# -*- 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.toml",
+]
+
+MOCHITEST_MANIFESTS += ["test/mochitest/mochitest.toml"]
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "DOM: Security")
diff --git a/browser/components/originattributes/test/browser/blobify.worker.js b/browser/components/originattributes/test/browser/blobify.worker.js
new file mode 100644
index 0000000000..0bac973a15
--- /dev/null
+++ b/browser/components/originattributes/test/browser/blobify.worker.js
@@ -0,0 +1,47 @@
+// Wait for a string to be posted to this worker.
+// Create a blob containing this string, and then
+// post back a blob URL pointing to the blob.
+
+var postStringInBlob = function (blobObject) {
+ var fileReader = new FileReaderSync();
+ var result = fileReader.readAsText(blobObject);
+ postMessage(result);
+};
+
+self.addEventListener("message", e => {
+ if (e.data.what === "blobify") {
+ try {
+ let blobURL = URL.createObjectURL(new Blob([e.data.message]));
+ postMessage({ blobURL });
+ } catch (ex) {
+ postMessage({ error: ex.message });
+ }
+ return;
+ }
+
+ if (e.data.what === "deblobify") {
+ if ("error" in e.data.message) {
+ postMessage(e.data.message);
+ return;
+ }
+ let blobURL = e.data.message.blobURL,
+ xhr = new XMLHttpRequest();
+ try {
+ xhr.open("GET", blobURL, true);
+ xhr.onload = function () {
+ postStringInBlob(xhr.response);
+ };
+ xhr.onerror = function () {
+ postMessage({ error: "xhr error" });
+ };
+ xhr.responseType = "blob";
+ xhr.send();
+ } catch (ex) {
+ postMessage({ error: ex.message });
+ }
+
+ return;
+ }
+
+ postMessage("Invalid operation!");
+});
diff --git a/browser/components/originattributes/test/browser/browser.toml b/browser/components/originattributes/test/browser/browser.toml
new file mode 100644
index 0000000000..5585b2a914
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser.toml
@@ -0,0 +1,120 @@
+[DEFAULT]
+tags = "usercontextid firstpartyisolation originattributes"
+support-files = [
+ "blobify.worker.js",
+ "dummy.html",
+ "file_broadcastChannel.html",
+ "file_broadcastChanneliFrame.html",
+ "file_cache.html",
+ "file_favicon.html",
+ "file_favicon.png",
+ "file_favicon.png^headers^",
+ "file_favicon_cache.html",
+ "file_favicon_cache.png",
+ "file_favicon_thirdParty.html",
+ "file_firstPartyBasic.html",
+ "file_postMessage.html",
+ "file_postMessageSender.html",
+ "file_saveAs.sjs",
+ "file_sharedworker.html",
+ "file_shared.worker.js",
+ "file_thirdPartyChild.audio.ogg",
+ "file_thirdPartyChild.embed.png",
+ "file_thirdPartyChild.fetch.html",
+ "file_thirdPartyChild.font.woff",
+ "file_thirdPartyChild.iframe.html",
+ "file_thirdPartyChild.favicon.png",
+ "file_thirdPartyChild.img.png",
+ "file_thirdPartyChild.import.js",
+ "file_thirdPartyChild.link.css",
+ "file_thirdPartyChild.object.png",
+ "file_thirdPartyChild.request.html",
+ "file_thirdPartyChild.script.js",
+ "file_thirdPartyChild.sharedworker.js",
+ "file_thirdPartyChild.track.vtt",
+ "file_thirdPartyChild.video.ogv",
+ "file_thirdPartyChild.worker.fetch.html",
+ "file_thirdPartyChild.worker.js",
+ "file_thirdPartyChild.worker.request.html",
+ "file_thirdPartyChild.worker.xhr.html",
+ "file_thirdPartyChild.xhr.html",
+ "file_windowOpenerRestriction.html",
+ "file_windowOpenerRestrictionTarget.html",
+ "head.js",
+ "test.js",
+ "test.js^headers^",
+ "test.html",
+ "test2.html",
+ "test2.js",
+ "test2.js^headers^",
+ "test_firstParty.html",
+ "test_firstParty_cookie.html",
+ "test_firstParty_html_redirect.html",
+ "test_firstParty_http_redirect.html",
+ "test_firstParty_http_redirect.html^headers^",
+ "test_firstParty_http_redirect_to_same_domain.html",
+ "test_firstParty_http_redirect_to_same_domain.html^headers^",
+ "test_firstParty_iframe_http_redirect.html",
+ "test_firstParty_postMessage.html",
+ "test_form.html",
+ "window.html",
+ "window2.html",
+ "window3.html",
+ "window_redirect.html",
+ "!/toolkit/content/tests/browser/common/mockTransfer.js",
+]
+# We don't want to run tests using BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN
+# (5 - aka Dynamic First Party Isolation) yet.
+prefs = ["network.cookie.cookieBehavior=4"]
+
+["browser_blobURLIsolation.js"]
+skip-if = ["verify && debug && os == 'win'"]
+
+["browser_broadcastChannel.js"]
+
+["browser_cache.js"]
+skip-if = ["verify"]
+
+["browser_cacheAPI.js"]
+
+["browser_clientAuth.js"]
+skip-if = ["verify"]
+
+["browser_cookieIsolation.js"]
+
+["browser_favicon_firstParty.js"]
+
+["browser_favicon_userContextId.js"]
+
+["browser_firstPartyIsolation.js"]
+skip-if = ["debug"] #Bug 1345346
+
+["browser_firstPartyIsolation_aboutPages.js"]
+
+["browser_firstPartyIsolation_about_newtab.js"]
+
+["browser_firstPartyIsolation_blobURI.js"]
+
+["browser_firstPartyIsolation_js_uri.js"]
+
+["browser_firstPartyIsolation_saveAs.js"]
+
+["browser_httpauth.js"]
+
+["browser_imageCacheIsolation.js"]
+
+["browser_localStorageIsolation.js"]
+
+["browser_permissions.js"]
+
+["browser_postMessage.js"]
+
+["browser_sanitize.js"]
+skip-if = [
+ "os == 'win'", #Bug 1544810
+ "os == 'linux' && bits == 64", #Bug 1544810
+]
+
+["browser_sharedworker.js"]
+
+["browser_windowOpenerRestriction.js"]
diff --git a/browser/components/originattributes/test/browser/browser_blobURLIsolation.js b/browser/components/originattributes/test/browser/browser_blobURLIsolation.js
new file mode 100644
index 0000000000..b19aabf6d5
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_blobURLIsolation.js
@@ -0,0 +1,123 @@
+/**
+ * Bug 1264573 - A test case for blob url isolation.
+ */
+
+const TEST_PAGE =
+ "http://mochi.test:8888/browser/browser/components/" +
+ "originattributes/test/browser/file_firstPartyBasic.html";
+const SCRIPT_WORKER_BLOBIFY = "blobify.worker.js";
+
+function page_blobify(browser, input) {
+ return SpecialPowers.spawn(browser, [input], function (contentInput) {
+ return {
+ blobURL: content.URL.createObjectURL(new content.Blob([contentInput])),
+ };
+ });
+}
+
+function page_deblobify(browser, blobURL) {
+ return SpecialPowers.spawn(
+ browser,
+ [blobURL],
+ async function (contentBlobURL) {
+ if ("error" in contentBlobURL) {
+ return contentBlobURL;
+ }
+ contentBlobURL = contentBlobURL.blobURL;
+
+ function blobURLtoBlob(aBlobURL) {
+ return new content.Promise(function (resolve) {
+ let xhr = new content.XMLHttpRequest();
+ xhr.open("GET", aBlobURL, true);
+ xhr.onload = function () {
+ resolve(xhr.response);
+ };
+ xhr.onerror = function () {
+ resolve("xhr error");
+ };
+ xhr.responseType = "blob";
+ xhr.send();
+ });
+ }
+
+ function blobToString(blob) {
+ return new content.Promise(function (resolve) {
+ let fileReader = new content.FileReader();
+ fileReader.onload = function () {
+ resolve(fileReader.result);
+ };
+ fileReader.readAsText(blob);
+ });
+ }
+
+ let blob = await blobURLtoBlob(contentBlobURL);
+ if (blob == "xhr error") {
+ return "xhr error";
+ }
+
+ return blobToString(blob);
+ }
+ );
+}
+
+function workerIO(browser, what, message) {
+ return SpecialPowers.spawn(
+ browser,
+ [
+ {
+ scriptFile: SCRIPT_WORKER_BLOBIFY,
+ message: { message, what },
+ },
+ ],
+ function (args) {
+ if (!content.worker) {
+ content.worker = new content.Worker(args.scriptFile);
+ }
+ let promise = new content.Promise(function (resolve) {
+ let listenFunction = function (event) {
+ content.worker.removeEventListener("message", listenFunction);
+ resolve(event.data);
+ };
+ content.worker.addEventListener("message", listenFunction);
+ });
+ content.worker.postMessage(args.message);
+ return promise;
+ }
+ );
+}
+
+let worker_blobify = (browser, input) => workerIO(browser, "blobify", input);
+let worker_deblobify = (browser, blobURL) =>
+ workerIO(browser, "deblobify", blobURL);
+
+function doTest(blobify, deblobify) {
+ let blobURL = null;
+ return async function (browser) {
+ if (blobURL === null) {
+ let input = Math.random().toString();
+ blobURL = await blobify(browser, input);
+ return input;
+ }
+ let result = await deblobify(browser, blobURL);
+ blobURL = null;
+ return result;
+ };
+}
+
+let tests = [];
+for (let blobify of [page_blobify, worker_blobify]) {
+ for (let deblobify of [page_deblobify, worker_deblobify]) {
+ tests.push(doTest(blobify, deblobify));
+ }
+}
+
+async function setup() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.partition.bloburl_per_partition_key", false],
+ ["dom.security.https_first", false],
+ ],
+ });
+}
+
+IsolationTestTools.runTests(TEST_PAGE, tests, null, setup);
diff --git a/browser/components/originattributes/test/browser/browser_broadcastChannel.js b/browser/components/originattributes/test/browser/browser_broadcastChannel.js
new file mode 100644
index 0000000000..805df90c17
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_broadcastChannel.js
@@ -0,0 +1,55 @@
+/*
+ * Bug 1264571 - A test case of broadcast channels for first party isolation.
+ */
+
+const TEST_DOMAIN = "http://example.net/";
+const TEST_PATH =
+ TEST_DOMAIN + "browser/browser/components/originattributes/test/browser/";
+const TEST_PAGE = TEST_PATH + "file_broadcastChannel.html";
+
+// IsolationTestTools flushes all preferences
+// hence we explicitly pref off https-first mode
+async function prefOffHttpsFirstMode() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.security.https_first", false]],
+ });
+}
+
+async function doTest(aBrowser) {
+ let response = await SpecialPowers.spawn(aBrowser, [], async function () {
+ let displayItem = content.document.getElementById("display");
+
+ // If there is nothing in the 'display', we will try to send a message to
+ // the broadcast channel and wait until this message has been delivered.
+ // The way that how we make sure the message is delivered is based on an
+ // iframe which will reply everything it receives from the broadcast channel
+ // to the current window through the postMessage. So, we can know that the
+ // boradcast message is sent successfully when the window receives a message
+ // from the iframe.
+ if (displayItem.innerHTML === "") {
+ let data = Math.random().toString();
+
+ let receivedData = await new Promise(resolve => {
+ let listenFunc = event => {
+ content.removeEventListener("message", listenFunc);
+ resolve(event.data);
+ };
+
+ let bc = new content.BroadcastChannel("testBroadcastChannel");
+
+ content.addEventListener("message", listenFunc);
+ bc.postMessage(data);
+ });
+
+ Assert.equal(receivedData, data, "The value should be the same.");
+
+ return receivedData;
+ }
+
+ return displayItem.innerHTML;
+ });
+
+ return response;
+}
+
+IsolationTestTools.runTests(TEST_PAGE, doTest, null, prefOffHttpsFirstMode);
diff --git a/browser/components/originattributes/test/browser/browser_cache.js b/browser/components/originattributes/test/browser/browser_cache.js
new file mode 100644
index 0000000000..ea8f0fe803
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_cache.js
@@ -0,0 +1,350 @@
+/*
+ * Bug 1264577 - A test case for testing caches of various submodules.
+ * This test case will load two pages that each page loads various resources
+ * within the same third party domain for the same originAttributes or different
+ * originAttributes. And then, it verifies the number of cache entries and
+ * the originAttributes of loading channels. If these two pages belong to
+ * the same originAttributes, the number of cache entries for a certain
+ * resource would be one. Otherwise, it would be two.
+ */
+
+const CC = Components.Constructor;
+
+let protocolProxyService = Cc[
+ "@mozilla.org/network/protocol-proxy-service;1"
+].getService(Ci.nsIProtocolProxyService);
+
+const TEST_DOMAIN = "http://example.net";
+const TEST_PATH = "/browser/browser/components/originattributes/test/browser/";
+const TEST_PAGE = TEST_DOMAIN + TEST_PATH + "file_cache.html";
+
+let suffixes = [
+ "iframe.html",
+ "link.css",
+ "script.js",
+ "img.png",
+ "object.png",
+ "embed.png",
+ "xhr.html",
+ "worker.xhr.html",
+ "audio.ogg",
+ "video.ogv",
+ "track.vtt",
+ "fetch.html",
+ "worker.fetch.html",
+ "request.html",
+ "worker.request.html",
+ "import.js",
+ "worker.js",
+ "sharedworker.js",
+ "font.woff",
+];
+
+// A random value for isolating video/audio elements across different tests.
+let randomSuffix;
+
+function clearAllImageCaches() {
+ let tools = SpecialPowers.Cc["@mozilla.org/image/tools;1"].getService(
+ SpecialPowers.Ci.imgITools
+ );
+ let imageCache = tools.getImgCacheForDocument(window.document);
+ imageCache.clearCache(true); // true=chrome
+ imageCache.clearCache(false); // false=content
+}
+
+function cacheDataForContext(loadContextInfo) {
+ return new Promise(resolve => {
+ let cacheEntries = [];
+ let cacheVisitor = {
+ onCacheStorageInfo(num, consumption) {},
+ onCacheEntryInfo(uri, idEnhance) {
+ cacheEntries.push({ uri, idEnhance });
+ },
+ onCacheEntryVisitCompleted() {
+ resolve(cacheEntries);
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsICacheStorageVisitor"]),
+ };
+ // Visiting the disk cache also visits memory storage so we do not
+ // need to use Services.cache2.memoryCacheStorage() here.
+ let storage = Services.cache2.diskCacheStorage(loadContextInfo);
+ storage.asyncVisitStorage(cacheVisitor, true);
+ });
+}
+
+let countMatchingCacheEntries = function (cacheEntries, domain, fileSuffix) {
+ return cacheEntries
+ .map(entry => entry.uri.asciiSpec)
+ .filter(spec => spec.includes(domain))
+ .filter(spec => spec.includes("file_thirdPartyChild." + fileSuffix)).length;
+};
+
+function observeChannels(onChannel) {
+ // We use a dummy proxy filter to catch all channels, even those that do not
+ // generate an "http-on-modify-request" notification, such as link preconnects.
+ let proxyFilter = {
+ applyFilter(aChannel, aProxy, aCallback) {
+ // We have the channel; provide it to the callback.
+ onChannel(aChannel);
+ // Pass on aProxy unmodified.
+ aCallback.onProxyFilterResult(aProxy);
+ },
+ };
+ protocolProxyService.registerChannelFilter(proxyFilter, 0);
+ // Return the stop() function:
+ return () => protocolProxyService.unregisterChannelFilter(proxyFilter);
+}
+
+function startObservingChannels(aMode) {
+ let stopObservingChannels = observeChannels(function (channel) {
+ let originalURISpec = channel.originalURI.spec;
+ if (originalURISpec.includes("example.net")) {
+ let loadInfo = channel.loadInfo;
+
+ switch (aMode) {
+ case TEST_MODE_FIRSTPARTY:
+ ok(
+ loadInfo.originAttributes.firstPartyDomain === "example.com" ||
+ loadInfo.originAttributes.firstPartyDomain === "example.org",
+ "first party for " +
+ originalURISpec +
+ " is " +
+ loadInfo.originAttributes.firstPartyDomain
+ );
+ break;
+
+ case TEST_MODE_NO_ISOLATION:
+ ok(
+ ChromeUtils.isOriginAttributesEqual(
+ loadInfo.originAttributes,
+ ChromeUtils.fillNonDefaultOriginAttributes()
+ ),
+ "OriginAttributes for " + originalURISpec + " is default."
+ );
+ break;
+
+ case TEST_MODE_CONTAINERS:
+ ok(
+ loadInfo.originAttributes.userContextId === 1 ||
+ loadInfo.originAttributes.userContextId === 2,
+ "userContextId for " +
+ originalURISpec +
+ " is " +
+ loadInfo.originAttributes.userContextId
+ );
+ break;
+
+ default:
+ ok(false, "Unknown test mode.");
+ }
+ }
+ });
+ return stopObservingChannels;
+}
+
+let stopObservingChannels;
+
+// The init function, which clears image and network caches, and generates
+// the random value for isolating video and audio elements across different
+// test runs.
+async function doInit(aMode) {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["network.predictor.enabled", false],
+ ["network.predictor.enable-prefetch", false],
+ ["privacy.partition.network_state", false],
+ ["dom.security.https_first", false],
+ ],
+ });
+ clearAllImageCaches();
+
+ Services.cache2.clear();
+
+ randomSuffix = Math.random();
+ stopObservingChannels = startObservingChannels(aMode);
+}
+
+// In the test function, we dynamically generate the video and audio element,
+// and assign a random suffix to their URL to isolate them across different
+// test runs.
+async function doTest(aBrowser) {
+ let argObj = {
+ randomSuffix,
+ urlPrefix: TEST_DOMAIN + TEST_PATH,
+ };
+
+ await SpecialPowers.spawn(aBrowser, [argObj], async function (arg) {
+ content.windowUtils.clearSharedStyleSheetCache();
+
+ let videoURL = arg.urlPrefix + "file_thirdPartyChild.video.ogv";
+ let audioURL = arg.urlPrefix + "file_thirdPartyChild.audio.ogg";
+ let trackURL = arg.urlPrefix + "file_thirdPartyChild.track.vtt";
+ let URLSuffix = "?r=" + arg.randomSuffix;
+
+ // Create the audio and video elements.
+ let audio = content.document.createElement("audio");
+ let video = content.document.createElement("video");
+ let audioSource = content.document.createElement("source");
+ let audioTrack = content.document.createElement("track");
+
+ // Append the audio and track element into the body, and wait until they're finished.
+ await new content.Promise(resolve => {
+ let audioLoaded = false;
+ let trackLoaded = false;
+
+ let audioListener = () => {
+ Assert.ok(true, `Audio suspended: ${audioURL + URLSuffix}`);
+ audio.removeEventListener("suspend", audioListener);
+
+ audioLoaded = true;
+ if (audioLoaded && trackLoaded) {
+ resolve();
+ }
+ };
+
+ let trackListener = () => {
+ Assert.ok(true, `Audio track loaded: ${audioURL + URLSuffix}`);
+ audioTrack.removeEventListener("load", trackListener);
+
+ trackLoaded = true;
+ if (audioLoaded && trackLoaded) {
+ resolve();
+ }
+ };
+
+ Assert.ok(true, `Loading audio: ${audioURL + URLSuffix}`);
+
+ // Add the event listeners before everything in case we lose events.
+ audioTrack.addEventListener("load", trackListener);
+ audio.addEventListener("suspend", audioListener);
+
+ // Assign attributes for the audio element.
+ audioSource.setAttribute("src", audioURL + URLSuffix);
+ audioSource.setAttribute("type", "audio/ogg");
+ audioTrack.setAttribute("src", trackURL);
+ audioTrack.setAttribute("kind", "subtitles");
+ audioTrack.setAttribute("default", true);
+
+ audio.appendChild(audioSource);
+ audio.appendChild(audioTrack);
+ audio.autoplay = true;
+
+ content.document.body.appendChild(audio);
+ });
+
+ // Append the video element into the body, and wait until it's finished.
+ await new content.Promise(resolve => {
+ let listener = () => {
+ Assert.ok(true, `Video suspended: ${videoURL + URLSuffix}`);
+ video.removeEventListener("suspend", listener);
+ resolve();
+ };
+
+ Assert.ok(true, `Loading video: ${videoURL + URLSuffix}`);
+
+ // Add the event listener before everything in case we lose the event.
+ video.addEventListener("suspend", listener);
+
+ // Assign attributes for the video element.
+ video.setAttribute("src", videoURL + URLSuffix);
+ video.setAttribute("type", "video/ogg");
+
+ content.document.body.appendChild(video);
+ });
+ });
+
+ return 0;
+}
+
+// The check function, which checks the number of cache entries.
+async function doCheck(aShouldIsolate, aInputA, aInputB) {
+ let expectedEntryCount = 1;
+ let data = [];
+ data = data.concat(
+ await cacheDataForContext(Services.loadContextInfo.default)
+ );
+ data = data.concat(
+ await cacheDataForContext(Services.loadContextInfo.private)
+ );
+ data = data.concat(
+ await cacheDataForContext(Services.loadContextInfo.custom(true, {}))
+ );
+ data = data.concat(
+ await cacheDataForContext(
+ Services.loadContextInfo.custom(false, { userContextId: 1 })
+ )
+ );
+ data = data.concat(
+ await cacheDataForContext(
+ Services.loadContextInfo.custom(true, { userContextId: 1 })
+ )
+ );
+ data = data.concat(
+ await cacheDataForContext(
+ Services.loadContextInfo.custom(false, { userContextId: 2 })
+ )
+ );
+ data = data.concat(
+ await cacheDataForContext(
+ Services.loadContextInfo.custom(true, { userContextId: 2 })
+ )
+ );
+ data = data.concat(
+ await cacheDataForContext(
+ Services.loadContextInfo.custom(false, {
+ firstPartyDomain: "example.com",
+ })
+ )
+ );
+ data = data.concat(
+ await cacheDataForContext(
+ Services.loadContextInfo.custom(true, { firstPartyDomain: "example.com" })
+ )
+ );
+ data = data.concat(
+ await cacheDataForContext(
+ Services.loadContextInfo.custom(false, {
+ firstPartyDomain: "example.org",
+ })
+ )
+ );
+ data = data.concat(
+ await cacheDataForContext(
+ Services.loadContextInfo.custom(true, { firstPartyDomain: "example.org" })
+ )
+ );
+
+ if (aShouldIsolate) {
+ expectedEntryCount = 2;
+ }
+
+ for (let suffix of suffixes) {
+ let foundEntryCount = countMatchingCacheEntries(
+ data,
+ "example.net",
+ suffix
+ );
+ let result = expectedEntryCount === foundEntryCount;
+ ok(
+ result,
+ "Cache entries expected for " +
+ suffix +
+ ": " +
+ expectedEntryCount +
+ ", and found " +
+ foundEntryCount
+ );
+ }
+
+ stopObservingChannels();
+ stopObservingChannels = undefined;
+ return true;
+}
+
+let testArgs = {
+ url: TEST_PAGE,
+ firstFrameSetting: DEFAULT_FRAME_SETTING,
+ secondFrameSetting: [TEST_TYPE_FRAME],
+};
+
+IsolationTestTools.runTests(testArgs, doTest, doCheck, doInit);
diff --git a/browser/components/originattributes/test/browser/browser_cacheAPI.js b/browser/components/originattributes/test/browser/browser_cacheAPI.js
new file mode 100644
index 0000000000..7ebdb0a1fe
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_cacheAPI.js
@@ -0,0 +1,24 @@
+const requestURL = "https://test1.example.com";
+
+function getResult(aBrowser) {
+ return SpecialPowers.spawn(aBrowser, [requestURL], async function (url) {
+ let cache = await content.caches.open("TEST_CACHE");
+ let response = await cache.match(url);
+ if (response) {
+ return response.statusText;
+ }
+ let result = Math.random().toString();
+ response = new content.Response("", { statusText: result });
+ await cache.put(url, response);
+ return result;
+ });
+}
+
+IsolationTestTools.runTests(
+ "https://test2.example.com",
+ getResult,
+ null,
+ null,
+ false,
+ /* aUseHttps */ true
+);
diff --git a/browser/components/originattributes/test/browser/browser_clientAuth.js b/browser/components/originattributes/test/browser/browser_clientAuth.js
new file mode 100644
index 0000000000..f93ec1dff2
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_clientAuth.js
@@ -0,0 +1,49 @@
+let certCached = true;
+let secondTabStarted = false;
+
+function onCertDialogLoaded(subject) {
+ certCached = false;
+ // Click OK.
+ subject.acceptDialog();
+}
+
+Services.obs.addObserver(onCertDialogLoaded, "cert-dialog-loaded");
+
+registerCleanupFunction(() => {
+ Services.obs.removeObserver(onCertDialogLoaded, "cert-dialog-loaded");
+});
+
+async function setup() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["security.default_personal_cert", "Ask Every Time"],
+ ["privacy.partition.network_state", false],
+ ],
+ });
+}
+
+function getResult() {
+ // The first tab always returns true.
+ if (!secondTabStarted) {
+ certCached = true;
+ secondTabStarted = true;
+ return true;
+ }
+
+ // The second tab returns true if the cert is cached, so it will be different
+ // from the result of the first tab, and considered isolated.
+ let ret = certCached;
+ certCached = true;
+ secondTabStarted = false;
+ return ret;
+}
+
+// aGetResultImmediately must be true because we need to get the result before
+// the next tab is opened.
+IsolationTestTools.runTests(
+ "https://requireclientcert.example.com",
+ getResult,
+ null, // aCompareResultFunc
+ setup, // aBeginFunc
+ true
+); // aGetResultImmediately
diff --git a/browser/components/originattributes/test/browser/browser_cookieIsolation.js b/browser/components/originattributes/test/browser/browser_cookieIsolation.js
new file mode 100644
index 0000000000..e10943e588
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_cookieIsolation.js
@@ -0,0 +1,43 @@
+/**
+ * Bug 1312541 - A test case for document.cookie isolation.
+ */
+
+const TEST_PAGE =
+ "https://example.net/browser/browser/components/" +
+ "originattributes/test/browser/file_firstPartyBasic.html";
+
+// Use a random key so we don't access it in later tests.
+const key = "key" + Math.random().toString();
+const re = new RegExp(key + "=([0-9.]+)");
+
+// Define the testing function
+function doTest(aBrowser) {
+ return SpecialPowers.spawn(
+ aBrowser,
+ [key, re],
+ function (contentKey, contentRe) {
+ let result = contentRe.exec(content.document.cookie);
+ if (result) {
+ return result[1];
+ }
+ // No value is found, so we create one.
+ let value = Math.random().toString();
+ content.document.cookie =
+ contentKey + "=" + value + "; SameSite=None; Secure;";
+ return value;
+ }
+ );
+}
+
+registerCleanupFunction(() => {
+ Services.cookies.removeAll();
+});
+
+IsolationTestTools.runTests(
+ TEST_PAGE,
+ doTest,
+ null,
+ null,
+ false,
+ true /* aUseHttps */
+);
diff --git a/browser/components/originattributes/test/browser/browser_favicon_firstParty.js b/browser/components/originattributes/test/browser/browser_favicon_firstParty.js
new file mode 100644
index 0000000000..300c2f9f25
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_favicon_firstParty.js
@@ -0,0 +1,511 @@
+/**
+ * Bug 1277803 - A test case for testing favicon loading across different first party domains.
+ */
+
+if (SpecialPowers.useRemoteSubframes) {
+ requestLongerTimeout(2);
+}
+
+const CC = Components.Constructor;
+
+const { PlacesTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PlacesTestUtils.sys.mjs"
+);
+
+let EventUtils = {};
+Services.scriptloader.loadSubScript(
+ "chrome://mochikit/content/tests/SimpleTest/EventUtils.js",
+ EventUtils
+);
+
+const FIRST_PARTY_ONE = "example.com";
+const FIRST_PARTY_TWO = "example.org";
+const THIRD_PARTY = "example.net";
+
+const TEST_SITE_ONE = "https://" + FIRST_PARTY_ONE;
+const TEST_SITE_TWO = "https://" + FIRST_PARTY_TWO;
+const THIRD_PARTY_SITE = "https://" + THIRD_PARTY;
+const TEST_DIRECTORY =
+ "/browser/browser/components/originattributes/test/browser/";
+
+const TEST_PAGE = TEST_DIRECTORY + "file_favicon.html";
+const TEST_THIRD_PARTY_PAGE = TEST_DIRECTORY + "file_favicon_thirdParty.html";
+const TEST_CACHE_PAGE = TEST_DIRECTORY + "file_favicon_cache.html";
+
+const FAVICON_URI = TEST_DIRECTORY + "file_favicon.png";
+const TEST_FAVICON_CACHE_URI = TEST_DIRECTORY + "file_favicon_cache.png";
+
+const ICON_DATA =
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAABH0lEQVRYw2P8////f4YBBEwMAwxGHcBCUMX/91DGOSj/BpT/DkpzQChGBSjfBErLQsVZhmoI/L8LpRdD6X1QietQGhYy7FB5aAgwmkLpBKi4BZTPMThDgBGjHIDF+f9mKD0fKvGBRKNdoF7sgPL1saaJwZgGDkJ9vpZMn8PAHqg5G9FyifBgD4H/W9HyOWrU/f+DIzHhkoeZxxgzZEIAVtJ9RxX+Q6DAxCmP3byhXxkxshAs5odqbcioAY3UC1CBLyTGOTqAmsfAOWRCwBvqxV0oIUB2OQAzDy3/D+a6wB7q8mCU2vD/nw94GziYIQOtDRn9oXz+IZMGBKGMbCjNh9Ii+v8HR4uIAUeLiEEbb9twELaIRlqrmHG0bzjiHQAA1LVfww8jwM4AAAAASUVORK5CYII=";
+
+let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
+
+function clearAllImageCaches() {
+ let tools = SpecialPowers.Cc["@mozilla.org/image/tools;1"].getService(
+ SpecialPowers.Ci.imgITools
+ );
+ let imageCache = tools.getImgCacheForDocument(window.document);
+ imageCache.clearCache(true); // true=chrome
+ imageCache.clearCache(false); // false=content
+}
+
+function clearAllPlacesFavicons() {
+ let faviconService = Cc["@mozilla.org/browser/favicon-service;1"].getService(
+ Ci.nsIFaviconService
+ );
+
+ return new Promise(resolve => {
+ let observer = {
+ observe(aSubject, aTopic, aData) {
+ if (aTopic === "places-favicons-expired") {
+ resolve();
+ Services.obs.removeObserver(observer, "places-favicons-expired");
+ }
+ },
+ };
+
+ Services.obs.addObserver(observer, "places-favicons-expired");
+ faviconService.expireAllFavicons();
+ });
+}
+
+function observeFavicon(aFirstPartyDomain, aExpectedCookie, aPageURI) {
+ let expectedPrincipal = Services.scriptSecurityManager.createContentPrincipal(
+ aPageURI,
+ { firstPartyDomain: aFirstPartyDomain }
+ );
+
+ return new Promise(resolve => {
+ let observer = {
+ observe(aSubject, aTopic, aData) {
+ // Make sure that the topic is 'http-on-modify-request'.
+ if (aTopic === "http-on-modify-request") {
+ // We check the firstPartyDomain for the originAttributes of the loading
+ // channel. All requests for the favicon should contain the correct
+ // firstPartyDomain. There are two requests for a favicon loading, one
+ // from the Places library and one from the XUL image. The difference
+ // of them is the loading principal. The Places will use the content
+ // principal and the XUL image will use the system principal.
+
+ let httpChannel = aSubject.QueryInterface(Ci.nsIHttpChannel);
+ let reqLoadInfo = httpChannel.loadInfo;
+ let loadingPrincipal = reqLoadInfo.loadingPrincipal;
+ let triggeringPrincipal = reqLoadInfo.triggeringPrincipal;
+
+ // Make sure this is a favicon request.
+ if (!httpChannel.URI.spec.endsWith(FAVICON_URI)) {
+ return;
+ }
+
+ // Check the first party domain.
+ is(
+ reqLoadInfo.originAttributes.firstPartyDomain,
+ aFirstPartyDomain,
+ "The loadInfo has correct first party domain"
+ );
+
+ ok(
+ loadingPrincipal.equals(expectedPrincipal),
+ "The loadingPrincipal of favicon loads should be the content prinicpal"
+ );
+ ok(
+ triggeringPrincipal.equals(expectedPrincipal),
+ "The triggeringPrincipal of favicon loads should be the content prinicpal"
+ );
+
+ let faviconCookie = httpChannel.getRequestHeader("cookie");
+
+ is(
+ faviconCookie,
+ aExpectedCookie,
+ "The cookie of the favicon loading is correct."
+ );
+ } else {
+ ok(false, "Received unexpected topic: ", aTopic);
+ }
+
+ Services.obs.removeObserver(observer, "http-on-modify-request");
+ resolve();
+ },
+ };
+
+ Services.obs.addObserver(observer, "http-on-modify-request");
+ });
+}
+
+function waitOnFaviconResponse(aFaviconURL) {
+ return new Promise(resolve => {
+ let observer = {
+ observe(aSubject, aTopic, aData) {
+ if (
+ aTopic === "http-on-examine-response" ||
+ aTopic === "http-on-examine-cached-response"
+ ) {
+ let httpChannel = aSubject.QueryInterface(Ci.nsIHttpChannel);
+ let loadInfo = httpChannel.loadInfo;
+
+ if (httpChannel.URI.spec !== aFaviconURL) {
+ return;
+ }
+
+ let result = {
+ topic: aTopic,
+ firstPartyDomain: loadInfo.originAttributes.firstPartyDomain,
+ };
+
+ resolve(result);
+ Services.obs.removeObserver(observer, "http-on-examine-response");
+ Services.obs.removeObserver(
+ observer,
+ "http-on-examine-cached-response"
+ );
+ }
+ },
+ };
+
+ Services.obs.addObserver(observer, "http-on-examine-response");
+ Services.obs.addObserver(observer, "http-on-examine-cached-response");
+ });
+}
+
+function waitOnFaviconLoaded(aFaviconURL) {
+ return PlacesTestUtils.waitForNotification("favicon-changed", events =>
+ events.some(e => e.faviconUrl == aFaviconURL)
+ );
+}
+
+async function openTab(aURL) {
+ let tab = BrowserTestUtils.addTab(gBrowser, aURL);
+
+ // Select tab and make sure its browser is focused.
+ gBrowser.selectedTab = tab;
+ tab.ownerGlobal.focus();
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+ return { tab, browser };
+}
+
+async function assignCookiesUnderFirstParty(aURL, aFirstParty, aCookieValue) {
+ // Open a tab under the given aFirstParty, and this tab will have an
+ // iframe which loads the aURL.
+ let tabInfo = await openTabInFirstParty(aURL, aFirstParty);
+
+ // Add cookies into the iframe.
+ await SpecialPowers.spawn(
+ tabInfo.browser,
+ [aCookieValue],
+ async function (value) {
+ content.document.cookie = value + "; SameSite=None; Secure;";
+ }
+ );
+
+ BrowserTestUtils.removeTab(tabInfo.tab);
+}
+
+async function generateCookies(aThirdParty) {
+ // we generate two different cookies for two first party domains.
+ let cookies = [];
+ cookies.push(Math.random().toString());
+ cookies.push(Math.random().toString());
+
+ let firstSiteURL;
+ let secondSiteURL;
+
+ if (aThirdParty) {
+ // Add cookies into the third party site with different first party domain.
+ firstSiteURL = THIRD_PARTY_SITE;
+ secondSiteURL = THIRD_PARTY_SITE;
+ } else {
+ // Add cookies into sites.
+ firstSiteURL = TEST_SITE_ONE;
+ secondSiteURL = TEST_SITE_TWO;
+ }
+
+ await assignCookiesUnderFirstParty(firstSiteURL, TEST_SITE_ONE, cookies[0]);
+ await assignCookiesUnderFirstParty(secondSiteURL, TEST_SITE_TWO, cookies[1]);
+
+ return cookies;
+}
+
+function assertIconIsData(item) {
+ let icon = item.getAttribute("image");
+ is(
+ icon.substring(0, 5),
+ "data:",
+ "Expected the image element to be a data URI"
+ );
+ is(icon, ICON_DATA, "Expected to see the correct data.");
+}
+
+async function doTest(aTestPage, aExpectedCookies, aFaviconURL) {
+ let firstPageURI = Services.io.newURI(TEST_SITE_ONE + aTestPage);
+ let secondPageURI = Services.io.newURI(TEST_SITE_TWO + aTestPage);
+
+ // Start to observe the event of that favicon has been fully loaded.
+ let promiseFaviconLoaded = waitOnFaviconLoaded(aFaviconURL);
+
+ // Start to observe the favicon requests earlier in case we miss it.
+ let promiseObserveFavicon = observeFavicon(
+ FIRST_PARTY_ONE,
+ aExpectedCookies[0],
+ firstPageURI
+ );
+
+ // Open the tab for the first site.
+ let tabInfo = await openTab(TEST_SITE_ONE + aTestPage);
+
+ // Waiting until favicon requests are all made.
+ await promiseObserveFavicon;
+
+ // Waiting until favicon loaded.
+ await promiseFaviconLoaded;
+
+ assertIconIsData(tabInfo.tab);
+
+ BrowserTestUtils.removeTab(tabInfo.tab);
+ // FIXME: We need to wait for the next event tick here to avoid observing
+ // the previous tab info in the next step (bug 1446725).
+ await new Promise(executeSoon);
+
+ // Start to observe the favicon requests earlier in case we miss it.
+ promiseObserveFavicon = observeFavicon(
+ FIRST_PARTY_TWO,
+ aExpectedCookies[1],
+ secondPageURI
+ );
+
+ // Open the tab for the second site.
+ tabInfo = await openTab(TEST_SITE_TWO + aTestPage);
+
+ // Waiting until favicon requests are all made.
+ await promiseObserveFavicon;
+
+ BrowserTestUtils.removeTab(tabInfo.tab);
+}
+
+async function doTestForAllTabsFavicon(
+ aTestPage,
+ aExpectedCookies,
+ aIsThirdParty
+) {
+ let faviconURI = aIsThirdParty
+ ? THIRD_PARTY_SITE + FAVICON_URI
+ : TEST_SITE_ONE + FAVICON_URI;
+
+ // Set the 'overflow' attribute to make allTabs button available.
+ let tabBrowser = document.getElementById("tabbrowser-tabs");
+ tabBrowser.setAttribute("overflow", true);
+
+ // Start to observe the event of that the favicon has been fully loaded.
+ let promiseFaviconLoaded = waitOnFaviconLoaded(faviconURI);
+
+ // Open the tab for the first site.
+ let tabInfo = await openTab(TEST_SITE_ONE + aTestPage);
+
+ // Waiting until the favicon loaded.
+ await promiseFaviconLoaded;
+
+ assertIconIsData(tabInfo.tab);
+
+ gTabsPanel.init();
+
+ // Make the popup of allTabs showing up and trigger the loading of the favicon.
+ let allTabsView = document.getElementById("allTabsMenu-allTabsView");
+ let allTabsPopupShownPromise = BrowserTestUtils.waitForEvent(
+ allTabsView,
+ "ViewShown"
+ );
+ gTabsPanel.showAllTabsPanel();
+ await allTabsPopupShownPromise;
+
+ assertIconIsData(
+ gTabsPanel.allTabsViewTabs.lastElementChild.firstElementChild
+ );
+
+ // Close the popup of allTabs and wait until it's done.
+ let allTabsPopupHiddenPromise = BrowserTestUtils.waitForEvent(
+ allTabsView.panelMultiView,
+ "PanelMultiViewHidden"
+ );
+ gTabsPanel.hideAllTabsPanel();
+ await allTabsPopupHiddenPromise;
+
+ // Close the tab.
+ BrowserTestUtils.removeTab(tabInfo.tab);
+
+ faviconURI = aIsThirdParty
+ ? THIRD_PARTY_SITE + FAVICON_URI
+ : TEST_SITE_TWO + FAVICON_URI;
+
+ // Start to observe the event of that favicon has been fully loaded.
+ promiseFaviconLoaded = waitOnFaviconLoaded(faviconURI);
+
+ // Open the tab for the second site.
+ tabInfo = await openTab(TEST_SITE_TWO + aTestPage);
+
+ // Wait until the favicon is fully loaded.
+ await promiseFaviconLoaded;
+
+ assertIconIsData(tabInfo.tab);
+
+ // Make the popup of allTabs showing up again.
+ allTabsPopupShownPromise = BrowserTestUtils.waitForEvent(
+ allTabsView,
+ "ViewShown"
+ );
+ gTabsPanel.showAllTabsPanel();
+ await allTabsPopupShownPromise;
+
+ assertIconIsData(
+ gTabsPanel.allTabsViewTabs.lastElementChild.firstElementChild
+ );
+
+ // Close the popup of allTabs and wait until it's done.
+ allTabsPopupHiddenPromise = BrowserTestUtils.waitForEvent(
+ allTabsView.panelMultiView,
+ "PanelMultiViewHidden"
+ );
+ gTabsPanel.hideAllTabsPanel();
+ await allTabsPopupHiddenPromise;
+
+ // Close the tab.
+ BrowserTestUtils.removeTab(tabInfo.tab);
+
+ // Reset the 'overflow' attribute to make the allTabs button hidden again.
+ tabBrowser.removeAttribute("overflow");
+}
+
+add_setup(async function () {
+ // Make sure first party isolation is enabled.
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.firstparty.isolate", true]],
+ });
+});
+
+// A clean up function to prevent affecting other tests.
+registerCleanupFunction(() => {
+ // Clear all cookies.
+ Services.cookies.removeAll();
+
+ // Clear all image caches and network caches.
+ clearAllImageCaches();
+
+ Services.cache2.clear();
+});
+
+add_task(async function test_favicon_firstParty() {
+ for (let testThirdParty of [false, true]) {
+ // Clear all image caches and network caches before running the test.
+ clearAllImageCaches();
+
+ Services.cache2.clear();
+
+ // Clear Places favicon caches.
+ await clearAllPlacesFavicons();
+
+ let cookies = await generateCookies(testThirdParty);
+
+ if (testThirdParty) {
+ await doTest(
+ TEST_THIRD_PARTY_PAGE,
+ cookies,
+ THIRD_PARTY_SITE + FAVICON_URI
+ );
+ } else {
+ await doTest(TEST_PAGE, cookies, TEST_SITE_ONE + FAVICON_URI);
+ }
+ }
+});
+
+add_task(async function test_allTabs_favicon_firstParty() {
+ for (let testThirdParty of [false, true]) {
+ // Clear all image caches and network caches before running the test.
+ clearAllImageCaches();
+
+ Services.cache2.clear();
+
+ // Clear Places favicon caches.
+ await clearAllPlacesFavicons();
+
+ let cookies = await generateCookies(testThirdParty);
+
+ if (testThirdParty) {
+ await doTestForAllTabsFavicon(
+ TEST_THIRD_PARTY_PAGE,
+ cookies,
+ testThirdParty
+ );
+ } else {
+ await doTestForAllTabsFavicon(TEST_PAGE, cookies, testThirdParty);
+ }
+ }
+});
+
+add_task(async function test_favicon_cache_firstParty() {
+ // Clear all image caches and network caches before running the test.
+ clearAllImageCaches();
+
+ Services.cache2.clear();
+
+ // Start to observer the event of that favicon has been fully loaded and cached.
+ let promiseForFaviconLoaded = waitOnFaviconLoaded(
+ THIRD_PARTY_SITE + TEST_FAVICON_CACHE_URI
+ );
+
+ // Start to observer for the favicon response of the first tab.
+ let responsePromise = waitOnFaviconResponse(
+ THIRD_PARTY_SITE + TEST_FAVICON_CACHE_URI
+ );
+
+ // Open the tab for the first site.
+ let tabInfoA = await openTab(TEST_SITE_ONE + TEST_CACHE_PAGE);
+
+ // Waiting for the favicon response.
+ let response = await responsePromise;
+
+ // Make sure the favicon is loaded through the network and its first party domain is correct.
+ is(
+ response.topic,
+ "http-on-examine-response",
+ "The favicon image should be loaded through network."
+ );
+ is(
+ response.firstPartyDomain,
+ FIRST_PARTY_ONE,
+ "We should only observe the network response for the first first party."
+ );
+
+ // Waiting until the favicon has been loaded and cached.
+ await promiseForFaviconLoaded;
+
+ // Here, we are going to observe the favicon response for the third tab which
+ // opens with the second first party.
+ let promiseForFaviconResponse = waitOnFaviconResponse(
+ THIRD_PARTY_SITE + TEST_FAVICON_CACHE_URI
+ );
+
+ // Open the tab for the second site.
+ let tabInfoB = await openTab(TEST_SITE_TWO + TEST_CACHE_PAGE);
+
+ // Wait for the favicon response. In this case, we suppose to catch the
+ // response for the third tab but not the second tab since it will not
+ // go through the network.
+ response = await promiseForFaviconResponse;
+
+ // Check that the favicon response has came from the network and it has the
+ // correct first party domain.
+ is(
+ response.topic,
+ "http-on-examine-response",
+ "The favicon image should be loaded through network again."
+ );
+ is(
+ response.firstPartyDomain,
+ FIRST_PARTY_TWO,
+ "We should only observe the network response for the second first party."
+ );
+
+ BrowserTestUtils.removeTab(tabInfoA.tab);
+ BrowserTestUtils.removeTab(tabInfoB.tab);
+});
diff --git a/browser/components/originattributes/test/browser/browser_favicon_userContextId.js b/browser/components/originattributes/test/browser/browser_favicon_userContextId.js
new file mode 100644
index 0000000000..2f0a5d06a9
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_favicon_userContextId.js
@@ -0,0 +1,406 @@
+/**
+ * Bug 1277803 - A test caes for testing favicon loading across different userContextId.
+ */
+
+if (SpecialPowers.useRemoteSubframes) {
+ requestLongerTimeout(2);
+}
+
+let EventUtils = {};
+Services.scriptloader.loadSubScript(
+ "chrome://mochikit/content/tests/SimpleTest/EventUtils.js",
+ EventUtils
+);
+
+ChromeUtils.defineESModuleGetters(this, {
+ PlacesTestUtils: "resource://testing-common/PlacesTestUtils.sys.mjs",
+});
+
+const TEST_SITE = "https://example.org";
+const TEST_THIRD_PARTY_SITE = "https://example.net";
+
+const TEST_PAGE =
+ TEST_SITE +
+ "/browser/browser/components/originattributes/" +
+ "test/browser/file_favicon.html";
+const FAVICON_URI =
+ TEST_SITE +
+ "/browser/browser/components/originattributes/" +
+ "test/browser/file_favicon.png";
+const TEST_THIRD_PARTY_PAGE =
+ TEST_THIRD_PARTY_SITE +
+ "/browser/browser/components/originattributes/" +
+ "test/browser/file_favicon_thirdParty.html";
+const THIRD_PARTY_FAVICON_URI =
+ TEST_THIRD_PARTY_SITE +
+ "/browser/browser/components/" +
+ "originattributes/test/browser/file_favicon.png";
+
+const USER_CONTEXT_ID_PERSONAL = 1;
+const USER_CONTEXT_ID_WORK = 2;
+
+let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
+
+function clearAllImageCaches() {
+ var tools = SpecialPowers.Cc["@mozilla.org/image/tools;1"].getService(
+ SpecialPowers.Ci.imgITools
+ );
+ var imageCache = tools.getImgCacheForDocument(window.document);
+ imageCache.clearCache(true); // true=chrome
+ imageCache.clearCache(false); // false=content
+}
+
+function clearAllPlacesFavicons() {
+ let faviconService = Cc["@mozilla.org/browser/favicon-service;1"].getService(
+ Ci.nsIFaviconService
+ );
+
+ return new Promise(resolve => {
+ let observer = {
+ observe(aSubject, aTopic, aData) {
+ if (aTopic === "places-favicons-expired") {
+ resolve();
+ Services.obs.removeObserver(observer, "places-favicons-expired");
+ }
+ },
+ };
+
+ Services.obs.addObserver(observer, "places-favicons-expired");
+ faviconService.expireAllFavicons();
+ });
+}
+
+function FaviconObserver(
+ aUserContextId,
+ aExpectedCookie,
+ aPageURI,
+ aFaviconURL
+) {
+ this.reset(aUserContextId, aExpectedCookie, aPageURI, aFaviconURL);
+}
+
+FaviconObserver.prototype = {
+ observe(aSubject, aTopic, aData) {
+ // Make sure that the topic is 'http-on-modify-request'.
+ if (aTopic === "http-on-modify-request") {
+ // We check the userContextId for the originAttributes of the loading
+ // channel. All requests for the favicon should contain the correct
+ // userContextId. There are two requests for a favicon loading, one
+ // from the Places library and one from the XUL image. The difference
+ // of them is the loading principal. The Places will use the content
+ // principal and the XUL image will use the system principal.
+
+ let httpChannel = aSubject.QueryInterface(Ci.nsIHttpChannel);
+ let reqLoadInfo = httpChannel.loadInfo;
+ let loadingPrincipal, triggeringPrincipal;
+
+ // Make sure this is a favicon request.
+ if (httpChannel.URI.spec !== this._faviconURL) {
+ return;
+ }
+
+ if (reqLoadInfo) {
+ loadingPrincipal = reqLoadInfo.loadingPrincipal;
+ triggeringPrincipal = reqLoadInfo.triggeringPrincipal;
+ }
+
+ // Check the userContextId.
+ is(
+ reqLoadInfo.originAttributes.userContextId,
+ this._curUserContextId,
+ "The loadInfo has correct userContextId"
+ );
+
+ ok(
+ loadingPrincipal.equals(this._expectedPrincipal),
+ "The loadingPrincipal of favicon loading from content should be the content prinicpal"
+ );
+ ok(
+ triggeringPrincipal.equals(this._expectedPrincipal),
+ "The triggeringPrincipal of favicon loading from content should be the content prinicpal"
+ );
+
+ let faviconCookie = httpChannel.getRequestHeader("cookie");
+
+ is(
+ faviconCookie,
+ this._expectedCookie,
+ "The cookie of the favicon loading is correct."
+ );
+ } else {
+ ok(false, "Received unexpected topic: ", aTopic);
+ }
+
+ this._faviconLoaded.resolve();
+ },
+
+ reset(aUserContextId, aExpectedCookie, aPageURI, aFaviconURL) {
+ this._curUserContextId = aUserContextId;
+ this._expectedCookie = aExpectedCookie;
+ this._expectedPrincipal =
+ Services.scriptSecurityManager.createContentPrincipal(aPageURI, {
+ userContextId: aUserContextId,
+ });
+ this._faviconURL = aFaviconURL;
+ this._faviconLoaded = Promise.withResolvers();
+ },
+
+ get promise() {
+ return this._faviconLoaded.promise;
+ },
+};
+
+function waitOnFaviconLoaded(aFaviconURL) {
+ return PlacesTestUtils.waitForNotification("favicon-changed", events =>
+ events.some(e => e.faviconUrl == aFaviconURL)
+ );
+}
+
+async function generateCookies(aHost) {
+ // we generate two different cookies for two userContextIds.
+ let cookies = [];
+ cookies.push(Math.random().toString());
+ cookies.push(Math.random().toString());
+
+ // Then, we add cookies into the site for 'personal' and 'work'.
+ let tabInfoA = await openTabInUserContext(aHost, USER_CONTEXT_ID_PERSONAL);
+ let tabInfoB = await openTabInUserContext(aHost, USER_CONTEXT_ID_WORK);
+
+ await SpecialPowers.spawn(
+ tabInfoA.browser,
+ [cookies[0]],
+ async function (value) {
+ content.document.cookie = value;
+ }
+ );
+
+ await SpecialPowers.spawn(
+ tabInfoB.browser,
+ [cookies[1]],
+ async function (value) {
+ content.document.cookie = value;
+ }
+ );
+
+ BrowserTestUtils.removeTab(tabInfoA.tab);
+ BrowserTestUtils.removeTab(tabInfoB.tab);
+
+ return cookies;
+}
+
+async function doTest(aTestPage, aFaviconHost, aFaviconURL) {
+ let cookies = await generateCookies(aFaviconHost);
+ let pageURI = makeURI(aTestPage);
+
+ // Create the observer object for observing request channels of the personal
+ // container.
+ let observer = new FaviconObserver(
+ USER_CONTEXT_ID_PERSONAL,
+ cookies[0],
+ pageURI,
+ aFaviconURL
+ );
+
+ // Add the observer earlier in case we miss it.
+ let promiseWaitOnFaviconLoaded = waitOnFaviconLoaded(aFaviconURL);
+
+ Services.obs.addObserver(observer, "http-on-modify-request");
+
+ // Open the tab with the personal container.
+ let tabInfo = await openTabInUserContext(aTestPage, USER_CONTEXT_ID_PERSONAL);
+
+ // Waiting for favicon requests are all made.
+ await observer.promise;
+ // Waiting for favicon loaded.
+ await promiseWaitOnFaviconLoaded;
+
+ // Close the tab.
+ BrowserTestUtils.removeTab(tabInfo.tab);
+ // FIXME: We need to wait for the next event tick here to avoid observing
+ // the previous tab info in the next step (bug 1446725).
+ await new Promise(executeSoon);
+
+ // Reset the observer for observing requests for the work container.
+ observer.reset(USER_CONTEXT_ID_WORK, cookies[1], pageURI, aFaviconURL);
+ tabInfo = await openTabInUserContext(aTestPage, USER_CONTEXT_ID_WORK);
+
+ // Waiting for favicon requests are all made.
+ await observer.promise;
+
+ Services.obs.removeObserver(observer, "http-on-modify-request");
+
+ BrowserTestUtils.removeTab(tabInfo.tab);
+}
+
+function assertIconIsData(item) {
+ is(
+ item.getAttribute("image").substring(0, 5),
+ "data:",
+ "Expected the image element to be a data URI"
+ );
+}
+
+async function doTestForAllTabsFavicon(aTestPage, aFaviconHost, aFaviconURL) {
+ await generateCookies(aFaviconHost);
+
+ // Set the 'overflow' attribute to make allTabs button available.
+ let tabBrowser = document.getElementById("tabbrowser-tabs");
+ tabBrowser.setAttribute("overflow", true);
+
+ // Add the observer earlier in case we miss it.
+ let promiseWaitOnFaviconLoaded = waitOnFaviconLoaded(aFaviconURL);
+
+ // Open the tab with the personal container.
+ let tabInfo = await openTabInUserContext(aTestPage, USER_CONTEXT_ID_PERSONAL);
+
+ // Waiting for favicon loaded.
+ await promiseWaitOnFaviconLoaded;
+
+ // We need to clear the image cache here for making sure the network request will
+ // be made for the favicon of allTabs menuitem.
+ clearAllImageCaches();
+
+ gTabsPanel.init();
+
+ // Make the popup of allTabs showing up and trigger the loading of the favicon.
+ let allTabsView = document.getElementById("allTabsMenu-allTabsView");
+ let allTabsPopupShownPromise = BrowserTestUtils.waitForEvent(
+ allTabsView,
+ "ViewShown"
+ );
+ gTabsPanel.showAllTabsPanel();
+ await allTabsPopupShownPromise;
+
+ assertIconIsData(
+ gTabsPanel.allTabsViewTabs.lastElementChild.firstElementChild
+ );
+
+ // Close the popup of allTabs and wait until it's done.
+ let allTabsPopupHiddenPromise = BrowserTestUtils.waitForEvent(
+ allTabsView.panelMultiView,
+ "PanelMultiViewHidden"
+ );
+ gTabsPanel.hideAllTabsPanel();
+ await allTabsPopupHiddenPromise;
+
+ // Close the tab.
+ BrowserTestUtils.removeTab(tabInfo.tab);
+ // FIXME: We need to wait for the next event tick here to avoid observing
+ // the previous tab info in the next step (bug 1446725).
+ await new Promise(executeSoon);
+
+ // Open the tab under the work container and wait until the favicon is loaded.
+ promiseWaitOnFaviconLoaded = waitOnFaviconLoaded(aFaviconURL);
+ tabInfo = await openTabInUserContext(aTestPage, USER_CONTEXT_ID_WORK);
+ await promiseWaitOnFaviconLoaded;
+
+ // Clear the image cache again.
+ clearAllImageCaches();
+
+ // Make the popup of allTabs showing up again.
+ allTabsPopupShownPromise = BrowserTestUtils.waitForEvent(
+ allTabsView,
+ "ViewShown"
+ );
+ gTabsPanel.showAllTabsPanel();
+ await allTabsPopupShownPromise;
+
+ assertIconIsData(
+ gTabsPanel.allTabsViewTabs.lastElementChild.firstElementChild
+ );
+
+ // Close the popup of allTabs and wait until it's done.
+ allTabsPopupHiddenPromise = BrowserTestUtils.waitForEvent(
+ allTabsView.panelMultiView,
+ "PanelMultiViewHidden"
+ );
+ gTabsPanel.hideAllTabsPanel();
+ await allTabsPopupHiddenPromise;
+
+ // Close the tab.
+ BrowserTestUtils.removeTab(tabInfo.tab);
+
+ // Reset the 'overflow' attribute to make the allTabs button hidden again.
+ tabBrowser.removeAttribute("overflow");
+}
+
+add_setup(async function () {
+ // Make sure userContext is enabled.
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.userContext.enabled", true]],
+ });
+});
+
+// A clean up function to prevent affecting other tests.
+registerCleanupFunction(() => {
+ // Clear all cookies.
+ Services.cookies.removeAll();
+
+ // Clear all image caches and network caches.
+ clearAllImageCaches();
+
+ Services.cache2.clear();
+
+ // Clear Places favicon caches.
+ clearAllPlacesFavicons();
+});
+
+add_task(async function test_favicon_userContextId() {
+ // Clear all image caches before running the test.
+ clearAllImageCaches();
+
+ // Clear all network caches.
+ Services.cache2.clear();
+
+ // Clear Places favicon caches.
+ await clearAllPlacesFavicons();
+
+ await doTest(TEST_PAGE, TEST_SITE, FAVICON_URI);
+});
+
+add_task(async function test_thirdPartyFavicon_userContextId() {
+ // Clear all image caches before running the test.
+ clearAllImageCaches();
+
+ // Clear all network caches.
+ Services.cache2.clear();
+
+ // Clear Places favicon caches.
+ await clearAllPlacesFavicons();
+
+ await doTest(
+ TEST_THIRD_PARTY_PAGE,
+ TEST_THIRD_PARTY_SITE,
+ THIRD_PARTY_FAVICON_URI
+ );
+});
+
+add_task(async function test_allTabs_favicon_userContextId() {
+ // Clear all image caches before running the test.
+ clearAllImageCaches();
+
+ // Clear all network caches.
+ Services.cache2.clear();
+
+ // Clear Places favicon caches.
+ await clearAllPlacesFavicons();
+
+ await doTestForAllTabsFavicon(TEST_PAGE, TEST_SITE, FAVICON_URI);
+});
+
+add_task(async function test_allTabs_thirdPartyFavicon_userContextId() {
+ // Clear all image caches before running the test.
+ clearAllImageCaches();
+
+ // Clear all network caches.
+ Services.cache2.clear();
+
+ // Clear Places favicon caches.
+ await clearAllPlacesFavicons();
+
+ await doTestForAllTabsFavicon(
+ TEST_THIRD_PARTY_PAGE,
+ TEST_THIRD_PARTY_SITE,
+ THIRD_PARTY_FAVICON_URI
+ );
+});
diff --git a/browser/components/originattributes/test/browser/browser_firstPartyIsolation.js b/browser/components/originattributes/test/browser/browser_firstPartyIsolation.js
new file mode 100644
index 0000000000..feec6550e3
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_firstPartyIsolation.js
@@ -0,0 +1,511 @@
+const BASE_URL =
+ "https://example.net/browser/browser/components/originattributes/test/browser/";
+const EXAMPLE_BASE_URL = BASE_URL.replace("example.net", "example.com");
+const BASE_DOMAIN = "example.net";
+
+add_setup(async function () {
+ Services.prefs.setBoolPref("privacy.firstparty.isolate", true);
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("privacy.firstparty.isolate");
+ Services.cookies.removeAll();
+ Services.cache2.clear();
+ });
+});
+
+/**
+ * Test for the top-level document and child iframes should have the
+ * firstPartyDomain attribute.
+ */
+add_task(async function principal_test() {
+ let tab = BrowserTestUtils.addTab(
+ gBrowser,
+ BASE_URL + "test_firstParty.html"
+ );
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser, true, function (url) {
+ return url == BASE_URL + "test_firstParty.html";
+ });
+
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [{ firstPartyDomain: BASE_DOMAIN }],
+ async function (attrs) {
+ Assert.ok(
+ true,
+ "document principal: " + content.document.nodePrincipal.origin
+ );
+ Assert.equal(
+ content.docShell.getOriginAttributes().firstPartyDomain,
+ "",
+ "top-level docShell shouldn't have firstPartyDomain attribute."
+ );
+ Assert.equal(
+ content.document.nodePrincipal.originAttributes.firstPartyDomain,
+ attrs.firstPartyDomain,
+ "The document should have firstPartyDomain"
+ );
+
+ for (let i = 1; i < 4; i++) {
+ let iframe = content.document.getElementById("iframe" + i);
+ await SpecialPowers.spawn(
+ iframe,
+ [attrs.firstPartyDomain],
+ function (firstPartyDomain) {
+ Assert.ok(
+ true,
+ "iframe principal: " + content.document.nodePrincipal.origin
+ );
+
+ Assert.equal(
+ content.docShell.getOriginAttributes().firstPartyDomain,
+ firstPartyDomain,
+ "iframe's docshell should have firstPartyDomain"
+ );
+
+ Assert.equal(
+ content.document.nodePrincipal.originAttributes.firstPartyDomain,
+ firstPartyDomain,
+ "iframe should have firstPartyDomain"
+ );
+ }
+ );
+ }
+ }
+ );
+
+ gBrowser.removeTab(tab);
+});
+
+/**
+ * Test for the cookie jars of the top-level document and child iframe should be
+ * isolated by firstPartyDomain.
+ */
+add_task(async function cookie_test() {
+ let tab = BrowserTestUtils.addTab(
+ gBrowser,
+ BASE_URL + "test_firstParty_cookie.html"
+ );
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser, true);
+
+ let count = 0;
+ for (let cookie of Services.cookies.cookies) {
+ count++;
+ Assert.equal(cookie.value, "foo", "Cookie value should be foo");
+ Assert.equal(
+ cookie.originAttributes.firstPartyDomain,
+ BASE_DOMAIN,
+ "Cookie's origin attributes should be " + BASE_DOMAIN
+ );
+ }
+
+ // one cookie is from requesting test.js from top-level doc, and the other from
+ // requesting test2.js from iframe test2.html.
+ Assert.equal(count, 2, "Should have two cookies");
+
+ gBrowser.removeTab(tab);
+});
+
+/**
+ * Test for after redirect, the top-level document should update the firstPartyDomain
+ * attribute. However if the redirect is happening on the iframe, the attribute
+ * should remain the same.
+ */
+add_task(async function redirect_test() {
+ let tab = BrowserTestUtils.addTab(
+ gBrowser,
+ BASE_URL + "test_firstParty_http_redirect.html"
+ );
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [{ firstPartyDomain: "example.com" }],
+ async function (attrs) {
+ Assert.ok(
+ true,
+ "document principal: " + content.document.nodePrincipal.origin
+ );
+ Assert.ok(true, "document uri: " + content.document.documentURI);
+
+ Assert.equal(
+ content.document.documentURI,
+ "https://example.com/browser/browser/components/originattributes/test/browser/dummy.html",
+ "The page should have been redirected to https://example.com/browser/browser/components/originattributes/test/browser/dummy.html"
+ );
+ Assert.equal(
+ content.document.nodePrincipal.originAttributes.firstPartyDomain,
+ attrs.firstPartyDomain,
+ "The document should have firstPartyDomain"
+ );
+ }
+ );
+
+ // Since this is a HTML redirect, we wait until the final page is loaded.
+ let tab2 = BrowserTestUtils.addTab(
+ gBrowser,
+ BASE_URL + "test_firstParty_html_redirect.html"
+ );
+ await BrowserTestUtils.browserLoaded(
+ tab2.linkedBrowser,
+ false,
+ function (url) {
+ return url == "https://example.com/";
+ }
+ );
+
+ await SpecialPowers.spawn(
+ tab2.linkedBrowser,
+ [{ firstPartyDomain: "example.com" }],
+ async function (attrs) {
+ Assert.ok(
+ true,
+ "2nd tab document principal: " + content.document.nodePrincipal.origin
+ );
+ Assert.ok(true, "2nd tab document uri: " + content.document.documentURI);
+ Assert.equal(
+ content.document.documentURI,
+ "https://example.com/",
+ "The page should have been redirected to https://example.com"
+ );
+ Assert.equal(
+ content.document.nodePrincipal.originAttributes.firstPartyDomain,
+ attrs.firstPartyDomain,
+ "The document should have firstPartyDomain"
+ );
+ }
+ );
+
+ let tab3 = BrowserTestUtils.addTab(
+ gBrowser,
+ BASE_URL + "test_firstParty_iframe_http_redirect.html"
+ );
+ await BrowserTestUtils.browserLoaded(
+ tab3.linkedBrowser,
+ true,
+ function (url) {
+ return url == BASE_URL + "test_firstParty_iframe_http_redirect.html";
+ }
+ );
+
+ // This redirect happens on the iframe, so unlike the two redirect tests above,
+ // the firstPartyDomain should still stick to the current top-level document,
+ // which is example.net.
+ await SpecialPowers.spawn(
+ tab3.linkedBrowser,
+ [{ firstPartyDomain: BASE_DOMAIN }],
+ async function (attrs) {
+ let iframe = content.document.getElementById("iframe1");
+ SpecialPowers.spawn(
+ iframe,
+ [attrs.firstPartyDomain],
+ function (firstPartyDomain) {
+ Assert.ok(
+ true,
+ "iframe document principal: " +
+ content.document.nodePrincipal.origin
+ );
+ Assert.ok(
+ true,
+ "iframe document uri: " + content.document.documentURI
+ );
+
+ Assert.equal(
+ content.document.documentURI,
+ "https://example.com/browser/browser/components/originattributes/test/browser/dummy.html",
+ "The page should have been redirected to https://example.com/browser/browser/components/originattributes/test/browser/dummy.html"
+ );
+
+ Assert.equal(
+ content.document.nodePrincipal.originAttributes.firstPartyDomain,
+ firstPartyDomain,
+ "The iframe should have firstPartyDomain: " + firstPartyDomain
+ );
+ }
+ );
+ }
+ );
+
+ gBrowser.removeTab(tab);
+ gBrowser.removeTab(tab2);
+ gBrowser.removeTab(tab3);
+});
+
+/**
+ * Test for postMessage between document and iframe.
+ */
+add_task(async function postMessage_test() {
+ let tab = BrowserTestUtils.addTab(
+ gBrowser,
+ BASE_URL + "test_firstParty_postMessage.html"
+ );
+
+ // The top-level page will post a message to its child iframe, and wait for
+ // another message from the iframe, once it receives the message, it will
+ // create another iframe, dummy.html.
+ // So we wait until dummy.html is loaded
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser, true, function (url) {
+ return url == BASE_URL + "dummy.html";
+ });
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ Assert.ok(
+ true,
+ "document principal: " + content.document.nodePrincipal.origin
+ );
+ let value = content.document.getElementById("message").textContent;
+ Assert.equal(value, "OK");
+ });
+
+ gBrowser.removeTab(tab);
+});
+
+/**
+ * When the web page calls window.open, the new window should have the same
+ * firstPartyDomain attribute.
+ */
+add_task(async function openWindow_test() {
+ Services.prefs.setIntPref("browser.link.open_newwindow", 2);
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("browser.link.open_newwindow");
+ });
+
+ let tab = BrowserTestUtils.addTab(gBrowser, BASE_URL + "window.html");
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [{ firstPartyDomain: BASE_DOMAIN }],
+ async function (attrs) {
+ let promise = new Promise(resolve => {
+ content.addEventListener("message", resolve, { once: true });
+ });
+ let w = Cu.unwaiveXrays(content.wrappedJSObject.open());
+ w.document.body.innerHTML = `<iframe id='iframe1' onload="window.opener.postMessage('ready', '*');" src='data:text/plain,test2'></iframe>`;
+
+ await promise;
+
+ Assert.equal(
+ w.docShell.getOriginAttributes().firstPartyDomain,
+ attrs.firstPartyDomain,
+ "window.open() should have correct firstPartyDomain attribute"
+ );
+ Assert.equal(
+ w.document.nodePrincipal.originAttributes.firstPartyDomain,
+ attrs.firstPartyDomain,
+ "The document should have correct firstPartyDomain"
+ );
+
+ let iframe = w.document.getElementById("iframe1");
+ await SpecialPowers.spawn(
+ iframe,
+ [attrs.firstPartyDomain],
+ function (firstPartyDomain) {
+ Assert.equal(
+ content.docShell.getOriginAttributes().firstPartyDomain,
+ firstPartyDomain,
+ "iframe's docshell should have correct rirstPartyDomain"
+ );
+
+ Assert.equal(
+ content.document.nodePrincipal.originAttributes.firstPartyDomain,
+ firstPartyDomain,
+ "iframe should have correct firstPartyDomain"
+ );
+ }
+ );
+
+ w.close();
+ }
+ );
+
+ gBrowser.removeTab(tab);
+});
+
+/**
+ * When the web page calls window.open, the top-level docshell in the new
+ * created window will have firstPartyDomain set.
+ */
+add_task(async function window_open_redirect_test() {
+ Services.prefs.setIntPref("browser.link.open_newwindow", 2);
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("browser.link.open_newwindow");
+ });
+
+ let tab = BrowserTestUtils.addTab(
+ gBrowser,
+ BASE_URL + "window_redirect.html"
+ );
+ let win = await BrowserTestUtils.waitForNewWindow({
+ url: BASE_URL + "dummy.html",
+ });
+
+ await SpecialPowers.spawn(
+ win.gBrowser.selectedBrowser,
+ [{ firstPartyDomain: BASE_DOMAIN }],
+ async function (attrs) {
+ Assert.equal(
+ content.docShell.getOriginAttributes().firstPartyDomain,
+ attrs.firstPartyDomain,
+ "window.open() should have firstPartyDomain attribute"
+ );
+ Assert.equal(
+ content.document.nodePrincipal.originAttributes.firstPartyDomain,
+ attrs.firstPartyDomain,
+ "The document should have firstPartyDomain"
+ );
+ }
+ );
+
+ gBrowser.removeTab(tab);
+ await BrowserTestUtils.closeWindow(win);
+});
+
+/**
+ * When the web page calls window.open, the top-level docshell in the new
+ * created window will inherit the firstPartyDomain attribute.
+ * However the top-level document will override the firstPartyDomain if the
+ * document is from another domain.
+ */
+add_task(async function window_open_iframe_test() {
+ Services.prefs.setIntPref("browser.link.open_newwindow", 2);
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("browser.link.open_newwindow");
+ });
+
+ let tab = BrowserTestUtils.addTab(gBrowser, BASE_URL + "window2.html");
+ let url = EXAMPLE_BASE_URL + "test_firstParty.html";
+ info("Waiting for window with url " + url);
+ let win = await BrowserTestUtils.waitForNewWindow({ url });
+
+ await SpecialPowers.spawn(
+ win.gBrowser.selectedBrowser,
+ [{ firstPartyDomain: BASE_DOMAIN }],
+ async function (attrs) {
+ Assert.equal(
+ content.docShell.getOriginAttributes().firstPartyDomain,
+ attrs.firstPartyDomain,
+ "window.open() should have firstPartyDomain attribute"
+ );
+
+ // The document is https://example.com/browser/browser/components/originattributes/test/browser/test_firstParty.html
+ // so the firstPartyDomain will be overriden to 'example.com'.
+ Assert.equal(
+ content.document.nodePrincipal.originAttributes.firstPartyDomain,
+ "example.com",
+ "The document should have firstPartyDomain"
+ );
+
+ let iframe = content.document.getElementById("iframe1");
+ SpecialPowers.spawn(iframe, [], function () {
+ Assert.equal(
+ content.docShell.getOriginAttributes().firstPartyDomain,
+ "example.com",
+ "iframe's docshell should have firstPartyDomain"
+ );
+ Assert.equal(
+ content.document.nodePrincipal.originAttributes.firstPartyDomain,
+ "example.com",
+ "iframe should have firstPartyDomain"
+ );
+ });
+ }
+ );
+
+ gBrowser.removeTab(tab);
+ await BrowserTestUtils.closeWindow(win);
+});
+
+/**
+ * Test for the loadInfo->TriggeringPrincipal is the document itself.
+ */
+add_task(async function form_test() {
+ let tab = BrowserTestUtils.addTab(gBrowser, BASE_URL + "test_form.html");
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [{ firstPartyDomain: BASE_DOMAIN }],
+ async function (attrs) {
+ Assert.equal(
+ content.document.nodePrincipal.originAttributes.firstPartyDomain,
+ attrs.firstPartyDomain,
+ "The document should have firstPartyDomain"
+ );
+
+ let submit = content.document.getElementById("submit");
+ submit.click();
+ }
+ );
+
+ gBrowser.removeTab(tab);
+});
+
+/**
+ * Another test for loadInfo->TriggeringPrincipal in the window.open case.
+ */
+add_task(async function window_open_form_test() {
+ Services.prefs.setIntPref("browser.link.open_newwindow", 2);
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("browser.link.open_newwindow");
+ });
+
+ let tab = BrowserTestUtils.addTab(gBrowser, BASE_URL + "window3.html");
+ let url = EXAMPLE_BASE_URL + "test_form.html";
+ info("Waiting for window with url " + url);
+ let win = await BrowserTestUtils.waitForNewWindow({ url });
+
+ await SpecialPowers.spawn(
+ win.gBrowser.selectedBrowser,
+ [{ firstPartyDomain: BASE_DOMAIN }],
+ async function (attrs) {
+ Assert.equal(
+ content.docShell.getOriginAttributes().firstPartyDomain,
+ attrs.firstPartyDomain,
+ "window.open() should have firstPartyDomain attribute"
+ );
+ Assert.equal(
+ content.document.nodePrincipal.originAttributes.firstPartyDomain,
+ "example.com",
+ "The document should have firstPartyDomain"
+ );
+
+ let submit = content.document.getElementById("submit");
+ submit.click();
+ }
+ );
+
+ gBrowser.removeTab(tab);
+ await BrowserTestUtils.closeWindow(win);
+});
+
+/**
+ * A test for using an IP address as the first party domain.
+ */
+add_task(async function ip_address_test() {
+ const ipAddr = "127.0.0.1";
+ const ipHost = `http://${ipAddr}/browser/browser/components/originattributes/test/browser/`;
+
+ Services.prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true);
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("network.proxy.allow_hijacking_localhost");
+ });
+
+ let tab = BrowserTestUtils.addTab(gBrowser, ipHost + "test_firstParty.html");
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser, true);
+
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [{ firstPartyDomain: ipAddr }],
+ async function (attrs) {
+ Assert.ok(
+ true,
+ "document principal: " + content.document.nodePrincipal.origin
+ );
+ Assert.equal(
+ content.document.nodePrincipal.originAttributes.firstPartyDomain,
+ attrs.firstPartyDomain,
+ "The firstPartyDomain should be set properly for the IP address"
+ );
+ }
+ );
+
+ gBrowser.removeTab(tab);
+});
diff --git a/browser/components/originattributes/test/browser/browser_firstPartyIsolation_aboutPages.js b/browser/components/originattributes/test/browser/browser_firstPartyIsolation_aboutPages.js
new file mode 100644
index 0000000000..6e83a7fc33
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_firstPartyIsolation_aboutPages.js
@@ -0,0 +1,258 @@
+if (SpecialPowers.useRemoteSubframes) {
+ requestLongerTimeout(2);
+}
+
+add_setup(async function () {
+ Services.prefs.setBoolPref("privacy.firstparty.isolate", true);
+
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("privacy.firstparty.isolate");
+ });
+});
+
+/**
+ * For loading the initial about:blank in e10s mode, it will be loaded with
+ * NullPrincipal, and we also test if the firstPartyDomain of the origin
+ * attributes is got from the origin itself.
+ */
+add_task(async function test_remote_window_open_aboutBlank() {
+ let win = await BrowserTestUtils.openNewBrowserWindow({ remote: true });
+ let browser = win.gBrowser.selectedBrowser;
+
+ Assert.ok(browser.isRemoteBrowser, "should be a remote browser");
+
+ await SpecialPowers.spawn(browser, [], async function () {
+ Assert.ok(true, "origin " + content.document.nodePrincipal.origin);
+
+ Assert.ok(
+ content.document.nodePrincipal.isNullPrincipal,
+ "The principal of remote about:blank should be a NullPrincipal."
+ );
+
+ let str = content.document.nodePrincipal.originNoSuffix;
+ let expectDomain =
+ str.substring("moz-nullprincipal:{".length, str.length - 1) + ".mozilla";
+ Assert.equal(
+ content.document.nodePrincipal.originAttributes.firstPartyDomain,
+ expectDomain,
+ "remote about:blank should have firstPartyDomain set to " + expectDomain
+ );
+ });
+
+ win.close();
+});
+
+/**
+ * For loading the initial about:blank in non-e10s mode, it will be loaded with
+ * a null principal. So we test if it has correct firstPartyDomain set.
+ */
+add_task(async function test_nonremote_window_open_aboutBlank() {
+ if (SpecialPowers.useRemoteSubframes) {
+ ok(true, "We cannot test non-e10s mode in Fission. So, we skip this.");
+ return;
+ }
+
+ let win = await BrowserTestUtils.openNewBrowserWindow({ remote: false });
+ let browser = win.gBrowser.selectedBrowser;
+
+ Assert.ok(!browser.isRemoteBrowser, "shouldn't be a remote browser");
+
+ await SpecialPowers.spawn(browser, [], async function () {
+ info("origin " + content.document.nodePrincipal.origin);
+
+ Assert.ok(
+ content.document.nodePrincipal.isNullPrincipal,
+ "The principal of remote about:blank should be a NullPrincipal."
+ );
+
+ let str = content.document.nodePrincipal.originNoSuffix;
+ let expectDomain =
+ str.substring("moz-nullprincipal:{".length, str.length - 1) + ".mozilla";
+ Assert.equal(
+ content.document.nodePrincipal.originAttributes.firstPartyDomain,
+ expectDomain,
+ "non-remote about:blank should have firstPartyDomain set to " +
+ expectDomain
+ );
+ });
+
+ win.close();
+});
+
+/**
+ * Check if data: URI inherits firstPartyDomain from about:blank correctly.
+ */
+add_task(async function test_remote_window_open_data_uri() {
+ // allow top level data: URI navigations, otherwise
+ // <a href="data:" would fail.
+ await SpecialPowers.pushPrefEnv({
+ set: [["security.data_uri.block_toplevel_data_uri_navigations", false]],
+ });
+ let win = await BrowserTestUtils.openNewBrowserWindow({ remote: true });
+ let browser = win.gBrowser.selectedBrowser;
+
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.body.innerHTML = `
+ <a href="data:text/plain,hello" id="test">hello</a>`;
+
+ let element = content.document.getElementById("test");
+ element.click();
+ });
+
+ await BrowserTestUtils.browserLoaded(browser, false, function (url) {
+ return url == "data:text/plain,hello";
+ });
+
+ await SpecialPowers.spawn(browser, [], async function () {
+ Assert.ok(true, "origin: " + content.document.nodePrincipal.origin);
+
+ Assert.ok(
+ content.document.nodePrincipal.isNullPrincipal,
+ "The principal of data: document should be a NullPrincipal."
+ );
+
+ Assert.ok(
+ content.document.nodePrincipal.originAttributes.firstPartyDomain != "",
+ "data: URI should have firstPartyDomain set."
+ );
+ });
+
+ win.close();
+});
+
+/**
+ * data: document contains an iframe, and we test that iframe should inherit
+ * origin attributes from the data: document.
+ */
+add_task(async function test_remote_window_open_data_uri2() {
+ let win = await BrowserTestUtils.openNewBrowserWindow({ remote: true });
+ let browser = win.gBrowser.selectedBrowser;
+ const TEST_PAGE =
+ "https://example.net/browser/browser/components/originattributes/test/browser/test2.html";
+
+ // The iframe test2.html will fetch test2.js, which will have cookies.
+ const DATA_URI = `data:text/html,
+ <iframe id="iframe1" src="${TEST_PAGE}"></iframe>`;
+ BrowserTestUtils.startLoadingURIString(browser, DATA_URI);
+ await BrowserTestUtils.browserLoaded(browser, true, TEST_PAGE);
+
+ await SpecialPowers.spawn(browser, [], async function () {
+ Assert.ok(true, "origin " + content.document.nodePrincipal.origin);
+
+ Assert.ok(
+ content.document.nodePrincipal.isNullPrincipal,
+ "The principal of data: document should be a NullPrincipal."
+ );
+
+ Assert.ok(
+ content.document.nodePrincipal.originAttributes.firstPartyDomain != "",
+ "data: URI should have firstPartyDomain set."
+ );
+
+ let iframe = content.document.getElementById("iframe1");
+ await SpecialPowers.spawn(
+ iframe,
+ [content.document.nodePrincipal.originAttributes.firstPartyDomain],
+ function (firstPartyDomain) {
+ Assert.ok(
+ true,
+ "iframe principal: " + content.document.nodePrincipal.origin
+ );
+
+ Assert.equal(
+ content.document.nodePrincipal.originAttributes.firstPartyDomain,
+ firstPartyDomain,
+ "iframe should inherit firstPartyDomain from parent document."
+ );
+ Assert.equal(
+ content.document.cookie,
+ "test2=foo",
+ "iframe should have cookies"
+ );
+ }
+ );
+ });
+
+ win.close();
+});
+
+/**
+ * about: pages should have firstPartyDomain set when we enable first party isolation.
+ */
+add_task(async function test_aboutURL() {
+ let aboutURLs = [];
+
+ // List of about: URLs that will initiate network requests.
+ let networkURLs = ["credits", "logins"];
+
+ for (let cid in Cc) {
+ let result = cid.match(
+ /@mozilla.org\/network\/protocol\/about;1\?what\=(.*)$/
+ );
+ if (!result) {
+ continue;
+ }
+
+ let aboutType = result[1];
+ let contract = "@mozilla.org/network/protocol/about;1?what=" + aboutType;
+ try {
+ let am = Cc[contract].getService(Ci.nsIAboutModule);
+ let uri = Services.io.newURI("about:" + aboutType);
+ let flags = am.getURIFlags(uri);
+
+ // We load pages with URI_SAFE_FOR_UNTRUSTED_CONTENT set, this means they
+ // are not loaded with System Principal but with content principal.
+ // Also we skip pages with HIDE_FROM_ABOUTABOUT, some of them may have
+ // errors while loading.
+ if (
+ flags & Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT &&
+ !(flags & Ci.nsIAboutModule.HIDE_FROM_ABOUTABOUT) &&
+ !networkURLs.includes(aboutType) &&
+ // handle about:newtab in browser_firstPartyIsolation_about_newtab.js
+ aboutType !== "newtab" &&
+ // protections kicks of async messaging as soon as it loads,
+ // this test closes the tab too soon causing errors
+ aboutType !== "protections"
+ ) {
+ aboutURLs.push(aboutType);
+ }
+ } catch (e) {
+ // getService might have thrown if the component doesn't actually
+ // implement nsIAboutModule
+ }
+ }
+
+ for (let url of aboutURLs) {
+ let tab = BrowserTestUtils.addTab(gBrowser, "about:" + url);
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ let attrs = {
+ firstPartyDomain: "about.ef2a7dd5-93bc-417f-a698-142c3116864f.mozilla",
+ };
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [{ attrs, url }],
+ async function (args) {
+ Assert.ok(
+ true,
+ "loading page about:" +
+ args.url +
+ ", origin is " +
+ content.document.nodePrincipal.origin
+ );
+ Assert.ok(true, "principal " + content.document.nodePrincipal);
+ Assert.equal(
+ content.document.nodePrincipal.originAttributes.firstPartyDomain,
+ args.attrs.firstPartyDomain,
+ "The about page should have firstPartyDomain set"
+ );
+ Assert.ok(
+ content.document.nodePrincipal.isContentPrincipal,
+ "The principal should be a content principal."
+ );
+ }
+ );
+
+ gBrowser.removeTab(tab);
+ }
+});
diff --git a/browser/components/originattributes/test/browser/browser_firstPartyIsolation_about_newtab.js b/browser/components/originattributes/test/browser/browser_firstPartyIsolation_about_newtab.js
new file mode 100644
index 0000000000..b3e175c6a1
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_firstPartyIsolation_about_newtab.js
@@ -0,0 +1,55 @@
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.newtab.preload", false],
+ ["privacy.firstparty.isolate", true],
+ ],
+ });
+});
+
+/**
+ * Test about:newtab will have firstPartytDomain set when we enable the pref.
+ *
+ * We split about:newtab from browser_firstPartyIsolation_aboutPages.js because
+ * about:newtab needs special care.
+ *
+ * In the original test browser_firstPartyIsolation_aboutPages.js, when calling
+ * tabbrowser.addTab, if it found out the uri is about:newtab, it will use
+ * the preloaded browser, however the preloaded browser is loaded before when we
+ * turn on the firstPartyIsolation pref, which won't have the pref set.
+ *
+ * To prevent to use the preloaded browser, a simple trick is open a window
+ * first.
+ **/
+add_task(async function test_aboutNewTab() {
+ // In Fission, we cannot open a non-remote window.
+ let win = await BrowserTestUtils.openNewBrowserWindow({
+ remote: SpecialPowers.useRemoteSubframes,
+ });
+ let gbrowser = win.gBrowser;
+ let tab = BrowserTestUtils.addTab(gbrowser, "about:newtab");
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ let attrs = {
+ firstPartyDomain: "about.ef2a7dd5-93bc-417f-a698-142c3116864f.mozilla",
+ };
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [{ attrs }],
+ async function (args) {
+ Assert.ok(true, "principal " + content.document.nodePrincipal.origin);
+ Assert.equal(
+ content.document.nodePrincipal.originAttributes.firstPartyDomain,
+ args.attrs.firstPartyDomain,
+ "about:newtab should have firstPartyDomain set"
+ );
+ Assert.ok(
+ content.document.nodePrincipal.isContentPrincipal,
+ "The principal should be a content principal."
+ );
+ }
+ );
+
+ gbrowser.removeTab(tab);
+ win.close();
+});
diff --git a/browser/components/originattributes/test/browser/browser_firstPartyIsolation_blobURI.js b/browser/components/originattributes/test/browser/browser_firstPartyIsolation_blobURI.js
new file mode 100644
index 0000000000..120e004e9a
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_firstPartyIsolation_blobURI.js
@@ -0,0 +1,116 @@
+add_setup(async function () {
+ Services.prefs.setBoolPref("privacy.firstparty.isolate", true);
+
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("privacy.firstparty.isolate");
+ Services.cookies.removeAll();
+ });
+});
+
+/**
+ * First we generate a Blob URI by using URL.createObjectURL(new Blob(..));
+ * then we navigate to this Blob URI, hence to make the top-level document URI
+ * is Blob URI.
+ * Later we create an iframe on this Blob: document, and we test that the iframe
+ * has correct firstPartyDomain.
+ */
+add_task(async function test_blob_uri_inherit_oa_from_content() {
+ const BASE_URI =
+ "http://mochi.test:8888/browser/browser/components/" +
+ "originattributes/test/browser/dummy.html";
+ const BASE_DOMAIN = "mochi.test";
+
+ // First we load a normal web page.
+ let win = await BrowserTestUtils.openNewBrowserWindow({ remote: true });
+ let browser = win.gBrowser.selectedBrowser;
+ BrowserTestUtils.startLoadingURIString(browser, BASE_URI);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ // Then navigate to the blob: URI.
+ await SpecialPowers.spawn(
+ browser,
+ [{ firstPartyDomain: BASE_DOMAIN }],
+ async function (attrs) {
+ Assert.ok(true, "origin " + content.document.nodePrincipal.origin);
+ Assert.equal(
+ content.document.nodePrincipal.originAttributes.firstPartyDomain,
+ attrs.firstPartyDomain,
+ "The document should have firstPartyDomain"
+ );
+
+ // Now we use createObjectURL to generate a blob URI and navigate to it.
+ let url = content.window.URL.createObjectURL(
+ new content.window.Blob(
+ [
+ `<script src="http://mochi.test:8888/browser/browser/components/originattributes/test/browser/test.js"></script>`,
+ ],
+ { type: "text/html" }
+ )
+ );
+ content.document.location = url;
+ }
+ );
+
+ // Wait for the Blob: URI to be loaded.
+ await BrowserTestUtils.browserLoaded(browser, false, function (url) {
+ info("BrowserTestUtils.browserLoaded url=" + url);
+ return url.startsWith("blob:http://mochi.test:8888/");
+ });
+
+ // We verify the blob document has correct origin attributes.
+ // Then we inject an iframe to it.
+ await SpecialPowers.spawn(
+ browser,
+ [{ firstPartyDomain: BASE_DOMAIN }],
+ async function (attrs) {
+ Assert.ok(
+ content.document.documentURI.startsWith("blob:http://mochi.test:8888/"),
+ "the document URI should be a blob URI."
+ );
+ Assert.ok(true, "origin " + content.document.nodePrincipal.origin);
+ Assert.equal(
+ content.document.nodePrincipal.originAttributes.firstPartyDomain,
+ attrs.firstPartyDomain,
+ "The document should have firstPartyDomain"
+ );
+
+ let iframe = content.document.createElement("iframe");
+ iframe.src = "http://example.com";
+ iframe.id = "iframe1";
+ content.document.body.appendChild(iframe);
+
+ // Wait for the iframe to be loaded.
+ await new content.Promise(done => {
+ iframe.addEventListener(
+ "load",
+ function () {
+ done();
+ },
+ { capture: true, once: true }
+ );
+ });
+ }
+ );
+
+ // Finally we verify the iframe has correct origin attributes.
+ await SpecialPowers.spawn(
+ browser,
+ [{ firstPartyDomain: BASE_DOMAIN }],
+ async function (attrs) {
+ let iframe = content.document.getElementById("iframe1");
+ await SpecialPowers.spawn(
+ iframe,
+ [attrs.firstPartyDomain],
+ function (firstPartyDomain) {
+ Assert.equal(
+ content.document.nodePrincipal.originAttributes.firstPartyDomain,
+ firstPartyDomain,
+ "iframe should inherit firstPartyDomain from blob: URI"
+ );
+ }
+ );
+ }
+ );
+
+ win.close();
+});
diff --git a/browser/components/originattributes/test/browser/browser_firstPartyIsolation_js_uri.js b/browser/components/originattributes/test/browser/browser_firstPartyIsolation_js_uri.js
new file mode 100644
index 0000000000..8aa199d47f
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_firstPartyIsolation_js_uri.js
@@ -0,0 +1,90 @@
+add_setup(async function () {
+ Services.prefs.setBoolPref("privacy.firstparty.isolate", true);
+
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("privacy.firstparty.isolate");
+ });
+});
+
+add_task(async function test_remote_window_open_js_uri() {
+ let win = await BrowserTestUtils.openNewBrowserWindow({ remote: true });
+ let browser = win.gBrowser.selectedBrowser;
+
+ Assert.ok(browser.isRemoteBrowser, "should be a remote browser");
+
+ BrowserTestUtils.startLoadingURIString(browser, `javascript:1;`);
+
+ await BrowserTestUtils.browserLoaded(browser);
+
+ await SpecialPowers.spawn(browser, [], async function () {
+ Assert.ok(true, "origin " + content.document.nodePrincipal.origin);
+
+ Assert.ok(
+ content.document.nodePrincipal.isNullPrincipal,
+ "The principal of remote javascript: should be a NullPrincipal."
+ );
+
+ let str = content.document.nodePrincipal.originNoSuffix;
+ let expectDomain =
+ str.substring("moz-nullprincipal:{".length, str.length - 1) + ".mozilla";
+ Assert.equal(
+ content.document.nodePrincipal.originAttributes.firstPartyDomain,
+ expectDomain,
+ "remote javascript: should have firstPartyDomain set to " + expectDomain
+ );
+ });
+
+ win.close();
+});
+
+add_task(async function test_remote_window_open_js_uri2() {
+ let win = await BrowserTestUtils.openNewBrowserWindow({ remote: true });
+ let browser = win.gBrowser.selectedBrowser;
+
+ Assert.ok(browser.isRemoteBrowser, "should be a remote browser");
+
+ BrowserTestUtils.startLoadingURIString(
+ browser,
+ `javascript:
+ let iframe = document.createElement("iframe");
+ iframe.src = "http://example.com";
+ iframe.id = "iframe1";
+ document.body.appendChild(iframe);
+ void(0);
+ `
+ );
+
+ await BrowserTestUtils.browserLoaded(browser, true, function (url) {
+ info("URL:" + url);
+ return url == "http://example.com/";
+ });
+
+ await SpecialPowers.spawn(browser, [], async function () {
+ Assert.ok(true, "origin " + content.document.nodePrincipal.origin);
+
+ Assert.ok(
+ content.document.nodePrincipal.isNullPrincipal,
+ "The principal of remote javascript: should be a NullPrincipal."
+ );
+
+ let str = content.document.nodePrincipal.originNoSuffix;
+ let expectDomain =
+ str.substring("moz-nullprincipal:{".length, str.length - 1) + ".mozilla";
+ Assert.equal(
+ content.document.nodePrincipal.originAttributes.firstPartyDomain,
+ expectDomain,
+ "remote javascript: should have firstPartyDomain set to " + expectDomain
+ );
+
+ let iframe = content.document.getElementById("iframe1");
+ await SpecialPowers.spawn(iframe, [expectDomain], function (domain) {
+ Assert.equal(
+ content.document.nodePrincipal.originAttributes.firstPartyDomain,
+ domain,
+ "iframe should have firstPartyDomain set to " + domain
+ );
+ });
+ });
+
+ win.close();
+});
diff --git a/browser/components/originattributes/test/browser/browser_firstPartyIsolation_saveAs.js b/browser/components/originattributes/test/browser/browser_firstPartyIsolation_saveAs.js
new file mode 100644
index 0000000000..b51ac12849
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_firstPartyIsolation_saveAs.js
@@ -0,0 +1,326 @@
+/**
+ * Bug 1508355 - A test case for ensuring the saving channel has a correct first
+ * party domain when going through different "Save ... AS."
+ */
+
+"use strict";
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js",
+ this
+);
+
+const TEST_FIRST_PARTY = "example.com";
+const TEST_ORIGIN = `http://${TEST_FIRST_PARTY}`;
+const TEST_BASE_PATH =
+ "/browser/browser/components/originattributes/test/browser/";
+const TEST_PATH = `${TEST_BASE_PATH}file_saveAs.sjs`;
+const TEST_PATH_VIDEO = `${TEST_BASE_PATH}file_thirdPartyChild.video.ogv`;
+const TEST_PATH_IMAGE = `${TEST_BASE_PATH}file_favicon.png`;
+
+// For the "Save Page As" test, we will check the channel of the sub-resource
+// within the page. In this case, it is a image.
+const TEST_PATH_PAGE = `${TEST_BASE_PATH}file_favicon.png`;
+
+// For the "Save Frame As" test, we will check the channel of the sub-resource
+// within the frame. In this case, it is a image.
+const TEST_PATH_FRAME = `${TEST_BASE_PATH}file_favicon.png`;
+
+let MockFilePicker = SpecialPowers.MockFilePicker;
+MockFilePicker.init(window);
+const tempDir = createTemporarySaveDirectory();
+MockFilePicker.displayDirectory = tempDir;
+
+add_setup(async function () {
+ info("Setting the prefs.");
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.firstparty.isolate", true],
+ ["dom.security.https_first", false],
+ ],
+ });
+
+ info("Setting MockFilePicker.");
+ mockTransferRegisterer.register();
+
+ registerCleanupFunction(function () {
+ mockTransferRegisterer.unregister();
+ MockFilePicker.cleanup();
+ tempDir.remove(true);
+ });
+});
+
+function createTemporarySaveDirectory() {
+ let saveDir = Services.dirsvc.get("TmpD", Ci.nsIFile);
+ saveDir.append("testsavedir");
+ saveDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+ return saveDir;
+}
+
+function createPromiseForObservingChannel(aURL, aFirstParty) {
+ return new Promise(resolve => {
+ let observer = (aSubject, aTopic) => {
+ if (aTopic === "http-on-modify-request") {
+ let httpChannel = aSubject.QueryInterface(Ci.nsIHttpChannel);
+ let reqLoadInfo = httpChannel.loadInfo;
+
+ // Make sure this is the request which we want to check.
+ if (!httpChannel.URI.spec.endsWith(aURL)) {
+ return;
+ }
+
+ info(`Checking loadInfo for URI: ${httpChannel.URI.spec}\n`);
+ is(
+ reqLoadInfo.originAttributes.firstPartyDomain,
+ aFirstParty,
+ "The loadInfo has correct first party domain"
+ );
+
+ Services.obs.removeObserver(observer, "http-on-modify-request");
+ resolve();
+ }
+ };
+
+ Services.obs.addObserver(observer, "http-on-modify-request");
+ });
+}
+
+function createPromiseForTransferComplete() {
+ return new Promise(resolve => {
+ MockFilePicker.showCallback = fp => {
+ info("MockFilePicker showCallback");
+
+ let fileName = fp.defaultString;
+ let destFile = tempDir.clone();
+ destFile.append(fileName);
+
+ MockFilePicker.setFiles([destFile]);
+ MockFilePicker.filterIndex = 0; // kSaveAsType_Complete
+
+ MockFilePicker.showCallback = null;
+ mockTransferCallback = function (downloadSuccess) {
+ ok(downloadSuccess, "File should have been downloaded successfully");
+ mockTransferCallback = () => {};
+ resolve();
+ };
+ };
+ });
+}
+
+async function doCommandForFrameType() {
+ info("Opening the frame sub-menu under the context menu.");
+ let contextMenu = document.getElementById("contentAreaContextMenu");
+ let frameMenu = contextMenu.querySelector("#frame");
+ let frameMenuPopup = frameMenu.menupopup;
+ let frameMenuPopupPromise = BrowserTestUtils.waitForEvent(
+ frameMenuPopup,
+ "popupshown"
+ );
+
+ frameMenu.openMenu(true);
+ await frameMenuPopupPromise;
+
+ info("Triggering the save process.");
+ let saveFrameCommand = contextMenu.querySelector("#context-saveframe");
+ frameMenuPopup.activateItem(saveFrameCommand);
+}
+
+add_task(async function test_setup() {
+ // Make sure SearchService is ready for it to be called.
+ await Services.search.init();
+});
+
+add_task(async function testContextMenuSaveAs() {
+ const TEST_DATA = [
+ { type: "link", path: TEST_PATH, target: "#link1" },
+ { type: "video", path: TEST_PATH_VIDEO, target: "#video1" },
+ { type: "image", path: TEST_PATH_IMAGE, target: "#image1" },
+ { type: "page", path: TEST_PATH_PAGE, target: "body" },
+ {
+ type: "frame",
+ path: TEST_PATH_FRAME,
+ target: "body",
+ doCommandFunc: doCommandForFrameType,
+ },
+ ];
+
+ for (const data of TEST_DATA) {
+ info(`Open a new tab for testing "Save ${data.type} as" in context menu.`);
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ `${TEST_ORIGIN}${TEST_PATH}?${data.type}=1`
+ );
+
+ let popupShownPromise = BrowserTestUtils.waitForEvent(
+ document,
+ "popupshown"
+ );
+
+ let browser = gBrowser.selectedBrowser;
+
+ if (data.type === "frame") {
+ browser = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ () => content.document.getElementById("frame1").browsingContext
+ );
+ }
+
+ info("Open the context menu.");
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ data.target,
+ {
+ type: "contextmenu",
+ button: 2,
+ },
+ browser
+ );
+
+ await popupShownPromise;
+
+ let transferCompletePromise = createPromiseForTransferComplete();
+ let observerPromise = createPromiseForObservingChannel(
+ data.path,
+ TEST_FIRST_PARTY
+ );
+
+ let contextMenu = document.getElementById("contentAreaContextMenu");
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(
+ contextMenu,
+ "popuphidden"
+ );
+
+ // Select "Save As" option from context menu.
+ if (!data.doCommandFunc) {
+ let saveElement = document.getElementById(`context-save${data.type}`);
+ info("Triggering the save process.");
+ contextMenu.activateItem(saveElement);
+ } else {
+ await data.doCommandFunc();
+ }
+
+ info("Waiting for the channel.");
+ await observerPromise;
+
+ info("Wait until the save is finished.");
+ await transferCompletePromise;
+
+ info("Wait until the menu is closed.");
+ await popupHiddenPromise;
+
+ BrowserTestUtils.removeTab(tab);
+ }
+});
+
+add_task(async function testFileMenuSavePageAs() {
+ info(`Open a new tab for testing "Save Page AS" in the file menu.`);
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ `${TEST_ORIGIN}${TEST_PATH}?page=1`
+ );
+
+ let transferCompletePromise = createPromiseForTransferComplete();
+ let observerPromise = createPromiseForObservingChannel(
+ TEST_PATH_PAGE,
+ TEST_FIRST_PARTY
+ );
+
+ let menubar = document.getElementById("main-menubar");
+ let filePopup = document.getElementById("menu_FilePopup");
+
+ // We only use the shortcut keys to open the file menu in Windows and Linux.
+ // Mac doesn't have a shortcut to only open the file menu. Instead, we directly
+ // trigger the save in MAC without any UI interactions.
+ if (Services.appinfo.OS !== "Darwin") {
+ let menubarActive = BrowserTestUtils.waitForEvent(
+ menubar,
+ "DOMMenuBarActive"
+ );
+ EventUtils.synthesizeKey("KEY_F10");
+ await menubarActive;
+
+ let popupShownPromise = BrowserTestUtils.waitForEvent(
+ filePopup,
+ "popupshown"
+ );
+ // In window, it still needs one extra down key to open the file menu.
+ if (Services.appinfo.OS === "WINNT") {
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ }
+ await popupShownPromise;
+ }
+
+ info("Triggering the save process.");
+ let fileSavePageAsElement = document.getElementById("menu_savePage");
+ fileSavePageAsElement.doCommand();
+
+ info("Waiting for the channel.");
+ await observerPromise;
+
+ info("Wait until the save is finished.");
+ await transferCompletePromise;
+
+ // Close the file menu.
+ if (Services.appinfo.OS !== "Darwin") {
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(
+ filePopup,
+ "popuphidden"
+ );
+ filePopup.hidePopup();
+ await popupHiddenPromise;
+ }
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function testPageInfoMediaSaveAs() {
+ info(
+ `Open a new tab for testing "Save AS" in the media panel of the page info.`
+ );
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ `${TEST_ORIGIN}${TEST_PATH}?pageinfo=1`
+ );
+
+ info("Open the media panel of the pageinfo.");
+ let pageInfo = BrowserPageInfo(
+ gBrowser.selectedBrowser.currentURI.spec,
+ "mediaTab"
+ );
+
+ await BrowserTestUtils.waitForEvent(pageInfo, "page-info-init");
+
+ let imageTree = pageInfo.document.getElementById("imagetree");
+ let imageRowsNum = imageTree.view.rowCount;
+
+ is(imageRowsNum, 2, "There should be two media items here.");
+
+ for (let i = 0; i < imageRowsNum; i++) {
+ imageTree.view.selection.select(i);
+ imageTree.ensureRowIsVisible(i);
+ imageTree.focus();
+
+ let url = pageInfo.gImageView.data[i][0]; // COL_IMAGE_ADDRESS
+ info(`Start to save the media item with URL: ${url}`);
+
+ let transferCompletePromise = createPromiseForTransferComplete();
+ let observerPromise = createPromiseForObservingChannel(
+ url,
+ TEST_FIRST_PARTY
+ );
+
+ info("Triggering the save process.");
+ let saveElement = pageInfo.document.getElementById("imagesaveasbutton");
+ saveElement.doCommand();
+
+ info("Waiting for the channel.");
+ await observerPromise;
+
+ info("Wait until the save is finished.");
+ await transferCompletePromise;
+ }
+
+ pageInfo.close();
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/components/originattributes/test/browser/browser_httpauth.js b/browser/components/originattributes/test/browser/browser_httpauth.js
new file mode 100644
index 0000000000..085d493c1b
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_httpauth.js
@@ -0,0 +1,79 @@
+let { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+let authPromptModalType = Services.prefs.getIntPref(
+ "prompts.modalType.httpAuth"
+);
+
+let commonDialogEnabled =
+ authPromptModalType === Services.prompt.MODAL_TYPE_WINDOW ||
+ (authPromptModalType === Services.prompt.MODAL_TYPE_TAB &&
+ Services.prefs.getBoolPref("prompts.tabChromePromptSubDialog"));
+
+let server = new HttpServer();
+server.registerPathHandler("/file.html", fileHandler);
+server.start(-1);
+
+let BASE_URI = "http://localhost:" + server.identity.primaryPort;
+let FILE_URI = BASE_URI + "/file.html";
+
+let credentialQueue = [];
+
+// Ask the user agent for authorization.
+function fileHandler(metadata, response) {
+ if (!metadata.hasHeader("Authorization")) {
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ response.setHeader("WWW-Authenticate", 'Basic realm="User Visible Realm"');
+ return;
+ }
+
+ // This will be "account:password" encoded in base64.
+ credentialQueue.push(metadata.getHeader("Authorization"));
+
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/html", false);
+ let body = "<html><body></body></html>";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function onCommonDialogLoaded(subject) {
+ let dialog;
+ if (commonDialogEnabled) {
+ dialog = subject.Dialog;
+ } else {
+ let promptBox =
+ subject.ownerGlobal.gBrowser.selectedBrowser.tabModalPromptBox;
+ dialog = promptBox.getPrompt(subject).Dialog;
+ }
+ // Submit random account and password
+ dialog.ui.loginTextbox.setAttribute("value", Math.random());
+ dialog.ui.password1Textbox.setAttribute("value", Math.random());
+ dialog.ui.button0.click();
+}
+
+let authPromptTopic = commonDialogEnabled
+ ? "common-dialog-loaded"
+ : "tabmodal-dialog-loaded";
+Services.obs.addObserver(onCommonDialogLoaded, authPromptTopic);
+
+registerCleanupFunction(() => {
+ Services.obs.removeObserver(onCommonDialogLoaded, authPromptTopic);
+ server.stop(() => {
+ server = null;
+ });
+});
+
+function getResult() {
+ // If two targets are isolated, they should get different credentials.
+ // Otherwise, the credentials will be cached and therefore the same.
+ return credentialQueue.shift();
+}
+
+async function doInit(aMode) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.partition.network_state", false]],
+ });
+}
+
+IsolationTestTools.runTests(FILE_URI, getResult, null, doInit);
diff --git a/browser/components/originattributes/test/browser/browser_imageCacheIsolation.js b/browser/components/originattributes/test/browser/browser_imageCacheIsolation.js
new file mode 100644
index 0000000000..34c77f746d
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_imageCacheIsolation.js
@@ -0,0 +1,92 @@
+/*
+ * Bug 1264572 - A test case for image cache isolation.
+ */
+
+requestLongerTimeout(2);
+
+let { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+const NUM_ISOLATION_LOADS = 2;
+const NUM_CACHED_LOADS = 1;
+
+let gHits = 0;
+
+let server = new HttpServer();
+server.registerPathHandler("/image.png", imageHandler);
+server.registerPathHandler("/file.html", fileHandler);
+server.start(-1);
+
+// Disable rcwn to make cache behavior deterministic.
+let rcwnEnabled = Services.prefs.getBoolPref("network.http.rcwn.enabled");
+Services.prefs.setBoolPref("network.http.rcwn.enabled", false);
+
+registerCleanupFunction(() => {
+ Services.prefs.setBoolPref("network.http.rcwn.enabled", rcwnEnabled);
+
+ server.stop(() => {
+ server = null;
+ });
+});
+
+let BASE_URI = "http://localhost:" + server.identity.primaryPort;
+let IMAGE_URI = BASE_URI + "/image.png";
+let FILE_URI = BASE_URI + "/file.html";
+
+function imageHandler(metadata, response) {
+ info("XXX: loading image from server");
+ gHits++;
+ response.setHeader("Cache-Control", "max-age=10000", false);
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "image/png", false);
+ var body = atob(
+ "iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAIAAADZSiLoAAAAEUlEQVQImWP4z8AAQTAamQkAhpcI+DeMzFcAAAAASUVORK5CYII="
+ );
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function fileHandler(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/html", false);
+ let body = `<html><body><image src=${IMAGE_URI}></body></html>`;
+ response.bodyOutputStream.write(body, body.length);
+}
+
+async function doBefore() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.partition.network_state", false]],
+ });
+
+ // reset hit counter
+ info("XXX resetting gHits");
+ gHits = 0;
+ info("XXX clearing image cache");
+ let imageCache = Cc["@mozilla.org/image/tools;1"]
+ .getService(Ci.imgITools)
+ .getImgCacheForDocument(null);
+ imageCache.clearCache(true);
+ imageCache.clearCache(false);
+ info("XXX clearning network cache");
+ Services.cache2.clear();
+}
+
+// the test function does nothing on purpose.
+function doTest(aBrowser) {
+ return 0;
+}
+
+// the check function
+function doCheck(shouldIsolate, a, b) {
+ // if we're doing first party isolation and the image cache isolation is
+ // working, then gHits should be 2 because the image would have been loaded
+ // one per first party domain. if first party isolation is disabled, then
+ // gHits should be 1 since there would be one image load from the server and
+ // one load from the image cache.
+ info(`XXX check: gHits == ${gHits}, shouldIsolate == ${shouldIsolate}`);
+ return shouldIsolate
+ ? gHits == NUM_ISOLATION_LOADS
+ : gHits == NUM_CACHED_LOADS;
+}
+
+IsolationTestTools.runTests(FILE_URI, doTest, doCheck, doBefore);
diff --git a/browser/components/originattributes/test/browser/browser_localStorageIsolation.js b/browser/components/originattributes/test/browser/browser_localStorageIsolation.js
new file mode 100644
index 0000000000..68990f6ea4
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_localStorageIsolation.js
@@ -0,0 +1,33 @@
+/**
+ * Bug 1264567 - A test case for localStorage isolation.
+ */
+
+const TEST_PAGE =
+ "http://mochi.test:8888/browser/browser/components/" +
+ "originattributes/test/browser/file_firstPartyBasic.html";
+
+// Use a random key so we don't access it in later tests.
+const key = Math.random().toString();
+
+// IsolationTestTools flushes all preferences
+// hence we explicitly pref off https-first mode
+async function prefOffHttpsFirstMode() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.security.https_first", false]],
+ });
+}
+
+// Define the testing function
+function doTest(aBrowser) {
+ return SpecialPowers.spawn(aBrowser, [key], function (contentKey) {
+ let value = content.localStorage.getItem(contentKey);
+ if (value === null) {
+ // No value is found, so we create one.
+ value = Math.random().toString();
+ content.localStorage.setItem(contentKey, value);
+ }
+ return value;
+ });
+}
+
+IsolationTestTools.runTests(TEST_PAGE, doTest, null, prefOffHttpsFirstMode);
diff --git a/browser/components/originattributes/test/browser/browser_permissions.js b/browser/components/originattributes/test/browser/browser_permissions.js
new file mode 100644
index 0000000000..27819e6443
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_permissions.js
@@ -0,0 +1,91 @@
+/**
+ * Bug 1282655 - Test if site permissions are universal across origin attributes.
+ *
+ * This test is testing the cookie "permission" for a specific URI.
+ */
+
+const { PermissionTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PermissionTestUtils.sys.mjs"
+);
+
+const TEST_PAGE = "https://example.net";
+const uri = Services.io.newURI(TEST_PAGE);
+
+async function disableCookies() {
+ Services.cookies.removeAll();
+ PermissionTestUtils.add(uri, "cookie", Services.perms.DENY_ACTION);
+
+ // A workaround for making this test working. In Bug 1330467, we separate the
+ // permissions between different firstPartyDomains, but not for the
+ // userContextID and the privateBrowsingId. So we need to manually add the
+ // permission for FPDs in order to make this test working. This test should be
+ // eventually removed once the permissions are isolated by OAs.
+ let principal = Services.scriptSecurityManager.createContentPrincipal(uri, {
+ firstPartyDomain: "example.com",
+ });
+ PermissionTestUtils.add(principal, "cookie", Services.perms.DENY_ACTION);
+
+ principal = Services.scriptSecurityManager.createContentPrincipal(uri, {
+ firstPartyDomain: "example.org",
+ });
+ PermissionTestUtils.add(principal, "cookie", Services.perms.DENY_ACTION);
+}
+
+async function ensureCookieNotSet(aBrowser) {
+ await SpecialPowers.spawn(aBrowser, [], async function () {
+ content.document.cookie = "key=value; SameSite=None; Secure;";
+ Assert.equal(
+ content.document.cookie,
+ "",
+ "Setting/reading cookies should be disabled" +
+ " for this domain for all origin attribute combinations."
+ );
+ });
+}
+
+IsolationTestTools.runTests(
+ TEST_PAGE,
+ ensureCookieNotSet,
+ () => true,
+ disableCookies
+);
+
+async function enableCookies() {
+ Services.cookies.removeAll();
+ PermissionTestUtils.add(uri, "cookie", Services.perms.ALLOW_ACTION);
+
+ // A workaround for making this test working.
+ let principal = Services.scriptSecurityManager.createContentPrincipal(uri, {
+ firstPartyDomain: "example.com",
+ });
+ PermissionTestUtils.add(principal, "cookie", Services.perms.ALLOW_ACTION);
+
+ principal = Services.scriptSecurityManager.createContentPrincipal(uri, {
+ firstPartyDomain: "example.org",
+ });
+ PermissionTestUtils.add(principal, "cookie", Services.perms.ALLOW_ACTION);
+}
+
+async function ensureCookieSet(aBrowser) {
+ await SpecialPowers.spawn(aBrowser, [], function () {
+ content.document.cookie = "key=value; SameSite=None; Secure;";
+ Assert.equal(
+ content.document.cookie,
+ "key=value",
+ "Setting/reading cookies should be" +
+ " enabled for this domain for all origin attribute combinations."
+ );
+ });
+}
+
+IsolationTestTools.runTests(
+ TEST_PAGE,
+ ensureCookieSet,
+ () => true,
+ enableCookies
+);
+
+registerCleanupFunction(() => {
+ SpecialPowers.clearUserPref("network.cookie.sameSite.laxByDefault");
+ Services.cookies.removeAll();
+});
diff --git a/browser/components/originattributes/test/browser/browser_postMessage.js b/browser/components/originattributes/test/browser/browser_postMessage.js
new file mode 100644
index 0000000000..a293213757
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_postMessage.js
@@ -0,0 +1,121 @@
+/**
+ * Bug 1492607 - Test for assuring that postMessage cannot go across OAs.
+ */
+
+const FPD_ONE = "http://example.com";
+const FPD_TWO = "http://example.org";
+
+const TEST_BASE = "/browser/browser/components/originattributes/test/browser/";
+
+add_setup(async function () {
+ // Make sure first party isolation is enabled.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.firstparty.isolate", true],
+ ["dom.security.https_first", false],
+ ],
+ });
+});
+
+async function runTestWithOptions(
+ aDifferentFPD,
+ aStarTargetOrigin,
+ aBlockAcrossFPD
+) {
+ let testPageURL = aDifferentFPD
+ ? FPD_ONE + TEST_BASE + "file_postMessage.html"
+ : FPD_TWO + TEST_BASE + "file_postMessage.html";
+
+ // Deciding the targetOrigin according to the test setting.
+ let targetOrigin;
+ if (aStarTargetOrigin) {
+ targetOrigin = "*";
+ } else {
+ targetOrigin = aDifferentFPD ? FPD_ONE : FPD_TWO;
+ }
+ let senderURL =
+ FPD_TWO + TEST_BASE + `file_postMessageSender.html?${targetOrigin}`;
+
+ // Open a tab to listen messages.
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, testPageURL);
+
+ // Use window.open() in the tab to open the sender tab. The sender tab
+ // will send a message through postMessage to window.opener.
+ let senderTabPromise = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ senderURL,
+ true
+ );
+ SpecialPowers.spawn(tab.linkedBrowser, [senderURL], aSenderPath => {
+ content.open(aSenderPath, "_blank");
+ });
+
+ // Wait and get the tab of the sender tab.
+ let senderTab = await senderTabPromise;
+
+ // The postMessage should be blocked when the first parties are different with
+ // the following two cases. First, it is using a non-star target origin.
+ // Second, it is using the star target origin and the pref
+ // 'privacy.firstparty.isolate.block_post_message' is true.
+ let shouldBlock = aDifferentFPD && (!aStarTargetOrigin || aBlockAcrossFPD);
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [shouldBlock], async aValue => {
+ await new Promise(resolve => {
+ content.addEventListener("message", async function eventHandler(aEvent) {
+ if (aEvent.data === "Self") {
+ let display = content.document.getElementById("display");
+ if (aValue) {
+ Assert.equal(
+ display.innerHTML,
+ "",
+ "It should not get a message from other OA."
+ );
+ } else {
+ await ContentTaskUtils.waitForCondition(
+ () => display.innerHTML == "Message",
+ "Wait for message to arrive"
+ );
+ Assert.equal(
+ display.innerHTML,
+ "Message",
+ "It should get a message from the same OA."
+ );
+ }
+
+ content.removeEventListener("message", eventHandler);
+ resolve();
+ }
+ });
+
+ // Trigger the content to send a postMessage to itself.
+ content.document.getElementById("button").click();
+ });
+ });
+
+ BrowserTestUtils.removeTab(tab);
+ BrowserTestUtils.removeTab(senderTab);
+}
+
+add_task(async function runTests() {
+ for (let useDifferentFPD of [true, false]) {
+ for (let useStarTargetOrigin of [true, false]) {
+ for (let enableBlocking of [true, false]) {
+ if (enableBlocking) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.firstparty.isolate.block_post_message", true]],
+ });
+ }
+
+ await runTestWithOptions(
+ useDifferentFPD,
+ useStarTargetOrigin,
+ enableBlocking
+ );
+
+ if (enableBlocking) {
+ await SpecialPowers.popPrefEnv();
+ }
+ }
+ }
+ }
+});
diff --git a/browser/components/originattributes/test/browser/browser_sanitize.js b/browser/components/originattributes/test/browser/browser_sanitize.js
new file mode 100644
index 0000000000..61d236f249
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_sanitize.js
@@ -0,0 +1,92 @@
+/**
+ * Bug 1270338 - Add a mochitest to ensure Sanitizer clears data for all containers
+ */
+
+if (SpecialPowers.useRemoteSubframes) {
+ requestLongerTimeout(4);
+}
+
+const CC = Components.Constructor;
+
+const TEST_DOMAIN = "https://example.net/";
+
+async function setCookies(aBrowser) {
+ await SpecialPowers.spawn(aBrowser, [], function () {
+ content.document.cookie = "key=value";
+ });
+}
+
+function cacheDataForContext(loadContextInfo) {
+ return new Promise(resolve => {
+ let cachedURIs = [];
+ let cacheVisitor = {
+ onCacheStorageInfo(num, consumption) {},
+ onCacheEntryInfo(uri, idEnhance) {
+ cachedURIs.push(uri.asciiSpec);
+ },
+ onCacheEntryVisitCompleted() {
+ resolve(cachedURIs);
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsICacheStorageVisitor"]),
+ };
+ // Visiting the disk cache also visits memory storage so we do not
+ // need to use Services.cache2.memoryCacheStorage() here.
+ let storage = Services.cache2.diskCacheStorage(loadContextInfo);
+ storage.asyncVisitStorage(cacheVisitor, true);
+ });
+}
+
+async function checkCookiesSanitized(aBrowser) {
+ await SpecialPowers.spawn(aBrowser, [], function () {
+ Assert.equal(
+ content.document.cookie,
+ "",
+ "Cookies of all origin attributes should be cleared."
+ );
+ });
+}
+
+function checkCacheExists(aShouldExist) {
+ return async function () {
+ let loadContextInfos = [
+ Services.loadContextInfo.default,
+ Services.loadContextInfo.custom(false, { userContextId: 1 }),
+ Services.loadContextInfo.custom(false, { userContextId: 2 }),
+ Services.loadContextInfo.custom(false, {
+ firstPartyDomain: "example.com",
+ }),
+ Services.loadContextInfo.custom(false, {
+ firstPartyDomain: "example.org",
+ }),
+ ];
+ let i = 0;
+ for (let loadContextInfo of loadContextInfos) {
+ let cacheURIs = await cacheDataForContext(loadContextInfo);
+ is(
+ cacheURIs.includes(TEST_DOMAIN),
+ aShouldExist,
+ TEST_DOMAIN +
+ " should " +
+ (aShouldExist ? "not " : "") +
+ "be cached for all origin attributes." +
+ i++
+ );
+ }
+ };
+}
+
+add_setup(async function () {
+ Services.cache2.clear();
+});
+
+// This will set the cookies and the cache.
+IsolationTestTools.runTests(TEST_DOMAIN, setCookies, () => true);
+
+add_task(checkCacheExists(true));
+
+add_task(async function sanitize() {
+ await Sanitizer.sanitize(["cookies", "cache"]);
+});
+
+add_task(checkCacheExists(false));
+IsolationTestTools.runTests(TEST_DOMAIN, checkCookiesSanitized, () => true);
diff --git a/browser/components/originattributes/test/browser/browser_sharedworker.js b/browser/components/originattributes/test/browser/browser_sharedworker.js
new file mode 100644
index 0000000000..5fd7f180ee
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_sharedworker.js
@@ -0,0 +1,30 @@
+/**
+ * Bug 1264593 - A test case for the shared worker by first party isolation.
+ */
+
+const TEST_DOMAIN = "https://example.net/";
+const TEST_PATH =
+ TEST_DOMAIN + "browser/browser/components/originattributes/test/browser/";
+const TEST_PAGE = TEST_PATH + "file_sharedworker.html";
+
+async function getResultFromSharedworker(aBrowser) {
+ let response = await SpecialPowers.spawn(aBrowser, [], async function () {
+ let worker = new content.SharedWorker(
+ "file_shared.worker.js",
+ "isolationSharedWorkerTest"
+ );
+
+ let result = await new content.Promise(resolve => {
+ worker.port.onmessage = function (e) {
+ content.document.getElementById("display").innerHTML = e.data;
+ resolve(e.data);
+ };
+ });
+
+ return result;
+ });
+
+ return response;
+}
+
+IsolationTestTools.runTests(TEST_PAGE, getResultFromSharedworker);
diff --git a/browser/components/originattributes/test/browser/browser_windowOpenerRestriction.js b/browser/components/originattributes/test/browser/browser_windowOpenerRestriction.js
new file mode 100644
index 0000000000..0822ba24c9
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_windowOpenerRestriction.js
@@ -0,0 +1,113 @@
+/**
+ * Bug 1339336 - A test case for testing pref 'privacy.firstparty.isolate.restrict_opener_access'
+ */
+
+const CC = Components.Constructor;
+
+const FIRST_PARTY_OPENER = "example.com";
+const FIRST_PARTY_TARGET = "example.org";
+const OPENER_PAGE =
+ "https://" +
+ FIRST_PARTY_OPENER +
+ "/browser/browser/components/" +
+ "originattributes/test/browser/file_windowOpenerRestriction.html";
+const TARGET_PAGE =
+ "https://" +
+ FIRST_PARTY_TARGET +
+ "/browser/browser/components/" +
+ "originattributes/test/browser/file_windowOpenerRestrictionTarget.html";
+
+async function testPref(aIsPrefEnabled) {
+ // Use a random key so we don't access it in later tests.
+ let cookieStr =
+ "key" + Math.random().toString() + "=" + Math.random().toString();
+
+ // Open the tab for the opener page.
+ let tab = BrowserTestUtils.addTab(gBrowser, OPENER_PAGE);
+
+ // Select this tab and make sure its browser is loaded and focused.
+ gBrowser.selectedTab = tab;
+ tab.ownerGlobal.focus();
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ await SpecialPowers.spawn(
+ browser,
+ [{ cookieStr, page: TARGET_PAGE, isPrefEnabled: aIsPrefEnabled }],
+ async function (obj) {
+ // Acquire the iframe element.
+ let childFrame = content.document.getElementById("child");
+
+ // Insert a cookie into this iframe.
+ await SpecialPowers.spawn(childFrame, [obj.cookieStr], aCookieStr => {
+ content.document.cookie = aCookieStr + "; SameSite=None; Secure;";
+ });
+
+ // Open the tab here and focus on it.
+ let openedPath = obj.page;
+ if (!obj.isPrefEnabled) {
+ // If the pref is not enabled, we pass the cookie value through the query string
+ // to tell the target page that it should check the cookie value.
+ openedPath += "?" + obj.cookieStr;
+ }
+
+ // Issue the opener page to open the target page and focus on it.
+ content.openedWindow = content.open(openedPath);
+ content.openedWindow.focus();
+ }
+ );
+
+ // Wait until the target page is loaded.
+ let targetBrowser = gBrowser.getBrowserForTab(gBrowser.selectedTab);
+ await BrowserTestUtils.browserLoaded(targetBrowser);
+
+ // The target page will do the check and show the result through its title.
+ is(
+ targetBrowser.contentTitle,
+ "pass",
+ "The behavior of window.opener is correct."
+ );
+
+ // Close Tabs.
+ await SpecialPowers.spawn(browser, [], async function () {
+ content.openedWindow.close();
+ });
+ BrowserTestUtils.removeTab(tab);
+
+ // Reset cookies
+ Services.cookies.removeAll();
+}
+
+add_task(async function runTests() {
+ let tests = [true, false];
+
+ // First, we test the scenario that the first party isolation is enabled.
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.firstparty.isolate", true]],
+ });
+
+ for (let enabled of tests) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.firstparty.isolate.restrict_opener_access", enabled]],
+ });
+
+ await testPref(enabled);
+ }
+
+ // Second, we test the scenario that the first party isolation is disabled.
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.firstparty.isolate", false]],
+ });
+
+ for (let enabled of tests) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.firstparty.isolate.restrict_opener_access", enabled]],
+ });
+
+ // When first party isolation is disabled, this pref will not affect the behavior of
+ // window.opener. And the correct behavior here is to allow access since the iframe in
+ // the opener page has the same origin with the target page.
+ await testPref(false);
+ }
+});
diff --git a/browser/components/originattributes/test/browser/dummy.html b/browser/components/originattributes/test/browser/dummy.html
new file mode 100644
index 0000000000..1a87e28408
--- /dev/null
+++ b/browser/components/originattributes/test/browser/dummy.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+<title>Dummy test page</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta>
+</head>
+<body>
+<p>Dummy test page</p>
+</body>
+</html>
diff --git a/browser/components/originattributes/test/browser/file_broadcastChannel.html b/browser/components/originattributes/test/browser/file_broadcastChannel.html
new file mode 100644
index 0000000000..deb4a154ea
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_broadcastChannel.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
+ <title>Page broadcast channel creator for first party isolation</title>
+</head>
+<body>
+ <div id="display" style="white-space:pre; font-family:monospace; display:inline;"></div>
+ <iframe id="iframe" src="file_broadcastChanneliFrame.html"></iframe>>
+<script type="text/javascript">
+let bc = new BroadcastChannel("testBroadcastChannel");
+bc.onmessage = function(e) {
+ document.getElementById("display").innerHTML = e.data;
+};
+</script>
+</body>
diff --git a/browser/components/originattributes/test/browser/file_broadcastChanneliFrame.html b/browser/components/originattributes/test/browser/file_broadcastChanneliFrame.html
new file mode 100644
index 0000000000..6f4c484bc9
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_broadcastChanneliFrame.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
+ <title>Page broadcast channel responder for first party isolation</title>
+</head>
+<body>
+ <div id="display" style="white-space:pre; font-family:monospace; display:inline;"></div>
+<script type="text/javascript">
+let bc = new BroadcastChannel("testBroadcastChannel");
+bc.onmessage = function(e) {
+ window.parent.postMessage(e.data, "*");
+};
+</script>
+</body>
diff --git a/browser/components/originattributes/test/browser/file_cache.html b/browser/components/originattributes/test/browser/file_cache.html
new file mode 100644
index 0000000000..24e09baefa
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_cache.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
+<head>
+ <link rel="icon" type="image/png" href="http://example.net/browser/browser/components/originattributes/test/browser/file_thirdPartyChild.favicon.png">
+ <link rel="stylesheet" type="text/css"
+ href="http://example.net/browser/browser/components/originattributes/test/browser/file_thirdPartyChild.link.css">
+ <link rel="preconnect" href="http://example.net">
+ <style type="text/css">
+ @font-face {
+ font-family: foo;
+ src: url("http://example.net/browser/browser/components/originattributes/test/browser/file_thirdPartyChild.font.woff") format('woff');
+ }
+ body { font-family: foo }
+ </style>
+</head>
+<body>
+<div>file_cache.html</div>
+
+<iframe src="http://example.net/browser/browser/components/originattributes/test/browser/file_thirdPartyChild.iframe.html">
+</iframe>
+
+<script src="http://example.net/browser/browser/components/originattributes/test/browser/file_thirdPartyChild.script.js">
+</script>
+
+<img src="http://example.net/browser/browser/components/originattributes/test/browser/file_thirdPartyChild.img.png">
+
+<embed src="http://example.net/browser/browser/components/originattributes/test/browser/file_thirdPartyChild.embed.png">
+
+<object data="http://example.net/browser/browser/components/originattributes/test/browser/file_thirdPartyChild.object.png"
+ type="image/png"></object>
+</body>
+</html>
diff --git a/browser/components/originattributes/test/browser/file_favicon.html b/browser/components/originattributes/test/browser/file_favicon.html
new file mode 100644
index 0000000000..f294b47758
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_favicon.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <meta charset='utf-8'>
+ <title>Favicon Test for originAttributes</title>
+ <link rel="icon" type="image/png" href="file_favicon.png" />
+ </head>
+ <body>
+ Favicon!!
+ </body>
+</html>
diff --git a/browser/components/originattributes/test/browser/file_favicon.png b/browser/components/originattributes/test/browser/file_favicon.png
new file mode 100644
index 0000000000..5535363c94
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_favicon.png
Binary files differ
diff --git a/browser/components/originattributes/test/browser/file_favicon.png^headers^ b/browser/components/originattributes/test/browser/file_favicon.png^headers^
new file mode 100644
index 0000000000..9e23c73b7f
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_favicon.png^headers^
@@ -0,0 +1 @@
+Cache-Control: no-cache
diff --git a/browser/components/originattributes/test/browser/file_favicon_cache.html b/browser/components/originattributes/test/browser/file_favicon_cache.html
new file mode 100644
index 0000000000..9e3e0114a1
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_favicon_cache.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <meta charset='utf-8'>
+ <title>Favicon Test for originAttributes</title>
+ <link rel="icon" type="image/png" href="https://example.net/browser/browser/components/originattributes/test/browser/file_favicon_cache.png" />
+ </head>
+ <body>
+ Third Party Favicon!!
+ </body>
+</html>
diff --git a/browser/components/originattributes/test/browser/file_favicon_cache.png b/browser/components/originattributes/test/browser/file_favicon_cache.png
new file mode 100644
index 0000000000..5535363c94
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_favicon_cache.png
Binary files differ
diff --git a/browser/components/originattributes/test/browser/file_favicon_thirdParty.html b/browser/components/originattributes/test/browser/file_favicon_thirdParty.html
new file mode 100644
index 0000000000..e50b0c3493
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_favicon_thirdParty.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <meta charset='utf-8'>
+ <title>Favicon Test for originAttributes</title>
+ <link rel="icon" type="image/png" href="https://example.net/browser/browser/components/originattributes/test/browser/file_favicon.png" />
+ </head>
+ <body>
+ Third Party Favicon!!
+ </body>
+</html>
diff --git a/browser/components/originattributes/test/browser/file_firstPartyBasic.html b/browser/components/originattributes/test/browser/file_firstPartyBasic.html
new file mode 100644
index 0000000000..713187fb2c
--- /dev/null
+++ b/browser/components/originattributes/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>
diff --git a/browser/components/originattributes/test/browser/file_postMessage.html b/browser/components/originattributes/test/browser/file_postMessage.html
new file mode 100644
index 0000000000..91255d0364
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_postMessage.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
+ <title>Test page for window.postMessage</title>
+</head>
+<body>
+ <div id="display" style="white-space:pre; font-family:monospace; display:inline;"></div>
+ <input type="button" id="button" title="button"/>
+ <script>
+
+ const TEST_PATH = "http://example.org/browser/browser/components/originattributes/test/browser/file_postMessageSender.html";
+
+ window.onload = () => {
+ window.addEventListener("message", aEvent => {
+ if (aEvent.data === "Message") {
+ document.getElementById("display").innerHTML = "Message";
+ }
+ });
+
+ document.getElementById("button").onclick = () => {
+ window.postMessage("Self", "/");
+ };
+ };
+ </script>
+</body>
+</html>
diff --git a/browser/components/originattributes/test/browser/file_postMessageSender.html b/browser/components/originattributes/test/browser/file_postMessageSender.html
new file mode 100644
index 0000000000..ebdffb990f
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_postMessageSender.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
+ <title>Test page for always sending a message to its opener through postMessage</title>
+</head>
+<body>
+ <script>
+ window.onload = () => {
+ // The target origin is coming from the query string.
+ let targetOrigin = window.location.search.substring(1);
+ window.opener.postMessage("Message", targetOrigin);
+ };
+ </script>
+</body>
+</html>
diff --git a/browser/components/originattributes/test/browser/file_saveAs.sjs b/browser/components/originattributes/test/browser/file_saveAs.sjs
new file mode 100644
index 0000000000..9b16250c76
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_saveAs.sjs
@@ -0,0 +1,38 @@
+const HTTP_ORIGIN = "http://example.com";
+const SECOND_ORIGIN = "http://example.org";
+const URI_PATH = "/browser/browser/components/originattributes/test/browser/";
+const LINK_PATH = `${URI_PATH}file_saveAs.sjs`;
+// Reusing existing ogv file for testing.
+const VIDEO_PATH = `${URI_PATH}file_thirdPartyChild.video.ogv`;
+// Reusing existing png file for testing.
+const IMAGE_PATH = `${URI_PATH}file_favicon.png`;
+const FRAME_PATH = `${SECOND_ORIGIN}${URI_PATH}file_saveAs.sjs?image=1`;
+
+function handleRequest(aRequest, aResponse) {
+ var params = new URLSearchParams(aRequest.queryString);
+ aResponse.setStatusLine(aRequest.httpVersion, 200);
+ aResponse.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
+ let contentBody = "";
+
+ if (params.has("link")) {
+ contentBody = `<a href="${LINK_PATH}" id="link1">this is a link</a>`;
+ } else if (params.has("video")) {
+ contentBody = `<video src="${VIDEO_PATH}" id="video1"> </video>`;
+ } else if (params.has("image")) {
+ contentBody = `<img src="${IMAGE_PATH}" id="image1">`;
+ } else if (params.has("page")) {
+ // We need at least one resource, like a img, a link or a script, to trigger
+ // downloading resources in "Save Page As". Otherwise, it will output the
+ // document directly without any network request.
+ contentBody = `<img src="${IMAGE_PATH}">`;
+ } else if (params.has("frame")) {
+ // Like "Save Page As", we need to put at least one resource in the frame.
+ // Here we also use a image.
+ contentBody = `<iframe src="${FRAME_PATH}" id="frame1"></iframe>`;
+ } else if (params.has("pageinfo")) {
+ contentBody = `<img src="${IMAGE_PATH}" id="image1">
+ <video src="${VIDEO_PATH}" id="video1"> </video>`;
+ }
+
+ aResponse.write(`<html><body>${contentBody}</body></html>`);
+}
diff --git a/browser/components/originattributes/test/browser/file_shared.worker.js b/browser/components/originattributes/test/browser/file_shared.worker.js
new file mode 100644
index 0000000000..e5f0b35946
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_shared.worker.js
@@ -0,0 +1,7 @@
+self.randomValue = Math.random();
+
+onconnect = function (e) {
+ let port = e.ports[0];
+ port.postMessage(self.randomValue);
+ port.start();
+};
diff --git a/browser/components/originattributes/test/browser/file_sharedworker.html b/browser/components/originattributes/test/browser/file_sharedworker.html
new file mode 100644
index 0000000000..b9ff793bd5
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_sharedworker.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
+ <title>Page SharedWorker creator for first party isolation</title>
+</head>
+<body>
+<div id="display" style="white-space:pre; font-family:monospace; display:inline;"></div>
+</body>
+</html>
diff --git a/browser/components/originattributes/test/browser/file_thirdPartyChild.audio.ogg b/browser/components/originattributes/test/browser/file_thirdPartyChild.audio.ogg
new file mode 100644
index 0000000000..edda4e9128
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.audio.ogg
Binary files differ
diff --git a/browser/components/originattributes/test/browser/file_thirdPartyChild.embed.png b/browser/components/originattributes/test/browser/file_thirdPartyChild.embed.png
new file mode 100644
index 0000000000..c5916f2897
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.embed.png
Binary files differ
diff --git a/browser/components/originattributes/test/browser/file_thirdPartyChild.favicon.png b/browser/components/originattributes/test/browser/file_thirdPartyChild.favicon.png
new file mode 100644
index 0000000000..c5916f2897
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.favicon.png
Binary files differ
diff --git a/browser/components/originattributes/test/browser/file_thirdPartyChild.fetch.html b/browser/components/originattributes/test/browser/file_thirdPartyChild.fetch.html
new file mode 100644
index 0000000000..037901ad06
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.fetch.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
+<!-- The child page, used by browser_cache.js -->
+<body>
+<div>thirdPartyChild.fetch.html</div>
+</body>
+</html>
diff --git a/browser/components/originattributes/test/browser/file_thirdPartyChild.font.woff b/browser/components/originattributes/test/browser/file_thirdPartyChild.font.woff
new file mode 100644
index 0000000000..acda4f3d9f
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.font.woff
Binary files differ
diff --git a/browser/components/originattributes/test/browser/file_thirdPartyChild.iframe.html b/browser/components/originattributes/test/browser/file_thirdPartyChild.iframe.html
new file mode 100644
index 0000000000..b047d5b412
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.iframe.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
+<!-- The child page, used by browser_cache.js -->
+<body>
+<div>thirdPartyChild.html</div>
+<script>
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", "http://example.net/browser/browser/components/originattributes/test/browser/file_thirdPartyChild.xhr.html", true);
+ xhr.send();
+ var worker = new Worker("http://example.net/browser/browser/components/originattributes/test/browser/file_thirdPartyChild.worker.js");
+ var sharedWorker = new SharedWorker("http://example.net/browser/browser/components/originattributes/test/browser/file_thirdPartyChild.sharedworker.js");
+
+ fetch("file_thirdPartyChild.fetch.html", {cache: "force-cache"} );
+ fetch(new Request("file_thirdPartyChild.request.html"), {cache: "force-cache"} );
+</script>
+</body>
+</html>
diff --git a/browser/components/originattributes/test/browser/file_thirdPartyChild.img.png b/browser/components/originattributes/test/browser/file_thirdPartyChild.img.png
new file mode 100644
index 0000000000..c5916f2897
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.img.png
Binary files differ
diff --git a/browser/components/originattributes/test/browser/file_thirdPartyChild.import.js b/browser/components/originattributes/test/browser/file_thirdPartyChild.import.js
new file mode 100644
index 0000000000..dbf8f83769
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.import.js
@@ -0,0 +1 @@
+// dummy script, to be called by self.importScripts(...)
diff --git a/browser/components/originattributes/test/browser/file_thirdPartyChild.link.css b/browser/components/originattributes/test/browser/file_thirdPartyChild.link.css
new file mode 100644
index 0000000000..6f8f41b4a4
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.link.css
@@ -0,0 +1 @@
+/* Dummy CSS file, used by browser_cache.js. */
diff --git a/browser/components/originattributes/test/browser/file_thirdPartyChild.object.png b/browser/components/originattributes/test/browser/file_thirdPartyChild.object.png
new file mode 100644
index 0000000000..c5916f2897
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.object.png
Binary files differ
diff --git a/browser/components/originattributes/test/browser/file_thirdPartyChild.request.html b/browser/components/originattributes/test/browser/file_thirdPartyChild.request.html
new file mode 100644
index 0000000000..108ed2ffa5
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.request.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
+<!-- The child page, used by browser_cache.js -->
+<body>
+<div>thirdPartyChild.request.html</div>
+</body>
+</html>
diff --git a/browser/components/originattributes/test/browser/file_thirdPartyChild.script.js b/browser/components/originattributes/test/browser/file_thirdPartyChild.script.js
new file mode 100644
index 0000000000..6ddf436c09
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.script.js
@@ -0,0 +1 @@
+// Dummy child script, used by browser_cache.js
diff --git a/browser/components/originattributes/test/browser/file_thirdPartyChild.sharedworker.js b/browser/components/originattributes/test/browser/file_thirdPartyChild.sharedworker.js
new file mode 100644
index 0000000000..b262fa10a3
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.sharedworker.js
@@ -0,0 +1 @@
+// dummy file
diff --git a/browser/components/originattributes/test/browser/file_thirdPartyChild.track.vtt b/browser/components/originattributes/test/browser/file_thirdPartyChild.track.vtt
new file mode 100644
index 0000000000..b37cb40e45
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.track.vtt
@@ -0,0 +1,13 @@
+WEBVTT FILE
+
+1
+00:00:00.500 --> 00:00:02.000 D:vertical A:start
+blah blah blah
+
+2
+00:00:02.500 --> 00:00:04.300
+this is a test
+
+3
+00:00:05.000 --> 00:00:07.000
+one more line
diff --git a/browser/components/originattributes/test/browser/file_thirdPartyChild.video.ogv b/browser/components/originattributes/test/browser/file_thirdPartyChild.video.ogv
new file mode 100644
index 0000000000..68dee3cf2b
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.video.ogv
Binary files differ
diff --git a/browser/components/originattributes/test/browser/file_thirdPartyChild.worker.fetch.html b/browser/components/originattributes/test/browser/file_thirdPartyChild.worker.fetch.html
new file mode 100644
index 0000000000..47e42d1e58
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.worker.fetch.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
+<!-- The child page, used by browser_cache.js -->
+<body>
+<div>thirdPartyChild.worker.fetch.html</div>
+</body>
+</html>
diff --git a/browser/components/originattributes/test/browser/file_thirdPartyChild.worker.js b/browser/components/originattributes/test/browser/file_thirdPartyChild.worker.js
new file mode 100644
index 0000000000..38aff85c30
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.worker.js
@@ -0,0 +1,20 @@
+var xhr = new XMLHttpRequest();
+xhr.open(
+ "GET",
+ "http://example.net/browser/browser/components/originattributes/test/browser/file_thirdPartyChild.worker.xhr.html",
+ true
+);
+xhr.send();
+
+fetch(
+ "http://example.net/browser/browser/components/originattributes/test/browser/file_thirdPartyChild.worker.fetch.html",
+ { cache: "force-cache" }
+);
+var myRequest = new Request(
+ "http://example.net/browser/browser/components/originattributes/test/browser/file_thirdPartyChild.worker.request.html"
+);
+fetch(myRequest, { cache: "force-cache" });
+
+self.importScripts(
+ "http://example.net/browser/browser/components/originattributes/test/browser/file_thirdPartyChild.import.js"
+);
diff --git a/browser/components/originattributes/test/browser/file_thirdPartyChild.worker.request.html b/browser/components/originattributes/test/browser/file_thirdPartyChild.worker.request.html
new file mode 100644
index 0000000000..5b5c55bfeb
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.worker.request.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
+<!-- The child page, used by browser_cache.js -->
+<body>
+<div>thirdPartyChild.worker.request.html</div>
+</body>
+</html>
diff --git a/browser/components/originattributes/test/browser/file_thirdPartyChild.worker.xhr.html b/browser/components/originattributes/test/browser/file_thirdPartyChild.worker.xhr.html
new file mode 100644
index 0000000000..9fc107f375
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.worker.xhr.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
+<!-- The child page, used by browser_cache.js -->
+<body>
+<div>thirdPartyChild.worker.xhr.html</div>
+</body>
+</html>
diff --git a/browser/components/originattributes/test/browser/file_thirdPartyChild.xhr.html b/browser/components/originattributes/test/browser/file_thirdPartyChild.xhr.html
new file mode 100644
index 0000000000..f56e7b3c1f
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.xhr.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
+<!-- The child page, used by browser_cache.js -->
+<body>
+<div>thirdPartyChild.html</div>
+</body>
+</html>
diff --git a/browser/components/originattributes/test/browser/file_windowOpenerRestriction.html b/browser/components/originattributes/test/browser/file_windowOpenerRestriction.html
new file mode 100644
index 0000000000..8925a6ccf5
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_windowOpenerRestriction.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
+ <title>Test page for window.opener accessibility</title>
+</head>
+<body>
+ <iframe id="child" name="child" src="https://example.org/browser/browser/components/originattributes/test/browser/file_firstPartyBasic.html"></iframe>
+</body>
+</html>
diff --git a/browser/components/originattributes/test/browser/file_windowOpenerRestrictionTarget.html b/browser/components/originattributes/test/browser/file_windowOpenerRestrictionTarget.html
new file mode 100644
index 0000000000..5a14834897
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_windowOpenerRestrictionTarget.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
+ <title>title not set</title>
+ <script>
+ // If the query string is given, we are expecting the window.opener can be accessed
+ // across different first party domains, so we will match the cookie value.
+ // Otherwise, the access of window.opener should be treated as cross-origin.
+ // Therefore, it should fail at this setting.
+ let openerRestriction = true;
+ let cookieValue;
+ if (window.location.search.length) {
+ cookieValue = window.location.search.substr(1);
+ openerRestriction = false;
+ }
+
+ try {
+ let openerFrame = window.opener.frames.child;
+ let result = openerFrame.document.cookie === cookieValue;
+ if (result && !openerRestriction) {
+ document.title = "pass";
+ }
+ } catch (e) {
+ if (openerRestriction) {
+ document.title = "pass";
+ }
+ }
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/browser/components/originattributes/test/browser/head.js b/browser/components/originattributes/test/browser/head.js
new file mode 100644
index 0000000000..bd4307fd1c
--- /dev/null
+++ b/browser/components/originattributes/test/browser/head.js
@@ -0,0 +1,463 @@
+/* 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/originattributes/test/browser/";
+
+// The flags of test modes.
+const TEST_MODE_FIRSTPARTY = 0;
+const TEST_MODE_NO_ISOLATION = 1;
+const TEST_MODE_CONTAINERS = 2;
+
+// The name of each mode.
+const TEST_MODE_NAMES = ["first party isolation", "no isolation", "containers"];
+
+// The frame types.
+const TEST_TYPE_FRAME = 1;
+const TEST_TYPE_IFRAME = 2;
+
+// The default frame setting.
+const DEFAULT_FRAME_SETTING = [TEST_TYPE_IFRAME];
+
+let gFirstPartyBasicPage = TEST_URL_PATH + "file_firstPartyBasic.html";
+
+/**
+ * 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.
+ */
+async function openTabInUserContext(aURL, aUserContextId) {
+ info(`Start to open tab in specific userContextID: ${aUserContextId}.`);
+ let originAttributes = {
+ userContextId: aUserContextId,
+ };
+ info("Create triggeringPrincipal.");
+ let triggeringPrincipal =
+ Services.scriptSecurityManager.createContentPrincipal(
+ makeURI(aURL),
+ originAttributes
+ );
+ // Open the tab in the correct userContextId.
+ info("Open the tab and wait for it to be loaded.");
+ let tab = BrowserTestUtils.addTab(gBrowser, aURL, {
+ userContextId: aUserContextId,
+ triggeringPrincipal,
+ });
+
+ // Select tab and make sure its browser is focused.
+ gBrowser.selectedTab = tab;
+ tab.ownerGlobal.focus();
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+ info("Finished tab opening.");
+
+ 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 by default or you could specify
+ * a frame setting to create nested frames. And this function will also modify
+ * the 'content' in the ContentTask to the target frame's window object.
+ *
+ * @param aURL
+ * The url of the iframe.
+ * @param aFirstPartyDomain
+ * The first party domain.
+ * @param aFrameSetting
+ * This setting controls how frames are organized within the page. The
+ * setting is an array of frame types, the first item indicates the
+ * frame type (iframe or frame) of the first layer of the frame structure,
+ * and the second item indicates the second layer, and so on. The aURL will
+ * be loaded at the deepest layer. This is optional.
+ *
+ * @return tab - The tab object of this tab.
+ * browser - The browser object of this tab.
+ */
+async function openTabInFirstParty(
+ aURL,
+ aFirstPartyDomain,
+ aFrameSetting = DEFAULT_FRAME_SETTING
+) {
+ info(`Start to open tab under first party domain "${aFirstPartyDomain}".`);
+ // If the first party domain ends with '/', we remove it.
+ if (aFirstPartyDomain.endsWith("/")) {
+ aFirstPartyDomain = aFirstPartyDomain.slice(0, -1);
+ }
+
+ let basicPageURL = aFirstPartyDomain + gFirstPartyBasicPage;
+
+ // Open the tab for the basic first party page.
+ info("Open the tab and then wait for it to be loaded.");
+ let tab = BrowserTestUtils.addTab(gBrowser, basicPageURL);
+
+ // Select tab and make sure its browser is focused.
+ gBrowser.selectedTab = tab;
+ tab.ownerGlobal.focus();
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ // Clone the frame setting here since we will modify it later.
+ let frameSetting = aFrameSetting.slice(0);
+ let frameType;
+ let targetBrowsingContext;
+
+ // Create the frame structure.
+ info("Create the frame structure.");
+ while ((frameType = frameSetting.shift())) {
+ if (!targetBrowsingContext) {
+ targetBrowsingContext = browser;
+ }
+
+ let frameURL = !frameSetting.length ? aURL : basicPageURL;
+
+ if (frameType == TEST_TYPE_FRAME) {
+ info("Add the frameset.");
+ targetBrowsingContext = await SpecialPowers.spawn(
+ targetBrowsingContext,
+ [frameURL],
+ async function (aFrameURL) {
+ // Add a frameset which carries the frame element.
+ let frameSet = content.document.createElement("frameset");
+ frameSet.cols = "50%,50%";
+
+ let frame = content.document.createElement("frame");
+ let dummyFrame = content.document.createElement("frame");
+
+ frameSet.appendChild(frame);
+ frameSet.appendChild(dummyFrame);
+
+ content.document.body.appendChild(frameSet);
+
+ // Wait for the frame to be loaded.
+ await new Promise(done => {
+ frame.addEventListener(
+ "load",
+ function () {
+ done();
+ },
+ { capture: true, once: true }
+ );
+
+ frame.setAttribute("src", aFrameURL);
+ });
+
+ return frame.browsingContext;
+ }
+ );
+ } else if (frameType == TEST_TYPE_IFRAME) {
+ info("Add the iframe.");
+ targetBrowsingContext = await SpecialPowers.spawn(
+ targetBrowsingContext,
+ [frameURL],
+ async function (aFrameURL) {
+ // Add an iframe.
+ let frame = content.document.createElement("iframe");
+ content.document.body.appendChild(frame);
+
+ // Wait for the frame to be loaded.
+ await new Promise(done => {
+ frame.addEventListener(
+ "load",
+ function () {
+ done();
+ },
+ { capture: true, once: true }
+ );
+
+ frame.setAttribute("src", aFrameURL);
+ });
+
+ return frame.browsingContext;
+ }
+ );
+ } else {
+ ok(false, "Invalid frame type.");
+ break;
+ }
+ info("Successfully added a frame");
+ }
+ info("Finished the frame structure");
+
+ return { tab, browser: targetBrowsingContext };
+}
+
+this.IsolationTestTools = {
+ /**
+ * Adds isolation tests for first party isolation, no isolation
+ * and containers respectively.
+ *
+ * @param aTask
+ * The testing task which will be run in different settings.
+ */
+ _add_task(aTask) {
+ let testSettings = [
+ {
+ mode: TEST_MODE_FIRSTPARTY,
+ skip: false,
+ prefs: [["privacy.firstparty.isolate", true]],
+ },
+ {
+ mode: TEST_MODE_NO_ISOLATION,
+ skip: false,
+ prefs: [["privacy.firstparty.isolate", false]],
+ },
+ {
+ mode: TEST_MODE_CONTAINERS,
+ skip: false,
+ prefs: [["privacy.userContext.enabled", true]],
+ },
+ ];
+
+ // Add test tasks.
+ for (let testSetting of testSettings) {
+ IsolationTestTools._addTaskForMode(
+ testSetting.mode,
+ testSetting.prefs,
+ testSetting.skip,
+ aTask
+ );
+ }
+ },
+
+ _addTaskForMode(aMode, aPref, aSkip, aTask) {
+ if (aSkip) {
+ return;
+ }
+
+ add_task(async function () {
+ info(`Starting the test for ${TEST_MODE_NAMES[aMode]}.`);
+
+ // Before run this task, reset the preferences first.
+ await SpecialPowers.flushPrefEnv();
+
+ // Make sure preferences are set properly.
+ await SpecialPowers.pushPrefEnv({ set: aPref });
+
+ await SpecialPowers.pushPrefEnv({ set: [["dom.ipc.processCount", 1]] });
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "network.auth.non-web-content-triggered-resources-http-auth-allow",
+ true,
+ ],
+ ],
+ });
+
+ await aTask(aMode);
+ });
+ },
+
+ /**
+ * Add a tab with the given tab setting, this will open different types of
+ * tabs according to the given test mode. A tab setting means a isolation
+ * target in different test mode; a tab setting indicates a first party
+ * domain when testing the first party isolation, it is a user context
+ * id when testing containers.
+ *
+ * @param aMode
+ * The test mode which decides what type of tabs will be opened.
+ * @param aURL
+ * The url which is going to open.
+ * @param aTabSettingObj
+ * The tab setting object includes 'firstPartyDomain' for the first party
+ * domain and 'userContextId' for Containers.
+ * @param aFrameSetting
+ * This setting controls how frames are organized within the page. The
+ * setting is an array of frame types, the first item indicates the
+ * frame type (iframe or frame) of the first layer of the frame structure,
+ * and the second item indicates the second layer, and so on. The aURL
+ * will be loaded at the deepest layer. This is optional.
+ *
+ * @return tab - The tab object of this tab.
+ * browser - The browser object of this tab.
+ */
+ _addTab(aMode, aURL, aTabSettingObj, aFrameSetting) {
+ if (aMode === TEST_MODE_CONTAINERS) {
+ return openTabInUserContext(aURL, aTabSettingObj.userContextId);
+ }
+
+ return openTabInFirstParty(
+ aURL,
+ aTabSettingObj.firstPartyDomain,
+ aFrameSetting
+ );
+ },
+
+ /**
+ * Run isolation tests. The framework will run tests with standard combinations
+ * of prefs and tab settings, and checks whether the isolation is working.
+ *
+ * @param aURL
+ * The URL of the page that will be tested or an object contains 'url',
+ * the tested page, 'firstFrameSetting' for the frame setting of the first
+ * tab, and 'secondFrameSetting' for the second tab.
+ * @param aGetResultFuncs
+ * An array of functions or a single function which are responsible for
+ * returning the isolation result back to the framework for further checking.
+ * Each of these functions will be provided the browser object of the tab,
+ * that allows modifying or fetchings results from the page content.
+ * @param aCompareResultFunc
+ * An optional function which allows modifying the way how does framework
+ * check results. This function will be provided a boolean to indicate
+ * the isolation is no or off and two results. This function should return
+ * a boolean to tell that whether isolation is working. If this function
+ * is not given, the framework will take case checking by itself.
+ * @param aBeforeFunc
+ * An optional function which is called before any tabs are created so
+ * that the test case can set up/reset local state.
+ * @param aGetResultImmediately
+ * An optional boolean to ensure we get results before the next tab is opened.
+ */
+ runTests(
+ aURL,
+ aGetResultFuncs,
+ aCompareResultFunc,
+ aBeforeFunc,
+ aGetResultImmediately,
+ aUseHttps
+ ) {
+ let pageURL;
+ let firstFrameSetting;
+ let secondFrameSetting;
+
+ // Request a longer timeout since the test will run a test for three times
+ // with different settings. Thus, one test here represents three tests.
+ // For this reason, we triple the timeout.
+ requestLongerTimeout(3);
+
+ if (typeof aURL === "string") {
+ pageURL = aURL;
+ } else if (typeof aURL === "object") {
+ pageURL = aURL.url;
+ firstFrameSetting = aURL.firstFrameSetting;
+ secondFrameSetting = aURL.secondFrameSetting;
+ }
+
+ if (!Array.isArray(aGetResultFuncs)) {
+ aGetResultFuncs = [aGetResultFuncs];
+ }
+
+ let tabSettings = aUseHttps
+ ? [
+ { firstPartyDomain: "https://example.com", userContextId: 1 },
+ { firstPartyDomain: "https://example.org", userContextId: 2 },
+ ]
+ : [
+ { firstPartyDomain: "http://example.com", userContextId: 1 },
+ { firstPartyDomain: "http://example.org", userContextId: 2 },
+ ];
+
+ this._add_task(async function (aMode) {
+ let tabSettingA = 0;
+
+ for (let tabSettingB of [0, 1]) {
+ // Give the test a chance to set up before each case is run.
+ if (aBeforeFunc) {
+ try {
+ await aBeforeFunc(aMode);
+ } catch (e) {
+ ok(false, `Caught error while doing testing setup: ${e}.`);
+ }
+ }
+ // Create Tabs.
+ info(`Create tab A for ${TEST_MODE_NAMES[aMode]} test.`);
+ let tabInfoA = await IsolationTestTools._addTab(
+ aMode,
+ pageURL,
+ tabSettings[tabSettingA],
+ firstFrameSetting
+ );
+ info(`Finished Create tab A for ${TEST_MODE_NAMES[aMode]} test.`);
+ let resultsA = [];
+ if (aGetResultImmediately) {
+ try {
+ info(
+ `Immediately get result from tab A for ${TEST_MODE_NAMES[aMode]} test`
+ );
+ for (let getResultFunc of aGetResultFuncs) {
+ resultsA.push(await getResultFunc(tabInfoA.browser));
+ }
+ } catch (e) {
+ ok(false, `Caught error while getting result from Tab A: ${e}.`);
+ }
+ }
+ info(`Create tab B for ${TEST_MODE_NAMES[aMode]}.`);
+ let tabInfoB = await IsolationTestTools._addTab(
+ aMode,
+ pageURL,
+ tabSettings[tabSettingB],
+ secondFrameSetting
+ );
+ info(`Finished Create tab B for ${TEST_MODE_NAMES[aMode]} test.`);
+ let i = 0;
+ for (let getResultFunc of aGetResultFuncs) {
+ // Fetch results from tabs.
+ info(`Fetching result from tab A for ${TEST_MODE_NAMES[aMode]}.`);
+ let resultA;
+ try {
+ resultA = aGetResultImmediately
+ ? resultsA[i++]
+ : await getResultFunc(tabInfoA.browser);
+ } catch (e) {
+ ok(false, `Caught error while getting result from Tab A: ${e}.`);
+ }
+ info(`Fetching result from tab B for ${TEST_MODE_NAMES[aMode]}.`);
+ let resultB;
+ try {
+ resultB = await getResultFunc(tabInfoB.browser);
+ } catch (e) {
+ ok(false, `Caught error while getting result from Tab B: ${e}.`);
+ }
+ // Compare results.
+ let result = false;
+ let shouldIsolate =
+ aMode !== TEST_MODE_NO_ISOLATION && tabSettingA !== tabSettingB;
+ if (aCompareResultFunc) {
+ result = await aCompareResultFunc(shouldIsolate, resultA, resultB);
+ } else {
+ result = shouldIsolate ? resultA !== resultB : resultA === resultB;
+ }
+
+ let msg =
+ `Result of Testing ${TEST_MODE_NAMES[aMode]} for ` +
+ `isolation ${shouldIsolate ? "on" : "off"} with TabSettingA ` +
+ `${tabSettingA} and tabSettingB ${tabSettingB}` +
+ `, resultA = ${resultA}, resultB = ${resultB}`;
+
+ ok(result, msg);
+ }
+
+ // Close Tabs.
+ BrowserTestUtils.removeTab(tabInfoA.tab);
+ BrowserTestUtils.removeTab(tabInfoB.tab);
+
+ // A workaround for avoiding a timing issue in Fission. This workaround
+ // makes sure that the shutdown process between parent and content
+ // is finished before the next round of testing.
+ if (SpecialPowers.useRemoteSubframes) {
+ await new Promise(resolve => {
+ let observer = (subject, topic, data) => {
+ if (topic === "ipc:content-shutdown") {
+ Services.obs.removeObserver(observer, "ipc:content-shutdown");
+ resolve();
+ }
+ };
+ Services.obs.addObserver(observer, "ipc:content-shutdown");
+ });
+ }
+ }
+ });
+ },
+};
diff --git a/browser/components/originattributes/test/browser/test.html b/browser/components/originattributes/test/browser/test.html
new file mode 100644
index 0000000000..fe5c7a2cf7
--- /dev/null
+++ b/browser/components/originattributes/test/browser/test.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1260931</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script>
+ window.onmessage = function(evt) {
+ if (evt.data != "HI") {
+ return;
+ }
+
+ window.parent.postMessage("OK", "https://example.net");
+ };
+ </script>
+</head>
+<body>
+ Hello World.
+</body>
+</html>
diff --git a/browser/components/originattributes/test/browser/test.js b/browser/components/originattributes/test/browser/test.js
new file mode 100644
index 0000000000..d290af9b06
--- /dev/null
+++ b/browser/components/originattributes/test/browser/test.js
@@ -0,0 +1 @@
+var i = 1;
diff --git a/browser/components/originattributes/test/browser/test.js^headers^ b/browser/components/originattributes/test/browser/test.js^headers^
new file mode 100644
index 0000000000..2ebf93751b
--- /dev/null
+++ b/browser/components/originattributes/test/browser/test.js^headers^
@@ -0,0 +1 @@
+Set-Cookie: test=foo; SameSite=None; Secure;
diff --git a/browser/components/originattributes/test/browser/test2.html b/browser/components/originattributes/test/browser/test2.html
new file mode 100644
index 0000000000..370be15600
--- /dev/null
+++ b/browser/components/originattributes/test/browser/test2.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1260931</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script src="test2.js"></script>
+</head>
+<body>
+ Hello World.
+</body>
+</html>
diff --git a/browser/components/originattributes/test/browser/test2.js b/browser/components/originattributes/test/browser/test2.js
new file mode 100644
index 0000000000..d290af9b06
--- /dev/null
+++ b/browser/components/originattributes/test/browser/test2.js
@@ -0,0 +1 @@
+var i = 1;
diff --git a/browser/components/originattributes/test/browser/test2.js^headers^ b/browser/components/originattributes/test/browser/test2.js^headers^
new file mode 100644
index 0000000000..0fbbf8c3eb
--- /dev/null
+++ b/browser/components/originattributes/test/browser/test2.js^headers^
@@ -0,0 +1 @@
+Set-Cookie: test2=foo; SameSite=None; Secure;
diff --git a/browser/components/originattributes/test/browser/test_firstParty.html b/browser/components/originattributes/test/browser/test_firstParty.html
new file mode 100644
index 0000000000..a2028ba663
--- /dev/null
+++ b/browser/components/originattributes/test/browser/test_firstParty.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8"/>
+ <title>Test for Bug 1260931</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <div>
+ <iframe id="iframe1" src="https://example.com"></iframe>
+ <iframe id="iframe2" sandbox="" src="https://example.com"></iframe>
+ <iframe id="iframe3" sandbox="allow-same-origin" src="https://example.com"></iframe>
+ </div>
+</body>
+</html>
diff --git a/browser/components/originattributes/test/browser/test_firstParty_cookie.html b/browser/components/originattributes/test/browser/test_firstParty_cookie.html
new file mode 100644
index 0000000000..44547c0d75
--- /dev/null
+++ b/browser/components/originattributes/test/browser/test_firstParty_cookie.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1260931</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script src="test.js"></script>
+</head>
+<body>
+ Hello World.
+ <iframe id="iframe1" src="test2.html"></iframe>
+</body>
+</html>
diff --git a/browser/components/originattributes/test/browser/test_firstParty_html_redirect.html b/browser/components/originattributes/test/browser/test_firstParty_html_redirect.html
new file mode 100644
index 0000000000..ac85b1d78a
--- /dev/null
+++ b/browser/components/originattributes/test/browser/test_firstParty_html_redirect.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf8" http-equiv="refresh" content="0; url=https://example.com/"/>
+ <title>Test for Bug 1260931</title>
+</head>
+<body>
+</body>
+</html>
diff --git a/browser/components/originattributes/test/browser/test_firstParty_http_redirect.html b/browser/components/originattributes/test/browser/test_firstParty_http_redirect.html
new file mode 100644
index 0000000000..7b794a011f
--- /dev/null
+++ b/browser/components/originattributes/test/browser/test_firstParty_http_redirect.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8"/>
+ <title>Test for Bug 1260931</title>
+</head>
+<body>
+</body>
+</html>
diff --git a/browser/components/originattributes/test/browser/test_firstParty_http_redirect.html^headers^ b/browser/components/originattributes/test/browser/test_firstParty_http_redirect.html^headers^
new file mode 100644
index 0000000000..ba266f7c68
--- /dev/null
+++ b/browser/components/originattributes/test/browser/test_firstParty_http_redirect.html^headers^
@@ -0,0 +1,2 @@
+HTTP 302 Found
+Location: https://example.com/browser/browser/components/originattributes/test/browser/dummy.html
diff --git a/browser/components/originattributes/test/browser/test_firstParty_http_redirect_to_same_domain.html b/browser/components/originattributes/test/browser/test_firstParty_http_redirect_to_same_domain.html
new file mode 100644
index 0000000000..7b794a011f
--- /dev/null
+++ b/browser/components/originattributes/test/browser/test_firstParty_http_redirect_to_same_domain.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8"/>
+ <title>Test for Bug 1260931</title>
+</head>
+<body>
+</body>
+</html>
diff --git a/browser/components/originattributes/test/browser/test_firstParty_http_redirect_to_same_domain.html^headers^ b/browser/components/originattributes/test/browser/test_firstParty_http_redirect_to_same_domain.html^headers^
new file mode 100644
index 0000000000..138d6102c1
--- /dev/null
+++ b/browser/components/originattributes/test/browser/test_firstParty_http_redirect_to_same_domain.html^headers^
@@ -0,0 +1,2 @@
+HTTP 302 Found
+Location: https://example.net/browser/browser/components/originattributes/test/browser/dummy.html
diff --git a/browser/components/originattributes/test/browser/test_firstParty_iframe_http_redirect.html b/browser/components/originattributes/test/browser/test_firstParty_iframe_http_redirect.html
new file mode 100644
index 0000000000..fd7df46c15
--- /dev/null
+++ b/browser/components/originattributes/test/browser/test_firstParty_iframe_http_redirect.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8"/>
+ <title>Test for Bug 1260931</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <div>
+ <iframe id="iframe1" src="test_firstParty_http_redirect.html"></iframe>
+ </div>
+</body>
+</html>
diff --git a/browser/components/originattributes/test/browser/test_firstParty_postMessage.html b/browser/components/originattributes/test/browser/test_firstParty_postMessage.html
new file mode 100644
index 0000000000..a056909e37
--- /dev/null
+++ b/browser/components/originattributes/test/browser/test_firstParty_postMessage.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8"/>
+ <title>Test for Bug 1260931</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<script>
+function onload() {
+ let iframe1 = document.getElementById("iframe1");
+ iframe1.contentWindow.postMessage("HI", "https://example.net");
+}
+
+window.onmessage = function(evt) {
+ document.getElementById("message").textContent = evt.data;
+
+ let iframe2 = document.createElement("iframe");
+ iframe2.src = "dummy.html";
+ document.body.appendChild(iframe2);
+};
+</script>
+<body onload="onload()">
+ <div>
+ <iframe id="iframe1" src="test.html"></iframe>
+ <span id="message"></span>
+ </div>
+</body>
+</html>
diff --git a/browser/components/originattributes/test/browser/test_form.html b/browser/components/originattributes/test/browser/test_form.html
new file mode 100644
index 0000000000..db1b900e8b
--- /dev/null
+++ b/browser/components/originattributes/test/browser/test_form.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1260931</title>
+</head>
+<body>
+<form action="test_firstParty_http_redirect_to_same_domain.html" method="POST">
+ First name: <input type="text" name="fname"><br>
+ Last name: <input type="text" name="lname"><br>
+ <input type="submit" id="submit" value="Submit">
+</form>
+</body>
+</html>
diff --git a/browser/components/originattributes/test/browser/window.html b/browser/components/originattributes/test/browser/window.html
new file mode 100644
index 0000000000..efcda61c10
--- /dev/null
+++ b/browser/components/originattributes/test/browser/window.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
+<html>
+ <head>
+ <meta charset="utf8">
+ <title>Page creating a popup</title>
+ </head>
+ <body>
+ <script type="text/javascript">
+ </script>
+ </body>
+</html>
diff --git a/browser/components/originattributes/test/browser/window2.html b/browser/components/originattributes/test/browser/window2.html
new file mode 100644
index 0000000000..955d2cebb0
--- /dev/null
+++ b/browser/components/originattributes/test/browser/window2.html
@@ -0,0 +1,11 @@
+<html>
+ <head>
+ <meta charset="utf8">
+ <title>Page creating a popup</title>
+ </head>
+ <body>
+ <script type="text/javascript">
+ var w = window.open("https://example.com/browser/browser/components/originattributes/test/browser/test_firstParty.html", "test");
+ </script>
+ </body>
+</html>
diff --git a/browser/components/originattributes/test/browser/window3.html b/browser/components/originattributes/test/browser/window3.html
new file mode 100644
index 0000000000..92b473aa9d
--- /dev/null
+++ b/browser/components/originattributes/test/browser/window3.html
@@ -0,0 +1,11 @@
+<html>
+ <head>
+ <meta charset="utf8">
+ <title>Page creating a popup</title>
+ </head>
+ <body>
+ <script type="text/javascript">
+ var w = window.open("https://example.com/browser/browser/components/originattributes/test/browser/test_form.html", "test");
+ </script>
+ </body>
+</html>
diff --git a/browser/components/originattributes/test/browser/window_redirect.html b/browser/components/originattributes/test/browser/window_redirect.html
new file mode 100644
index 0000000000..37beed9101
--- /dev/null
+++ b/browser/components/originattributes/test/browser/window_redirect.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
+<html>
+ <head>
+ <meta charset="utf8">
+ <title>Page creating a popup</title>
+ </head>
+ <body>
+ <script type="text/javascript">
+ var w = window.open("test_firstParty_http_redirect_to_same_domain.html", "test");
+ </script>
+ </body>
+</html>
diff --git a/browser/components/originattributes/test/mochitest/file_empty.html b/browser/components/originattributes/test/mochitest/file_empty.html
new file mode 100644
index 0000000000..15648ec5aa
--- /dev/null
+++ b/browser/components/originattributes/test/mochitest/file_empty.html
@@ -0,0 +1,2 @@
+<h1>I'm just a support file</h1>
+<p>I get loaded to do permission testing.</p>
diff --git a/browser/components/originattributes/test/mochitest/mochitest.toml b/browser/components/originattributes/test/mochitest/mochitest.toml
new file mode 100644
index 0000000000..6db84ec300
--- /dev/null
+++ b/browser/components/originattributes/test/mochitest/mochitest.toml
@@ -0,0 +1,6 @@
+[DEFAULT]
+skip-if = ["os == 'android'"] # bug 1730213
+support-files = ["file_empty.html"]
+
+["test_permissions_api.html"]
+skip-if = ["xorigin"] # Hangs
diff --git a/browser/components/originattributes/test/mochitest/test_permissions_api.html b/browser/components/originattributes/test/mochitest/test_permissions_api.html
new file mode 100644
index 0000000000..56acae77e8
--- /dev/null
+++ b/browser/components/originattributes/test/mochitest/test_permissions_api.html
@@ -0,0 +1,176 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title>Test for Permissions API</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+
+<body>
+ <pre id="test"></pre>
+ <script type="application/javascript">
+ "use strict";
+
+ const {
+ UNKNOWN_ACTION,
+ PROMPT_ACTION,
+ ALLOW_ACTION,
+ DENY_ACTION,
+ } = SpecialPowers.Ci.nsIPermissionManager;
+
+ SimpleTest.waitForExplicitFinish();
+
+ const PERMISSIONS = [{
+ name: "geolocation",
+ type: "geo",
+ }, {
+ name: "notifications",
+ type: "desktop-notification",
+ }, {
+ name: "push",
+ type: "desktop-notification",
+ }, {
+ name: "persistent-storage",
+ type: "persistent-storage",
+ }, {
+ name: "midi",
+ type: "midi",
+ } ];
+
+ const UNSUPPORTED_PERMISSIONS = [
+ "foobarbaz", // Not in spec, for testing only.
+ ];
+
+ // Create a closure, so that tests are run on the correct window object.
+ function createPermissionTester(aWindow) {
+ return {
+ setPermissions(allow) {
+ const permissions = PERMISSIONS.map(({ type }) => {
+ return {
+ type,
+ allow,
+ "context": aWindow.document,
+ };
+ });
+ return new Promise((resolve) => {
+ SpecialPowers.popPermissions(() => {
+ SpecialPowers.pushPermissions(permissions, resolve);
+ });
+ });
+ },
+ checkPermissions(state) {
+ const promisesToQuery = PERMISSIONS.map(({ name }) => {
+ return aWindow.navigator.permissions
+ .query({ name })
+ .then(
+ () => is(state, state, `correct state for '${name}'`),
+ () => ok(false, `query should not have rejected for '${name}'`)
+ );
+ });
+ return Promise.all(promisesToQuery);
+ },
+ checkUnsupportedPermissions() {
+ const promisesToQuery = UNSUPPORTED_PERMISSIONS.map(({ name }) => {
+ return aWindow.navigator.permissions
+ .query({ name })
+ .then(
+ () => ok(false, `query should not have resolved for '${name}'`),
+ error => {
+ is(error.name, "TypeError",
+ `query should have thrown TypeError for '${name}'`);
+ }
+ );
+ });
+ return Promise.all(promisesToQuery);
+ },
+ promiseStateChanged(name, state) {
+ return aWindow.navigator.permissions
+ .query({ name })
+ .then(status => {
+ return new Promise( resolve => {
+ status.onchange = () => {
+ status.onchange = null;
+ is(status.state, state, `state changed for '${name}'`);
+ resolve();
+ };
+ });
+ },
+ () => ok(false, `query should not have rejected for '${name}'`));
+ },
+ testStatusOnChange() {
+ return new Promise((resolve) => {
+ SpecialPowers.popPermissions(() => {
+ const permission = "geolocation";
+ const promiseGranted = this.promiseStateChanged(permission, "granted");
+ this.setPermissions(ALLOW_ACTION);
+ promiseGranted.then(async () => {
+ const promisePrompt = this.promiseStateChanged(permission, "prompt");
+ await SpecialPowers.popPermissions();
+ return promisePrompt;
+ }).then(resolve);
+ });
+ });
+ },
+ testInvalidQuery() {
+ return aWindow.navigator.permissions
+ .query({ name: "invalid" })
+ .then(
+ () => ok(false, "invalid query should not have resolved"),
+ () => ok(true, "invalid query should have rejected")
+ );
+ },
+ };
+ }
+
+ function enablePrefs() {
+ const ops = {
+ "set": [
+ ["privacy.firstparty.isolate", true],
+ ],
+ };
+ return SpecialPowers.pushPrefEnv(ops);
+ }
+
+ function createIframe() {
+ return new Promise((resolve) => {
+ const iframe = document.createElement("iframe");
+ iframe.src = "file_empty.html";
+ iframe.onload = () => resolve(iframe.contentWindow);
+ document.body.appendChild(iframe);
+ });
+ }
+
+ window.onload = () => {
+ enablePrefs()
+ .then(createIframe)
+ .then(createPermissionTester)
+ .then((tester) => {
+ return tester
+ .checkUnsupportedPermissions()
+ .then(() => tester.setPermissions(UNKNOWN_ACTION))
+ .then(() => tester.checkPermissions("prompt"))
+ .then(() => tester.setPermissions(PROMPT_ACTION))
+ .then(() => tester.checkPermissions("prompt"))
+ .then(() => tester.setPermissions(ALLOW_ACTION))
+ .then(() => tester.checkPermissions("granted"))
+ .then(() => tester.setPermissions(DENY_ACTION))
+ .then(() => tester.checkPermissions("denied"))
+ .then(() => tester.testStatusOnChange())
+ .then(() => tester.testInvalidQuery())
+ })
+ .then(SimpleTest.finish)
+ .catch((e) => {
+ ok(false, `Unexpected error ${e}`);
+ SimpleTest.finish();
+ });
+ };
+ </script>
+</body>
+
+</html>