tor-browser

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

structured-clone.any.js (9001B)


      1 // META: title=Indexed DB and Structured Serializing/Deserializing
      2 // META: timeout=long
      3 // META: script=resources/support-promises.js
      4 // META: script=/common/subset-tests.js
      5 // META: variant=?1-20
      6 // META: variant=?21-40
      7 // META: variant=?41-60
      8 // META: variant=?61-80
      9 // META: variant=?81-100
     10 // META: variant=?101-last
     11 'use strict';
     12 
     13 // Tests Indexed DB coverage of HTML's Safe "passing of structured data"
     14 // https://html.spec.whatwg.org/multipage/structured-data.html
     15 
     16 function describe(value) {
     17  let type, str;
     18  if (typeof value === 'object' && value) {
     19    type = Object.getPrototypeOf(value).constructor.name;
     20    // Handle Number(-0), etc.
     21    str = Object.is(value.valueOf(), -0) ? '-0' : String(value);
     22  } else {
     23    type = typeof value;
     24    // Handle primitive -0.
     25    str = Object.is(value, -0) ? '-0' : String(value);
     26  }
     27  return `${type}: ${str}`;
     28 }
     29 
     30 function cloneTest(value, verifyFunc) {
     31  subsetTest(promise_test, async t => {
     32    const db = await createDatabase(t, db => {
     33      const store = db.createObjectStore('store');
     34      // This index is not used, but evaluating key path on each put()
     35      // call will exercise (de)serialization.
     36      store.createIndex('index', 'dummyKeyPath');
     37    });
     38    t.add_cleanup(() => {
     39      if (db) {
     40        db.close();
     41        indexedDB.deleteDatabase(db.name);
     42      }
     43    });
     44    const tx = db.transaction('store', 'readwrite');
     45    const store = tx.objectStore('store');
     46    await promiseForRequest(t, store.put(value, 'key'));
     47    const result = await promiseForRequest(t, store.get('key'));
     48    // Because the async verifyFunc may await async values that are independent
     49    // of the transaction lifetime (ex: blob.text()), we must only await it
     50    // after adding listeners to the transaction.
     51    await promiseForTransaction(t, tx);
     52    await verifyFunc(value, result);
     53  }, describe(value));
     54 }
     55 
     56 // Specialization of cloneTest() for objects, with common asserts.
     57 function cloneObjectTest(value, verifyFunc) {
     58  cloneTest(value, async (orig, clone) => {
     59    assert_not_equals(orig, clone);
     60    assert_equals(typeof clone, 'object');
     61    assert_equals(Object.getPrototypeOf(orig), Object.getPrototypeOf(clone));
     62    await verifyFunc(orig, clone);
     63  });
     64 }
     65 
     66 function cloneFailureTest(value) {
     67  subsetTest(promise_test, async t => {
     68    const db = await createDatabase(t, db => {
     69      db.createObjectStore('store');
     70    });
     71    t.add_cleanup(() => {
     72      if (db) {
     73        db.close();
     74        indexedDB.deleteDatabase(db.name);
     75      }
     76    });
     77    const tx = db.transaction('store', 'readwrite');
     78    const store = tx.objectStore('store');
     79    assert_throws_dom('DataCloneError', () => store.put(value, 'key'));
     80  }, 'Not serializable: ' + describe(value));
     81 }
     82 
     83 //
     84 // ECMAScript types
     85 //
     86 
     87 // Primitive values: Undefined, Null, Boolean, Number, BigInt, String
     88 const booleans = [false, true];
     89 const numbers = [
     90  NaN,
     91  -Infinity,
     92  -Number.MAX_VALUE,
     93  -0xffffffff,
     94  -0x80000000,
     95  -0x7fffffff,
     96  -1,
     97  -Number.MIN_VALUE,
     98  -0,
     99  0,
    100  1,
    101  Number.MIN_VALUE,
    102  0x7fffffff,
    103  0x80000000,
    104  0xffffffff,
    105  Number.MAX_VALUE,
    106  Infinity,
    107 ];
    108 const bigints = [
    109  -12345678901234567890n,
    110  -1n,
    111  0n,
    112  1n,
    113  12345678901234567890n,
    114 ];
    115 const strings = [
    116  '',
    117  'this is a sample string',
    118  'null(\0)',
    119 ];
    120 
    121 [undefined, null].concat(booleans, numbers, bigints, strings)
    122  .forEach(value => cloneTest(value, (orig, clone) => {
    123    assert_equals(orig, clone);
    124  }));
    125 
    126 // "Primitive" Objects (Boolean, Number, BigInt, String)
    127 [].concat(booleans, numbers, bigints, strings)
    128  .forEach(value => cloneObjectTest(Object(value), (orig, clone) => {
    129    assert_equals(orig.valueOf(), clone.valueOf());
    130  }));
    131 
    132 // Dates
    133 [
    134  new Date(-1e13),
    135  new Date(-1e12),
    136  new Date(-1e9),
    137  new Date(-1e6),
    138  new Date(-1e3),
    139  new Date(0),
    140  new Date(1e3),
    141  new Date(1e6),
    142  new Date(1e9),
    143  new Date(1e12),
    144  new Date(1e13)
    145 ].forEach(value => cloneTest(value, (orig, clone) => {
    146    assert_not_equals(orig, clone);
    147    assert_equals(typeof clone, 'object');
    148    assert_equals(Object.getPrototypeOf(orig), Object.getPrototypeOf(clone));
    149    assert_equals(orig.valueOf(), clone.valueOf());
    150  }));
    151 
    152 // Regular Expressions
    153 [
    154  new RegExp(),
    155  /abc/,
    156  /abc/g,
    157  /abc/i,
    158  /abc/gi,
    159  /abc/m,
    160  /abc/mg,
    161  /abc/mi,
    162  /abc/mgi,
    163  /abc/gimsuy,
    164 ].forEach(value => cloneObjectTest(value, (orig, clone) => {
    165  assert_equals(orig.toString(), clone.toString());
    166 }));
    167 
    168 // ArrayBuffer
    169 cloneObjectTest(new Uint8Array([0, 1, 254, 255]).buffer, (orig, clone) => {
    170  assert_array_equals(new Uint8Array(orig), new Uint8Array(clone));
    171 });
    172 
    173 // TODO SharedArrayBuffer
    174 
    175 // Array Buffer Views
    176 let byteArrays = [
    177  new Uint8Array([]),
    178  new Uint8Array([0, 1, 254, 255]),
    179  new Uint16Array([0x0000, 0x0001, 0xFFFE, 0xFFFF]),
    180  new Uint32Array([0x00000000, 0x00000001, 0xFFFFFFFE, 0xFFFFFFFF]),
    181  new Int8Array([0, 1, 254, 255]),
    182  new Int16Array([0x0000, 0x0001, 0xFFFE, 0xFFFF]),
    183  new Int32Array([0x00000000, 0x00000001, 0xFFFFFFFE, 0xFFFFFFFF]),
    184  new Uint8ClampedArray([0, 1, 254, 255]),
    185  new Float32Array([-Infinity, -1.5, -1, -0.5, 0, 0.5, 1, 1.5, Infinity, NaN]),
    186  new Float64Array([-Infinity, -Number.MAX_VALUE, -Number.MIN_VALUE, 0,
    187                    Number.MIN_VALUE, Number.MAX_VALUE, Infinity, NaN])
    188 ]
    189 
    190 if (typeof Float16Array !== 'undefined') {
    191  byteArrays.push(
    192      new Float16Array([-Infinity, -1.5, -1, -0.5, 0, 0.5, 1, 1.5, Infinity, NaN]));
    193 }
    194 
    195 byteArrays.forEach(value => cloneObjectTest(value, (orig, clone) => {
    196  assert_array_equals(orig, clone);
    197 }));
    198 
    199 // Map
    200 cloneObjectTest(new Map([[1,2],[3,4]]), (orig, clone) => {
    201  assert_array_equals([...orig.keys()], [...clone.keys()]);
    202  assert_array_equals([...orig.values()], [...clone.values()]);
    203 });
    204 
    205 // Set
    206 cloneObjectTest(new Set([1,2,3,4]), (orig, clone) => {
    207  assert_array_equals([...orig.values()], [...clone.values()]);
    208 });
    209 
    210 // Error
    211 [
    212  new Error(),
    213  new Error('abc', 'def'),
    214  new EvalError(),
    215  new EvalError('ghi', 'jkl'),
    216  new RangeError(),
    217  new RangeError('ghi', 'jkl'),
    218  new ReferenceError(),
    219  new ReferenceError('ghi', 'jkl'),
    220  new SyntaxError(),
    221  new SyntaxError('ghi', 'jkl'),
    222  new TypeError(),
    223  new TypeError('ghi', 'jkl'),
    224  new URIError(),
    225  new URIError('ghi', 'jkl'),
    226 ].forEach(value => cloneObjectTest(value, (orig, clone) => {
    227  assert_equals(orig.name, clone.name);
    228  assert_equals(orig.message, clone.message);
    229 }));
    230 
    231 // Arrays
    232 [
    233  [],
    234  [1,2,3],
    235  Object.assign(
    236    ['foo', 'bar'],
    237    {10: true, 11: false, 20: 123, 21: 456, 30: null}),
    238  Object.assign(
    239    ['foo', 'bar'],
    240    {a: true, b: false, foo: 123, bar: 456, '': null}),
    241 ].forEach(value => cloneObjectTest(value, (orig, clone) => {
    242  assert_array_equals(orig, clone);
    243  assert_array_equals(Object.keys(orig), Object.keys(clone));
    244  Object.keys(orig).forEach(key => {
    245    assert_equals(orig[key], clone[key], `Property ${key}`);
    246  });
    247 }));
    248 
    249 // Objects
    250 cloneObjectTest({foo: true, bar: false}, (orig, clone) => {
    251  assert_array_equals(Object.keys(orig), Object.keys(clone));
    252  Object.keys(orig).forEach(key => {
    253    assert_equals(orig[key], clone[key], `Property ${key}`);
    254  });
    255 });
    256 
    257 //
    258 // [Serializable] Platform objects
    259 //
    260 
    261 // TODO: Test these additional interfaces:
    262 // * DOMQuad
    263 // * DOMException
    264 // * RTCCertificate
    265 
    266 // Geometry types
    267 [
    268  new DOMMatrix(),
    269  new DOMMatrixReadOnly(),
    270  new DOMPoint(),
    271  new DOMPointReadOnly(),
    272  new DOMRect,
    273  new DOMRectReadOnly(),
    274 ].forEach(value => cloneObjectTest(value, (orig, clone) => {
    275  Object.keys(Object.getPrototypeOf(orig)).forEach(key => {
    276    assert_equals(orig[key], clone[key], `Property ${key}`);
    277  });
    278 }));
    279 
    280 // ImageData
    281 const image_data = new ImageData(8, 8);
    282 for (let i = 0; i < 256; ++i) {
    283  image_data.data[i] = i;
    284 }
    285 cloneObjectTest(image_data, (orig, clone) => {
    286  assert_equals(orig.width, clone.width);
    287  assert_equals(orig.height, clone.height);
    288  assert_array_equals(orig.data, clone.data);
    289 });
    290 
    291 // Blob
    292 cloneObjectTest(
    293  new Blob(['This is a test.'], {type: 'a/b'}),
    294  async (orig, clone) => {
    295    assert_equals(orig.size, clone.size);
    296    assert_equals(orig.type, clone.type);
    297    assert_equals(await orig.text(), await clone.text());
    298  });
    299 
    300 // File
    301 cloneObjectTest(
    302  new File(['This is a test.'], 'foo.txt', {type: 'c/d'}),
    303  async (orig, clone) => {
    304    assert_equals(orig.size, clone.size);
    305    assert_equals(orig.type, clone.type);
    306    assert_equals(orig.name, clone.name);
    307    assert_equals(orig.lastModified, clone.lastModified);
    308    assert_equals(await orig.text(), await clone.text());
    309  });
    310 
    311 
    312 // FileList - exposed in Workers, but not constructable.
    313 if ('document' in self) {
    314  // TODO: Test with populated list.
    315  cloneObjectTest(
    316    Object.assign(document.createElement('input'),
    317                  {type: 'file', multiple: true}).files,
    318    async (orig, clone) => {
    319      assert_equals(orig.length, clone.length);
    320    });
    321 }
    322 
    323 //
    324 // Non-serializable types
    325 //
    326 [
    327  // ECMAScript types
    328  function() {},
    329  Symbol('desc'),
    330 
    331  // Non-[Serializable] platform objects
    332  self,
    333  new Event(''),
    334  new MessageChannel()
    335 ].forEach(cloneFailureTest);