tor-browser

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

test_cache_behavior.js (7248B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 * http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 const { ObliviousHTTP } = ChromeUtils.importESModule(
      7  "resource://gre/modules/ObliviousHTTP.sys.mjs"
      8 );
      9 const { sinon } = ChromeUtils.importESModule(
     10  "resource://testing-common/Sinon.sys.mjs"
     11 );
     12 const { HttpServer } = ChromeUtils.importESModule(
     13  "resource://testing-common/httpd.sys.mjs"
     14 );
     15 const { TestUtils } = ChromeUtils.importESModule(
     16  "resource://testing-common/TestUtils.sys.mjs"
     17 );
     18 
     19 let gHttpServer;
     20 
     21 const TEST_CONTENT_TYPE = "image/jpeg";
     22 const TEST_CONTENT_CHARSET = "UTF-8";
     23 const TEST_CONTENT_TYPE_HEADER = `${TEST_CONTENT_TYPE};charset=${TEST_CONTENT_CHARSET}`;
     24 
     25 /**
     26 * Waits for an nsICacheEntry to exist for urlString in the anonymous load
     27 * context with > 0 data size.
     28 *
     29 * @param {string} urlString
     30 *   The string of the URL to check for.
     31 * @returns {Promise<undefined>}
     32 */
     33 async function waitForCacheEntry(urlString) {
     34  const lci = Services.loadContextInfo.anonymous;
     35  const storage = Services.cache2.diskCacheStorage(lci);
     36  const uri = Services.io.newURI(urlString);
     37 
     38  await TestUtils.waitForCondition(() => {
     39    try {
     40      return storage.exists(uri, "");
     41    } catch (e) {
     42      return false;
     43    }
     44  });
     45 
     46  let entry = await new Promise((resolve, reject) => {
     47    storage.asyncOpenURI(uri, "", Ci.nsICacheStorage.OPEN_READONLY, {
     48      onCacheEntryCheck: () => Ci.nsICacheEntryOpenCallback.ENTRY_WANTED,
     49      onCacheEntryAvailable: (foundEntry, isNew, status) => {
     50        if (Components.isSuccessCode(status)) {
     51          resolve(foundEntry);
     52        } else {
     53          reject(new Error(`Cache entry operation failed: ${status}`));
     54        }
     55      },
     56    });
     57  });
     58 
     59  await TestUtils.waitForCondition(() => {
     60    try {
     61      return entry.dataSize > 0;
     62    } catch (e) {
     63      return false;
     64    }
     65  });
     66 }
     67 
     68 add_setup(async function () {
     69  // Start HTTP server for cache testing
     70  gHttpServer = new HttpServer();
     71  gHttpServer.start(-1);
     72 
     73  // Set OHTTP preferences for testing
     74  Services.prefs.setCharPref(
     75    "browser.newtabpage.activity-stream.discoverystream.ohttp.configURL",
     76    "https://example.com/ohttp-config"
     77  );
     78  Services.prefs.setCharPref(
     79    "browser.newtabpage.activity-stream.discoverystream.ohttp.relayURL",
     80    "https://example.com/ohttp-relay"
     81  );
     82 
     83  registerCleanupFunction(async () => {
     84    await new Promise(resolve => gHttpServer.stop(resolve));
     85    Services.prefs.clearUserPref(
     86      "browser.newtabpage.activity-stream.discoverystream.ohttp.configURL"
     87    );
     88    Services.prefs.clearUserPref(
     89      "browser.newtabpage.activity-stream.discoverystream.ohttp.relayURL"
     90    );
     91  });
     92 });
     93 
     94 /**
     95 * Test that OHTTP responses populate the cache and subsequent requests use cache.
     96 */
     97 add_task(async function test_ohttp_populates_cache_and_cache_hit() {
     98  const sandbox = sinon.createSandbox();
     99 
    100  try {
    101    MockOHTTPService.reset();
    102 
    103    // Stub ObliviousHTTP.getOHTTPConfig to avoid network requests
    104    sandbox
    105      .stub(ObliviousHTTP, "getOHTTPConfig")
    106      .resolves(new Uint8Array([1, 2, 3, 4]));
    107 
    108    const imageURL = "https://example.com/test-cache-population.jpg";
    109    const testURI = createTestOHTTPResourceURI(imageURL);
    110 
    111    // First request - should go via OHTTP and populate cache
    112    MockOHTTPService.shouldUseContentTypeHeader = TEST_CONTENT_TYPE_HEADER;
    113    const firstChannel = createTestChannel(testURI);
    114 
    115    let firstRequestData = "";
    116    await new Promise(resolve => {
    117      const listener = createDataCollectingListener((data, success) => {
    118        Assert.ok(success, "First request should succeed");
    119        firstRequestData = data;
    120        resolve();
    121      });
    122 
    123      firstChannel.asyncOpen(listener);
    124    });
    125 
    126    // Verify OHTTP service was called for first request
    127    Assert.ok(
    128      MockOHTTPService.channelCreated,
    129      "Should call OHTTP service for cache miss"
    130    );
    131    Assert.equal(
    132      MockOHTTPService.totalChannels,
    133      1,
    134      "Should make one OHTTP request"
    135    );
    136    Assert.greater(
    137      firstRequestData.length,
    138      0,
    139      "Should receive data from OHTTP"
    140    );
    141 
    142    // Reset mock service state for second request
    143    MockOHTTPService.channelCreated = false;
    144 
    145    await waitForCacheEntry(imageURL);
    146    Assert.ok(true, "Found cache entry.");
    147 
    148    // Second request - should hit cache without calling OHTTP
    149    const secondChannel = createTestChannel(testURI);
    150 
    151    let secondRequestData = "";
    152    let cacheHit = false;
    153    await new Promise(resolve => {
    154      const listener = createDataCollectingListener((data, success) => {
    155        Assert.ok(success, "Second request should succeed");
    156        secondRequestData = data;
    157        cacheHit = true;
    158        resolve();
    159      });
    160 
    161      secondChannel.asyncOpen(listener);
    162    });
    163 
    164    // Verify cache hit behavior
    165    Assert.ok(cacheHit, "Second request should succeed from cache");
    166    Assert.equal(
    167      secondChannel.contentType,
    168      TEST_CONTENT_TYPE,
    169      "Got the right content type from the cache"
    170    );
    171    Assert.equal(
    172      secondChannel.contentCharset,
    173      TEST_CONTENT_CHARSET,
    174      "Got the right content charset from the cache"
    175    );
    176    Assert.ok(
    177      !MockOHTTPService.channelCreated,
    178      "Should not call OHTTP service for cache hit"
    179    );
    180    Assert.equal(
    181      MockOHTTPService.totalChannels,
    182      1,
    183      "Should still be only one OHTTP request"
    184    );
    185    Assert.equal(
    186      firstRequestData,
    187      secondRequestData,
    188      "Cache hit should return same data as original request"
    189    );
    190  } finally {
    191    sandbox.restore();
    192  }
    193 });
    194 
    195 /**
    196 * Test OHTTP fallback when cache miss occurs.
    197 */
    198 add_task(async function test_ohttp_fallback_on_cache_miss() {
    199  const sandbox = sinon.createSandbox();
    200 
    201  try {
    202    // Reset mock service state
    203    MockOHTTPService.reset();
    204 
    205    // Stub ObliviousHTTP.getOHTTPConfig to avoid network requests
    206    sandbox
    207      .stub(ObliviousHTTP, "getOHTTPConfig")
    208      .resolves(new Uint8Array([1, 2, 3, 4]));
    209 
    210    // Use a URL that won't be in cache
    211    const uncachedImageURL = `https://localhost:${gHttpServer.identity.primaryPort}/uncached-image.jpg`;
    212    const testURI = createTestOHTTPResourceURI(uncachedImageURL);
    213    const channel = createTestChannel(testURI);
    214 
    215    let loadCompleted = false;
    216    let receivedData = false;
    217    await new Promise(resolve => {
    218      const listener = createCompletionListener((success, hasData) => {
    219        loadCompleted = success;
    220        receivedData = hasData;
    221        resolve();
    222      });
    223 
    224      channel.asyncOpen(listener);
    225    });
    226 
    227    // Verify that the mock OHTTP service was called
    228    Assert.ok(
    229      MockOHTTPService.channelCreated,
    230      "Should call OHTTP service when cache miss occurs"
    231    );
    232    Assert.ok(loadCompleted, "Should load successfully via mocked OHTTP");
    233    Assert.ok(receivedData, "Should receive data via mocked OHTTP");
    234 
    235    // Verify the correct URLs were passed to the OHTTP service
    236    Assert.equal(
    237      MockOHTTPService.lastTargetURI.spec,
    238      uncachedImageURL,
    239      "Should request correct target URL via OHTTP"
    240    );
    241    Assert.equal(
    242      MockOHTTPService.lastRelayURI.spec,
    243      "https://example.com/ohttp-relay",
    244      "Should use correct relay URL"
    245    );
    246  } finally {
    247    sandbox.restore();
    248  }
    249 });