summaryrefslogtreecommitdiffstats
path: root/devtools/server/tests/browser/browser_storage_dynamic_windows.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/server/tests/browser/browser_storage_dynamic_windows.js')
-rw-r--r--devtools/server/tests/browser/browser_storage_dynamic_windows.js410
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;
+}