Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Errors

/*
* Bug 1264577 - A test case for testing caches of various submodules.
* This test case will load two pages that each page loads various resources
* within the same third party domain for the same originAttributes or different
* originAttributes. And then, it verifies the number of cache entries and
* the originAttributes of loading channels. If these two pages belong to
* the same originAttributes, the number of cache entries for a certain
* resource would be one. Otherwise, it would be two.
*/
const CC = Components.Constructor;
let protocolProxyService = Cc[
"@mozilla.org/network/protocol-proxy-service;1"
].getService(Ci.nsIProtocolProxyService);
const TEST_DOMAIN = "http://example.net";
const TEST_PATH = "/browser/browser/components/originattributes/test/browser/";
const TEST_PAGE = TEST_DOMAIN + TEST_PATH + "file_cache.html";
let suffixes = [
"iframe.html",
"link.css",
"script.js",
"img.png",
"object.png",
"embed.png",
"xhr.html",
"worker.xhr.html",
"audio.ogg",
"video.webm",
"track.vtt",
"fetch.html",
"worker.fetch.html",
"request.html",
"worker.request.html",
"import.js",
"worker.js",
"sharedworker.js",
"font.woff",
];
// A random value for isolating video/audio elements across different tests.
let randomSuffix;
function clearAllImageCaches() {
let tools = SpecialPowers.Cc["@mozilla.org/image/tools;1"].getService(
SpecialPowers.Ci.imgITools
);
let imageCache = tools.getImgCacheForDocument(window.document);
imageCache.clearCache(true); // true=chrome
imageCache.clearCache(false); // false=content
}
function cacheDataForContext(loadContextInfo) {
return new Promise(resolve => {
let cacheEntries = [];
let cacheVisitor = {
onCacheStorageInfo() {},
onCacheEntryInfo(uri, idEnhance) {
cacheEntries.push({ uri, idEnhance });
},
onCacheEntryVisitCompleted() {
resolve(cacheEntries);
},
QueryInterface: ChromeUtils.generateQI(["nsICacheStorageVisitor"]),
};
// Visiting the disk cache also visits memory storage so we do not
// need to use Services.cache2.memoryCacheStorage() here.
let storage = Services.cache2.diskCacheStorage(loadContextInfo);
storage.asyncVisitStorage(cacheVisitor, true);
});
}
let countMatchingCacheEntries = function (cacheEntries, domain, fileSuffix) {
return cacheEntries
.map(entry => entry.uri.asciiSpec)
.filter(spec => spec.includes(domain))
.filter(spec => spec.includes("file_thirdPartyChild." + fileSuffix)).length;
};
function observeChannels(onChannel) {
// We use a dummy proxy filter to catch all channels, even those that do not
// generate an "http-on-modify-request" notification, such as link preconnects.
let proxyFilter = {
applyFilter(aChannel, aProxy, aCallback) {
// We have the channel; provide it to the callback.
onChannel(aChannel);
// Pass on aProxy unmodified.
aCallback.onProxyFilterResult(aProxy);
},
};
protocolProxyService.registerChannelFilter(proxyFilter, 0);
// Return the stop() function:
return () => protocolProxyService.unregisterChannelFilter(proxyFilter);
}
function startObservingChannels(aMode) {
let stopObservingChannels = observeChannels(function (channel) {
let originalURISpec = channel.originalURI.spec;
if (originalURISpec.includes("example.net")) {
let loadInfo = channel.loadInfo;
switch (aMode) {
case TEST_MODE_FIRSTPARTY:
ok(
loadInfo.originAttributes.firstPartyDomain === "example.com" ||
loadInfo.originAttributes.firstPartyDomain === "example.org",
"first party for " +
originalURISpec +
" is " +
loadInfo.originAttributes.firstPartyDomain
);
break;
case TEST_MODE_NO_ISOLATION:
ok(
ChromeUtils.isOriginAttributesEqual(
loadInfo.originAttributes,
ChromeUtils.fillNonDefaultOriginAttributes()
),
"OriginAttributes for " + originalURISpec + " is default."
);
break;
case TEST_MODE_CONTAINERS:
ok(
loadInfo.originAttributes.userContextId === 1 ||
loadInfo.originAttributes.userContextId === 2,
"userContextId for " +
originalURISpec +
" is " +
loadInfo.originAttributes.userContextId
);
break;
default:
ok(false, "Unknown test mode.");
}
}
});
return stopObservingChannels;
}
let stopObservingChannels;
// The init function, which clears image and network caches, and generates
// the random value for isolating video and audio elements across different
// test runs.
async function doInit(aMode) {
await SpecialPowers.pushPrefEnv({
set: [
["network.predictor.enabled", false],
["network.predictor.enable-prefetch", false],
["privacy.partition.network_state", false],
["dom.security.https_first", false],
],
});
clearAllImageCaches();
Services.cache2.clear();
randomSuffix = Math.random();
stopObservingChannels = startObservingChannels(aMode);
}
// In the test function, we dynamically generate the video and audio element,
// and assign a random suffix to their URL to isolate them across different
// test runs.
async function doTest(aBrowser) {
let argObj = {
randomSuffix,
urlPrefix: TEST_DOMAIN + TEST_PATH,
};
await SpecialPowers.spawn(aBrowser, [argObj], async function (arg) {
content.windowUtils.clearSharedStyleSheetCache();
let videoURL = arg.urlPrefix + "file_thirdPartyChild.video.webm";
let audioURL = arg.urlPrefix + "file_thirdPartyChild.audio.ogg";
let trackURL = arg.urlPrefix + "file_thirdPartyChild.track.vtt";
let URLSuffix = "?r=" + arg.randomSuffix;
// Create the audio and video elements.
let audio = content.document.createElement("audio");
let video = content.document.createElement("video");
let audioSource = content.document.createElement("source");
let audioTrack = content.document.createElement("track");
// Append the audio and track element into the body, and wait until they're finished.
await new content.Promise(resolve => {
let audioLoaded = false;
let trackLoaded = false;
let audioListener = () => {
Assert.ok(true, `Audio suspended: ${audioURL + URLSuffix}`);
audio.removeEventListener("suspend", audioListener);
audioLoaded = true;
if (audioLoaded && trackLoaded) {
resolve();
}
};
let trackListener = () => {
Assert.ok(true, `Audio track loaded: ${audioURL + URLSuffix}`);
audioTrack.removeEventListener("load", trackListener);
trackLoaded = true;
if (audioLoaded && trackLoaded) {
resolve();
}
};
Assert.ok(true, `Loading audio: ${audioURL + URLSuffix}`);
// Add the event listeners before everything in case we lose events.
audioTrack.addEventListener("load", trackListener);
audio.addEventListener("suspend", audioListener);
// Assign attributes for the audio element.
audioSource.setAttribute("src", audioURL + URLSuffix);
audioSource.setAttribute("type", "audio/ogg");
audioTrack.setAttribute("src", trackURL);
audioTrack.setAttribute("kind", "subtitles");
audioTrack.setAttribute("default", true);
audio.appendChild(audioSource);
audio.appendChild(audioTrack);
audio.autoplay = true;
content.document.body.appendChild(audio);
});
// Append the video element into the body, and wait until it's finished.
await new content.Promise(resolve => {
let listener = () => {
Assert.ok(true, `Video suspended: ${videoURL + URLSuffix}`);
video.removeEventListener("suspend", listener);
resolve();
};
Assert.ok(true, `Loading video: ${videoURL + URLSuffix}`);
// Add the event listener before everything in case we lose the event.
video.addEventListener("suspend", listener);
// Assign attributes for the video element.
video.setAttribute("src", videoURL + URLSuffix);
video.setAttribute("type", "video/ogg");
content.document.body.appendChild(video);
});
});
return 0;
}
// The check function, which checks the number of cache entries.
async function doCheck(aShouldIsolate) {
let expectedEntryCount = 1;
let data = [];
data = data.concat(
await cacheDataForContext(Services.loadContextInfo.default)
);
data = data.concat(
await cacheDataForContext(Services.loadContextInfo.private)
);
data = data.concat(
await cacheDataForContext(Services.loadContextInfo.custom(true, {}))
);
data = data.concat(
await cacheDataForContext(
Services.loadContextInfo.custom(false, { userContextId: 1 })
)
);
data = data.concat(
await cacheDataForContext(
Services.loadContextInfo.custom(true, { userContextId: 1 })
)
);
data = data.concat(
await cacheDataForContext(
Services.loadContextInfo.custom(false, { userContextId: 2 })
)
);
data = data.concat(
await cacheDataForContext(
Services.loadContextInfo.custom(true, { userContextId: 2 })
)
);
data = data.concat(
await cacheDataForContext(
Services.loadContextInfo.custom(false, {
firstPartyDomain: "example.com",
})
)
);
data = data.concat(
await cacheDataForContext(
Services.loadContextInfo.custom(true, { firstPartyDomain: "example.com" })
)
);
data = data.concat(
await cacheDataForContext(
Services.loadContextInfo.custom(false, {
firstPartyDomain: "example.org",
})
)
);
data = data.concat(
await cacheDataForContext(
Services.loadContextInfo.custom(true, { firstPartyDomain: "example.org" })
)
);
if (aShouldIsolate) {
expectedEntryCount = 2;
}
for (let suffix of suffixes) {
let foundEntryCount = countMatchingCacheEntries(
data,
"example.net",
suffix
);
let result = expectedEntryCount === foundEntryCount;
ok(
result,
"Cache entries expected for " +
suffix +
": " +
expectedEntryCount +
", and found " +
foundEntryCount
);
}
stopObservingChannels();
stopObservingChannels = undefined;
return true;
}
let testArgs = {
url: TEST_PAGE,
firstFrameSetting: DEFAULT_FRAME_SETTING,
secondFrameSetting: [TEST_TYPE_FRAME],
};
IsolationTestTools.runTests(testArgs, doTest, doCheck, doInit);