tor-browser

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

large-requests-abort.any.js (7184B)


      1 // META: title=IndexedDB: transactions with large request results are aborted correctly
      2 // META: global=window,worker
      3 // META: script=resources/support-promises.js
      4 // META: timeout=long
      5 
      6 // Spec: https://w3c.github.io/IndexedDB/#abort-transaction
      7 
      8 'use strict';
      9 
     10 // Should be large enough to trigger large value handling in the IndexedDB
     11 // engines that have special code paths for large values.
     12 const wrapThreshold = 128 * 1024;
     13 
     14 function populateStore(store) {
     15  store.put({id: 1, key: 'k1', value: largeValue(wrapThreshold, 1)});
     16  store.put({id: 2, key: 'k2', value: ['small-2']});
     17  store.put({id: 3, key: 'k3', value: largeValue(wrapThreshold, 3)});
     18  store.put({id: 4, key: 'k4', value: ['small-4']});
     19 }
     20 
     21 // Opens index cursors for operations that require open cursors.
     22 //
     23 // onsuccess is called if all cursors are opened successfully. Otherwise,
     24 // onerror will be called at least once.
     25 function openCursors(testCase, index, operations, onerror, onsuccess) {
     26  let pendingCursors = 0;
     27 
     28  for (let operation of operations) {
     29    const opcode = operation[0];
     30    const primaryKey = operation[1];
     31    let request;
     32    switch (opcode) {
     33      case 'continue':
     34        request =
     35            index.openCursor(IDBKeyRange.lowerBound(`k${primaryKey - 1}`));
     36        break;
     37      case 'continue-empty':
     38        // k4 is the last key in the data set, so calling continue() will get
     39        // the cursor past the end of the store.
     40        request = index.openCursor(IDBKeyRange.lowerBound('k4'));
     41        break;
     42      default:
     43        continue;
     44    }
     45 
     46    operation[2] = request;
     47    ++pendingCursors;
     48 
     49    request.onsuccess = testCase.step_func(() => {
     50      --pendingCursors;
     51      if (!pendingCursors)
     52        onsuccess();
     53    });
     54    request.onerror = testCase.step_func(onerror);
     55  }
     56 
     57  if (!pendingCursors)
     58    onsuccess();
     59 }
     60 
     61 function doOperation(testCase, store, index, operation, requestId, results) {
     62  const opcode = operation[0];
     63  const primaryKey = operation[1];
     64  const cursor = operation[2];
     65 
     66  return new Promise((resolve, reject) => {
     67    let request;
     68    switch (opcode) {
     69      case 'add':  // Tests returning a primary key.
     70        request =
     71            store.add({key: `k${primaryKey}`, value: [`small-${primaryKey}`]});
     72        break;
     73      case 'put':  // Tests returning a primary key.
     74        request =
     75            store.put({key: `k${primaryKey}`, value: [`small-${primaryKey}`]});
     76        break;
     77      case 'put-with-id':  // Tests returning success or a primary key.
     78        request = store.put({
     79          key: `k${primaryKey}`,
     80          value: [`small-${primaryKey}`],
     81          id: primaryKey
     82        });
     83        break;
     84      case 'get':        // Tests returning a value.
     85      case 'get-empty':  // Tests returning undefined.
     86        request = store.get(primaryKey);
     87        break;
     88      case 'getall':  // Tests returning an array of values.
     89        request = store.getAll();
     90        break;
     91      case 'error':  // Tests returning an error.
     92        request =
     93            store.put({key: `k${primaryKey}`, value: [`small-${primaryKey}`]});
     94        break;
     95      case 'continue':        // Tests returning a key, primary key, and value.
     96      case 'continue-empty':  // Tests returning null.
     97        request = cursor;
     98        cursor.result.continue();
     99        break;
    100      case 'open':  // Tests returning a cursor, key, primary key, and value.
    101      case 'open-empty':  // Tests returning null.
    102        request = index.openCursor(IDBKeyRange.lowerBound(`k${primaryKey}`));
    103        break;
    104      case 'count':  // Tests returning a numeric result.
    105        request = index.count();
    106        break;
    107    };
    108 
    109    request.onsuccess = testCase.step_func(() => {
    110      reject(new Error(
    111          'requests should not succeed after the transaction is aborted'));
    112    });
    113    request.onerror = testCase.step_func(event => {
    114      event.preventDefault();
    115      results.push([requestId, request.error]);
    116      resolve();
    117    });
    118  });
    119 }
    120 
    121 function abortTest(label, operations) {
    122  promise_test(testCase => {
    123    return createDatabase(
    124               testCase,
    125               (database, transaction) => {
    126                 const store = database.createObjectStore(
    127                     'test-store', {autoIncrement: true, keyPath: 'id'});
    128                 store.createIndex('test-index', 'key', {unique: true});
    129                 populateStore(store);
    130               })
    131        .then(database => {
    132          const transaction = database.transaction(['test-store'], 'readwrite');
    133          const store = transaction.objectStore('test-store');
    134          const index = store.index('test-index');
    135          return new Promise((resolve, reject) => {
    136            openCursors(testCase, index, operations, reject, () => {
    137              const results = [];
    138              const promises = [];
    139              for (let i = 0; i < operations.length; ++i) {
    140                const promise = doOperation(
    141                    testCase, store, index, operations[i], i, results);
    142                promises.push(promise);
    143              };
    144              transaction.abort();
    145              resolve(Promise.all(promises).then(() => results));
    146            });
    147          });
    148        })
    149        .then(results => {
    150          assert_equals(
    151              results.length, operations.length,
    152              'Promise.all should resolve after all sub-promises resolve');
    153          for (let i = 0; i < operations.length; ++i) {
    154            assert_equals(
    155                results[i][0], i,
    156                'error event order should match request order');
    157            assert_equals(
    158                results[i][1].name, 'AbortError',
    159                'transaction aborting should result in AbortError on all requests');
    160          }
    161        });
    162  }, label);
    163 }
    164 
    165 abortTest('small values', [
    166  ['get', 2],
    167  ['count', null],
    168  ['continue-empty', null],
    169  ['get-empty', 5],
    170  ['add', 5],
    171  ['open', 2],
    172  ['continue', 2],
    173  ['get', 4],
    174  ['get-empty', 6],
    175  ['count', null],
    176  ['put-with-id', 5],
    177  ['put', 6],
    178  ['error', 3],
    179  ['continue', 4],
    180  ['count', null],
    181  ['get-empty', 7],
    182  ['open', 4],
    183  ['open-empty', 7],
    184  ['add', 7],
    185 ]);
    186 
    187 abortTest('large values', [
    188  ['open', 1],
    189  ['get', 1],
    190  ['getall', 4],
    191  ['get', 3],
    192  ['continue', 3],
    193  ['open', 3],
    194 ]);
    195 
    196 abortTest('large value followed by small values', [
    197  ['get', 1],
    198  ['getall', null],
    199  ['open', 2],
    200  ['continue-empty', null],
    201  ['get', 2],
    202  ['get-empty', 5],
    203  ['count', null],
    204  ['continue-empty', null],
    205  ['open-empty', 5],
    206  ['add', 5],
    207  ['error', 1],
    208  ['continue', 2],
    209  ['get-empty', 6],
    210  ['put-with-id', 5],
    211  ['put', 6],
    212 ]);
    213 
    214 abortTest('large values mixed with small values', [
    215  ['get', 1],
    216  ['get', 2],
    217  ['get-empty', 5],
    218  ['count', null],
    219  ['continue-empty', null],
    220  ['open', 1],
    221  ['continue', 2],
    222  ['open-empty', 5],
    223  ['getall', 4],
    224  ['open', 2],
    225  ['continue-empty', null],
    226  ['add', 5],
    227  ['get', 3],
    228  ['count', null],
    229  ['get-empty', 6],
    230  ['put-with-id', 5],
    231  ['getall', null],
    232  ['continue', 3],
    233  ['open-empty', 6],
    234  ['put', 6],
    235  ['error', 1],
    236  ['continue', 2],
    237  ['open', 4],
    238  ['get-empty', 7],
    239  ['count', null],
    240  ['continue', 3],
    241  ['add', 7],
    242  ['getall', null],
    243  ['error', 3],
    244  ['count', null],
    245 ]);