From a0843f3171307790e96a502fc0f806dc146d3f98 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Sat, 13 Aug 2016 01:56:21 -0400 Subject: [PATCH] Update to latest Mozilla SpecialPowers code for Proxy.create This fixes the "Proxy.create is not a function" errors during PDF metadata retrieval in Firefox 48 after the old Proxy API removal, but it still throws "doc.location is null". Addresses #1076 --- .../xpcom/translation/translate_firefox.js | 429 ++++++++---------- 1 file changed, 181 insertions(+), 248 deletions(-) diff --git a/chrome/content/zotero/xpcom/translation/translate_firefox.js b/chrome/content/zotero/xpcom/translation/translate_firefox.js index f2cf4f691b..5efb730743 100644 --- a/chrome/content/zotero/xpcom/translation/translate_firefox.js +++ b/chrome/content/zotero/xpcom/translation/translate_firefox.js @@ -37,10 +37,12 @@ const BOMs = { Components.utils.import("resource://gre/modules/NetUtil.jsm"); Components.utils.import("resource://gre/modules/Services.jsm"); -Zotero.Translate.DOMWrapper = new function() { +Zotero.Translate.DOMWrapper = new function() { + var Cu = Components.utils; + /* * BEGIN SPECIAL POWERS WRAPPING CODE - * https://mxr.mozilla.org/mozilla-central/source/testing/mochitest/tests/SimpleTest/specialpowersAPI.js?raw=1 + * https://dxr.mozilla.org/mozilla-central/source/testing/specialpowers/content/specialpowersAPI.js */ function isWrappable(x) { if (typeof x === "object") @@ -49,30 +51,75 @@ Zotero.Translate.DOMWrapper = new function() { }; function isWrapper(x) { - return isWrappable(x) && (typeof x.__wrappedObject !== "undefined"); + return isWrappable(x) && (typeof x.SpecialPowers_wrappedObject !== "undefined"); }; function unwrapIfWrapped(x) { return isWrapper(x) ? unwrapPrivileged(x) : x; }; - function isXrayWrapper(x) { - try { - return x.toString().indexOf("XrayWrapper") !== -1; - } catch(e) { - // The toString() implementation could theoretically throw. But it never - // throws for Xray, so we can just assume non-xray in that case. + function wrapIfUnwrapped(x) { + return isWrapper(x) ? x : wrapPrivileged(x); + } + + function isObjectOrArray(obj) { + if (Object(obj) !== obj) return false; - } + let arrayClasses = ['Object', 'Array', 'Int8Array', 'Uint8Array', + 'Int16Array', 'Uint16Array', 'Int32Array', + 'Uint32Array', 'Float32Array', 'Float64Array', + 'Uint8ClampedArray']; + let className = Cu.getClassName(obj, true); + return arrayClasses.indexOf(className) != -1; + } + + // In general, we want Xray wrappers for content DOM objects, because waiving + // Xray gives us Xray waiver wrappers that clamp the principal when we cross + // compartment boundaries. However, there are some exceptions where we want + // to use a waiver: + // + // * Xray adds some gunk to toString(), which has the potential to confuse + // consumers that aren't expecting Xray wrappers. Since toString() is a + // non-privileged method that returns only strings, we can just waive Xray + // for that case. + // + // * We implement Xrays to pure JS [[Object]] and [[Array]] instances that + // filter out tricky things like callables. This is the right thing for + // security in general, but tends to break tests that try to pass object + // literals into SpecialPowers. So we waive [[Object]] and [[Array]] + // instances before inspecting properties. + // + // * When we don't have meaningful Xray semantics, we create an Opaque + // XrayWrapper for security reasons. For test code, we generally want to see + // through that sort of thing. + function waiveXraysIfAppropriate(obj, propName) { + if (propName == 'toString' || isObjectOrArray(obj) || + /Opaque/.test(Object.prototype.toString.call(obj))) + { + return XPCNativeWrapper.unwrap(obj); + } + return obj; } // We can't call apply() directy on Xray-wrapped functions, so we have to be // clever. function doApply(fun, invocant, args) { - return Function.prototype.apply.call(fun, invocant, args); + // We implement Xrays to pure JS [[Object]] instances that filter out tricky + // things like callables. This is the right thing for security in general, + // but tends to break tests that try to pass object literals into + // SpecialPowers. So we waive [[Object]] instances when they're passed to a + // SpecialPowers-wrapped callable. + // + // Note that the transitive nature of Xray waivers means that any property + // pulled off such an object will also be waived, and so we'll get principal + // clamping for Xrayed DOM objects reached from literals, so passing things + // like {l : xoWin.location} won't work. Hopefully the rabbit hole doesn't + // go that deep. + args = args.map(x => isObjectOrArray(x) ? Cu.waiveXrays(x) : x); + return Reflect.apply(fun, invocant, args); } - - function wrapPrivileged(obj, overrides) { + + function wrapPrivileged(obj) { // Primitives pass straight through. if (!isWrappable(obj)) @@ -82,38 +129,13 @@ Zotero.Translate.DOMWrapper = new function() { if (isWrapper(obj)) throw "Trying to double-wrap object!"; - // Make our core wrapper object. - var handler = new SpecialPowersHandler(obj, overrides); + let dummy; + if (typeof obj === "function") + dummy = function() {}; + else + dummy = Object.create(null); - // If the object is callable, make a function proxy. - if (typeof obj === "function") { - var callTrap = function() { - // The invocant and arguments may or may not be wrappers. Unwrap them if necessary. - var invocant = unwrapIfWrapped(this); - var unwrappedArgs = Array.prototype.slice.call(arguments).map(unwrapIfWrapped); - - return wrapPrivileged(doApply(obj, invocant, unwrappedArgs)); - }; - var constructTrap = function() { - // The arguments may or may not be wrappers. Unwrap them if necessary. - var unwrappedArgs = Array.prototype.slice.call(arguments).map(unwrapIfWrapped); - - // Constructors are tricky, because we can't easily call apply on them. - // As a workaround, we create a wrapper constructor with the same - // |prototype| property. - var FakeConstructor = function() { - doApply(obj, this, unwrappedArgs); - }; - FakeConstructor.prototype = obj.prototype; - - return wrapPrivileged(new FakeConstructor()); - }; - - return Proxy.createFunction(handler, callTrap, constructTrap); - } - - // Otherwise, just make a regular object proxy. - return Proxy.create(handler); + return new Proxy(dummy, new SpecialPowersHandler(obj)); }; function unwrapPrivileged(x) { @@ -129,219 +151,130 @@ Zotero.Translate.DOMWrapper = new function() { if (!isWrapper(x)) throw "Trying to unwrap a non-wrapped object!"; - // Unwrap. - return x.__wrappedObject; + var obj = x.SpecialPowers_wrappedObject; + // unwrapped. + return obj; }; - function crawlProtoChain(obj, fn) { - var rv = fn(obj); - if (rv !== undefined) - return rv; - if (Object.getPrototypeOf(obj)) - return crawlProtoChain(Object.getPrototypeOf(obj), fn); - }; - - /* - * We want to waive the __exposedProps__ security check for SpecialPowers-wrapped - * objects. We do this by creating a proxy singleton that just always returns 'rw' - * for any property name. - */ - function ExposedPropsWaiverHandler() { - // NB: XPConnect denies access if the relevant member of __exposedProps__ is not - // enumerable. - var _permit = { value: 'rw', writable: false, configurable: false, enumerable: true }; - return { - getOwnPropertyDescriptor: function(name) { return _permit; }, - ownKeys: function() { throw Error("Can't enumerate ExposedPropsWaiver"); }, - enumerate: function() { throw Error("Can't enumerate ExposedPropsWaiver"); }, - defineProperty: function(name) { throw Error("Can't define props on ExposedPropsWaiver"); }, - deleteProperty: function(name) { throw Error("Can't delete props from ExposedPropsWaiver"); } - }; - }; - ExposedPropsWaiver = new Proxy({}, ExposedPropsWaiverHandler()); - - function SpecialPowersHandler(obj, overrides) { - this.wrappedObject = obj; - this.overrides = overrides ? overrides : {}; - }; - - // Allow us to transitively maintain the membrane by wrapping descriptors - // we return. - SpecialPowersHandler.prototype.doGetPropertyDescriptor = function(name, own) { - - // Handle our special API. - if (name == "__wrappedObject") - return { value: this.wrappedObject, writeable: false, configurable: false, enumerable: false }; - if (name == "__wrapperOverrides") - return { value: this.overrides, writeable: false, configurable: false, enumerable: false }; - // Handle __exposedProps__. - if (name == "__exposedProps__") - return { value: ExposedPropsWaiver, writable: false, configurable: false, enumerable: false }; - - // In general, we want Xray wrappers for content DOM objects, because waiving - // Xray gives us Xray waiver wrappers that clamp the principal when we cross - // compartment boundaries. However, Xray adds some gunk to toString(), which - // has the potential to confuse consumers that aren't expecting Xray wrappers. - // Since toString() is a non-privileged method that returns only strings, we - // can just waive Xray for that case. - var obj = name == 'toString' ? XPCNativeWrapper.unwrap(this.wrappedObject) - : this.wrappedObject; - - // - // Call through to the wrapped object. - // - // Note that we have several cases here, each of which requires special handling. - // - var desc; - - // Hack for overriding some properties - if (this.overrides.hasOwnProperty(name)) - return { "enumerable": true, "value": this.overrides[name] }; - // Case 1: Own Properties. - // - // This one is easy, thanks to Object.getOwnPropertyDescriptor(). - else if (own) - desc = Object.getOwnPropertyDescriptor(obj, name); - - // Case 2: Not own, not Xray-wrapped. - // - // Here, we can just crawl the prototype chain, calling - // Object.getOwnPropertyDescriptor until we find what we want. - // - // NB: Make sure to check this.wrappedObject here, rather than obj, because - // we may have waived Xray on obj above. - else if (!isXrayWrapper(this.wrappedObject)) - try { - desc = crawlProtoChain(obj, function(o) {return Object.getOwnPropertyDescriptor(o, name);}); - } catch(e) { - // we hit bug 560072 if DOM is not wrapped - // https://bugzilla.mozilla.org/show_bug.cgi?id=560072 - if (name in obj) { - // same guess as below - desc = {value: obj[name], writable: false, configurable: true, enumerable: true}; - } - } - - // Case 3: Not own, Xray-wrapped. - // - // This one is harder, because we Xray wrappers are flattened and don't have - // a prototype. Xray wrappers are proxies themselves, so we'd love to just call - // through to XrayWrapper::getPropertyDescriptor(). Unfortunately though, - // we don't have any way to do that. :-( - // - // So we first try with a call to getOwnPropertyDescriptor(). If that fails, - // we make up a descriptor, using some assumptions about what kinds of things - // tend to live on the prototypes of Xray-wrapped objects. - else { - desc = Object.getOwnPropertyDescriptor(obj, name); - if (!desc) { - var getter = Object.prototype.__lookupGetter__.call(obj, name); - var setter = Object.prototype.__lookupSetter__.call(obj, name); - if (getter || setter) - desc = {get: getter, set: setter, configurable: true, enumerable: true}; - else if (name in obj) - desc = {value: obj[name], writable: false, configurable: true, enumerable: true}; - } - } - - // Bail if we've got nothing. - if (typeof desc === 'undefined') - return undefined; - - // When accessors are implemented as JSPropertyOps rather than JSNatives (ie, - // QuickStubs), the js engine does the wrong thing and treats it as a value - // descriptor rather than an accessor descriptor. Jorendorff suggested this - // little hack to work around it. See bug 520882. - if (desc && 'value' in desc && desc.value === undefined) - desc.value = obj[name]; - - // A trapping proxy's properties must always be configurable, but sometimes - // this we get non-configurable properties from Object.getOwnPropertyDescriptor(). - // Tell a white lie. - desc.configurable = true; - - // Transitively maintain the wrapper membrane. - function wrapIfExists(key) { if (key in desc) desc[key] = wrapPrivileged(desc[key]); }; - wrapIfExists('value'); - wrapIfExists('get'); - wrapIfExists('set'); - - return desc; - }; - - SpecialPowersHandler.prototype.getOwnPropertyDescriptor = function(name) { - return this.doGetPropertyDescriptor(name, true); - }; - - SpecialPowersHandler.prototype.getPropertyDescriptor = function(name) { - return this.doGetPropertyDescriptor(name, false); - }; - - function doGetOwnPropertyNames(obj, props) { - - // Insert our special API. It's not enumerable, but getPropertyNames() - // includes non-enumerable properties. - var specialAPI = '__wrappedObject'; - if (props.indexOf(specialAPI) == -1) - props.push(specialAPI); - - - // Do the normal thing. - var flt = function(a) { return props.indexOf(a) == -1; }; - props = props.concat(Object.getOwnPropertyNames(obj).filter(flt)); - - // If we've got an Xray wrapper, include the expandos as well. - if ('wrappedJSObject' in obj) - props = props.concat(Object.getOwnPropertyNames(obj.wrappedJSObject) - .filter(flt)); - - return props; + function SpecialPowersHandler(wrappedObject) { + this.wrappedObject = wrappedObject; } - SpecialPowersHandler.prototype.getOwnPropertyNames = function() { - return doGetOwnPropertyNames(this.wrappedObject, []); - }; + SpecialPowersHandler.prototype = { + construct(target, args) { + // The arguments may or may not be wrappers. Unwrap them if necessary. + var unwrappedArgs = Array.prototype.slice.call(args).map(unwrapIfWrapped); - SpecialPowersHandler.prototype.getPropertyNames = function() { + // We want to invoke "obj" as a constructor, but using unwrappedArgs as + // the arguments. Make sure to wrap and re-throw exceptions! + try { + return wrapIfUnwrapped(Reflect.construct(this.wrappedObject, unwrappedArgs)); + } catch (e) { + throw wrapIfUnwrapped(e); + } + }, - // Manually walk the prototype chain, making sure to add only property names - // that haven't been overridden. - // - // There's some trickiness here with Xray wrappers. Xray wrappers don't have - // a prototype, so we need to unwrap them if we want to get all of the names - // with Object.getOwnPropertyNames(). But we don't really want to unwrap the - // base object, because that will include expandos that are inaccessible via - // our implementation of get{,Own}PropertyDescriptor(). So we unwrap just - // before accessing the prototype. This ensures that we get Xray vision on - // the base object, and no Xray vision for the rest of the way up. - var obj = this.wrappedObject; - var props = []; - props = doGetOwnPropertyNames(this.overrides, props); - while (obj) { - props = doGetOwnPropertyNames(obj, props); - obj = Object.getPrototypeOf(XPCNativeWrapper.unwrap(obj)); + apply(target, thisValue, args) { + // The invocant and arguments may or may not be wrappers. Unwrap + // them if necessary. + var invocant = unwrapIfWrapped(thisValue); + var unwrappedArgs = Array.prototype.slice.call(args).map(unwrapIfWrapped); + + try { + return wrapIfUnwrapped(doApply(this.wrappedObject, invocant, unwrappedArgs)); + } catch (e) { + // Wrap exceptions and re-throw them. + throw wrapIfUnwrapped(e); + } + }, + + has(target, prop) { + if (prop === "SpecialPowers_wrappedObject") + return true; + + return Reflect.has(this.wrappedObject, prop); + }, + + get(target, prop, receiver) { + if (prop === "SpecialPowers_wrappedObject") + return this.wrappedObject; + + let obj = waiveXraysIfAppropriate(this.wrappedObject, prop); + return wrapIfUnwrapped(Reflect.get(obj, prop)); + }, + + set(target, prop, val, receiver) { + if (prop === "SpecialPowers_wrappedObject") + return false; + + let obj = waiveXraysIfAppropriate(this.wrappedObject, prop); + return Reflect.set(obj, prop, unwrapIfWrapped(val)); + }, + + delete(target, prop) { + if (prop === "SpecialPowers_wrappedObject") + return false; + + return Reflect.deleteProperty(this.wrappedObject, prop); + }, + + defineProperty(target, prop, descriptor) { + throw "Can't call defineProperty on SpecialPowers wrapped object"; + }, + + getOwnPropertyDescriptor(target, prop) { + // Handle our special API. + if (prop === "SpecialPowers_wrappedObject") { + return { value: this.wrappedObject, writeable: true, + configurable: true, enumerable: false }; + } + + let obj = waiveXraysIfAppropriate(this.wrappedObject, prop); + let desc = Reflect.getOwnPropertyDescriptor(obj, prop); + + if (desc === undefined) + return undefined; + + // Transitively maintain the wrapper membrane. + function wrapIfExists(key) { + if (key in desc) + desc[key] = wrapIfUnwrapped(desc[key]); + }; + + wrapIfExists('value'); + wrapIfExists('get'); + wrapIfExists('set'); + + // A trapping proxy's properties must always be configurable, but sometimes + // we come across non-configurable properties. Tell a white lie. + desc.configurable = true; + + return desc; + }, + + ownKeys(target) { + // Insert our special API. It's not enumerable, but ownKeys() + // includes non-enumerable properties. + let props = ['SpecialPowers_wrappedObject']; + + // Do the normal thing. + let flt = (a) => !props.includes(a); + props = props.concat(Reflect.ownKeys(this.wrappedObject).filter(flt)); + + // If we've got an Xray wrapper, include the expandos as well. + if ('wrappedJSObject' in this.wrappedObject) { + props = props.concat(Reflect.ownKeys(this.wrappedObject.wrappedJSObject) + .filter(flt)); + } + + return props; + }, + + preventExtensions(target) { + throw "Can't call preventExtensions on SpecialPowers wrapped object"; } - return props; }; - SpecialPowersHandler.prototype.defineProperty = function(name, desc) { - return Object.defineProperty(this.wrappedObject, name, desc); - }; - - SpecialPowersHandler.prototype.delete = function(name) { - return delete this.wrappedObject[name]; - }; - - SpecialPowersHandler.prototype.fix = function() { return undefined; /* Throws a TypeError. */ }; - - // Per the ES5 spec this is a derived trap, but it's fundamental in spidermonkey - // for some reason. See bug 665198. - SpecialPowersHandler.prototype.enumerate = function() { - var t = this; - var filt = function(name) { return t.getPropertyDescriptor(name).enumerable; }; - return this.getPropertyNames().filter(filt); - }; - /* * END SPECIAL POWERS WRAPPING CODE */