summaryrefslogtreecommitdiffstats
path: root/browser/components/originattributes
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /browser/components/originattributes
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'browser/components/originattributes')
-rw-r--r--browser/components/originattributes/moz.build16
-rw-r--r--browser/components/originattributes/test/browser/.eslintrc.js7
-rw-r--r--browser/components/originattributes/test/browser/browser.ini64
-rw-r--r--browser/components/originattributes/test/browser/browser_blobURLIsolation.js97
-rw-r--r--browser/components/originattributes/test/browser/browser_broadcastChannel.js47
-rw-r--r--browser/components/originattributes/test/browser/browser_cache.js259
-rw-r--r--browser/components/originattributes/test/browser/browser_clientAuth.js44
-rw-r--r--browser/components/originattributes/test/browser/browser_cookieIsolation.js31
-rw-r--r--browser/components/originattributes/test/browser/browser_favicon_firstParty.js343
-rw-r--r--browser/components/originattributes/test/browser/browser_favicon_userContextId.js257
-rw-r--r--browser/components/originattributes/test/browser/browser_firstPartyIsolation.js174
-rw-r--r--browser/components/originattributes/test/browser/browser_httpauth.js54
-rw-r--r--browser/components/originattributes/test/browser/browser_imageCacheIsolation.js80
-rw-r--r--browser/components/originattributes/test/browser/browser_localStorageIsolation.js24
-rw-r--r--browser/components/originattributes/test/browser/browser_sharedworker.js26
-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.html25
-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_sharedworker.html10
-rw-r--r--browser/components/originattributes/test/browser/file_sharedworker.js9
-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.fetch.html8
-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.js9
-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/head.js365
-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_iframe_http_redirect.html13
-rw-r--r--browser/components/originattributes/test/browser/test_firstParty_postMessage.html28
-rw-r--r--browser/components/originattributes/test/browser/window.html13
-rw-r--r--browser/components/originattributes/test/browser/worker_blobify.js11
-rw-r--r--browser/components/originattributes/test/browser/worker_deblobify.js31
-rw-r--r--browser/components/originattributes/test/mochitest/file_empty.html2
-rw-r--r--browser/components/originattributes/test/mochitest/mochitest.ini5
-rw-r--r--browser/components/originattributes/test/mochitest/test_permissions_api.html207
66 files changed, 2500 insertions, 0 deletions
diff --git a/browser/components/originattributes/moz.build b/browser/components/originattributes/moz.build
new file mode 100644
index 000000000..ea5943ea1
--- /dev/null
+++ b/browser/components/originattributes/moz.build
@@ -0,0 +1,16 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+BROWSER_CHROME_MANIFESTS += [
+ 'test/browser/browser.ini',
+]
+
+MOCHITEST_MANIFESTS += [
+ 'test/mochitest/mochitest.ini'
+]
+
+with Files('**'):
+ BUG_COMPONENT = ('Firefox', 'OriginAttributes')
diff --git a/browser/components/originattributes/test/browser/.eslintrc.js b/browser/components/originattributes/test/browser/.eslintrc.js
new file mode 100644
index 000000000..7c8021192
--- /dev/null
+++ b/browser/components/originattributes/test/browser/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
diff --git a/browser/components/originattributes/test/browser/browser.ini b/browser/components/originattributes/test/browser/browser.ini
new file mode 100644
index 000000000..61f674377
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser.ini
@@ -0,0 +1,64 @@
+[DEFAULT]
+tags = usercontextid firstpartyisolation originattributes
+support-files =
+ 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_sharedworker.html
+ file_sharedworker.js
+ file_thirdPartyChild.audio.ogg
+ file_thirdPartyChild.embed.png
+ file_thirdPartyChild.fetch.html
+ file_thirdPartyChild.iframe.html
+ 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
+ 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_iframe_http_redirect.html
+ test_firstParty_postMessage.html
+ window.html
+ worker_blobify.js
+ worker_deblobify.js
+
+[browser_broadcastChannel.js]
+[browser_cache.js]
+[browser_cookieIsolation.js]
+[browser_favicon_firstParty.js]
+[browser_favicon_userContextId.js]
+[browser_firstPartyIsolation.js]
+[browser_localStorageIsolation.js]
+[browser_blobURLIsolation.js]
+[browser_imageCacheIsolation.js]
+[browser_sharedworker.js]
+[browser_httpauth.js]
+[browser_clientAuth.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 000000000..1d1b7c8e1
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_blobURLIsolation.js
@@ -0,0 +1,97 @@
+/**
+ * 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 = "worker_blobify.js";
+const SCRIPT_WORKER_DEBLOBIFY = "worker_deblobify.js";
+
+function page_blobify(browser, input) {
+ return ContentTask.spawn(browser, input, function(input) {
+ return { blobURL: content.URL.createObjectURL(new content.Blob([input])) };
+ });
+}
+
+function page_deblobify(browser, blobURL) {
+ return ContentTask.spawn(browser, blobURL, function* (blobURL) {
+ if ("error" in blobURL) {
+ return blobURL;
+ }
+ blobURL = blobURL.blobURL;
+
+ function blobURLtoBlob(blobURL) {
+ return new content.Promise(function (resolve) {
+ let xhr = new content.XMLHttpRequest();
+ xhr.open("GET", blobURL, 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 = yield blobURLtoBlob(blobURL);
+ if (blob == "xhr error") {
+ return "xhr error";
+ }
+
+ return yield blobToString(blob);
+ });
+}
+
+function workerIO(browser, scriptFile, message) {
+ return ContentTask.spawn(browser, {scriptFile, message}, function* (args) {
+ let worker = new content.Worker(args.scriptFile);
+ let promise = new content.Promise(function(resolve) {
+ let listenFunction = function(event) {
+ worker.removeEventListener("message", listenFunction, false);
+ worker.terminate();
+ resolve(event.data);
+ };
+ worker.addEventListener("message", listenFunction, false);
+ });
+ worker.postMessage(args.message);
+ return yield promise;
+ });
+}
+
+let worker_blobify = (browser, input) => workerIO(browser, SCRIPT_WORKER_BLOBIFY, input);
+let worker_deblobify = (browser, blobURL) => workerIO(browser, SCRIPT_WORKER_DEBLOBIFY, blobURL);
+
+function doTest(blobify, deblobify) {
+ let blobURL = null;
+ return function* (browser) {
+ if (blobURL === null) {
+ let input = Math.random().toString();
+ blobURL = yield blobify(browser, input);
+ return input;
+ }
+ let result = yield 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));
+ }
+}
+
+IsolationTestTools.runTests(TEST_PAGE, tests);
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 000000000..3a2bd7405
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_broadcastChannel.js
@@ -0,0 +1,47 @@
+/*
+ * 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";
+
+function* doTest(aBrowser) {
+ let response = yield ContentTask.spawn(aBrowser, null, 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 = yield new Promise(resolve => {
+ let listenFunc = event => {
+ content.removeEventListener("message", listenFunc);
+ resolve(event.data);
+ };
+
+ let bc = new content.BroadcastChannel("testBroadcastChannel");
+
+ content.addEventListener("message", listenFunc, false);
+ bc.postMessage(data);
+ });
+
+ is(receivedData, data, "The value should be the same.");
+
+ return receivedData;
+ }
+
+ return displayItem.innerHTML;
+ });
+
+ return response;
+}
+
+IsolationTestTools.runTests(TEST_PAGE, doTest);
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 000000000..d5f3a8f58
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_cache.js
@@ -0,0 +1,259 @@
+/*
+ * 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 { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu } = Components;
+
+let {LoadContextInfo} = Cu.import("resource://gre/modules/LoadContextInfo.jsm", {});
+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"];
+
+// 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: uri,
+ idEnhance: idEnhance });
+ },
+ onCacheEntryVisitCompleted() {
+ resolve(cacheEntries);
+ },
+ QueryInterface(iid) {
+ if (iid.equals(Ci.nsICacheStorageVisitor))
+ return this;
+
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ }
+ };
+ // 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, false);
+ 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 : function (aProxyService, aChannel, aProxy) {
+ // We have the channel; provide it to the callback.
+ onChannel(aChannel);
+ // Pass on aProxy unmodified.
+ return 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.
+function* doInit(aMode) {
+ yield SpecialPowers.pushPrefEnv({"set": [["network.predictor.enabled", false],
+ ["network.predictor.enable-prefetch", false]]});
+ clearAllImageCaches();
+
+ let networkCache = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Ci.nsICacheStorageService);
+ networkCache.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.
+function* doTest(aBrowser) {
+
+ let argObj = {
+ randomSuffix: randomSuffix,
+ urlPrefix: TEST_DOMAIN + TEST_PATH,
+ };
+
+ yield ContentTask.spawn(aBrowser, argObj, function* (arg) {
+ 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.
+ yield new Promise(resolve => {
+ let audioLoaded = false;
+ let trackLoaded = false;
+
+ let audioListener = () => {
+ audio.removeEventListener("canplaythrough", audioListener);
+
+ audioLoaded = true;
+ if (audioLoaded && trackLoaded) {
+ resolve();
+ }
+ };
+
+ let trackListener = () => {
+ audioTrack.removeEventListener("load", trackListener);
+
+ trackLoaded = true;
+ if (audioLoaded && trackLoaded) {
+ resolve();
+ }
+ };
+
+ // Add the event listeners before everything in case we lose events.
+ audioTrack.addEventListener("load", trackListener, false);
+ audio.addEventListener("canplaythrough", audioListener, false);
+
+ // Assign attributes for the audio element.
+ audioSource.setAttribute("src", audioURL + URLSuffix);
+ audioSource.setAttribute("type", "audio/ogg");
+ audioTrack.setAttribute("src", trackURL);
+ audioTrack.setAttribute("kind", "subtitles");
+
+ 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.
+ yield new Promise(resolve => {
+ let listener = () => {
+ video.removeEventListener("canplaythrough", listener);
+ resolve();
+ };
+
+ // Add the event listener before everything in case we lose the event.
+ video.addEventListener("canplaythrough", listener, false);
+
+ // 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.
+function* doCheck(aShouldIsolate, aInputA, aInputB) {
+ let expectedEntryCount = 1;
+ let data = [];
+ data = data.concat(yield cacheDataForContext(LoadContextInfo.default));
+ data = data.concat(yield cacheDataForContext(LoadContextInfo.private));
+ data = data.concat(yield cacheDataForContext(LoadContextInfo.custom(true, {})));
+ data = data.concat(yield cacheDataForContext(LoadContextInfo.custom(false, { userContextId: 1 })));
+ data = data.concat(yield cacheDataForContext(LoadContextInfo.custom(true, { userContextId: 1 })));
+ data = data.concat(yield cacheDataForContext(LoadContextInfo.custom(false, { userContextId: 2 })));
+ data = data.concat(yield cacheDataForContext(LoadContextInfo.custom(true, { userContextId: 2 })));
+ data = data.concat(yield cacheDataForContext(LoadContextInfo.custom(false, { firstPartyDomain: "example.com" })));
+ data = data.concat(yield cacheDataForContext(LoadContextInfo.custom(true, { firstPartyDomain: "example.com" })));
+ data = data.concat(yield cacheDataForContext(LoadContextInfo.custom(false, { firstPartyDomain: "example.org" })));
+ data = data.concat(yield cacheDataForContext(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_clientAuth.js b/browser/components/originattributes/test/browser/browser_clientAuth.js
new file mode 100644
index 000000000..48961dce0
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_clientAuth.js
@@ -0,0 +1,44 @@
+let certCached = true;
+let secondTabStarted = false;
+
+function onCertDialogLoaded(subject) {
+ certCached = false;
+ // Click OK.
+ subject.acceptDialog();
+}
+
+Services.obs.addObserver(onCertDialogLoaded, "cert-dialog-loaded", false);
+
+registerCleanupFunction(() => {
+ Services.obs.removeObserver(onCertDialogLoaded, "cert-dialog-loaded");
+});
+
+function* setup() {
+ yield SpecialPowers.pushPrefEnv({
+ set: [["security.default_personal_cert", "Ask Every Time"]]
+ });
+}
+
+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 000000000..6259723ba
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_cookieIsolation.js
@@ -0,0 +1,31 @@
+/**
+ * Bug 1312541 - A test case for document.cookie 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 = "key" + Math.random().toString();
+const re = new RegExp(key + "=([0-9\.]+)");
+
+// Define the testing function
+function* doTest(aBrowser) {
+ return yield ContentTask.spawn(aBrowser, {key, re},
+ function ({key, re}) {
+ let result = re.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 = key + "=" + value;
+ return value;
+ });
+}
+
+registerCleanupFunction(() => {
+ Services.cookies.removeAll();
+});
+
+IsolationTestTools.runTests(TEST_PAGE, doTest);
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 000000000..b3a18947b
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_favicon_firstParty.js
@@ -0,0 +1,343 @@
+/**
+ * Bug 1277803 - A test case for testing favicon loading across different first party domains.
+ */
+
+const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/PlacesUtils.jsm");
+
+const FIRST_PARTY_ONE = "example.com";
+const FIRST_PARTY_TWO = "example.org";
+const THIRD_PARTY = "mochi.test:8888";
+
+const TEST_SITE_ONE = "http://" + FIRST_PARTY_ONE;
+const TEST_SITE_TWO = "http://" + FIRST_PARTY_TWO;
+const THIRD_PARTY_SITE = "http://" + 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";
+
+let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
+let makeURI = Cu.import("resource://gre/modules/BrowserUtils.jsm", {}).BrowserUtils.makeURI;
+
+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", false);
+ }
+ }
+ };
+
+ Services.obs.addObserver(observer, "places-favicons-expired", false);
+ faviconService.expireAllFavicons();
+ });
+}
+
+function observeFavicon(aFirstPartyDomain, aExpectedCookie, aPageURI) {
+ let faviconReqXUL = false;
+ let faviconReqPlaces = false;
+ let expectedPrincipal = Services.scriptSecurityManager
+ .createCodebasePrincipal(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");
+
+ if (loadingPrincipal.equals(systemPrincipal)) {
+ faviconReqXUL = true;
+ ok(triggeringPrincipal.equals(expectedPrincipal),
+ "The triggeringPrincipal of favicon loading from XUL should be the content principal.");
+ } else {
+ faviconReqPlaces = true;
+ ok(loadingPrincipal.equals(expectedPrincipal),
+ "The loadingPrincipal of favicon loading from Places 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);
+ }
+
+ if (faviconReqXUL && faviconReqPlaces) {
+ Services.obs.removeObserver(observer, "http-on-modify-request", false);
+ resolve();
+ }
+ }
+ };
+
+ Services.obs.addObserver(observer, "http-on-modify-request", false);
+ });
+}
+
+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", false);
+ Services.obs.removeObserver(observer, "http-on-examine-cached-response", false);
+ }
+ }
+ };
+
+ Services.obs.addObserver(observer, "http-on-examine-response", false);
+ Services.obs.addObserver(observer, "http-on-examine-cached-response", false);
+ });
+}
+
+function waitOnFaviconLoaded(aFaviconURL) {
+ return new Promise(resolve => {
+ let observer = {
+ onPageChanged(uri, attr, value, id) {
+
+ if (attr === Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON &&
+ value === aFaviconURL) {
+ resolve();
+ PlacesUtils.history.removeObserver(observer, false);
+ }
+ },
+ };
+
+ PlacesUtils.history.addObserver(observer, false);
+ });
+}
+
+function* openTab(aURL) {
+ let tab = gBrowser.addTab(aURL);
+
+ // Select tab and make sure its browser is focused.
+ gBrowser.selectedTab = tab;
+ tab.ownerGlobal.focus();
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ yield BrowserTestUtils.browserLoaded(browser);
+ return {tab, browser};
+}
+
+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 = yield openTabInFirstParty(aURL, aFirstParty);
+
+ // Add cookies into the iframe.
+ yield ContentTask.spawn(tabInfo.browser, aCookieValue, function* (value) {
+ content.document.cookie = value;
+ });
+
+ yield BrowserTestUtils.removeTab(tabInfo.tab);
+}
+
+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;
+ }
+
+ yield assignCookiesUnderFirstParty(firstSiteURL, TEST_SITE_ONE, cookies[0]);
+ yield assignCookiesUnderFirstParty(secondSiteURL, TEST_SITE_TWO, cookies[1]);
+
+ return cookies;
+}
+
+function* doTest(aTestPage, aExpectedCookies, aFaviconURL) {
+ let firstPageURI = makeURI(TEST_SITE_ONE + aTestPage);
+ let secondPageURI = makeURI(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 = yield openTab(TEST_SITE_ONE + aTestPage);
+
+ // Waiting until favicon requests are all made.
+ yield promiseObserveFavicon;
+
+ // Waiting until favicon loaded.
+ yield promiseFaviconLoaded;
+
+ // Close the tab.
+ yield BrowserTestUtils.removeTab(tabInfo.tab);
+
+ // 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 = yield openTab(TEST_SITE_TWO + aTestPage);
+
+ // Waiting until favicon requests are all made.
+ yield promiseObserveFavicon;
+
+ yield BrowserTestUtils.removeTab(tabInfo.tab);
+}
+
+add_task(function* setup() {
+ // Make sure first party isolation is enabled.
+ yield SpecialPowers.pushPrefEnv({"set": [
+ ["privacy.firstparty.isolate", true]
+ ]});
+});
+
+// A clean up function to prevent affecting other tests.
+registerCleanupFunction(() => {
+ // Clear all cookies.
+ let cookieMgr = Cc["@mozilla.org/cookiemanager;1"]
+ .getService(Ci.nsICookieManager);
+ cookieMgr.removeAll();
+
+ // Clear all image caches and network caches.
+ clearAllImageCaches();
+
+ let networkCache = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Ci.nsICacheStorageService);
+ networkCache.clear();
+});
+
+add_task(function* test_favicon_firstParty() {
+ for (let testThirdParty of [false, true]) {
+ // Clear all image caches and network caches before running the test.
+ clearAllImageCaches();
+
+ let networkCache = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Ci.nsICacheStorageService);
+ networkCache.clear();
+
+ // Clear Places favicon caches.
+ yield clearAllPlacesFavicons();
+
+ let cookies = yield generateCookies(testThirdParty);
+
+ if (testThirdParty) {
+ yield doTest(TEST_THIRD_PARTY_PAGE, cookies, THIRD_PARTY_SITE + FAVICON_URI);
+ } else {
+ yield doTest(TEST_PAGE, cookies, TEST_SITE_ONE + FAVICON_URI);
+ }
+ }
+});
+
+add_task(function* test_favicon_cache_firstParty() {
+ // Clear all image caches and network caches before running the test.
+ clearAllImageCaches();
+
+ let networkCache = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Ci.nsICacheStorageService);
+ networkCache.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 = yield openTab(TEST_SITE_ONE + TEST_CACHE_PAGE);
+
+ // Waiting for the favicon response.
+ let response = yield 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.
+ yield promiseForFaviconLoaded;
+
+ // Open the tab again for checking the image cache is working correctly.
+ let tabInfoB = yield openTab(TEST_SITE_ONE + TEST_CACHE_PAGE);
+
+ // Start to observe the favicon response, the second tab actually will not
+ // make any network request since the favicon will be loaded by the cache for
+ // both Places and XUL image. So 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 tabInfoC = yield 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 = yield 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.");
+
+ yield BrowserTestUtils.removeTab(tabInfoA.tab);
+ yield BrowserTestUtils.removeTab(tabInfoB.tab);
+ yield BrowserTestUtils.removeTab(tabInfoC.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 000000000..507e0a6d4
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_favicon_userContextId.js
@@ -0,0 +1,257 @@
+/**
+ * Bug 1277803 - A test caes for testing favicon loading across different userContextId.
+ */
+
+const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu } = Components;
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+
+const TEST_SITE = "http://example.net";
+const TEST_THIRD_PARTY_SITE = "http://mochi.test:8888";
+
+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 = "http://example.com/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();
+let makeURI = Cu.import("resource://gre/modules/BrowserUtils.jsm", {}).BrowserUtils.makeURI;
+
+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", false);
+ }
+ }
+ };
+
+ Services.obs.addObserver(observer, "places-favicons-expired", false);
+ 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;
+ let 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");
+
+ if (loadingPrincipal.equals(systemPrincipal)) {
+ this._faviconReqXUL = true;
+ ok(triggeringPrincipal.equals(this._expectedPrincipal),
+ "The triggeringPrincipal of favicon loading from XUL should be the content principal.");
+ } else {
+ this._faviconReqPlaces = true;
+ ok(loadingPrincipal.equals(this._expectedPrincipal),
+ "The loadingPrincipal of favicon loading from Places 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);
+ }
+
+ if (this._faviconReqXUL && this._faviconReqPlaces) {
+ this._faviconLoaded.resolve();
+ }
+ },
+
+ reset(aUserContextId, aExpectedCookie, aPageURI, aFaviconURL) {
+ this._curUserContextId = aUserContextId;
+ this._expectedCookie = aExpectedCookie;
+ this._expectedPrincipal = Services.scriptSecurityManager
+ .createCodebasePrincipal(aPageURI, { userContextId: aUserContextId });
+ this._faviconReqXUL = false;
+ this._faviconReqPlaces = false;
+ this._faviconURL = aFaviconURL;
+ this._faviconLoaded = new Promise.defer();
+ },
+
+ get promise() {
+ return this._faviconLoaded.promise;
+ }
+};
+
+function waitOnFaviconLoaded(aFaviconURL) {
+ return new Promise(resolve => {
+ let observer = {
+ onPageChanged(uri, attr, value, id) {
+
+ if (attr === Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON &&
+ value === aFaviconURL) {
+ resolve();
+ PlacesUtils.history.removeObserver(observer, false);
+ }
+ },
+ };
+
+ PlacesUtils.history.addObserver(observer, false);
+ });
+}
+
+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 = yield openTabInUserContext(aHost, USER_CONTEXT_ID_PERSONAL);
+ let tabInfoB = yield openTabInUserContext(aHost, USER_CONTEXT_ID_WORK);
+
+ yield ContentTask.spawn(tabInfoA.browser, cookies[0], function* (value) {
+ content.document.cookie = value;
+ });
+
+ yield ContentTask.spawn(tabInfoB.browser, cookies[1], function* (value) {
+ content.document.cookie = value;
+ });
+
+ yield BrowserTestUtils.removeTab(tabInfoA.tab);
+ yield BrowserTestUtils.removeTab(tabInfoB.tab);
+
+ return cookies;
+}
+
+function* doTest(aTestPage, aFaviconHost, aFaviconURL) {
+ let cookies = yield 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", false);
+
+ // Open the tab with the personal container.
+ let tabInfo = yield openTabInUserContext(aTestPage, USER_CONTEXT_ID_PERSONAL);
+
+ // Waiting for favicon requests are all made.
+ yield observer.promise;
+ // Waiting for favicon loaded.
+ yield promiseWaitOnFaviconLoaded;
+
+ // Close the tab.
+ yield BrowserTestUtils.removeTab(tabInfo.tab);
+
+ // Reset the observer for observing requests for the work container.
+ observer.reset(USER_CONTEXT_ID_WORK, cookies[1], pageURI, aFaviconURL);
+ tabInfo = yield openTabInUserContext(aTestPage, USER_CONTEXT_ID_WORK);
+
+ // Waiting for favicon requests are all made.
+ yield observer.promise;
+
+ Services.obs.removeObserver(observer, "http-on-modify-request", false);
+
+ yield BrowserTestUtils.removeTab(tabInfo.tab);
+}
+
+add_task(function* setup() {
+ // Make sure userContext is enabled.
+ yield SpecialPowers.pushPrefEnv({"set": [
+ ["privacy.userContext.enabled", true]
+ ]});
+});
+
+// A clean up function to prevent affecting other tests.
+registerCleanupFunction(() => {
+ // Clear all cookies.
+ let cookieMgr = Cc["@mozilla.org/cookiemanager;1"]
+ .getService(Ci.nsICookieManager);
+ cookieMgr.removeAll();
+
+ // Clear all image caches and network caches.
+ clearAllImageCaches();
+
+ let networkCache = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Ci.nsICacheStorageService);
+ networkCache.clear();
+
+ // Clear Places favicon caches.
+ clearAllPlacesFavicons();
+});
+
+add_task(function* test_favicon_userContextId() {
+ // Clear all image caches before running the test.
+ clearAllImageCaches();
+
+ // Clear all network caches.
+ let networkCache = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Ci.nsICacheStorageService);
+ networkCache.clear();
+
+ // Clear Places favicon caches.
+ yield clearAllPlacesFavicons();
+
+ yield doTest(TEST_PAGE, TEST_SITE, FAVICON_URI);
+});
+
+add_task(function* test_thirdPartyFavicon_userContextId() {
+ // Clear all image caches before running the test.
+ clearAllImageCaches();
+
+ // Clear all network caches.
+ let networkCache = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Ci.nsICacheStorageService);
+ networkCache.clear();
+
+ // Clear Places favicon caches.
+ yield clearAllPlacesFavicons();
+
+ yield doTest(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 000000000..ddda6afae
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_firstPartyIsolation.js
@@ -0,0 +1,174 @@
+const BASE_URL = "http://mochi.test:8888/browser/browser/components/originattributes/test/browser/";
+const BASE_DOMAIN = "mochi.test";
+
+add_task(function* setup() {
+ Services.prefs.setBoolPref("privacy.firstparty.isolate", true);
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("privacy.firstparty.isolate");
+ });
+});
+
+/**
+ * Test for the top-level document and child iframes should have the
+ * firstPartyDomain attribute.
+ */
+add_task(function* principal_test() {
+ let tab = gBrowser.addTab(BASE_URL + "test_firstParty.html");
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser, true, function (url) {
+ return url == BASE_URL + "test_firstParty.html";
+ });
+
+ yield ContentTask.spawn(tab.linkedBrowser, { firstPartyDomain: BASE_DOMAIN }, function* (attrs) {
+ info("document principal: " + content.document.nodePrincipal.origin);
+ Assert.equal(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);
+ info("iframe principal: " + iframe.contentDocument.nodePrincipal.origin);
+ Assert.equal(iframe.frameLoader.docShell.getOriginAttributes().firstPartyDomain,
+ attrs.firstPartyDomain, "iframe's docshell should have firstPartyDomain");
+ Assert.equal(iframe.contentDocument.nodePrincipal.originAttributes.firstPartyDomain,
+ attrs.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(function* cookie_test() {
+ let tab = gBrowser.addTab(BASE_URL + "test_firstParty_cookie.html");
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser, true);
+
+ let iter = Services.cookies.enumerator;
+ let count = 0;
+ while (iter.hasMoreElements()) {
+ count++;
+ let cookie = iter.getNext().QueryInterface(Ci.nsICookie2);
+ 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(function* redirect_test() {
+ let tab = gBrowser.addTab(BASE_URL + "test_firstParty_http_redirect.html");
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ yield ContentTask.spawn(tab.linkedBrowser, { firstPartyDomain: "example.com" }, function* (attrs) {
+ info("document principal: " + content.document.nodePrincipal.origin);
+ info("document uri: " + content.document.documentURI);
+
+ Assert.equal(content.document.documentURI, "http://example.com/browser/browser/components/originattributes/test/browser/dummy.html",
+ "The page should have been redirected to http://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 = gBrowser.addTab(BASE_URL + "test_firstParty_html_redirect.html");
+ yield BrowserTestUtils.browserLoaded(tab2.linkedBrowser, false, function(url) {
+ return url == "http://example.com/";
+ });
+
+ yield ContentTask.spawn(tab2.linkedBrowser, { firstPartyDomain: "example.com" }, function* (attrs) {
+ info("2nd tab document principal: " + content.document.nodePrincipal.origin);
+ info("2nd tab document uri: " + content.document.documentURI);
+ Assert.equal(content.document.documentURI, "http://example.com/",
+ "The page should have been redirected to http://example.com");
+ Assert.equal(content.document.nodePrincipal.originAttributes.firstPartyDomain,
+ attrs.firstPartyDomain, "The document should have firstPartyDomain");
+ });
+
+ let tab3 = gBrowser.addTab(BASE_URL + "test_firstParty_iframe_http_redirect.html");
+ yield 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 mochi.test.
+ yield ContentTask.spawn(tab3.linkedBrowser, { firstPartyDomain: "mochi.test" }, function* (attrs) {
+ let iframe = content.document.getElementById("iframe1");
+ info("iframe document principal: " + iframe.contentDocument.nodePrincipal.origin);
+ info("iframe document uri: " + iframe.contentDocument.documentURI);
+
+ Assert.equal(iframe.contentDocument.documentURI, "http://example.com/browser/browser/components/originattributes/test/browser/dummy.html",
+ "The page should have been redirected to http://example.com/browser/browser/components/originattributes/test/browser/dummy.html");
+ Assert.equal(iframe.contentDocument.nodePrincipal.originAttributes.firstPartyDomain,
+ attrs.firstPartyDomain, "The iframe should have firstPartyDomain: " + attrs.firstPartyDomain);
+ });
+
+ gBrowser.removeTab(tab);
+ gBrowser.removeTab(tab2);
+ gBrowser.removeTab(tab3);
+});
+
+/**
+ * Test for postMessage between document and iframe.
+ */
+add_task(function* postMessage_test() {
+ let tab = gBrowser.addTab(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
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser, true, function (url) {
+ return url == BASE_URL + "dummy.html";
+ });
+
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ info("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(function* openWindow_test() {
+ Services.prefs.setIntPref("browser.link.open_newwindow", 2);
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("browser.link.open_newwindow");
+ });
+
+ let tab = gBrowser.addTab(BASE_URL + "window.html");
+ let win = yield BrowserTestUtils.waitForNewWindow();
+
+ yield ContentTask.spawn(win.gBrowser.selectedBrowser, { firstPartyDomain: "mochi.test" }, function* (attrs) {
+ Assert.equal(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");
+
+ let iframe = content.document.getElementById("iframe1");
+ Assert.equal(iframe.frameLoader.docShell.getOriginAttributes().firstPartyDomain,
+ attrs.firstPartyDomain, "iframe's docshell should have firstPartyDomain");
+ Assert.equal(iframe.contentDocument.nodePrincipal.originAttributes.firstPartyDomain,
+ attrs.firstPartyDomain, "iframe should have firstPartyDomain");
+ });
+
+ gBrowser.removeTab(tab);
+ yield BrowserTestUtils.closeWindow(win);
+});
+
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 000000000..0b7b1540e
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_httpauth.js
@@ -0,0 +1,54 @@
+let Cu = Components.utils;
+let {HttpServer} = Cu.import("resource://testing-common/httpd.js", {});
+
+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) {
+ // Submit random account and password
+ let dialog = subject.Dialog;
+ dialog.ui.loginTextbox.setAttribute("value", Math.random());
+ dialog.ui.password1Textbox.setAttribute("value", Math.random());
+ dialog.ui.button0.click();
+}
+
+Services.obs.addObserver(onCommonDialogLoaded, "common-dialog-loaded", false);
+
+registerCleanupFunction(() => {
+ Services.obs.removeObserver(onCommonDialogLoaded, "common-dialog-loaded");
+ 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();
+}
+
+IsolationTestTools.runTests(FILE_URI, getResult);
+
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 000000000..a24cec9ac
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_imageCacheIsolation.js
@@ -0,0 +1,80 @@
+/*
+ * Bug 1264572 - A test case for image cache isolation.
+ */
+
+requestLongerTimeout(2);
+
+let Cu = Components.utils;
+let {HttpServer} = Cu.import("resource://testing-common/httpd.js", {});
+
+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);
+
+registerCleanupFunction(() => {
+ 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 = "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);
+}
+
+function doBefore() {
+ // 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');
+ let networkCache = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Ci.nsICacheStorageService);
+ networkCache.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 000000000..41bde80e4
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_localStorageIsolation.js
@@ -0,0 +1,24 @@
+/**
+ * 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();
+
+// Define the testing function
+function* doTest(aBrowser) {
+ return yield ContentTask.spawn(aBrowser, key, function (key) {
+ let value = content.localStorage.getItem(key);
+ if (value === null) {
+ // No value is found, so we create one.
+ value = Math.random().toString();
+ content.localStorage.setItem(key, value);
+ }
+ return value;
+ });
+}
+
+IsolationTestTools.runTests(TEST_PAGE, doTest);
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 000000000..7049407f6
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_sharedworker.js
@@ -0,0 +1,26 @@
+/**
+ * Bug 1264593 - A test case for the shared worker by 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_sharedworker.html";
+
+function* getResultFromSharedworker(aBrowser) {
+ let response = yield ContentTask.spawn(aBrowser, null, function* () {
+ let worker = new content.SharedWorker("file_sharedworker.js", "isolationSharedWorkerTest");
+
+ let result = yield new 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/dummy.html b/browser/components/originattributes/test/browser/dummy.html
new file mode 100644
index 000000000..1a87e2840
--- /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 000000000..14bd7a022
--- /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;version=1.7">
+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 000000000..a2140e617
--- /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;version=1.7">
+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 000000000..788ec899d
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_cache.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
+<head>
+ <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">
+</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 000000000..b571134e1
--- /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> \ No newline at end of file
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 000000000..5535363c9
--- /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 000000000..9e23c73b7
--- /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 000000000..2a7343b8e
--- /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="http://mochi.test:8888/browser/browser/components/originattributes/test/browser/file_favicon_cache.png" />
+ </head>
+ <body>
+ Third Party Favicon!!
+ </body>
+</html> \ No newline at end of file
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 000000000..5535363c9
--- /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 000000000..4a2dd680a
--- /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="http://mochi.test:8888/browser/browser/components/originattributes/test/browser/file_favicon.png" />
+ </head>
+ <body>
+ Third Party Favicon!!
+ </body>
+</html> \ No newline at end of file
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 000000000..713187fb2
--- /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_sharedworker.html b/browser/components/originattributes/test/browser/file_sharedworker.html
new file mode 100644
index 000000000..b9ff793bd
--- /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_sharedworker.js b/browser/components/originattributes/test/browser/file_sharedworker.js
new file mode 100644
index 000000000..82f075a37
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_sharedworker.js
@@ -0,0 +1,9 @@
+self.randomValue = Math.random();
+
+/* global onconnect:true */
+
+onconnect = function (e) {
+ let port = e.ports[0];
+ port.postMessage(self.randomValue);
+ port.start();
+};
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 000000000..edda4e912
--- /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 000000000..c5916f289
--- /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.fetch.html b/browser/components/originattributes/test/browser/file_thirdPartyChild.fetch.html
new file mode 100644
index 000000000..037901ad0
--- /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.iframe.html b/browser/components/originattributes/test/browser/file_thirdPartyChild.iframe.html
new file mode 100644
index 000000000..b047d5b41
--- /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 000000000..c5916f289
--- /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 000000000..dbf8f8376
--- /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 000000000..06d6e2672
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.link.css
@@ -0,0 +1 @@
+/* Dummy CSS file, used by browser_cache.js. */ \ No newline at end of file
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 000000000..c5916f289
--- /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 000000000..108ed2ffa
--- /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 000000000..6ddf436c0
--- /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 000000000..b262fa10a
--- /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 000000000..b37cb40e4
--- /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 000000000..68dee3cf2
--- /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 000000000..47e42d1e5
--- /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 000000000..b04e2c7de
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.worker.js
@@ -0,0 +1,9 @@
+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 000000000..5b5c55bfe
--- /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 000000000..9fc107f37
--- /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 000000000..f56e7b3c1
--- /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/head.js b/browser/components/originattributes/test/browser/head.js
new file mode 100644
index 000000000..96559a10d
--- /dev/null
+++ b/browser/components/originattributes/test/browser/head.js
@@ -0,0 +1,365 @@
+/* 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 = 0;
+const TEST_TYPE_IFRAME = 1;
+
+// 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.
+ */
+function* openTabInUserContext(aURL, aUserContextId) {
+ // Open the tab in the correct userContextId.
+ let tab = gBrowser.addTab(aURL, {userContextId: aUserContextId});
+
+ // Select tab and make sure its browser is focused.
+ gBrowser.selectedTab = tab;
+ tab.ownerGlobal.focus();
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ yield BrowserTestUtils.browserLoaded(browser);
+ return {tab, browser};
+}
+
+/**
+ * Add a tab for a page with the given first party domain. This page will have
+ * an iframe which is loaded with the given url 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.
+ */
+function* openTabInFirstParty(aURL, aFirstPartyDomain,
+ aFrameSetting = DEFAULT_FRAME_SETTING) {
+
+ // 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.
+ let tab = gBrowser.addTab(basicPageURL);
+
+ // Select tab and make sure its browser is focused.
+ gBrowser.selectedTab = tab;
+ tab.ownerGlobal.focus();
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ let pageArgs = { url: aURL,
+ frames: aFrameSetting,
+ typeFrame: TEST_TYPE_FRAME,
+ typeIFrame: TEST_TYPE_IFRAME,
+ basicFrameSrc: basicPageURL};
+
+ // Create the frame structure.
+ yield ContentTask.spawn(browser, pageArgs, function* (arg) {
+ let typeFrame = arg.typeFrame;
+ let typeIFrame = arg.typeIFrame;
+
+ // Redefine the 'content' for allowing us to change its target, and making
+ // ContentTask.spawn can directly work on the frame element.
+ this.frameWindow = content;
+
+ Object.defineProperty(this, "content", {
+ get: () => this.frameWindow
+ });
+
+ let frameElement;
+ let numOfLayers = 0;
+
+ for (let type of arg.frames) {
+ let document = content.document;
+ numOfLayers++;
+
+ if (type === typeFrame) {
+ // Add a frameset which carries the frame element.
+ let frameSet = document.createElement('frameset');
+ frameSet.cols = "50%,50%";
+
+ let frame = document.createElement('frame');
+ let dummyFrame = document.createElement('frame');
+
+ frameSet.appendChild(frame);
+ frameSet.appendChild(dummyFrame);
+
+ document.body.appendChild(frameSet);
+
+ frameElement = frame;
+ } else if (type === typeIFrame) {
+ // Add an iframe.
+ let iframe = document.createElement('iframe');
+ document.body.appendChild(iframe);
+
+ frameElement = iframe;
+ } else {
+ ok(false, "Invalid frame type.");
+ break;
+ }
+
+ // Wait for the frame to be loaded.
+ yield new Promise(done => {
+ frameElement.addEventListener("load", function loadEnd() {
+ frameElement.removeEventListener("load", loadEnd, true);
+ done();
+ }, true);
+
+ // If it is the deepest layer, we load the target URL. Otherwise, we
+ // load a basic page.
+ if (numOfLayers === arg.frames.length) {
+ frameElement.setAttribute("src", arg.url);
+ } else {
+ frameElement.setAttribute("src", arg.basicFrameSrc);
+ }
+ });
+
+ // Redirect the 'content' to the frame's window.
+ this.frameWindow = frameElement.contentWindow;
+ }
+ });
+
+ return {tab, browser};
+}
+
+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) {
+ add_task(function* addTaskForIsolationTests() {
+ 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(function* () {
+ info("Starting the test for " + TEST_MODE_NAMES[aMode]);
+
+ // Before run this task, reset the preferences first.
+ yield SpecialPowers.flushPrefEnv();
+
+ // Make sure preferences are set properly.
+ yield SpecialPowers.pushPrefEnv({"set": aPref});
+
+ yield SpecialPowers.pushPrefEnv({"set": [["dom.ipc.processCount", 1]]});
+
+ yield 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) {
+ 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 = [
+ { firstPartyDomain: "http://example.com", userContextId: 1},
+ { firstPartyDomain: "http://example.org", userContextId: 2}
+ ];
+
+ this._add_task(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) {
+ yield aBeforeFunc(aMode);
+ }
+
+ // Create Tabs.
+ let tabInfoA = yield IsolationTestTools._addTab(aMode,
+ pageURL,
+ tabSettings[tabSettingA],
+ firstFrameSetting);
+ let resultsA = [];
+ if (aGetResultImmediately) {
+ for (let getResultFunc of aGetResultFuncs) {
+ resultsA.push(yield getResultFunc(tabInfoA.browser));
+ }
+ }
+ let tabInfoB = yield IsolationTestTools._addTab(aMode,
+ pageURL,
+ tabSettings[tabSettingB],
+ secondFrameSetting);
+ let i = 0;
+ for (let getResultFunc of aGetResultFuncs) {
+ // Fetch results from tabs.
+ let resultA = aGetResultImmediately ? resultsA[i++] :
+ yield getResultFunc(tabInfoA.browser);
+ let resultB = yield getResultFunc(tabInfoB.browser);
+
+ // Compare results.
+ let result = false;
+ let shouldIsolate = (aMode !== TEST_MODE_NO_ISOLATION) &&
+ tabSettingA !== tabSettingB;
+ if (aCompareResultFunc) {
+ result = yield aCompareResultFunc(shouldIsolate, resultA, resultB);
+ } else {
+ result = shouldIsolate ? resultA !== resultB :
+ resultA === resultB;
+ }
+
+ let msg = `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.
+ yield BrowserTestUtils.removeTab(tabInfoA.tab);
+ yield BrowserTestUtils.removeTab(tabInfoB.tab);
+ }
+ });
+ }
+};
diff --git a/browser/components/originattributes/test/browser/test.html b/browser/components/originattributes/test/browser/test.html
new file mode 100644
index 000000000..214daa4b7
--- /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", "http://mochi.test:8888");
+ };
+ </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 000000000..d290af9b0
--- /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 000000000..881f5bff0
--- /dev/null
+++ b/browser/components/originattributes/test/browser/test.js^headers^
@@ -0,0 +1 @@
+Set-Cookie: test=foo
diff --git a/browser/components/originattributes/test/browser/test2.html b/browser/components/originattributes/test/browser/test2.html
new file mode 100644
index 000000000..370be1560
--- /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 000000000..d290af9b0
--- /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 000000000..43604be7f
--- /dev/null
+++ b/browser/components/originattributes/test/browser/test2.js^headers^
@@ -0,0 +1 @@
+Set-Cookie: test2=foo
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 000000000..a90e01c4c
--- /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="http://example.com"></iframe>
+ <iframe id="iframe2" sandbox="" src="http://example.com"></iframe>
+ <iframe id="iframe3" sandbox="allow-same-origin" src="http://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 000000000..44547c0d7
--- /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 000000000..3c52d4f8c
--- /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=http://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 000000000..7b794a011
--- /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 000000000..c6d2757aa
--- /dev/null
+++ b/browser/components/originattributes/test/browser/test_firstParty_http_redirect.html^headers^
@@ -0,0 +1,2 @@
+HTTP 302 Found
+Location: http://example.com/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 000000000..fd7df46c1
--- /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 000000000..5df8a5950
--- /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", "http://mochi.test:8888");
+}
+
+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/window.html b/browser/components/originattributes/test/browser/window.html
new file mode 100644
index 000000000..34216030c
--- /dev/null
+++ b/browser/components/originattributes/test/browser/window.html
@@ -0,0 +1,13 @@
+<!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();
+ w.document.body.innerHTML = "<iframe id='iframe1' src='data:text/plain,test2'></iframe>";
+ </script>
+ </body>
+</html>
diff --git a/browser/components/originattributes/test/browser/worker_blobify.js b/browser/components/originattributes/test/browser/worker_blobify.js
new file mode 100644
index 000000000..56c0996a3
--- /dev/null
+++ b/browser/components/originattributes/test/browser/worker_blobify.js
@@ -0,0 +1,11 @@
+// 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.
+self.addEventListener("message", function (e) {
+ try {
+ var blobURL = URL.createObjectURL(new Blob([e.data]));
+ postMessage({ blobURL });
+ } catch (e) {
+ postMessage({ error: e.message });
+ }
+}, false);
diff --git a/browser/components/originattributes/test/browser/worker_deblobify.js b/browser/components/originattributes/test/browser/worker_deblobify.js
new file mode 100644
index 000000000..1d6511a20
--- /dev/null
+++ b/browser/components/originattributes/test/browser/worker_deblobify.js
@@ -0,0 +1,31 @@
+// Wait for a blob URL to be posted to this worker.
+// Obtain the blob, and read the string contained in it.
+// Post back the string.
+
+var postStringInBlob = function (blobObject) {
+ var fileReader = new FileReaderSync();
+ var result = fileReader.readAsText(blobObject);
+ postMessage(result);
+};
+
+self.addEventListener("message", function (e) {
+ if ("error" in e.data) {
+ postMessage(e.data);
+ return;
+ }
+ var blobURL = e.data.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 (e) {
+ postMessage({ error: e.message });
+ }
+}, false);
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 000000000..bc98b4d2e
--- /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> \ No newline at end of file
diff --git a/browser/components/originattributes/test/mochitest/mochitest.ini b/browser/components/originattributes/test/mochitest/mochitest.ini
new file mode 100644
index 000000000..5df9998ca
--- /dev/null
+++ b/browser/components/originattributes/test/mochitest/mochitest.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+support-files =
+ file_empty.html
+
+[test_permissions_api.html]
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 000000000..63c74d1fe
--- /dev/null
+++ b/browser/components/originattributes/test/mochitest/test_permissions_api.html
@@ -0,0 +1,207 @@
+<!--
+ 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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+
+<body>
+ <pre id="test"></pre>
+ <script type="application/javascript;version=1.8">
+ /*globals SpecialPowers, SimpleTest, is, ok, */
+ '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'
+ }, ];
+
+ const UNSUPPORTED_PERMISSIONS = [
+ 'foobarbaz', // Not in spec, for testing only.
+ 'midi',
+ ];
+
+ // 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);
+ });
+ });
+ },
+ revokePermissions() {
+ const promisesToRevoke = PERMISSIONS.map(({ name }) => {
+ return aWindow.navigator.permissions
+ .revoke({ name })
+ .then(
+ ({ state }) => is(state, 'prompt', `correct state for '${name}'`),
+ () => ok(false, `revoke should not have rejected for '${name}'`)
+ );
+ });
+ return Promise.all(promisesToRevoke);
+ },
+ revokeUnsupportedPermissions() {
+ const promisesToRevoke = UNSUPPORTED_PERMISSIONS.map(({ name }) => {
+ return aWindow.navigator.permissions
+ .revoke({ name })
+ .then(
+ () => ok(false, `revoke should not have resolved for '${name}'`),
+ error => is(error.name, 'TypeError', `revoke should have thrown TypeError for '${name}'`)
+ );
+ });
+ return Promise.all(promisesToRevoke);
+ },
+ 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(() => {
+ const promisePrompt = this.promiseStateChanged(permission, 'prompt');
+ 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')
+ );
+ },
+ testInvalidRevoke() {
+ return aWindow.navigator.permissions
+ .revoke({ name: 'invalid' })
+ .then(
+ () => ok(false, 'invalid revoke should not have resolved'),
+ () => ok(true, 'invalid revoke should have rejected')
+ );
+ },
+ };
+ }
+
+ function enablePrefs() {
+ const ops = {
+ 'set': [
+ ['dom.permissions.revoke.enable', true],
+ ['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);
+ });
+ }
+ debugger;
+ 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(() => tester.revokeUnsupportedPermissions())
+ .then(() => tester.revokePermissions())
+ .then(() => tester.checkPermissions('prompt'))
+ .then(() => tester.testInvalidRevoke());
+ })
+ .then(SimpleTest.finish)
+ .catch((e) => {
+ ok(false, `Unexpected error ${e}`);
+ SimpleTest.finish();
+ });
+ };
+ </script>
+</body>
+
+</html>