tor-browser

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

test_observablearray_proxyhandler.html (30415B)


      1 <!-- Any copyright is dedicated to the Public Domain.
      2 - http://creativecommons.org/publicdomain/zero/1.0/ -->
      3 <!DOCTYPE HTML>
      4 <html>
      5 <head>
      6 <title>Test Observable Array Type</title>
      7 <script src="/tests/SimpleTest/SimpleTest.js"></script>
      8 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
      9 </head>
     10 <body>
     11 <script>
     12 /* global TestInterfaceObservableArray */
     13 
     14 add_task(async function init() {
     15  await SpecialPowers.pushPrefEnv({set: [["dom.expose_test_interfaces", true]]});
     16 });
     17 
     18 add_task(function testObservableArrayExoticObjects_defineProperty() {
     19  let setCallbackCount = 0;
     20  let deleteCallbackCount = 0;
     21  let setCallbackTests = null;
     22  let deleteCallbackTests = null;
     23 
     24  let m = new TestInterfaceObservableArray({
     25    setBooleanCallback(value, index) {
     26      setCallbackCount++;
     27      if (typeof setCallbackTests === 'function') {
     28        setCallbackTests(value, index);
     29      }
     30    },
     31    deleteBooleanCallback(value, index) {
     32      deleteCallbackCount++;
     33      if (typeof deleteCallbackTests === 'function') {
     34        deleteCallbackTests(value, index);
     35      }
     36    },
     37  });
     38  m.observableArrayBoolean = [true, true, true];
     39 
     40  let b = m.observableArrayBoolean;
     41  ok(Array.isArray(b), "observable array should be an array type");
     42  is(b.length, 3, "length of observable array should be 0");
     43 
     44  // Test length
     45  [
     46    // [descriptor, shouldThrow, expectedResult]
     47    // Invalid descriptor
     48    [{configurable: true, value: 0}, false, false],
     49    [{enumerable: true, value: 0}, false, false],
     50    [{writable: false, value: 0}, false, false],
     51    [{get: ()=>{}}, false, false],
     52    [{set: ()=>{}}, false, false],
     53    [{get: ()=>{}, set: ()=>{}}, false, false],
     54    [{get: ()=>{}, value: 0}, true],
     55    // Invalid length value
     56    [{value: 1.9}, true],
     57    [{value: "invalid"}, true],
     58    [{value: {}}, true],
     59    // length value should not greater than current length
     60    [{value: b.length + 1}, false, false],
     61    // descriptor without value
     62    [{configurable: false, enumerable: false, writable: true}, false, true],
     63    // Success
     64    [{value: b.length}, false, true],
     65    [{value: b.length - 1}, false, true],
     66    [{value: 0}, false, true],
     67  ].forEach(function([descriptor, shouldThrow, expectedResult]) {
     68    // Initialize
     69    let oldLen = b.length;
     70    let oldValues = b.slice();
     71    let deleteCallbackIndex = oldLen - 1;
     72    let success = expectedResult && "value" in descriptor;
     73    setCallbackCount = 0;
     74    deleteCallbackCount = 0;
     75    setCallbackTests = null;
     76    deleteCallbackTests = function(_value, _index) {
     77      is(_value, oldValues[deleteCallbackIndex], "deleteCallbackTests: test value argument");
     78      is(_index, deleteCallbackIndex, "deleteCallbackTests: test index argument");
     79      deleteCallbackIndex--;
     80    };
     81 
     82    // Test
     83    info(`defining "length" property with ${JSON.stringify(descriptor)}`);
     84    try {
     85      is(Reflect.defineProperty(b, "length", descriptor), expectedResult,
     86         `Reflect.defineProperty should return ${expectedResult}`);
     87      ok(!shouldThrow, "Reflect.defineProperty should not throw");
     88    } catch(e) {
     89      ok(shouldThrow, `Reflect.defineProperty throws ${e}`);
     90    }
     91    is(setCallbackCount, 0, "setCallback count");
     92    is(deleteCallbackCount, success ? oldLen - descriptor.value : 0, "deleteCallback count");
     93    isDeeply(b, success ? oldValues.slice(0, descriptor.value) : oldValues, "property values");
     94    is(b.length, success ? descriptor.value : oldLen, "length of observable array");
     95  });
     96 
     97  // Test indexed value
     98  [
     99    // [index, descriptor, shouldThrow, expectedResult]
    100    // Invalid descriptor
    101    [0, {configurable: false, value: true}, false, false],
    102    [0, {enumerable: false, value: true}, false, false],
    103    [0, {writable: false, value: true}, false, false],
    104    [0, {get: ()=>{}}, false, false],
    105    [0, {set: ()=>{}}, false, false],
    106    [0, {get: ()=>{}, set: ()=>{}}, false, false],
    107    [0, {get: ()=>{}, value: true}, true],
    108    // Index could not greater than last index + 1.
    109    [b.length + 1, {configurable: true, enumerable: true, value: true}, false, false],
    110    // descriptor without value
    111    [b.length, {configurable: true, enumerable: true}, false, true],
    112    // Success
    113    [b.length, {configurable: true, enumerable: true, value: true}, false, true],
    114    [b.length + 1, {configurable: true, enumerable: true, value: true}, false, true],
    115  ].forEach(function([index, descriptor, shouldThrow, expectedResult]) {
    116    // Initialize
    117    let oldLen = b.length;
    118    let oldValue = b[index];
    119    let success = expectedResult && "value" in descriptor;
    120    setCallbackCount = 0;
    121    deleteCallbackCount = 0;
    122    setCallbackTests = function(_value, _index) {
    123      is(_value, descriptor.value, "setCallbackTests: test value argument");
    124      is(_index, index, "setCallbackTests: test index argument");
    125    };
    126    deleteCallbackTests = function(_value, _index) {
    127      is(_value, oldValue, "deleteCallbackTests: test value argument");
    128      is(_index, index, "deleteCallbackTests: test index argument");
    129    };
    130 
    131    // Test
    132    info(`defining ${index} property with ${JSON.stringify(descriptor)}`);
    133    try {
    134      is(Reflect.defineProperty(b, index, descriptor), expectedResult,
    135         `Reflect.defineProperty should return ${expectedResult}`);
    136      ok(!shouldThrow, "Reflect.defineProperty should not throw");
    137    } catch(e) {
    138      ok(shouldThrow, `Reflect.defineProperty throws ${e}`);
    139    }
    140    is(setCallbackCount, success ? 1 : 0, "setCallback count");
    141    is(deleteCallbackCount, (oldLen > index) ? 1 : 0, "deleteCallback count");
    142    is(b[index], success ? descriptor.value : oldValue, "property value");
    143    is(b.length, success ? Math.max(index + 1, oldLen) : oldLen, "length of observable array");
    144  });
    145 
    146  // Test other property
    147  [
    148    // [property, descriptor, shouldThrow, expectedResult]
    149    ["prop1", {configurable: false, value: "value1"}, false, true],
    150    ["prop1", {configurable: true, value: "value2"}, false, false],
    151    ["prop2", {enumerable: false, value: 5}, false, true],
    152    ["prop3", {enumerable: false, value: []}, false, true],
    153    ["prop4", {enumerable: false, value: {}}, false, true],
    154    ["prop5", {get: ()=>{}, value: true}, true, false],
    155    ["prop6", {get: ()=>{}, set: ()=>{}}, false, true],
    156  ].forEach(function([property, descriptor, shouldThrow, expectedResult]) {
    157    // Initialize
    158    let oldValue = b[property];
    159    let oldLen = b.length;
    160    setCallbackCount = 0;
    161    deleteCallbackCount = 0;
    162    setCallbackTests = null;
    163    deleteCallbackTests = null;
    164 
    165    // Test
    166    info(`defining ${property} property with ${JSON.stringify(descriptor)}`);
    167    try {
    168      is(Reflect.defineProperty(b, property, descriptor), expectedResult,
    169         `Reflect.defineProperty should return ${expectedResult}`);
    170      ok(!shouldThrow, "Reflect.defineProperty should not throw");
    171    } catch(e) {
    172      ok(shouldThrow, `Reflect.defineProperty throws ${e}`);
    173    }
    174    is(setCallbackCount, 0, "setCallback count");
    175    is(deleteCallbackCount, 0, "deleteCallback count");
    176    is(b[property], expectedResult ? descriptor.value : oldValue, "property value");
    177    is(b.length, oldLen, "length of observable array");
    178  });
    179 });
    180 
    181 add_task(function testObservableArrayExoticObjects_defineProperty_callback_throw() {
    182  let setCallbackCount = 0;
    183  let deleteCallbackCount = 0;
    184 
    185  const minLen = 3;
    186  let m = new TestInterfaceObservableArray({
    187    setBooleanCallback(value) {
    188      setCallbackCount++;
    189      if (value) {
    190        throw new Error("setBooleanCallback");
    191      }
    192    },
    193    deleteBooleanCallback(value, index) {
    194      deleteCallbackCount++;
    195      if (index < minLen) {
    196        throw new Error("deleteBooleanCallback");
    197      }
    198    },
    199  });
    200  m.observableArrayBoolean = [false, false, false, false, false];
    201 
    202  let b = m.observableArrayBoolean;
    203  ok(Array.isArray(b), "observable array should be an array type");
    204  is(b.length, 5, "length of observable array should be 3");
    205 
    206  // Test length
    207  [
    208    // [length, shouldThrow]
    209    [b.length, false],
    210    [b.length - 1, false],
    211    [0, true],
    212  ].forEach(function([length, shouldThrow]) {
    213    // Initialize
    214    let oldValues = b.slice();
    215    let oldLen = b.length;
    216    let descriptor = {value: length};
    217    setCallbackCount = 0;
    218    deleteCallbackCount = 0;
    219 
    220    // Test
    221    info(`defining "length" property with ${JSON.stringify(descriptor)}`);
    222    try {
    223      ok(Reflect.defineProperty(b, "length", descriptor),
    224         "Reflect.defineProperty should return true");
    225      ok(!shouldThrow, "Reflect.defineProperty should not throw");
    226    } catch(e) {
    227      ok(shouldThrow, `Reflect.defineProperty throws ${e}`);
    228    }
    229    is(setCallbackCount, 0, "setCallback count");
    230    is(deleteCallbackCount, oldLen - (shouldThrow ? minLen - 1 : length), "deleteCallback count");
    231    isDeeply(b, oldValues.slice(0, shouldThrow ? minLen : length), "property values");
    232    is(b.length, shouldThrow ? minLen : length, "length of observable array");
    233  });
    234 
    235  // Test indexed value
    236  [
    237    // [index, value, shouldThrow]
    238    [b.length, true, true],
    239    [b.length, false, false],
    240    [b.length + 1, false, false],
    241    [b.length + 1, true, true],
    242    [0, true, true],
    243    [0, false, true],
    244  ].forEach(function([index, value, shouldThrow]) {
    245    // Initialize
    246    let oldValue = b[index];
    247    let oldLen = b.length;
    248    let descriptor = {configurable: true, enumerable: true, value};
    249    setCallbackCount = 0;
    250    deleteCallbackCount = 0;
    251 
    252    // Test
    253    info(`defining ${index} property with ${JSON.stringify(descriptor)}`);
    254    try {
    255      ok(Reflect.defineProperty(b, index, descriptor), "Reflect.defineProperty should return true");
    256      ok(!shouldThrow, "Reflect.defineProperty should not throw");
    257    } catch(e) {
    258      ok(shouldThrow, `Reflect.defineProperty throws ${e}`);
    259    }
    260    is(setCallbackCount, (index < minLen) ? 0 : 1, "setCallback count");
    261    is(deleteCallbackCount, (oldLen > index) ? 1 : 0, "deleteCallback count");
    262    is(b[index], shouldThrow ? oldValue : value, "property value");
    263    is(b.length, shouldThrow ? oldLen : Math.max(oldLen, index + 1), "length of observable array");
    264  });
    265 
    266  // Test other property
    267  [
    268    // [property, descriptor, expectedResult]
    269    ["prop1", {configurable: false, value: "value1"}, true],
    270    ["prop1", {configurable: true, value: "value2"}, false],
    271    ["prop2", {enumerable: false, value: 5}, true],
    272    ["prop3", {enumerable: false, value: []}, true],
    273    ["prop4", {enumerable: false, value: {}}, true],
    274  ].forEach(function([property, descriptor, expectedResult]) {
    275    // Initialize
    276    let oldValue = b[property];
    277    let oldLen = b.length;
    278    setCallbackCount = 0;
    279    deleteCallbackCount = 0;
    280 
    281    // Test
    282    info(`defining ${property} property with ${JSON.stringify(descriptor)}`);
    283    try {
    284      is(Reflect.defineProperty(b, property, descriptor), expectedResult,
    285         `Reflect.defineProperty should return ${expectedResult}`);
    286      ok(true, "Reflect.defineProperty should not throw");
    287    } catch(e) {
    288      ok(false, `Reflect.defineProperty throws ${e}`);
    289    }
    290    is(setCallbackCount, 0, "setCallback count");
    291    is(deleteCallbackCount, 0, "deleteCallback count");
    292    is(b[property], expectedResult ? descriptor.value : oldValue, "property value");
    293    is(b.length, oldLen, "length of observable array");
    294  });
    295 });
    296 
    297 add_task(function testObservableArrayExoticObjects_deleteProperty() {
    298  let setCallbackCount = 0;
    299  let deleteCallbackCount = 0;
    300  let deleteCallbackTests = null;
    301 
    302  let m = new TestInterfaceObservableArray({
    303    setBooleanCallback() {
    304      setCallbackCount++;
    305    },
    306    deleteBooleanCallback(value, index) {
    307      deleteCallbackCount++;
    308      if (typeof deleteCallbackTests === 'function') {
    309        deleteCallbackTests(value, index);
    310      }
    311    },
    312  });
    313  m.observableArrayBoolean = [true, true];
    314 
    315  let b = m.observableArrayBoolean;
    316  ok(Array.isArray(b), "observable array should be an array type");
    317  is(b.length, 2, "length of observable array should be 2");
    318 
    319  // Test length
    320  setCallbackCount = 0;
    321  deleteCallbackCount = 0;
    322  info("deleting length property");
    323  ok(!Reflect.deleteProperty(b, "length"), "test result of deleting length property");
    324  is(setCallbackCount, 0, "setCallback should not be called");
    325  is(deleteCallbackCount, 0, "deleteCallback should not be called");
    326  is(b.length, 2, "length should still be 2");
    327 
    328  // Test indexed value
    329  [
    330    // [index, expectedResult]
    331    [2, false],
    332    [0, false],
    333    [1, true],
    334  ].forEach(function([index, expectedResult]) {
    335    // Initialize
    336    let oldLen = b.length;
    337    let oldValue = b[index];
    338    setCallbackCount = 0;
    339    deleteCallbackCount = 0;
    340    deleteCallbackTests = function(_value, _index) {
    341      is(_value, oldValue, "deleteCallbackTests: test value argument");
    342      is(_index, index, "deleteCallbackTests: test index argument");
    343    };
    344 
    345    // Test
    346    info(`deleting ${index} property`);
    347    is(Reflect.deleteProperty(b, index), expectedResult,
    348       `Reflect.deleteProperty should return ${expectedResult}`);
    349    is(setCallbackCount, 0, "setCallback count");
    350    is(deleteCallbackCount, expectedResult ? 1 : 0, "deleteCallback count");
    351    is(b[index], expectedResult ? undefined : oldValue, "property value");
    352    is(b.length, expectedResult ? oldLen - 1 : oldLen,
    353       "length of observable array");
    354  });
    355 
    356  // Test other property
    357  [
    358    // [property, value]
    359    ["prop1", "value1"],
    360    ["prop2", 5],
    361    ["prop3", []],
    362    ["prop4", {}],
    363  ].forEach(function([property, value]) {
    364    // Initialize
    365    b[property] = value;
    366    let oldLen = b.length;
    367    setCallbackCount = 0;
    368    deleteCallbackCount = 0;
    369    deleteCallbackTests = null;
    370 
    371    // Test
    372    info(`deleting ${property} property`);
    373    is(b[property], value, `property value should be ${value} before deleting`);
    374    ok(Reflect.deleteProperty(b, property), "Reflect.deleteProperty should return true");
    375    is(setCallbackCount, 0, "setCallback count");
    376    is(deleteCallbackCount, 0, "deleteCallback count");
    377    is(b[property], undefined, "property value should be undefined after deleting");
    378    is(b.length, oldLen, "length of observable array");
    379  });
    380 });
    381 
    382 add_task(function testObservableArrayExoticObjects_deleteProperty_callback_throw() {
    383  let setCallbackCount = 0;
    384  let deleteCallbackCount = 0;
    385 
    386  let m = new TestInterfaceObservableArray({
    387    setBooleanCallback() {
    388      setCallbackCount++;
    389    },
    390    deleteBooleanCallback(value) {
    391      deleteCallbackCount++;
    392      if (value) {
    393        throw new Error("deleteBooleanCallback");
    394      }
    395    },
    396  });
    397  m.observableArrayBoolean = [true, false];
    398 
    399  let b = m.observableArrayBoolean;
    400  ok(Array.isArray(b), "observable array should be an array type");
    401  is(b.length, 2, "length of observable array should be 2");
    402 
    403  // Test indexed value
    404  let index = b.length;
    405  while (index--) {
    406    // Initialize
    407    let oldValue = b[index];
    408    let oldLen = b.length;
    409    setCallbackCount = 0;
    410    deleteCallbackCount = 0;
    411 
    412    // Test
    413    info(`deleting index ${index}`);
    414    try {
    415      ok(Reflect.deleteProperty(b, index), "Reflect.deleteProperty should return true");
    416      ok(!oldValue, "Reflect.deleteProperty should not throw");
    417    } catch(e) {
    418      ok(oldValue, `Reflect.deleteProperty throws ${e}`);
    419    }
    420    is(setCallbackCount, 0, "setCallback count");
    421    is(deleteCallbackCount, 1, "deleteCallback count");
    422    is(b[index], oldValue ? oldValue : undefined, "property value");
    423    is(b.length, oldValue ? oldLen : oldLen - 1, "length of observable array");
    424  }
    425 
    426  // Test other property
    427  [
    428    // [property, value]
    429    ["prop1", "value1"],
    430    ["prop2", 5],
    431    ["prop3", []],
    432    ["prop4", {}],
    433    ["prop5", false],
    434  ].forEach(function([property, value]) {
    435    // Initialize
    436    b[property] = value;
    437    let oldLen = b.length;
    438    setCallbackCount = 0;
    439    deleteCallbackCount = 0;
    440 
    441    // Test
    442    info(`deleting ${property} property`);
    443    is(b[property], value, `property value should be ${JSON.stringify(value)} before deleting`);
    444    try {
    445      ok(Reflect.deleteProperty(b, property), `Reflect.deleteProperty should return true`);
    446      ok(true, "Reflect.deleteProperty should not throw");
    447    } catch(e) {
    448      ok(false, `Reflect.deleteProperty throws ${e}`);
    449    }
    450    is(setCallbackCount, 0, "setCallback count");
    451    is(deleteCallbackCount, 0, "deleteCallback count");
    452    is(b[property], undefined, `property value should be undefined after deleting`);
    453    is(b.length, oldLen, "length of observable array");
    454  });
    455 });
    456 
    457 add_task(function testObservableArrayExoticObjects_get() {
    458  let m = new TestInterfaceObservableArray();
    459  m.observableArrayBoolean = [true, false];
    460 
    461  let b = m.observableArrayBoolean;
    462  ok(Array.isArray(b), "observable array should be an array type");
    463  is(b.length, 2, "length of observable array should be 2");
    464 
    465  // Test length
    466  is(Reflect.get(b, "length"), 2, "test result of getting length property");
    467 
    468  // Test indexed value
    469  is(Reflect.get(b, 0), true, "test result of getting index 0");
    470  is(Reflect.get(b, 1), false, "test result of getting index 1");
    471  is(Reflect.get(b, 2), undefined, "test result of getting index 2");
    472 
    473  // Test other property
    474  [
    475    // [property, value]
    476    ["prop1", "value1"],
    477    ["prop2", 5],
    478    ["prop3", []],
    479    ["prop4", {}],
    480  ].forEach(function([property, value]) {
    481    is(Reflect.get(b, property), undefined, `test ${property} property before setting property value`);
    482    b[property] = value;
    483    is(Reflect.get(b, property), value, `test ${property} property after setting property value`);
    484  });
    485 });
    486 
    487 add_task(function testObservableArrayExoticObjects_getOwnPropertyDescriptor() {
    488  function TestDescriptor(object, property, exist, configurable, enumerable,
    489                          writable, value) {
    490    let descriptor = Reflect.getOwnPropertyDescriptor(object, property);
    491    if (!exist) {
    492      is(descriptor, undefined, `descriptor of ${property} property should be undefined`);
    493      return;
    494    }
    495 
    496    is(descriptor.configurable, configurable, `test descriptor of ${property} property (configurable)`);
    497    is(descriptor.enumerable, enumerable, `test descriptor of ${property} property (enumerable)`);
    498    is(descriptor.writable, writable, `test descriptor of ${property} property (writable)`);
    499    is(descriptor.value, value, `test descriptor of ${property} property (value)`);
    500  }
    501 
    502  let m = new TestInterfaceObservableArray();
    503  m.observableArrayBoolean = [true, false];
    504 
    505  let b = m.observableArrayBoolean;
    506  ok(Array.isArray(b), "observable array should be an array type");
    507  is(b.length, 2, "length of observable array should be 2");
    508 
    509  // Test length
    510  TestDescriptor(b, "length", true, false /* configurable */,
    511                 false /* enumerable */, true /* writable */ , 2 /* value */);
    512 
    513  // Test indexed value
    514  TestDescriptor(b, 0, true, true /* configurable */, true /* enumerable */,
    515                 true /* writable */ , true /* value */);
    516  TestDescriptor(b, 1, true, true /* configurable */, true /* enumerable */,
    517                 true /* writable */ , false /* value */);
    518  TestDescriptor(b, 2, false);
    519 
    520  // Test other property
    521  [
    522    // [property, value, configurable, enumerable, writable]
    523    ["prop1", "value1", true, true, true],
    524    ["prop2", 5, true, true, false],
    525    ["prop3", [], true, false, false],
    526    ["prop4", {}, false, false, false],
    527  ].forEach(function([property, value, configurable, enumerable, writable]) {
    528    Object.defineProperty(b, property, {
    529      value,
    530      configurable,
    531      enumerable,
    532      writable,
    533    });
    534    TestDescriptor(b, property, true, configurable, enumerable, writable , value);
    535  });
    536 });
    537 
    538 add_task(function testObservableArrayExoticObjects_has() {
    539  let m = new TestInterfaceObservableArray();
    540  m.observableArrayBoolean = [true, false];
    541 
    542  let b = m.observableArrayBoolean;
    543  ok(Array.isArray(b), "observable array should be an array type");
    544  is(b.length, 2, "length of observable array should be 2");
    545 
    546  // Test length
    547  ok(Reflect.has(b, "length"), `test length property`);
    548 
    549  // Test indexed value
    550  ok(Reflect.has(b, 0), `test 0 property`);
    551  ok(Reflect.has(b, 1), `test 1 property`);
    552  ok(!Reflect.has(b, 2), `test 2 property`);
    553 
    554  // Test other property
    555  [
    556    // [property, value]
    557    ["prop1", "value1"],
    558    ["prop2", 5],
    559    ["prop3", []],
    560    ["prop4", {}],
    561  ].forEach(function([property, value]) {
    562    ok(!Reflect.has(b, property), `test ${property} property before setting property value`);
    563    b[property] = value;
    564    ok(Reflect.has(b, property), `test ${property} property after setting property value`);
    565  });
    566 });
    567 
    568 add_task(function testObservableArrayExoticObjects_ownKeys() {
    569  let m = new TestInterfaceObservableArray();
    570  m.observableArrayBoolean = [true, false];
    571 
    572  let b = m.observableArrayBoolean;
    573  ok(Array.isArray(b), "observable array should be an array type");
    574  is(b.length, 2, "length of observable array should be 2");
    575 
    576  // Add other properties
    577  b.prop1 = "value1";
    578  b.prop2 = 5;
    579  b.prop3 = [];
    580  b.prop4 = {};
    581 
    582  let keys = Reflect.ownKeys(b);
    583  SimpleTest.isDeeply(keys, ["0", "1", "length", "prop1", "prop2", "prop3", "prop4"], `test property keys`);
    584 });
    585 
    586 add_task(function testObservableArrayExoticObjects_preventExtensions() {
    587  let m = new TestInterfaceObservableArray();
    588  let b = m.observableArrayBoolean;
    589  ok(Array.isArray(b), "observable array should be an array type");
    590  is(b.length, 0, "length of observable array should be 0");
    591 
    592  // Test preventExtensions
    593  ok(Reflect.isExtensible(b), "test isExtensible before preventExtensions");
    594  ok(!Reflect.preventExtensions(b), "test preventExtensions");
    595  ok(Reflect.isExtensible(b), "test isExtensible after preventExtensions");
    596 });
    597 
    598 add_task(function testObservableArrayExoticObjects_set() {
    599  let setCallbackCount = 0;
    600  let deleteCallbackCount = 0;
    601  let setCallbackTests = null;
    602  let deleteCallbackTests = null;
    603 
    604  let m = new TestInterfaceObservableArray({
    605    setBooleanCallback(value, index) {
    606      setCallbackCount++;
    607      if (typeof setCallbackTests === 'function') {
    608        setCallbackTests(value, index);
    609      }
    610    },
    611    deleteBooleanCallback(value, index) {
    612      deleteCallbackCount++;
    613      if (typeof deleteCallbackTests === 'function') {
    614        deleteCallbackTests(value, index);
    615      }
    616    },
    617  });
    618  m.observableArrayBoolean = [true, true, true];
    619 
    620  let b = m.observableArrayBoolean;
    621  ok(Array.isArray(b), "observable array should be an array type");
    622  is(b.length, 3, "length of observable array should be 3");
    623 
    624  // Test length
    625  [
    626    // [length, shouldThrow, expectedResult]
    627    // Invalid length value
    628    [1.9, true],
    629    ['invalid', true],
    630    [{}, true],
    631    // length value should not greater than current length
    632    [b.length + 1, false, false],
    633    // Success
    634    [b.length, false, true],
    635    [b.length - 1, false, true],
    636    [0, false, true],
    637  ].forEach(function([length, shouldThrow, expectedResult]) {
    638    // Initialize
    639    let oldLen = b.length;
    640    let oldValues = b.slice();
    641    setCallbackCount = 0;
    642    deleteCallbackCount = 0;
    643    setCallbackTests = null;
    644    let deleteCallbackIndex = oldLen - 1;
    645    deleteCallbackTests = function(_value, _index) {
    646      is(_value, oldValues[deleteCallbackIndex], "deleteCallbackTests: test value argument");
    647      is(_index, deleteCallbackIndex, "deleteCallbackTests: test index argument");
    648      deleteCallbackIndex--;
    649    };
    650 
    651    // Test
    652    info(`setting "length" property value to ${length}`);
    653    try {
    654      is(Reflect.set(b, "length", length), expectedResult, `Reflect.set should return ${expectedResult}`);
    655      ok(!shouldThrow, "Reflect.set should not throw");
    656    } catch(e) {
    657      ok(shouldThrow, `Reflect.set throws ${e}`);
    658    }
    659    is(setCallbackCount, 0, "setCallback count");
    660    is(deleteCallbackCount, expectedResult ? oldLen - length : 0, "deleteCallback count");
    661    isDeeply(b, expectedResult ? oldValues.slice(0, length) : oldValues, "property values");
    662    is(b.length, expectedResult ? length : oldLen, "length of observable array");
    663  });
    664 
    665  // Test indexed value
    666  [
    667    // [index, value, shouldThrow, expectedResult]
    668    // Index could not greater than last index.
    669    [b.length + 1, true, false, false],
    670    // Success
    671    [b.length, true, false, true],
    672    [b.length + 1, true, false, true],
    673  ].forEach(function([index, value, shouldThrow, expectedResult]) {
    674    // Initialize
    675    let oldLen = b.length;
    676    let oldValue = b[index];
    677    setCallbackCount = 0;
    678    deleteCallbackCount = 0;
    679    setCallbackTests = function(_value, _index) {
    680      is(_value, value, "setCallbackTests: test value argument");
    681      is(_index, index, "setCallbackTests: test index argument");
    682    };
    683    deleteCallbackTests = function(_value, _index) {
    684      is(_value, oldValue, "deleteCallbackTests: test value argument");
    685      is(_index, index, "deleteCallbackTests: test index argument");
    686    };
    687 
    688    // Test
    689    info(`setting ${index} property to ${value}`);
    690    try {
    691      is(Reflect.set(b, index, value), expectedResult, `Reflect.set should return ${expectedResult}`);
    692      ok(!shouldThrow, "Reflect.set should not throw");
    693    } catch(e) {
    694      ok(shouldThrow, `Reflect.set throws ${e}`);
    695    }
    696    is(setCallbackCount, expectedResult ? 1 : 0, "setCallback count");
    697    is(deleteCallbackCount, (oldLen > index) ? 1 : 0, "deleteCallback count");
    698    is(b[index], expectedResult ? value : oldValue, "property value");
    699    is(b.length, expectedResult ? Math.max(index + 1, oldLen) : oldLen, "length of observable array");
    700  });
    701 
    702  // Test other property
    703  [
    704    // [property, value]
    705    ["prop1", "value1"],
    706    ["prop1", "value2"],
    707    ["prop2", 5],
    708    ["prop3", []],
    709    ["prop4", {}],
    710  ].forEach(function([property, value]) {
    711    // Initialize
    712    let oldLen = b.length;
    713    setCallbackCount = 0;
    714    deleteCallbackCount = 0;
    715    setCallbackTests = null;
    716    deleteCallbackTests = null;
    717 
    718    // Test
    719    info(`setting ${property} property to ${value}`);
    720    ok(Reflect.set(b, property, value), "Reflect.defineProperty should return true");
    721    is(setCallbackCount, 0, "setCallback count");
    722    is(deleteCallbackCount, 0, "deleteCallback count");
    723    is(b[property], value, "property value");
    724    is(b.length, oldLen, "length of observable array");
    725  });
    726 });
    727 
    728 add_task(function testObservableArrayExoticObjects_set_callback_throw() {
    729  let setCallbackCount = 0;
    730  let deleteCallbackCount = 0;
    731 
    732  const minLen = 3;
    733  let m = new TestInterfaceObservableArray({
    734    setBooleanCallback(value) {
    735      setCallbackCount++;
    736      if (value) {
    737        throw new Error("setBooleanCallback");
    738      }
    739    },
    740    deleteBooleanCallback(value, index) {
    741      deleteCallbackCount++;
    742      if (index < minLen) {
    743        throw new Error("deleteBooleanCallback");
    744      }
    745    },
    746  });
    747  m.observableArrayBoolean = [false, false, false, false, false];
    748 
    749  let b = m.observableArrayBoolean;
    750  ok(Array.isArray(b), "observable array should be an array type");
    751  is(b.length, 5, "length of observable array should be 3");
    752 
    753  // Test length
    754  [
    755    // [value, shouldThrow]
    756    [b.length, false],
    757    [b.length - 1, false],
    758    [0, true],
    759  ].forEach(function([length, shouldThrow]) {
    760    // Initialize
    761    let oldValues = b.slice();
    762    let oldLen = b.length;
    763    setCallbackCount = 0;
    764    deleteCallbackCount = 0;
    765 
    766    // Test
    767    info(`setting "length" property to ${length}`);
    768    try {
    769      ok(Reflect.set(b, "length", length), "Reflect.set should return true");
    770      ok(!shouldThrow, `Reflect.set should not throw`);
    771    } catch(e) {
    772      ok(shouldThrow, `Reflect.set throws ${e}`);
    773    }
    774    is(setCallbackCount, 0, "setCallback should not be called");
    775    is(deleteCallbackCount, oldLen - (shouldThrow ? minLen - 1 : length), "deleteCallback count");
    776    isDeeply(b, oldValues.slice(0, shouldThrow ? minLen : length), "property values");
    777    is(b.length, shouldThrow ? minLen : length, "length of observable array");
    778  });
    779 
    780  // Test indexed value
    781  [
    782    // [index, value, shouldThrow]
    783    [b.length, true, true],
    784    [b.length, false, false],
    785    [b.length + 1, false, false],
    786    [b.length + 1, true, true],
    787    [0, false, true],
    788    [0, true, true],
    789  ].forEach(function([index, value, shouldThrow]) {
    790    // Initialize
    791    let oldValue = b[index];
    792    let oldLen = b.length;
    793    setCallbackCount = 0;
    794    deleteCallbackCount = 0;
    795 
    796    // Test
    797    info(`setting ${index} property to ${value}`);
    798    try {
    799      ok(Reflect.set(b, index, value), "Reflect.set should return true");
    800      ok(!shouldThrow, `Reflect.set should not throw`);
    801    } catch(e) {
    802      ok(shouldThrow, `Reflect.set throws ${e}`);
    803    }
    804    is(setCallbackCount, (index < minLen) ? 0 : 1, "setCallback count");
    805    is(deleteCallbackCount, (oldLen > index) ? 1 : 0, "deleteCallback count");
    806    is(b[index], shouldThrow ? oldValue : value, "property value");
    807    is(b.length, shouldThrow ? oldLen : Math.max(oldLen, index + 1), "length of observable array");
    808  });
    809 
    810  // Test other property
    811  [
    812    ["prop1", "value1"],
    813    ["prop1", "value2"],
    814    ["prop2", 5],
    815    ["prop3", []],
    816    ["prop4", {}],
    817  ].forEach(function([property, value]) {
    818    // Initialize
    819    let oldLen = b.length;
    820    setCallbackCount = 0;
    821    deleteCallbackCount = 0;
    822 
    823    // Test
    824    info(`setting ${property} property to ${JSON.stringify(value)}`);
    825    try {
    826      ok(Reflect.set(b, property, value), "Reflect.set should return true");
    827      ok(true, `Reflect.set should not throw`);
    828    } catch(e) {
    829      ok(false, `Reflect.set throws ${e}`);
    830    }
    831    is(setCallbackCount, 0, "setCallback should not be called");
    832    is(deleteCallbackCount, 0, "deleteCallback should be called");
    833    is(b[property], value, "property value");
    834    is(b.length, oldLen, "length of observable array");
    835  });
    836 });
    837 
    838 add_task(function testObservableArrayExoticObjects_invalidtype() {
    839  let m = new TestInterfaceObservableArray();
    840  let i = m.observableArrayInterface;
    841  ok(Array.isArray(i), "Observable array should be an array type");
    842  is(i.length, 0, "length should be 0");
    843 
    844  [true, "invalid"].forEach(function(value) {
    845    SimpleTest.doesThrow(() => {
    846      let descriptor = {configurable: true, enumerable: true, writable: true, value};
    847      Reflect.defineProperty(i, i.length, descriptor);
    848    }, `defining ${i.length} property with ${JSON.stringify(value)} should throw`);
    849 
    850    SimpleTest.doesThrow(() => {
    851      Reflect.set(i, i.length, value);
    852    }, `setting ${i.length} property to ${JSON.stringify(value)} should throw`);
    853  });
    854 
    855  is(i.length, 0, "length should still be 0");
    856 });
    857 </script>
    858 </body>
    859 </html>