browser_storage_listings.js (20268B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 Services.scriptloader.loadSubScript( 7 "chrome://mochitests/content/browser/devtools/server/tests/browser/storage-helpers.js", 8 this 9 ); 10 11 const l10n = new Localization(["devtools/client/storage.ftl"], true); 12 const sessionString = l10n.formatValueSync("storage-expires-session"); 13 14 const storeMap = { 15 cookies: { 16 "http://test1.example.org": [ 17 { 18 name: "c1", 19 value: "foobar", 20 expires: 2000000000000, 21 path: "/browser", 22 host: "test1.example.org", 23 hostOnly: true, 24 isSecure: false, 25 }, 26 { 27 name: "cs2", 28 value: "sessionCookie", 29 path: "/", 30 host: ".example.org", 31 expires: 0, 32 hostOnly: false, 33 isSecure: false, 34 }, 35 { 36 name: "c3", 37 value: "foobar-2", 38 expires: 2000000001000, 39 path: "/", 40 host: "test1.example.org", 41 hostOnly: true, 42 isSecure: true, 43 }, 44 ], 45 46 "http://sectest1.example.org": [ 47 { 48 name: "cs2", 49 value: "sessionCookie", 50 path: "/", 51 host: ".example.org", 52 expires: 0, 53 hostOnly: false, 54 isSecure: false, 55 }, 56 { 57 name: "sc1", 58 value: "foobar", 59 path: "/browser/devtools/server/tests/browser", 60 host: "sectest1.example.org", 61 expires: 0, 62 hostOnly: true, 63 isSecure: false, 64 }, 65 ], 66 67 "https://sectest1.example.org": [ 68 { 69 name: "uc1", 70 value: "foobar", 71 host: ".example.org", 72 path: "/", 73 expires: 0, 74 hostOnly: false, 75 isSecure: true, 76 }, 77 { 78 name: "cs2", 79 value: "sessionCookie", 80 path: "/", 81 host: ".example.org", 82 expires: 0, 83 hostOnly: false, 84 isSecure: false, 85 }, 86 { 87 name: "sc1", 88 value: "foobar", 89 path: "/browser/devtools/server/tests/browser", 90 host: "sectest1.example.org", 91 expires: 0, 92 hostOnly: true, 93 isSecure: false, 94 }, 95 ], 96 }, 97 "local-storage": { 98 "http://test1.example.org": [ 99 { 100 name: "ls1", 101 value: "foobar", 102 }, 103 { 104 name: "ls2", 105 value: "foobar-2", 106 }, 107 ], 108 "http://sectest1.example.org": [ 109 { 110 name: "iframe-u-ls1", 111 value: "foobar", 112 }, 113 ], 114 "https://sectest1.example.org": [ 115 { 116 name: "iframe-s-ls1", 117 value: "foobar", 118 }, 119 ], 120 }, 121 "session-storage": { 122 "http://test1.example.org": [ 123 { 124 name: "ss1", 125 value: "foobar-3", 126 }, 127 ], 128 "http://sectest1.example.org": [ 129 { 130 name: "iframe-u-ss1", 131 value: "foobar1", 132 }, 133 { 134 name: "iframe-u-ss2", 135 value: "foobar2", 136 }, 137 ], 138 "https://sectest1.example.org": [ 139 { 140 name: "iframe-s-ss1", 141 value: "foobar-2", 142 }, 143 ], 144 }, 145 }; 146 147 const IDBValues = { 148 listStoresResponse: { 149 "http://test1.example.org": [ 150 ["idb1 (default)", "obj1"], 151 ["idb1 (default)", "obj2"], 152 ["idb2 (default)", "obj3"], 153 ], 154 "http://sectest1.example.org": [], 155 "https://sectest1.example.org": [ 156 ["idb-s1 (default)", "obj-s1"], 157 ["idb-s2 (default)", "obj-s2"], 158 ], 159 }, 160 dbDetails: { 161 "http://test1.example.org": [ 162 { 163 db: "idb1 (default)", 164 origin: "http://test1.example.org", 165 version: 1, 166 objectStores: 2, 167 }, 168 { 169 db: "idb2 (default)", 170 origin: "http://test1.example.org", 171 version: 1, 172 objectStores: 1, 173 }, 174 ], 175 "http://sectest1.example.org": [], 176 "https://sectest1.example.org": [ 177 { 178 db: "idb-s1 (default)", 179 origin: "https://sectest1.example.org", 180 version: 1, 181 objectStores: 1, 182 }, 183 { 184 db: "idb-s2 (default)", 185 origin: "https://sectest1.example.org", 186 version: 1, 187 objectStores: 1, 188 }, 189 ], 190 }, 191 objectStoreDetails: { 192 "http://test1.example.org": { 193 "idb1 (default)": [ 194 { 195 objectStore: "obj1", 196 keyPath: "id", 197 autoIncrement: false, 198 indexes: [ 199 { 200 name: "name", 201 keyPath: "name", 202 unique: false, 203 multiEntry: false, 204 }, 205 { 206 name: "email", 207 keyPath: "email", 208 unique: true, 209 multiEntry: false, 210 }, 211 ], 212 }, 213 { 214 objectStore: "obj2", 215 keyPath: "id2", 216 autoIncrement: false, 217 indexes: [], 218 }, 219 ], 220 "idb2 (default)": [ 221 { 222 objectStore: "obj3", 223 keyPath: "id3", 224 autoIncrement: false, 225 indexes: [ 226 { 227 name: "name2", 228 keyPath: "name2", 229 unique: true, 230 multiEntry: false, 231 }, 232 ], 233 }, 234 ], 235 }, 236 "http://sectest1.example.org": {}, 237 "https://sectest1.example.org": { 238 "idb-s1 (default)": [ 239 { 240 objectStore: "obj-s1", 241 keyPath: "id", 242 autoIncrement: false, 243 indexes: [], 244 }, 245 ], 246 "idb-s2 (default)": [ 247 { 248 objectStore: "obj-s2", 249 keyPath: "id3", 250 autoIncrement: true, 251 indexes: [ 252 { 253 name: "name2", 254 keyPath: "name2", 255 unique: true, 256 multiEntry: false, 257 }, 258 ], 259 }, 260 ], 261 }, 262 }, 263 entries: { 264 "http://test1.example.org": { 265 "idb1 (default)#obj1": [ 266 { 267 name: 1, 268 value: { 269 id: 1, 270 name: "foo", 271 email: "foo@bar.com", 272 }, 273 }, 274 { 275 name: 2, 276 value: { 277 id: 2, 278 name: "foo2", 279 email: "foo2@bar.com", 280 }, 281 }, 282 { 283 name: 3, 284 value: { 285 id: 3, 286 name: "foo2", 287 email: "foo3@bar.com", 288 }, 289 }, 290 ], 291 "idb1 (default)#obj2": [ 292 { 293 name: 1, 294 value: { 295 id2: 1, 296 name: "foo", 297 email: "foo@bar.com", 298 extra: "baz", 299 }, 300 }, 301 ], 302 "idb2 (default)#obj3": [], 303 }, 304 "http://sectest1.example.org": {}, 305 "https://sectest1.example.org": { 306 "idb-s1 (default)#obj-s1": [ 307 { 308 name: 6, 309 value: { 310 id: 6, 311 name: "foo", 312 email: "foo@bar.com", 313 }, 314 }, 315 { 316 name: 7, 317 value: { 318 id: 7, 319 name: "foo2", 320 email: "foo2@bar.com", 321 }, 322 }, 323 ], 324 "idb-s2 (default)#obj-s2": [ 325 { 326 name: 13, 327 value: { 328 id2: 13, 329 name2: "foo", 330 email: "foo@bar.com", 331 }, 332 }, 333 ], 334 }, 335 }, 336 }; 337 338 async function testStores(commands) { 339 const { resourceCommand } = commands; 340 const { TYPES } = resourceCommand; 341 /** 342 * Data is a dictionary whose keys are storage types (their resourceType) 343 * while values are objects with following attributes: 344 * - hosts: dictionary of storage values (values are specific to each storage type) 345 * keyed by host names. 346 * - dataByHost: dictionary of storage objects keyed by host names. 347 * storages objects are returned by StorageActor.getStoreObjects. 348 * For IndexedDB it is different, instead it is still a dictionary 349 * keyed by host names, but each value is yet another sub dictionary with 350 * a special "main" attribute, with global store objects. 351 * Then, there will be one key per idb database, with their store objects 352 * as value. 353 */ 354 const data = {}; 355 await resourceCommand.watchResources( 356 [ 357 TYPES.COOKIE, 358 TYPES.LOCAL_STORAGE, 359 TYPES.SESSION_STORAGE, 360 TYPES.INDEXED_DB, 361 ], 362 { 363 async onAvailable(resources) { 364 for (const resource of resources) { 365 const { resourceType } = resource; 366 if (!data[resourceType]) { 367 data[resourceType] = { hosts: {}, dataByHost: {} }; 368 } 369 370 for (const host in resource.hosts) { 371 if (!data[resourceType].hosts[host]) { 372 data[resourceType].hosts[host] = []; 373 } 374 // For indexed DB, we have some values, the database names. Other are empty arrays. 375 const hostValues = resource.hosts[host]; 376 data[resourceType].hosts[host].push(...hostValues); 377 378 // For INDEXED_DB, it is slightly more complex, as we may have 3 store per host, 379 if (resourceType == TYPES.INDEXED_DB) { 380 if (!data[resourceType].dataByHost[host]) { 381 data[resourceType].dataByHost[host] = {}; 382 } 383 data[resourceType].dataByHost[host].main = 384 await resource.getStoreObjects(host, null, { 385 sessionString, 386 }); 387 for (const name of resource.hosts[host]) { 388 const objName = JSON.parse(name).slice(0, 1); 389 data[resourceType].dataByHost[host][objName] = 390 await resource.getStoreObjects( 391 host, 392 [JSON.stringify(objName)], 393 { sessionString } 394 ); 395 data[resourceType].dataByHost[host][name] = 396 await resource.getStoreObjects(host, [name], { 397 sessionString, 398 }); 399 } 400 } else { 401 data[resourceType].dataByHost[host] = 402 await resource.getStoreObjects(host, null, { sessionString }); 403 } 404 } 405 } 406 }, 407 } 408 ); 409 410 await testCookies(data.cookies); 411 await testLocalStorage(data["local-storage"]); 412 await testSessionStorage(data["session-storage"]); 413 await testIndexedDB(data["indexed-db"]); 414 } 415 416 function testCookies({ hosts, dataByHost }) { 417 is( 418 Object.keys(hosts).length, 419 3, 420 "Correct number of host entries for cookies" 421 ); 422 return testCookiesObjects(0, hosts, dataByHost); 423 } 424 425 async function testCookiesObjects(index, hosts, dataByHost) { 426 const host = Object.keys(hosts)[index]; 427 ok(!!storeMap.cookies[host], "Host is present in the list : " + host); 428 const data = dataByHost[host]; 429 let cookiesLength = 0; 430 for (const secureCookie of storeMap.cookies[host]) { 431 if (secureCookie.isSecure) { 432 ++cookiesLength; 433 } 434 } 435 // Any secure cookies did not get stored in the database. 436 is( 437 data.total, 438 storeMap.cookies[host].length - cookiesLength, 439 "Number of cookies in host " + host + " matches" 440 ); 441 for (const item of data.data) { 442 let found = false; 443 for (const toMatch of storeMap.cookies[host]) { 444 if (item.name == toMatch.name) { 445 found = true; 446 ok(true, "Found cookie " + item.name + " in response"); 447 is(item.value.str, toMatch.value, "The value matches."); 448 is(item.expires, toMatch.expires, "The expiry time matches."); 449 is(item.path, toMatch.path, "The path matches."); 450 is(item.host, toMatch.host, "The host matches."); 451 is(item.isSecure, toMatch.isSecure, "The isSecure value matches."); 452 is(item.hostOnly, toMatch.hostOnly, "The hostOnly value matches."); 453 break; 454 } 455 } 456 ok(found, "cookie " + item.name + " should exist in response"); 457 } 458 459 if (index == Object.keys(hosts).length - 1) { 460 return; 461 } 462 await testCookiesObjects(++index, hosts, dataByHost); 463 } 464 465 function testLocalStorage({ hosts, dataByHost }) { 466 is( 467 Object.keys(hosts).length, 468 3, 469 "Correct number of host entries for local storage" 470 ); 471 return testLocalStorageObjects(0, hosts, dataByHost); 472 } 473 474 var testLocalStorageObjects = async function (index, hosts, dataByHost) { 475 const host = Object.keys(hosts)[index]; 476 ok( 477 !!storeMap["local-storage"][host], 478 "Host is present in the list : " + host 479 ); 480 const data = dataByHost[host]; 481 is( 482 data.total, 483 storeMap["local-storage"][host].length, 484 "Number of local storage items in host " + host + " matches" 485 ); 486 for (const item of data.data) { 487 let found = false; 488 for (const toMatch of storeMap["local-storage"][host]) { 489 if (item.name == toMatch.name) { 490 found = true; 491 ok(true, "Found local storage item " + item.name + " in response"); 492 is(item.value.str, toMatch.value, "The value matches."); 493 break; 494 } 495 } 496 ok(found, "local storage item " + item.name + " should exist in response"); 497 } 498 499 if (index == Object.keys(hosts).length - 1) { 500 return; 501 } 502 await testLocalStorageObjects(++index, hosts, dataByHost); 503 }; 504 505 function testSessionStorage({ hosts, dataByHost }) { 506 is( 507 Object.keys(hosts).length, 508 3, 509 "Correct number of host entries for session storage" 510 ); 511 return testSessionStorageObjects(0, hosts, dataByHost); 512 } 513 514 async function testSessionStorageObjects(index, hosts, dataByHost) { 515 const host = Object.keys(hosts)[index]; 516 ok( 517 !!storeMap["session-storage"][host], 518 "Host is present in the list : " + host 519 ); 520 const data = dataByHost[host]; 521 is( 522 data.total, 523 storeMap["session-storage"][host].length, 524 "Number of session storage items in host " + host + " matches" 525 ); 526 for (const item of data.data) { 527 let found = false; 528 for (const toMatch of storeMap["session-storage"][host]) { 529 if (item.name == toMatch.name) { 530 found = true; 531 ok(true, "Found session storage item " + item.name + " in response"); 532 is(item.value.str, toMatch.value, "The value matches."); 533 break; 534 } 535 } 536 ok( 537 found, 538 "session storage item " + item.name + " should exist in response" 539 ); 540 } 541 542 if (index == Object.keys(hosts).length - 1) { 543 return; 544 } 545 await testSessionStorageObjects(++index, hosts, dataByHost); 546 } 547 548 async function testIndexedDB({ hosts, dataByHost }) { 549 is( 550 Object.keys(hosts).length, 551 3, 552 "Correct number of host entries for indexed db" 553 ); 554 555 for (const host in hosts) { 556 for (const item of hosts[host]) { 557 const parsedItem = JSON.parse(item); 558 let found = false; 559 for (const toMatch of IDBValues.listStoresResponse[host]) { 560 if (toMatch[0] == parsedItem[0] && toMatch[1] == parsedItem[1]) { 561 found = true; 562 break; 563 } 564 } 565 ok(found, item + " should exist in list stores response"); 566 } 567 } 568 569 await testIndexedDBs(0, hosts, dataByHost); 570 await testObjectStores(0, hosts, dataByHost); 571 await testIDBEntries(0, hosts, dataByHost); 572 } 573 574 async function testIndexedDBs(index, hosts, dataByHost) { 575 const host = Object.keys(hosts)[index]; 576 const data = dataByHost[host].main; 577 is( 578 data.total, 579 IDBValues.dbDetails[host].length, 580 "Number of indexed db in host " + host + " matches" 581 ); 582 for (const item of data.data) { 583 let found = false; 584 for (const toMatch of IDBValues.dbDetails[host]) { 585 if (item.uniqueKey == toMatch.db) { 586 found = true; 587 ok(true, "Found indexed db " + item.uniqueKey + " in response"); 588 is(item.origin, toMatch.origin, "The origin matches."); 589 is(item.version, toMatch.version, "The version matches."); 590 is( 591 item.objectStores, 592 toMatch.objectStores, 593 "The number of object stores matches." 594 ); 595 break; 596 } 597 } 598 ok(found, "indexed db " + item.uniqueKey + " should exist in response"); 599 } 600 601 ok(!!IDBValues.dbDetails[host], "Host is present in the list : " + host); 602 if (index == Object.keys(hosts).length - 1) { 603 return; 604 } 605 await testIndexedDBs(++index, hosts, dataByHost); 606 } 607 608 async function testObjectStores(ix, hosts, dataByHost) { 609 const host = Object.keys(hosts)[ix]; 610 const matchItems = (data, db) => { 611 is( 612 data.total, 613 IDBValues.objectStoreDetails[host][db].length, 614 "Number of object stores in host " + host + " matches" 615 ); 616 for (const item of data.data) { 617 let found = false; 618 for (const toMatch of IDBValues.objectStoreDetails[host][db]) { 619 if (item.objectStore == toMatch.objectStore) { 620 found = true; 621 ok(true, "Found object store " + item.objectStore + " in response"); 622 is(item.keyPath, toMatch.keyPath, "The keyPath matches."); 623 is( 624 item.autoIncrement, 625 toMatch.autoIncrement, 626 "The autoIncrement matches." 627 ); 628 // We might already have parsed the JSON value, in which case this will no longer be a string 629 item.indexes = 630 typeof item.indexes == "string" 631 ? JSON.parse(item.indexes) 632 : item.indexes; 633 is( 634 item.indexes.length, 635 toMatch.indexes.length, 636 "Number of indexes match" 637 ); 638 for (const index of item.indexes) { 639 let indexFound = false; 640 for (const toMatchIndex of toMatch.indexes) { 641 if (toMatchIndex.name == index.name) { 642 indexFound = true; 643 ok(true, "Found index " + index.name); 644 is( 645 index.keyPath, 646 toMatchIndex.keyPath, 647 "The keyPath of index matches." 648 ); 649 is(index.unique, toMatchIndex.unique, "The unique matches"); 650 is( 651 index.multiEntry, 652 toMatchIndex.multiEntry, 653 "The multiEntry matches" 654 ); 655 break; 656 } 657 } 658 ok(indexFound, "Index " + index + " should exist in response"); 659 } 660 break; 661 } 662 } 663 ok(found, "indexed db " + item.name + " should exist in response"); 664 } 665 }; 666 667 ok( 668 !!IDBValues.objectStoreDetails[host], 669 "Host is present in the list : " + host 670 ); 671 for (const name of hosts[host]) { 672 const objName = JSON.parse(name).slice(0, 1); 673 matchItems(dataByHost[host][objName], objName[0]); 674 } 675 if (ix == Object.keys(hosts).length - 1) { 676 return; 677 } 678 await testObjectStores(++ix, hosts, dataByHost); 679 } 680 681 async function testIDBEntries(index, hosts, dataByHost) { 682 const host = Object.keys(hosts)[index]; 683 const matchItems = (data, obj) => { 684 is( 685 data.total, 686 IDBValues.entries[host][obj].length, 687 "Number of items in object store " + obj + " matches" 688 ); 689 for (const item of data.data) { 690 let found = false; 691 for (const toMatch of IDBValues.entries[host][obj]) { 692 if (item.name == toMatch.name) { 693 found = true; 694 ok(true, "Found indexed db item " + item.name + " in response"); 695 const value = JSON.parse(item.value.str); 696 is( 697 Object.keys(value).length, 698 Object.keys(toMatch.value).length, 699 "Number of entries in the value matches" 700 ); 701 for (const key in value) { 702 is( 703 value[key], 704 toMatch.value[key], 705 "value of " + key + " value key matches" 706 ); 707 } 708 break; 709 } 710 } 711 ok(found, "indexed db item " + item.name + " should exist in response"); 712 } 713 }; 714 715 ok(!!IDBValues.entries[host], "Host is present in the list : " + host); 716 for (const name of hosts[host]) { 717 const parsed = JSON.parse(name); 718 matchItems(dataByHost[host][name], parsed[0] + "#" + parsed[1]); 719 } 720 if (index == Object.keys(hosts).length - 1) { 721 return; 722 } 723 await testObjectStores(++index, hosts, dataByHost); 724 } 725 726 add_task(async function () { 727 await SpecialPowers.pushPrefEnv({ 728 set: [["network.cookie.maxageCap", 0]], 729 }); 730 731 const { commands } = await openTabAndSetupStorage( 732 MAIN_DOMAIN + "storage-listings.html" 733 ); 734 735 await testStores(commands); 736 737 await clearStorage(); 738 739 // Forcing GC/CC to get rid of docshells and windows created by this test. 740 forceCollections(); 741 await commands.destroy(); 742 forceCollections(); 743 });