tor-browser

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

test_sharedMap.js (8942B)


      1 "use strict";
      2 
      3 const { AppConstants } = ChromeUtils.importESModule(
      4  "resource://gre/modules/AppConstants.sys.mjs"
      5 );
      6 const { XPCShellContentUtils } = ChromeUtils.importESModule(
      7  "resource://testing-common/XPCShellContentUtils.sys.mjs"
      8 );
      9 
     10 const PROCESS_COUNT_PREF = "dom.ipc.processCount";
     11 
     12 const remote = AppConstants.platform !== "android";
     13 
     14 XPCShellContentUtils.init(this);
     15 
     16 let contentPage;
     17 
     18 async function readBlob(key, sharedData = Services.cpmm.sharedData) {
     19  const { ExtensionUtils } = ChromeUtils.importESModule(
     20    "resource://gre/modules/ExtensionUtils.sys.mjs"
     21  );
     22 
     23  let reader = new FileReader();
     24  reader.readAsText(sharedData.get(key));
     25  await ExtensionUtils.promiseEvent(reader, "loadend");
     26  return reader.result;
     27 }
     28 
     29 function getKey(key, sharedData = Services.cpmm.sharedData) {
     30  return sharedData.get(key);
     31 }
     32 
     33 function hasKey(key, sharedData = Services.cpmm.sharedData) {
     34  return sharedData.has(key);
     35 }
     36 
     37 function getContents(sharedMap = Services.cpmm.sharedData) {
     38  return {
     39    keys: Array.from(sharedMap.keys()),
     40    values: Array.from(sharedMap.values()),
     41    entries: Array.from(sharedMap.entries()),
     42    getValues: Array.from(sharedMap.keys(), key => sharedMap.get(key)),
     43  };
     44 }
     45 
     46 function checkMap(contents, expected) {
     47  expected = Array.from(expected);
     48 
     49  equal(contents.keys.length, expected.length, "Got correct number of keys");
     50  equal(
     51    contents.values.length,
     52    expected.length,
     53    "Got correct number of values"
     54  );
     55  equal(
     56    contents.entries.length,
     57    expected.length,
     58    "Got correct number of entries"
     59  );
     60 
     61  for (let [i, [key, val]] of contents.entries.entries()) {
     62    equal(key, contents.keys[i], `keys()[${i}] matches entries()[${i}]`);
     63    deepEqual(
     64      val,
     65      contents.values[i],
     66      `values()[${i}] matches entries()[${i}]`
     67    );
     68  }
     69 
     70  expected.sort(([a], [b]) => a.localeCompare(b));
     71  contents.entries.sort(([a], [b]) => a.localeCompare(b));
     72 
     73  for (let [i, [key, val]] of contents.entries.entries()) {
     74    equal(
     75      key,
     76      expected[i][0],
     77      `expected[${i}].key matches entries()[${i}].key`
     78    );
     79    deepEqual(
     80      val,
     81      expected[i][1],
     82      `expected[${i}].value matches entries()[${i}].value`
     83    );
     84  }
     85 }
     86 
     87 function checkParentMap(expected) {
     88  info("Checking parent map");
     89  checkMap(getContents(Services.ppmm.sharedData), expected);
     90 }
     91 
     92 async function checkContentMaps(expected, parentOnly = false) {
     93  info("Checking in-process content map");
     94  checkMap(getContents(Services.cpmm.sharedData), expected);
     95 
     96  if (!parentOnly) {
     97    info("Checking out-of-process content map");
     98    let contents = await contentPage.spawn([], getContents);
     99    checkMap(contents, expected);
    100  }
    101 }
    102 
    103 async function loadContentPage() {
    104  let page = await XPCShellContentUtils.loadContentPage("data:text/html,", {
    105    remote,
    106  });
    107  registerCleanupFunction(() => page.close());
    108  return page;
    109 }
    110 
    111 add_setup(async function () {
    112  // Start with one content process so that we can increase the number
    113  // later and test the behavior of a fresh content process.
    114  Services.prefs.setIntPref(PROCESS_COUNT_PREF, 1);
    115 
    116  contentPage = await loadContentPage();
    117 });
    118 
    119 add_task(async function test_sharedMap() {
    120  let { sharedData } = Services.ppmm;
    121 
    122  info("Check that parent and child maps are both initially empty");
    123 
    124  checkParentMap([]);
    125  await checkContentMaps([]);
    126 
    127  let expected = [
    128    ["foo-a", { foo: "a" }],
    129    ["foo-b", { foo: "b" }],
    130    ["bar-c", null],
    131    ["bar-d", 42],
    132  ];
    133 
    134  function setKey(key, val) {
    135    sharedData.set(key, val);
    136    expected = expected.filter(([k]) => k != key);
    137    expected.push([key, val]);
    138  }
    139  function deleteKey(key) {
    140    sharedData.delete(key);
    141    expected = expected.filter(([k]) => k != key);
    142  }
    143 
    144  for (let [key, val] of expected) {
    145    sharedData.set(key, val);
    146  }
    147 
    148  info(
    149    "Add some entries, test that they are initially only available in the parent"
    150  );
    151 
    152  checkParentMap(expected);
    153  await checkContentMaps([]);
    154 
    155  info("Flush. Check that changes are visible in both parent and children");
    156 
    157  sharedData.flush();
    158 
    159  checkParentMap(expected);
    160  await checkContentMaps(expected);
    161 
    162  info(
    163    "Add another entry. Check that it is initially only available in the parent"
    164  );
    165 
    166  let oldExpected = Array.from(expected);
    167 
    168  setKey("baz-a", { meh: "meh" });
    169 
    170  // When we do several checks in a row, we can't check the values in
    171  // the content process, since the async checks may allow the idle
    172  // flush task to run, and update it before we're ready.
    173 
    174  checkParentMap(expected);
    175  checkContentMaps(oldExpected, true);
    176 
    177  info(
    178    "Add another entry. Check that both new entries are only available in the parent"
    179  );
    180 
    181  setKey("baz-a", { meh: 12 });
    182 
    183  checkParentMap(expected);
    184  checkContentMaps(oldExpected, true);
    185 
    186  info(
    187    "Delete an entry. Check that all changes are only visible in the parent"
    188  );
    189 
    190  deleteKey("foo-b");
    191 
    192  checkParentMap(expected);
    193  checkContentMaps(oldExpected, true);
    194 
    195  info(
    196    "Flush. Check that all entries are available in both parent and children"
    197  );
    198 
    199  sharedData.flush();
    200 
    201  checkParentMap(expected);
    202  await checkContentMaps(expected);
    203 
    204  info("Test that entries are automatically flushed on idle:");
    205 
    206  info(
    207    "Add a new entry. Check that it is initially only available in the parent"
    208  );
    209 
    210  // Test the idle flush task.
    211  oldExpected = Array.from(expected);
    212 
    213  setKey("thing", "stuff");
    214 
    215  checkParentMap(expected);
    216  checkContentMaps(oldExpected, true);
    217 
    218  info(
    219    "Wait for an idle timeout. Check that changes are now visible in all children"
    220  );
    221 
    222  await new Promise(resolve => ChromeUtils.idleDispatch(resolve));
    223 
    224  checkParentMap(expected);
    225  await checkContentMaps(expected);
    226 
    227  // Test that has() rebuilds map after a flush.
    228  sharedData.set("grick", true);
    229  sharedData.flush();
    230  equal(
    231    await contentPage.spawn(["grick"], hasKey),
    232    true,
    233    "has() should see key after flush"
    234  );
    235 
    236  sharedData.set("grack", true);
    237  sharedData.flush();
    238  equal(
    239    await contentPage.spawn(["gruck"], hasKey),
    240    false,
    241    "has() should return false for nonexistent key"
    242  );
    243 });
    244 
    245 add_task(async function test_blobs() {
    246  let { sharedData } = Services.ppmm;
    247 
    248  let text = [
    249    "The quick brown fox jumps over the lazy dog",
    250    "Lorem ipsum dolor sit amet, consectetur adipiscing elit",
    251    "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
    252  ];
    253  let blobs = text.map(str => new Blob([str]));
    254 
    255  let data = { foo: { bar: "baz" } };
    256 
    257  sharedData.set("blob0", blobs[0]);
    258  sharedData.set("blob1", blobs[1]);
    259  sharedData.set("data", data);
    260 
    261  equal(
    262    await readBlob("blob0", sharedData),
    263    text[0],
    264    "Expected text for blob0 in parent ppmm"
    265  );
    266 
    267  sharedData.flush();
    268 
    269  equal(
    270    await readBlob("blob0", sharedData),
    271    text[0],
    272    "Expected text for blob0 in parent ppmm"
    273  );
    274  equal(
    275    await readBlob("blob1", sharedData),
    276    text[1],
    277    "Expected text for blob1 in parent ppmm"
    278  );
    279 
    280  equal(
    281    await readBlob("blob0"),
    282    text[0],
    283    "Expected text for blob0 in parent cpmm"
    284  );
    285  equal(
    286    await readBlob("blob1"),
    287    text[1],
    288    "Expected text for blob1 in parent cpmm"
    289  );
    290 
    291  equal(
    292    await contentPage.spawn(["blob0"], readBlob),
    293    text[0],
    294    "Expected text for blob0 in child 1 cpmm"
    295  );
    296  equal(
    297    await contentPage.spawn(["blob1"], readBlob),
    298    text[1],
    299    "Expected text for blob1 in child 1 cpmm"
    300  );
    301 
    302  // Start a second child process
    303  Services.prefs.setIntPref(PROCESS_COUNT_PREF, 2);
    304 
    305  let page2 = await loadContentPage();
    306 
    307  equal(
    308    await page2.spawn(["blob0"], readBlob),
    309    text[0],
    310    "Expected text for blob0 in child 2 cpmm"
    311  );
    312  equal(
    313    await page2.spawn(["blob1"], readBlob),
    314    text[1],
    315    "Expected text for blob1 in child 2 cpmm"
    316  );
    317 
    318  sharedData.set("blob0", blobs[2]);
    319 
    320  equal(
    321    await readBlob("blob0", sharedData),
    322    text[2],
    323    "Expected text for blob0 in parent ppmm"
    324  );
    325 
    326  sharedData.flush();
    327 
    328  equal(
    329    await readBlob("blob0", sharedData),
    330    text[2],
    331    "Expected text for blob0 in parent ppmm"
    332  );
    333  equal(
    334    await readBlob("blob1", sharedData),
    335    text[1],
    336    "Expected text for blob1 in parent ppmm"
    337  );
    338 
    339  equal(
    340    await readBlob("blob0"),
    341    text[2],
    342    "Expected text for blob0 in parent cpmm"
    343  );
    344  equal(
    345    await readBlob("blob1"),
    346    text[1],
    347    "Expected text for blob1 in parent cpmm"
    348  );
    349 
    350  equal(
    351    await contentPage.spawn(["blob0"], readBlob),
    352    text[2],
    353    "Expected text for blob0 in child 1 cpmm"
    354  );
    355  equal(
    356    await contentPage.spawn(["blob1"], readBlob),
    357    text[1],
    358    "Expected text for blob1 in child 1 cpmm"
    359  );
    360 
    361  equal(
    362    await page2.spawn(["blob0"], readBlob),
    363    text[2],
    364    "Expected text for blob0 in child 2 cpmm"
    365  );
    366  equal(
    367    await page2.spawn(["blob1"], readBlob),
    368    text[1],
    369    "Expected text for blob1 in child 2 cpmm"
    370  );
    371 
    372  deepEqual(
    373    await page2.spawn(["data"], getKey),
    374    data,
    375    "Expected data for data key in child 2 cpmm"
    376  );
    377 });