diff options
Diffstat (limited to 'devtools/server/tests/browser/browser_storage_dynamic_windows.js')
-rw-r--r-- | devtools/server/tests/browser/browser_storage_dynamic_windows.js | 410 |
1 files changed, 410 insertions, 0 deletions
diff --git a/devtools/server/tests/browser/browser_storage_dynamic_windows.js b/devtools/server/tests/browser/browser_storage_dynamic_windows.js new file mode 100644 index 0000000000..0417cf0f09 --- /dev/null +++ b/devtools/server/tests/browser/browser_storage_dynamic_windows.js @@ -0,0 +1,410 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/devtools/server/tests/browser/storage-helpers.js", + 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": { + "http://test1.example.org": [ + JSON.stringify(["idb1", "obj1"]), + JSON.stringify(["idb1", "obj2"]), + JSON.stringify(["idb2", "obj3"]), + ], + "http://sectest1.example.org": [], + }, + "local-storage": { + "http://test1.example.org": ["ls1", "ls2"], + "http://sectest1.example.org": ["iframe-u-ls1"], + }, + "session-storage": { + "http://test1.example.org": ["ss1"], + "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: { + "https://sectest1.example.org": [ + getCookieId("cs2", ".example.org", "/"), + getCookieId( + "sc1", + "sectest1.example.org", + "/browser/devtools/server/tests/browser" + ), + ], + "http://sectest1.example.org": [ + 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. + "https://sectest1.example.org": [], + }, + "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: { + "http://sectest1.example.org": [], + }, + "indexed-db": { + "http://sectest1.example.org": [], + }, + "local-storage": { + "http://sectest1.example.org": [], + }, + "session-storage": { + "http://sectest1.example.org": [], + }, +}; + +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; +} |