1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 * This module handles wrapping privileged objects so that they can be exposed
8 * to unprivileged contexts. It is only to be used in automated tests.
10 * Its exact semantics are also liable to change at any time, so any callers
11 * relying on undocumented behavior or subtle platform features should expect
12 * breakage. Those callers should, wherever possible, migrate to fully
13 * chrome-privileged scripts when they need to interact with privileged APIs.
16 /* globals XPCNativeWrapper */
18 Cu.crashIfNotInAutomation();
20 var EXPORTED_SYMBOLS = ["WrapPrivileged"];
22 let wrappedObjects = new WeakMap();
23 let perWindowInfo = new WeakMap();
24 let noAutoWrap = new WeakSet();
26 function isWrappable(x) {
27 if (typeof x === "object") {
30 return typeof x === "function";
33 function isWrapper(x) {
35 return isWrappable(x) && wrappedObjects.has(x);
37 // If `x` is a remote object proxy, trying to access an unexpected property
38 // on it will throw a security error, even though we're chrome privileged.
39 // However, remote proxies are not SpecialPowers wrappers, so:
44 function unwrapIfWrapped(x) {
45 return isWrapper(x) ? unwrapPrivileged(x) : x;
48 function wrapIfUnwrapped(x, w) {
49 return isWrapper(x) ? x : wrapPrivileged(x, w);
52 function isObjectOrArray(obj) {
53 if (Object(obj) !== obj) {
69 let className = Cu.getClassName(obj, true);
70 return arrayClasses.includes(className);
73 // In general, we want Xray wrappers for content DOM objects, because waiving
74 // Xray gives us Xray waiver wrappers that clamp the principal when we cross
75 // compartment boundaries. However, there are some exceptions where we want
78 // * Xray adds some gunk to toString(), which has the potential to confuse
79 // consumers that aren't expecting Xray wrappers. Since toString() is a
80 // non-privileged method that returns only strings, we can just waive Xray
83 // * We implement Xrays to pure JS [[Object]] and [[Array]] instances that
84 // filter out tricky things like callables. This is the right thing for
85 // security in general, but tends to break tests that try to pass object
86 // literals into SpecialPowers. So we waive [[Object]] and [[Array]]
87 // instances before inspecting properties.
89 // * When we don't have meaningful Xray semantics, we create an Opaque
90 // XrayWrapper for security reasons. For test code, we generally want to see
91 // through that sort of thing.
92 function waiveXraysIfAppropriate(obj, propName) {
94 propName == "toString" ||
95 isObjectOrArray(obj) ||
96 /Opaque/.test(Object.prototype.toString.call(obj))
98 return XPCNativeWrapper.unwrap(obj);
103 // We can't call apply() directy on Xray-wrapped functions, so we have to be
105 function doApply(fun, invocant, args) {
106 // We implement Xrays to pure JS [[Object]] instances that filter out tricky
107 // things like callables. This is the right thing for security in general,
108 // but tends to break tests that try to pass object literals into
109 // SpecialPowers. So we waive [[Object]] instances when they're passed to a
110 // SpecialPowers-wrapped callable.
112 // Note that the transitive nature of Xray waivers means that any property
113 // pulled off such an object will also be waived, and so we'll get principal
114 // clamping for Xrayed DOM objects reached from literals, so passing things
115 // like {l : xoWin.location} won't work. Hopefully the rabbit hole doesn't
117 args = args.map(x => (isObjectOrArray(x) ? Cu.waiveXrays(x) : x));
118 return Reflect.apply(fun, invocant, args);
121 function wrapPrivileged(obj, win) {
122 // Primitives pass straight through.
123 if (!isWrappable(obj)) {
127 // No double wrapping.
128 if (isWrapper(obj)) {
129 throw new Error("Trying to double-wrap object!");
132 let { windowID, proxies, handler } = perWindowInfo.get(win) || {};
133 // |windowUtils| is undefined if |win| is a non-window object
134 // such as a sandbox.
135 let currentID = win.windowGlobalChild
136 ? win.windowGlobalChild.innerWindowId
138 // Values are dead objects if the inner window is changed.
139 if (windowID !== currentID) {
140 windowID = currentID;
141 proxies = new WeakMap();
142 handler = Cu.cloneInto(SpecialPowersHandler, win, {
143 cloneFunctions: true,
145 handler.wrapped = new win.WeakMap();
146 perWindowInfo.set(win, { windowID, proxies, handler });
149 if (proxies.has(obj)) {
150 return proxies.get(obj).proxy;
153 let className = Cu.getClassName(obj, true);
154 if (className === "ArrayBuffer") {
155 // Since |new Uint8Array(<proxy>)| doesn't work as expected, we have to
156 // return a real ArrayBuffer.
157 return obj instanceof win.ArrayBuffer ? obj : Cu.cloneInto(obj, win);
161 if (typeof obj === "function") {
162 dummy = Cu.exportFunction(function() {}, win);
164 dummy = new win.Object();
166 handler.wrapped.set(dummy, { obj });
168 let proxy = new win.Proxy(dummy, handler);
169 wrappedObjects.set(proxy, obj);
171 case "AnonymousContent":
172 // Caching anonymous content will cause crashes (bug 1636015).
174 case "CSS2Properties":
176 case "CSSStyleSheet":
177 // Caching these classes will cause memory leaks.
180 proxies.set(obj, { proxy });
186 function unwrapPrivileged(x) {
187 // We don't wrap primitives, so sometimes we have a primitive where we'd
188 // expect to have a wrapper. The proxy pretends to be the type that it's
189 // emulating, so we can just as easily check isWrappable() on a proxy as
190 // we can on an unwrapped object.
191 if (!isWrappable(x)) {
195 // If we have a wrappable type, make sure it's wrapped.
197 throw new Error("Trying to unwrap a non-wrapped object!");
201 return wrappedObjects.get(x);
204 function wrapExceptions(global, fn) {
208 throw wrapIfUnwrapped(e, global);
212 function specialPowersHasInstance(value) {
213 // Because we return wrapped versions of this function, when it's called its
214 // wrapper will unwrap the "this" as well as the function itself. So our
215 // "this" is the unwrapped thing we started out with.
216 return value instanceof this;
219 let SpecialPowersHandler = {
220 construct(target, args) {
221 // The arguments may or may not be wrappers. Unwrap them if necessary.
222 var unwrappedArgs = Array.from(Cu.waiveXrays(args), x =>
223 unwrapIfWrapped(Cu.unwaiveXrays(x))
226 // We want to invoke "obj" as a constructor, but using unwrappedArgs as
228 let global = Cu.getGlobalForObject(this);
229 return wrapExceptions(global, () =>
231 Reflect.construct(this.wrapped.get(target).obj, unwrappedArgs),
237 apply(target, thisValue, args) {
238 let wrappedObject = this.wrapped.get(target).obj;
239 let global = Cu.getGlobalForObject(this);
240 // The invocant and arguments may or may not be wrappers. Unwrap
241 // them if necessary.
242 var invocant = unwrapIfWrapped(thisValue);
244 return wrapExceptions(global, () => {
245 if (noAutoWrap.has(wrappedObject)) {
246 args = Array.from(Cu.waiveXrays(args), x => Cu.unwaiveXrays(x));
247 return doApply(wrappedObject, invocant, args);
250 if (wrappedObject.name == "then") {
251 args = Array.from(Cu.waiveXrays(args), x =>
252 wrapCallback(Cu.unwaiveXrays(x), global)
255 args = Array.from(Cu.waiveXrays(args), x =>
256 unwrapIfWrapped(Cu.unwaiveXrays(x))
260 return wrapIfUnwrapped(doApply(wrappedObject, invocant, args), global);
265 return Reflect.has(this.wrapped.get(target).obj, prop);
268 get(target, prop, receiver) {
269 let global = Cu.getGlobalForObject(this);
270 return wrapExceptions(global, () => {
271 let obj = waiveXraysIfAppropriate(this.wrapped.get(target).obj, prop);
272 let val = Reflect.get(obj, prop);
273 if (val === undefined && prop == Symbol.hasInstance) {
274 // Special-case Symbol.hasInstance to pass the hasInstance check on to our
275 // target. We only do this when the target doesn't have its own
276 // Symbol.hasInstance already. Once we get rid of JS engine class
277 // instance hooks (bug 1448218) and always use Symbol.hasInstance, we can
278 // remove this bit (bug 1448400).
279 return wrapPrivileged(specialPowersHasInstance, global);
281 return wrapIfUnwrapped(val, global);
285 set(target, prop, val, receiver) {
286 return wrapExceptions(Cu.getGlobalForObject(this), () => {
287 let obj = waiveXraysIfAppropriate(this.wrapped.get(target).obj, prop);
288 return Reflect.set(obj, prop, unwrapIfWrapped(val));
292 delete(target, prop) {
293 return wrapExceptions(Cu.getGlobalForObject(this), () => {
294 return Reflect.deleteProperty(this.wrapped.get(target).obj, prop);
298 defineProperty(target, prop, descriptor) {
300 "Can't call defineProperty on SpecialPowers wrapped object"
304 getOwnPropertyDescriptor(target, prop) {
305 let global = Cu.getGlobalForObject(this);
306 return wrapExceptions(global, () => {
307 let obj = waiveXraysIfAppropriate(this.wrapped.get(target).obj, prop);
308 let desc = Reflect.getOwnPropertyDescriptor(obj, prop);
310 if (desc === undefined) {
311 if (prop == Symbol.hasInstance) {
312 // Special-case Symbol.hasInstance to pass the hasInstance check on to
313 // our target. We only do this when the target doesn't have its own
314 // Symbol.hasInstance already. Once we get rid of JS engine class
315 // instance hooks (bug 1448218) and always use Symbol.hasInstance, we
316 // can remove this bit (bug 1448400).
318 value: wrapPrivileged(specialPowersHasInstance, global),
328 // Transitively maintain the wrapper membrane.
329 let wrapIfExists = key => {
331 desc[key] = wrapIfUnwrapped(desc[key], global);
335 wrapIfExists("value");
339 // A trapping proxy's properties must always be configurable, but sometimes
340 // we come across non-configurable properties. Tell a white lie.
341 desc.configurable = true;
343 return wrapIfUnwrapped(desc, global);
350 // Do the normal thing.
351 let wrappedObject = this.wrapped.get(target).obj;
352 let flt = a => !props.includes(a);
353 props = props.concat(Reflect.ownKeys(wrappedObject).filter(flt));
355 // If we've got an Xray wrapper, include the expandos as well.
356 if ("wrappedJSObject" in wrappedObject) {
357 props = props.concat(
358 Reflect.ownKeys(wrappedObject.wrappedJSObject).filter(flt)
362 return Cu.cloneInto(props, Cu.getGlobalForObject(this));
365 preventExtensions(target) {
367 "Can't call preventExtensions on SpecialPowers wrapped object"
372 function wrapCallback(cb, win) {
373 // Do not wrap if it is already privileged.
374 if (!isWrappable(cb) || Cu.getObjectPrincipal(cb).isSystemPrincipal) {
377 return function SpecialPowersCallbackWrapper() {
378 var args = Array.from(arguments, obj => wrapIfUnwrapped(obj, win));
379 let invocant = wrapIfUnwrapped(this, win);
380 return unwrapIfWrapped(cb.apply(invocant, args));
384 function wrapCallbackObject(obj, win) {
385 // Do not wrap if it is already privileged.
386 if (!isWrappable(obj) || Cu.getObjectPrincipal(obj).isSystemPrincipal) {
389 obj = Cu.waiveXrays(obj);
392 if (typeof obj[i] == "function") {
393 wrapper[i] = wrapCallback(Cu.unwaiveXrays(obj[i]), win);
401 function disableAutoWrap(...objs) {
402 objs.forEach(x => noAutoWrap.add(x));
405 var WrapPrivileged = {
406 wrap: wrapIfUnwrapped,
407 unwrap: unwrapIfWrapped,