tor-browser

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

browser_storage_dynamic_windows.js (11901B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 Services.scriptloader.loadSubScript(
      7  "chrome://mochitests/content/browser/devtools/server/tests/browser/storage-helpers.js",
      8  this
      9 );
     10 
     11 // beforeReload references an object representing the initialized state of the
     12 // storage actor.
     13 const beforeReload = {
     14  cookies: {
     15    "http://test1.example.org": ["c1", "cs2", "c3", "uc1"],
     16    "http://sectest1.example.org": ["uc1", "cs2"],
     17  },
     18  "indexed-db": {
     19    "http://test1.example.org": [
     20      JSON.stringify(["idb1", "obj1"]),
     21      JSON.stringify(["idb1", "obj2"]),
     22      JSON.stringify(["idb2", "obj3"]),
     23    ],
     24    "http://sectest1.example.org": [],
     25  },
     26  "local-storage": {
     27    "http://test1.example.org": ["ls1", "ls2"],
     28    "http://sectest1.example.org": ["iframe-u-ls1"],
     29  },
     30  "session-storage": {
     31    "http://test1.example.org": ["ss1"],
     32    "http://sectest1.example.org": ["iframe-u-ss1", "iframe-u-ss2"],
     33  },
     34 };
     35 
     36 // afterIframeAdded references the items added when an iframe containing storage
     37 // items is added to the page.
     38 const afterIframeAdded = {
     39  cookies: {
     40    "https://sectest1.example.org": [
     41      getCookieId("cs2", ".example.org", "/"),
     42      getCookieId(
     43        "sc1",
     44        "sectest1.example.org",
     45        "/browser/devtools/server/tests/browser"
     46      ),
     47    ],
     48    "http://sectest1.example.org": [
     49      getCookieId(
     50        "sc1",
     51        "sectest1.example.org",
     52        "/browser/devtools/server/tests/browser"
     53      ),
     54    ],
     55  },
     56  "indexed-db": {
     57    // empty because indexed db creation happens after the page load, so at
     58    // the time of window-ready, there was no indexed db present.
     59    "https://sectest1.example.org": [],
     60  },
     61  "local-storage": {
     62    "https://sectest1.example.org": ["iframe-s-ls1"],
     63  },
     64  "session-storage": {
     65    "https://sectest1.example.org": ["iframe-s-ss1"],
     66  },
     67 };
     68 
     69 // afterIframeRemoved references the items deleted when an iframe containing
     70 // storage items is removed from the page.
     71 const afterIframeRemoved = {
     72  cookies: {
     73    "http://sectest1.example.org": [],
     74  },
     75  "indexed-db": {
     76    "http://sectest1.example.org": [],
     77  },
     78  "local-storage": {
     79    "http://sectest1.example.org": [],
     80  },
     81  "session-storage": {
     82    "http://sectest1.example.org": [],
     83  },
     84 };
     85 
     86 add_task(async function () {
     87  const { commands } = await openTabAndSetupStorage(
     88    MAIN_DOMAIN + "storage-dynamic-windows.html"
     89  );
     90 
     91  const { resourceCommand } = commands;
     92  const { TYPES } = resourceCommand;
     93  const allResources = {};
     94  const onAvailable = resources => {
     95    for (const resource of resources) {
     96      is(
     97        resource.targetFront.targetType,
     98        commands.targetCommand.TYPES.FRAME,
     99        "Each storage resource has a valid 'targetFront' attribute"
    100      );
    101      // Because we have iframes, we have distinct targets, each spawning their own storage resource
    102      if (allResources[resource.resourceType]) {
    103        allResources[resource.resourceType].push(resource);
    104      } else {
    105        allResources[resource.resourceType] = [resource];
    106      }
    107    }
    108  };
    109  const parentProcessStorages = [TYPES.COOKIE, TYPES.INDEXED_DB];
    110  const contentProcessStorages = [TYPES.LOCAL_STORAGE, TYPES.SESSION_STORAGE];
    111  const allStorages = [...parentProcessStorages, ...contentProcessStorages];
    112  await resourceCommand.watchResources(allStorages, { onAvailable });
    113  is(
    114    Object.keys(allStorages).length,
    115    allStorages.length,
    116    "Got all the storage resources"
    117  );
    118 
    119  // Do a copy of all the initial storages as test function may spawn new resources for the same
    120  // type and override the initial ones.
    121  // We do not call unwatchResources as it would clear its cache and next call
    122  // to watchResources with ignoreExistingResources would break and reprocess all resources again.
    123  const initialResources = Object.assign({}, allResources);
    124 
    125  testWindowsBeforeReload(initialResources);
    126 
    127  await testAddIframe(commands, initialResources, {
    128    contentProcessStorages,
    129    parentProcessStorages,
    130  });
    131 
    132  await testRemoveIframe(commands, initialResources, {
    133    contentProcessStorages,
    134    parentProcessStorages,
    135  });
    136 
    137  await clearStorage();
    138 
    139  // Forcing GC/CC to get rid of docshells and windows created by this test.
    140  forceCollections();
    141  await commands.destroy();
    142  forceCollections();
    143 });
    144 
    145 function testWindowsBeforeReload(resources) {
    146  for (const storageType in beforeReload) {
    147    ok(resources[storageType], `${storageType} storage actor is present`);
    148 
    149    const hosts = {};
    150    for (const resource of resources[storageType]) {
    151      for (const [hostType, hostValues] of Object.entries(resource.hosts)) {
    152        if (!hosts[hostType]) {
    153          hosts[hostType] = [];
    154        }
    155 
    156        hosts[hostType].push(hostValues);
    157      }
    158    }
    159 
    160    // If this test is run with chrome debugging enabled we get an extra
    161    // key for "chrome". We don't want the test to fail in this case, so
    162    // ignore it.
    163    if (storageType == "indexedDB") {
    164      delete hosts.chrome;
    165    }
    166 
    167    is(
    168      Object.keys(hosts).length,
    169      Object.keys(beforeReload[storageType]).length,
    170      `Number of hosts for ${storageType} match`
    171    );
    172    for (const host in beforeReload[storageType]) {
    173      ok(hosts[host], `Host ${host} is present`);
    174    }
    175  }
    176 }
    177 
    178 /**
    179 * Wait for new storage resources to be created of the given types.
    180 */
    181 async function waitForNewResourcesAndUpdates(commands, resourceTypes) {
    182  // When fission is off, we don't expect any new resource
    183  if (resourceTypes.length === 0) {
    184    return { newResources: [], updates: [] };
    185  }
    186  const { resourceCommand } = commands;
    187  let resolve;
    188  const promise = new Promise(r => (resolve = r));
    189  const allResources = {};
    190  const allUpdates = {};
    191  const onAvailable = resources => {
    192    for (const resource of resources) {
    193      if (resource.resourceType in allResources) {
    194        ok(false, `Got multiple ${resource.resourceTypes} resources`);
    195      }
    196      allResources[resource.resourceType] = resource;
    197      ok(true, `Got resource for ${resource.resourceType}`);
    198 
    199      // Stop watching for resources when we got them all
    200      if (Object.keys(allResources).length == resourceTypes.length) {
    201        resourceCommand.unwatchResources(resourceTypes, {
    202          onAvailable,
    203        });
    204      }
    205 
    206      // But also listen for updates on each new resource
    207      resource.once("single-store-update").then(update => {
    208        ok(true, `Got updates for ${resource.resourceType}`);
    209        allUpdates[resource.resourceType] = update;
    210 
    211        // Resolve only once we got all the updates, for all the resources
    212        if (Object.keys(allUpdates).length == resourceTypes.length) {
    213          resolve({ newResources: allResources, updates: allUpdates });
    214        }
    215      });
    216    }
    217  };
    218  await resourceCommand.watchResources(resourceTypes, {
    219    onAvailable,
    220    ignoreExistingResources: true,
    221  });
    222  return promise;
    223 }
    224 
    225 /**
    226 * Wait for single-store-update events on all the given storage resources.
    227 */
    228 function waitForResourceUpdates(resources, resourceTypes) {
    229  const allUpdates = {};
    230  const promises = [];
    231  for (const type of resourceTypes) {
    232    // Resolves once any of the many resources for the given storage type updates
    233    const promise = Promise.any(
    234      resources[type].map(resource => resource.once("single-store-update"))
    235    );
    236    promise.then(update => {
    237      ok(true, `Got updates for ${type}`);
    238      allUpdates[type] = update;
    239    });
    240    promises.push(promise);
    241  }
    242  return Promise.all(promises).then(() => allUpdates);
    243 }
    244 
    245 async function testAddIframe(
    246  commands,
    247  resources,
    248  { contentProcessStorages, parentProcessStorages }
    249 ) {
    250  info("Testing if new iframe addition works properly");
    251 
    252  // Expect:
    253  // - new resources alongside single-store-update events for content process storages
    254  // - only single-store-update events for previous resources for parent process storages
    255  const onResources = waitForNewResourcesAndUpdates(
    256    commands,
    257    contentProcessStorages
    258  );
    259  const onUpdates = waitForResourceUpdates(resources, parentProcessStorages);
    260 
    261  await SpecialPowers.spawn(
    262    gBrowser.selectedBrowser,
    263    [ALT_DOMAIN_SECURED],
    264    secured => {
    265      const doc = content.document;
    266 
    267      const iframe = doc.createElement("iframe");
    268      iframe.src = secured + "storage-secured-iframe.html";
    269 
    270      doc.querySelector("body").appendChild(iframe);
    271    }
    272  );
    273 
    274  info("Wait for all resources");
    275  const { newResources, updates } = await onResources;
    276  info("Wait for all updates");
    277  const previousResourceUpdates = await onUpdates;
    278 
    279  for (const resourceType of contentProcessStorages) {
    280    const resource = newResources[resourceType];
    281    const expected = afterIframeAdded[resourceType];
    282    // The resource only comes with hosts, without any values.
    283    // Each host will be an empty array.
    284    Assert.deepEqual(
    285      Object.keys(resource.hosts),
    286      Object.keys(expected),
    287      `List of hosts for resource ${resourceType} is correct`
    288    );
    289    for (const host in resource.hosts) {
    290      is(
    291        resource.hosts[host].length,
    292        0,
    293        "For new resources, each host has no value and is an empty array"
    294      );
    295    }
    296    const update = updates[resourceType];
    297    const storageKey = resourceTypeToStorageKey(resourceType);
    298    Assert.deepEqual(
    299      update.added[storageKey],
    300      expected,
    301      "We get an update after the resource, with the host values"
    302    );
    303  }
    304 
    305  for (const resourceType of parentProcessStorages) {
    306    const expected = afterIframeAdded[resourceType];
    307    const update = previousResourceUpdates[resourceType];
    308    const storageKey = resourceTypeToStorageKey(resourceType);
    309    Assert.deepEqual(
    310      update.added[storageKey],
    311      expected,
    312      `We get an update after the resource ${resourceType}, with the host values`
    313    );
    314  }
    315 
    316  return newResources;
    317 }
    318 
    319 async function testRemoveIframe(
    320  commands,
    321  resources,
    322  { contentProcessStorages, parentProcessStorages }
    323 ) {
    324  info("Testing if iframe removal works properly");
    325 
    326  // We only get updates for parent process storages, content process storage
    327  // resources are wiped via their related target destruction.
    328  const onUpdates = waitForResourceUpdates(resources, parentProcessStorages);
    329 
    330  await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
    331    for (const iframe of content.document.querySelectorAll("iframe")) {
    332      if (iframe.src.startsWith("http:")) {
    333        iframe.remove();
    334        break;
    335      }
    336    }
    337  });
    338 
    339  info("Wait for all updates");
    340  const previousResourceUpdates = await onUpdates;
    341 
    342  for (const resourceType of parentProcessStorages) {
    343    const expected = afterIframeRemoved[resourceType];
    344    const update = previousResourceUpdates[resourceType];
    345    const storageKey = resourceTypeToStorageKey(resourceType);
    346    Assert.deepEqual(
    347      update.deleted[storageKey],
    348      expected,
    349      `We get an update after the resource ${resourceType}, with the host values`
    350    );
    351  }
    352 
    353  // The iframe target is destroyed, which ends up destroying related resources
    354  const destroyedResourceTypes = [];
    355  for (const storageType in resources) {
    356    for (const resource of resources[storageType]) {
    357      if (resource.isDestroyed()) {
    358        destroyedResourceTypes.push(resource.resourceType);
    359      }
    360    }
    361  }
    362  Assert.deepEqual(
    363    destroyedResourceTypes.sort(),
    364    contentProcessStorages.sort(),
    365    "Content process storage resources have been destroyed [local and session storages]"
    366  );
    367 }
    368 
    369 /**
    370 * single-store-update emits objects using attributes with old "storage key" namings,
    371 * which is different from resource type namings.
    372 */
    373 function resourceTypeToStorageKey(resourceType) {
    374  if (resourceType == "local-storage") {
    375    return "localStorage";
    376  }
    377  if (resourceType == "session-storage") {
    378    return "sessionStorage";
    379  }
    380  if (resourceType == "indexed-db") {
    381    return "indexedDB";
    382  }
    383  return resourceType;
    384 }