diff options
Diffstat (limited to 'devtools/server/tests/browser/browser_storage_listings.js')
-rw-r--r-- | devtools/server/tests/browser/browser_storage_listings.js | 743 |
1 files changed, 743 insertions, 0 deletions
diff --git a/devtools/server/tests/browser/browser_storage_listings.js b/devtools/server/tests/browser/browser_storage_listings.js new file mode 100644 index 0000000000..40365ede85 --- /dev/null +++ b/devtools/server/tests/browser/browser_storage_listings.js @@ -0,0 +1,743 @@ +/* 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 +); + +const l10n = new Localization(["devtools/client/storage.ftl"], true); +const sessionString = l10n.formatValueSync("storage-expires-session"); + +const storeMap = { + cookies: { + "http://test1.example.org": [ + { + name: "c1", + value: "foobar", + expires: 2000000000000, + path: "/browser", + host: "test1.example.org", + hostOnly: true, + isSecure: false, + }, + { + name: "cs2", + value: "sessionCookie", + path: "/", + host: ".example.org", + expires: 0, + hostOnly: false, + isSecure: false, + }, + { + name: "c3", + value: "foobar-2", + expires: 2000000001000, + path: "/", + host: "test1.example.org", + hostOnly: true, + isSecure: true, + }, + ], + + "http://sectest1.example.org": [ + { + name: "cs2", + value: "sessionCookie", + path: "/", + host: ".example.org", + expires: 0, + hostOnly: false, + isSecure: false, + }, + { + name: "sc1", + value: "foobar", + path: "/browser/devtools/server/tests/browser", + host: "sectest1.example.org", + expires: 0, + hostOnly: true, + isSecure: false, + }, + ], + + "https://sectest1.example.org": [ + { + name: "uc1", + value: "foobar", + host: ".example.org", + path: "/", + expires: 0, + hostOnly: false, + isSecure: true, + }, + { + name: "cs2", + value: "sessionCookie", + path: "/", + host: ".example.org", + expires: 0, + hostOnly: false, + isSecure: false, + }, + { + name: "sc1", + value: "foobar", + path: "/browser/devtools/server/tests/browser", + host: "sectest1.example.org", + expires: 0, + hostOnly: true, + isSecure: false, + }, + ], + }, + "local-storage": { + "http://test1.example.org": [ + { + name: "ls1", + value: "foobar", + }, + { + name: "ls2", + value: "foobar-2", + }, + ], + "http://sectest1.example.org": [ + { + name: "iframe-u-ls1", + value: "foobar", + }, + ], + "https://sectest1.example.org": [ + { + name: "iframe-s-ls1", + value: "foobar", + }, + ], + }, + "session-storage": { + "http://test1.example.org": [ + { + name: "ss1", + value: "foobar-3", + }, + ], + "http://sectest1.example.org": [ + { + name: "iframe-u-ss1", + value: "foobar1", + }, + { + name: "iframe-u-ss2", + value: "foobar2", + }, + ], + "https://sectest1.example.org": [ + { + name: "iframe-s-ss1", + value: "foobar-2", + }, + ], + }, +}; + +const IDBValues = { + listStoresResponse: { + "http://test1.example.org": [ + ["idb1 (default)", "obj1"], + ["idb1 (default)", "obj2"], + ["idb2 (default)", "obj3"], + ], + "http://sectest1.example.org": [], + "https://sectest1.example.org": [ + ["idb-s1 (default)", "obj-s1"], + ["idb-s2 (default)", "obj-s2"], + ], + }, + dbDetails: { + "http://test1.example.org": [ + { + db: "idb1 (default)", + origin: "http://test1.example.org", + version: 1, + objectStores: 2, + }, + { + db: "idb2 (default)", + origin: "http://test1.example.org", + version: 1, + objectStores: 1, + }, + ], + "http://sectest1.example.org": [], + "https://sectest1.example.org": [ + { + db: "idb-s1 (default)", + origin: "https://sectest1.example.org", + version: 1, + objectStores: 1, + }, + { + db: "idb-s2 (default)", + origin: "https://sectest1.example.org", + version: 1, + objectStores: 1, + }, + ], + }, + objectStoreDetails: { + "http://test1.example.org": { + "idb1 (default)": [ + { + objectStore: "obj1", + keyPath: "id", + autoIncrement: false, + indexes: [ + { + name: "name", + keyPath: "name", + unique: false, + multiEntry: false, + }, + { + name: "email", + keyPath: "email", + unique: true, + multiEntry: false, + }, + ], + }, + { + objectStore: "obj2", + keyPath: "id2", + autoIncrement: false, + indexes: [], + }, + ], + "idb2 (default)": [ + { + objectStore: "obj3", + keyPath: "id3", + autoIncrement: false, + indexes: [ + { + name: "name2", + keyPath: "name2", + unique: true, + multiEntry: false, + }, + ], + }, + ], + }, + "http://sectest1.example.org": {}, + "https://sectest1.example.org": { + "idb-s1 (default)": [ + { + objectStore: "obj-s1", + keyPath: "id", + autoIncrement: false, + indexes: [], + }, + ], + "idb-s2 (default)": [ + { + objectStore: "obj-s2", + keyPath: "id3", + autoIncrement: true, + indexes: [ + { + name: "name2", + keyPath: "name2", + unique: true, + multiEntry: false, + }, + ], + }, + ], + }, + }, + entries: { + "http://test1.example.org": { + "idb1 (default)#obj1": [ + { + name: 1, + value: { + id: 1, + name: "foo", + email: "foo@bar.com", + }, + }, + { + name: 2, + value: { + id: 2, + name: "foo2", + email: "foo2@bar.com", + }, + }, + { + name: 3, + value: { + id: 3, + name: "foo2", + email: "foo3@bar.com", + }, + }, + ], + "idb1 (default)#obj2": [ + { + name: 1, + value: { + id2: 1, + name: "foo", + email: "foo@bar.com", + extra: "baz", + }, + }, + ], + "idb2 (default)#obj3": [], + }, + "http://sectest1.example.org": {}, + "https://sectest1.example.org": { + "idb-s1 (default)#obj-s1": [ + { + name: 6, + value: { + id: 6, + name: "foo", + email: "foo@bar.com", + }, + }, + { + name: 7, + value: { + id: 7, + name: "foo2", + email: "foo2@bar.com", + }, + }, + ], + "idb-s2 (default)#obj-s2": [ + { + name: 13, + value: { + id2: 13, + name2: "foo", + email: "foo@bar.com", + }, + }, + ], + }, + }, +}; + +async function testStores(commands) { + const { resourceCommand } = commands; + const { TYPES } = resourceCommand; + /** + * Data is a dictionary whose keys are storage types (their resourceType) + * while values are objects with following attributes: + * - hosts: dictionary of storage values (values are specific to each storage type) + * keyed by host names. + * - dataByHost: dictionary of storage objects keyed by host names. + * storages objects are returned by StorageActor.getStoreObjects. + * For IndexedDB it is different, instead it is still a dictionary + * keyed by host names, but each value is yet another sub dictionary with + * a special "main" attribute, with global store objects. + * Then, there will be one key per idb database, with their store objects + * as value. + */ + const data = {}; + await resourceCommand.watchResources( + [ + TYPES.COOKIE, + TYPES.LOCAL_STORAGE, + TYPES.SESSION_STORAGE, + TYPES.INDEXED_DB, + ], + { + async onAvailable(resources) { + for (const resource of resources) { + const { resourceType } = resource; + if (!data[resourceType]) { + data[resourceType] = { hosts: {}, dataByHost: {} }; + } + + for (const host in resource.hosts) { + if (!data[resourceType].hosts[host]) { + data[resourceType].hosts[host] = []; + } + // For indexed DB, we have some values, the database names. Other are empty arrays. + const hostValues = resource.hosts[host]; + data[resourceType].hosts[host].push(...hostValues); + + // For INDEXED_DB, it is slightly more complex, as we may have 3 store per host, + if (resourceType == TYPES.INDEXED_DB) { + if (!data[resourceType].dataByHost[host]) { + data[resourceType].dataByHost[host] = {}; + } + data[resourceType].dataByHost[host].main = + await resource.getStoreObjects(host, null, { + sessionString, + }); + for (const name of resource.hosts[host]) { + const objName = JSON.parse(name).slice(0, 1); + data[resourceType].dataByHost[host][objName] = + await resource.getStoreObjects( + host, + [JSON.stringify(objName)], + { sessionString } + ); + data[resourceType].dataByHost[host][name] = + await resource.getStoreObjects(host, [name], { + sessionString, + }); + } + } else { + data[resourceType].dataByHost[host] = + await resource.getStoreObjects(host, null, { sessionString }); + } + } + } + }, + } + ); + + await testCookies(data.cookies); + await testLocalStorage(data["local-storage"]); + await testSessionStorage(data["session-storage"]); + await testIndexedDB(data["indexed-db"]); +} + +function testCookies({ hosts, dataByHost }) { + is( + Object.keys(hosts).length, + 3, + "Correct number of host entries for cookies" + ); + return testCookiesObjects(0, hosts, dataByHost); +} + +async function testCookiesObjects(index, hosts, dataByHost) { + const host = Object.keys(hosts)[index]; + ok(!!storeMap.cookies[host], "Host is present in the list : " + host); + const data = dataByHost[host]; + let cookiesLength = 0; + for (const secureCookie of storeMap.cookies[host]) { + if (secureCookie.isSecure) { + ++cookiesLength; + } + } + // Any secure cookies did not get stored in the database. + is( + data.total, + storeMap.cookies[host].length - cookiesLength, + "Number of cookies in host " + host + " matches" + ); + for (const item of data.data) { + let found = false; + for (const toMatch of storeMap.cookies[host]) { + if (item.name == toMatch.name) { + found = true; + ok(true, "Found cookie " + item.name + " in response"); + is(item.value.str, toMatch.value, "The value matches."); + is(item.expires, toMatch.expires, "The expiry time matches."); + is(item.path, toMatch.path, "The path matches."); + is(item.host, toMatch.host, "The host matches."); + is(item.isSecure, toMatch.isSecure, "The isSecure value matches."); + is(item.hostOnly, toMatch.hostOnly, "The hostOnly value matches."); + break; + } + } + ok(found, "cookie " + item.name + " should exist in response"); + } + + if (index == Object.keys(hosts).length - 1) { + return; + } + await testCookiesObjects(++index, hosts, dataByHost); +} + +function testLocalStorage({ hosts, dataByHost }) { + is( + Object.keys(hosts).length, + 3, + "Correct number of host entries for local storage" + ); + return testLocalStorageObjects(0, hosts, dataByHost); +} + +var testLocalStorageObjects = async function (index, hosts, dataByHost) { + const host = Object.keys(hosts)[index]; + ok( + !!storeMap["local-storage"][host], + "Host is present in the list : " + host + ); + const data = dataByHost[host]; + is( + data.total, + storeMap["local-storage"][host].length, + "Number of local storage items in host " + host + " matches" + ); + for (const item of data.data) { + let found = false; + for (const toMatch of storeMap["local-storage"][host]) { + if (item.name == toMatch.name) { + found = true; + ok(true, "Found local storage item " + item.name + " in response"); + is(item.value.str, toMatch.value, "The value matches."); + break; + } + } + ok(found, "local storage item " + item.name + " should exist in response"); + } + + if (index == Object.keys(hosts).length - 1) { + return; + } + await testLocalStorageObjects(++index, hosts, dataByHost); +}; + +function testSessionStorage({ hosts, dataByHost }) { + is( + Object.keys(hosts).length, + 3, + "Correct number of host entries for session storage" + ); + return testSessionStorageObjects(0, hosts, dataByHost); +} + +async function testSessionStorageObjects(index, hosts, dataByHost) { + const host = Object.keys(hosts)[index]; + ok( + !!storeMap["session-storage"][host], + "Host is present in the list : " + host + ); + const data = dataByHost[host]; + is( + data.total, + storeMap["session-storage"][host].length, + "Number of session storage items in host " + host + " matches" + ); + for (const item of data.data) { + let found = false; + for (const toMatch of storeMap["session-storage"][host]) { + if (item.name == toMatch.name) { + found = true; + ok(true, "Found session storage item " + item.name + " in response"); + is(item.value.str, toMatch.value, "The value matches."); + break; + } + } + ok( + found, + "session storage item " + item.name + " should exist in response" + ); + } + + if (index == Object.keys(hosts).length - 1) { + return; + } + await testSessionStorageObjects(++index, hosts, dataByHost); +} + +async function testIndexedDB({ hosts, dataByHost }) { + is( + Object.keys(hosts).length, + 3, + "Correct number of host entries for indexed db" + ); + + for (const host in hosts) { + for (const item of hosts[host]) { + const parsedItem = JSON.parse(item); + let found = false; + for (const toMatch of IDBValues.listStoresResponse[host]) { + if (toMatch[0] == parsedItem[0] && toMatch[1] == parsedItem[1]) { + found = true; + break; + } + } + ok(found, item + " should exist in list stores response"); + } + } + + await testIndexedDBs(0, hosts, dataByHost); + await testObjectStores(0, hosts, dataByHost); + await testIDBEntries(0, hosts, dataByHost); +} + +async function testIndexedDBs(index, hosts, dataByHost) { + const host = Object.keys(hosts)[index]; + const data = dataByHost[host].main; + is( + data.total, + IDBValues.dbDetails[host].length, + "Number of indexed db in host " + host + " matches" + ); + for (const item of data.data) { + let found = false; + for (const toMatch of IDBValues.dbDetails[host]) { + if (item.uniqueKey == toMatch.db) { + found = true; + ok(true, "Found indexed db " + item.uniqueKey + " in response"); + is(item.origin, toMatch.origin, "The origin matches."); + is(item.version, toMatch.version, "The version matches."); + is( + item.objectStores, + toMatch.objectStores, + "The number of object stores matches." + ); + break; + } + } + ok(found, "indexed db " + item.uniqueKey + " should exist in response"); + } + + ok(!!IDBValues.dbDetails[host], "Host is present in the list : " + host); + if (index == Object.keys(hosts).length - 1) { + return; + } + await testIndexedDBs(++index, hosts, dataByHost); +} + +async function testObjectStores(ix, hosts, dataByHost) { + const host = Object.keys(hosts)[ix]; + const matchItems = (data, db) => { + is( + data.total, + IDBValues.objectStoreDetails[host][db].length, + "Number of object stores in host " + host + " matches" + ); + for (const item of data.data) { + let found = false; + for (const toMatch of IDBValues.objectStoreDetails[host][db]) { + if (item.objectStore == toMatch.objectStore) { + found = true; + ok(true, "Found object store " + item.objectStore + " in response"); + is(item.keyPath, toMatch.keyPath, "The keyPath matches."); + is( + item.autoIncrement, + toMatch.autoIncrement, + "The autoIncrement matches." + ); + // We might already have parsed the JSON value, in which case this will no longer be a string + item.indexes = + typeof item.indexes == "string" + ? JSON.parse(item.indexes) + : item.indexes; + is( + item.indexes.length, + toMatch.indexes.length, + "Number of indexes match" + ); + for (const index of item.indexes) { + let indexFound = false; + for (const toMatchIndex of toMatch.indexes) { + if (toMatchIndex.name == index.name) { + indexFound = true; + ok(true, "Found index " + index.name); + is( + index.keyPath, + toMatchIndex.keyPath, + "The keyPath of index matches." + ); + is(index.unique, toMatchIndex.unique, "The unique matches"); + is( + index.multiEntry, + toMatchIndex.multiEntry, + "The multiEntry matches" + ); + break; + } + } + ok(indexFound, "Index " + index + " should exist in response"); + } + break; + } + } + ok(found, "indexed db " + item.name + " should exist in response"); + } + }; + + ok( + !!IDBValues.objectStoreDetails[host], + "Host is present in the list : " + host + ); + for (const name of hosts[host]) { + const objName = JSON.parse(name).slice(0, 1); + matchItems(dataByHost[host][objName], objName[0]); + } + if (ix == Object.keys(hosts).length - 1) { + return; + } + await testObjectStores(++ix, hosts, dataByHost); +} + +async function testIDBEntries(index, hosts, dataByHost) { + const host = Object.keys(hosts)[index]; + const matchItems = (data, obj) => { + is( + data.total, + IDBValues.entries[host][obj].length, + "Number of items in object store " + obj + " matches" + ); + for (const item of data.data) { + let found = false; + for (const toMatch of IDBValues.entries[host][obj]) { + if (item.name == toMatch.name) { + found = true; + ok(true, "Found indexed db item " + item.name + " in response"); + const value = JSON.parse(item.value.str); + is( + Object.keys(value).length, + Object.keys(toMatch.value).length, + "Number of entries in the value matches" + ); + for (const key in value) { + is( + value[key], + toMatch.value[key], + "value of " + key + " value key matches" + ); + } + break; + } + } + ok(found, "indexed db item " + item.name + " should exist in response"); + } + }; + + ok(!!IDBValues.entries[host], "Host is present in the list : " + host); + for (const name of hosts[host]) { + const parsed = JSON.parse(name); + matchItems(dataByHost[host][name], parsed[0] + "#" + parsed[1]); + } + if (index == Object.keys(hosts).length - 1) { + return; + } + await testObjectStores(++index, hosts, dataByHost); +} + +add_task(async function () { + await SpecialPowers.pushPrefEnv({ + set: [["privacy.documentCookies.maxage", 0]], + }); + + const { commands } = await openTabAndSetupStorage( + MAIN_DOMAIN + "storage-listings.html" + ); + + await testStores(commands); + + await clearStorage(); + + // Forcing GC/CC to get rid of docshells and windows created by this test. + forceCollections(); + await commands.destroy(); + forceCollections(); +}); |