4 https://bugzilla.mozilla.org/show_bug.cgi?id=1929055
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" />
14 var { AppConstants
} = SpecialPowers
.ChromeUtils
.importESModule(
15 "resource://gre/modules/AppConstants.sys.mjs"
17 const isExplicitResourceManagementEnabled
= AppConstants
.ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
;
20 SimpleTest
.waitForExplicitFinish();
22 let simpleConstructors
= [
24 'AsyncDisposableStack',
27 const errorObjectClasses
= [
31 simpleConstructors
= simpleConstructors
.concat(errorObjectClasses
);
33 const iwin
= document
.getElementById('ifr').contentWindow
;
35 if (!isExplicitResourceManagementEnabled
) {
36 for (let c
of simpleConstructors
) {
37 is(iwin
[c
], undefined, "Constructors should not be exposed: " + c
);
43 await SpecialPowers
.pushPrefEnv({
44 set: [["javascript.options.experimental.explicit_resource_management", true]],
47 let global
= Cu
.getGlobalForObject
.bind(Cu
);
49 // Copied from js/xpconnect/tests/chrome/test_xrayToJS.xhtml
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
) {
56 if (typeof c
=== 'object') {
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
);
72 var gPrototypeProperties
= {};
73 var gConstructorProperties
= {};
74 // Properties which cannot be invoked if callable without potentially
75 // rendering the object useless.
76 var gStatefulProperties
= {};
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
) {
87 desc
= Object
.getOwnPropertyDescriptor(obj
, name
);
91 obj
= Object
.getPrototypeOf(obj
);
93 return desc
? (desc
.get || desc
.value
) : undefined;
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");
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");
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") {
130 info(`Running tests for property: ${localCtor.name}.${name}`);
131 // Test both methods and getter properties.
132 function lookupCallable(obj
) {
135 desc
= Object
.getOwnPropertyDescriptor(obj
, name
);
136 obj
= Object
.getPrototypeOf(obj
);
138 return desc
.get || desc
.value
;
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.
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");
172 function filterOut(array
, props
) {
173 return array
.filter(p
=> !props
.includes(p
));
176 function propertyIsGetter(obj
, name
) {
177 return !!Object
.getOwnPropertyDescriptor(obj
, name
).get;
180 function constructorProps(arr
) {
181 // Some props live on all constructors
182 return arr
.concat(["prototype", "length", "name"]);
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();
190 arr
.sort((a
, b
) => sortKey(a
) < sortKey(b
) ? -1 : +1);
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();
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");
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");
226 is(xrayProto
.constructor, iwin
[classname
], "constructor property works");
228 xrayProto
.expando
= 42;
229 is(xray
.expando
, 42, "Xrayed instances see proto expandos");
230 is(xray2
.expando
, 42, "Xrayed instances see proto expandos");
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]
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");
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");
263 gPrototypeProperties
.DisposableStack
= [
264 "adopt", "constructor", "defer", "dispose", "disposed", "move", "use",
265 Symbol
.toStringTag
, Symbol
.dispose
267 gStatefulProperties
.DisposableStack
= ["dispose", Symbol
.dispose
, "move"];
268 gConstructorProperties
.DisposableStack
= constructorProps([]);
270 gPrototypeProperties
.AsyncDisposableStack
= [
271 "adopt", "constructor", "defer", "disposeAsync", "disposed", "move", "use",
272 Symbol
.toStringTag
, Symbol
.asyncDispose
274 gStatefulProperties
.AsyncDisposableStack
= ["disposeAsync", Symbol
.asyncDispose
, "move"];
275 gConstructorProperties
.AsyncDisposableStack
= constructorProps([]);
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
]);
284 function testDisposableStack() {
285 testXray("DisposableStack", new iwin
.DisposableStack(), new iwin
.DisposableStack());
288 function testAsyncDisposableStack() {
289 testXray("AsyncDisposableStack", new iwin
.AsyncDisposableStack(), new iwin
.AsyncDisposableStack());
292 function testSuppressedError() {
293 const c
= errorObjectClasses
[0];
294 const args
= ['error', 'suppressed', 'some message'];
295 var e
= new iwin
.SuppressedError(...args
);
297 // Copied from js/xpconnect/tests/chrome/test_xrayToJS.xhtml
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");
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");
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');
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");
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");
327 testDisposableStack();
329 testAsyncDisposableStack();
331 testSuppressedError();
333 await SpecialPowers
.popPrefEnv();
340 <a target=
"_blank" href=
"https://bugzilla.mozilla.org/show_bug.cgi?id=1929055">Mozilla Bug
1929055</a>
342 <iframe id=
"ifr" onload=
"go();" src=
"http://example.org/tests/js/xpconnect/tests/mochitest/file_empty.html" />