tor-browser

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

test_xrayToJS.xhtml (59077B)


      1 <?xml version="1.0"?>
      2 <?xml-stylesheet type="text/css" href="chrome://global/skin"?>
      3 <?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
      4 <!--
      5 https://bugzilla.mozilla.org/show_bug.cgi?id=933681
      6 -->
      7 <window title="Mozilla Bug 933681"
      8        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
      9  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
     10 
     11  <!-- test results are displayed in the html:body -->
     12  <body xmlns="http://www.w3.org/1999/xhtml">
     13  <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=933681"
     14     target="_blank">Mozilla Bug 933681</a>
     15  </body>
     16 
     17  <!-- test code goes here -->
     18  <script type="application/javascript">
     19  <![CDATA[
     20 
     21  /** Test for ES constructors on Xrayed globals. */
     22  SimpleTest.waitForExplicitFinish();
     23  let global = Cu.getGlobalForObject.bind(Cu);
     24 
     25  function checkThrows(f, rgxp, msg) {
     26    try {
     27      f();
     28      ok(false, "Should have thrown: " + msg);
     29    } catch (e) {
     30      ok(true, "Threw as expected: " + msg);
     31      ok(rgxp.test(e), "Message correct: " + e);
     32    }
     33  }
     34 
     35  var { AppConstants } = SpecialPowers.ChromeUtils.importESModule(
     36    "resource://gre/modules/AppConstants.sys.mjs"
     37  );
     38  var isNightlyBuild = AppConstants.NIGHTLY_BUILD;
     39  var isReleaseOrBeta = AppConstants.RELEASE_OR_BETA;
     40 
     41  let typedArrayClasses = ['Uint8Array', 'Int8Array', 'Uint16Array', 'Int16Array',
     42                           'Uint32Array', 'Int32Array', 'Float32Array', 'Float64Array',
     43                           'Uint8ClampedArray'];
     44  let errorObjectClasses = ['Error', 'InternalError', 'EvalError', 'RangeError', 'ReferenceError',
     45                            'SyntaxError', 'TypeError', 'URIError', 'AggregateError'];
     46 
     47  // A simpleConstructors entry can either be the name of a constructor as a
     48  // string, or an object containing the properties `name`, and `args`.
     49  // In the former case, the constructor is invoked without any args; in the
     50  // latter case, it is invoked with `args` as the arguments list.
     51  let simpleConstructors = ['Object', 'Function', 'Array', 'Boolean', 'Date', 'Number',
     52                            'String', 'RegExp', 'ArrayBuffer', 'WeakMap', 'WeakSet', 'Map', 'Set',
     53                            {name: 'Promise', args: [function(){}]}];
     54 
     55  // All TypedArray constructors can be called with zero arguments.
     56  simpleConstructors = simpleConstructors.concat(typedArrayClasses);
     57 
     58  // All Error constructors except AggregateError can be called with zero arguments.
     59  simpleConstructors = simpleConstructors.concat(errorObjectClasses.filter(name => {
     60    return name !== 'AggregateError';
     61  }));
     62 
     63  function go() {
     64    window.iwin = document.getElementById('ifr').contentWindow;
     65    /* global iwin */
     66 
     67    // Test constructors that can be instantiated with zero arguments, or with
     68    // a fixed set of arguments provided using `...rest`.
     69    for (let c of simpleConstructors) {
     70      var args = [];
     71      if (typeof c === 'object') {
     72        args = c.args;
     73        c = c.name;
     74      }
     75      ok(iwin[c], "Constructors appear: " + c);
     76      is(iwin[c], Cu.unwaiveXrays(iwin.wrappedJSObject[c]),
     77         "we end up with the appropriate constructor: " + c);
     78      is(Cu.unwaiveXrays(Cu.waiveXrays(new iwin[c](...args)).constructor), iwin[c],
     79         "constructor property is set up right: " + c);
     80      let expectedProto = Cu.isOpaqueWrapper(new iwin[c](...args)) ?
     81        iwin.Object.prototype : iwin[c].prototype;
     82      is(Object.getPrototypeOf(new iwin[c](...args)), expectedProto,
     83         "prototype is correct: " + c);
     84      is(global(new iwin[c](...args)), iwin, "Got the right global: " + c);
     85    }
     86 
     87    // Test Object in more detail.
     88    var num = new iwin.Object(4);
     89    is(Cu.waiveXrays(num).valueOf(), 4, "primitive object construction works");
     90    is(global(num), iwin, "correct global for num");
     91    var obj = new iwin.Object();
     92    obj.foo = 2;
     93    var withProto = Cu.unwaiveXrays(Cu.waiveXrays(iwin).Object.create(obj));
     94    is(global(withProto), iwin, "correct global for withProto");
     95    is(Cu.waiveXrays(withProto).foo, 2, "Inherits properly");
     96 
     97    // Test Function.
     98    var primitiveFun = new iwin.Function('return 2');
     99    is(global(primitiveFun), iwin, "function construction works");
    100    is(primitiveFun(), 2, "basic function works");
    101    var doSetFoo = new iwin.Function('arg', 'arg.foo = 2;');
    102    is(global(doSetFoo), iwin, "function with args works");
    103    try {
    104      doSetFoo({});
    105      ok(false, "should have thrown while setting property on object");
    106    } catch (e) {
    107      ok(!!/denied/.test(e), "Threw correctly: " + e);
    108    }
    109    var factoryFun = new iwin.Function('return {foo: 32}');
    110    is(global(factoryFun), iwin, "proper global for factoryFun");
    111    is(factoryFun().foo, 32, "factoryFun invokable");
    112    is(global(factoryFun()), iwin, "minted objects live in the content scope");
    113    testXray('Function', factoryFun, new iwin.Function(), ['length', 'name']);
    114    var echoThis = new iwin.Function('return this;');
    115    echoThis.wrappedJSObject.bind = 42;
    116    var boundEchoThis = echoThis.bind(document);
    117    is(boundEchoThis(), document, "bind() works correctly over Xrays");
    118    is(global(boundEchoThis), window, "bound functions live in the caller's scope");
    119    ok(/return this/.test(echoThis.toSource()), 'toSource works: ' + echoThis.toSource());
    120    ok(/return this/.test(echoThis.toString()), 'toString works: ' + echoThis.toString());
    121    is(iwin.Function.prototype, Object.getPrototypeOf(echoThis), "Function.prototype works for standard classes");
    122    is(echoThis.prototype, undefined, "Function.prototype not visible for non standard constructors");
    123    iwin.eval('var foopyFunction = function namedFoopyFunction(a, b, c) {}');
    124    var foopyFunction = Cu.unwaiveXrays(Cu.waiveXrays(iwin).foopyFunction);
    125    ok(Cu.isXrayWrapper(foopyFunction), "Should be Xrays");
    126    is(foopyFunction.name, "namedFoopyFunction", ".name works over Xrays");
    127    is(foopyFunction.length, 3, ".length works over Xrays");
    128    ok(Object.getOwnPropertyNames(foopyFunction).includes('length'), "Should list length");
    129    ok(Object.getOwnPropertyNames(foopyFunction).includes('name'), "Should list name");
    130    ok(!Object.getOwnPropertyNames(foopyFunction).includes('prototype'), "Should not list prototype");
    131    ok(Object.getOwnPropertyNames(iwin.Array).includes('prototype'), "Should list prototype for standard constructor");
    132 
    133    // Test BoundFunction.
    134    iwin.eval('var boundFoopyFunction = foopyFunction.bind(null, 1)');
    135    var boundFoopyFunction = Cu.unwaiveXrays(Cu.waiveXrays(iwin).boundFoopyFunction);
    136    is(boundFoopyFunction.name, "bound namedFoopyFunction", "bound .name works over Xrays");
    137    is(boundFoopyFunction.length, 2, "bound .length works over Xrays");
    138    is(JSON.stringify(Object.getOwnPropertyNames(boundFoopyFunction).sort()), '["length","name"]', "Should list length and name");
    139    // Mutate .name, it's just a data property.
    140    iwin.eval('Object.defineProperty(boundFoopyFunction, "name", {value: "foobar", configurable: true, writable: true});');
    141    is(boundFoopyFunction.name, "foobar", "mutated .name works over Xrays");
    142    iwin.eval('boundFoopyFunction.name = 123;');
    143    is(boundFoopyFunction.name, undefined, "only support string for .name");
    144    iwin.eval('delete boundFoopyFunction.name');
    145    is(boundFoopyFunction.name, undefined, "deleted .name works over Xrays");
    146    // Mutate .length.
    147    iwin.eval('Object.defineProperty(boundFoopyFunction, "length", {value: 456, configurable: true, writable: true});');
    148    is(boundFoopyFunction.length, 456, "mutated .length works over Xrays");
    149    iwin.eval('boundFoopyFunction.length = "bar";');
    150    is(boundFoopyFunction.length, undefined, "only support number for .length");
    151 
    152    // Test proxies.
    153    var targetObject = new iwin.Object();
    154    targetObject.foo = 9;
    155    var forwardingProxy = new iwin.Proxy(targetObject, new iwin.Object());
    156    is(global(forwardingProxy), iwin, "proxy global correct");
    157    is(Cu.waiveXrays(forwardingProxy).foo, 9, "forwards correctly");
    158 
    159    // Test AggregateError.
    160    {
    161      // AggregateError throws when called without an iterable object as its first argument.
    162      let args = [new iwin.Array()];
    163 
    164      ok(iwin.AggregateError, "AggregateError constructor is present");
    165      is(iwin.AggregateError, Cu.unwaiveXrays(iwin.wrappedJSObject.AggregateError),
    166         "we end up with the appropriate AggregateError constructor");
    167      is(Cu.unwaiveXrays(Cu.waiveXrays(new iwin.AggregateError(...args)).constructor), iwin.AggregateError,
    168         "AggregateError constructor property is set up right");
    169      let expectedProto =  Cu.isOpaqueWrapper(new iwin.AggregateError(...args)) ?
    170        iwin.Object.prototype : iwin.AggregateError.prototype;
    171      is(Object.getPrototypeOf(new iwin.AggregateError(...args)), expectedProto,
    172         "AggregateError prototype is correct");
    173      is(global(new iwin.AggregateError(...args)), iwin, "Got the right global for AggregateError");
    174    }
    175 
    176    // Test eval.
    177    var toEval = "({a: 2, b: {foo: 'bar'}, f: function() { return window; }})";
    178    is(global(iwin.eval(toEval)), iwin, "eval creates objects in the correct global");
    179    is(iwin.eval(toEval).b.foo, 'bar', "eval-ed object looks right");
    180    is(Cu.waiveXrays(iwin.eval(toEval)).f(), Cu.waiveXrays(iwin), "evaled function works right");
    181 
    182    testDate();
    183 
    184    testObject();
    185 
    186    testArray();
    187 
    188    testTypedArrays();
    189 
    190    testErrorObjects();
    191 
    192    testRegExp();
    193 
    194    testPromise();
    195 
    196    testArrayBuffer();
    197 
    198    testMap();
    199 
    200    testSet();
    201 
    202    testWeakMap();
    203 
    204    testWeakSet();
    205 
    206    testProxy();
    207 
    208    testDataView();
    209 
    210    testNumber();
    211 
    212    SimpleTest.finish();
    213  }
    214 
    215  // Maintain a static list of the properties that are available on each standard
    216  // prototype, so that we make sure to audit any new ones to make sure they're
    217  // Xray-safe.
    218  //
    219  // DO NOT CHANGE WTIHOUT REVIEW FROM AN XPCONNECT PEER.
    220  var gPrototypeProperties = {};
    221  var gConstructorProperties = {};
    222  // Properties which cannot be invoked if callable without potentially
    223  // rendering the object useless.
    224  var gStatefulProperties = {};
    225  function constructorProps(arr) {
    226    // Some props live on all constructors
    227    return arr.concat(["prototype", "length", "name"]);
    228  }
    229  gPrototypeProperties.Date =
    230    ["getTime", "getTimezoneOffset", "getYear", "getFullYear", "getUTCFullYear",
    231    "getMonth", "getUTCMonth", "getDate", "getUTCDate", "getDay", "getUTCDay",
    232    "getHours", "getUTCHours", "getMinutes", "getUTCMinutes", "getSeconds",
    233    "getUTCSeconds", "getMilliseconds", "getUTCMilliseconds", "setTime",
    234    "setYear", "setFullYear", "setUTCFullYear", "setMonth", "setUTCMonth",
    235    "setDate", "setUTCDate", "setHours", "setUTCHours", "setMinutes",
    236    "setUTCMinutes", "setSeconds", "setUTCSeconds", "setMilliseconds",
    237    "setUTCMilliseconds", "toUTCString", "toLocaleString",
    238    "toLocaleDateString", "toLocaleTimeString", "toDateString", "toTimeString",
    239    "toISOString", "toJSON", "toSource", "toString", "toTemporalInstant",
    240    "valueOf", "constructor", "toGMTString", Symbol.toPrimitive];
    241  gConstructorProperties.Date = constructorProps(["UTC", "parse", "now"]);
    242  gPrototypeProperties.Object =
    243    ["constructor", "toSource", "toString", "toLocaleString", "valueOf",
    244     "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable",
    245     "__defineGetter__", "__defineSetter__", "__lookupGetter__", "__lookupSetter__",
    246     "__proto__"];
    247  gConstructorProperties.Object =
    248    constructorProps(["setPrototypeOf", "getOwnPropertyDescriptor", "getOwnPropertyDescriptors",
    249                      "keys", "is", "defineProperty", "defineProperties", "create",
    250                      "getOwnPropertyNames", "getOwnPropertySymbols",
    251                      "preventExtensions", "freeze", "fromEntries", "isFrozen", "seal",
    252                      "isSealed", "assign", "getPrototypeOf", "values",
    253                      "entries", "isExtensible", "hasOwn", "groupBy"]);
    254  gPrototypeProperties.Array =
    255    ["length", "toSource", "toString", "toLocaleString", "join", "reverse", "sort", "push",
    256      "pop", "shift", "unshift", "splice", "concat", "slice", "lastIndexOf", "indexOf",
    257      "includes", "forEach", "map", "reduce", "reduceRight", "filter", "some", "every", "find",
    258      "findIndex", "copyWithin", "fill", Symbol.iterator, Symbol.unscopables, "entries", "keys",
    259      "values", "constructor", "flat", "flatMap", "at", "findLast", "findLastIndex",
    260      "toReversed", "toSorted", "toSpliced", "with"];
    261  gConstructorProperties.Array =
    262    constructorProps(["isArray", "from", "fromAsync", "of", Symbol.species]);
    263  for (let c of typedArrayClasses) {
    264    gPrototypeProperties[c] = ["constructor", "BYTES_PER_ELEMENT"];
    265    gConstructorProperties[c] = constructorProps(["BYTES_PER_ELEMENT"]);
    266  }
    267  // There is no TypedArray constructor, looks like.
    268  is(window.TypedArray, undefined, "If this ever changes, add to this test!");
    269  for (let c of errorObjectClasses) {
    270      gPrototypeProperties[c] = ["constructor", "name", "message", "stack"];
    271      gConstructorProperties[c] = constructorProps([]);
    272  }
    273 
    274  if (typeof Error.isError === "function") {
    275    gConstructorProperties.Error.push("isError");
    276  }
    277 
    278  gConstructorProperties.Error.push("captureStackTrace");
    279 
    280  // toString and toSource only live on the parent proto (Error.prototype).
    281  gPrototypeProperties.Error.push('toString');
    282  gPrototypeProperties.Error.push('toSource');
    283 
    284  gPrototypeProperties.Function =
    285    ["constructor", "toSource", "toString", "apply", "call", "bind",
    286     "length", "name", "arguments", "caller", Symbol.hasInstance];
    287  gConstructorProperties.Function = constructorProps([])
    288 
    289  gPrototypeProperties.RegExp =
    290    ["constructor", "toSource", "toString", "compile", "exec", "test",
    291     Symbol.match, Symbol.matchAll, Symbol.replace, Symbol.search, Symbol.split,
    292     "flags", "dotAll", "global", "hasIndices", "ignoreCase", "multiline", "source", "sticky",
    293     "unicode", "unicodeSets"];
    294  gConstructorProperties.RegExp =
    295    constructorProps(["escape", "input", "lastMatch", "lastParen",
    296                      "leftContext", "rightContext", "$1", "$2", "$3", "$4",
    297                      "$5", "$6", "$7", "$8", "$9", "$_", "$&", "$+",
    298                      "$`", "$'", Symbol.species])
    299 
    300  gPrototypeProperties.Promise =
    301    ["constructor", "catch", "then", "finally", Symbol.toStringTag];
    302 
    303  gConstructorProperties.Promise =
    304      constructorProps(["resolve", "reject", "all", "allSettled", "any", "race", "try",
    305                        "withResolvers", Symbol.species]);
    306 
    307  gPrototypeProperties.ArrayBuffer =
    308    ["constructor", "byteLength", "detached", "slice", Symbol.toStringTag, "transfer", "transferToFixedLength", "maxByteLength", "resizable", "resize"];
    309  gConstructorProperties.ArrayBuffer =
    310    constructorProps(["isView", Symbol.species]);
    311  gStatefulProperties.ArrayBuffer = ["transfer", "transferToFixedLength"]
    312 
    313  gPrototypeProperties.SharedArrayBuffer = ["constructor", "slice", "byteLength", "detached", Symbol.toStringTag, "transfer", "transferToFixedLength", "maxByteLength", "growable", "grow"];
    314  gConstructorProperties.SharedArrayBuffer = constructorProps([Symbol.species]);
    315  gStatefulProperties.SharedArrayBuffer = ["transfer", "transferToFixedLength"]
    316 
    317  gPrototypeProperties.Map =
    318    ["constructor", "size", Symbol.toStringTag, "get", "getOrInsert", "getOrInsertComputed", "has", "set", "delete",
    319     "keys", "values", "clear", "forEach", "entries", Symbol.iterator];
    320  gConstructorProperties.Map =
    321    constructorProps(["groupBy", Symbol.species]);
    322 
    323  gPrototypeProperties.Set =
    324     [Symbol.toStringTag, Symbol.iterator, "add", "clear", "constructor", "delete",
    325      "difference", "entries", "forEach", "has", "intersection", "isDisjointFrom",
    326      "isSubsetOf", "isSupersetOf", "keys", "size", "symmetricDifference", "union",
    327      "values"];
    328  gConstructorProperties.Set =
    329    constructorProps([Symbol.species]);
    330 
    331  gPrototypeProperties.WeakMap =
    332    ["constructor", Symbol.toStringTag, "get", "getOrInsert", "getOrInsertComputed", "has", "set", "delete"];
    333  gConstructorProperties.WeakMap =
    334    constructorProps([]);
    335 
    336  gPrototypeProperties.WeakSet =
    337    ["constructor", Symbol.toStringTag, "has", "add", "delete"];
    338  gConstructorProperties.WeakSet =
    339    constructorProps([]);
    340 
    341  gPrototypeProperties.DataView =
    342    ["constructor", "buffer", "byteLength", "byteOffset", Symbol.toStringTag,
    343     "getInt8", "getUint8", "getInt16", "getUint16",
    344     "getInt32", "getUint32", "getFloat32", "getFloat64",
    345     "setInt8", "setUint8", "setInt16", "setUint16",
    346     "setInt32", "setUint32", "setFloat32", "setFloat64",
    347     "getBigInt64", "getBigUint64", "setBigInt64", "setBigUint64",
    348     "getFloat16", "setFloat16"];
    349  gConstructorProperties.DataView = constructorProps([]);
    350 
    351  // Sort an array that may contain symbols as well as strings.
    352  function sortProperties(arr) {
    353    function sortKey(prop) {
    354      return typeof prop + ":" + prop.toString();
    355    }
    356    arr.sort((a, b) => sortKey(a) < sortKey(b) ? -1 : +1);
    357  }
    358 
    359  // Sort all the lists so we don't need to mutate them later (or copy them
    360  // again to sort them).
    361  for (let c of Object.keys(gPrototypeProperties))
    362    sortProperties(gPrototypeProperties[c]);
    363  for (let c of Object.keys(gConstructorProperties))
    364    sortProperties(gConstructorProperties[c]);
    365 
    366  function filterOut(array, props) {
    367    return array.filter(p => !props.includes(p));
    368  }
    369 
    370  function isTypedArrayClass(classname) {
    371    return typedArrayClasses.includes(classname);
    372  }
    373 
    374  function propertyIsGetter(obj, name) {
    375    return !!Object.getOwnPropertyDescriptor(obj, name).get;
    376  }
    377 
    378  function testProtoCallables(protoCallables, xray, xrayProto, localProto, callablesExcluded) {
    379    // Handle undefined callablesExcluded.
    380    let dontCall = callablesExcluded ?? [];
    381    for (let name of protoCallables) {
    382      info("Running tests for property: " + name);
    383      // Test both methods and getter properties.
    384      function lookupCallable(obj) {
    385        let desc = null;
    386        do {
    387          desc = Object.getOwnPropertyDescriptor(obj, name);
    388          if (desc) {
    389            break;
    390          }
    391          obj = Object.getPrototypeOf(obj);
    392        } while (obj);
    393        return desc ? (desc.get || desc.value) : undefined;
    394      };
    395      ok(xrayProto.hasOwnProperty(name), `proto should have the property '${name}' as own`);
    396      ok(!xray.hasOwnProperty(name), `instance should not have the property '${name}' as own`);
    397      let method = lookupCallable(xrayProto);
    398      is(typeof method, 'function', "Methods from Xrays are functions");
    399      is(global(method), window, "Methods from Xrays are local");
    400      ok(method instanceof Function, "instanceof works on methods from Xrays");
    401      is(lookupCallable(xrayProto), method, "Holder caching works properly");
    402      is(lookupCallable(xray), method, "Proto props resolve on the instance");
    403      let local = lookupCallable(localProto);
    404      is(method.length, local.length, "Function.length identical");
    405      if (!method.length && !dontCall.includes(name)) {
    406        try {
    407          is(method.call(xray) + "", local.call(xray) + "",
    408             "Xray and local method results stringify identically");
    409        } catch (e) {
    410          let expected = new RegExp(e.toString());
    411          checkThrows(function() { method.call(xray) + ""; }, expected, "Xray and local methods both throw when stringified");
    412          checkThrows(function() { local.call(xray) + ""; }, expected, "Xray and local methods both throw when stringified");
    413        }
    414        // If invoking this method returns something non-Xrayable (opaque), the
    415        // stringification is going to return [object Object].
    416        // This happens for set[@@iterator] and other Iterator objects.
    417        let callable = lookupCallable(xray.wrappedJSObject);
    418        if (!Cu.isOpaqueWrapper(method.call(xray)) && callable) {
    419          try {
    420            is(method.call(xray) + "",
    421               callable.call(xray.wrappedJSObject) + "",
    422               "Xray and waived method results stringify identically");
    423          } catch (e) {
    424            let expected = new RegExp(e.toString());
    425            checkThrows(function() { method.call(xray) + ""; }, expected, "Xray and local methods both throw when stringified");
    426            checkThrows(function() { callable.call(xray.wrappedJSObject) + ""; }, expected, "Xray and local methods both throw when stringified");
    427          }
    428        }
    429      }
    430    }
    431  }
    432 
    433  function testCtorCallables(ctorCallables, xrayCtor, localCtor) {
    434    for (let name of ctorCallables) {
    435      // Don't try to test Function.prototype, since that is in fact a callable
    436      // but doesn't really do the things we expect callables to do here
    437      // (e.g. it's in the wrong global, since it gets Xrayed itself).
    438      if (name == "prototype" && localCtor.name == "Function") {
    439        continue;
    440      }
    441      info(`Running tests for property: ${localCtor.name}.${name}`);
    442      // Test both methods and getter properties.
    443      function lookupCallable(obj) {
    444        let desc = null;
    445        do {
    446          desc = Object.getOwnPropertyDescriptor(obj, name);
    447          obj = Object.getPrototypeOf(obj);
    448        } while (!desc);
    449        return desc.get || desc.value;
    450      };
    451 
    452      ok(xrayCtor.hasOwnProperty(name), "ctor should have the property as own");
    453      let method = lookupCallable(xrayCtor);
    454      is(typeof method, 'function', "Methods from ctor Xrays are functions");
    455      is(global(method), window, "Methods from ctor Xrays are local");
    456      ok(method instanceof Function,
    457         "instanceof works on methods from ctor Xrays");
    458      is(lookupCallable(xrayCtor), method,
    459         "Holder caching works properly on ctors");
    460      let local = lookupCallable(localCtor);
    461      is(method.length, local.length,
    462         "Function.length identical for method from ctor");
    463      // Don't try to do the return-value check on Date.now(), since there is
    464      // absolutely no reason it should return the same value each time.
    465      //
    466      // Also don't try to do the return-value check on Regexp.lastMatch and
    467      // Regexp["$&"] (which are aliases), because they get state off the global
    468      // they live in, as far as I can tell, so testing them over Xrays will be
    469      // wrong: on the Xray they will actaully get the lastMatch of _our_
    470      // global, not the Xrayed one.
    471      if (!method.length &&
    472          !(localCtor.name == "Date" && name == "now") &&
    473          !(localCtor.name == "RegExp" && (name == "lastMatch" || name == "$&"))) {
    474        is(method.call(xrayCtor) + "", local.call(xrayCtor) + "",
    475           "Xray and local method results stringify identically on constructors");
    476        is(method.call(xrayCtor) + "",
    477           lookupCallable(xrayCtor.wrappedJSObject).call(xrayCtor.wrappedJSObject) + "",
    478           "Xray and waived method results stringify identically");
    479      }
    480    }
    481  }
    482 
    483  function testXray(classname, xray, xray2, propsToSkip, ctorPropsToSkip = []) {
    484    propsToSkip = propsToSkip || [];
    485    let xrayProto = Object.getPrototypeOf(xray);
    486    let localProto = window[classname].prototype;
    487    let desiredProtoProps = Object.getOwnPropertyNames(localProto).sort();
    488 
    489    is(desiredProtoProps.toSource(),
    490       gPrototypeProperties[classname].filter(id => typeof id === "string").toSource(),
    491       "A property on the " + classname +
    492       " prototype has changed! You need a security audit from an XPConnect peer");
    493    is(Object.getOwnPropertySymbols(localProto).map(uneval).sort().toSource(),
    494       gPrototypeProperties[classname].filter(id => typeof id !== "string").map(uneval).sort().toSource(),
    495       "A symbol-keyed property on the " + classname +
    496       " prototype has been changed! You need a security audit from an XPConnect peer");
    497 
    498    let protoProps = filterOut(desiredProtoProps, propsToSkip);
    499    let protoCallables = protoProps.filter(name => propertyIsGetter(localProto, name, classname) ||
    500                                                   typeof localProto[name] == 'function' &&
    501                                                   name != 'constructor');
    502    let callablesExcluded = gStatefulProperties[classname];
    503    ok(!!protoCallables.length, "Need something to test");
    504    is(xrayProto, iwin[classname].prototype, "Xray proto is correct");
    505    is(xrayProto, xray.__proto__, "Proto accessors agree");
    506    var protoProto = classname == "Object" ? null : iwin.Object.prototype;
    507    is(Object.getPrototypeOf(xrayProto), protoProto, "proto proto is correct");
    508    testProtoCallables(protoCallables, xray, xrayProto, localProto, callablesExcluded);
    509    is(Object.getOwnPropertyNames(xrayProto).sort().toSource(),
    510       protoProps.toSource(), "getOwnPropertyNames works");
    511    is(Object.getOwnPropertySymbols(xrayProto).map(uneval).sort().toSource(),
    512       gPrototypeProperties[classname].filter(id => typeof id !== "string" && !propsToSkip.includes(id))
    513         .map(uneval).sort().toSource(),
    514       "getOwnPropertySymbols works");
    515 
    516    is(xrayProto.constructor, iwin[classname], "constructor property works");
    517 
    518    xrayProto.expando = 42;
    519    is(xray.expando, 42, "Xrayed instances see proto expandos");
    520    is(xray2.expando, 42, "Xrayed instances see proto expandos");
    521 
    522    // Now test constructors
    523    let localCtor = window[classname];
    524    let xrayCtor = xrayProto.constructor;
    525    // We already checked that this is the same as iwin[classname]
    526 
    527    let desiredCtorProps =
    528        Object.getOwnPropertyNames(localCtor).sort();
    529    is(desiredCtorProps.toSource(),
    530       gConstructorProperties[classname].filter(id => typeof id === "string").toSource(),
    531       "A property on the " + classname +
    532       " constructor has changed! You need a security audit from an XPConnect peer");
    533    let desiredCtorSymbols =
    534        Object.getOwnPropertySymbols(localCtor).map(uneval).sort()
    535    is(desiredCtorSymbols.toSource(),
    536       gConstructorProperties[classname].filter(id => typeof id !== "string").map(uneval).sort().toSource(),
    537       "A symbol-keyed property on the " + classname +
    538       " constructor has been changed! You need a security audit from an XPConnect peer");
    539 
    540    let ctorProps = filterOut(desiredCtorProps, ctorPropsToSkip);
    541    let ctorSymbols = filterOut(desiredCtorSymbols, ctorPropsToSkip.map(uneval));
    542    let ctorCallables = ctorProps.filter(name => propertyIsGetter(localCtor, name, classname) ||
    543                                         typeof localCtor[name] == 'function');
    544    testCtorCallables(ctorCallables, xrayCtor, localCtor);
    545    is(Object.getOwnPropertyNames(xrayCtor).sort().toSource(),
    546       ctorProps.toSource(), "getOwnPropertyNames works on Xrayed ctors");
    547    is(Object.getOwnPropertySymbols(xrayCtor).map(uneval).sort().toSource(),
    548       ctorSymbols.toSource(), "getOwnPropertySymbols works on Xrayed ctors");
    549  }
    550 
    551  // We will need arraysEqual and testArrayIterators both in this global scope
    552  // and in sandboxes.
    553  function arraysEqual(arr1, arr2, reason) {
    554    is(arr1.length, arr2.length, `${reason}; lengths should be equal`)
    555    for (var i = 0; i < arr1.length; ++i) {
    556      if (Array.isArray(arr2[i])) {
    557        arraysEqual(arr1[i], arr2[i], `${reason}; item at index ${i}`);
    558      } else {
    559        is(arr1[i], arr2[i], `${reason}; item at index ${i} should be equal`);
    560      }
    561    }
    562  }
    563 
    564  function testArrayIterators(arrayLike, equivalentArray, reason) {
    565    arraysEqual([...arrayLike], equivalentArray, `${reason}; spread operator`);
    566    arraysEqual([...arrayLike.entries()], [...equivalentArray.entries()],
    567                `${reason}; entries`);
    568    arraysEqual([...arrayLike.keys()], [...equivalentArray.keys()],
    569                `${reason}; keys`);
    570    if (arrayLike.values) {
    571      arraysEqual([...arrayLike.values()], equivalentArray,
    572                  `${reason}; values`);
    573    }
    574 
    575    var forEachCopy = [];
    576    arrayLike.forEach(function(arg) { forEachCopy.push(arg); });
    577    arraysEqual(forEachCopy, equivalentArray, `${reason}; forEach copy`);
    578 
    579    var everyCopy = [];
    580    arrayLike.every(function(arg) { everyCopy.push(arg); return true; });
    581    arraysEqual(everyCopy, equivalentArray, `${reason}; every() copy`);
    582 
    583    var filterCopy = [];
    584    var filterResult = arrayLike.filter(function(arg) {
    585      filterCopy.push(arg);
    586      return true;
    587    });
    588    arraysEqual(filterCopy, equivalentArray, `${reason}; filter copy`);
    589    arraysEqual([...filterResult], equivalentArray, `${reason}; filter result`);
    590 
    591    var findCopy = [];
    592    arrayLike.find(function(arg) { findCopy.push(arg); return false; });
    593    arraysEqual(findCopy, equivalentArray, `${reason}; find() copy`);
    594 
    595    var findIndexCopy = [];
    596    arrayLike.findIndex(function(arg) { findIndexCopy.push(arg); return false; });
    597    arraysEqual(findIndexCopy, equivalentArray, `${reason}; findIndex() copy`);
    598 
    599    var mapCopy = [];
    600    var mapResult = arrayLike.map(function(arg) { mapCopy.push(arg); return arg});
    601    arraysEqual(mapCopy, equivalentArray, `${reason}; map() copy`);
    602    arraysEqual([...mapResult], equivalentArray, `${reason}; map() result`);
    603 
    604    var reduceCopy = [];
    605    arrayLike.reduce(function(_, arg) { reduceCopy.push(arg); }, 0);
    606    arraysEqual(reduceCopy, equivalentArray, `${reason}; reduce() copy`);
    607 
    608    var reduceRightCopy = [];
    609    arrayLike.reduceRight(function(_, arg) { reduceRightCopy.unshift(arg); }, 0);
    610    arraysEqual(reduceRightCopy, equivalentArray, `${reason}; reduceRight() copy`);
    611 
    612    var someCopy = [];
    613    arrayLike.some(function(arg) { someCopy.push(arg); return false; });
    614    arraysEqual(someCopy, equivalentArray, `${reason}; some() copy`);
    615  }
    616 
    617  function testDate() {
    618    // toGMTString is handled oddly in the engine. We don't bother to support
    619    // it over Xrays.
    620    let propsToSkip = ['toGMTString'];
    621 
    622    testXray('Date', new iwin.Date(), new iwin.Date(), propsToSkip);
    623 
    624    // Test the self-hosted toLocaleString.
    625    var d = new iwin.Date();
    626    isnot(d.toLocaleString, Cu.unwaiveXrays(d.wrappedJSObject.toLocaleString), "Different function identities");
    627    is(Cu.getGlobalForObject(d.toLocaleString), window, "Xray global is correct");
    628    is(Cu.getGlobalForObject(d.wrappedJSObject.toLocaleString), iwin, "Underlying global is correct");
    629    is(d.toLocaleString('de-DE'), d.wrappedJSObject.toLocaleString('de-DE'), "Results match");
    630  }
    631 
    632  var uniqueSymbol;
    633 
    634  function testObject() {
    635    testXray('Object', Cu.unwaiveXrays(Cu.waiveXrays(iwin).Object.create(new iwin.Object())),
    636             new iwin.Object(), []);
    637 
    638    // Construct an object full of tricky things.
    639    let symbolProps = '';
    640    uniqueSymbol = iwin.eval('var uniqueSymbol = Symbol("uniqueSymbol"); uniqueSymbol');
    641    symbolProps = `, [uniqueSymbol]: 43,
    642                   [Symbol.for("registrySymbolProp")]: 44`;
    643    var trickyObject =
    644      iwin.eval(`(function() {
    645                 var o = new Object({
    646                   primitiveProp: 42, objectProp: { foo: 2 },
    647                   xoProp: top, hasOwnProperty: 10,
    648                   get getterProp() { return 2; },
    649                   set setterProp(x) { },
    650                   get getterSetterProp() { return 3; },
    651                   set getterSetterProp(x) { },
    652                   callableProp: function() { },
    653                   nonXrayableProp: new Map()[Symbol.iterator]()
    654                   ${symbolProps}
    655                 });
    656                 Object.defineProperty(o, "nonConfigurableGetterSetterProp",
    657                    { get: function() { return 5; }, set: function() {} });
    658                 return o;
    659                 })()`);
    660    testTrickyObject(trickyObject);
    661  }
    662 
    663  function testArray() {
    664    // The |length| property is generally very weird, especially with respect
    665    // to its behavior on the prototype. Array.prototype is actually an Array
    666    // instance, and therefore has a vestigial .length. But we don't want to
    667    // show that over Xrays, and generally want .length to just appear as an
    668    // |own| data property. So we add it to the ignore list here, and check it
    669    // separately.
    670    //
    671    // |Symbol.unscopables| should in principle be exposed, but it is
    672    // inconvenient (as it's a data property, unsupported by ClassSpec) and
    673    // low value.
    674    let propsToSkip = ['length', Symbol.unscopables];
    675 
    676    testXray('Array', new iwin.Array(20), new iwin.Array(), propsToSkip);
    677 
    678    let symbolProps = '';
    679    uniqueSymbol = iwin.eval('var uniqueSymbol = Symbol("uniqueSymbol"); uniqueSymbol');
    680    symbolProps = `trickyArray[uniqueSymbol] = 43;
    681                   trickyArray[Symbol.for("registrySymbolProp")] = 44;`;
    682    var trickyArray =
    683      iwin.eval(`var trickyArray = [];
    684                 trickyArray.primitiveProp = 42;
    685                 trickyArray.objectProp = { foo: 2 };
    686                 trickyArray.xoProp = top;
    687                 trickyArray.hasOwnProperty = 10;
    688                 Object.defineProperty(trickyArray, 'getterProp', { get: function() { return 2; }});
    689                 Object.defineProperty(trickyArray, 'setterProp', { set: function(x) {}});
    690                 Object.defineProperty(trickyArray, 'getterSetterProp', { get: function() { return 3; }, set: function(x) {}, configurable: true});
    691                 Object.defineProperty(trickyArray, 'nonConfigurableGetterSetterProp', { get: function() { return 5; }, set: function(x) {}});
    692                 trickyArray.callableProp = function() {};
    693                 trickyArray.nonXrayableProp = new Map()[Symbol.iterator]();
    694                 ${symbolProps}
    695                 trickyArray;`);
    696 
    697    // Test indexed access.
    698    trickyArray.wrappedJSObject[9] = "some indexed property";
    699    is(trickyArray[9], "some indexed property", "indexed properties work correctly over Xrays");
    700    is(trickyArray.length, 10, "Length works correctly over Xrays");
    701    checkThrows(function() { "use strict"; delete trickyArray.length; }, /config/, "Can't delete non-configurable 'length' property");
    702    delete trickyArray[9];
    703    is(trickyArray[9], undefined, "Delete works correctly over Xrays");
    704    is(trickyArray.wrappedJSObject[9], undefined, "Delete works correctly over Xrays (viewed via waiver)");
    705    is(trickyArray.length, 10, "length doesn't change");
    706    trickyArray[11] = "some other indexed property";
    707    is(trickyArray.length, 12, "length now changes");
    708    is(trickyArray.wrappedJSObject[11], "some other indexed property");
    709    trickyArray.length = 0;
    710    is(trickyArray.length, 0, "Setting length works over Xray");
    711    is(trickyArray[11], undefined, "Setting length truncates over Xray");
    712    Object.defineProperty(trickyArray, 'length', { configurable: false, enumerable: false, writable: false, value: 0 });
    713    trickyArray[1] = "hi";
    714    is(trickyArray.length, 0, "Length remains non-writable");
    715    is(trickyArray[1], undefined, "Frozen length forbids new properties");
    716    is(trickyArray instanceof iwin.Array, true, "instanceof should work across xray wrappers.");
    717    testTrickyObject(trickyArray);
    718 
    719    testArrayIterators(new iwin.Array(1, 1, 2, 3, 5), [1, 1, 2, 3, 5]);
    720  }
    721 
    722  // Parts of this function are kind of specific to testing Object, but we factor
    723  // it out so that we can re-use the trickyObject stuff on Arrays.
    724  function testTrickyObject(trickyObject) {
    725 
    726    // Make sure it looks right under the hood.
    727    is(trickyObject.wrappedJSObject.getterProp, 2, "Underlying object has getter");
    728    is(Cu.unwaiveXrays(trickyObject.wrappedJSObject.xoProp), top, "Underlying object has xo property");
    729 
    730    // Test getOwnPropertyNames.
    731    var expectedNames = ['objectProp', 'primitiveProp'];
    732    if (trickyObject instanceof iwin.Array)
    733      expectedNames.push('length');
    734    is(Object.getOwnPropertyNames(trickyObject).sort().toSource(),
    735       expectedNames.sort().toSource(), "getOwnPropertyNames should be filtered correctly");
    736    var expectedSymbols = [Symbol.for("registrySymbolProp"), uniqueSymbol];
    737    is(Object.getOwnPropertySymbols(trickyObject).map(uneval).sort().toSource(),
    738       expectedSymbols.map(uneval).sort().toSource(),
    739       "getOwnPropertySymbols should be filtered correctly");
    740 
    741    // Test that cloning uses the Xray view.
    742    var cloned = Cu.cloneInto(trickyObject, this);
    743    is(Object.getOwnPropertyNames(cloned).sort().toSource(),
    744       expectedNames.sort().toSource(), "structured clone should use the Xray view");
    745    is(Object.getOwnPropertySymbols(cloned).map(uneval).sort().toSource(),
    746       "[]", "structured cloning doesn't clone symbol-keyed properties yet");
    747 
    748    // Test iteration and in-place modification. Beware of 'expando', which is the property
    749    // we placed on the xray proto.
    750    var propCount = 0;
    751    for (let prop in trickyObject) {
    752      if (prop == 'primitiveProp')
    753        trickyObject[prop] = trickyObject[prop] - 10;
    754      if (prop != 'expando') {
    755        // eslint-disable-next-line no-self-assign
    756        trickyObject[prop] = trickyObject[prop];
    757      }
    758      ++propCount;
    759    }
    760    is(propCount, 3, "Should iterate the correct number of times");
    761 
    762    // Test Object.keys.
    763    is(Object.keys(trickyObject).sort().toSource(),
    764       ['objectProp', 'primitiveProp'].toSource(), "Object.keys should be filtered correctly");
    765 
    766    // Test getOwnPropertyDescriptor.
    767    is(trickyObject.primitiveProp, 32, "primitive prop works");
    768    is(trickyObject.objectProp.foo, 2, "object prop works");
    769    is(typeof trickyObject.callableProp, 'undefined', "filtering works correctly");
    770    is(Object.getOwnPropertyDescriptor(trickyObject, 'primitiveProp').value, 32, "getOwnPropertyDescriptor works");
    771    is(Object.getOwnPropertyDescriptor(trickyObject, 'xoProp'), undefined, "filtering works with getOwnPropertyDescriptor");
    772 
    773    // Test defineProperty.
    774 
    775    trickyObject.primitiveSetByXray = 'fourty two';
    776    is(trickyObject.primitiveSetByXray, 'fourty two', "Can set primitive correctly over Xray (ready via Xray)");
    777    is(trickyObject.wrappedJSObject.primitiveSetByXray, 'fourty two', "Can set primitive correctly over Xray (ready via Waiver)");
    778 
    779    var newContentObject = iwin.eval('new Object({prop: 99, get getterProp() { return 2; }})');
    780    trickyObject.objectSetByXray = newContentObject;
    781    is(trickyObject.objectSetByXray.prop, 99, "Can set object correctly over Xray (ready via Xray)");
    782    is(trickyObject.wrappedJSObject.objectSetByXray.prop, 99, "Can set object correctly over Xray (ready via Waiver)");
    783    checkThrows(function() { trickyObject.rejectedProp = {foo: 33}}, /cross-origin object/,
    784                "Should reject privileged object property definition");
    785 
    786    // Test JSON.stringify.
    787    var jsonStr = JSON.stringify(newContentObject);
    788    ok(/prop/.test(jsonStr), "JSON stringification should work: " + jsonStr);
    789 
    790    // Test deletion.
    791    delete newContentObject.prop;
    792    ok(!newContentObject.hasOwnProperty('prop'), "Deletion should work");
    793    ok(!newContentObject.wrappedJSObject.hasOwnProperty('prop'), "Deletion should forward");
    794    delete newContentObject.getterProp;
    795    ok(newContentObject.wrappedJSObject.hasOwnProperty('getterProp'), "Deletion be no-op for filtered property");
    796 
    797    // We should be able to overwrite an existing accessor prop and convert it
    798    // to a value prop.
    799    is(trickyObject.wrappedJSObject.getterSetterProp, 3, "Underlying object has getter");
    800    is(trickyObject.getterSetterProp, undefined, "Filtering properly over Xray");
    801    trickyObject.getterSetterProp = 'redefined';
    802    is(trickyObject.getterSetterProp, 'redefined', "Redefinition works");
    803    is(trickyObject.wrappedJSObject.getterSetterProp, 'redefined', "Redefinition forwards");
    804 
    805    // We should NOT be able to overwrite an existing non-configurable accessor
    806    // prop, though.
    807    is(trickyObject.wrappedJSObject.nonConfigurableGetterSetterProp, 5,
    808       "Underlying object has getter");
    809    is(trickyObject.nonConfigurableGetterSetterProp, undefined,
    810       "Filtering properly over Xray here too");
    811    is((trickyObject.nonConfigurableGetterSetterProp = 'redefined'), 'redefined',
    812       "Assigning to non-configurable prop should fail silently in non-strict mode");
    813    checkThrows(function() {
    814      "use strict";
    815      trickyObject.nonConfigurableGetterSetterProp = 'redefined';
    816    }, /config/, "Should throw when redefining non-configurable prop in strict mode");
    817    is(trickyObject.nonConfigurableGetterSetterProp, undefined,
    818       "Redefinition should have failed");
    819    is(trickyObject.wrappedJSObject.nonConfigurableGetterSetterProp, 5,
    820       "Redefinition really should have failed");
    821 
    822    checkThrows(function() { trickyObject.hasOwnProperty = 33; }, /shadow/,
    823                "Should reject shadowing of pre-existing inherited properties over Xrays");
    824 
    825    checkThrows(function() { Object.defineProperty(trickyObject, 'rejectedProp', { get() { return undefined; }}); },
    826                /accessor property/, "Should reject accessor property definition");
    827  }
    828 
    829  function testTypedArrays() {
    830    // We don't invoke testXray with %TypedArray%, because that function isn't
    831    // set up to deal with "anonymous" dependent classes (that is, classes not
    832    // visible as a global property, which %TypedArray% is not), and fixing it
    833    // up is more trouble than it's worth.
    834 
    835    var typedArrayProto = Object.getPrototypeOf(Int8Array.prototype);
    836 
    837    var desiredInheritedProps = Object.getOwnPropertyNames(typedArrayProto).sort();
    838    var inheritedProps =
    839      filterOut(desiredInheritedProps, ["BYTES_PER_ELEMENT", "constructor"]);
    840 
    841    var inheritedCallables =
    842      inheritedProps.filter(name => (propertyIsGetter(typedArrayProto, name) ||
    843                                     typeof typedArrayProto[name] === "function") &&
    844                                    name !== "constructor");
    845 
    846    for (let c of typedArrayClasses) {
    847      var t = new iwin[c](10);
    848      checkThrows(function() { t[2]; }, /performant/, "direct property-wise reading of typed arrays forbidden over Xrays");
    849      checkThrows(function() { t[2] = 3; }, /performant/, "direct property-wise writing of typed arrays forbidden over Xrays");
    850      var wesb = new Cu.Sandbox([iwin], {isWebExtensionContentScript: true});
    851      wesb.t = t;
    852      wesb.eval('t[2] = 3');
    853      is(wesb.eval('t.wrappedJSObject[2]'), 3, "direct property-wise writing of typed arrays allowed for WebExtension content scripts");
    854      is(wesb.eval('t[2]'), 3, "direct property-wise reading and writing of typed arrays allowed for WebExtensions content scripts");
    855 
    856      t.wrappedJSObject[2] = 3;
    857      is(t.wrappedJSObject[2], 3, "accessing elements over waivers works");
    858      t.wrappedJSObject.expando = 'hi';
    859      is(t.wrappedJSObject.expando, 'hi', "access expandos over waivers works");
    860      is(Cu.cloneInto(t, window)[2], 3, "cloneInto works");
    861      is(Cu.cloneInto(t, window).expando, undefined, "cloneInto does not copy expandos");
    862      is(Object.getOwnPropertyNames(t).sort().toSource(),
    863         '["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]',
    864         "Only indexed properties visible over Xrays");
    865      Object.defineProperty(t.wrappedJSObject, 'length', {value: 42});
    866      is(t.wrappedJSObject.length, 42, "Set tricky expando")
    867      is(t.length, 10, "Length accessor works over Xrays")
    868      is(t.byteLength, t.length * window[c].prototype.BYTES_PER_ELEMENT, "byteLength accessor works over Xrays")
    869 
    870      // Can create TypedArray from content ArrayBuffer
    871      var buffer = new iwin.ArrayBuffer(8);
    872      new window[c](buffer);
    873 
    874      var xray = new iwin[c](0);
    875      var xrayTypedArrayProto = Object.getPrototypeOf(Object.getPrototypeOf(xray));
    876      testProtoCallables(inheritedCallables, new iwin[c](0), xrayTypedArrayProto, typedArrayProto);
    877 
    878      // When testing iterators, make sure to do so from inside our web
    879      // extension sandbox, since from chrome we can't poke their indices.  Note
    880      // that we have to actually recreate our functions that touch typed array
    881      // indices inside the sandbox, not just export them, because otherwise
    882      // they'll just run with our principal anyway.
    883      //
    884      // But we do want to export is(), since we want ours called.
    885      wesb.eval(String(arraysEqual));
    886      wesb.eval(String(testArrayIterators));
    887      Cu.exportFunction(is, wesb,
    888                        { defineAs: "is" });
    889      wesb.eval('testArrayIterators(t, [0, 0, 3, 0, 0, 0, 0, 0, 0, 0])');
    890    }
    891  }
    892 
    893  function testErrorObjects() {
    894    // We only invoke testXray with Error, because that function isn't set up
    895    // to deal with dependent classes and fixing it up is more trouble than
    896    // it's worth.
    897    testXray('Error', new iwin.Error('some error message'), new iwin.Error());
    898 
    899    // Make sure that the dependent classes have their prototypes set up correctly.
    900    for (let c of errorObjectClasses.filter(x => x != "Error")) {
    901      var args = ['some message'];
    902      if (c === 'AggregateError') {
    903        // AggregateError's first argument is the list of aggregated errors.
    904        args.unshift(new iwin.Array('error 1', 'error 2'));
    905      }
    906      var e = new iwin[c](...args);
    907      is(Object.getPrototypeOf(e).name, c, "Prototype has correct name");
    908      is(Object.getPrototypeOf(Object.getPrototypeOf(e)), iwin.Error.prototype, "Dependent prototype set up correctly");
    909      is(e.name, c, "Exception name inherited correctly");
    910 
    911      function testProperty(name, criterion, goodReplacement, faultyReplacement) {
    912        ok(criterion(e[name]), name + " property is correct: " + e[name]);
    913        e.wrappedJSObject[name] = goodReplacement;
    914        is(e[name], goodReplacement, name + " property ok after replacement: " + goodReplacement);
    915        e.wrappedJSObject[name] = faultyReplacement;
    916        is(e[name], name == 'message' ? "" : undefined, name + " property skipped after suspicious replacement");
    917      }
    918      testProperty('message', x => x == 'some message', 'some other message', 42);
    919      testProperty('fileName', x => x == '', 'otherFilename.html', new iwin.Object());
    920      testProperty('columnNumber', x => x == 1, 99, 99.5);
    921      testProperty('lineNumber', x => x == 0, 50, 'foo');
    922 
    923      if (c === 'AggregateError') {
    924        let {errors} = e;
    925        is(errors.length, 2, "errors property has the correct length");
    926        is(errors[0], 'error 1', "errors[0] has the correct value");
    927        is(errors[1], 'error 2', "errors[1] has the correct value");
    928 
    929        e.wrappedJSObject.errors = 42;
    930        is(e.wrappedJSObject.errors, 42, "errors is a plain data property");
    931        is(e.errors, 42, "visible over Xrays");
    932      }
    933 
    934      // Note - an Exception newed via Xrays is going to have an empty stack given the
    935      // current semantics and implementation. This tests the current behavior, but that
    936      // may change in bug 1036527 or similar.
    937      //
    938      // Furthermore, xrays should always return an error's original stack, and
    939      // not overwrite it.
    940      var stack = e.stack;
    941      ok(/^\s*$/.test(stack), "stack property should be correct");
    942      e.wrappedJSObject.stack = "not a stack";
    943      is(e.stack, stack, "Xrays should never get an overwritten stack property.");
    944 
    945      // Test the .cause property is correctly handled, too.
    946      if (isNightlyBuild) {
    947        let cause = 'error cause';
    948        let options = new iwin.Object();
    949        options.cause = cause;
    950        args.push(options);
    951 
    952        let e = new iwin[c](...args);
    953        is(e.cause, cause);
    954 
    955        e.wrappedJSObject.cause = 42;
    956        is(e.wrappedJSObject.cause, 42, "cause is a plain data property");
    957        is(e.cause, 42, "visible over Xrays");
    958      }
    959    }
    960  }
    961 
    962  function testRegExp() {
    963    // RegExp statics are very weird, and in particular RegExp has static
    964    // properties that have to do with the last regexp execution in the global.
    965    // Xraying those makes no sense, so we just skip constructor properties for
    966    // RegExp xrays.
    967    // RegExp[@@species] is affected by above skip, but we don't fix it until
    968    // compelling use-case appears, as supporting RegExp[@@species] while
    969    // skipping other static properties makes things complicated.
    970    // Since RegExp.escape is a method, there's no obvious reason to skip it,
    971    // but it would require some changes in the Xray code (would need to special
    972    // case it in xpc::JSXrayTraits::resolveOwnProperty and
    973    // xpc::JSXrayTraits::enumerateNames) that are not necessarily worth the effort
    974    // since it is a static method with no state.
    975    let ctorPropsToSkip = ["escape", "input", "lastMatch", "lastParen",
    976                           "leftContext", "rightContext", "$1", "$2", "$3",
    977                           "$4", "$5", "$6", "$7", "$8", "$9", "$_", "$&",
    978                           "$+", "$`", "$'", Symbol.species];
    979    testXray('RegExp', new iwin.RegExp('foo'), new iwin.RegExp(), [],
    980             ctorPropsToSkip);
    981 
    982    // Test the self-hosted |flags| property, toString, and toSource.
    983    for (var flags of ["", "g", "i", "m", "y", "gimy"]) {
    984      var re = new iwin.RegExp("foo", flags);
    985      is(re.flags, re.wrappedJSObject.flags, "Results match");
    986 
    987      isnot(re.toString, Cu.unwaiveXrays(re.wrappedJSObject.toString), "Different function identities");
    988      is(Cu.getGlobalForObject(re.toString), window, "Xray global is correct");
    989      is(Cu.getGlobalForObject(re.wrappedJSObject.toString), iwin, "Underlying global is correct");
    990      is(re.toString(), re.wrappedJSObject.toString(), "Results match");
    991 
    992      isnot(re.toSource, Cu.unwaiveXrays(re.wrappedJSObject.toSource), "Different function identities");
    993      is(Cu.getGlobalForObject(re.toSource), window, "Xray global is correct");
    994      if (re.wrappedJSObject.toSource) {
    995        is(Cu.getGlobalForObject(re.wrappedJSObject.toSource), iwin, "Underlying global is correct");
    996        is(re.toSource(), re.wrappedJSObject.toSource(), "Results match");
    997      }
    998 
    999      // Test with modified flags accessors
   1000      iwin.eval(`
   1001 var props = ["global", "ignoreCase", "multiline", "sticky", "source", "unicode"];
   1002 var origDescs = {};
   1003 for (var prop of props) {
   1004  origDescs[prop] = Object.getOwnPropertyDescriptor(RegExp.prototype, prop);
   1005  Object.defineProperty(RegExp.prototype, prop, {
   1006    get: function() {
   1007      throw new Error("modified accessor is called");
   1008    }
   1009  });
   1010 }
   1011 `);
   1012      try {
   1013        is(re.flags, flags, "Unmodified flags accessors are called");
   1014        is(re.toString(), "/foo/" + flags, "Unmodified flags and source accessors are called");
   1015        is(re.toSource(), "/foo/" + flags, "Unmodified flags and source accessors are called");
   1016      } finally {
   1017        iwin.eval(`
   1018 for (var prop of props) {
   1019  Object.defineProperty(RegExp.prototype, prop, origDescs[prop]);
   1020 }
   1021 `);
   1022      }
   1023    }
   1024  }
   1025 
   1026  // Note: this is a small set of basic tests. More in-depth tests are located
   1027  // in test_promise_xrays.html.
   1028  function testPromise() {
   1029    testXray('Promise', new iwin.Promise(function(){}), new iwin.Promise(function(){}));
   1030 
   1031    // Test catch and then.
   1032    var pr = new iwin.Promise(function(){});
   1033    isnot(pr.catch, Cu.unwaiveXrays(pr.wrappedJSObject.catch), "Different function identities");
   1034    is(Cu.getGlobalForObject(pr.catch), window, "Xray global is correct");
   1035    is(Cu.getGlobalForObject(pr.wrappedJSObject.catch), iwin, "Underlying global is correct");
   1036 
   1037    isnot(pr.then, Cu.unwaiveXrays(pr.wrappedJSObject.then), "Different function identities");
   1038    is(Cu.getGlobalForObject(pr.then), window, "Xray global is correct");
   1039    is(Cu.getGlobalForObject(pr.wrappedJSObject.then), iwin, "Underlying global is correct");
   1040  }
   1041 
   1042  function testArrayBuffer() {
   1043    let constructors = ['ArrayBuffer'];
   1044 
   1045    for (const c of constructors) {
   1046      testXray(c, new iwin[c](0), new iwin[c](12));
   1047 
   1048      var t = new iwin[c](12);
   1049      is(t.byteLength, 12, `${c} byteLength is correct`);
   1050 
   1051      is(t.slice(4).byteLength, 8, `${c} byteLength is correct after slicing`);
   1052      is(Cu.getGlobalForObject(t.slice(4)), iwin, "Slice results lives in the target compartment");
   1053      is(Object.getPrototypeOf(t.slice(4)), iwin[c].prototype, "Slice results proto lives in target compartment")
   1054 
   1055      var i32Array = new Int32Array(t);
   1056      // i32Array is going to be created in the buffer's target compartment,
   1057      // but usually this is unobservable, because the proto is set to
   1058      // the current compartment's prototype.
   1059      // However Xrays ignore the object's proto and claim its proto is
   1060      // the default proto for that class in the relevant compartment,
   1061      // so see through this proto hack.
   1062      todo_is(Object.getPrototypeOf(i32Array), Int32Array.prototype, "Int32Array has correct proto");
   1063      is(i32Array.length, 3, `Int32Array created from Xray ${c} has the correct length`);
   1064      is(i32Array.buffer, t, "Int32Array has the correct buffer that we passed in");
   1065 
   1066      i32Array = new iwin.Int32Array(t);
   1067      is(Object.getPrototypeOf(i32Array), iwin.Int32Array.prototype, "Xray Int32Array has correct proto");
   1068      is(i32Array.length, 3, `Xray Int32Array created from Xray ${c} has the correct length`);
   1069      is(i32Array.buffer, t, "Xray Int32Array has the correct buffer that we passed in");
   1070 
   1071      t = (new iwin.Int32Array(2)).buffer;
   1072      is(t.byteLength, 8, `Can access ${c} returned by buffer property`);
   1073    }
   1074  }
   1075 
   1076  function testMap() {
   1077    testXray('Map', new iwin.Map(), new iwin.Map());
   1078 
   1079    var t = iwin.eval(`new Map([[1, "a"], [null, "b"]])`);
   1080    is(t.size, 2, "Map size is correct");
   1081    is(t.get(1), "a", "Key 1 has the correct value");
   1082    is(t.get(null), "b", "Key null has the correct value");
   1083    is(t.has(1), true, "Has Key 1");
   1084    is(t.set(3, 5).get(3), 5, "Correctly sets key");
   1085    is(t.delete(null), true, "Key null can be deleted");
   1086 
   1087    let values = [];
   1088    t.forEach((value) => values.push(value));
   1089    is(values.toString(), "a,5", "forEach enumerates values correctly");
   1090 
   1091    t.clear();
   1092    is(t.size, 0, "Map is empty after calling clear");
   1093  }
   1094 
   1095  function testSet() {
   1096    testXray('Set', new iwin.Set(), new iwin.Set());
   1097 
   1098    var t = iwin.eval(`new Set([1, null])`);
   1099    is(t.size, 2, "Set size is correct");
   1100    is(t.has(1), true, "Contains 1");
   1101    is(t.has(null), true, "Contains null");
   1102    is(t.add(5).has(5), true, "Can add value to set");
   1103    is(t.delete(null), true, "Value null can be deleted");
   1104 
   1105    let values = [];
   1106    t.forEach(value => values.push(value));
   1107    is(values.toString(), "1,5", "forEach enumerates values correctly");
   1108 
   1109    t.clear();
   1110    is(t.size, 0, "Set is empty after calling clear");
   1111  }
   1112 
   1113  function testWeakMap() {
   1114    testXray('WeakMap', new iwin.WeakMap(), new iwin.WeakMap());
   1115 
   1116    var key1 = iwin.eval(`var key1 = {}; key1`);
   1117    var key2 = iwin.eval(`var key2 = []; key2`);
   1118    var key3 = iwin.eval(`var key3 = /a/; key3`);
   1119    var key4 = {};
   1120    var key5 = [];
   1121    var t = iwin.eval(`new WeakMap([[key1, "a"], [key2, "b"]])`);
   1122    is(t.get(key1), "a", "key1 has the correct value");
   1123    is(t.get(key2), "b", "key2 has the correct value");
   1124    is(t.has(key1), true, "Has key1");
   1125    is(t.has(key3), false, "Doesn't have key3");
   1126    is(t.has(key5), false, "Doesn't have key5");
   1127    is(t.set(key4, 5).get(key4), 5, "Correctly sets key");
   1128    is(t.get(key1), "a", "key1 has the correct value after modification");
   1129    is(t.get(key2), "b", "key2 has the correct value after modification");
   1130    is(t.delete(key1), true, "key1 can be deleted");
   1131    is(t.delete(key2), true, "key2 can be deleted");
   1132    is(t.delete(key3), false, "key3 cannot be deleted");
   1133    is(t.delete(key4), true, "key4 can be deleted");
   1134    is(t.delete(key5), false, "key5 cannot be deleted");
   1135  }
   1136 
   1137  function testWeakSet() {
   1138    testXray('WeakSet', new iwin.WeakSet(), new iwin.WeakSet());
   1139 
   1140    var key1 = iwin.eval(`var key1 = {}; key1`);
   1141    var key2 = iwin.eval(`var key2 = []; key2`);
   1142    var key3 = iwin.eval(`var key3 = /a/; key3`);
   1143    var key4 = {};
   1144    var key5 = [];
   1145    var t = iwin.eval(`new WeakSet([key1, key2])`);
   1146    is(t.has(key1), true, "Has key1");
   1147    is(t.has(key2), true, "Has key2");
   1148    is(t.has(key3), false, "Doesn't have key3");
   1149    is(t.has(key5), false, "Doesn't have key5");
   1150    is(t.add(key4, 5).has(key4), true, "Can add value to set");
   1151    is(t.delete(key1), true, "key1 can be deleted");
   1152    is(t.delete(key2), true, "key2 can be deleted");
   1153    is(t.delete(key3), false, "key3 cannot be deleted");
   1154    is(t.delete(key4), true, "key4 can be deleted");
   1155    is(t.delete(key5), false, "key5 cannot be deleted");
   1156  }
   1157 
   1158  function testProxy() {
   1159    let ProxyCtor = iwin.Proxy;
   1160    is(Object.getOwnPropertyNames(ProxyCtor).sort().toSource(),
   1161       ["length", "name"].sort().toSource(),
   1162       "Xrayed Proxy constructor should not have any properties");
   1163    is(ProxyCtor.prototype, undefined, "Proxy.prototype should not be set");
   1164    // Proxy.revocable can safely be exposed, but it is not.
   1165    // Until it is supported, check that the property is not set.
   1166    is(ProxyCtor.revocable, undefined, "Proxy.reflect is not set");
   1167  }
   1168 
   1169  function testDataView() {
   1170    testXray('DataView', new iwin.DataView(new iwin.ArrayBuffer(4)),
   1171             new iwin.DataView(new iwin.ArrayBuffer(8)));
   1172 
   1173    const versions = [() => iwin.eval(`new DataView(new ArrayBuffer(8))`),
   1174                      () => new DataView(new iwin.ArrayBuffer(8))];
   1175 
   1176    for (const constructor of versions) {
   1177      let t = constructor();
   1178      is(t.byteLength, 8, `byteLength correct for "${constructor}"`);
   1179      is(t.byteOffset, 0, `byteOffset correct for "${constructor}"`);
   1180      is(t.buffer.byteLength, 8, `buffer works for "${constructor}"`);
   1181 
   1182      const get = ["getInt8", "getUint8", "getInt16", "getUint16",
   1183                   "getInt32", "getUint32", "getFloat32", "getFloat64"];
   1184 
   1185      const set = ["setInt8", "setUint8", "setInt16", "setUint16",
   1186                   "setInt32", "setUint32", "setFloat32", "setFloat64"];
   1187 
   1188      for (const f of get) {
   1189        let x = t[f](0);
   1190        is(x, 0, `${f} is 0 for "${constructor}"`);
   1191        is(typeof x, 'number', `typeof ${f} is number for "${constructor}"`);
   1192      }
   1193 
   1194      for (const f of ["getBigInt64", "getBigUint64"]) {
   1195        let x = t[f](0);
   1196        is(x, BigInt(0), `${f} is 0n for "${constructor}"`);
   1197        is(typeof x, 'bigint', `typeof ${f} is bigint for "${constructor}"`);
   1198      }
   1199 
   1200      for (let i = 0; i < set.length; i++) {
   1201        t[set[i]](0, 13);
   1202        is(t[get[i]](0), 13, `${get[i]}(0) afer ${set[i]}(0, 13) is 13 for "${constructor}"`);
   1203      }
   1204 
   1205      for (const k of ["BigInt64", "BigUint64"]) {
   1206        t["set" + k](0, BigInt(13));
   1207        is(t["get" + k](0), BigInt(13), `get${k}(0) afer set${k}(0, 13n) is 13n for "${constructor}"`);
   1208      }
   1209    }
   1210  }
   1211 
   1212  function testNumber() {
   1213    // We don't actually support Xrays to Number yet.  This is testing
   1214    // that case.  If we add such support, we might have to start
   1215    // using a different non-Xrayed class here, if we can find one.
   1216    let xrayCtor = iwin.Number;
   1217    is(Object.getOwnPropertyNames(xrayCtor).sort().toSource(),
   1218       Object.getOwnPropertyNames(function() {}).sort().toSource(),
   1219       "We should not have any static properties on a non-Xrayable constructor");
   1220    is(xrayCtor.noSuchProperty, undefined,
   1221       "Where did our noSuchProperty property come from?");
   1222  }
   1223 
   1224  ]]>
   1225  </script>
   1226  <iframe id="ifr" onload="go();" src="http://example.org/tests/js/xpconnect/tests/mochitest/file_empty.html" />
   1227 </window>