browser_scriptCache_perf_timeline.js (19742B)
1 const URL_BASE = "https://example.com/browser/dom/tests/browser/"; 2 const URL_BASE2 = "https://example.net/browser/dom/tests/browser/"; 3 4 function testFields( 5 entry, 6 { hasBodyAccess, hasTimingAccess, isCacheOf }, 7 desc 8 ) { 9 Assert.equal(entry.entryType, "resource", "entryType should be available"); 10 Assert.equal( 11 entry.initiatorType, 12 "script", 13 "initiatorType should be available" 14 ); 15 16 if (hasTimingAccess) { 17 Assert.equal( 18 entry.nextHopProtocol, 19 "http/1.1", 20 `nextHopProtocol should be available for ${desc}` 21 ); 22 } else { 23 Assert.equal( 24 entry.nextHopProtocol, 25 "", 26 `nextHopProtocol should be hidden for ${desc}` 27 ); 28 } 29 30 if (hasBodyAccess) { 31 Assert.equal( 32 entry.responseStatus, 33 200, 34 `responseStatus should be available for ${desc}` 35 ); 36 } else { 37 Assert.equal( 38 entry.responseStatus, 39 0, 40 `responseStatus should be hidden for ${desc}` 41 ); 42 } 43 44 if (hasBodyAccess) { 45 Assert.equal( 46 entry.contentType, 47 "text/javascript", 48 `contentType should be available for ${desc}` 49 ); 50 } else { 51 Assert.equal( 52 entry.contentType, 53 "", 54 `contentType should be hidden for ${desc}` 55 ); 56 } 57 58 Assert.greater( 59 entry.startTime, 60 0, 61 `startTime should be non-zero for ${desc}` 62 ); 63 Assert.greater( 64 entry.responseEnd, 65 0, 66 `responseEnd should be non-zero for ${desc}` 67 ); 68 Assert.lessOrEqual( 69 entry.startTime, 70 entry.responseEnd, 71 `startTime <= responseEnd for ${desc}` 72 ); 73 74 if (hasTimingAccess) { 75 Assert.deepEqual( 76 entry.serverTiming, 77 [ 78 { name: "name1", duration: 0, description: "" }, 79 { name: "name2", duration: 20, description: "" }, 80 { name: "name3", duration: 30, description: "desc3" }, 81 ], 82 `serverTiming should be available for ${desc}` 83 ); 84 } else { 85 Assert.deepEqual( 86 entry.serverTiming, 87 [], 88 `serverTiming should be hidden for ${desc}` 89 ); 90 } 91 92 if (hasBodyAccess) { 93 Assert.greater( 94 entry.encodedBodySize, 95 0, 96 `encodedBodySize should be available for ${desc}` 97 ); 98 } else { 99 Assert.equal( 100 entry.encodedBodySize, 101 0, 102 `encodedBodySize should be hidden for ${desc}` 103 ); 104 } 105 106 if (isCacheOf) { 107 Assert.equal( 108 entry.encodedBodySize, 109 isCacheOf.encodedBodySize, 110 `encodedBodySize should equal to non-cache case for ${desc}` 111 ); 112 } 113 114 if (hasBodyAccess) { 115 Assert.greater( 116 entry.decodedBodySize, 117 0, 118 `decodedBodySize should be available for ${desc}` 119 ); 120 } else { 121 Assert.equal( 122 entry.decodedBodySize, 123 0, 124 `decodedBodySize should be hidden for ${desc}` 125 ); 126 } 127 128 if (isCacheOf) { 129 Assert.equal( 130 entry.decodedBodySize, 131 isCacheOf.decodedBodySize, 132 `decodedBodySize should equal to non-cache case for ${desc}` 133 ); 134 } 135 136 if (hasTimingAccess) { 137 if (isCacheOf) { 138 Assert.equal( 139 entry.transferSize, 140 0, 141 `transferSize should be zero for ${desc}` 142 ); 143 } else if (hasBodyAccess) { 144 Assert.greater( 145 entry.transferSize, 146 300, 147 `transferSize should be non-zero +300 for ${desc}` 148 ); 149 } else { 150 Assert.equal( 151 entry.transferSize, 152 300, 153 `transferSize should be zero +300 for ${desc}` 154 ); 155 } 156 } else { 157 Assert.equal( 158 entry.transferSize, 159 0, 160 `transferSize should be hidden for ${desc}` 161 ); 162 } 163 } 164 165 async function doTestCompleteCacheAfterReload(type) { 166 await SpecialPowers.pushPrefEnv({ 167 set: [["dom.script_loader.experimental.navigation_cache", true]], 168 }); 169 registerCleanupFunction(() => SpecialPowers.popPrefEnv()); 170 171 ChromeUtils.clearResourceCache(); 172 Services.cache2.clear(); 173 174 await BrowserTestUtils.withNewTab( 175 { 176 gBrowser, 177 url: URL_BASE + "dummy.html", 178 }, 179 async function (browser) { 180 const JS_URL = URL_BASE + "perf_server.sjs?cacheable"; 181 182 const task = async (url, type) => { 183 await new Promise(resolve => { 184 const script = content.document.createElement("script"); 185 script.src = url; 186 if (type === "module") { 187 script.type = "module"; 188 } 189 script.addEventListener("load", resolve); 190 content.document.head.append(script); 191 }); 192 193 const entries = content.performance 194 .getEntriesByType("resource") 195 .filter(entry => entry.name.includes("perf_server.sjs")); 196 if (entries.length != 1) { 197 throw new Error(`Expect one entry, got ${entries.length} entries`); 198 } 199 // NOTE: entries[0].toJSON() doesn't convert serverTiming items. 200 return JSON.parse(JSON.stringify(entries[0])); 201 }; 202 203 const entry = await SpecialPowers.spawn(browser, [JS_URL, type], task); 204 Assert.equal(entry.name, JS_URL); 205 testFields( 206 entry, 207 { 208 hasBodyAccess: true, 209 hasTimingAccess: true, 210 }, 211 "same origin (non-cached)" 212 ); 213 214 await BrowserTestUtils.reloadTab(gBrowser.selectedTab); 215 216 const cacheEntry = await SpecialPowers.spawn( 217 browser, 218 [JS_URL, type], 219 task 220 ); 221 Assert.equal(cacheEntry.name, JS_URL); 222 testFields( 223 cacheEntry, 224 { 225 hasBodyAccess: true, 226 hasTimingAccess: true, 227 isCacheOf: entry, 228 }, 229 "same origin (cached)" 230 ); 231 } 232 ); 233 } 234 235 add_task(async function testScriptCompleteCacheAfterReload() { 236 await doTestCompleteCacheAfterReload("script"); 237 }); 238 add_task(async function testModuleCompleteCacheAfterReload() { 239 await doTestCompleteCacheAfterReload("module"); 240 }); 241 242 async function doTestCompleteCacheInSameDocument(type) { 243 await SpecialPowers.pushPrefEnv({ 244 set: [["dom.script_loader.experimental.navigation_cache", true]], 245 }); 246 registerCleanupFunction(() => SpecialPowers.popPrefEnv()); 247 248 ChromeUtils.clearResourceCache(); 249 Services.cache2.clear(); 250 251 await BrowserTestUtils.withNewTab( 252 { 253 gBrowser, 254 url: URL_BASE + "dummy.html", 255 }, 256 async function (browser) { 257 const JS_URL = URL_BASE + "perf_server.sjs?cacheable"; 258 259 const task = async (url, type) => { 260 // Before reload: 261 // * The first load is not cache 262 // * The second load is complete cache 263 // After reload: 264 // * Both loads are complete cache 265 266 for (let i = 0; i < 2; i++) { 267 await new Promise(resolve => { 268 const script = content.document.createElement("script"); 269 script.src = url; 270 if (type === "module") { 271 script.type = "module"; 272 } 273 script.addEventListener("load", () => { 274 resolve(); 275 }); 276 content.document.head.append(script); 277 }); 278 } 279 280 const entries = content.performance 281 .getEntriesByType("resource") 282 .filter(entry => entry.name.includes("perf_server.sjs")); 283 // In contrast to CSS, classic scripts perform "fetch" for both. 284 // Module scripts become singleton, and only one "fetch" happens. 285 if (type === "script") { 286 if (entries.length != 2) { 287 throw new Error( 288 `Expect two entries, got ${entries.length} entries` 289 ); 290 } 291 } else if (entries.length != 1) { 292 throw new Error(`Expect one entry, got ${entries.length} entries`); 293 } 294 return JSON.parse(JSON.stringify(entries)); 295 }; 296 297 const entries = await SpecialPowers.spawn(browser, [JS_URL, type], task); 298 Assert.equal(entries[0].name, JS_URL); 299 if (type === "script") { 300 Assert.equal(entries[1].name, JS_URL); 301 } 302 testFields( 303 entries[0], 304 { 305 hasBodyAccess: true, 306 hasTimingAccess: true, 307 }, 308 "same origin (non-cached)" 309 ); 310 if (type === "script") { 311 testFields( 312 entries[1], 313 { 314 hasBodyAccess: true, 315 hasTimingAccess: true, 316 isCacheOf: entries[0], 317 }, 318 "same origin (cached)" 319 ); 320 } 321 322 await BrowserTestUtils.reloadTab(gBrowser.selectedTab); 323 324 const cacheEntries = await SpecialPowers.spawn( 325 browser, 326 [JS_URL, type], 327 task 328 ); 329 Assert.equal(cacheEntries[0].name, JS_URL); 330 if (type === "script") { 331 Assert.equal(cacheEntries[1].name, JS_URL); 332 } 333 testFields( 334 cacheEntries[0], 335 { 336 hasBodyAccess: true, 337 hasTimingAccess: true, 338 isCacheOf: entries[0], 339 }, 340 "same origin (cached)" 341 ); 342 if (type === "script") { 343 testFields( 344 cacheEntries[1], 345 { 346 hasBodyAccess: true, 347 hasTimingAccess: true, 348 isCacheOf: entries[0], 349 }, 350 "same origin (cached)" 351 ); 352 } 353 } 354 ); 355 } 356 357 add_task(async function testScriptCompleteCacheInSameDocument() { 358 await doTestCompleteCacheInSameDocument("script"); 359 }); 360 add_task(async function testModuleCompleteCacheInSameDocument() { 361 await doTestCompleteCacheInSameDocument("module"); 362 }); 363 364 async function doTestNoCacheReload(type) { 365 await SpecialPowers.pushPrefEnv({ 366 set: [["dom.script_loader.experimental.navigation_cache", true]], 367 }); 368 registerCleanupFunction(() => SpecialPowers.popPrefEnv()); 369 370 ChromeUtils.clearResourceCache(); 371 Services.cache2.clear(); 372 373 await BrowserTestUtils.withNewTab( 374 { 375 gBrowser, 376 url: URL_BASE + "dummy.html", 377 }, 378 async function (browser) { 379 const JS_URL = URL_BASE + "perf_server.sjs?"; 380 381 const task = async (url, type) => { 382 await new Promise(resolve => { 383 const script = content.document.createElement("script"); 384 script.src = url; 385 if (type === "module") { 386 script.type = "module"; 387 } 388 script.addEventListener("load", resolve); 389 content.document.head.append(script); 390 }); 391 392 const entries = content.performance 393 .getEntriesByType("resource") 394 .filter(entry => entry.name.includes("perf_server.sjs")); 395 if (entries.length != 1) { 396 throw new Error(`Expect one entry, got ${entries.length} entries`); 397 } 398 return JSON.parse(JSON.stringify(entries[0])); 399 }; 400 401 const entry = await SpecialPowers.spawn(browser, [JS_URL, type], task); 402 Assert.equal(entry.name, JS_URL); 403 testFields( 404 entry, 405 { 406 hasBodyAccess: true, 407 hasTimingAccess: true, 408 }, 409 "same origin (non-cached)" 410 ); 411 412 await BrowserTestUtils.reloadTab(gBrowser.selectedTab); 413 414 // Reloading the JS shouldn't hit any cache. 415 416 const reloadEntry = await SpecialPowers.spawn( 417 browser, 418 [JS_URL, type], 419 task 420 ); 421 Assert.equal(reloadEntry.name, JS_URL); 422 testFields( 423 reloadEntry, 424 { 425 hasBodyAccess: true, 426 hasTimingAccess: true, 427 }, 428 "same origin (non-cached)" 429 ); 430 } 431 ); 432 } 433 434 add_task(async function testScriptNoCacheReload() { 435 await doTestNoCacheReload("script"); 436 }); 437 add_task(async function testModuleNoCacheReload() { 438 await doTestNoCacheReload("module"); 439 }); 440 441 async function doTest_NoCORS(type) { 442 await SpecialPowers.pushPrefEnv({ 443 set: [["dom.script_loader.experimental.navigation_cache", true]], 444 }); 445 registerCleanupFunction(() => SpecialPowers.popPrefEnv()); 446 447 ChromeUtils.clearResourceCache(); 448 Services.cache2.clear(); 449 450 await BrowserTestUtils.withNewTab( 451 { 452 gBrowser, 453 url: URL_BASE + "dummy.html", 454 }, 455 async function (browser) { 456 const JS_URL = URL_BASE2 + "perf_server.sjs?cacheable"; 457 458 const task = async (url, type) => { 459 await new Promise(resolve => { 460 const script = content.document.createElement("script"); 461 script.src = url; 462 if (type === "module") { 463 script.type = "module"; 464 } 465 script.addEventListener("load", resolve); 466 content.document.head.append(script); 467 }); 468 469 const entries = content.performance 470 .getEntriesByType("resource") 471 .filter(entry => entry.name.includes("perf_server.sjs")); 472 if (entries.length != 1) { 473 throw new Error(`Expect one entry, got ${entries.length} entries`); 474 } 475 return JSON.parse(JSON.stringify(entries[0])); 476 }; 477 478 const entry = await SpecialPowers.spawn(browser, [JS_URL, type], task); 479 Assert.equal(entry.name, JS_URL); 480 testFields( 481 entry, 482 { 483 hasBodyAccess: false, 484 hasTimingAccess: false, 485 }, 486 "cross origin (non-cached)" 487 ); 488 489 await BrowserTestUtils.reloadTab(gBrowser.selectedTab); 490 491 const cacheEntry = await SpecialPowers.spawn( 492 browser, 493 [JS_URL, type], 494 task 495 ); 496 Assert.equal(cacheEntry.name, JS_URL); 497 testFields( 498 cacheEntry, 499 { 500 hasBodyAccess: false, 501 hasTimingAccess: false, 502 isCacheOf: entry, 503 }, 504 "cross origin (cached)" 505 ); 506 } 507 ); 508 } 509 510 add_task(async function testScript_NoCORS() { 511 await doTest_NoCORS("script"); 512 }); 513 // Modules cannot be loaded with no CORS. 514 515 async function doTest_NoCORS_TAO(type) { 516 await SpecialPowers.pushPrefEnv({ 517 set: [["dom.script_loader.experimental.navigation_cache", true]], 518 }); 519 registerCleanupFunction(() => SpecialPowers.popPrefEnv()); 520 521 ChromeUtils.clearResourceCache(); 522 Services.cache2.clear(); 523 524 await BrowserTestUtils.withNewTab( 525 { 526 gBrowser, 527 url: URL_BASE + "dummy.html", 528 }, 529 async function (browser) { 530 const JS_URL = URL_BASE2 + "perf_server.sjs?cacheable,tao"; 531 532 const task = async (url, type) => { 533 await new Promise(resolve => { 534 const script = content.document.createElement("script"); 535 script.src = url; 536 if (type === "module") { 537 script.type = "module"; 538 } 539 script.addEventListener("load", resolve); 540 content.document.head.append(script); 541 }); 542 543 const entries = content.performance 544 .getEntriesByType("resource") 545 .filter(entry => entry.name.includes("perf_server.sjs")); 546 if (entries.length != 1) { 547 throw new Error(`Expect one entry, got ${entries.length} entries`); 548 } 549 return JSON.parse(JSON.stringify(entries[0])); 550 }; 551 552 const entry = await SpecialPowers.spawn(browser, [JS_URL, type], task); 553 Assert.equal(entry.name, JS_URL); 554 testFields( 555 entry, 556 { 557 hasBodyAccess: false, 558 hasTimingAccess: true, 559 }, 560 "cross origin with Timing-Allow-Origin (non-cached)" 561 ); 562 563 await BrowserTestUtils.reloadTab(gBrowser.selectedTab); 564 565 const cacheEntry = await SpecialPowers.spawn( 566 browser, 567 [JS_URL, type], 568 task 569 ); 570 Assert.equal(cacheEntry.name, JS_URL); 571 testFields( 572 cacheEntry, 573 { 574 hasBodyAccess: false, 575 hasTimingAccess: true, 576 isCacheOf: entry, 577 }, 578 "cross origin with Timing-Allow-Origin (cached)" 579 ); 580 } 581 ); 582 } 583 584 add_task(async function testScript_NoCORS_TAO() { 585 await doTest_NoCORS_TAO("script"); 586 }); 587 // Modules cannot be loaded with no CORS. 588 589 async function doTest_CORS(type) { 590 await SpecialPowers.pushPrefEnv({ 591 set: [["dom.script_loader.experimental.navigation_cache", true]], 592 }); 593 registerCleanupFunction(() => SpecialPowers.popPrefEnv()); 594 595 ChromeUtils.clearResourceCache(); 596 Services.cache2.clear(); 597 598 await BrowserTestUtils.withNewTab( 599 { 600 gBrowser, 601 url: URL_BASE + "dummy.html", 602 }, 603 async function (browser) { 604 const JS_URL = URL_BASE2 + "perf_server.sjs?cacheable,cors"; 605 606 const task = async (url, type) => { 607 await new Promise(resolve => { 608 const script = content.document.createElement("script"); 609 script.setAttribute("crossorigin", "anonymous"); 610 script.src = url; 611 if (type === "module") { 612 script.type = "module"; 613 } 614 script.addEventListener("load", resolve); 615 content.document.head.append(script); 616 }); 617 618 const entries = content.performance 619 .getEntriesByType("resource") 620 .filter(entry => entry.name.includes("perf_server.sjs")); 621 if (entries.length != 1) { 622 throw new Error(`Expect one entry, got ${entries.length} entries`); 623 } 624 return JSON.parse(JSON.stringify(entries[0])); 625 }; 626 627 const entry = await SpecialPowers.spawn(browser, [JS_URL, type], task); 628 Assert.equal(entry.name, JS_URL); 629 testFields( 630 entry, 631 { 632 hasBodyAccess: true, 633 hasTimingAccess: false, 634 }, 635 "CORS (non-cached)" 636 ); 637 638 await BrowserTestUtils.reloadTab(gBrowser.selectedTab); 639 640 const cacheEntry = await SpecialPowers.spawn( 641 browser, 642 [JS_URL, type], 643 task 644 ); 645 Assert.equal(cacheEntry.name, JS_URL); 646 testFields( 647 cacheEntry, 648 { 649 hasBodyAccess: true, 650 hasTimingAccess: false, 651 isCacheOf: entry, 652 }, 653 "CORS (cached)" 654 ); 655 } 656 ); 657 } 658 659 add_task(async function testScript_CORS() { 660 await doTest_CORS("script"); 661 }); 662 add_task(async function testModule_CORS() { 663 await doTest_CORS("module"); 664 }); 665 666 async function doTest_CORS_TAO(type) { 667 await SpecialPowers.pushPrefEnv({ 668 set: [["dom.script_loader.experimental.navigation_cache", true]], 669 }); 670 registerCleanupFunction(() => SpecialPowers.popPrefEnv()); 671 672 ChromeUtils.clearResourceCache(); 673 Services.cache2.clear(); 674 675 await BrowserTestUtils.withNewTab( 676 { 677 gBrowser, 678 url: URL_BASE + "dummy.html", 679 }, 680 async function (browser) { 681 const JS_URL = URL_BASE2 + "perf_server.sjs?cacheable,cors,tao"; 682 683 const task = async (url, type) => { 684 await new Promise(resolve => { 685 const script = content.document.createElement("script"); 686 script.setAttribute("crossorigin", "anonymous"); 687 script.src = url; 688 if (type === "module") { 689 script.type = "module"; 690 } 691 script.addEventListener("load", resolve); 692 content.document.head.append(script); 693 }); 694 695 const entries = content.performance 696 .getEntriesByType("resource") 697 .filter(entry => entry.name.includes("perf_server.sjs")); 698 if (entries.length != 1) { 699 throw new Error(`Expect one entry, got ${entries.length} entries`); 700 } 701 return JSON.parse(JSON.stringify(entries[0])); 702 }; 703 704 const entry = await SpecialPowers.spawn(browser, [JS_URL, type], task); 705 Assert.equal(entry.name, JS_URL); 706 testFields( 707 entry, 708 { 709 hasBodyAccess: true, 710 hasTimingAccess: true, 711 }, 712 "CORS with Timing-Allow-Origin (non-cached)" 713 ); 714 715 await BrowserTestUtils.reloadTab(gBrowser.selectedTab); 716 717 const cacheEntry = await SpecialPowers.spawn( 718 browser, 719 [JS_URL, type], 720 task 721 ); 722 Assert.equal(cacheEntry.name, JS_URL); 723 testFields( 724 cacheEntry, 725 { 726 hasBodyAccess: true, 727 hasTimingAccess: true, 728 isCacheOf: entry, 729 }, 730 "CORS with Timing-Allow-Origin (cached)" 731 ); 732 } 733 ); 734 } 735 736 add_task(async function testScript_CORS_TAO() { 737 await doTest_CORS_TAO("script"); 738 }); 739 add_task(async function testModule_CORS_TAO() { 740 await doTest_CORS_TAO("module"); 741 });