tor-browser

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

head.js (18025B)


      1 /**
      2 * Any copyright is dedicated to the Public Domain.
      3 * http://creativecommons.org/publicdomain/zero/1.0/
      4 */
      5 
      6 const NS_OK = Cr.NS_OK;
      7 const NS_ERROR_FAILURE = Cr.NS_ERROR_FAILURE;
      8 const NS_ERROR_UNEXPECTED = Cr.NS_ERROR_UNEXPECTED;
      9 const NS_ERROR_FILE_NO_DEVICE_SPACE = Cr.NS_ERROR_FILE_NO_DEVICE_SPACE;
     10 
     11 const loggingEnabled = false;
     12 
     13 var testGenerator;
     14 
     15 loadScript("dom/quota/test/common/xpcshell.js");
     16 
     17 function log(msg) {
     18  if (loggingEnabled) {
     19    info(msg);
     20  }
     21 }
     22 
     23 function is(a, b, msg) {
     24  Assert.equal(a, b, msg);
     25 }
     26 
     27 function ok(cond, msg) {
     28  Assert.ok(!!cond, msg);
     29 }
     30 
     31 function todo(cond, msg) {
     32  todo_check_true(cond);
     33 }
     34 
     35 function run_test() {
     36  runTest();
     37 }
     38 
     39 if (!this.runTest) {
     40  this.runTest = function () {
     41    do_get_profile();
     42 
     43    enableStorageTesting();
     44    enableTesting();
     45 
     46    // In order to support converting tests to using async functions from using
     47    // generator functions, we detect async functions by checking the name of
     48    // function's constructor.
     49    Assert.strictEqual(
     50      typeof testSteps,
     51      "function",
     52      "There should be a testSteps function"
     53    );
     54    if (testSteps.constructor.name === "AsyncFunction") {
     55      // Do run our existing cleanup function that would normally be called by
     56      // the generator's call to finishTest().
     57      registerCleanupFunction(function () {
     58        resetStorageTesting();
     59        resetTesting();
     60      });
     61 
     62      add_task(testSteps);
     63 
     64      // Since we defined run_test, we must invoke run_next_test() to start the
     65      // async test.
     66      run_next_test();
     67    } else {
     68      Assert.strictEqual(
     69        testSteps.constructor.name,
     70        "GeneratorFunction",
     71        "Unsupported function type"
     72      );
     73 
     74      do_test_pending();
     75 
     76      testGenerator = testSteps();
     77      testGenerator.next();
     78    }
     79  };
     80 }
     81 
     82 function finishTest() {
     83  resetStorageTesting();
     84  resetTesting();
     85 
     86  executeSoon(function () {
     87    do_test_finished();
     88  });
     89 }
     90 
     91 function grabArgAndContinueHandler(arg) {
     92  testGenerator.next(arg);
     93 }
     94 
     95 function continueToNextStep() {
     96  executeSoon(function () {
     97    testGenerator.next();
     98  });
     99 }
    100 
    101 function continueToNextStepSync() {
    102  testGenerator.next();
    103 }
    104 
    105 function enableTesting() {
    106  SpecialPowers.setBoolPref(
    107    "dom.storage.enable_unsupported_legacy_implementation",
    108    false
    109  );
    110 }
    111 
    112 function resetTesting() {
    113  SpecialPowers.clearUserPref(
    114    "dom.storage.enable_unsupported_legacy_implementation"
    115  );
    116 }
    117 
    118 function setGlobalLimit(globalLimit) {
    119  SpecialPowers.setIntPref(
    120    "dom.quotaManager.temporaryStorage.fixedLimit",
    121    globalLimit
    122  );
    123 }
    124 
    125 function resetGlobalLimit() {
    126  SpecialPowers.clearUserPref("dom.quotaManager.temporaryStorage.fixedLimit");
    127 }
    128 
    129 function storageInitialized(callback) {
    130  let request = SpecialPowers._getQuotaManager().storageInitialized();
    131  request.callback = callback;
    132 
    133  return request;
    134 }
    135 
    136 function persistentStorageInitialized(callback) {
    137  let request = SpecialPowers._getQuotaManager().persistentStorageInitialized();
    138  request.callback = callback;
    139 
    140  return request;
    141 }
    142 
    143 function temporaryStorageInitialized(callback) {
    144  let request = SpecialPowers._getQuotaManager().temporaryStorageInitialized();
    145  request.callback = callback;
    146 
    147  return request;
    148 }
    149 
    150 function persistentOriginInitialized(principal, callback) {
    151  let request =
    152    SpecialPowers._getQuotaManager().persistentOriginInitialized(principal);
    153  request.callback = callback;
    154 
    155  return request;
    156 }
    157 
    158 function temporaryOriginInitialized(persistence, principal, callback) {
    159  let request = SpecialPowers._getQuotaManager().temporaryOriginInitialized(
    160    persistence,
    161    principal
    162  );
    163  request.callback = callback;
    164 
    165  return request;
    166 }
    167 
    168 function init(callback) {
    169  let request = SpecialPowers._getQuotaManager().init();
    170  request.callback = callback;
    171 
    172  return request;
    173 }
    174 
    175 function initializePersistentStorage(callback) {
    176  let request = SpecialPowers._getQuotaManager().initializePersistentStorage();
    177  request.callback = callback;
    178 
    179  return request;
    180 }
    181 
    182 function initTemporaryStorage(callback) {
    183  let request = SpecialPowers._getQuotaManager().initTemporaryStorage();
    184  request.callback = callback;
    185 
    186  return request;
    187 }
    188 
    189 function initPersistentOrigin(principal, callback) {
    190  let request =
    191    SpecialPowers._getQuotaManager().initializePersistentOrigin(principal);
    192  request.callback = callback;
    193 
    194  return request;
    195 }
    196 
    197 function initTemporaryOrigin(
    198  persistence,
    199  principal,
    200  createIfNonExistent = true,
    201  callback
    202 ) {
    203  let request = SpecialPowers._getQuotaManager().initializeTemporaryOrigin(
    204    persistence,
    205    principal,
    206    createIfNonExistent
    207  );
    208  request.callback = callback;
    209 
    210  return request;
    211 }
    212 
    213 function initPersistentClient(principal, client, callback) {
    214  let request = SpecialPowers._getQuotaManager().initializePersistentClient(
    215    principal,
    216    client
    217  );
    218  request.callback = callback;
    219 
    220  return request;
    221 }
    222 
    223 function initTemporaryClient(
    224  persistence,
    225  principal,
    226  client,
    227  createIfNonExistent = true,
    228  callback
    229 ) {
    230  let request = SpecialPowers._getQuotaManager().initializeTemporaryClient(
    231    persistence,
    232    principal,
    233    client,
    234    createIfNonExistent
    235  );
    236  request.callback = callback;
    237 
    238  return request;
    239 }
    240 
    241 function getFullOriginMetadata(persistence, principal, callback) {
    242  const request = SpecialPowers._getQuotaManager().getFullOriginMetadata(
    243    persistence,
    244    principal
    245  );
    246  request.callback = callback;
    247 
    248  return request;
    249 }
    250 
    251 function clearClient(principal, client, persistence, callback) {
    252  let request = SpecialPowers._getQuotaManager().clearStoragesForClient(
    253    principal,
    254    client,
    255    persistence
    256  );
    257  request.callback = callback;
    258 
    259  return request;
    260 }
    261 
    262 function clearOrigin(principal, persistence, callback) {
    263  let request = SpecialPowers._getQuotaManager().clearStoragesForPrincipal(
    264    principal,
    265    persistence
    266  );
    267  request.callback = callback;
    268 
    269  return request;
    270 }
    271 
    272 function clearOriginsByPrefix(principal, persistence, callback) {
    273  let request = SpecialPowers._getQuotaManager().clearStoragesForOriginPrefix(
    274    principal,
    275    persistence
    276  );
    277  request.callback = callback;
    278 
    279  return request;
    280 }
    281 
    282 function clearPrivateBrowsing(callback) {
    283  let request =
    284    SpecialPowers._getQuotaManager().clearStoragesForPrivateBrowsing();
    285  request.callback = callback;
    286 
    287  return request;
    288 }
    289 
    290 function resetClient(principal, client) {
    291  let request = Services.qms.resetStoragesForClient(
    292    principal,
    293    client,
    294    "default"
    295  );
    296 
    297  return request;
    298 }
    299 
    300 function persist(principal, callback) {
    301  let request = SpecialPowers._getQuotaManager().persist(principal);
    302  request.callback = callback;
    303 
    304  return request;
    305 }
    306 
    307 function persisted(principal, callback) {
    308  let request = SpecialPowers._getQuotaManager().persisted(principal);
    309  request.callback = callback;
    310 
    311  return request;
    312 }
    313 
    314 function estimateOrigin(principal, callback) {
    315  let request = SpecialPowers._getQuotaManager().estimate(principal);
    316  request.callback = callback;
    317 
    318  return request;
    319 }
    320 
    321 function listOrigins(callback) {
    322  let request = SpecialPowers._getQuotaManager().listOrigins(callback);
    323  request.callback = callback;
    324 
    325  return request;
    326 }
    327 
    328 function getPersistedFromMetadata(readBuffer) {
    329  const persistedPosition = 8; // Persisted state is stored in the 9th byte
    330  let view =
    331    readBuffer instanceof Uint8Array ? readBuffer : new Uint8Array(readBuffer);
    332 
    333  return !!view[persistedPosition];
    334 }
    335 
    336 function grabResultAndContinueHandler(request) {
    337  testGenerator.next(request.result);
    338 }
    339 
    340 function grabUsageAndContinueHandler(request) {
    341  testGenerator.next(request.result.usage);
    342 }
    343 
    344 function getUsage(usageHandler, getAll) {
    345  let request = SpecialPowers._getQuotaManager().getUsage(usageHandler, getAll);
    346 
    347  return request;
    348 }
    349 
    350 function getOriginUsage(principal) {
    351  let request = Services.qms.getUsageForPrincipal(principal, function () {});
    352 
    353  return request;
    354 }
    355 
    356 function getCachedOriginUsage(principal) {
    357  let request = Services.qms.getCachedUsageForPrincipal(
    358    principal,
    359    function () {}
    360  );
    361 
    362  return request;
    363 }
    364 
    365 function getCachedOriginUsage(principal) {
    366  let request = Services.qms.getCachedUsageForPrincipal(principal);
    367 
    368  return request;
    369 }
    370 
    371 function getCurrentUsage(usageHandler) {
    372  let principal = Cc["@mozilla.org/systemprincipal;1"].createInstance(
    373    Ci.nsIPrincipal
    374  );
    375  let request = SpecialPowers._getQuotaManager().getUsageForPrincipal(
    376    principal,
    377    usageHandler
    378  );
    379 
    380  return request;
    381 }
    382 
    383 function getPrincipal(url, attr = {}) {
    384  let uri = Cc["@mozilla.org/network/io-service;1"]
    385    .getService(Ci.nsIIOService)
    386    .newURI(url);
    387  let ssm = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(
    388    Ci.nsIScriptSecurityManager
    389  );
    390  return ssm.createContentPrincipal(uri, attr);
    391 }
    392 
    393 var SpecialPowers = {
    394  getBoolPref(prefName) {
    395    return this._getPrefs().getBoolPref(prefName);
    396  },
    397 
    398  setBoolPref(prefName, value) {
    399    this._getPrefs().setBoolPref(prefName, value);
    400  },
    401 
    402  setIntPref(prefName, value) {
    403    this._getPrefs().setIntPref(prefName, value);
    404  },
    405 
    406  clearUserPref(prefName) {
    407    this._getPrefs().clearUserPref(prefName);
    408  },
    409 
    410  _getPrefs() {
    411    let prefService = Cc["@mozilla.org/preferences-service;1"].getService(
    412      Ci.nsIPrefService
    413    );
    414    return prefService.getBranch(null);
    415  },
    416 
    417  _getQuotaManager() {
    418    return Cc["@mozilla.org/dom/quota-manager-service;1"].getService(
    419      Ci.nsIQuotaManagerService
    420    );
    421  },
    422 };
    423 
    424 function installPackages(packageRelativePaths) {
    425  if (packageRelativePaths.length != 2) {
    426    throw new Error("Unsupported number of package relative paths");
    427  }
    428 
    429  for (const packageRelativePath of packageRelativePaths) {
    430    installPackage(packageRelativePath);
    431  }
    432 }
    433 
    434 // Take current storage structure on disk and compare it with the expected
    435 // structure. The expected structure is defined in JSON and consists of a per
    436 // test package definition and a shared package definition. The shared package
    437 // definition should contain unknown stuff which needs to be properly handled
    438 // in all situations.
    439 function verifyStorage(packageDefinitionRelativePaths, key, sharedKey) {
    440  if (packageDefinitionRelativePaths.length != 2) {
    441    throw new Error("Unsupported number of package definition relative paths");
    442  }
    443 
    444  function verifyEntries(entries, name, indent = "") {
    445    log(`${indent}Verifying ${name} entries`);
    446 
    447    indent += "  ";
    448 
    449    for (const entry of entries) {
    450      const maybeName = entry.name;
    451 
    452      log(`${indent}Verifying entry ${maybeName}`);
    453 
    454      let hasName = false;
    455      let hasDir = false;
    456      let hasEntries = false;
    457 
    458      for (const property in entry) {
    459        switch (property) {
    460          case "note":
    461          case "todo":
    462            break;
    463 
    464          case "name":
    465            hasName = true;
    466            break;
    467 
    468          case "dir":
    469            hasDir = true;
    470            break;
    471 
    472          case "entries":
    473            hasEntries = true;
    474            break;
    475 
    476          default:
    477            throw new Error(`Unknown property ${property}`);
    478        }
    479      }
    480 
    481      if (!hasName) {
    482        throw new Error("An entry must have the name property");
    483      }
    484 
    485      if (!hasDir) {
    486        throw new Error("An entry must have the dir property");
    487      }
    488 
    489      if (hasEntries && !entry.dir) {
    490        throw new Error("An entry can't have entries if it's not a directory");
    491      }
    492 
    493      if (hasEntries) {
    494        verifyEntries(entry.entries, entry.name, indent);
    495      }
    496    }
    497  }
    498 
    499  function getCurrentEntries() {
    500    log("Getting current entries");
    501 
    502    function getEntryForFile(file) {
    503      let entry = {
    504        name: file.leafName,
    505        dir: file.isDirectory(),
    506      };
    507 
    508      if (file.isDirectory()) {
    509        const enumerator = file.directoryEntries;
    510        let nextFile;
    511        while ((nextFile = enumerator.nextFile)) {
    512          if (!entry.entries) {
    513            entry.entries = [];
    514          }
    515          entry.entries.push(getEntryForFile(nextFile));
    516        }
    517      }
    518 
    519      return entry;
    520    }
    521 
    522    let entries = [];
    523 
    524    let file = getRelativeFile("indexedDB");
    525    if (file.exists()) {
    526      entries.push(getEntryForFile(file));
    527    }
    528 
    529    file = getRelativeFile("storage");
    530    if (file.exists()) {
    531      entries.push(getEntryForFile(file));
    532    }
    533 
    534    file = getRelativeFile("storage.sqlite");
    535    if (file.exists()) {
    536      entries.push(getEntryForFile(file));
    537    }
    538 
    539    verifyEntries(entries, "current");
    540 
    541    return entries;
    542  }
    543 
    544  function getEntriesFromPackageDefinition(
    545    packageDefinitionRelativePath,
    546    lookupKey
    547  ) {
    548    log(`Getting ${lookupKey} entries from ${packageDefinitionRelativePath}`);
    549 
    550    const currentDir = Services.dirsvc.get("CurWorkD", Ci.nsIFile);
    551    const file = getRelativeFile(
    552      packageDefinitionRelativePath + ".json",
    553      currentDir
    554    );
    555 
    556    const fileInputStream = Cc[
    557      "@mozilla.org/network/file-input-stream;1"
    558    ].createInstance(Ci.nsIFileInputStream);
    559    fileInputStream.init(file, -1, -1, 0);
    560 
    561    const scriptableInputStream = Cc[
    562      "@mozilla.org/scriptableinputstream;1"
    563    ].createInstance(Ci.nsIScriptableInputStream);
    564    scriptableInputStream.init(fileInputStream);
    565 
    566    const data = scriptableInputStream.readBytes(
    567      scriptableInputStream.available()
    568    );
    569 
    570    const obj = JSON.parse(data);
    571 
    572    const result = obj.find(({ key: elementKey }) => elementKey == lookupKey);
    573 
    574    if (!result) {
    575      throw new Error("The file doesn't contain an element for given key");
    576    }
    577 
    578    if (!result.entries) {
    579      throw new Error("The element doesn't have the entries property");
    580    }
    581 
    582    verifyEntries(result.entries, lookupKey);
    583 
    584    return result.entries;
    585  }
    586 
    587  function addSharedEntries(expectedEntries, sharedEntries, name, indent = "") {
    588    log(`${indent}Checking common ${name} entries`);
    589 
    590    indent += "  ";
    591 
    592    for (const sharedEntry of sharedEntries) {
    593      const expectedEntry = expectedEntries.find(
    594        ({ name: elementName }) => elementName == sharedEntry.name
    595      );
    596 
    597      if (expectedEntry) {
    598        log(`${indent}Checking common entry ${sharedEntry.name}`);
    599 
    600        if (!expectedEntry.dir || !sharedEntry.dir) {
    601          throw new Error("A common entry must be a directory");
    602        }
    603 
    604        if (!expectedEntry.entries && !sharedEntry.entries) {
    605          throw new Error("A common entry must not be a leaf");
    606        }
    607 
    608        if (sharedEntry.entries) {
    609          if (!expectedEntry.entries) {
    610            expectedEntry.entries = [];
    611          }
    612 
    613          addSharedEntries(
    614            expectedEntry.entries,
    615            sharedEntry.entries,
    616            sharedEntry.name,
    617            indent
    618          );
    619        }
    620      } else {
    621        log(`${indent}Adding entry ${sharedEntry.name}`);
    622        expectedEntries.push(sharedEntry);
    623      }
    624    }
    625  }
    626 
    627  function compareEntries(currentEntries, expectedEntries, name, indent = "") {
    628    log(`${indent}Comparing ${name} entries`);
    629 
    630    indent += "  ";
    631 
    632    if (currentEntries.length != expectedEntries.length) {
    633      throw new Error("Entries must have the same length");
    634    }
    635 
    636    for (const currentEntry of currentEntries) {
    637      log(`${indent}Comparing entry ${currentEntry.name}`);
    638 
    639      const expectedEntry = expectedEntries.find(
    640        ({ name: elementName }) => elementName == currentEntry.name
    641      );
    642 
    643      if (!expectedEntry) {
    644        throw new Error("Cannot find a matching entry");
    645      }
    646 
    647      if (expectedEntry.dir != currentEntry.dir) {
    648        throw new Error("The dir property doesn't match");
    649      }
    650 
    651      if (
    652        (expectedEntry.entries && !currentEntry.entries) ||
    653        (!expectedEntry.entries && currentEntry.entries)
    654      ) {
    655        throw new Error("The entries property doesn't match");
    656      }
    657 
    658      if (expectedEntry.entries) {
    659        compareEntries(
    660          currentEntry.entries,
    661          expectedEntry.entries,
    662          currentEntry.name,
    663          indent
    664        );
    665      }
    666    }
    667  }
    668 
    669  const currentEntries = getCurrentEntries();
    670 
    671  log("Stringified current entries: " + JSON.stringify(currentEntries));
    672 
    673  const expectedEntries = getEntriesFromPackageDefinition(
    674    packageDefinitionRelativePaths[0],
    675    key
    676  );
    677  const sharedEntries = getEntriesFromPackageDefinition(
    678    packageDefinitionRelativePaths[1],
    679    sharedKey ? sharedKey : key
    680  );
    681 
    682  addSharedEntries(expectedEntries, sharedEntries, key);
    683 
    684  log("Stringified expected entries: " + JSON.stringify(expectedEntries));
    685 
    686  compareEntries(currentEntries, expectedEntries, key);
    687 }
    688 
    689 async function verifyInitializationStatus(
    690  expectStorageIsInitialized,
    691  expectPersistentStorageIsInitialized,
    692  expectTemporaryStorageIsInitialized
    693 ) {
    694  if (!expectStorageIsInitialized && expectPersistentStorageIsInitialized) {
    695    throw new Error("Invalid expectation");
    696  }
    697 
    698  if (!expectStorageIsInitialized && expectTemporaryStorageIsInitialized) {
    699    throw new Error("Invalid expectation");
    700  }
    701 
    702  let request = storageInitialized();
    703  await requestFinished(request);
    704 
    705  const storageIsInitialized = request.result;
    706 
    707  request = persistentStorageInitialized();
    708  await requestFinished(request);
    709 
    710  const persistentStorageIsInitialized = request.result;
    711 
    712  request = temporaryStorageInitialized();
    713  await requestFinished(request);
    714 
    715  const temporaryStorageIsInitialized = request.result;
    716 
    717  ok(
    718    !(!storageIsInitialized && persistentStorageIsInitialized),
    719    "Initialization status is consistent"
    720  );
    721 
    722  ok(
    723    !(!storageIsInitialized && temporaryStorageIsInitialized),
    724    "Initialization status is consistent"
    725  );
    726 
    727  if (expectStorageIsInitialized) {
    728    ok(storageIsInitialized, "Storage is initialized");
    729  } else {
    730    ok(!storageIsInitialized, "Storage is not initialized");
    731  }
    732 
    733  if (expectPersistentStorageIsInitialized) {
    734    ok(persistentStorageIsInitialized, "Persistent storage is initialized");
    735  } else {
    736    ok(
    737      !persistentStorageIsInitialized,
    738      "Persistent storage is not initialized"
    739    );
    740  }
    741 
    742  if (expectTemporaryStorageIsInitialized) {
    743    ok(temporaryStorageIsInitialized, "Temporary storage is initialized");
    744  } else {
    745    ok(!temporaryStorageIsInitialized, "Temporary storage is not initialized");
    746  }
    747 }