diff options
Diffstat (limited to 'browser/components/originattributes')
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 Binary files differnew file mode 100644 index 000000000..5535363c9 --- /dev/null +++ b/browser/components/originattributes/test/browser/file_favicon.png 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 Binary files differnew file mode 100644 index 000000000..5535363c9 --- /dev/null +++ b/browser/components/originattributes/test/browser/file_favicon_cache.png 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 Binary files differnew file mode 100644 index 000000000..edda4e912 --- /dev/null +++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.audio.ogg diff --git a/browser/components/originattributes/test/browser/file_thirdPartyChild.embed.png b/browser/components/originattributes/test/browser/file_thirdPartyChild.embed.png Binary files differnew file mode 100644 index 000000000..c5916f289 --- /dev/null +++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.embed.png 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 Binary files differnew file mode 100644 index 000000000..c5916f289 --- /dev/null +++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.img.png 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 Binary files differnew file mode 100644 index 000000000..c5916f289 --- /dev/null +++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.object.png 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 Binary files differnew file mode 100644 index 000000000..68dee3cf2 --- /dev/null +++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.video.ogv 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> |