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