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