123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943 |
- <?xml version="1.0"?>
- <?xml-stylesheet type="text/css" href="chrome://global/skin"?>
- <?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
- <!--
- https://bugzilla.mozilla.org/show_bug.cgi?id=933681
- -->
- <window title="Mozilla Bug 933681"
- xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
- <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
- <!-- test results are displayed in the html:body -->
- <body xmlns="http://www.w3.org/1999/xhtml">
- <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=933681"
- target="_blank">Mozilla Bug 933681</a>
- </body>
- <!-- test code goes here -->
- <script type="application/javascript">
- <![CDATA[
- /** Test for ES constructors on Xrayed globals. **/
- SimpleTest.waitForExplicitFinish();
- const Cc = Components.classes;
- const Ci = Components.interfaces;
- const Cu = Components.utils;
- let global = Cu.getGlobalForObject.bind(Cu);
- function checkThrows(f, rgxp, msg) {
- try {
- f();
- ok(false, "Should have thrown: " + msg);
- } catch (e) {
- ok(true, "Threw as expected: " + msg);
- ok(rgxp.test(e), "Message correct: " + e);
- }
- }
- typedArrayClasses = ['Uint8Array', 'Int8Array', 'Uint16Array', 'Int16Array',
- 'Uint32Array', 'Int32Array', 'Float32Array', 'Float64Array',
- 'Uint8ClampedArray'];
- errorObjectClasses = ['Error', 'InternalError', 'EvalError', 'RangeError', 'ReferenceError',
- 'SyntaxError', 'TypeError', 'URIError'];
- // A simpleConstructors entry can either be the name of a constructor as a
- // string, or an object containing the properties `name`, and `args`.
- // In the former case, the constructor is invoked without any args; in the
- // latter case, it is invoked with `args` as the arguments list.
- simpleConstructors = ['Object', 'Function', 'Array', 'Boolean', 'Date', 'Number',
- 'String', 'RegExp', 'ArrayBuffer', 'WeakMap', 'Map', 'Set',
- {name: 'Promise', args: [function(){}]}].concat(typedArrayClasses)
- .concat(errorObjectClasses);
- function go() {
- window.iwin = document.getElementById('ifr').contentWindow;
- // Test constructors that can be instantiated with zero arguments, or with
- // a fixed set of arguments provided using `...rest`.
- for (var c of simpleConstructors) {
- var args = [];
- if (typeof c === 'object') {
- args = c.args;
- c = c.name;
- }
- ok(iwin[c], "Constructors appear: " + c);
- is(iwin[c], Cu.unwaiveXrays(iwin.wrappedJSObject[c]),
- "we end up with the appropriate constructor: " + c);
- is(Cu.unwaiveXrays(Cu.waiveXrays(new iwin[c](...args)).constructor), iwin[c],
- "constructor property is set up right: " + c);
- let expectedProto = /Opaque/.test(new iwin[c](...args)) ? iwin['Object'].prototype
- : iwin[c].prototype;
- is(Object.getPrototypeOf(new iwin[c](...args)), expectedProto,
- "prototype is correct: " + c);
- is(global(new iwin[c](...args)), iwin, "Got the right global: " + c);
- }
- // Test Object in more detail.
- var num = new iwin.Object(4);
- is(Cu.waiveXrays(num).valueOf(), 4, "primitive object construction works");
- is(global(num), iwin, "correct global for num");
- var obj = new iwin.Object();
- obj.foo = 2;
- var withProto = Cu.unwaiveXrays(Cu.waiveXrays(iwin).Object.create(obj));
- is(global(withProto), iwin, "correct global for withProto");
- is(Cu.waiveXrays(withProto).foo, 2, "Inherits properly");
- // Test Function.
- var primitiveFun = new iwin.Function('return 2');
- is(global(primitiveFun), iwin, "function construction works");
- is(primitiveFun(), 2, "basic function works");
- var doSetFoo = new iwin.Function('arg', 'arg.foo = 2;');
- is(global(doSetFoo), iwin, "function with args works");
- try {
- doSetFoo(new Object());
- ok(false, "should have thrown while setting property on object");
- } catch (e) {
- ok(!!/denied/.test(e), "Threw correctly: " + e);
- }
- var factoryFun = new iwin.Function('return {foo: 32}');
- is(global(factoryFun), iwin, "proper global for factoryFun");
- is(factoryFun().foo, 32, "factoryFun invokable");
- is(global(factoryFun()), iwin, "minted objects live in the content scope");
- testXray('Function', factoryFun, new iwin.Function(), ['length', 'name']);
- var echoThis = new iwin.Function('return this;');
- echoThis.wrappedJSObject.bind = 42;
- var boundEchoThis = echoThis.bind(document);
- is(boundEchoThis(), document, "bind() works correctly over Xrays");
- is(global(boundEchoThis), window, "bound functions live in the caller's scope");
- ok(/return this/.test(echoThis.toSource()), 'toSource works: ' + echoThis.toSource());
- ok(/return this/.test(echoThis.toString()), 'toString works: ' + echoThis.toString());
- is(iwin.Function.prototype, Object.getPrototypeOf(echoThis), "Function.prototype works for standard classes");
- is(echoThis.prototype, undefined, "Function.prototype not visible for non standard constructors");
- iwin.eval('var foopyFunction = function namedFoopyFunction(a, b, c) {}');
- var foopyFunction = Cu.unwaiveXrays(Cu.waiveXrays(iwin).foopyFunction);
- ok(Cu.isXrayWrapper(foopyFunction), "Should be Xrays");
- is(foopyFunction.name, "namedFoopyFunction", ".name works over Xrays");
- is(foopyFunction.length, 3, ".length works over Xrays");
- ok(Object.getOwnPropertyNames(foopyFunction).indexOf('length') >= 0, "Should list length");
- ok(Object.getOwnPropertyNames(foopyFunction).indexOf('name') >= 0, "Should list name");
- ok(Object.getOwnPropertyNames(foopyFunction).indexOf('prototype') == -1, "Should not list prototype");
- ok(Object.getOwnPropertyNames(iwin.Array).indexOf('prototype') >= 0, "Should list prototype for standard constructor");
- // Test proxies.
- var targetObject = new iwin.Object();
- targetObject.foo = 9;
- var forwardingProxy = new iwin.Proxy(targetObject, new iwin.Object());
- is(global(forwardingProxy), iwin, "proxy global correct");
- is(Cu.waiveXrays(forwardingProxy).foo, 9, "forwards correctly");
- // Test eval.
- var toEval = "({a: 2, b: {foo: 'bar'}, f: function() { return window; }})";
- is(global(iwin.eval(toEval)), iwin, "eval creates objects in the correct global");
- is(iwin.eval(toEval).b.foo, 'bar', "eval-ed object looks right");
- is(Cu.waiveXrays(iwin.eval(toEval)).f(), Cu.waiveXrays(iwin), "evaled function works right");
- testDate();
- testObject();
- testArray();
- testTypedArrays();
- testErrorObjects();
- testRegExp();
- testPromise();
- testArrayBuffer();
- // We could also test DataView and Iterator here for completeness, but it's
- // more trouble than it's worth.
- SimpleTest.finish();
- }
- // Maintain a static list of the properties that are available on each standard
- // prototype, so that we make sure to audit any new ones to make sure they're
- // Xray-safe.
- //
- // DO NOT CHANGE WTIHOUT REVIEW FROM AN XPCONNECT PEER.
- var version = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo).version;
- var isNightlyBuild = version.endsWith("a1");
- var isReleaseOrBeta = !version.includes("a");
- var gPrototypeProperties = {};
- var gConstructorProperties = {};
- function constructorProps(arr) {
- // Some props live on all constructors
- return arr.concat(["prototype", "length", "name"]);
- }
- gPrototypeProperties['Date'] =
- ["getTime", "getTimezoneOffset", "getYear", "getFullYear", "getUTCFullYear",
- "getMonth", "getUTCMonth", "getDate", "getUTCDate", "getDay", "getUTCDay",
- "getHours", "getUTCHours", "getMinutes", "getUTCMinutes", "getSeconds",
- "getUTCSeconds", "getMilliseconds", "getUTCMilliseconds", "setTime",
- "setYear", "setFullYear", "setUTCFullYear", "setMonth", "setUTCMonth",
- "setDate", "setUTCDate", "setHours", "setUTCHours", "setMinutes",
- "setUTCMinutes", "setSeconds", "setUTCSeconds", "setMilliseconds",
- "setUTCMilliseconds", "toUTCString", "toLocaleFormat", "toLocaleString",
- "toLocaleDateString", "toLocaleTimeString", "toDateString", "toTimeString",
- "toISOString", "toJSON", "toSource", "toString", "valueOf", "constructor",
- "toGMTString", Symbol.toPrimitive];
- gConstructorProperties['Date'] = constructorProps(["UTC", "parse", "now"]);
- gPrototypeProperties['Object'] =
- ["constructor", "toSource", "toString", "toLocaleString", "valueOf",
- "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable",
- "__defineGetter__", "__defineSetter__", "__lookupGetter__", "__lookupSetter__",
- "__proto__"];
- gConstructorProperties['Object'] =
- constructorProps(["setPrototypeOf", "getOwnPropertyDescriptor", "getOwnPropertyDescriptors",
- "keys", "is", "defineProperty", "defineProperties", "create",
- "getOwnPropertyNames", "getOwnPropertySymbols",
- "preventExtensions", "freeze", "fromEntries", "isFrozen", "seal",
- "isSealed", "assign", "getPrototypeOf", "values",
- "entries", "isExtensible"]);
- gPrototypeProperties['Array'] =
- ["length", "toSource", "toString", "toLocaleString", "join", "reverse", "sort", "push",
- "pop", "shift", "unshift", "splice", "concat", "slice", "lastIndexOf", "indexOf",
- "includes", "forEach", "map", "reduce", "reduceRight", "filter", "some", "every", "find",
- "findIndex", "copyWithin", "fill", Symbol.iterator, Symbol.unscopables, "entries", "keys",
- "values", "constructor", "flat", "flatMap"];
- if (isNightlyBuild) {
- // ...nothing now
- }
- gConstructorProperties['Array'] =
- constructorProps(["join", "reverse", "sort", "push", "pop", "shift",
- "unshift", "splice", "concat", "slice", "isArray",
- "lastIndexOf", "indexOf", "forEach", "map", "filter",
- "every", "some", "reduce", "reduceRight", "from", "of",
- Symbol.species]);
- for (var c of typedArrayClasses) {
- gPrototypeProperties[c] = ["constructor", "BYTES_PER_ELEMENT"];
- gConstructorProperties[c] = constructorProps(["BYTES_PER_ELEMENT"]);
- }
- gPrototypeProperties['TypedArray'] =
- ["length", "buffer", "byteLength", "byteOffset", Symbol.iterator, "subarray",
- "set", "copyWithin", "find", "findIndex", "forEach","indexOf", "lastIndexOf", "includes",
- "reverse", "join", "every", "some", "reduce", "reduceRight", "entries", "keys", "values",
- "slice", "map", "filter"];
- // There is no TypedArray constructor, looks like.
- is(window.TypedArray, undefined, "If this ever changes, add to this test!");
- for (var c of errorObjectClasses) {
- gPrototypeProperties[c] = ["constructor", "name", "message", "stack"];
- gConstructorProperties[c] = constructorProps([]);
- }
- // toString and toSource only live on the parent proto (Error.prototype).
- gPrototypeProperties['Error'].push('toString');
- gPrototypeProperties['Error'].push('toSource');
- gPrototypeProperties['Function'] =
- ["constructor", "toSource", "toString", "apply", "call", "bind",
- "isGenerator", "length", "name", "arguments", "caller", Symbol.hasInstance];
- gConstructorProperties['Function'] = constructorProps([])
- gPrototypeProperties['RegExp'] =
- ["constructor", "toSource", "toString", "compile", "exec", "test",
- Symbol.match, Symbol.replace, Symbol.search, Symbol.split,
- "flags", "global", "ignoreCase", "multiline", "source", "sticky", "unicode"];
- gConstructorProperties['RegExp'] =
- constructorProps(["input", "lastMatch", "lastParen",
- "leftContext", "rightContext", "$1", "$2", "$3", "$4",
- "$5", "$6", "$7", "$8", "$9", "$_", "$&", "$+",
- "$`", "$'", Symbol.species])
- gPrototypeProperties['Promise'] =
- ["constructor", "catch", "then", "finally", Symbol.toStringTag];
- gConstructorProperties['Promise'] =
- constructorProps(["resolve", "reject", "all", "race", Symbol.species]);
- gPrototypeProperties['ArrayBuffer'] =
- ["constructor", "byteLength", "slice", Symbol.toStringTag];
- gConstructorProperties['ArrayBuffer'] =
- constructorProps(["isView", "slice", Symbol.species]);
- if (!isReleaseOrBeta) {
- gPrototypeProperties['SharedArrayBuffer'] = ["constructor", "slice", "byteLength", Symbol.toStringTag];
- gConstructorProperties['SharedArrayBuffer'] = constructorProps([Symbol.species]);
- } else {
- is(typeof SharedArrayBuffer, "undefined", "Enable tests!");
- }
- // Sort an array that may contain symbols as well as strings.
- function sortProperties(arr) {
- function sortKey(prop) {
- return typeof prop + ":" + prop.toString();
- }
- arr.sort((a, b) => sortKey(a) < sortKey(b) ? -1 : +1);
- }
- // Sort all the lists so we don't need to mutate them later (or copy them
- // again to sort them).
- for (var c of Object.keys(gPrototypeProperties))
- sortProperties(gPrototypeProperties[c]);
- for (var c of Object.keys(gConstructorProperties))
- sortProperties(gConstructorProperties[c]);
- function filterOut(array, props) {
- return array.filter(p => props.indexOf(p) == -1);
- }
- function isTypedArrayClass(classname) {
- return typedArrayClasses.indexOf(classname) >= 0;
- }
- function propertyIsGetter(obj, name, classname) {
- return !!Object.getOwnPropertyDescriptor(obj, name).get;
- }
- function testProtoCallables(protoCallables, xray, xrayProto, localProto) {
- for (let name of protoCallables) {
- info("Running tests for property: " + name);
- // Test both methods and getter properties.
- function lookupCallable(obj) {
- let desc = null;
- do {
- desc = Object.getOwnPropertyDescriptor(obj, name);
- obj = Object.getPrototypeOf(obj);
- } while (!desc);
- return desc.get || desc.value;
- };
- ok(xrayProto.hasOwnProperty(name), "proto should have the property as own");
- ok(!xray.hasOwnProperty(name), "instance should not have the property as own");
- let method = lookupCallable(xrayProto);
- is(typeof method, 'function', "Methods from Xrays are functions");
- is(global(method), window, "Methods from Xrays are local");
- ok(method instanceof Function, "instanceof works on methods from Xrays");
- is(lookupCallable(xrayProto), method, "Holder caching works properly");
- is(lookupCallable(xray), method, "Proto props resolve on the instance");
- let local = lookupCallable(localProto);
- is(method.length, local.length, "Function.length identical");
- if (method.length == 0) {
- is(method.call(xray) + "", local.call(xray) + "",
- "Xray and local method results stringify identically");
- // If invoking this method returns something non-Xrayable, the
- // stringification is going to return [object Opaque].
- ok(!/Opaque/.test(method.call(xray)), "Method result is xrayable");
- is(method.call(xray) + "",
- lookupCallable(xray.wrappedJSObject).call(xray.wrappedJSObject) + "",
- "Xray and waived method results stringify identically");
- }
- }
- }
- function testCtorCallables(ctorCallables, xrayCtor, localCtor) {
- for (let name of ctorCallables) {
- // Don't try to test Function.prototype, since that is in fact a callable
- // but doesn't really do the things we expect callables to do here
- // (e.g. it's in the wrong global, since it gets Xrayed itself).
- if (name == "prototype" && localCtor.name == "Function") {
- continue;
- }
- info(`Running tests for property: ${localCtor.name}.${name}`);
- // Test both methods and getter properties.
- function lookupCallable(obj) {
- let desc = null;
- do {
- desc = Object.getOwnPropertyDescriptor(obj, name);
- obj = Object.getPrototypeOf(obj);
- } while (!desc);
- return desc.get || desc.value;
- };
- ok(xrayCtor.hasOwnProperty(name), "ctor should have the property as own");
- let method = lookupCallable(xrayCtor);
- is(typeof method, 'function', "Methods from ctor Xrays are functions");
- is(global(method), window, "Methods from ctor Xrays are local");
- ok(method instanceof Function,
- "instanceof works on methods from ctor Xrays");
- is(lookupCallable(xrayCtor), method,
- "Holder caching works properly on ctors");
- let local = lookupCallable(localCtor);
- is(method.length, local.length,
- "Function.length identical for method from ctor");
- // Don't try to do the return-value check on Date.now(), since there is
- // absolutely no reason it should return the same value each time.
- //
- // Also don't try to do the return-value check on Regexp.lastMatch and
- // Regexp["$&"] (which are aliases), because they get state off the global
- // they live in, as far as I can tell, so testing them over Xrays will be
- // wrong: on the Xray they will actaully get the lastMatch of _our_
- // global, not the Xrayed one.
- if (method.length == 0 &&
- !(localCtor.name == "Date" && name == "now") &&
- !(localCtor.name == "RegExp" && (name == "lastMatch" || name == "$&"))) {
- is(method.call(xrayCtor) + "", local.call(xrayCtor) + "",
- "Xray and local method results stringify identically on constructors");
- is(method.call(xrayCtor) + "",
- lookupCallable(xrayCtor.wrappedJSObject).call(xrayCtor.wrappedJSObject) + "",
- "Xray and waived method results stringify identically");
- }
- }
- }
- function testXray(classname, xray, xray2, propsToSkip, ctorPropsToSkip = []) {
- propsToSkip = propsToSkip || [];
- let xrayProto = Object.getPrototypeOf(xray);
- let localProto = window[classname].prototype;
- let desiredProtoProps = Object.getOwnPropertyNames(localProto).sort();
- is(desiredProtoProps.toSource(),
- gPrototypeProperties[classname].filter(id => typeof id === "string").toSource(),
- "A property on the " + classname +
- " prototype has changed! You need a security audit from an XPConnect peer");
- is(Object.getOwnPropertySymbols(localProto).map(uneval).sort().toSource(),
- gPrototypeProperties[classname].filter(id => typeof id !== "string").map(uneval).sort().toSource(),
- "A symbol-keyed property on the " + classname +
- " prototype has been changed! You need a security audit from an XPConnect peer");
- let protoProps = filterOut(desiredProtoProps, propsToSkip);
- let protoCallables = protoProps.filter(name => propertyIsGetter(localProto, name, classname) ||
- typeof localProto[name] == 'function' &&
- name != 'constructor');
- ok(protoCallables.length > 0, "Need something to test");
- is(xrayProto, iwin[classname].prototype, "Xray proto is correct");
- is(xrayProto, xray.__proto__, "Proto accessors agree");
- var protoProto = classname == "Object" ? null : iwin.Object.prototype;
- is(Object.getPrototypeOf(xrayProto), protoProto, "proto proto is correct");
- testProtoCallables(protoCallables, xray, xrayProto, localProto);
- is(Object.getOwnPropertyNames(xrayProto).sort().toSource(),
- protoProps.toSource(), "getOwnPropertyNames works");
- is(Object.getOwnPropertySymbols(xrayProto).map(uneval).sort().toSource(),
- gPrototypeProperties[classname].filter(id => typeof id !== "string" && !propsToSkip.includes(id))
- .map(uneval).sort().toSource(),
- "getOwnPropertySymbols works");
- is(xrayProto.constructor, iwin[classname], "constructor property works");
- xrayProto.expando = 42;
- is(xray.expando, 42, "Xrayed instances see proto expandos");
- is(xray2.expando, 42, "Xrayed instances see proto expandos");
- // Now test constructors
- localCtor = window[classname];
- xrayCtor = xrayProto.constructor;
- // We already checked that this is the same as iwin[classname]
- let desiredCtorProps =
- Object.getOwnPropertyNames(localCtor).sort();
- is(desiredCtorProps.toSource(),
- gConstructorProperties[classname].filter(id => typeof id === "string").toSource(),
- "A property on the " + classname +
- " constructor has changed! You need a security audit from an XPConnect peer");
- let desiredCtorSymbols =
- Object.getOwnPropertySymbols(localCtor).map(uneval).sort()
- is(desiredCtorSymbols.toSource(),
- gConstructorProperties[classname].filter(id => typeof id !== "string").map(uneval).sort().toSource(),
- "A symbol-keyed property on the " + classname +
- " constructor has been changed! You need a security audit from an XPConnect peer");
- let ctorProps = filterOut(desiredCtorProps, ctorPropsToSkip);
- let ctorSymbols = filterOut(desiredCtorSymbols, ctorPropsToSkip.map(uneval));
- let ctorCallables = ctorProps.filter(name => propertyIsGetter(localCtor, name, classname) ||
- typeof localCtor[name] == 'function');
- testCtorCallables(ctorCallables, xrayCtor, localCtor);
- is(Object.getOwnPropertyNames(xrayCtor).sort().toSource(),
- ctorProps.toSource(), "getOwnPropertyNames works on Xrayed ctors");
- is(Object.getOwnPropertySymbols(xrayCtor).map(uneval).sort().toSource(),
- ctorSymbols.toSource(), "getOwnPropertySymbols works on Xrayed ctors");
- }
- // We will need arraysEqual and testArrayIterators both in this global scope
- // and in sandboxes, so define them as strings up front.
- var arraysEqualSource = `function arraysEqual(arr1, arr2, reason) {
- is(arr1.length, arr2.length, \`\${reason}; lengths should be equal\`)
- for (var i = 0; i < arr1.length; ++i) {
- if (Array.isArray(arr2[i])) {
- arraysEqual(arr1[i], arr2[i], \`\${reason}; item at index \${i}\`);
- } else {
- is(arr1[i], arr2[i], \`\${reason}; item at index \${i} should be equal\`);
- }
- }
- }`;
- eval(arraysEqualSource);
- var testArrayIteratorsSource = `
- function testArrayIterators(arrayLike, equivalentArray, reason) {
- arraysEqual([...arrayLike], equivalentArray, \`\${reason}; spread operator\`);
- arraysEqual([...arrayLike.entries()], [...equivalentArray.entries()],
- \`\${reason}; entries\`);
- arraysEqual([...arrayLike.keys()], [...equivalentArray.keys()],
- \`\${reason}; keys\`);
- if (arrayLike.values) {
- arraysEqual([...arrayLike.values()], equivalentArray,
- \`\${reason}; values\`);
- }
- var forEachCopy = [];
- arrayLike.forEach(function(arg) { forEachCopy.push(arg); });
- arraysEqual(forEachCopy, equivalentArray, \`\${reason}; forEach copy\`);
- var everyCopy = [];
- arrayLike.every(function(arg) { everyCopy.push(arg); return true; });
- arraysEqual(everyCopy, equivalentArray, \`\${reason}; every() copy\`);
- var filterCopy = [];
- var filterResult = arrayLike.filter(function(arg) {
- filterCopy.push(arg);
- return true;
- });
- arraysEqual(filterCopy, equivalentArray, \`\${reason}; filter copy\`);
- arraysEqual([...filterResult], equivalentArray, \`\${reason}; filter result\`);
- var findCopy = [];
- arrayLike.find(function(arg) { findCopy.push(arg); return false; });
- arraysEqual(findCopy, equivalentArray, \`\${reason}; find() copy\`);
- var findIndexCopy = [];
- arrayLike.findIndex(function(arg) { findIndexCopy.push(arg); return false; });
- arraysEqual(findIndexCopy, equivalentArray, \`\${reason}; findIndex() copy\`);
- var mapCopy = [];
- var mapResult = arrayLike.map(function(arg) { mapCopy.push(arg); return arg});
- arraysEqual(mapCopy, equivalentArray, \`\${reason}; map() copy\`);
- arraysEqual([...mapResult], equivalentArray, \`\${reason}; map() result\`);
- var reduceCopy = [];
- arrayLike.reduce(function(_, arg) { reduceCopy.push(arg); }, 0);
- arraysEqual(reduceCopy, equivalentArray, \`\${reason}; reduce() copy\`);
- var reduceRightCopy = [];
- arrayLike.reduceRight(function(_, arg) { reduceRightCopy.unshift(arg); }, 0);
- arraysEqual(reduceRightCopy, equivalentArray, \`\${reason}; reduceRight() copy\`);
- var someCopy = [];
- arrayLike.some(function(arg) { someCopy.push(arg); return false; });
- arraysEqual(someCopy, equivalentArray, \`\${reason}; some() copy\`);
- }`;
- eval(testArrayIteratorsSource);
- function testDate() {
- // toGMTString is handled oddly in the engine. We don't bother to support
- // it over Xrays.
- let propsToSkip = ['toGMTString'];
- testXray('Date', new iwin.Date(), new iwin.Date(), propsToSkip);
- // Test the self-hosted toLocaleString.
- var d = new iwin.Date();
- isnot(d.toLocaleString, Cu.unwaiveXrays(d.wrappedJSObject.toLocaleString), "Different function identities");
- is(Cu.getGlobalForObject(d.toLocaleString), window, "Xray global is correct");
- is(Cu.getGlobalForObject(d.wrappedJSObject.toLocaleString), iwin, "Underlying global is correct");
- is(d.toLocaleString('de-DE'), d.wrappedJSObject.toLocaleString('de-DE'), "Results match");
- }
- var uniqueSymbol;
- function testObject() {
- testXray('Object', Cu.unwaiveXrays(Cu.waiveXrays(iwin).Object.create(new iwin.Object())),
- new iwin.Object(), []);
- // Construct an object full of tricky things.
- let symbolProps = '';
- uniqueSymbol = iwin.eval('var uniqueSymbol = Symbol("uniqueSymbol"); uniqueSymbol');
- symbolProps = `, [uniqueSymbol]: 43,
- [Symbol.for("registrySymbolProp")]: 44`;
- var trickyObject =
- iwin.eval(`(function() {
- var o = new Object({
- primitiveProp: 42, objectProp: { foo: 2 },
- xoProp: top, hasOwnProperty: 10,
- get getterProp() { return 2; },
- set setterProp(x) { },
- get getterSetterProp() { return 3; },
- set getterSetterProp(x) { },
- callableProp: function() { },
- nonXrayableProp: new WeakMap()
- ${symbolProps}
- });
- Object.defineProperty(o, "nonConfigurableGetterSetterProp",
- { get: function() { return 5; }, set: function() {} });
- return o;
- })()`);
- testTrickyObject(trickyObject);
- }
- function testArray() {
- // The |length| property is generally very weird, especially with respect
- // to its behavior on the prototype. Array.prototype is actually an Array
- // instance, and therefore has a vestigial .length. But we don't want to
- // show that over Xrays, and generally want .length to just appear as an
- // |own| data property. So we add it to the ignore list here, and check it
- // separately.
- //
- // |Symbol.unscopables| should in principle be exposed, but it is
- // inconvenient (as it's a data property, unsupported by ClassSpec) and
- // low value.
- let propsToSkip = ['length', Symbol.unscopables];
- testXray('Array', new iwin.Array(20), new iwin.Array(), propsToSkip);
- let symbolProps = '';
- uniqueSymbol = iwin.eval('var uniqueSymbol = Symbol("uniqueSymbol"); uniqueSymbol');
- symbolProps = `trickyArray[uniqueSymbol] = 43;
- trickyArray[Symbol.for("registrySymbolProp")] = 44;`;
- var trickyArray =
- iwin.eval(`var trickyArray = [];
- trickyArray.primitiveProp = 42;
- trickyArray.objectProp = { foo: 2 };
- trickyArray.xoProp = top;
- trickyArray.hasOwnProperty = 10;
- Object.defineProperty(trickyArray, 'getterProp', { get: function() { return 2; }});
- Object.defineProperty(trickyArray, 'setterProp', { set: function(x) {}});
- Object.defineProperty(trickyArray, 'getterSetterProp', { get: function() { return 3; }, set: function(x) {}, configurable: true});
- Object.defineProperty(trickyArray, 'nonConfigurableGetterSetterProp', { get: function() { return 5; }, set: function(x) {}});
- trickyArray.callableProp = function() {};
- trickyArray.nonXrayableProp = new WeakMap();
- ${symbolProps}
- trickyArray;`);
- // Test indexed access.
- trickyArray.wrappedJSObject[9] = "some indexed property";
- is(trickyArray[9], "some indexed property", "indexed properties work correctly over Xrays");
- is(trickyArray.length, 10, "Length works correctly over Xrays");
- checkThrows(function() { "use strict"; delete trickyArray.length; }, /config/, "Can't delete non-configurable 'length' property");
- delete trickyArray[9];
- is(trickyArray[9], undefined, "Delete works correctly over Xrays");
- is(trickyArray.wrappedJSObject[9], undefined, "Delete works correctly over Xrays (viewed via waiver)");
- is(trickyArray.length, 10, "length doesn't change");
- trickyArray[11] = "some other indexed property";
- is(trickyArray.length, 12, "length now changes");
- is(trickyArray.wrappedJSObject[11], "some other indexed property");
- trickyArray.length = 0;
- is(trickyArray.length, 0, "Setting length works over Xray");
- is(trickyArray[11], undefined, "Setting length truncates over Xray");
- Object.defineProperty(trickyArray, 'length', { configurable: false, enumerable: false, writable: false, value: 0 });
- trickyArray[1] = "hi";
- is(trickyArray.length, 0, "Length remains non-writable");
- is(trickyArray[1], undefined, "Frozen length forbids new properties");
- is(trickyArray instanceof iwin.Array, true, "instanceof should work across xray wrappers.");
- testTrickyObject(trickyArray);
- testArrayIterators(new iwin.Array(1, 1, 2, 3, 5), [1, 1, 2, 3, 5]);
- }
- // Parts of this function are kind of specific to testing Object, but we factor
- // it out so that we can re-use the trickyObject stuff on Arrays.
- function testTrickyObject(trickyObject) {
- // Make sure it looks right under the hood.
- is(trickyObject.wrappedJSObject.getterProp, 2, "Underlying object has getter");
- is(Cu.unwaiveXrays(trickyObject.wrappedJSObject.xoProp), top, "Underlying object has xo property");
- // Test getOwnPropertyNames.
- var expectedNames = ['objectProp', 'primitiveProp'];
- if (trickyObject instanceof iwin.Array)
- expectedNames.push('length');
- is(Object.getOwnPropertyNames(trickyObject).sort().toSource(),
- expectedNames.sort().toSource(), "getOwnPropertyNames should be filtered correctly");
- var expectedSymbols = [Symbol.for("registrySymbolProp"), uniqueSymbol];
- is(Object.getOwnPropertySymbols(trickyObject).map(uneval).sort().toSource(),
- expectedSymbols.map(uneval).sort().toSource(),
- "getOwnPropertySymbols should be filtered correctly");
- // Test that cloning uses the Xray view.
- var cloned = Cu.cloneInto(trickyObject, this);
- is(Object.getOwnPropertyNames(cloned).sort().toSource(),
- expectedNames.sort().toSource(), "structured clone should use the Xray view");
- is(Object.getOwnPropertySymbols(cloned).map(uneval).sort().toSource(),
- "[]", "structured cloning doesn't clone symbol-keyed properties yet");
- // Test iteration and in-place modification. Beware of 'expando', which is the property
- // we placed on the xray proto.
- var propCount = 0;
- for (let prop in trickyObject) {
- if (prop == 'primitiveProp')
- trickyObject[prop] = trickyObject[prop] - 10;
- if (prop != 'expando')
- trickyObject[prop] = trickyObject[prop];
- ++propCount;
- }
- is(propCount, 3, "Should iterate the correct number of times");
- // Test Object.keys.
- is(Object.keys(trickyObject).sort().toSource(),
- ['objectProp', 'primitiveProp'].toSource(), "Object.keys should be filtered correctly");
- // Test getOwnPropertyDescriptor.
- is(trickyObject.primitiveProp, 32, "primitive prop works");
- is(trickyObject.objectProp.foo, 2, "object prop works");
- is(typeof trickyObject.callableProp, 'undefined', "filtering works correctly");
- is(Object.getOwnPropertyDescriptor(trickyObject, 'primitiveProp').value, 32, "getOwnPropertyDescriptor works");
- is(Object.getOwnPropertyDescriptor(trickyObject, 'xoProp'), undefined, "filtering works with getOwnPropertyDescriptor");
- // Test defineProperty.
- trickyObject.primitiveSetByXray = 'fourty two';
- is(trickyObject.primitiveSetByXray, 'fourty two', "Can set primitive correctly over Xray (ready via Xray)");
- is(trickyObject.wrappedJSObject.primitiveSetByXray, 'fourty two', "Can set primitive correctly over Xray (ready via Waiver)");
- var newContentObject = iwin.eval('new Object({prop: 99, get getterProp() { return 2; }})');
- trickyObject.objectSetByXray = newContentObject;
- is(trickyObject.objectSetByXray.prop, 99, "Can set object correctly over Xray (ready via Xray)");
- is(trickyObject.wrappedJSObject.objectSetByXray.prop, 99, "Can set object correctly over Xray (ready via Waiver)");
- checkThrows(function() { trickyObject.rejectedProp = {foo: 33}}, /cross-origin object/,
- "Should reject privileged object property definition");
- // Test JSON.stringify.
- var jsonStr = JSON.stringify(newContentObject);
- ok(/prop/.test(jsonStr), "JSON stringification should work: " + jsonStr);
- // Test deletion.
- delete newContentObject.prop;
- ok(!newContentObject.hasOwnProperty('prop'), "Deletion should work");
- ok(!newContentObject.wrappedJSObject.hasOwnProperty('prop'), "Deletion should forward");
- delete newContentObject.getterProp;
- ok(newContentObject.wrappedJSObject.hasOwnProperty('getterProp'), "Deletion be no-op for filtered property");
- // We should be able to overwrite an existing accessor prop and convert it
- // to a value prop.
- is(trickyObject.wrappedJSObject.getterSetterProp, 3, "Underlying object has getter");
- is(trickyObject.getterSetterProp, undefined, "Filtering properly over Xray");
- trickyObject.getterSetterProp = 'redefined';
- is(trickyObject.getterSetterProp, 'redefined', "Redefinition works");
- is(trickyObject.wrappedJSObject.getterSetterProp, 'redefined', "Redefinition forwards");
- // We should NOT be able to overwrite an existing non-configurable accessor
- // prop, though.
- is(trickyObject.wrappedJSObject.nonConfigurableGetterSetterProp, 5,
- "Underlying object has getter");
- is(trickyObject.nonConfigurableGetterSetterProp, undefined,
- "Filtering properly over Xray here too");
- is((trickyObject.nonConfigurableGetterSetterProp = 'redefined'), 'redefined',
- "Assigning to non-configurable prop should fail silently in non-strict mode");
- checkThrows(function() {
- "use strict";
- trickyObject.nonConfigurableGetterSetterProp = 'redefined';
- }, /config/, "Should throw when redefining non-configurable prop in strict mode");
- is(trickyObject.nonConfigurableGetterSetterProp, undefined,
- "Redefinition should have failed");
- is(trickyObject.wrappedJSObject.nonConfigurableGetterSetterProp, 5,
- "Redefinition really should have failed");
- checkThrows(function() { trickyObject.hasOwnProperty = 33; }, /shadow/,
- "Should reject shadowing of pre-existing inherited properties over Xrays");
- checkThrows(function() { Object.defineProperty(trickyObject, 'rejectedProp', { get: function() {}}); },
- /accessor property/, "Should reject accessor property definition");
- }
- function testTypedArrays() {
- // We don't invoke testXray with %TypedArray%, because that function isn't
- // set up to deal with "anonymous" dependent classes (that is, classes not
- // visible as a global property, which %TypedArray% is not), and fixing it
- // up is more trouble than it's worth.
- var typedArrayProto = Object.getPrototypeOf(Int8Array.prototype);
- var desiredInheritedProps = Object.getOwnPropertyNames(typedArrayProto).sort();
- var inheritedProps =
- filterOut(desiredInheritedProps, ["BYTES_PER_ELEMENT", "constructor"]);
- var inheritedCallables =
- inheritedProps.filter(name => (propertyIsGetter(typedArrayProto, name) ||
- typeof typedArrayProto[name] === "function") &&
- name !== "constructor");
- for (var c of typedArrayClasses) {
- var t = new iwin[c](10);
- checkThrows(function() { t[2]; }, /performant/, "direct property-wise reading of typed arrays forbidden over Xrays");
- checkThrows(function() { t[2] = 3; }, /performant/, "direct property-wise writing of typed arrays forbidden over Xrays");
- var wesb = new Cu.Sandbox([iwin], {isWebExtensionContentScript: true});
- wesb.t = t;
- wesb.eval('t[2] = 3');
- is(wesb.eval('t.wrappedJSObject[2]'), 3, "direct property-wise writing of typed arrays allowed for WebExtension content scripts");
- is(wesb.eval('t[2]'), 3, "direct property-wise reading and writing of typed arrays allowed for WebExtensions content scripts");
- t.wrappedJSObject[2] = 3;
- is(t.wrappedJSObject[2], 3, "accessing elements over waivers works");
- t.wrappedJSObject.expando = 'hi';
- is(t.wrappedJSObject.expando, 'hi', "access expandos over waivers works");
- is(Cu.cloneInto(t, window)[2], 3, "cloneInto works");
- is(Cu.cloneInto(t, window).expando, undefined, "cloneInto does not copy expandos");
- is(Object.getOwnPropertyNames(t).sort().toSource(),
- '["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]',
- "Only indexed properties visible over Xrays");
- Object.defineProperty(t.wrappedJSObject, 'length', {value: 42});
- is(t.wrappedJSObject.length, 42, "Set tricky expando")
- is(t.length, 10, "Length accessor works over Xrays")
- is(t.byteLength, t.length * window[c].prototype.BYTES_PER_ELEMENT, "byteLength accessor works over Xrays")
- // Can create TypedArray from content ArrayBuffer
- var buffer = new iwin.ArrayBuffer(8);
- eval(`new ${c}(buffer);`);
- var xray = new iwin[c](0);
- var xrayTypedArrayProto = Object.getPrototypeOf(Object.getPrototypeOf(xray));
- testProtoCallables(inheritedCallables, new iwin[c](0), xrayTypedArrayProto, typedArrayProto);
- // When testing iterators, make sure to do so from inside our web
- // extension sandbox, since from chrome we can't poke their indices. Note
- // that we have to actually recreate our functions that touch typed array
- // indices inside the sandbox, not just export them, because otherwise
- // they'll just run with our principal anyway.
- //
- // But we do want to export is(), since we want ours called.
- wesb.eval(arraysEqualSource);
- wesb.eval(testArrayIteratorsSource);
- Cu.exportFunction(is, wesb,
- { defineAs: "is" });
- wesb.eval('testArrayIterators(t, [0, 0, 3, 0, 0, 0, 0, 0, 0, 0])');
- }
- }
- function testErrorObjects() {
- // We only invoke testXray with Error, because that function isn't set up
- // to deal with dependent classes and fixing it up is more trouble than
- // it's worth.
- testXray('Error', new iwin.Error('some error message'), new iwin.Error());
- // Make sure that the dependent classes have their prototypes set up correctly.
- for (let c of errorObjectClasses.filter(x => x != "Error")) {
- var e = new iwin[c]('some message');
- is(Object.getPrototypeOf(e).name, c, "Prototype has correct name");
- is(Object.getPrototypeOf(Object.getPrototypeOf(e)), iwin.Error.prototype, "Dependent prototype set up correctly");
- is(e.name, c, "Exception name inherited correctly");
- function testProperty(name, criterion, goodReplacement, faultyReplacement) {
- ok(criterion(e[name]), name + " property is correct: " + e[name]);
- e.wrappedJSObject[name] = goodReplacement;
- is(e[name], goodReplacement, name + " property ok after replacement: " + goodReplacement);
- e.wrappedJSObject[name] = faultyReplacement;
- is(e[name], name == 'message' ? "" : undefined, name + " property skipped after suspicious replacement");
- }
- testProperty('message', x => x == 'some message', 'some other message', 42);
- testProperty('fileName', x => x == '', 'otherFilename.html', new iwin.Object());
- testProperty('columnNumber', x => x == 1, 99, 99.5);
- testProperty('lineNumber', x => x == 0, 50, 'foo');
- // Note - an Exception newed via Xrays is going to have an empty stack given the
- // current semantics and implementation. This tests the current behavior, but that
- // may change in bug 1036527 or similar.
- //
- // Furthermore, xrays should always return an error's original stack, and
- // not overwrite it.
- var stack = e.stack;
- ok(/^\s*$/.test(stack), "stack property should be correct");
- e.wrappedJSObject.stack = "not a stack";
- is(e.stack, stack, "Xrays should never get an overwritten stack property.");
- }
- }
- function testRegExp() {
- // RegExp statics are very weird, and in particular RegExp has static
- // properties that have to do with the last regexp execution in the global.
- // Xraying those makes no sense, so we just skip constructor properties for
- // RegExp xrays.
- // RegExp[@@species] is affected by above skip, but we don't fix it until
- // compelling use-case appears, as supporting RegExp[@@species] while
- // skipping other static properties makes things complicated.
- let ctorPropsToSkip = ["input", "lastMatch", "lastParen",
- "leftContext", "rightContext", "$1", "$2", "$3",
- "$4", "$5", "$6", "$7", "$8", "$9", "$_", "$&",
- "$+", "$`", "$'", Symbol.species];
- testXray('RegExp', new iwin.RegExp('foo'), new iwin.RegExp(), [],
- ctorPropsToSkip);
- // Test the self-hosted |flags| property, toString, and toSource.
- for (var flags of ["", "g", "i", "m", "y", "gimy"]) {
- var re = new iwin.RegExp("foo", flags);
- is(re.flags, re.wrappedJSObject.flags, "Results match");
- isnot(re.toString, Cu.unwaiveXrays(re.wrappedJSObject.toString), "Different function identities");
- is(Cu.getGlobalForObject(re.toString), window, "Xray global is correct");
- is(Cu.getGlobalForObject(re.wrappedJSObject.toString), iwin, "Underlying global is correct");
- is(re.toString(), re.wrappedJSObject.toString(), "Results match");
- isnot(re.toSource, Cu.unwaiveXrays(re.wrappedJSObject.toSource), "Different function identities");
- is(Cu.getGlobalForObject(re.toSource), window, "Xray global is correct");
- is(Cu.getGlobalForObject(re.wrappedJSObject.toSource), iwin, "Underlying global is correct");
- is(re.toSource(), re.wrappedJSObject.toSource(), "Results match");
- // Test with modified flags accessors
- iwin.eval(`
- var props = ["global", "ignoreCase", "multiline", "sticky", "source", "unicode"];
- var origDescs = {};
- for (var prop of props) {
- origDescs[prop] = Object.getOwnPropertyDescriptor(RegExp.prototype, prop);
- Object.defineProperty(RegExp.prototype, prop, {
- get: function() {
- throw new Error("modified accessor is called");
- }
- });
- }
- `);
- try {
- is(re.flags, flags, "Unmodified flags accessors are called");
- is(re.toString(), "/foo/" + flags, "Unmodified flags and source accessors are called");
- is(re.toSource(), "/foo/" + flags, "Unmodified flags and source accessors are called");
- } finally {
- iwin.eval(`
- for (var prop of props) {
- Object.defineProperty(RegExp.prototype, prop, origDescs[prop]);
- }
- `);
- }
- }
- }
- // Note: this is a small set of basic tests. More in-depth tests are located
- // in test_promise_xrays.html.
- function testPromise() {
- testXray('Promise', new iwin.Promise(function(){}), new iwin.Promise(function(){}));
- // Test catch and then.
- var pr = new iwin.Promise(function(){});
- isnot(pr.catch, Cu.unwaiveXrays(pr.wrappedJSObject.catch), "Different function identities");
- is(Cu.getGlobalForObject(pr.catch), window, "Xray global is correct");
- is(Cu.getGlobalForObject(pr.wrappedJSObject.catch), iwin, "Underlying global is correct");
- isnot(pr.then, Cu.unwaiveXrays(pr.wrappedJSObject.then), "Different function identities");
- is(Cu.getGlobalForObject(pr.then), window, "Xray global is correct");
- is(Cu.getGlobalForObject(pr.wrappedJSObject.then), iwin, "Underlying global is correct");
- }
- function testArrayBuffer() {
- let constructors = ['ArrayBuffer'];
- if (!isReleaseOrBeta) {
- constructors.push('SharedArrayBuffer');
- }
- for (const c of constructors) {
- testXray(c, new iwin[c](0), new iwin[c](12));
- var t = new iwin[c](12);
- is(t.byteLength, 12, `${c} byteLength is correct`);
- is(t.slice(4).byteLength, 8, `${c} byteLength is correct after slicing`);
- is(Cu.getGlobalForObject(t.slice(4)), iwin, "Slice results lives in the target compartment");
- is(Object.getPrototypeOf(t.slice(4)), iwin[c].prototype, "Slice results proto lives in target compartment")
- // SharedArrayBuffer does not have static slice method
- if (c === 'ArrayBuffer') {
- is(ArrayBuffer.slice(t, 4).byteLength, 8, `${c}.slice (deprecated) works`);
- }
- var i32Array = new Int32Array(t);
- // i32Array is going to be created in the buffer's target compartment,
- // but usually this is unobservable, because the proto is set to
- // the current compartment's prototype.
- // However Xrays ignore the object's proto and claim its proto is
- // the default proto for that class in the relevant compartment,
- // so see through this proto hack.
- todo_is(Object.getPrototypeOf(i32Array), Int32Array.prototype, "Int32Array has correct proto");
- is(i32Array.length, 3, `Int32Array created from Xray ${c} has the correct length`);
- is(i32Array.buffer, t, "Int32Array has the correct buffer that we passed in");
- i32Array = new iwin.Int32Array(t);
- is(Object.getPrototypeOf(i32Array), iwin.Int32Array.prototype, "Xray Int32Array has correct proto");
- is(i32Array.length, 3, `Xray Int32Array created from Xray ${c} has the correct length`);
- is(i32Array.buffer, t, "Xray Int32Array has the correct buffer that we passed in");
- t = (new iwin.Int32Array(2)).buffer;
- is(t.byteLength, 8, `Can access ${c} returned by buffer property`);
- }
- }
- ]]>
- </script>
- <iframe id="ifr" onload="go();" src="http://example.org/tests/js/xpconnect/tests/mochitest/file_empty.html" />
- </window>
|