test_xrayToExplicitResourceManagement.html (15776B)
1 <!DOCTYPE html> 2 <html lang="en"> 3 <!-- 4 https://bugzilla.mozilla.org/show_bug.cgi?id=1929055 5 --> 6 7 <head> 8 <meta charset="UTF-8"> 9 <title>Test for Bug 1929055</title> 10 <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> 11 <link rel="stylesheet" type="text/css" href="chrome://global/skin" /> 12 <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> 13 <script> 14 var { AppConstants } = SpecialPowers.ChromeUtils.importESModule( 15 "resource://gre/modules/AppConstants.sys.mjs" 16 ); 17 const isExplicitResourceManagementEnabled = AppConstants.ENABLE_EXPLICIT_RESOURCE_MANAGEMENT; 18 19 async function go() { 20 SimpleTest.waitForExplicitFinish(); 21 22 let simpleConstructors = [ 23 'DisposableStack', 24 'AsyncDisposableStack', 25 ]; 26 27 const errorObjectClasses = [ 28 'SuppressedError', 29 ] 30 31 simpleConstructors = simpleConstructors.concat(errorObjectClasses); 32 33 const iwin = document.getElementById('ifr').contentWindow; 34 35 if (!isExplicitResourceManagementEnabled) { 36 for (let c of simpleConstructors) { 37 is(iwin[c], undefined, "Constructors should not be exposed: " + c); 38 } 39 SimpleTest.finish(); 40 return; 41 } 42 43 await SpecialPowers.pushPrefEnv({ 44 set: [["javascript.options.experimental.explicit_resource_management", true]], 45 }); 46 47 let global = Cu.getGlobalForObject.bind(Cu); 48 49 // Copied from js/xpconnect/tests/chrome/test_xrayToJS.xhtml 50 // ==== BEGIN === 51 52 // Test constructors that can be instantiated with zero arguments, or with 53 // a fixed set of arguments provided using `...rest`. 54 for (let c of simpleConstructors) { 55 var args = []; 56 if (typeof c === 'object') { 57 args = c.args; 58 c = c.name; 59 } 60 ok(iwin[c], "Constructors appear: " + c); 61 is(iwin[c], Cu.unwaiveXrays(iwin.wrappedJSObject[c]), 62 "we end up with the appropriate constructor: " + c); 63 is(Cu.unwaiveXrays(Cu.waiveXrays(new iwin[c](...args)).constructor), iwin[c], 64 "constructor property is set up right: " + c); 65 let expectedProto = Cu.isOpaqueWrapper(new iwin[c](...args)) ? 66 iwin.Object.prototype : iwin[c].prototype; 67 is(Object.getPrototypeOf(new iwin[c](...args)), expectedProto, 68 "prototype is correct: " + c); 69 is(global(new iwin[c](...args)), iwin, "Got the right global: " + c); 70 } 71 72 var gPrototypeProperties = {}; 73 var gConstructorProperties = {}; 74 // Properties which cannot be invoked if callable without potentially 75 // rendering the object useless. 76 var gStatefulProperties = {}; 77 78 function testProtoCallables(protoCallables, xray, xrayProto, localProto, callablesExcluded) { 79 // Handle undefined callablesExcluded. 80 let dontCall = callablesExcluded ?? []; 81 for (let name of protoCallables) { 82 info("Running tests for property: " + name); 83 // Test both methods and getter properties. 84 function lookupCallable(obj) { 85 let desc = null; 86 do { 87 desc = Object.getOwnPropertyDescriptor(obj, name); 88 if (desc) { 89 break; 90 } 91 obj = Object.getPrototypeOf(obj); 92 } while (obj); 93 return desc ? (desc.get || desc.value) : undefined; 94 }; 95 ok(xrayProto.hasOwnProperty(name), `proto should have the property '${name}' as own`); 96 ok(!xray.hasOwnProperty(name), `instance should not have the property '${name}' as own`); 97 let method = lookupCallable(xrayProto); 98 is(typeof method, 'function', "Methods from Xrays are functions"); 99 is(global(method), window, "Methods from Xrays are local"); 100 ok(method instanceof Function, "instanceof works on methods from Xrays"); 101 is(lookupCallable(xrayProto), method, "Holder caching works properly"); 102 is(lookupCallable(xray), method, "Proto props resolve on the instance"); 103 let local = lookupCallable(localProto); 104 is(method.length, local.length, "Function.length identical"); 105 if (!method.length && !dontCall.includes(name)) { 106 is(method.call(xray) + "", local.call(xray) + "", 107 "Xray and local method results stringify identically"); 108 109 // If invoking this method returns something non-Xrayable (opaque), the 110 // stringification is going to return [object Object]. 111 // This happens for set[@@iterator] and other Iterator objects. 112 let callable = lookupCallable(xray.wrappedJSObject); 113 if (!Cu.isOpaqueWrapper(method.call(xray)) && callable) { 114 is(method.call(xray) + "", 115 callable.call(xray.wrappedJSObject) + "", 116 "Xray and waived method results stringify identically"); 117 } 118 } 119 } 120 } 121 122 function testCtorCallables(ctorCallables, xrayCtor, localCtor) { 123 for (let name of ctorCallables) { 124 // Don't try to test Function.prototype, since that is in fact a callable 125 // but doesn't really do the things we expect callables to do here 126 // (e.g. it's in the wrong global, since it gets Xrayed itself). 127 if (name == "prototype" && localCtor.name == "Function") { 128 continue; 129 } 130 info(`Running tests for property: ${localCtor.name}.${name}`); 131 // Test both methods and getter properties. 132 function lookupCallable(obj) { 133 let desc = null; 134 do { 135 desc = Object.getOwnPropertyDescriptor(obj, name); 136 obj = Object.getPrototypeOf(obj); 137 } while (!desc); 138 return desc.get || desc.value; 139 }; 140 141 ok(xrayCtor.hasOwnProperty(name), "ctor should have the property as own"); 142 let method = lookupCallable(xrayCtor); 143 is(typeof method, 'function', "Methods from ctor Xrays are functions"); 144 is(global(method), window, "Methods from ctor Xrays are local"); 145 ok(method instanceof Function, 146 "instanceof works on methods from ctor Xrays"); 147 is(lookupCallable(xrayCtor), method, 148 "Holder caching works properly on ctors"); 149 let local = lookupCallable(localCtor); 150 is(method.length, local.length, 151 "Function.length identical for method from ctor"); 152 // Don't try to do the return-value check on Date.now(), since there is 153 // absolutely no reason it should return the same value each time. 154 // 155 // Also don't try to do the return-value check on Regexp.lastMatch and 156 // Regexp["$&"] (which are aliases), because they get state off the global 157 // they live in, as far as I can tell, so testing them over Xrays will be 158 // wrong: on the Xray they will actaully get the lastMatch of _our_ 159 // global, not the Xrayed one. 160 if (!method.length && 161 !(localCtor.name == "Date" && name == "now") && 162 !(localCtor.name == "RegExp" && (name == "lastMatch" || name == "$&"))) { 163 is(method.call(xrayCtor) + "", local.call(xrayCtor) + "", 164 "Xray and local method results stringify identically on constructors"); 165 is(method.call(xrayCtor) + "", 166 lookupCallable(xrayCtor.wrappedJSObject).call(xrayCtor.wrappedJSObject) + "", 167 "Xray and waived method results stringify identically"); 168 } 169 } 170 } 171 172 function filterOut(array, props) { 173 return array.filter(p => !props.includes(p)); 174 } 175 176 function propertyIsGetter(obj, name) { 177 return !!Object.getOwnPropertyDescriptor(obj, name).get; 178 } 179 180 function constructorProps(arr) { 181 // Some props live on all constructors 182 return arr.concat(["prototype", "length", "name"]); 183 } 184 185 // Sort an array that may contain symbols as well as strings. 186 function sortProperties(arr) { 187 function sortKey(prop) { 188 return typeof prop + ":" + prop.toString(); 189 } 190 arr.sort((a, b) => sortKey(a) < sortKey(b) ? -1 : +1); 191 } 192 193 function testXray(classname, xray, xray2, propsToSkip, ctorPropsToSkip = []) { 194 propsToSkip = propsToSkip || []; 195 let xrayProto = Object.getPrototypeOf(xray); 196 let localProto = window[classname].prototype; 197 let desiredProtoProps = Object.getOwnPropertyNames(localProto).sort(); 198 199 is(desiredProtoProps.toSource(), 200 gPrototypeProperties[classname].filter(id => typeof id === "string").toSource(), 201 "A property on the " + classname + 202 " prototype has changed! You need a security audit from an XPConnect peer"); 203 is(Object.getOwnPropertySymbols(localProto).map(uneval).sort().toSource(), 204 gPrototypeProperties[classname].filter(id => typeof id !== "string").map(uneval).sort().toSource(), 205 "A symbol-keyed property on the " + classname + 206 " prototype has been changed! You need a security audit from an XPConnect peer"); 207 208 let protoProps = filterOut(desiredProtoProps, propsToSkip); 209 let protoCallables = protoProps.filter(name => propertyIsGetter(localProto, name, classname) || 210 typeof localProto[name] == 'function' && 211 name != 'constructor'); 212 let callablesExcluded = gStatefulProperties[classname]; 213 ok(!!protoCallables.length, "Need something to test"); 214 is(xrayProto, iwin[classname].prototype, "Xray proto is correct"); 215 is(xrayProto, xray.__proto__, "Proto accessors agree"); 216 var protoProto = classname == "Object" ? null : iwin.Object.prototype; 217 is(Object.getPrototypeOf(xrayProto), protoProto, "proto proto is correct"); 218 testProtoCallables(protoCallables, xray, xrayProto, localProto, callablesExcluded); 219 is(Object.getOwnPropertyNames(xrayProto).sort().toSource(), 220 protoProps.toSource(), "getOwnPropertyNames works"); 221 is(Object.getOwnPropertySymbols(xrayProto).map(uneval).sort().toSource(), 222 gPrototypeProperties[classname].filter(id => typeof id !== "string" && !propsToSkip.includes(id)) 223 .map(uneval).sort().toSource(), 224 "getOwnPropertySymbols works"); 225 226 is(xrayProto.constructor, iwin[classname], "constructor property works"); 227 228 xrayProto.expando = 42; 229 is(xray.expando, 42, "Xrayed instances see proto expandos"); 230 is(xray2.expando, 42, "Xrayed instances see proto expandos"); 231 232 // Now test constructors 233 let localCtor = window[classname]; 234 let xrayCtor = xrayProto.constructor; 235 // We already checked that this is the same as iwin[classname] 236 237 let desiredCtorProps = 238 Object.getOwnPropertyNames(localCtor).sort(); 239 is(desiredCtorProps.toSource(), 240 gConstructorProperties[classname].filter(id => typeof id === "string").toSource(), 241 "A property on the " + classname + 242 " constructor has changed! You need a security audit from an XPConnect peer"); 243 let desiredCtorSymbols = 244 Object.getOwnPropertySymbols(localCtor).map(uneval).sort() 245 is(desiredCtorSymbols.toSource(), 246 gConstructorProperties[classname].filter(id => typeof id !== "string").map(uneval).sort().toSource(), 247 "A symbol-keyed property on the " + classname + 248 " constructor has been changed! You need a security audit from an XPConnect peer"); 249 250 let ctorProps = filterOut(desiredCtorProps, ctorPropsToSkip); 251 let ctorSymbols = filterOut(desiredCtorSymbols, ctorPropsToSkip.map(uneval)); 252 let ctorCallables = ctorProps.filter(name => propertyIsGetter(localCtor, name, classname) || 253 typeof localCtor[name] == 'function'); 254 testCtorCallables(ctorCallables, xrayCtor, localCtor); 255 is(Object.getOwnPropertyNames(xrayCtor).sort().toSource(), 256 ctorProps.toSource(), "getOwnPropertyNames works on Xrayed ctors"); 257 is(Object.getOwnPropertySymbols(xrayCtor).map(uneval).sort().toSource(), 258 ctorSymbols.toSource(), "getOwnPropertySymbols works on Xrayed ctors"); 259 } 260 261 // ==== END === 262 263 gPrototypeProperties.DisposableStack = [ 264 "adopt", "constructor", "defer", "dispose", "disposed", "move", "use", 265 Symbol.toStringTag, Symbol.dispose 266 ]; 267 gStatefulProperties.DisposableStack = ["dispose", Symbol.dispose, "move"]; 268 gConstructorProperties.DisposableStack = constructorProps([]); 269 270 gPrototypeProperties.AsyncDisposableStack = [ 271 "adopt", "constructor", "defer", "disposeAsync", "disposed", "move", "use", 272 Symbol.toStringTag, Symbol.asyncDispose 273 ]; 274 gStatefulProperties.AsyncDisposableStack = ["disposeAsync", Symbol.asyncDispose, "move"]; 275 gConstructorProperties.AsyncDisposableStack = constructorProps([]); 276 277 // Sort all the lists so we don't need to mutate them later (or copy them 278 // again to sort them). 279 for (let c of Object.keys(gPrototypeProperties)) 280 sortProperties(gPrototypeProperties[c]); 281 for (let c of Object.keys(gConstructorProperties)) 282 sortProperties(gConstructorProperties[c]); 283 284 function testDisposableStack() { 285 testXray("DisposableStack", new iwin.DisposableStack(), new iwin.DisposableStack()); 286 } 287 288 function testAsyncDisposableStack() { 289 testXray("AsyncDisposableStack", new iwin.AsyncDisposableStack(), new iwin.AsyncDisposableStack()); 290 } 291 292 function testSuppressedError() { 293 const c = errorObjectClasses[0]; 294 const args = ['error', 'suppressed', 'some message']; 295 var e = new iwin.SuppressedError(...args); 296 297 // Copied from js/xpconnect/tests/chrome/test_xrayToJS.xhtml 298 // ==== BEGIN === 299 300 is(Object.getPrototypeOf(e).name, c, "Prototype has correct name"); 301 is(Object.getPrototypeOf(Object.getPrototypeOf(e)), iwin.Error.prototype, "Dependent prototype set up correctly"); 302 is(e.name, c, "Exception name inherited correctly"); 303 304 function testProperty(name, criterion, goodReplacement, faultyReplacement) { 305 ok(criterion(e[name]), name + " property is correct: " + e[name]); 306 e.wrappedJSObject[name] = goodReplacement; 307 is(e[name], goodReplacement, name + " property ok after replacement: " + goodReplacement); 308 e.wrappedJSObject[name] = faultyReplacement; 309 is(e[name], name == 'message' ? "" : undefined, name + " property skipped after suspicious replacement"); 310 } 311 testProperty('message', x => x == 'some message', 'some other message', 42); 312 testProperty('fileName', x => x == '', 'otherFilename.html', new iwin.Object()); 313 testProperty('columnNumber', x => x == 1, 99, 99.5); 314 testProperty('lineNumber', x => x == 0, 50, 'foo'); 315 316 // ==== END === 317 318 e.wrappedJSObject.error = 42; 319 is(e.wrappedJSObject.error, 42, "errors is a plain data property"); 320 is(e.error, 42, "error visible over Xrays"); 321 322 e.wrappedJSObject.suppressed = 43; 323 is(e.wrappedJSObject.suppressed, 43, "suppressed is a plain data property"); 324 is(e.suppressed, 43, "suppressed visible over Xrays"); 325 } 326 327 testDisposableStack(); 328 329 testAsyncDisposableStack(); 330 331 testSuppressedError(); 332 333 await SpecialPowers.popPrefEnv(); 334 SimpleTest.finish(); 335 } 336 </script> 337 </head> 338 339 <body> 340 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1929055">Mozilla Bug 1929055</a> 341 342 <iframe id="ifr" onload="go();" src="http://example.org/tests/js/xpconnect/tests/mochitest/file_empty.html" /> 343 </body> 344 345 </html>