commit c102a828cc194fa7a7eb4ffd5d01d7c5810033f3 Author: Arthur Edelstein arthuredelstein@gmail.com Date: Mon Dec 15 02:35:03 2014 -0800
Bug #13749.2: Regression tests for first-party isolation of cache
This test ensures that if first-party isolation is enabled ("privacy.thirdparty.isolate" pref is set to 2) then when a loaded file is cached, it is indexed by the URL-bar domain.
In this test, a number of files are loaded (via IFRAME, LINK, SCRIPT, IMG, OBJECT, EMBED, AUDIO, VIDEO, TRACK and XMLHttpRequest) by parent pages with different URL bar domains. When isolation is active, we test to confirm that a separate copy of each file is cached for each different parent domain. We also test to make sure that when isolation is inactive, a single copy of the child page is cached and reused for all parent domains. --- netwerk/test/browser/browser.ini | 14 ++ netwerk/test/browser/browser_cacheFirstParty.js | 157 ++++++++++++++++++++++ netwerk/test/browser/firstPartyGrandParent.html | 10 ++ netwerk/test/browser/firstPartyParent.html | 38 ++++++ netwerk/test/browser/thirdPartyChild.audio.ogg | Bin 0 -> 2603 bytes netwerk/test/browser/thirdPartyChild.embed.png | Bin 0 -> 95 bytes netwerk/test/browser/thirdPartyChild.iframe.html | 13 ++ netwerk/test/browser/thirdPartyChild.img.png | Bin 0 -> 95 bytes netwerk/test/browser/thirdPartyChild.link.css | 1 + netwerk/test/browser/thirdPartyChild.object.png | Bin 0 -> 95 bytes netwerk/test/browser/thirdPartyChild.script.js | 1 + netwerk/test/browser/thirdPartyChild.track.vtt | 13 ++ netwerk/test/browser/thirdPartyChild.video.ogv | Bin 0 -> 16049 bytes netwerk/test/browser/thirdPartyChild.xhr.html | 8 ++ 14 files changed, 255 insertions(+)
diff --git a/netwerk/test/browser/browser.ini b/netwerk/test/browser/browser.ini index a5f31f9..cae28f7 100644 --- a/netwerk/test/browser/browser.ini +++ b/netwerk/test/browser/browser.ini @@ -1,3 +1,17 @@ [DEFAULT] +support-files = + firstPartyGrandParent.html + firstPartyParent.html + thirdPartyChild.link.css + thirdPartyChild.iframe.html + thirdPartyChild.script.js + thirdPartyChild.audio.ogg + thirdPartyChild.video.ogv + thirdPartyChild.embed.png + thirdPartyChild.img.png + thirdPartyChild.object.png + thirdPartyChild.xhr.html + thirdPartyChild.track.vtt
+[browser_cacheFirstParty.js] [browser_NetUtil.js] diff --git a/netwerk/test/browser/browser_cacheFirstParty.js b/netwerk/test/browser/browser_cacheFirstParty.js new file mode 100644 index 0000000..673b8c9 --- /dev/null +++ b/netwerk/test/browser/browser_cacheFirstParty.js @@ -0,0 +1,157 @@ +// # URL-bar domain (first party) isolation test + +// This test ensures that if first-party isolation is enabled +// ("privacy.thirdparty.isolate" pref is set to 2) then when a loaded file is cached, +// it is indexed by the URL-bar domain. + +// In this test, a number of files are loaded (via IFRAME, LINK, SCRIPT, IMG, OBJECT, +// EMBED, AUDIO, VIDEO, TRACK and XMLHttpRequest) by parent pages with different URL bar +// domains. When isolation is active, we test to confirm that a separate copy of each file +// is cached for each different parent domain. We also test to make sure that when +// isolation is inactive, a single copy of the child page is cached and reused for all +// parent domains. + +// In this file, functions are defined in call stack order (later functions call earlier +// functions). Comments are formatted for docco. + +/* jshint esnext:true */ + +// __Mozilla utilities__. +const Cu = Components.utils; +const Ci = Components.interfaces; +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/Task.jsm"); + +// __listen(target, eventType, timeoutMs, useCapture)__. +// Calls addEventListener on target, with the given eventType. +// Returns a Promise that resolves to an Event object, if the event fires. +// If a timeout occurs, then Promise is rejected with a "Timed out" error. +// For use with Task.jsm. +let listen = function (target, eventType, timeoutMs, useCapture) { + return new Promise(function (resolve, reject) { + let listenFunction = function (event) { + target.removeEventListener(eventType, listenFunction, useCapture); + resolve(event); + }; + target.addEventListener(eventType, listenFunction, useCapture); + window.setTimeout(() => reject(new Error("Timed out")), timeoutMs); + }); +}; + +// __copyObject(obj)__. +// Returns a shallow copy of an object. Works with JS native objects. +let copyObject = function (obj) { + let result = {}; + for (let k of Object.keys(obj)) { + result[k] = obj[k]; + } + return result; +}; + +// __cacheData()__. +// Returns the current state of the cache. +let cacheData = function () { + let devices = [], + cacheEntries = [], + cacheVisitor = { visitDevice : function (deviceID, deviceInfo) { + devices.push(copyObject(deviceInfo)); + return true; + }, + visitEntry : function (deviceID, entryInfo) { + cacheEntries.push(copyObject(entryInfo)); + return true; + }, + QueryInterface : function(iid) { + if (iid.equals(Ci.nsICacheVisitor)) return this; + } }; + Services.cache.visitEntries(cacheVisitor); + return { devices : devices, cacheEntries : cacheEntries }; +}; + +// __loadURLinNewTab(URL)__. +// Opens a new tab at a given URL, and waits for it to load. Times out after 5 sec. +// Returns a promise that resolves to the tab. (Task.jsm coroutine.) +let loadURLinNewTab = function* (URL) { + let tab = gBrowser.addTab(URL), + browser = gBrowser.getBrowserForTab(tab), + result = yield listen(browser, "load", 5000, true); + return tab; +}; + +// __countMatchingCacheEntries(cacheEntries, domain, fileSuffix)__. +// Reports how many cache entries contain a given domain name and file suffix. +let countMatchingCacheEntries = function (cacheEntries, domain, fileSuffix) { + return cacheEntries.map(entry => entry.key) + .filter(key => key.contains(domain)) + .filter(key => key.contains("thirdPartyChild." + fileSuffix)) + .length; +}; + +// __Constants__. +let privacyPref = "privacy.thirdparty.isolate", + parentPage = "/browser/netwerk/test/browser/firstPartyParent.html", + grandParentPage = "/browser/netwerk/test/browser/firstPartyGrandParent.html", + // Parent domains (the iframe "child" domain is example.net): + domains = ["test1", "test2"], + // We duplicate domains, to check that two pages with the same first party domain + // share cached embedded objects. + duplicatedDomains = [].concat(domains, domains), + // We will check cache for example.net content from + // iframe, link, script, img, object, embed, xhr, audio, video, track + suffixes = ["iframe.html", "link.css", "script.js", "img.png", "object.png", + "embed.png", "xhr.html", "audio.ogg", "video.ogv", "track.vtt" ]; + +// __checkCachePopulation(pref, numberOfDomains)__. +// Check if the number of entries found in the cache for each +// embedded file type matches the number we expect, given the +// number of domains and the isolation state. +let checkCachePopulation = function (pref, numberOfDomains) { + let expectedEntryCount = (pref === 2) ? (2 * numberOfDomains) : 1; + // Collect cache data. + let data = cacheData(); + for (let suffix of suffixes) { + let foundEntryCount = countMatchingCacheEntries(data.cacheEntries, "example.net", suffix), + result = (suffix.startsWith("video") || suffix.startsWith("audio")) ? + // Video and audio elements aren't always cached, so + // tolerate fewer cached copies. + (expectedEntryCount === 1 ? (foundEntryCount <= 1) : (foundEntryCount > 1)) : + (expectedEntryCount === foundEntryCount); + // Report results to mochitest + ok(result, "Cache entries expected for " + suffix + + ": " + expectedEntryCount); + } +}; + +// __test()__. +// The main testing function. +let test = function () { + waitForExplicitFinish(); + // Launch a Task.jsm coroutine so we can open tabs and wait for each of them to open, + // one by one. + Task.spawn(function* () { + // Keep original pref value for restoring after the tests. + let originalPrefValue = Services.prefs.getIntPref(privacyPref); + // Test the pref with both values: 2 (isolating by first party) or 0 (not isolating) + for (let pref of [2, 0]) { + // Clear the cache. + Services.cache2.clear(); + // Set the pref to desired value + Services.prefs.setIntPref(privacyPref, pref); + // Open test tabs + let tabs = []; + for (let domain of duplicatedDomains) { + tabs.push(yield loadURLinNewTab("http://" + domain + ".example.com" + parentPage)); + tabs.push(yield loadURLinNewTab("http://" + domain + ".example.org" + grandParentPage)); + } + // Run checks to make sure cache has expected number of entries for + // the chosen pref state. + checkCachePopulation(pref, domains.length); + // Clean up by removing tabs. + tabs.forEach(tab => gBrowser.removeTab(tab)); + } + // Restore the pref to its original value. + Services.prefs.setIntPref(privacyPref, originalPrefValue); + // All tests have now been run. + finish(); + }); +}; diff --git a/netwerk/test/browser/firstPartyGrandParent.html b/netwerk/test/browser/firstPartyGrandParent.html new file mode 100644 index 0000000..c990831 --- /dev/null +++ b/netwerk/test/browser/firstPartyGrandParent.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> +<head> +<meta content="text/html;charset=utf-8" http-equiv="Content-Type"> +</head> +<frameset cols="50%,50%"> + <frame src="http://example.net/browser/netwerk/test/browser/firstPartyParent.html"> + <frame src=""> +</frameset> +</html> diff --git a/netwerk/test/browser/firstPartyParent.html b/netwerk/test/browser/firstPartyParent.html new file mode 100644 index 0000000..7f2f5c8 --- /dev/null +++ b/netwerk/test/browser/firstPartyParent.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<html> +<!-- The parent page, used by browser_cacheFirstParty.js --> +<meta content="text/html;charset=utf-8" http-equiv="Content-Type"> +<head> + <link rel="stylesheet" type="text/css" + href="http://example.net/browser/netwerk/test/browser/thirdPartyChild.link.css%22%... +</head> +<body> +<div>firstPartyParent.html</div> + +<iframe src="http://example.net/browser/netwerk/test/browser/thirdPartyChild.iframe.html"> +</iframe> + +<script src="http://example.net/browser/netwerk/test/browser/thirdPartyChild.script.js"> +</script> + +<img src="http://example.net/browser/netwerk/test/browser/thirdPartyChild.img.png"> + +<embed src="http://example.net/browser/netwerk/test/browser/thirdPartyChild.embed.png"> + +<object data="http://example.net/browser/netwerk/test/browser/thirdPartyChild.object.png" + type="image/png"></object> + +<audio id="audio" autoplay> + <source src="http://example.net/browser/netwerk/test/browser/thirdPartyChild.audio.ogg" + type="audio/ogg"> + <track src="http://example.net/browser/netwerk/test/browser/thirdPartyChild.track.vtt" + kind="subtitles"> +</audio> + +<video id="video" + src="http://example.net/browser/netwerk/test/browser/thirdPartyChild.video.ogv" + type="video/ogg"> +</video> + +</body> +</html> diff --git a/netwerk/test/browser/thirdPartyChild.audio.ogg b/netwerk/test/browser/thirdPartyChild.audio.ogg new file mode 100644 index 0000000..edda4e9 Binary files /dev/null and b/netwerk/test/browser/thirdPartyChild.audio.ogg differ diff --git a/netwerk/test/browser/thirdPartyChild.embed.png b/netwerk/test/browser/thirdPartyChild.embed.png new file mode 100644 index 0000000..c5916f2 Binary files /dev/null and b/netwerk/test/browser/thirdPartyChild.embed.png differ diff --git a/netwerk/test/browser/thirdPartyChild.iframe.html b/netwerk/test/browser/thirdPartyChild.iframe.html new file mode 100644 index 0000000..f7b7531 --- /dev/null +++ b/netwerk/test/browser/thirdPartyChild.iframe.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<html> +<meta content="text/html;charset=utf-8" http-equiv="Content-Type"> +<!-- The child page, used by browser_cacheFirstParty.js --> +<body> +<div>thirdPartyChild.html</div> +<script> + var xhr = new XMLHttpRequest(); + xhr.open("GET", "http://example.net/browser/netwerk/test/browser/thirdPartyChild.xhr.html", true); + xhr.send(); +</script> +</body> +</html> diff --git a/netwerk/test/browser/thirdPartyChild.img.png b/netwerk/test/browser/thirdPartyChild.img.png new file mode 100644 index 0000000..c5916f2 Binary files /dev/null and b/netwerk/test/browser/thirdPartyChild.img.png differ diff --git a/netwerk/test/browser/thirdPartyChild.link.css b/netwerk/test/browser/thirdPartyChild.link.css new file mode 100644 index 0000000..cf8a82e --- /dev/null +++ b/netwerk/test/browser/thirdPartyChild.link.css @@ -0,0 +1 @@ +/* Dummy CSS file, used by browser_cacheFirstParty.js. */ \ No newline at end of file diff --git a/netwerk/test/browser/thirdPartyChild.object.png b/netwerk/test/browser/thirdPartyChild.object.png new file mode 100644 index 0000000..c5916f2 Binary files /dev/null and b/netwerk/test/browser/thirdPartyChild.object.png differ diff --git a/netwerk/test/browser/thirdPartyChild.script.js b/netwerk/test/browser/thirdPartyChild.script.js new file mode 100644 index 0000000..1cf2937 --- /dev/null +++ b/netwerk/test/browser/thirdPartyChild.script.js @@ -0,0 +1 @@ +// Dummy child script, used by browser_cacheFirstParty.js diff --git a/netwerk/test/browser/thirdPartyChild.track.vtt b/netwerk/test/browser/thirdPartyChild.track.vtt new file mode 100644 index 0000000..b37cb40 --- /dev/null +++ b/netwerk/test/browser/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/netwerk/test/browser/thirdPartyChild.video.ogv b/netwerk/test/browser/thirdPartyChild.video.ogv new file mode 100644 index 0000000..68dee3c Binary files /dev/null and b/netwerk/test/browser/thirdPartyChild.video.ogv differ diff --git a/netwerk/test/browser/thirdPartyChild.xhr.html b/netwerk/test/browser/thirdPartyChild.xhr.html new file mode 100644 index 0000000..b750364 --- /dev/null +++ b/netwerk/test/browser/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_cacheFirstParty.js --> +<body> +<div>thirdPartyChild.html</div> +</body> +</html>