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>