tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 });