Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Errors

/* Any copyright is dedicated to the Public Domain.
"use strict";
Services.scriptloader.loadSubScript(
this
);
// beforeReload references an object representing the initialized state of the
// storage actor.
const beforeReload = {
cookies: {
"http://test1.example.org": ["c1", "cs2", "c3", "uc1"],
"http://sectest1.example.org": ["uc1", "cs2"],
},
"indexed-db": {
JSON.stringify(["idb1", "obj1"]),
JSON.stringify(["idb1", "obj2"]),
JSON.stringify(["idb2", "obj3"]),
],
},
"local-storage": {
"http://test1.example.org": ["ls1", "ls2"],
"http://sectest1.example.org": ["iframe-u-ls1"],
},
"session-storage": {
"http://sectest1.example.org": ["iframe-u-ss1", "iframe-u-ss2"],
},
};
// afterIframeAdded references the items added when an iframe containing storage
// items is added to the page.
const afterIframeAdded = {
cookies: {
getCookieId("cs2", ".example.org", "/"),
getCookieId(
"sc1",
"sectest1.example.org",
"/browser/devtools/server/tests/browser"
),
],
getCookieId(
"sc1",
"sectest1.example.org",
"/browser/devtools/server/tests/browser"
),
],
},
"indexed-db": {
// empty because indexed db creation happens after the page load, so at
// the time of window-ready, there was no indexed db present.
},
"local-storage": {
"https://sectest1.example.org": ["iframe-s-ls1"],
},
"session-storage": {
"https://sectest1.example.org": ["iframe-s-ss1"],
},
};
// afterIframeRemoved references the items deleted when an iframe containing
// storage items is removed from the page.
const afterIframeRemoved = {
cookies: {
},
"indexed-db": {
},
"local-storage": {
},
"session-storage": {
},
};
add_task(async function () {
const { commands } = await openTabAndSetupStorage(
MAIN_DOMAIN + "storage-dynamic-windows.html"
);
const { resourceCommand } = commands;
const { TYPES } = resourceCommand;
const allResources = {};
const onAvailable = resources => {
for (const resource of resources) {
is(
resource.targetFront.targetType,
commands.targetCommand.TYPES.FRAME,
"Each storage resource has a valid 'targetFront' attribute"
);
// Because we have iframes, we have distinct targets, each spawning their own storage resource
if (allResources[resource.resourceType]) {
allResources[resource.resourceType].push(resource);
} else {
allResources[resource.resourceType] = [resource];
}
}
};
const parentProcessStorages = [TYPES.COOKIE, TYPES.INDEXED_DB];
const contentProcessStorages = [TYPES.LOCAL_STORAGE, TYPES.SESSION_STORAGE];
const allStorages = [...parentProcessStorages, ...contentProcessStorages];
await resourceCommand.watchResources(allStorages, { onAvailable });
is(
Object.keys(allStorages).length,
allStorages.length,
"Got all the storage resources"
);
// Do a copy of all the initial storages as test function may spawn new resources for the same
// type and override the initial ones.
// We do not call unwatchResources as it would clear its cache and next call
// to watchResources with ignoreExistingResources would break and reprocess all resources again.
const initialResources = Object.assign({}, allResources);
testWindowsBeforeReload(initialResources);
await testAddIframe(commands, initialResources, {
contentProcessStorages,
parentProcessStorages,
allStorages,
});
await testRemoveIframe(commands, initialResources, {
contentProcessStorages,
parentProcessStorages,
allStorages,
});
await clearStorage();
// Forcing GC/CC to get rid of docshells and windows created by this test.
forceCollections();
await commands.destroy();
forceCollections();
});
function testWindowsBeforeReload(resources) {
for (const storageType in beforeReload) {
ok(resources[storageType], `${storageType} storage actor is present`);
const hosts = {};
for (const resource of resources[storageType]) {
for (const [hostType, hostValues] of Object.entries(resource.hosts)) {
if (!hosts[hostType]) {
hosts[hostType] = [];
}
hosts[hostType].push(hostValues);
}
}
// If this test is run with chrome debugging enabled we get an extra
// key for "chrome". We don't want the test to fail in this case, so
// ignore it.
if (storageType == "indexedDB") {
delete hosts.chrome;
}
is(
Object.keys(hosts).length,
Object.keys(beforeReload[storageType]).length,
`Number of hosts for ${storageType} match`
);
for (const host in beforeReload[storageType]) {
ok(hosts[host], `Host ${host} is present`);
}
}
}
/**
* Wait for new storage resources to be created of the given types.
*/
async function waitForNewResourcesAndUpdates(commands, resourceTypes) {
// When fission is off, we don't expect any new resource
if (resourceTypes.length === 0) {
return { newResources: [], updates: [] };
}
const { resourceCommand } = commands;
let resolve;
const promise = new Promise(r => (resolve = r));
const allResources = {};
const allUpdates = {};
const onAvailable = resources => {
for (const resource of resources) {
if (resource.resourceType in allResources) {
ok(false, `Got multiple ${resource.resourceTypes} resources`);
}
allResources[resource.resourceType] = resource;
ok(true, `Got resource for ${resource.resourceType}`);
// Stop watching for resources when we got them all
if (Object.keys(allResources).length == resourceTypes.length) {
resourceCommand.unwatchResources(resourceTypes, {
onAvailable,
});
}
// But also listen for updates on each new resource
resource.once("single-store-update").then(update => {
ok(true, `Got updates for ${resource.resourceType}`);
allUpdates[resource.resourceType] = update;
// Resolve only once we got all the updates, for all the resources
if (Object.keys(allUpdates).length == resourceTypes.length) {
resolve({ newResources: allResources, updates: allUpdates });
}
});
}
};
await resourceCommand.watchResources(resourceTypes, {
onAvailable,
ignoreExistingResources: true,
});
return promise;
}
/**
* Wait for single-store-update events on all the given storage resources.
*/
function waitForResourceUpdates(resources, resourceTypes) {
const allUpdates = {};
const promises = [];
for (const type of resourceTypes) {
// Resolves once any of the many resources for the given storage type updates
const promise = Promise.any(
resources[type].map(resource => resource.once("single-store-update"))
);
promise.then(update => {
ok(true, `Got updates for ${type}`);
allUpdates[type] = update;
});
promises.push(promise);
}
return Promise.all(promises).then(() => allUpdates);
}
async function testAddIframe(
commands,
resources,
{ contentProcessStorages, parentProcessStorages, allStorages }
) {
info("Testing if new iframe addition works properly");
// If Fission or EFT is enabled:
// * we get new resources alongside single-store-update events for content process storages
// * only single-store-update events for previous resources for parent process storages
// Otherwise if fission is disables:
// * we get single-store-update events for all previous resources
const onResources = waitForNewResourcesAndUpdates(
commands,
isFissionEnabled() || isEveryFrameTargetEnabled()
? contentProcessStorages
: []
);
// If fission or EFT is enabled, we only get update for parent process storages.
// The content process storage resources are notified via brand new resource instances.
const storagesWithUpdates =
isFissionEnabled() || isEveryFrameTargetEnabled()
? parentProcessStorages
: allStorages;
const onUpdates = waitForResourceUpdates(resources, storagesWithUpdates);
await SpecialPowers.spawn(
gBrowser.selectedBrowser,
[ALT_DOMAIN_SECURED],
secured => {
const doc = content.document;
const iframe = doc.createElement("iframe");
iframe.src = secured + "storage-secured-iframe.html";
doc.querySelector("body").appendChild(iframe);
}
);
info("Wait for all resources");
const { newResources, updates } = await onResources;
info("Wait for all updates");
const previousResourceUpdates = await onUpdates;
if (isFissionEnabled() || isEveryFrameTargetEnabled()) {
for (const resourceType of contentProcessStorages) {
const resource = newResources[resourceType];
const expected = afterIframeAdded[resourceType];
// The resource only comes with hosts, without any values.
// Each host will be an empty array.
Assert.deepEqual(
Object.keys(resource.hosts),
Object.keys(expected),
`List of hosts for resource ${resourceType} is correct`
);
for (const host in resource.hosts) {
is(
resource.hosts[host].length,
0,
"For new resources, each host has no value and is an empty array"
);
}
const update = updates[resourceType];
const storageKey = resourceTypeToStorageKey(resourceType);
Assert.deepEqual(
update.added[storageKey],
expected,
"We get an update after the resource, with the host values"
);
}
}
for (const resourceType of storagesWithUpdates) {
const expected = afterIframeAdded[resourceType];
const update = previousResourceUpdates[resourceType];
const storageKey = resourceTypeToStorageKey(resourceType);
Assert.deepEqual(
update.added[storageKey],
expected,
`We get an update after the resource ${resourceType}, with the host values`
);
}
return newResources;
}
async function testRemoveIframe(
commands,
resources,
{ contentProcessStorages, parentProcessStorages, allStorages }
) {
info("Testing if iframe removal works properly");
// If fission or EFT is enabled, we only get update for parent process storages.
// The content process storage resources are wiped via their related target destruction.
const onUpdates = waitForResourceUpdates(
resources,
isFissionEnabled() || isEveryFrameTargetEnabled()
? parentProcessStorages
: allStorages
);
await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
for (const iframe of content.document.querySelectorAll("iframe")) {
if (iframe.src.startsWith("http:")) {
iframe.remove();
break;
}
}
});
info("Wait for all updates");
const previousResourceUpdates = await onUpdates;
const storagesWithUpdates =
isFissionEnabled() || isEveryFrameTargetEnabled()
? parentProcessStorages
: allStorages;
for (const resourceType of storagesWithUpdates) {
const expected = afterIframeRemoved[resourceType];
const update = previousResourceUpdates[resourceType];
const storageKey = resourceTypeToStorageKey(resourceType);
Assert.deepEqual(
update.deleted[storageKey],
expected,
`We get an update after the resource ${resourceType}, with the host values`
);
}
// With Fission or EFT, the iframe target is destroyed,
// which ends up destroying the related resources
if (isFissionEnabled() || isEveryFrameTargetEnabled()) {
const destroyedResourceTypes = [];
for (const storageType in resources) {
for (const resource of resources[storageType]) {
if (resource.isDestroyed()) {
destroyedResourceTypes.push(resource.resourceType);
}
}
}
Assert.deepEqual(
destroyedResourceTypes.sort(),
contentProcessStorages.sort(),
"Content process storage resources have been destroyed [local and session storages]"
);
}
}
/**
* single-store-update emits objects using attributes with old "storage key" namings,
* which is different from resource type namings.
*/
function resourceTypeToStorageKey(resourceType) {
if (resourceType == "local-storage") {
return "localStorage";
}
if (resourceType == "session-storage") {
return "sessionStorage";
}
if (resourceType == "indexed-db") {
return "indexedDB";
}
return resourceType;
}