michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "jsproxy.h" michael@0: michael@0: #include michael@0: michael@0: #include "jsapi.h" michael@0: #include "jscntxt.h" michael@0: #include "jsfun.h" michael@0: #include "jsgc.h" michael@0: #include "jswrapper.h" michael@0: michael@0: #include "gc/Marking.h" michael@0: #include "vm/WrapperObject.h" michael@0: michael@0: #include "jsatominlines.h" michael@0: #include "jsinferinlines.h" michael@0: #include "jsobjinlines.h" michael@0: michael@0: #include "vm/ObjectImpl-inl.h" michael@0: michael@0: using namespace js; michael@0: using namespace js::gc; michael@0: using mozilla::ArrayLength; michael@0: michael@0: void michael@0: js::AutoEnterPolicy::reportErrorIfExceptionIsNotPending(JSContext *cx, jsid id) michael@0: { michael@0: if (JS_IsExceptionPending(cx)) michael@0: return; michael@0: michael@0: if (JSID_IS_VOID(id)) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, michael@0: JSMSG_OBJECT_ACCESS_DENIED); michael@0: } else { michael@0: JSString *str = IdToString(cx, id); michael@0: const jschar *prop = str ? str->getCharsZ(cx) : nullptr; michael@0: JS_ReportErrorNumberUC(cx, js_GetErrorMessage, nullptr, michael@0: JSMSG_PROPERTY_ACCESS_DENIED, prop); michael@0: } michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: void michael@0: js::AutoEnterPolicy::recordEnter(JSContext *cx, HandleObject proxy, HandleId id, Action act) michael@0: { michael@0: if (allowed()) { michael@0: context = cx; michael@0: enteredProxy.construct(proxy); michael@0: enteredId.construct(id); michael@0: enteredAction = act; michael@0: prev = cx->runtime()->enteredPolicy; michael@0: cx->runtime()->enteredPolicy = this; michael@0: } michael@0: } michael@0: michael@0: void michael@0: js::AutoEnterPolicy::recordLeave() michael@0: { michael@0: if (!enteredProxy.empty()) { michael@0: JS_ASSERT(context->runtime()->enteredPolicy == this); michael@0: context->runtime()->enteredPolicy = prev; michael@0: } michael@0: } michael@0: michael@0: JS_FRIEND_API(void) michael@0: js::assertEnteredPolicy(JSContext *cx, JSObject *proxy, jsid id, michael@0: BaseProxyHandler::Action act) michael@0: { michael@0: MOZ_ASSERT(proxy->is()); michael@0: MOZ_ASSERT(cx->runtime()->enteredPolicy); michael@0: MOZ_ASSERT(cx->runtime()->enteredPolicy->enteredProxy.ref().get() == proxy); michael@0: MOZ_ASSERT(cx->runtime()->enteredPolicy->enteredId.ref().get() == id); michael@0: MOZ_ASSERT(cx->runtime()->enteredPolicy->enteredAction & act); michael@0: } michael@0: #endif michael@0: michael@0: BaseProxyHandler::BaseProxyHandler(const void *family) michael@0: : mFamily(family), michael@0: mHasPrototype(false), michael@0: mHasSecurityPolicy(false) michael@0: { michael@0: } michael@0: michael@0: BaseProxyHandler::~BaseProxyHandler() michael@0: { michael@0: } michael@0: michael@0: bool michael@0: BaseProxyHandler::enter(JSContext *cx, HandleObject wrapper, HandleId id, Action act, michael@0: bool *bp) michael@0: { michael@0: *bp = true; michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: BaseProxyHandler::has(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) michael@0: { michael@0: assertEnteredPolicy(cx, proxy, id, GET); michael@0: Rooted desc(cx); michael@0: if (!getPropertyDescriptor(cx, proxy, id, &desc)) michael@0: return false; michael@0: *bp = !!desc.object(); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: BaseProxyHandler::hasOwn(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) michael@0: { michael@0: // Note: Proxy::set needs to invoke hasOwn to determine where the setter michael@0: // lives, so we allow SET operations to invoke us. michael@0: assertEnteredPolicy(cx, proxy, id, GET | SET); michael@0: Rooted desc(cx); michael@0: if (!getOwnPropertyDescriptor(cx, proxy, id, &desc)) michael@0: return false; michael@0: *bp = !!desc.object(); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: BaseProxyHandler::get(JSContext *cx, HandleObject proxy, HandleObject receiver, michael@0: HandleId id, MutableHandleValue vp) michael@0: { michael@0: assertEnteredPolicy(cx, proxy, id, GET); michael@0: michael@0: Rooted desc(cx); michael@0: if (!getPropertyDescriptor(cx, proxy, id, &desc)) michael@0: return false; michael@0: if (!desc.object()) { michael@0: vp.setUndefined(); michael@0: return true; michael@0: } michael@0: if (!desc.getter() || michael@0: (!desc.hasGetterObject() && desc.getter() == JS_PropertyStub)) michael@0: { michael@0: vp.set(desc.value()); michael@0: return true; michael@0: } michael@0: if (desc.hasGetterObject()) michael@0: return InvokeGetterOrSetter(cx, receiver, ObjectValue(*desc.getterObject()), michael@0: 0, nullptr, vp); michael@0: if (!desc.isShared()) michael@0: vp.set(desc.value()); michael@0: else michael@0: vp.setUndefined(); michael@0: michael@0: return CallJSPropertyOp(cx, desc.getter(), receiver, id, vp); michael@0: } michael@0: michael@0: bool michael@0: BaseProxyHandler::set(JSContext *cx, HandleObject proxy, HandleObject receiver, michael@0: HandleId id, bool strict, MutableHandleValue vp) michael@0: { michael@0: assertEnteredPolicy(cx, proxy, id, SET); michael@0: michael@0: // Find an own or inherited property. The code here is strange for maximum michael@0: // backward compatibility with earlier code written before ES6 and before michael@0: // SetPropertyIgnoringNamedGetter. michael@0: Rooted desc(cx); michael@0: if (!getOwnPropertyDescriptor(cx, proxy, id, &desc)) michael@0: return false; michael@0: bool descIsOwn = desc.object() != nullptr; michael@0: if (!descIsOwn) { michael@0: if (!getPropertyDescriptor(cx, proxy, id, &desc)) michael@0: return false; michael@0: } michael@0: michael@0: return SetPropertyIgnoringNamedGetter(cx, this, proxy, receiver, id, &desc, descIsOwn, strict, michael@0: vp); michael@0: } michael@0: michael@0: bool michael@0: js::SetPropertyIgnoringNamedGetter(JSContext *cx, BaseProxyHandler *handler, michael@0: HandleObject proxy, HandleObject receiver, michael@0: HandleId id, MutableHandle desc, michael@0: bool descIsOwn, bool strict, MutableHandleValue vp) michael@0: { michael@0: /* The control-flow here differs from ::get() because of the fall-through case below. */ michael@0: if (descIsOwn) { michael@0: JS_ASSERT(desc.object()); michael@0: michael@0: // Check for read-only properties. michael@0: if (desc.isReadonly()) michael@0: return strict ? Throw(cx, id, JSMSG_CANT_REDEFINE_PROP) : true; michael@0: if (!desc.setter()) { michael@0: // Be wary of the odd explicit undefined setter case possible through michael@0: // Object.defineProperty. michael@0: if (!desc.hasSetterObject()) michael@0: desc.setSetter(JS_StrictPropertyStub); michael@0: } else if (desc.hasSetterObject() || desc.setter() != JS_StrictPropertyStub) { michael@0: if (!CallSetter(cx, receiver, id, desc.setter(), desc.attributes(), strict, vp)) michael@0: return false; michael@0: if (!proxy->is() || proxy->as().handler() != handler) michael@0: return true; michael@0: if (desc.isShared()) michael@0: return true; michael@0: } michael@0: if (!desc.getter()) { michael@0: // Same as above for the null setter case. michael@0: if (!desc.hasGetterObject()) michael@0: desc.setGetter(JS_PropertyStub); michael@0: } michael@0: desc.value().set(vp.get()); michael@0: return handler->defineProperty(cx, receiver, id, desc); michael@0: } michael@0: if (desc.object()) { michael@0: // Check for read-only properties. michael@0: if (desc.isReadonly()) michael@0: return strict ? Throw(cx, id, JSMSG_CANT_REDEFINE_PROP) : true; michael@0: if (!desc.setter()) { michael@0: // Be wary of the odd explicit undefined setter case possible through michael@0: // Object.defineProperty. michael@0: if (!desc.hasSetterObject()) michael@0: desc.setSetter(JS_StrictPropertyStub); michael@0: } else if (desc.hasSetterObject() || desc.setter() != JS_StrictPropertyStub) { michael@0: if (!CallSetter(cx, receiver, id, desc.setter(), desc.attributes(), strict, vp)) michael@0: return false; michael@0: if (!proxy->is() || proxy->as().handler() != handler) michael@0: return true; michael@0: if (desc.isShared()) michael@0: return true; michael@0: } michael@0: if (!desc.getter()) { michael@0: // Same as above for the null setter case. michael@0: if (!desc.hasGetterObject()) michael@0: desc.setGetter(JS_PropertyStub); michael@0: } michael@0: desc.value().set(vp.get()); michael@0: return handler->defineProperty(cx, receiver, id, desc); michael@0: } michael@0: michael@0: desc.object().set(receiver); michael@0: desc.value().set(vp.get()); michael@0: desc.setAttributes(JSPROP_ENUMERATE); michael@0: desc.setGetter(nullptr); michael@0: desc.setSetter(nullptr); // Pick up the class getter/setter. michael@0: return handler->defineProperty(cx, receiver, id, desc); michael@0: } michael@0: michael@0: bool michael@0: BaseProxyHandler::keys(JSContext *cx, HandleObject proxy, AutoIdVector &props) michael@0: { michael@0: assertEnteredPolicy(cx, proxy, JSID_VOID, ENUMERATE); michael@0: JS_ASSERT(props.length() == 0); michael@0: michael@0: if (!getOwnPropertyNames(cx, proxy, props)) michael@0: return false; michael@0: michael@0: /* Select only the enumerable properties through in-place iteration. */ michael@0: Rooted desc(cx); michael@0: RootedId id(cx); michael@0: size_t i = 0; michael@0: for (size_t j = 0, len = props.length(); j < len; j++) { michael@0: JS_ASSERT(i <= j); michael@0: id = props[j]; michael@0: AutoWaivePolicy policy(cx, proxy, id, BaseProxyHandler::GET); michael@0: if (!getOwnPropertyDescriptor(cx, proxy, id, &desc)) michael@0: return false; michael@0: if (desc.object() && desc.isEnumerable()) michael@0: props[i++] = id; michael@0: } michael@0: michael@0: JS_ASSERT(i <= props.length()); michael@0: props.resize(i); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: BaseProxyHandler::iterate(JSContext *cx, HandleObject proxy, unsigned flags, MutableHandleValue vp) michael@0: { michael@0: assertEnteredPolicy(cx, proxy, JSID_VOID, ENUMERATE); michael@0: michael@0: AutoIdVector props(cx); michael@0: if ((flags & JSITER_OWNONLY) michael@0: ? !keys(cx, proxy, props) michael@0: : !enumerate(cx, proxy, props)) { michael@0: return false; michael@0: } michael@0: michael@0: return EnumeratedIdVectorToIterator(cx, proxy, flags, props, vp); michael@0: } michael@0: michael@0: bool michael@0: BaseProxyHandler::call(JSContext *cx, HandleObject proxy, const CallArgs &args) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("callable proxies should implement call trap"); michael@0: } michael@0: michael@0: bool michael@0: BaseProxyHandler::construct(JSContext *cx, HandleObject proxy, const CallArgs &args) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("callable proxies should implement construct trap"); michael@0: } michael@0: michael@0: const char * michael@0: BaseProxyHandler::className(JSContext *cx, HandleObject proxy) michael@0: { michael@0: return proxy->isCallable() ? "Function" : "Object"; michael@0: } michael@0: michael@0: JSString * michael@0: BaseProxyHandler::fun_toString(JSContext *cx, HandleObject proxy, unsigned indent) michael@0: { michael@0: if (proxy->isCallable()) michael@0: return JS_NewStringCopyZ(cx, "function () {\n [native code]\n}"); michael@0: RootedValue v(cx, ObjectValue(*proxy)); michael@0: ReportIsNotFunction(cx, v); michael@0: return nullptr; michael@0: } michael@0: michael@0: bool michael@0: BaseProxyHandler::regexp_toShared(JSContext *cx, HandleObject proxy, michael@0: RegExpGuard *g) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("This should have been a wrapped regexp"); michael@0: } michael@0: michael@0: bool michael@0: BaseProxyHandler::defaultValue(JSContext *cx, HandleObject proxy, JSType hint, michael@0: MutableHandleValue vp) michael@0: { michael@0: return DefaultValue(cx, proxy, hint, vp); michael@0: } michael@0: michael@0: bool michael@0: BaseProxyHandler::nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl, CallArgs args) michael@0: { michael@0: ReportIncompatible(cx, args); michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: BaseProxyHandler::hasInstance(JSContext *cx, HandleObject proxy, MutableHandleValue v, bool *bp) michael@0: { michael@0: assertEnteredPolicy(cx, proxy, JSID_VOID, GET); michael@0: RootedValue val(cx, ObjectValue(*proxy.get())); michael@0: js_ReportValueError(cx, JSMSG_BAD_INSTANCEOF_RHS, michael@0: JSDVG_SEARCH_STACK, val, js::NullPtr()); michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: BaseProxyHandler::objectClassIs(HandleObject proxy, ESClassValue classValue, JSContext *cx) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: BaseProxyHandler::finalize(JSFreeOp *fop, JSObject *proxy) michael@0: { michael@0: } michael@0: michael@0: JSObject * michael@0: BaseProxyHandler::weakmapKeyDelegate(JSObject *proxy) michael@0: { michael@0: return nullptr; michael@0: } michael@0: michael@0: bool michael@0: BaseProxyHandler::getPrototypeOf(JSContext *cx, HandleObject proxy, MutableHandleObject protop) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("Must override getPrototypeOf with lazy prototype."); michael@0: } michael@0: michael@0: bool michael@0: BaseProxyHandler::setPrototypeOf(JSContext *cx, HandleObject, HandleObject, bool *) michael@0: { michael@0: // Disallow sets of protos on proxies with lazy protos, but no hook. michael@0: // This keeps us away from the footgun of having the first proto set opt michael@0: // you out of having dynamic protos altogether. michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_SETPROTOTYPEOF_FAIL, michael@0: "incompatible Proxy"); michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: BaseProxyHandler::watch(JSContext *cx, HandleObject proxy, HandleId id, HandleObject callable) michael@0: { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_WATCH, michael@0: proxy->getClass()->name); michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: BaseProxyHandler::unwatch(JSContext *cx, HandleObject proxy, HandleId id) michael@0: { michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: BaseProxyHandler::slice(JSContext *cx, HandleObject proxy, uint32_t begin, uint32_t end, michael@0: HandleObject result) michael@0: { michael@0: assertEnteredPolicy(cx, proxy, JSID_VOID, GET); michael@0: michael@0: return js::SliceSlowly(cx, proxy, proxy, begin, end, result); michael@0: } michael@0: michael@0: bool michael@0: DirectProxyHandler::getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id, michael@0: MutableHandle desc) michael@0: { michael@0: assertEnteredPolicy(cx, proxy, id, GET | SET); michael@0: JS_ASSERT(!hasPrototype()); // Should never be called if there's a prototype. michael@0: RootedObject target(cx, proxy->as().target()); michael@0: return JS_GetPropertyDescriptorById(cx, target, id, desc); michael@0: } michael@0: michael@0: bool michael@0: DirectProxyHandler::getOwnPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id, michael@0: MutableHandle desc) michael@0: { michael@0: assertEnteredPolicy(cx, proxy, id, GET | SET); michael@0: RootedObject target(cx, proxy->as().target()); michael@0: return js::GetOwnPropertyDescriptor(cx, target, id, desc); michael@0: } michael@0: michael@0: bool michael@0: DirectProxyHandler::defineProperty(JSContext *cx, HandleObject proxy, HandleId id, michael@0: MutableHandle desc) michael@0: { michael@0: assertEnteredPolicy(cx, proxy, id, SET); michael@0: RootedObject target(cx, proxy->as().target()); michael@0: RootedValue v(cx, desc.value()); michael@0: return CheckDefineProperty(cx, target, id, v, desc.getter(), desc.setter(), desc.attributes()) && michael@0: JS_DefinePropertyById(cx, target, id, v, desc.getter(), desc.setter(), desc.attributes()); michael@0: } michael@0: michael@0: bool michael@0: DirectProxyHandler::getOwnPropertyNames(JSContext *cx, HandleObject proxy, michael@0: AutoIdVector &props) michael@0: { michael@0: assertEnteredPolicy(cx, proxy, JSID_VOID, ENUMERATE); michael@0: RootedObject target(cx, proxy->as().target()); michael@0: return GetPropertyNames(cx, target, JSITER_OWNONLY | JSITER_HIDDEN, &props); michael@0: } michael@0: michael@0: bool michael@0: DirectProxyHandler::delete_(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) michael@0: { michael@0: assertEnteredPolicy(cx, proxy, id, SET); michael@0: RootedObject target(cx, proxy->as().target()); michael@0: return JS_DeletePropertyById2(cx, target, id, bp); michael@0: } michael@0: michael@0: bool michael@0: DirectProxyHandler::enumerate(JSContext *cx, HandleObject proxy, michael@0: AutoIdVector &props) michael@0: { michael@0: assertEnteredPolicy(cx, proxy, JSID_VOID, ENUMERATE); michael@0: JS_ASSERT(!hasPrototype()); // Should never be called if there's a prototype. michael@0: RootedObject target(cx, proxy->as().target()); michael@0: return GetPropertyNames(cx, target, 0, &props); michael@0: } michael@0: michael@0: bool michael@0: DirectProxyHandler::call(JSContext *cx, HandleObject proxy, const CallArgs &args) michael@0: { michael@0: assertEnteredPolicy(cx, proxy, JSID_VOID, CALL); michael@0: RootedValue target(cx, proxy->as().private_()); michael@0: return Invoke(cx, args.thisv(), target, args.length(), args.array(), args.rval()); michael@0: } michael@0: michael@0: bool michael@0: DirectProxyHandler::construct(JSContext *cx, HandleObject proxy, const CallArgs &args) michael@0: { michael@0: assertEnteredPolicy(cx, proxy, JSID_VOID, CALL); michael@0: RootedValue target(cx, proxy->as().private_()); michael@0: return InvokeConstructor(cx, target, args.length(), args.array(), args.rval().address()); michael@0: } michael@0: michael@0: bool michael@0: DirectProxyHandler::nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl, michael@0: CallArgs args) michael@0: { michael@0: args.setThis(ObjectValue(*args.thisv().toObject().as().target())); michael@0: if (!test(args.thisv())) { michael@0: ReportIncompatible(cx, args); michael@0: return false; michael@0: } michael@0: michael@0: return CallNativeImpl(cx, impl, args); michael@0: } michael@0: michael@0: bool michael@0: DirectProxyHandler::hasInstance(JSContext *cx, HandleObject proxy, MutableHandleValue v, michael@0: bool *bp) michael@0: { michael@0: assertEnteredPolicy(cx, proxy, JSID_VOID, GET); michael@0: bool b; michael@0: RootedObject target(cx, proxy->as().target()); michael@0: if (!HasInstance(cx, target, v, &b)) michael@0: return false; michael@0: *bp = !!b; michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: DirectProxyHandler::getPrototypeOf(JSContext *cx, HandleObject proxy, MutableHandleObject protop) michael@0: { michael@0: RootedObject target(cx, proxy->as().target()); michael@0: return JSObject::getProto(cx, target, protop); michael@0: } michael@0: michael@0: bool michael@0: DirectProxyHandler::setPrototypeOf(JSContext *cx, HandleObject proxy, HandleObject proto, bool *bp) michael@0: { michael@0: RootedObject target(cx, proxy->as().target()); michael@0: return JSObject::setProto(cx, target, proto, bp); michael@0: } michael@0: michael@0: bool michael@0: DirectProxyHandler::objectClassIs(HandleObject proxy, ESClassValue classValue, michael@0: JSContext *cx) michael@0: { michael@0: RootedObject target(cx, proxy->as().target()); michael@0: return ObjectClassIs(target, classValue, cx); michael@0: } michael@0: michael@0: const char * michael@0: DirectProxyHandler::className(JSContext *cx, HandleObject proxy) michael@0: { michael@0: assertEnteredPolicy(cx, proxy, JSID_VOID, GET); michael@0: RootedObject target(cx, proxy->as().target()); michael@0: return JSObject::className(cx, target); michael@0: } michael@0: michael@0: JSString * michael@0: DirectProxyHandler::fun_toString(JSContext *cx, HandleObject proxy, michael@0: unsigned indent) michael@0: { michael@0: assertEnteredPolicy(cx, proxy, JSID_VOID, GET); michael@0: RootedObject target(cx, proxy->as().target()); michael@0: return fun_toStringHelper(cx, target, indent); michael@0: } michael@0: michael@0: bool michael@0: DirectProxyHandler::regexp_toShared(JSContext *cx, HandleObject proxy, michael@0: RegExpGuard *g) michael@0: { michael@0: RootedObject target(cx, proxy->as().target()); michael@0: return RegExpToShared(cx, target, g); michael@0: } michael@0: michael@0: JSObject * michael@0: DirectProxyHandler::weakmapKeyDelegate(JSObject *proxy) michael@0: { michael@0: return UncheckedUnwrap(proxy); michael@0: } michael@0: michael@0: DirectProxyHandler::DirectProxyHandler(const void *family) michael@0: : BaseProxyHandler(family) michael@0: { michael@0: } michael@0: michael@0: bool michael@0: DirectProxyHandler::has(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) michael@0: { michael@0: assertEnteredPolicy(cx, proxy, id, GET); michael@0: JS_ASSERT(!hasPrototype()); // Should never be called if there's a prototype. michael@0: bool found; michael@0: RootedObject target(cx, proxy->as().target()); michael@0: if (!JS_HasPropertyById(cx, target, id, &found)) michael@0: return false; michael@0: *bp = !!found; michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: DirectProxyHandler::hasOwn(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) michael@0: { michael@0: // Note: Proxy::set needs to invoke hasOwn to determine where the setter michael@0: // lives, so we allow SET operations to invoke us. michael@0: assertEnteredPolicy(cx, proxy, id, GET | SET); michael@0: RootedObject target(cx, proxy->as().target()); michael@0: Rooted desc(cx); michael@0: if (!JS_GetPropertyDescriptorById(cx, target, id, &desc)) michael@0: return false; michael@0: *bp = (desc.object() == target); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: DirectProxyHandler::get(JSContext *cx, HandleObject proxy, HandleObject receiver, michael@0: HandleId id, MutableHandleValue vp) michael@0: { michael@0: assertEnteredPolicy(cx, proxy, id, GET); michael@0: RootedObject target(cx, proxy->as().target()); michael@0: return JSObject::getGeneric(cx, target, receiver, id, vp); michael@0: } michael@0: michael@0: bool michael@0: DirectProxyHandler::set(JSContext *cx, HandleObject proxy, HandleObject receiver, michael@0: HandleId id, bool strict, MutableHandleValue vp) michael@0: { michael@0: assertEnteredPolicy(cx, proxy, id, SET); michael@0: RootedObject target(cx, proxy->as().target()); michael@0: return JSObject::setGeneric(cx, target, receiver, id, vp, strict); michael@0: } michael@0: michael@0: bool michael@0: DirectProxyHandler::keys(JSContext *cx, HandleObject proxy, AutoIdVector &props) michael@0: { michael@0: assertEnteredPolicy(cx, proxy, JSID_VOID, ENUMERATE); michael@0: RootedObject target(cx, proxy->as().target()); michael@0: return GetPropertyNames(cx, target, JSITER_OWNONLY, &props); michael@0: } michael@0: michael@0: bool michael@0: DirectProxyHandler::iterate(JSContext *cx, HandleObject proxy, unsigned flags, michael@0: MutableHandleValue vp) michael@0: { michael@0: assertEnteredPolicy(cx, proxy, JSID_VOID, ENUMERATE); michael@0: JS_ASSERT(!hasPrototype()); // Should never be called if there's a prototype. michael@0: RootedObject target(cx, proxy->as().target()); michael@0: return GetIterator(cx, target, flags, vp); michael@0: } michael@0: michael@0: bool michael@0: DirectProxyHandler::isExtensible(JSContext *cx, HandleObject proxy, bool *extensible) michael@0: { michael@0: RootedObject target(cx, proxy->as().target()); michael@0: return JSObject::isExtensible(cx, target, extensible); michael@0: } michael@0: michael@0: bool michael@0: DirectProxyHandler::preventExtensions(JSContext *cx, HandleObject proxy) michael@0: { michael@0: RootedObject target(cx, proxy->as().target()); michael@0: return JSObject::preventExtensions(cx, target); michael@0: } michael@0: michael@0: static bool michael@0: GetFundamentalTrap(JSContext *cx, HandleObject handler, HandlePropertyName name, michael@0: MutableHandleValue fvalp) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: michael@0: return JSObject::getProperty(cx, handler, handler, name, fvalp); michael@0: } michael@0: michael@0: static bool michael@0: GetDerivedTrap(JSContext *cx, HandleObject handler, HandlePropertyName name, michael@0: MutableHandleValue fvalp) michael@0: { michael@0: JS_ASSERT(name == cx->names().has || michael@0: name == cx->names().hasOwn || michael@0: name == cx->names().get || michael@0: name == cx->names().set || michael@0: name == cx->names().keys || michael@0: name == cx->names().iterate); michael@0: michael@0: return JSObject::getProperty(cx, handler, handler, name, fvalp); michael@0: } michael@0: michael@0: static bool michael@0: Trap(JSContext *cx, HandleObject handler, HandleValue fval, unsigned argc, Value* argv, michael@0: MutableHandleValue rval) michael@0: { michael@0: return Invoke(cx, ObjectValue(*handler), fval, argc, argv, rval); michael@0: } michael@0: michael@0: static bool michael@0: Trap1(JSContext *cx, HandleObject handler, HandleValue fval, HandleId id, MutableHandleValue rval) michael@0: { michael@0: rval.set(IdToValue(id)); // Re-use out-param to avoid Rooted overhead. michael@0: JSString *str = ToString(cx, rval); michael@0: if (!str) michael@0: return false; michael@0: rval.setString(str); michael@0: return Trap(cx, handler, fval, 1, rval.address(), rval); michael@0: } michael@0: michael@0: static bool michael@0: Trap2(JSContext *cx, HandleObject handler, HandleValue fval, HandleId id, Value v_, michael@0: MutableHandleValue rval) michael@0: { michael@0: RootedValue v(cx, v_); michael@0: rval.set(IdToValue(id)); // Re-use out-param to avoid Rooted overhead. michael@0: JSString *str = ToString(cx, rval); michael@0: if (!str) michael@0: return false; michael@0: rval.setString(str); michael@0: JS::AutoValueArray<2> argv(cx); michael@0: argv[0].set(rval); michael@0: argv[1].set(v); michael@0: return Trap(cx, handler, fval, 2, argv.begin(), rval); michael@0: } michael@0: michael@0: static bool michael@0: ParsePropertyDescriptorObject(JSContext *cx, HandleObject obj, const Value &v, michael@0: MutableHandle desc, bool complete = false) michael@0: { michael@0: AutoPropDescArrayRooter descs(cx); michael@0: PropDesc *d = descs.append(); michael@0: if (!d || !d->initialize(cx, v)) michael@0: return false; michael@0: if (complete) michael@0: d->complete(); michael@0: desc.object().set(obj); michael@0: desc.value().set(d->hasValue() ? d->value() : UndefinedValue()); michael@0: desc.setAttributes(d->attributes()); michael@0: desc.setGetter(d->getter()); michael@0: desc.setSetter(d->setter()); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: IndicatePropertyNotFound(MutableHandle desc) michael@0: { michael@0: desc.object().set(nullptr); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: ValueToBool(HandleValue v, bool *bp) michael@0: { michael@0: *bp = ToBoolean(v); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: ArrayToIdVector(JSContext *cx, const Value &array, AutoIdVector &props) michael@0: { michael@0: JS_ASSERT(props.length() == 0); michael@0: michael@0: if (array.isPrimitive()) michael@0: return true; michael@0: michael@0: RootedObject obj(cx, &array.toObject()); michael@0: uint32_t length; michael@0: if (!GetLengthProperty(cx, obj, &length)) michael@0: return false; michael@0: michael@0: RootedValue v(cx); michael@0: for (uint32_t n = 0; n < length; ++n) { michael@0: if (!CheckForInterrupt(cx)) michael@0: return false; michael@0: if (!JSObject::getElement(cx, obj, obj, n, &v)) michael@0: return false; michael@0: RootedId id(cx); michael@0: if (!ValueToId(cx, v, &id)) michael@0: return false; michael@0: if (!props.append(id)) michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: namespace { michael@0: michael@0: /* Derived class for all scripted indirect proxy handlers. */ michael@0: class ScriptedIndirectProxyHandler : public BaseProxyHandler michael@0: { michael@0: public: michael@0: ScriptedIndirectProxyHandler(); michael@0: virtual ~ScriptedIndirectProxyHandler(); michael@0: michael@0: /* ES5 Harmony fundamental proxy traps. */ michael@0: virtual bool preventExtensions(JSContext *cx, HandleObject proxy) MOZ_OVERRIDE; michael@0: virtual bool getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id, michael@0: MutableHandle desc) MOZ_OVERRIDE; michael@0: virtual bool getOwnPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id, michael@0: MutableHandle desc) MOZ_OVERRIDE; michael@0: virtual bool defineProperty(JSContext *cx, HandleObject proxy, HandleId id, michael@0: MutableHandle desc) MOZ_OVERRIDE; michael@0: virtual bool getOwnPropertyNames(JSContext *cx, HandleObject proxy, AutoIdVector &props); michael@0: virtual bool delete_(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) MOZ_OVERRIDE; michael@0: virtual bool enumerate(JSContext *cx, HandleObject proxy, AutoIdVector &props) MOZ_OVERRIDE; michael@0: michael@0: /* ES5 Harmony derived proxy traps. */ michael@0: virtual bool has(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) MOZ_OVERRIDE; michael@0: virtual bool hasOwn(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) MOZ_OVERRIDE; michael@0: virtual bool get(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id, michael@0: MutableHandleValue vp) MOZ_OVERRIDE; michael@0: virtual bool set(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id, michael@0: bool strict, MutableHandleValue vp) MOZ_OVERRIDE; michael@0: virtual bool keys(JSContext *cx, HandleObject proxy, AutoIdVector &props) MOZ_OVERRIDE; michael@0: virtual bool iterate(JSContext *cx, HandleObject proxy, unsigned flags, michael@0: MutableHandleValue vp) MOZ_OVERRIDE; michael@0: michael@0: /* Spidermonkey extensions. */ michael@0: virtual bool isExtensible(JSContext *cx, HandleObject proxy, bool *extensible) MOZ_OVERRIDE; michael@0: virtual bool call(JSContext *cx, HandleObject proxy, const CallArgs &args) MOZ_OVERRIDE; michael@0: virtual bool construct(JSContext *cx, HandleObject proxy, const CallArgs &args) MOZ_OVERRIDE; michael@0: virtual bool nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl, michael@0: CallArgs args) MOZ_OVERRIDE; michael@0: virtual JSString *fun_toString(JSContext *cx, HandleObject proxy, unsigned indent) MOZ_OVERRIDE; michael@0: virtual bool isScripted() MOZ_OVERRIDE { return true; } michael@0: michael@0: static ScriptedIndirectProxyHandler singleton; michael@0: }; michael@0: michael@0: /* michael@0: * Old-style indirect proxies allow callers to specify distinct scripted michael@0: * [[Call]] and [[Construct]] traps. We use an intermediate object so that we michael@0: * can stash this information in a single reserved slot on the proxy object. michael@0: * michael@0: * Note - Currently this is slightly unnecesary, because we actually have 2 michael@0: * extra slots, neither of which are used for ScriptedIndirectProxy. But we're michael@0: * eventually moving towards eliminating one of those slots, and so we don't michael@0: * want to add a dependency here. michael@0: */ michael@0: static Class CallConstructHolder = { michael@0: "CallConstructHolder", michael@0: JSCLASS_HAS_RESERVED_SLOTS(2) | JSCLASS_IS_ANONYMOUS michael@0: }; michael@0: michael@0: } /* anonymous namespace */ michael@0: michael@0: // This variable exists solely to provide a unique address for use as an identifier. michael@0: static const char sScriptedIndirectProxyHandlerFamily = 0; michael@0: michael@0: ScriptedIndirectProxyHandler::ScriptedIndirectProxyHandler() michael@0: : BaseProxyHandler(&sScriptedIndirectProxyHandlerFamily) michael@0: { michael@0: } michael@0: michael@0: ScriptedIndirectProxyHandler::~ScriptedIndirectProxyHandler() michael@0: { michael@0: } michael@0: michael@0: bool michael@0: ScriptedIndirectProxyHandler::isExtensible(JSContext *cx, HandleObject proxy, bool *extensible) michael@0: { michael@0: // Scripted indirect proxies don't support extensibility changes. michael@0: *extensible = true; michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: ScriptedIndirectProxyHandler::preventExtensions(JSContext *cx, HandleObject proxy) michael@0: { michael@0: // See above. michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_CHANGE_EXTENSIBILITY); michael@0: return false; michael@0: } michael@0: michael@0: static bool michael@0: ReturnedValueMustNotBePrimitive(JSContext *cx, HandleObject proxy, JSAtom *atom, const Value &v) michael@0: { michael@0: if (v.isPrimitive()) { michael@0: JSAutoByteString bytes; michael@0: if (AtomToPrintableString(cx, atom, &bytes)) { michael@0: RootedValue val(cx, ObjectOrNullValue(proxy)); michael@0: js_ReportValueError2(cx, JSMSG_BAD_TRAP_RETURN_VALUE, michael@0: JSDVG_SEARCH_STACK, val, js::NullPtr(), bytes.ptr()); michael@0: } michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: static JSObject * michael@0: GetIndirectProxyHandlerObject(JSObject *proxy) michael@0: { michael@0: return proxy->as().private_().toObjectOrNull(); michael@0: } michael@0: michael@0: bool michael@0: ScriptedIndirectProxyHandler::getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id, michael@0: MutableHandle desc) michael@0: { michael@0: RootedObject handler(cx, GetIndirectProxyHandlerObject(proxy)); michael@0: RootedValue fval(cx), value(cx); michael@0: return GetFundamentalTrap(cx, handler, cx->names().getPropertyDescriptor, &fval) && michael@0: Trap1(cx, handler, fval, id, &value) && michael@0: ((value.get().isUndefined() && IndicatePropertyNotFound(desc)) || michael@0: (ReturnedValueMustNotBePrimitive(cx, proxy, cx->names().getPropertyDescriptor, value) && michael@0: ParsePropertyDescriptorObject(cx, proxy, value, desc))); michael@0: } michael@0: michael@0: bool michael@0: ScriptedIndirectProxyHandler::getOwnPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id, michael@0: MutableHandle desc) michael@0: { michael@0: RootedObject handler(cx, GetIndirectProxyHandlerObject(proxy)); michael@0: RootedValue fval(cx), value(cx); michael@0: return GetFundamentalTrap(cx, handler, cx->names().getOwnPropertyDescriptor, &fval) && michael@0: Trap1(cx, handler, fval, id, &value) && michael@0: ((value.get().isUndefined() && IndicatePropertyNotFound(desc)) || michael@0: (ReturnedValueMustNotBePrimitive(cx, proxy, cx->names().getPropertyDescriptor, value) && michael@0: ParsePropertyDescriptorObject(cx, proxy, value, desc))); michael@0: } michael@0: michael@0: bool michael@0: ScriptedIndirectProxyHandler::defineProperty(JSContext *cx, HandleObject proxy, HandleId id, michael@0: MutableHandle desc) michael@0: { michael@0: RootedObject handler(cx, GetIndirectProxyHandlerObject(proxy)); michael@0: RootedValue fval(cx), value(cx); michael@0: return GetFundamentalTrap(cx, handler, cx->names().defineProperty, &fval) && michael@0: NewPropertyDescriptorObject(cx, desc, &value) && michael@0: Trap2(cx, handler, fval, id, value, &value); michael@0: } michael@0: michael@0: bool michael@0: ScriptedIndirectProxyHandler::getOwnPropertyNames(JSContext *cx, HandleObject proxy, michael@0: AutoIdVector &props) michael@0: { michael@0: RootedObject handler(cx, GetIndirectProxyHandlerObject(proxy)); michael@0: RootedValue fval(cx), value(cx); michael@0: return GetFundamentalTrap(cx, handler, cx->names().getOwnPropertyNames, &fval) && michael@0: Trap(cx, handler, fval, 0, nullptr, &value) && michael@0: ArrayToIdVector(cx, value, props); michael@0: } michael@0: michael@0: bool michael@0: ScriptedIndirectProxyHandler::delete_(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) michael@0: { michael@0: RootedObject handler(cx, GetIndirectProxyHandlerObject(proxy)); michael@0: RootedValue fval(cx), value(cx); michael@0: return GetFundamentalTrap(cx, handler, cx->names().delete_, &fval) && michael@0: Trap1(cx, handler, fval, id, &value) && michael@0: ValueToBool(value, bp); michael@0: } michael@0: michael@0: bool michael@0: ScriptedIndirectProxyHandler::enumerate(JSContext *cx, HandleObject proxy, AutoIdVector &props) michael@0: { michael@0: RootedObject handler(cx, GetIndirectProxyHandlerObject(proxy)); michael@0: RootedValue fval(cx), value(cx); michael@0: return GetFundamentalTrap(cx, handler, cx->names().enumerate, &fval) && michael@0: Trap(cx, handler, fval, 0, nullptr, &value) && michael@0: ArrayToIdVector(cx, value, props); michael@0: } michael@0: michael@0: bool michael@0: ScriptedIndirectProxyHandler::has(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) michael@0: { michael@0: RootedObject handler(cx, GetIndirectProxyHandlerObject(proxy)); michael@0: RootedValue fval(cx), value(cx); michael@0: if (!GetDerivedTrap(cx, handler, cx->names().has, &fval)) michael@0: return false; michael@0: if (!js_IsCallable(fval)) michael@0: return BaseProxyHandler::has(cx, proxy, id, bp); michael@0: return Trap1(cx, handler, fval, id, &value) && michael@0: ValueToBool(value, bp); michael@0: } michael@0: michael@0: bool michael@0: ScriptedIndirectProxyHandler::hasOwn(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) michael@0: { michael@0: RootedObject handler(cx, GetIndirectProxyHandlerObject(proxy)); michael@0: RootedValue fval(cx), value(cx); michael@0: if (!GetDerivedTrap(cx, handler, cx->names().hasOwn, &fval)) michael@0: return false; michael@0: if (!js_IsCallable(fval)) michael@0: return BaseProxyHandler::hasOwn(cx, proxy, id, bp); michael@0: return Trap1(cx, handler, fval, id, &value) && michael@0: ValueToBool(value, bp); michael@0: } michael@0: michael@0: bool michael@0: ScriptedIndirectProxyHandler::get(JSContext *cx, HandleObject proxy, HandleObject receiver, michael@0: HandleId id, MutableHandleValue vp) michael@0: { michael@0: RootedObject handler(cx, GetIndirectProxyHandlerObject(proxy)); michael@0: RootedValue idv(cx, IdToValue(id)); michael@0: JSString *str = ToString(cx, idv); michael@0: if (!str) michael@0: return false; michael@0: RootedValue value(cx, StringValue(str)); michael@0: JS::AutoValueArray<2> argv(cx); michael@0: argv[0].setObjectOrNull(receiver); michael@0: argv[1].set(value); michael@0: RootedValue fval(cx); michael@0: if (!GetDerivedTrap(cx, handler, cx->names().get, &fval)) michael@0: return false; michael@0: if (!js_IsCallable(fval)) michael@0: return BaseProxyHandler::get(cx, proxy, receiver, id, vp); michael@0: return Trap(cx, handler, fval, 2, argv.begin(), vp); michael@0: } michael@0: michael@0: bool michael@0: ScriptedIndirectProxyHandler::set(JSContext *cx, HandleObject proxy, HandleObject receiver, michael@0: HandleId id, bool strict, MutableHandleValue vp) michael@0: { michael@0: RootedObject handler(cx, GetIndirectProxyHandlerObject(proxy)); michael@0: RootedValue idv(cx, IdToValue(id)); michael@0: JSString *str = ToString(cx, idv); michael@0: if (!str) michael@0: return false; michael@0: RootedValue value(cx, StringValue(str)); michael@0: JS::AutoValueArray<3> argv(cx); michael@0: argv[0].setObjectOrNull(receiver); michael@0: argv[1].set(value); michael@0: argv[2].set(vp); michael@0: RootedValue fval(cx); michael@0: if (!GetDerivedTrap(cx, handler, cx->names().set, &fval)) michael@0: return false; michael@0: if (!js_IsCallable(fval)) michael@0: return BaseProxyHandler::set(cx, proxy, receiver, id, strict, vp); michael@0: return Trap(cx, handler, fval, 3, argv.begin(), &value); michael@0: } michael@0: michael@0: bool michael@0: ScriptedIndirectProxyHandler::keys(JSContext *cx, HandleObject proxy, AutoIdVector &props) michael@0: { michael@0: RootedObject handler(cx, GetIndirectProxyHandlerObject(proxy)); michael@0: RootedValue value(cx); michael@0: if (!GetDerivedTrap(cx, handler, cx->names().keys, &value)) michael@0: return false; michael@0: if (!js_IsCallable(value)) michael@0: return BaseProxyHandler::keys(cx, proxy, props); michael@0: return Trap(cx, handler, value, 0, nullptr, &value) && michael@0: ArrayToIdVector(cx, value, props); michael@0: } michael@0: michael@0: bool michael@0: ScriptedIndirectProxyHandler::iterate(JSContext *cx, HandleObject proxy, unsigned flags, michael@0: MutableHandleValue vp) michael@0: { michael@0: RootedObject handler(cx, GetIndirectProxyHandlerObject(proxy)); michael@0: RootedValue value(cx); michael@0: if (!GetDerivedTrap(cx, handler, cx->names().iterate, &value)) michael@0: return false; michael@0: if (!js_IsCallable(value)) michael@0: return BaseProxyHandler::iterate(cx, proxy, flags, vp); michael@0: return Trap(cx, handler, value, 0, nullptr, vp) && michael@0: ReturnedValueMustNotBePrimitive(cx, proxy, cx->names().iterate, vp); michael@0: } michael@0: michael@0: bool michael@0: ScriptedIndirectProxyHandler::call(JSContext *cx, HandleObject proxy, const CallArgs &args) michael@0: { michael@0: assertEnteredPolicy(cx, proxy, JSID_VOID, CALL); michael@0: RootedObject ccHolder(cx, &proxy->as().extra(0).toObject()); michael@0: JS_ASSERT(ccHolder->getClass() == &CallConstructHolder); michael@0: RootedValue call(cx, ccHolder->getReservedSlot(0)); michael@0: JS_ASSERT(call.isObject() && call.toObject().isCallable()); michael@0: return Invoke(cx, args.thisv(), call, args.length(), args.array(), args.rval()); michael@0: } michael@0: michael@0: bool michael@0: ScriptedIndirectProxyHandler::construct(JSContext *cx, HandleObject proxy, const CallArgs &args) michael@0: { michael@0: assertEnteredPolicy(cx, proxy, JSID_VOID, CALL); michael@0: RootedObject ccHolder(cx, &proxy->as().extra(0).toObject()); michael@0: JS_ASSERT(ccHolder->getClass() == &CallConstructHolder); michael@0: RootedValue construct(cx, ccHolder->getReservedSlot(1)); michael@0: JS_ASSERT(construct.isObject() && construct.toObject().isCallable()); michael@0: return InvokeConstructor(cx, construct, args.length(), args.array(), michael@0: args.rval().address()); michael@0: } michael@0: michael@0: bool michael@0: ScriptedIndirectProxyHandler::nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl, michael@0: CallArgs args) michael@0: { michael@0: return BaseProxyHandler::nativeCall(cx, test, impl, args); michael@0: } michael@0: michael@0: JSString * michael@0: ScriptedIndirectProxyHandler::fun_toString(JSContext *cx, HandleObject proxy, unsigned indent) michael@0: { michael@0: assertEnteredPolicy(cx, proxy, JSID_VOID, GET); michael@0: if (!proxy->isCallable()) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, michael@0: JSMSG_INCOMPATIBLE_PROTO, michael@0: js_Function_str, js_toString_str, michael@0: "object"); michael@0: return nullptr; michael@0: } michael@0: RootedObject obj(cx, &proxy->as().extra(0).toObject().getReservedSlot(0).toObject()); michael@0: return fun_toStringHelper(cx, obj, indent); michael@0: } michael@0: michael@0: ScriptedIndirectProxyHandler ScriptedIndirectProxyHandler::singleton; michael@0: michael@0: /* Derived class for all scripted direct proxy handlers. */ michael@0: class ScriptedDirectProxyHandler : public DirectProxyHandler { michael@0: public: michael@0: ScriptedDirectProxyHandler(); michael@0: virtual ~ScriptedDirectProxyHandler(); michael@0: michael@0: /* ES5 Harmony fundamental proxy traps. */ michael@0: virtual bool preventExtensions(JSContext *cx, HandleObject proxy) MOZ_OVERRIDE; michael@0: virtual bool getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id, michael@0: MutableHandle desc) MOZ_OVERRIDE; michael@0: virtual bool getOwnPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id, michael@0: MutableHandle desc) MOZ_OVERRIDE; michael@0: virtual bool defineProperty(JSContext *cx, HandleObject proxy, HandleId id, michael@0: MutableHandle desc) MOZ_OVERRIDE; michael@0: virtual bool getOwnPropertyNames(JSContext *cx, HandleObject proxy, AutoIdVector &props); michael@0: virtual bool delete_(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) MOZ_OVERRIDE; michael@0: virtual bool enumerate(JSContext *cx, HandleObject proxy, AutoIdVector &props) MOZ_OVERRIDE; michael@0: michael@0: /* ES5 Harmony derived proxy traps. */ michael@0: virtual bool has(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) MOZ_OVERRIDE; michael@0: virtual bool hasOwn(JSContext *cx,HandleObject proxy, HandleId id, bool *bp) MOZ_OVERRIDE; michael@0: virtual bool get(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id, michael@0: MutableHandleValue vp) MOZ_OVERRIDE; michael@0: virtual bool set(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id, michael@0: bool strict, MutableHandleValue vp) MOZ_OVERRIDE; michael@0: virtual bool keys(JSContext *cx, HandleObject proxy, AutoIdVector &props) MOZ_OVERRIDE; michael@0: virtual bool iterate(JSContext *cx, HandleObject proxy, unsigned flags, michael@0: MutableHandleValue vp) MOZ_OVERRIDE; michael@0: michael@0: /* ES6 Harmony traps */ michael@0: virtual bool isExtensible(JSContext *cx, HandleObject proxy, bool *extensible) MOZ_OVERRIDE; michael@0: michael@0: /* Spidermonkey extensions. */ michael@0: virtual bool call(JSContext *cx, HandleObject proxy, const CallArgs &args) MOZ_OVERRIDE; michael@0: virtual bool construct(JSContext *cx, HandleObject proxy, const CallArgs &args) MOZ_OVERRIDE; michael@0: virtual bool isScripted() MOZ_OVERRIDE { return true; } michael@0: michael@0: static ScriptedDirectProxyHandler singleton; michael@0: }; michael@0: michael@0: // This variable exists solely to provide a unique address for use as an identifier. michael@0: static const char sScriptedDirectProxyHandlerFamily = 0; michael@0: michael@0: // Aux.2 FromGenericPropertyDescriptor(Desc) michael@0: static bool michael@0: FromGenericPropertyDescriptor(JSContext *cx, PropDesc *desc, MutableHandleValue rval) michael@0: { michael@0: // Aux.2 step 1 michael@0: if (desc->isUndefined()) { michael@0: rval.setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: // steps 3-9 michael@0: if (!desc->makeObject(cx)) michael@0: return false; michael@0: rval.set(desc->pd()); michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Aux.3 NormalizePropertyDescriptor(Attributes) michael@0: * michael@0: * NOTE: to minimize code duplication, the code for this function is shared with michael@0: * that for Aux.4 NormalizeAndCompletePropertyDescriptor (see below). The michael@0: * argument complete is used to distinguish between the two. michael@0: */ michael@0: static bool michael@0: NormalizePropertyDescriptor(JSContext *cx, MutableHandleValue vp, bool complete = false) michael@0: { michael@0: // Aux.4 step 1 michael@0: if (complete && vp.isUndefined()) michael@0: return true; michael@0: michael@0: // Aux.3 steps 1-2 / Aux.4 steps 2-3 michael@0: AutoPropDescArrayRooter descs(cx); michael@0: PropDesc *desc = descs.append(); michael@0: if (!desc || !desc->initialize(cx, vp.get())) michael@0: return false; michael@0: if (complete) michael@0: desc->complete(); michael@0: JS_ASSERT(!vp.isPrimitive()); // due to desc->initialize michael@0: RootedObject attributes(cx, &vp.toObject()); michael@0: michael@0: /* michael@0: * Aux.3 step 3 / Aux.4 step 4 michael@0: * michael@0: * NOTE: Aux.4 step 4 actually specifies FromPropertyDescriptor here. michael@0: * However, the way FromPropertyDescriptor is implemented (PropDesc:: michael@0: * makeObject) is actually closer to FromGenericPropertyDescriptor, michael@0: * and is in fact used to implement the latter, so we might as well call it michael@0: * directly. michael@0: */ michael@0: if (!FromGenericPropertyDescriptor(cx, desc, vp)) michael@0: return false; michael@0: if (vp.isUndefined()) michael@0: return true; michael@0: RootedObject descObj(cx, &vp.toObject()); michael@0: michael@0: // Aux.3 steps 4-5 / Aux.4 steps 5-6 michael@0: AutoIdVector props(cx); michael@0: if (!GetPropertyNames(cx, attributes, 0, &props)) michael@0: return false; michael@0: size_t n = props.length(); michael@0: for (size_t i = 0; i < n; ++i) { michael@0: RootedId id(cx, props[i]); michael@0: if (JSID_IS_ATOM(id)) { michael@0: JSAtom *atom = JSID_TO_ATOM(id); michael@0: const JSAtomState &atomState = cx->names(); michael@0: if (atom == atomState.value || atom == atomState.writable || michael@0: atom == atomState.get || atom == atomState.set || michael@0: atom == atomState.enumerable || atom == atomState.configurable) michael@0: { michael@0: continue; michael@0: } michael@0: } michael@0: michael@0: RootedValue v(cx); michael@0: if (!JSObject::getGeneric(cx, descObj, attributes, id, &v)) michael@0: return false; michael@0: if (!JSObject::defineGeneric(cx, descObj, id, v, nullptr, nullptr, JSPROP_ENUMERATE)) michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: // Aux.4 NormalizeAndCompletePropertyDescriptor(Attributes) michael@0: static inline bool michael@0: NormalizeAndCompletePropertyDescriptor(JSContext *cx, MutableHandleValue vp) michael@0: { michael@0: return NormalizePropertyDescriptor(cx, vp, true); michael@0: } michael@0: michael@0: static inline bool michael@0: IsDataDescriptor(const PropertyDescriptor &desc) michael@0: { michael@0: return desc.obj && !(desc.attrs & (JSPROP_GETTER | JSPROP_SETTER)); michael@0: } michael@0: michael@0: static inline bool michael@0: IsAccessorDescriptor(const PropertyDescriptor &desc) michael@0: { michael@0: return desc.obj && desc.attrs & (JSPROP_GETTER | JSPROP_SETTER); michael@0: } michael@0: michael@0: // Aux.5 ValidateProperty(O, P, Desc) michael@0: static bool michael@0: ValidateProperty(JSContext *cx, HandleObject obj, HandleId id, PropDesc *desc, bool *bp) michael@0: { michael@0: // step 1 michael@0: Rooted current(cx); michael@0: if (!GetOwnPropertyDescriptor(cx, obj, id, ¤t)) michael@0: return false; michael@0: michael@0: /* michael@0: * steps 2-4 are redundant since ValidateProperty is never called unless michael@0: * target.[[HasOwn]](P) is true michael@0: */ michael@0: JS_ASSERT(current.object()); michael@0: michael@0: // step 5 michael@0: if (!desc->hasValue() && !desc->hasWritable() && !desc->hasGet() && !desc->hasSet() && michael@0: !desc->hasEnumerable() && !desc->hasConfigurable()) michael@0: { michael@0: *bp = true; michael@0: return true; michael@0: } michael@0: michael@0: // step 6 michael@0: if ((!desc->hasWritable() || desc->writable() == !current.isReadonly()) && michael@0: (!desc->hasGet() || desc->getter() == current.getter()) && michael@0: (!desc->hasSet() || desc->setter() == current.setter()) && michael@0: (!desc->hasEnumerable() || desc->enumerable() == current.isEnumerable()) && michael@0: (!desc->hasConfigurable() || desc->configurable() == !current.isPermanent())) michael@0: { michael@0: if (!desc->hasValue()) { michael@0: *bp = true; michael@0: return true; michael@0: } michael@0: bool same = false; michael@0: if (!SameValue(cx, desc->value(), current.value(), &same)) michael@0: return false; michael@0: if (same) { michael@0: *bp = true; michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: // step 7 michael@0: if (current.isPermanent()) { michael@0: if (desc->hasConfigurable() && desc->configurable()) { michael@0: *bp = false; michael@0: return true; michael@0: } michael@0: michael@0: if (desc->hasEnumerable() && michael@0: desc->enumerable() != current.isEnumerable()) michael@0: { michael@0: *bp = false; michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: // step 8 michael@0: if (desc->isGenericDescriptor()) { michael@0: *bp = true; michael@0: return true; michael@0: } michael@0: michael@0: // step 9 michael@0: if (IsDataDescriptor(current) != desc->isDataDescriptor()) { michael@0: *bp = !current.isPermanent(); michael@0: return true; michael@0: } michael@0: michael@0: // step 10 michael@0: if (IsDataDescriptor(current)) { michael@0: JS_ASSERT(desc->isDataDescriptor()); // by step 9 michael@0: if (current.isPermanent() && current.isReadonly()) { michael@0: if (desc->hasWritable() && desc->writable()) { michael@0: *bp = false; michael@0: return true; michael@0: } michael@0: michael@0: if (desc->hasValue()) { michael@0: bool same; michael@0: if (!SameValue(cx, desc->value(), current.value(), &same)) michael@0: return false; michael@0: if (!same) { michael@0: *bp = false; michael@0: return true; michael@0: } michael@0: } michael@0: } michael@0: michael@0: *bp = true; michael@0: return true; michael@0: } michael@0: michael@0: // steps 11-12 michael@0: JS_ASSERT(IsAccessorDescriptor(current)); // by step 10 michael@0: JS_ASSERT(desc->isAccessorDescriptor()); // by step 9 michael@0: *bp = (!current.isPermanent() || michael@0: ((!desc->hasSet() || desc->setter() == current.setter()) && michael@0: (!desc->hasGet() || desc->getter() == current.getter()))); michael@0: return true; michael@0: } michael@0: michael@0: // Aux.6 IsSealed(O, P) michael@0: static bool michael@0: IsSealed(JSContext* cx, HandleObject obj, HandleId id, bool *bp) michael@0: { michael@0: // step 1 michael@0: Rooted desc(cx); michael@0: if (!GetOwnPropertyDescriptor(cx, obj, id, &desc)) michael@0: return false; michael@0: michael@0: // steps 2-3 michael@0: *bp = desc.object() && desc.isPermanent(); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: HasOwn(JSContext *cx, HandleObject obj, HandleId id, bool *bp) michael@0: { michael@0: Rooted desc(cx); michael@0: if (!JS_GetPropertyDescriptorById(cx, obj, id, &desc)) michael@0: return false; michael@0: *bp = (desc.object() == obj); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: IdToExposableValue(JSContext *cx, HandleId id, MutableHandleValue value) michael@0: { michael@0: value.set(IdToValue(id)); // Re-use out-param to avoid Rooted overhead. michael@0: JSString *name = ToString(cx, value); michael@0: if (!name) michael@0: return false; michael@0: value.set(StringValue(name)); michael@0: return true; michael@0: } michael@0: michael@0: // Get the [[ProxyHandler]] of a scripted direct proxy. michael@0: // michael@0: // NB: This *must* stay synched with proxy(). michael@0: static JSObject * michael@0: GetDirectProxyHandlerObject(JSObject *proxy) michael@0: { michael@0: JS_ASSERT(proxy->as().handler() == &ScriptedDirectProxyHandler::singleton); michael@0: return proxy->as().extra(0).toObjectOrNull(); michael@0: } michael@0: michael@0: // TrapGetOwnProperty(O, P) michael@0: static bool michael@0: TrapGetOwnProperty(JSContext *cx, HandleObject proxy, HandleId id, MutableHandleValue rval) michael@0: { michael@0: // step 1 michael@0: RootedObject handler(cx, GetDirectProxyHandlerObject(proxy)); michael@0: michael@0: // step 2 michael@0: RootedObject target(cx, proxy->as().target()); michael@0: michael@0: // step 3 michael@0: RootedValue trap(cx); michael@0: if (!JSObject::getProperty(cx, handler, handler, cx->names().getOwnPropertyDescriptor, &trap)) michael@0: return false; michael@0: michael@0: // step 4 michael@0: if (trap.isUndefined()) { michael@0: Rooted desc(cx); michael@0: if (!GetOwnPropertyDescriptor(cx, target, id, &desc)) michael@0: return false; michael@0: return NewPropertyDescriptorObject(cx, desc, rval); michael@0: } michael@0: michael@0: // step 5 michael@0: RootedValue value(cx); michael@0: if (!IdToExposableValue(cx, id, &value)) michael@0: return false; michael@0: Value argv[] = { michael@0: ObjectValue(*target), michael@0: value michael@0: }; michael@0: RootedValue trapResult(cx); michael@0: if (!Invoke(cx, ObjectValue(*handler), trap, ArrayLength(argv), argv, &trapResult)) michael@0: return false; michael@0: michael@0: // step 6 michael@0: if (!NormalizeAndCompletePropertyDescriptor(cx, &trapResult)) michael@0: return false; michael@0: michael@0: // step 7 michael@0: if (trapResult.isUndefined()) { michael@0: bool sealed; michael@0: if (!IsSealed(cx, target, id, &sealed)) michael@0: return false; michael@0: if (sealed) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_REPORT_NC_AS_NE); michael@0: return false; michael@0: } michael@0: michael@0: bool extensible; michael@0: if (!JSObject::isExtensible(cx, target, &extensible)) michael@0: return false; michael@0: if (!extensible) { michael@0: bool found; michael@0: if (!HasOwn(cx, target, id, &found)) michael@0: return false; michael@0: if (found) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_REPORT_E_AS_NE); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: rval.set(UndefinedValue()); michael@0: return true; michael@0: } michael@0: michael@0: // step 8 michael@0: bool isFixed; michael@0: if (!HasOwn(cx, target, id, &isFixed)) michael@0: return false; michael@0: michael@0: // step 9 michael@0: bool extensible; michael@0: if (!JSObject::isExtensible(cx, target, &extensible)) michael@0: return false; michael@0: if (!extensible && !isFixed) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_REPORT_NEW); michael@0: return false; michael@0: } michael@0: michael@0: AutoPropDescArrayRooter descs(cx); michael@0: PropDesc *desc = descs.append(); michael@0: if (!desc || !desc->initialize(cx, trapResult)) michael@0: return false; michael@0: michael@0: /* step 10 */ michael@0: if (isFixed) { michael@0: bool valid; michael@0: if (!ValidateProperty(cx, target, id, desc, &valid)) michael@0: return false; michael@0: michael@0: if (!valid) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_REPORT_INVALID); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: // step 11 michael@0: if (!desc->configurable() && !isFixed) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_REPORT_NE_AS_NC); michael@0: return false; michael@0: } michael@0: michael@0: // step 12 michael@0: rval.set(trapResult); michael@0: return true; michael@0: } michael@0: michael@0: // TrapDefineOwnProperty(O, P, DescObj, Throw) michael@0: static bool michael@0: TrapDefineOwnProperty(JSContext *cx, HandleObject proxy, HandleId id, MutableHandleValue vp) michael@0: { michael@0: // step 1 michael@0: RootedObject handler(cx, GetDirectProxyHandlerObject(proxy)); michael@0: michael@0: // step 2 michael@0: RootedObject target(cx, proxy->as().target()); michael@0: michael@0: // step 3 michael@0: RootedValue trap(cx); michael@0: if (!JSObject::getProperty(cx, handler, handler, cx->names().defineProperty, &trap)) michael@0: return false; michael@0: michael@0: // step 4 michael@0: if (trap.isUndefined()) { michael@0: Rooted desc(cx); michael@0: if (!ParsePropertyDescriptorObject(cx, proxy, vp, &desc)) michael@0: return false; michael@0: return JS_DefinePropertyById(cx, target, id, desc.value(), desc.getter(), desc.setter(), michael@0: desc.attributes()); michael@0: } michael@0: michael@0: // step 5 michael@0: RootedValue normalizedDesc(cx, vp); michael@0: if (!NormalizePropertyDescriptor(cx, &normalizedDesc)) michael@0: return false; michael@0: michael@0: // step 6 michael@0: RootedValue value(cx); michael@0: if (!IdToExposableValue(cx, id, &value)) michael@0: return false; michael@0: Value argv[] = { michael@0: ObjectValue(*target), michael@0: value, michael@0: normalizedDesc michael@0: }; michael@0: RootedValue trapResult(cx); michael@0: if (!Invoke(cx, ObjectValue(*handler), trap, ArrayLength(argv), argv, &trapResult)) michael@0: return false; michael@0: michael@0: // steps 7-8 michael@0: if (ToBoolean(trapResult)) { michael@0: bool isFixed; michael@0: if (!HasOwn(cx, target, id, &isFixed)) michael@0: return false; michael@0: michael@0: bool extensible; michael@0: if (!JSObject::isExtensible(cx, target, &extensible)) michael@0: return false; michael@0: if (!extensible && !isFixed) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_DEFINE_NEW); michael@0: return false; michael@0: } michael@0: michael@0: AutoPropDescArrayRooter descs(cx); michael@0: PropDesc *desc = descs.append(); michael@0: if (!desc || !desc->initialize(cx, normalizedDesc)) michael@0: return false; michael@0: michael@0: if (isFixed) { michael@0: bool valid; michael@0: if (!ValidateProperty(cx, target, id, desc, &valid)) michael@0: return false; michael@0: if (!valid) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_DEFINE_INVALID); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: if (!desc->configurable() && !isFixed) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_DEFINE_NE_AS_NC); michael@0: return false; michael@0: } michael@0: michael@0: vp.set(BooleanValue(true)); michael@0: return true; michael@0: } michael@0: michael@0: // step 9 michael@0: // FIXME: API does not include a Throw parameter michael@0: vp.set(BooleanValue(false)); michael@0: return true; michael@0: } michael@0: michael@0: static inline void michael@0: ReportInvalidTrapResult(JSContext *cx, JSObject *proxy, JSAtom *atom) michael@0: { michael@0: RootedValue v(cx, ObjectOrNullValue(proxy)); michael@0: JSAutoByteString bytes; michael@0: if (!AtomToPrintableString(cx, atom, &bytes)) michael@0: return; michael@0: js_ReportValueError2(cx, JSMSG_INVALID_TRAP_RESULT, JSDVG_IGNORE_STACK, v, michael@0: js::NullPtr(), bytes.ptr()); michael@0: } michael@0: michael@0: // This function is shared between getOwnPropertyNames, enumerate, and keys michael@0: static bool michael@0: ArrayToIdVector(JSContext *cx, HandleObject proxy, HandleObject target, HandleValue v, michael@0: AutoIdVector &props, unsigned flags, JSAtom *trapName_) michael@0: { michael@0: JS_ASSERT(v.isObject()); michael@0: RootedObject array(cx, &v.toObject()); michael@0: RootedAtom trapName(cx, trapName_); michael@0: michael@0: // steps g-h michael@0: uint32_t n; michael@0: if (!GetLengthProperty(cx, array, &n)) michael@0: return false; michael@0: michael@0: // steps i-k michael@0: for (uint32_t i = 0; i < n; ++i) { michael@0: // step i michael@0: RootedValue v(cx); michael@0: if (!JSObject::getElement(cx, array, array, i, &v)) michael@0: return false; michael@0: michael@0: // step ii michael@0: RootedId id(cx); michael@0: if (!ValueToId(cx, v, &id)) michael@0: return false; michael@0: michael@0: // step iii michael@0: for (uint32_t j = 0; j < i; ++j) { michael@0: if (props[j] == id) { michael@0: ReportInvalidTrapResult(cx, proxy, trapName); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: // step iv michael@0: bool isFixed; michael@0: if (!HasOwn(cx, target, id, &isFixed)) michael@0: return false; michael@0: michael@0: // step v michael@0: bool extensible; michael@0: if (!JSObject::isExtensible(cx, target, &extensible)) michael@0: return false; michael@0: if (!extensible && !isFixed) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_REPORT_NEW); michael@0: return false; michael@0: } michael@0: michael@0: // step vi michael@0: if (!props.append(id)) michael@0: return false; michael@0: } michael@0: michael@0: // step l michael@0: AutoIdVector ownProps(cx); michael@0: if (!GetPropertyNames(cx, target, flags, &ownProps)) michael@0: return false; michael@0: michael@0: // step m michael@0: for (size_t i = 0; i < ownProps.length(); ++i) { michael@0: RootedId id(cx, ownProps[i]); michael@0: michael@0: bool found = false; michael@0: for (size_t j = 0; j < props.length(); ++j) { michael@0: if (props[j] == id) { michael@0: found = true; michael@0: break; michael@0: } michael@0: } michael@0: if (found) michael@0: continue; michael@0: michael@0: // step i michael@0: bool sealed; michael@0: if (!IsSealed(cx, target, id, &sealed)) michael@0: return false; michael@0: if (sealed) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_SKIP_NC); michael@0: return false; michael@0: } michael@0: michael@0: // step ii michael@0: bool isFixed; michael@0: if (!HasOwn(cx, target, id, &isFixed)) michael@0: return false; michael@0: michael@0: // step iii michael@0: bool extensible; michael@0: if (!JSObject::isExtensible(cx, target, &extensible)) michael@0: return false; michael@0: if (!extensible && isFixed) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_REPORT_E_AS_NE); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: // step n michael@0: return true; michael@0: } michael@0: michael@0: ScriptedDirectProxyHandler::ScriptedDirectProxyHandler() michael@0: : DirectProxyHandler(&sScriptedDirectProxyHandlerFamily) michael@0: { michael@0: } michael@0: michael@0: ScriptedDirectProxyHandler::~ScriptedDirectProxyHandler() michael@0: { michael@0: } michael@0: michael@0: bool michael@0: ScriptedDirectProxyHandler::preventExtensions(JSContext *cx, HandleObject proxy) michael@0: { michael@0: // step a michael@0: RootedObject handler(cx, GetDirectProxyHandlerObject(proxy)); michael@0: michael@0: // step b michael@0: RootedObject target(cx, proxy->as().target()); michael@0: michael@0: // step c michael@0: RootedValue trap(cx); michael@0: if (!JSObject::getProperty(cx, handler, handler, cx->names().preventExtensions, &trap)) michael@0: return false; michael@0: michael@0: // step d michael@0: if (trap.isUndefined()) michael@0: return DirectProxyHandler::preventExtensions(cx, proxy); michael@0: michael@0: // step e michael@0: Value argv[] = { michael@0: ObjectValue(*target) michael@0: }; michael@0: RootedValue trapResult(cx); michael@0: if (!Invoke(cx, ObjectValue(*handler), trap, ArrayLength(argv), argv, &trapResult)) michael@0: return false; michael@0: michael@0: // step f michael@0: bool success = ToBoolean(trapResult); michael@0: if (success) { michael@0: // step g michael@0: bool extensible; michael@0: if (!JSObject::isExtensible(cx, target, &extensible)) michael@0: return false; michael@0: if (extensible) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_REPORT_AS_NON_EXTENSIBLE); michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: // step h michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_CHANGE_EXTENSIBILITY); michael@0: return false; michael@0: } michael@0: michael@0: // FIXME: Move to Proxy::getPropertyDescriptor once ScriptedIndirectProxy is removed michael@0: bool michael@0: ScriptedDirectProxyHandler::getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id, michael@0: MutableHandle desc) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: michael@0: if (!GetOwnPropertyDescriptor(cx, proxy, id, desc)) michael@0: return false; michael@0: if (desc.object()) michael@0: return true; michael@0: RootedObject proto(cx); michael@0: if (!JSObject::getProto(cx, proxy, &proto)) michael@0: return false; michael@0: if (!proto) { michael@0: JS_ASSERT(!desc.object()); michael@0: return true; michael@0: } michael@0: return JS_GetPropertyDescriptorById(cx, proto, id, desc); michael@0: } michael@0: michael@0: bool michael@0: ScriptedDirectProxyHandler::getOwnPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id, michael@0: MutableHandle desc) michael@0: { michael@0: // step 1 michael@0: RootedValue v(cx); michael@0: if (!TrapGetOwnProperty(cx, proxy, id, &v)) michael@0: return false; michael@0: michael@0: // step 2 michael@0: if (v.isUndefined()) { michael@0: desc.object().set(nullptr); michael@0: return true; michael@0: } michael@0: michael@0: // steps 3-4 michael@0: return ParsePropertyDescriptorObject(cx, proxy, v, desc, true); michael@0: } michael@0: michael@0: bool michael@0: ScriptedDirectProxyHandler::defineProperty(JSContext *cx, HandleObject proxy, HandleId id, michael@0: MutableHandle desc) michael@0: { michael@0: // step 1 michael@0: AutoPropDescArrayRooter descs(cx); michael@0: PropDesc *d = descs.append(); michael@0: d->initFromPropertyDescriptor(desc); michael@0: RootedValue v(cx); michael@0: if (!FromGenericPropertyDescriptor(cx, d, &v)) michael@0: return false; michael@0: michael@0: // step 2 michael@0: return TrapDefineOwnProperty(cx, proxy, id, &v); michael@0: } michael@0: michael@0: bool michael@0: ScriptedDirectProxyHandler::getOwnPropertyNames(JSContext *cx, HandleObject proxy, michael@0: AutoIdVector &props) michael@0: { michael@0: // step a michael@0: RootedObject handler(cx, GetDirectProxyHandlerObject(proxy)); michael@0: michael@0: // step b michael@0: RootedObject target(cx, proxy->as().target()); michael@0: michael@0: // step c michael@0: RootedValue trap(cx); michael@0: if (!JSObject::getProperty(cx, handler, handler, cx->names().getOwnPropertyNames, &trap)) michael@0: return false; michael@0: michael@0: // step d michael@0: if (trap.isUndefined()) michael@0: return DirectProxyHandler::getOwnPropertyNames(cx, proxy, props); michael@0: michael@0: // step e michael@0: Value argv[] = { michael@0: ObjectValue(*target) michael@0: }; michael@0: RootedValue trapResult(cx); michael@0: if (!Invoke(cx, ObjectValue(*handler), trap, ArrayLength(argv), argv, &trapResult)) michael@0: return false; michael@0: michael@0: // step f michael@0: if (trapResult.isPrimitive()) { michael@0: ReportInvalidTrapResult(cx, proxy, cx->names().getOwnPropertyNames); michael@0: return false; michael@0: } michael@0: michael@0: // steps g to n are shared michael@0: return ArrayToIdVector(cx, proxy, target, trapResult, props, JSITER_OWNONLY | JSITER_HIDDEN, michael@0: cx->names().getOwnPropertyNames); michael@0: } michael@0: michael@0: // Proxy.[[Delete]](P, Throw) michael@0: bool michael@0: ScriptedDirectProxyHandler::delete_(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) michael@0: { michael@0: // step 1 michael@0: RootedObject handler(cx, GetDirectProxyHandlerObject(proxy)); michael@0: michael@0: // step 2 michael@0: RootedObject target(cx, proxy->as().target()); michael@0: michael@0: // step 3 michael@0: RootedValue trap(cx); michael@0: if (!JSObject::getProperty(cx, handler, handler, cx->names().deleteProperty, &trap)) michael@0: return false; michael@0: michael@0: // step 4 michael@0: if (trap.isUndefined()) michael@0: return DirectProxyHandler::delete_(cx, proxy, id, bp); michael@0: michael@0: // step 5 michael@0: RootedValue value(cx); michael@0: if (!IdToExposableValue(cx, id, &value)) michael@0: return false; michael@0: Value argv[] = { michael@0: ObjectValue(*target), michael@0: value michael@0: }; michael@0: RootedValue trapResult(cx); michael@0: if (!Invoke(cx, ObjectValue(*handler), trap, ArrayLength(argv), argv, &trapResult)) michael@0: return false; michael@0: michael@0: // step 6-7 michael@0: if (ToBoolean(trapResult)) { michael@0: Rooted desc(cx); michael@0: if (!GetOwnPropertyDescriptor(cx, target, id, &desc)) michael@0: return false; michael@0: michael@0: if (desc.object() && desc.isPermanent()) { michael@0: RootedValue v(cx, IdToValue(id)); michael@0: js_ReportValueError(cx, JSMSG_CANT_DELETE, JSDVG_IGNORE_STACK, v, js::NullPtr()); michael@0: return false; michael@0: } michael@0: michael@0: *bp = true; michael@0: return true; michael@0: } michael@0: michael@0: // step 8 michael@0: // FIXME: API does not include a Throw parameter michael@0: *bp = false; michael@0: return true; michael@0: } michael@0: michael@0: // 12.6.4 The for-in Statement, step 6 michael@0: bool michael@0: ScriptedDirectProxyHandler::enumerate(JSContext *cx, HandleObject proxy, AutoIdVector &props) michael@0: { michael@0: // step a michael@0: RootedObject handler(cx, GetDirectProxyHandlerObject(proxy)); michael@0: michael@0: // step b michael@0: RootedObject target(cx, proxy->as().target()); michael@0: michael@0: // step c michael@0: RootedValue trap(cx); michael@0: if (!JSObject::getProperty(cx, handler, handler, cx->names().enumerate, &trap)) michael@0: return false; michael@0: michael@0: // step d michael@0: if (trap.isUndefined()) michael@0: return DirectProxyHandler::enumerate(cx, proxy, props); michael@0: michael@0: // step e michael@0: Value argv[] = { michael@0: ObjectOrNullValue(target) michael@0: }; michael@0: RootedValue trapResult(cx); michael@0: if (!Invoke(cx, ObjectValue(*handler), trap, ArrayLength(argv), argv, &trapResult)) michael@0: return false; michael@0: michael@0: // step f michael@0: if (trapResult.isPrimitive()) { michael@0: JSAutoByteString bytes; michael@0: if (!AtomToPrintableString(cx, cx->names().enumerate, &bytes)) michael@0: return false; michael@0: RootedValue v(cx, ObjectOrNullValue(proxy)); michael@0: js_ReportValueError2(cx, JSMSG_INVALID_TRAP_RESULT, JSDVG_SEARCH_STACK, michael@0: v, js::NullPtr(), bytes.ptr()); michael@0: return false; michael@0: } michael@0: michael@0: // steps g-m are shared michael@0: // FIXME: the trap should return an iterator object, see bug 783826 michael@0: return ArrayToIdVector(cx, proxy, target, trapResult, props, 0, cx->names().enumerate); michael@0: } michael@0: michael@0: // Proxy.[[HasProperty]](P) michael@0: bool michael@0: ScriptedDirectProxyHandler::has(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) michael@0: { michael@0: // step 1 michael@0: RootedObject handler(cx, GetDirectProxyHandlerObject(proxy)); michael@0: michael@0: // step 2 michael@0: RootedObject target(cx, proxy->as().target()); michael@0: michael@0: // step 3 michael@0: RootedValue trap(cx); michael@0: if (!JSObject::getProperty(cx, handler, handler, cx->names().has, &trap)) michael@0: return false; michael@0: michael@0: // step 4 michael@0: if (trap.isUndefined()) michael@0: return DirectProxyHandler::has(cx, proxy, id, bp); michael@0: michael@0: // step 5 michael@0: RootedValue value(cx); michael@0: if (!IdToExposableValue(cx, id, &value)) michael@0: return false; michael@0: Value argv[] = { michael@0: ObjectOrNullValue(target), michael@0: value michael@0: }; michael@0: RootedValue trapResult(cx); michael@0: if (!Invoke(cx, ObjectValue(*handler), trap, ArrayLength(argv), argv, &trapResult)) michael@0: return false; michael@0: michael@0: // step 6 michael@0: bool success = ToBoolean(trapResult);; michael@0: michael@0: // step 7 michael@0: if (!success) { michael@0: Rooted desc(cx); michael@0: if (!GetOwnPropertyDescriptor(cx, target, id, &desc)) michael@0: return false; michael@0: michael@0: if (desc.object()) { michael@0: if (desc.isPermanent()) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_REPORT_NC_AS_NE); michael@0: return false; michael@0: } michael@0: michael@0: bool extensible; michael@0: if (!JSObject::isExtensible(cx, target, &extensible)) michael@0: return false; michael@0: if (!extensible) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_REPORT_E_AS_NE); michael@0: return false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // step 8 michael@0: *bp = success; michael@0: return true; michael@0: } michael@0: michael@0: // Proxy.[[HasOwnProperty]](P) michael@0: bool michael@0: ScriptedDirectProxyHandler::hasOwn(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) michael@0: { michael@0: // step 1 michael@0: RootedObject handler(cx, GetDirectProxyHandlerObject(proxy)); michael@0: michael@0: // step 2 michael@0: RootedObject target(cx, proxy->as().target()); michael@0: michael@0: // step 3 michael@0: RootedValue trap(cx); michael@0: if (!JSObject::getProperty(cx, handler, handler, cx->names().hasOwn, &trap)) michael@0: return false; michael@0: michael@0: // step 4 michael@0: if (trap.isUndefined()) michael@0: return DirectProxyHandler::hasOwn(cx, proxy, id, bp); michael@0: michael@0: // step 5 michael@0: RootedValue value(cx); michael@0: if (!IdToExposableValue(cx, id, &value)) michael@0: return false; michael@0: Value argv[] = { michael@0: ObjectOrNullValue(target), michael@0: value michael@0: }; michael@0: RootedValue trapResult(cx); michael@0: if (!Invoke(cx, ObjectValue(*handler), trap, ArrayLength(argv), argv, &trapResult)) michael@0: return false; michael@0: michael@0: // step 6 michael@0: bool success = ToBoolean(trapResult); michael@0: michael@0: // steps 7-8 michael@0: if (!success) { michael@0: bool sealed; michael@0: if (!IsSealed(cx, target, id, &sealed)) michael@0: return false; michael@0: if (sealed) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_REPORT_NC_AS_NE); michael@0: return false; michael@0: } michael@0: michael@0: bool extensible; michael@0: if (!JSObject::isExtensible(cx, target, &extensible)) michael@0: return false; michael@0: if (!extensible) { michael@0: bool isFixed; michael@0: if (!HasOwn(cx, target, id, &isFixed)) michael@0: return false; michael@0: if (isFixed) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_REPORT_E_AS_NE); michael@0: return false; michael@0: } michael@0: } michael@0: } else { michael@0: bool extensible; michael@0: if (!JSObject::isExtensible(cx, target, &extensible)) michael@0: return false; michael@0: if (!extensible) { michael@0: bool isFixed; michael@0: if (!HasOwn(cx, target, id, &isFixed)) michael@0: return false; michael@0: if (!isFixed) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_REPORT_NEW); michael@0: return false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // step 9 michael@0: *bp = !!success; michael@0: return true; michael@0: } michael@0: michael@0: // Proxy.[[GetP]](P, Receiver) michael@0: bool michael@0: ScriptedDirectProxyHandler::get(JSContext *cx, HandleObject proxy, HandleObject receiver, michael@0: HandleId id, MutableHandleValue vp) michael@0: { michael@0: // step 1 michael@0: RootedObject handler(cx, GetDirectProxyHandlerObject(proxy)); michael@0: michael@0: // step 2 michael@0: RootedObject target(cx, proxy->as().target()); michael@0: michael@0: // step 3 michael@0: RootedValue trap(cx); michael@0: if (!JSObject::getProperty(cx, handler, handler, cx->names().get, &trap)) michael@0: return false; michael@0: michael@0: // step 4 michael@0: if (trap.isUndefined()) michael@0: return DirectProxyHandler::get(cx, proxy, receiver, id, vp); michael@0: michael@0: // step 5 michael@0: RootedValue value(cx); michael@0: if (!IdToExposableValue(cx, id, &value)) michael@0: return false; michael@0: Value argv[] = { michael@0: ObjectOrNullValue(target), michael@0: value, michael@0: ObjectOrNullValue(receiver) michael@0: }; michael@0: RootedValue trapResult(cx); michael@0: if (!Invoke(cx, ObjectValue(*handler), trap, ArrayLength(argv), argv, &trapResult)) michael@0: return false; michael@0: michael@0: // step 6 michael@0: Rooted desc(cx); michael@0: if (!GetOwnPropertyDescriptor(cx, target, id, &desc)) michael@0: return false; michael@0: michael@0: // step 7 michael@0: if (desc.object()) { michael@0: if (IsDataDescriptor(desc) && desc.isPermanent() && desc.isReadonly()) { michael@0: bool same; michael@0: if (!SameValue(cx, trapResult, desc.value(), &same)) michael@0: return false; michael@0: if (!same) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MUST_REPORT_SAME_VALUE); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: if (IsAccessorDescriptor(desc) && desc.isPermanent() && !desc.hasGetterObject()) { michael@0: if (!trapResult.isUndefined()) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MUST_REPORT_UNDEFINED); michael@0: return false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // step 8 michael@0: vp.set(trapResult); michael@0: return true; michael@0: } michael@0: michael@0: // Proxy.[[SetP]](P, V, Receiver) michael@0: bool michael@0: ScriptedDirectProxyHandler::set(JSContext *cx, HandleObject proxy, HandleObject receiver, michael@0: HandleId id, bool strict, MutableHandleValue vp) michael@0: { michael@0: // step 1 michael@0: RootedObject handler(cx, GetDirectProxyHandlerObject(proxy)); michael@0: michael@0: // step 2 michael@0: RootedObject target(cx, proxy->as().target()); michael@0: michael@0: // step 3 michael@0: RootedValue trap(cx); michael@0: if (!JSObject::getProperty(cx, handler, handler, cx->names().set, &trap)) michael@0: return false; michael@0: michael@0: // step 4 michael@0: if (trap.isUndefined()) michael@0: return DirectProxyHandler::set(cx, proxy, receiver, id, strict, vp); michael@0: michael@0: // step 5 michael@0: RootedValue value(cx); michael@0: if (!IdToExposableValue(cx, id, &value)) michael@0: return false; michael@0: Value argv[] = { michael@0: ObjectOrNullValue(target), michael@0: value, michael@0: vp.get(), michael@0: ObjectValue(*receiver) michael@0: }; michael@0: RootedValue trapResult(cx); michael@0: if (!Invoke(cx, ObjectValue(*handler), trap, ArrayLength(argv), argv, &trapResult)) michael@0: return false; michael@0: michael@0: // step 6 michael@0: bool success = ToBoolean(trapResult); michael@0: michael@0: // step 7 michael@0: if (success) { michael@0: Rooted desc(cx); michael@0: if (!GetOwnPropertyDescriptor(cx, target, id, &desc)) michael@0: return false; michael@0: michael@0: if (desc.object()) { michael@0: if (IsDataDescriptor(desc) && desc.isPermanent() && desc.isReadonly()) { michael@0: bool same; michael@0: if (!SameValue(cx, vp, desc.value(), &same)) michael@0: return false; michael@0: if (!same) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_SET_NW_NC); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: if (IsAccessorDescriptor(desc) && desc.isPermanent() && !desc.hasSetterObject()) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_SET_WO_SETTER); michael@0: return false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // step 8 michael@0: vp.set(BooleanValue(success)); michael@0: return true; michael@0: } michael@0: michael@0: // 15.2.3.14 Object.keys (O), step 2 michael@0: bool michael@0: ScriptedDirectProxyHandler::keys(JSContext *cx, HandleObject proxy, AutoIdVector &props) michael@0: { michael@0: // step a michael@0: RootedObject handler(cx, GetDirectProxyHandlerObject(proxy)); michael@0: michael@0: // step b michael@0: RootedObject target(cx, proxy->as().target()); michael@0: michael@0: // step c michael@0: RootedValue trap(cx); michael@0: if (!JSObject::getProperty(cx, handler, handler, cx->names().keys, &trap)) michael@0: return false; michael@0: michael@0: // step d michael@0: if (trap.isUndefined()) michael@0: return DirectProxyHandler::keys(cx, proxy, props); michael@0: michael@0: // step e michael@0: Value argv[] = { michael@0: ObjectOrNullValue(target) michael@0: }; michael@0: RootedValue trapResult(cx); michael@0: if (!Invoke(cx, ObjectValue(*handler), trap, ArrayLength(argv), argv, &trapResult)) michael@0: return false; michael@0: michael@0: // step f michael@0: if (trapResult.isPrimitive()) { michael@0: JSAutoByteString bytes; michael@0: if (!AtomToPrintableString(cx, cx->names().keys, &bytes)) michael@0: return false; michael@0: RootedValue v(cx, ObjectOrNullValue(proxy)); michael@0: js_ReportValueError2(cx, JSMSG_INVALID_TRAP_RESULT, JSDVG_IGNORE_STACK, michael@0: v, js::NullPtr(), bytes.ptr()); michael@0: return false; michael@0: } michael@0: michael@0: // steps g-n are shared michael@0: return ArrayToIdVector(cx, proxy, target, trapResult, props, JSITER_OWNONLY, cx->names().keys); michael@0: } michael@0: michael@0: // ES6 (5 April, 2014) 9.5.3 Proxy.[[IsExtensible]](P) michael@0: bool michael@0: ScriptedDirectProxyHandler::isExtensible(JSContext *cx, HandleObject proxy, bool *extensible) michael@0: { michael@0: RootedObject handler(cx, GetDirectProxyHandlerObject(proxy)); michael@0: michael@0: RootedObject target(cx, proxy->as().target()); michael@0: michael@0: RootedValue trap(cx); michael@0: if (!JSObject::getProperty(cx, handler, handler, cx->names().isExtensible, &trap)) michael@0: return false; michael@0: michael@0: if (trap.isUndefined()) michael@0: return DirectProxyHandler::isExtensible(cx, proxy, extensible); michael@0: michael@0: Value argv[] = { michael@0: ObjectValue(*target) michael@0: }; michael@0: RootedValue trapResult(cx); michael@0: if (!Invoke(cx, ObjectValue(*handler), trap, ArrayLength(argv), argv, &trapResult)) michael@0: return false; michael@0: michael@0: bool booleanTrapResult = ToBoolean(trapResult); michael@0: bool targetResult; michael@0: if (!JSObject::isExtensible(cx, target, &targetResult)) michael@0: return false; michael@0: michael@0: if (targetResult != booleanTrapResult) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_PROXY_EXTENSIBILITY); michael@0: return false; michael@0: } michael@0: michael@0: *extensible = booleanTrapResult; michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: ScriptedDirectProxyHandler::iterate(JSContext *cx, HandleObject proxy, unsigned flags, michael@0: MutableHandleValue vp) michael@0: { michael@0: // FIXME: Provide a proper implementation for this trap, see bug 787004 michael@0: return DirectProxyHandler::iterate(cx, proxy, flags, vp); michael@0: } michael@0: michael@0: bool michael@0: ScriptedDirectProxyHandler::call(JSContext *cx, HandleObject proxy, const CallArgs &args) michael@0: { michael@0: // step 1 michael@0: RootedObject handler(cx, GetDirectProxyHandlerObject(proxy)); michael@0: michael@0: // step 2 michael@0: RootedObject target(cx, proxy->as().target()); michael@0: michael@0: /* michael@0: * NB: Remember to throw a TypeError here if we change NewProxyObject so that this trap can get michael@0: * called for non-callable objects michael@0: */ michael@0: michael@0: // step 3 michael@0: RootedObject argsArray(cx, NewDenseCopiedArray(cx, args.length(), args.array())); michael@0: if (!argsArray) michael@0: return false; michael@0: michael@0: // step 4 michael@0: RootedValue trap(cx); michael@0: if (!JSObject::getProperty(cx, handler, handler, cx->names().apply, &trap)) michael@0: return false; michael@0: michael@0: // step 5 michael@0: if (trap.isUndefined()) michael@0: return DirectProxyHandler::call(cx, proxy, args); michael@0: michael@0: // step 6 michael@0: Value argv[] = { michael@0: ObjectValue(*target), michael@0: args.thisv(), michael@0: ObjectValue(*argsArray) michael@0: }; michael@0: RootedValue thisValue(cx, ObjectValue(*handler)); michael@0: return Invoke(cx, thisValue, trap, ArrayLength(argv), argv, args.rval()); michael@0: } michael@0: michael@0: bool michael@0: ScriptedDirectProxyHandler::construct(JSContext *cx, HandleObject proxy, const CallArgs &args) michael@0: { michael@0: // step 1 michael@0: RootedObject handler(cx, GetDirectProxyHandlerObject(proxy)); michael@0: michael@0: // step 2 michael@0: RootedObject target(cx, proxy->as().target()); michael@0: michael@0: /* michael@0: * NB: Remember to throw a TypeError here if we change NewProxyObject so that this trap can get michael@0: * called for non-callable objects michael@0: */ michael@0: michael@0: // step 3 michael@0: RootedObject argsArray(cx, NewDenseCopiedArray(cx, args.length(), args.array())); michael@0: if (!argsArray) michael@0: return false; michael@0: michael@0: // step 4 michael@0: RootedValue trap(cx); michael@0: if (!JSObject::getProperty(cx, handler, handler, cx->names().construct, &trap)) michael@0: return false; michael@0: michael@0: // step 5 michael@0: if (trap.isUndefined()) michael@0: return DirectProxyHandler::construct(cx, proxy, args); michael@0: michael@0: // step 6 michael@0: Value constructArgv[] = { michael@0: ObjectValue(*target), michael@0: ObjectValue(*argsArray) michael@0: }; michael@0: RootedValue thisValue(cx, ObjectValue(*handler)); michael@0: if (!Invoke(cx, thisValue, trap, ArrayLength(constructArgv), constructArgv, args.rval())) michael@0: return false; michael@0: if (!args.rval().isObject()) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_PROXY_CONSTRUCT_OBJECT); michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: ScriptedDirectProxyHandler ScriptedDirectProxyHandler::singleton; michael@0: michael@0: #define INVOKE_ON_PROTOTYPE(cx, handler, proxy, protoCall) \ michael@0: JS_BEGIN_MACRO \ michael@0: RootedObject proto(cx); \ michael@0: if (!JSObject::getProto(cx, proxy, &proto)) \ michael@0: return false; \ michael@0: if (!proto) \ michael@0: return true; \ michael@0: assertSameCompartment(cx, proxy, proto); \ michael@0: return protoCall; \ michael@0: JS_END_MACRO \ michael@0: michael@0: bool michael@0: Proxy::getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id, michael@0: MutableHandle desc) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: BaseProxyHandler *handler = proxy->as().handler(); michael@0: desc.object().set(nullptr); // default result if we refuse to perform this action michael@0: AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET, true); michael@0: if (!policy.allowed()) michael@0: return policy.returnValue(); michael@0: if (!handler->hasPrototype()) michael@0: return handler->getPropertyDescriptor(cx, proxy, id, desc); michael@0: if (!handler->getOwnPropertyDescriptor(cx, proxy, id, desc)) michael@0: return false; michael@0: if (desc.object()) michael@0: return true; michael@0: INVOKE_ON_PROTOTYPE(cx, handler, proxy, JS_GetPropertyDescriptorById(cx, proto, id, desc)); michael@0: } michael@0: michael@0: bool michael@0: Proxy::getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id, MutableHandleValue vp) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: michael@0: Rooted desc(cx); michael@0: if (!Proxy::getPropertyDescriptor(cx, proxy, id, &desc)) michael@0: return false; michael@0: return NewPropertyDescriptorObject(cx, desc, vp); michael@0: } michael@0: michael@0: bool michael@0: Proxy::getOwnPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id, michael@0: MutableHandle desc) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: michael@0: BaseProxyHandler *handler = proxy->as().handler(); michael@0: desc.object().set(nullptr); // default result if we refuse to perform this action michael@0: AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET, true); michael@0: if (!policy.allowed()) michael@0: return policy.returnValue(); michael@0: return handler->getOwnPropertyDescriptor(cx, proxy, id, desc); michael@0: } michael@0: michael@0: bool michael@0: Proxy::getOwnPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id, michael@0: MutableHandleValue vp) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: michael@0: Rooted desc(cx); michael@0: if (!Proxy::getOwnPropertyDescriptor(cx, proxy, id, &desc)) michael@0: return false; michael@0: return NewPropertyDescriptorObject(cx, desc, vp); michael@0: } michael@0: michael@0: bool michael@0: Proxy::defineProperty(JSContext *cx, HandleObject proxy, HandleId id, michael@0: MutableHandle desc) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: BaseProxyHandler *handler = proxy->as().handler(); michael@0: AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::SET, true); michael@0: if (!policy.allowed()) michael@0: return policy.returnValue(); michael@0: return proxy->as().handler()->defineProperty(cx, proxy, id, desc); michael@0: } michael@0: michael@0: bool michael@0: Proxy::defineProperty(JSContext *cx, HandleObject proxy, HandleId id, HandleValue v) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: Rooted desc(cx); michael@0: return ParsePropertyDescriptorObject(cx, proxy, v, &desc) && michael@0: Proxy::defineProperty(cx, proxy, id, &desc); michael@0: } michael@0: michael@0: bool michael@0: Proxy::getOwnPropertyNames(JSContext *cx, HandleObject proxy, AutoIdVector &props) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: BaseProxyHandler *handler = proxy->as().handler(); michael@0: AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, BaseProxyHandler::ENUMERATE, true); michael@0: if (!policy.allowed()) michael@0: return policy.returnValue(); michael@0: return proxy->as().handler()->getOwnPropertyNames(cx, proxy, props); michael@0: } michael@0: michael@0: bool michael@0: Proxy::delete_(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: BaseProxyHandler *handler = proxy->as().handler(); michael@0: *bp = true; // default result if we refuse to perform this action michael@0: AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::SET, true); michael@0: if (!policy.allowed()) michael@0: return policy.returnValue(); michael@0: return proxy->as().handler()->delete_(cx, proxy, id, bp); michael@0: } michael@0: michael@0: JS_FRIEND_API(bool) michael@0: js::AppendUnique(JSContext *cx, AutoIdVector &base, AutoIdVector &others) michael@0: { michael@0: AutoIdVector uniqueOthers(cx); michael@0: if (!uniqueOthers.reserve(others.length())) michael@0: return false; michael@0: for (size_t i = 0; i < others.length(); ++i) { michael@0: bool unique = true; michael@0: for (size_t j = 0; j < base.length(); ++j) { michael@0: if (others[i] == base[j]) { michael@0: unique = false; michael@0: break; michael@0: } michael@0: } michael@0: if (unique) michael@0: uniqueOthers.append(others[i]); michael@0: } michael@0: return base.appendAll(uniqueOthers); michael@0: } michael@0: michael@0: bool michael@0: Proxy::enumerate(JSContext *cx, HandleObject proxy, AutoIdVector &props) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: BaseProxyHandler *handler = proxy->as().handler(); michael@0: AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, BaseProxyHandler::ENUMERATE, true); michael@0: if (!policy.allowed()) michael@0: return policy.returnValue(); michael@0: if (!handler->hasPrototype()) michael@0: return proxy->as().handler()->enumerate(cx, proxy, props); michael@0: if (!handler->keys(cx, proxy, props)) michael@0: return false; michael@0: AutoIdVector protoProps(cx); michael@0: INVOKE_ON_PROTOTYPE(cx, handler, proxy, michael@0: GetPropertyNames(cx, proto, 0, &protoProps) && michael@0: AppendUnique(cx, props, protoProps)); michael@0: } michael@0: michael@0: bool michael@0: Proxy::has(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: BaseProxyHandler *handler = proxy->as().handler(); michael@0: *bp = false; // default result if we refuse to perform this action michael@0: AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET, true); michael@0: if (!policy.allowed()) michael@0: return policy.returnValue(); michael@0: if (!handler->hasPrototype()) michael@0: return handler->has(cx, proxy, id, bp); michael@0: if (!handler->hasOwn(cx, proxy, id, bp)) michael@0: return false; michael@0: if (*bp) michael@0: return true; michael@0: bool Bp; michael@0: INVOKE_ON_PROTOTYPE(cx, handler, proxy, michael@0: JS_HasPropertyById(cx, proto, id, &Bp) && michael@0: ((*bp = Bp) || true)); michael@0: } michael@0: michael@0: bool michael@0: Proxy::hasOwn(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: BaseProxyHandler *handler = proxy->as().handler(); michael@0: *bp = false; // default result if we refuse to perform this action michael@0: AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET, true); michael@0: if (!policy.allowed()) michael@0: return policy.returnValue(); michael@0: return handler->hasOwn(cx, proxy, id, bp); michael@0: } michael@0: michael@0: bool michael@0: Proxy::get(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id, michael@0: MutableHandleValue vp) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: BaseProxyHandler *handler = proxy->as().handler(); michael@0: vp.setUndefined(); // default result if we refuse to perform this action michael@0: AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET, true); michael@0: if (!policy.allowed()) michael@0: return policy.returnValue(); michael@0: bool own; michael@0: if (!handler->hasPrototype()) { michael@0: own = true; michael@0: } else { michael@0: if (!handler->hasOwn(cx, proxy, id, &own)) michael@0: return false; michael@0: } michael@0: if (own) michael@0: return handler->get(cx, proxy, receiver, id, vp); michael@0: INVOKE_ON_PROTOTYPE(cx, handler, proxy, JSObject::getGeneric(cx, proto, receiver, id, vp)); michael@0: } michael@0: michael@0: bool michael@0: Proxy::callProp(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id, michael@0: MutableHandleValue vp) michael@0: { michael@0: // The inline caches need an access point for JSOP_CALLPROP sites that accounts michael@0: // for the possibility of __noSuchMethod__ michael@0: if (!Proxy::get(cx, proxy, receiver, id, vp)) michael@0: return false; michael@0: michael@0: #if JS_HAS_NO_SUCH_METHOD michael@0: if (MOZ_UNLIKELY(vp.isPrimitive())) { michael@0: if (!OnUnknownMethod(cx, proxy, IdToValue(id), vp)) michael@0: return false; michael@0: } michael@0: #endif michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: Proxy::set(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id, bool strict, michael@0: MutableHandleValue vp) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: BaseProxyHandler *handler = proxy->as().handler(); michael@0: AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::SET, true); michael@0: if (!policy.allowed()) michael@0: return policy.returnValue(); michael@0: michael@0: // If the proxy doesn't require that we consult its prototype for the michael@0: // non-own cases, we can sink to the |set| trap. michael@0: if (!handler->hasPrototype()) michael@0: return handler->set(cx, proxy, receiver, id, strict, vp); michael@0: michael@0: // If we have an existing (own or non-own) property with a setter, we want michael@0: // to invoke that. michael@0: Rooted desc(cx); michael@0: if (!Proxy::getPropertyDescriptor(cx, proxy, id, &desc)) michael@0: return false; michael@0: if (desc.object() && desc.setter() && desc.setter() != JS_StrictPropertyStub) michael@0: return CallSetter(cx, receiver, id, desc.setter(), desc.attributes(), strict, vp); michael@0: michael@0: // Ok. Either there was no pre-existing property, or it was a value prop michael@0: // that we're going to shadow. Make a property descriptor and define it. michael@0: Rooted newDesc(cx); michael@0: newDesc.value().set(vp); michael@0: return handler->defineProperty(cx, receiver, id, &newDesc); michael@0: } michael@0: michael@0: bool michael@0: Proxy::keys(JSContext *cx, HandleObject proxy, AutoIdVector &props) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: BaseProxyHandler *handler = proxy->as().handler(); michael@0: AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, BaseProxyHandler::ENUMERATE, true); michael@0: if (!policy.allowed()) michael@0: return policy.returnValue(); michael@0: return handler->keys(cx, proxy, props); michael@0: } michael@0: michael@0: bool michael@0: Proxy::iterate(JSContext *cx, HandleObject proxy, unsigned flags, MutableHandleValue vp) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: BaseProxyHandler *handler = proxy->as().handler(); michael@0: vp.setUndefined(); // default result if we refuse to perform this action michael@0: if (!handler->hasPrototype()) { michael@0: AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, michael@0: BaseProxyHandler::ENUMERATE, true); michael@0: // If the policy denies access but wants us to return true, we need michael@0: // to hand a valid (empty) iterator object to the caller. michael@0: if (!policy.allowed()) { michael@0: AutoIdVector props(cx); michael@0: return policy.returnValue() && michael@0: EnumeratedIdVectorToIterator(cx, proxy, flags, props, vp); michael@0: } michael@0: return handler->iterate(cx, proxy, flags, vp); michael@0: } michael@0: AutoIdVector props(cx); michael@0: // The other Proxy::foo methods do the prototype-aware work for us here. michael@0: if ((flags & JSITER_OWNONLY) michael@0: ? !Proxy::keys(cx, proxy, props) michael@0: : !Proxy::enumerate(cx, proxy, props)) { michael@0: return false; michael@0: } michael@0: return EnumeratedIdVectorToIterator(cx, proxy, flags, props, vp); michael@0: } michael@0: michael@0: bool michael@0: Proxy::isExtensible(JSContext *cx, HandleObject proxy, bool *extensible) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: return proxy->as().handler()->isExtensible(cx, proxy, extensible); michael@0: } michael@0: michael@0: bool michael@0: Proxy::preventExtensions(JSContext *cx, HandleObject proxy) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: BaseProxyHandler *handler = proxy->as().handler(); michael@0: return handler->preventExtensions(cx, proxy); michael@0: } michael@0: michael@0: bool michael@0: Proxy::call(JSContext *cx, HandleObject proxy, const CallArgs &args) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: BaseProxyHandler *handler = proxy->as().handler(); michael@0: michael@0: // Because vp[0] is JS_CALLEE on the way in and JS_RVAL on the way out, we michael@0: // can only set our default value once we're sure that we're not calling the michael@0: // trap. michael@0: AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, michael@0: BaseProxyHandler::CALL, true); michael@0: if (!policy.allowed()) { michael@0: args.rval().setUndefined(); michael@0: return policy.returnValue(); michael@0: } michael@0: michael@0: return handler->call(cx, proxy, args); michael@0: } michael@0: michael@0: bool michael@0: Proxy::construct(JSContext *cx, HandleObject proxy, const CallArgs &args) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: BaseProxyHandler *handler = proxy->as().handler(); michael@0: michael@0: // Because vp[0] is JS_CALLEE on the way in and JS_RVAL on the way out, we michael@0: // can only set our default value once we're sure that we're not calling the michael@0: // trap. michael@0: AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, michael@0: BaseProxyHandler::CALL, true); michael@0: if (!policy.allowed()) { michael@0: args.rval().setUndefined(); michael@0: return policy.returnValue(); michael@0: } michael@0: michael@0: return handler->construct(cx, proxy, args); michael@0: } michael@0: michael@0: bool michael@0: Proxy::nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl, CallArgs args) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: RootedObject proxy(cx, &args.thisv().toObject()); michael@0: // Note - we don't enter a policy here because our security architecture michael@0: // guards against nativeCall by overriding the trap itself in the right michael@0: // circumstances. michael@0: return proxy->as().handler()->nativeCall(cx, test, impl, args); michael@0: } michael@0: michael@0: bool michael@0: Proxy::hasInstance(JSContext *cx, HandleObject proxy, MutableHandleValue v, bool *bp) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: BaseProxyHandler *handler = proxy->as().handler(); michael@0: *bp = false; // default result if we refuse to perform this action michael@0: AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, BaseProxyHandler::GET, true); michael@0: if (!policy.allowed()) michael@0: return policy.returnValue(); michael@0: return proxy->as().handler()->hasInstance(cx, proxy, v, bp); michael@0: } michael@0: michael@0: bool michael@0: Proxy::objectClassIs(HandleObject proxy, ESClassValue classValue, JSContext *cx) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: return proxy->as().handler()->objectClassIs(proxy, classValue, cx); michael@0: } michael@0: michael@0: const char * michael@0: Proxy::className(JSContext *cx, HandleObject proxy) michael@0: { michael@0: // Check for unbounded recursion, but don't signal an error; className michael@0: // needs to be infallible. michael@0: int stackDummy; michael@0: if (!JS_CHECK_STACK_SIZE(GetNativeStackLimit(cx), &stackDummy)) michael@0: return "too much recursion"; michael@0: michael@0: BaseProxyHandler *handler = proxy->as().handler(); michael@0: AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, michael@0: BaseProxyHandler::GET, /* mayThrow = */ false); michael@0: // Do the safe thing if the policy rejects. michael@0: if (!policy.allowed()) { michael@0: return handler->BaseProxyHandler::className(cx, proxy); michael@0: } michael@0: return handler->className(cx, proxy); michael@0: } michael@0: michael@0: JSString * michael@0: Proxy::fun_toString(JSContext *cx, HandleObject proxy, unsigned indent) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return nullptr); michael@0: BaseProxyHandler *handler = proxy->as().handler(); michael@0: AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, michael@0: BaseProxyHandler::GET, /* mayThrow = */ false); michael@0: // Do the safe thing if the policy rejects. michael@0: if (!policy.allowed()) michael@0: return handler->BaseProxyHandler::fun_toString(cx, proxy, indent); michael@0: return handler->fun_toString(cx, proxy, indent); michael@0: } michael@0: michael@0: bool michael@0: Proxy::regexp_toShared(JSContext *cx, HandleObject proxy, RegExpGuard *g) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: return proxy->as().handler()->regexp_toShared(cx, proxy, g); michael@0: } michael@0: michael@0: bool michael@0: Proxy::defaultValue(JSContext *cx, HandleObject proxy, JSType hint, MutableHandleValue vp) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: return proxy->as().handler()->defaultValue(cx, proxy, hint, vp); michael@0: } michael@0: michael@0: JSObject * const TaggedProto::LazyProto = reinterpret_cast(0x1); michael@0: michael@0: bool michael@0: Proxy::getPrototypeOf(JSContext *cx, HandleObject proxy, MutableHandleObject proto) michael@0: { michael@0: JS_ASSERT(proxy->getTaggedProto().isLazy()); michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: return proxy->as().handler()->getPrototypeOf(cx, proxy, proto); michael@0: } michael@0: michael@0: bool michael@0: Proxy::setPrototypeOf(JSContext *cx, HandleObject proxy, HandleObject proto, bool *bp) michael@0: { michael@0: JS_ASSERT(proxy->getTaggedProto().isLazy()); michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: return proxy->as().handler()->setPrototypeOf(cx, proxy, proto, bp); michael@0: } michael@0: michael@0: /* static */ bool michael@0: Proxy::watch(JSContext *cx, JS::HandleObject proxy, JS::HandleId id, JS::HandleObject callable) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: return proxy->as().handler()->watch(cx, proxy, id, callable); michael@0: } michael@0: michael@0: /* static */ bool michael@0: Proxy::unwatch(JSContext *cx, JS::HandleObject proxy, JS::HandleId id) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: return proxy->as().handler()->unwatch(cx, proxy, id); michael@0: } michael@0: michael@0: /* static */ bool michael@0: Proxy::slice(JSContext *cx, HandleObject proxy, uint32_t begin, uint32_t end, michael@0: HandleObject result) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: BaseProxyHandler *handler = proxy->as().handler(); michael@0: AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, BaseProxyHandler::GET, michael@0: /* mayThrow = */ true); michael@0: if (!policy.allowed()) { michael@0: if (policy.returnValue()) { michael@0: JS_ASSERT(!cx->isExceptionPending()); michael@0: return js::SliceSlowly(cx, proxy, proxy, begin, end, result); michael@0: } michael@0: return false; michael@0: } michael@0: return handler->slice(cx, proxy, begin, end, result); michael@0: } michael@0: michael@0: JSObject * michael@0: js::proxy_innerObject(JSContext *cx, HandleObject obj) michael@0: { michael@0: return obj->as().private_().toObjectOrNull(); michael@0: } michael@0: michael@0: bool michael@0: js::proxy_LookupGeneric(JSContext *cx, HandleObject obj, HandleId id, michael@0: MutableHandleObject objp, MutableHandleShape propp) michael@0: { michael@0: bool found; michael@0: if (!Proxy::has(cx, obj, id, &found)) michael@0: return false; michael@0: michael@0: if (found) { michael@0: MarkNonNativePropertyFound(propp); michael@0: objp.set(obj); michael@0: } else { michael@0: objp.set(nullptr); michael@0: propp.set(nullptr); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::proxy_LookupProperty(JSContext *cx, HandleObject obj, HandlePropertyName name, michael@0: MutableHandleObject objp, MutableHandleShape propp) michael@0: { michael@0: RootedId id(cx, NameToId(name)); michael@0: return proxy_LookupGeneric(cx, obj, id, objp, propp); michael@0: } michael@0: michael@0: bool michael@0: js::proxy_LookupElement(JSContext *cx, HandleObject obj, uint32_t index, michael@0: MutableHandleObject objp, MutableHandleShape propp) michael@0: { michael@0: RootedId id(cx); michael@0: if (!IndexToId(cx, index, &id)) michael@0: return false; michael@0: return proxy_LookupGeneric(cx, obj, id, objp, propp); michael@0: } michael@0: michael@0: bool michael@0: js::proxy_DefineGeneric(JSContext *cx, HandleObject obj, HandleId id, HandleValue value, michael@0: PropertyOp getter, StrictPropertyOp setter, unsigned attrs) michael@0: { michael@0: Rooted desc(cx); michael@0: desc.object().set(obj); michael@0: desc.value().set(value); michael@0: desc.setAttributes(attrs); michael@0: desc.setGetter(getter); michael@0: desc.setSetter(setter); michael@0: return Proxy::defineProperty(cx, obj, id, &desc); michael@0: } michael@0: michael@0: bool michael@0: js::proxy_DefineProperty(JSContext *cx, HandleObject obj, HandlePropertyName name, HandleValue value, michael@0: PropertyOp getter, StrictPropertyOp setter, unsigned attrs) michael@0: { michael@0: Rooted id(cx, NameToId(name)); michael@0: return proxy_DefineGeneric(cx, obj, id, value, getter, setter, attrs); michael@0: } michael@0: michael@0: bool michael@0: js::proxy_DefineElement(JSContext *cx, HandleObject obj, uint32_t index, HandleValue value, michael@0: PropertyOp getter, StrictPropertyOp setter, unsigned attrs) michael@0: { michael@0: RootedId id(cx); michael@0: if (!IndexToId(cx, index, &id)) michael@0: return false; michael@0: return proxy_DefineGeneric(cx, obj, id, value, getter, setter, attrs); michael@0: } michael@0: michael@0: bool michael@0: js::proxy_GetGeneric(JSContext *cx, HandleObject obj, HandleObject receiver, HandleId id, michael@0: MutableHandleValue vp) michael@0: { michael@0: return Proxy::get(cx, obj, receiver, id, vp); michael@0: } michael@0: michael@0: bool michael@0: js::proxy_GetProperty(JSContext *cx, HandleObject obj, HandleObject receiver, HandlePropertyName name, michael@0: MutableHandleValue vp) michael@0: { michael@0: Rooted id(cx, NameToId(name)); michael@0: return proxy_GetGeneric(cx, obj, receiver, id, vp); michael@0: } michael@0: michael@0: bool michael@0: js::proxy_GetElement(JSContext *cx, HandleObject obj, HandleObject receiver, uint32_t index, michael@0: MutableHandleValue vp) michael@0: { michael@0: RootedId id(cx); michael@0: if (!IndexToId(cx, index, &id)) michael@0: return false; michael@0: return proxy_GetGeneric(cx, obj, receiver, id, vp); michael@0: } michael@0: michael@0: bool michael@0: js::proxy_SetGeneric(JSContext *cx, HandleObject obj, HandleId id, michael@0: MutableHandleValue vp, bool strict) michael@0: { michael@0: return Proxy::set(cx, obj, obj, id, strict, vp); michael@0: } michael@0: michael@0: bool michael@0: js::proxy_SetProperty(JSContext *cx, HandleObject obj, HandlePropertyName name, michael@0: MutableHandleValue vp, bool strict) michael@0: { michael@0: Rooted id(cx, NameToId(name)); michael@0: return proxy_SetGeneric(cx, obj, id, vp, strict); michael@0: } michael@0: michael@0: bool michael@0: js::proxy_SetElement(JSContext *cx, HandleObject obj, uint32_t index, michael@0: MutableHandleValue vp, bool strict) michael@0: { michael@0: RootedId id(cx); michael@0: if (!IndexToId(cx, index, &id)) michael@0: return false; michael@0: return proxy_SetGeneric(cx, obj, id, vp, strict); michael@0: } michael@0: michael@0: bool michael@0: js::proxy_GetGenericAttributes(JSContext *cx, HandleObject obj, HandleId id, unsigned *attrsp) michael@0: { michael@0: Rooted desc(cx); michael@0: if (!Proxy::getOwnPropertyDescriptor(cx, obj, id, &desc)) michael@0: return false; michael@0: *attrsp = desc.attributes(); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::proxy_SetGenericAttributes(JSContext *cx, HandleObject obj, HandleId id, unsigned *attrsp) michael@0: { michael@0: /* Lookup the current property descriptor so we have setter/getter/value. */ michael@0: Rooted desc(cx); michael@0: if (!Proxy::getOwnPropertyDescriptor(cx, obj, id, &desc)) michael@0: return false; michael@0: desc.setAttributes(*attrsp); michael@0: return Proxy::defineProperty(cx, obj, id, &desc); michael@0: } michael@0: michael@0: static bool michael@0: proxy_DeleteGeneric(JSContext *cx, HandleObject obj, HandleId id, bool *succeeded) michael@0: { michael@0: bool deleted; michael@0: if (!Proxy::delete_(cx, obj, id, &deleted)) michael@0: return false; michael@0: *succeeded = deleted; michael@0: return js_SuppressDeletedProperty(cx, obj, id); michael@0: } michael@0: michael@0: bool michael@0: js::proxy_DeleteProperty(JSContext *cx, HandleObject obj, HandlePropertyName name, bool *succeeded) michael@0: { michael@0: RootedId id(cx, NameToId(name)); michael@0: return proxy_DeleteGeneric(cx, obj, id, succeeded); michael@0: } michael@0: michael@0: bool michael@0: js::proxy_DeleteElement(JSContext *cx, HandleObject obj, uint32_t index, bool *succeeded) michael@0: { michael@0: RootedId id(cx); michael@0: if (!IndexToId(cx, index, &id)) michael@0: return false; michael@0: return proxy_DeleteGeneric(cx, obj, id, succeeded); michael@0: } michael@0: michael@0: void michael@0: js::proxy_Trace(JSTracer *trc, JSObject *obj) michael@0: { michael@0: JS_ASSERT(obj->is()); michael@0: ProxyObject::trace(trc, obj); michael@0: } michael@0: michael@0: /* static */ void michael@0: ProxyObject::trace(JSTracer *trc, JSObject *obj) michael@0: { michael@0: ProxyObject *proxy = &obj->as(); michael@0: michael@0: #ifdef DEBUG michael@0: if (!trc->runtime()->gcDisableStrictProxyCheckingCount && proxy->is()) { michael@0: JSObject *referent = &proxy->private_().toObject(); michael@0: if (referent->compartment() != proxy->compartment()) { michael@0: /* michael@0: * Assert that this proxy is tracked in the wrapper map. We maintain michael@0: * the invariant that the wrapped object is the key in the wrapper map. michael@0: */ michael@0: Value key = ObjectValue(*referent); michael@0: WrapperMap::Ptr p = proxy->compartment()->lookupWrapper(key); michael@0: JS_ASSERT(*p->value().unsafeGet() == ObjectValue(*proxy)); michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: // Note: If you add new slots here, make sure to change michael@0: // nuke() to cope. michael@0: MarkCrossCompartmentSlot(trc, obj, proxy->slotOfPrivate(), "private"); michael@0: MarkSlot(trc, proxy->slotOfExtra(0), "extra0"); michael@0: michael@0: /* michael@0: * The GC can use the second reserved slot to link the cross compartment michael@0: * wrappers into a linked list, in which case we don't want to trace it. michael@0: */ michael@0: if (!proxy->is()) michael@0: MarkSlot(trc, proxy->slotOfExtra(1), "extra1"); michael@0: michael@0: /* michael@0: * Allow for people to add extra slots to "proxy" classes, without allowing michael@0: * them to set their own trace hook. Trace the extras. michael@0: */ michael@0: unsigned numSlots = JSCLASS_RESERVED_SLOTS(proxy->getClass()); michael@0: for (unsigned i = PROXY_MINIMUM_SLOTS; i < numSlots; i++) michael@0: MarkSlot(trc, proxy->slotOfClassSpecific(i), "class-specific"); michael@0: } michael@0: michael@0: JSObject * michael@0: js::proxy_WeakmapKeyDelegate(JSObject *obj) michael@0: { michael@0: JS_ASSERT(obj->is()); michael@0: return obj->as().handler()->weakmapKeyDelegate(obj); michael@0: } michael@0: michael@0: bool michael@0: js::proxy_Convert(JSContext *cx, HandleObject proxy, JSType hint, MutableHandleValue vp) michael@0: { michael@0: JS_ASSERT(proxy->is()); michael@0: return Proxy::defaultValue(cx, proxy, hint, vp); michael@0: } michael@0: michael@0: void michael@0: js::proxy_Finalize(FreeOp *fop, JSObject *obj) michael@0: { michael@0: JS_ASSERT(obj->is()); michael@0: obj->as().handler()->finalize(fop, obj); michael@0: } michael@0: michael@0: bool michael@0: js::proxy_HasInstance(JSContext *cx, HandleObject proxy, MutableHandleValue v, bool *bp) michael@0: { michael@0: bool b; michael@0: if (!Proxy::hasInstance(cx, proxy, v, &b)) michael@0: return false; michael@0: *bp = !!b; michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::proxy_Call(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: RootedObject proxy(cx, &args.callee()); michael@0: JS_ASSERT(proxy->is()); michael@0: return Proxy::call(cx, proxy, args); michael@0: } michael@0: michael@0: bool michael@0: js::proxy_Construct(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: RootedObject proxy(cx, &args.callee()); michael@0: JS_ASSERT(proxy->is()); michael@0: return Proxy::construct(cx, proxy, args); michael@0: } michael@0: michael@0: bool michael@0: js::proxy_Watch(JSContext *cx, HandleObject obj, HandleId id, HandleObject callable) michael@0: { michael@0: return Proxy::watch(cx, obj, id, callable); michael@0: } michael@0: michael@0: bool michael@0: js::proxy_Unwatch(JSContext *cx, HandleObject obj, HandleId id) michael@0: { michael@0: return Proxy::unwatch(cx, obj, id); michael@0: } michael@0: michael@0: bool michael@0: js::proxy_Slice(JSContext *cx, HandleObject proxy, uint32_t begin, uint32_t end, michael@0: HandleObject result) michael@0: { michael@0: return Proxy::slice(cx, proxy, begin, end, result); michael@0: } michael@0: michael@0: #define PROXY_CLASS(callOp, constructOp) \ michael@0: PROXY_CLASS_DEF("Proxy", \ michael@0: 0, /* additional slots */ \ michael@0: JSCLASS_HAS_CACHED_PROTO(JSProto_Proxy), \ michael@0: callOp, \ michael@0: constructOp) michael@0: michael@0: const Class js::ProxyObject::uncallableClass_ = PROXY_CLASS(nullptr, nullptr); michael@0: const Class js::ProxyObject::callableClass_ = PROXY_CLASS(proxy_Call, proxy_Construct); michael@0: michael@0: const Class* const js::CallableProxyClassPtr = &ProxyObject::callableClass_; michael@0: const Class* const js::UncallableProxyClassPtr = &ProxyObject::uncallableClass_; michael@0: michael@0: JS_FRIEND_API(JSObject *) michael@0: js::NewProxyObject(JSContext *cx, BaseProxyHandler *handler, HandleValue priv, JSObject *proto_, michael@0: JSObject *parent_, const ProxyOptions &options) michael@0: { michael@0: return ProxyObject::New(cx, handler, priv, TaggedProto(proto_), parent_, michael@0: options); michael@0: } michael@0: michael@0: void michael@0: ProxyObject::renew(JSContext *cx, BaseProxyHandler *handler, Value priv) michael@0: { michael@0: JS_ASSERT_IF(IsCrossCompartmentWrapper(this), IsDeadProxyObject(this)); michael@0: JS_ASSERT(getParent() == cx->global()); michael@0: JS_ASSERT(getClass() == &uncallableClass_); michael@0: JS_ASSERT(!getClass()->ext.innerObject); michael@0: JS_ASSERT(getTaggedProto().isLazy()); michael@0: michael@0: setSlot(HANDLER_SLOT, PrivateValue(handler)); michael@0: setCrossCompartmentSlot(PRIVATE_SLOT, priv); michael@0: setSlot(EXTRA_SLOT + 0, UndefinedValue()); michael@0: setSlot(EXTRA_SLOT + 1, UndefinedValue()); michael@0: } michael@0: michael@0: static bool michael@0: proxy(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: if (args.length() < 2) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, michael@0: "Proxy", "1", "s"); michael@0: return false; michael@0: } michael@0: RootedObject target(cx, NonNullObject(cx, args[0])); michael@0: if (!target) michael@0: return false; michael@0: RootedObject handler(cx, NonNullObject(cx, args[1])); michael@0: if (!handler) michael@0: return false; michael@0: RootedValue priv(cx, ObjectValue(*target)); michael@0: ProxyOptions options; michael@0: options.selectDefaultClass(target->isCallable()); michael@0: ProxyObject *proxy = michael@0: ProxyObject::New(cx, &ScriptedDirectProxyHandler::singleton, michael@0: priv, TaggedProto(TaggedProto::LazyProto), cx->global(), michael@0: options); michael@0: if (!proxy) michael@0: return false; michael@0: proxy->setExtra(0, ObjectOrNullValue(handler)); michael@0: args.rval().setObject(*proxy); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: proxy_create(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: if (args.length() < 1) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, michael@0: "create", "0", "s"); michael@0: return false; michael@0: } michael@0: JSObject *handler = NonNullObject(cx, args[0]); michael@0: if (!handler) michael@0: return false; michael@0: JSObject *proto, *parent = nullptr; michael@0: if (args.get(1).isObject()) { michael@0: proto = &args[1].toObject(); michael@0: parent = proto->getParent(); michael@0: } else { michael@0: JS_ASSERT(IsFunctionObject(&args.callee())); michael@0: proto = nullptr; michael@0: } michael@0: if (!parent) michael@0: parent = args.callee().getParent(); michael@0: RootedValue priv(cx, ObjectValue(*handler)); michael@0: JSObject *proxy = NewProxyObject(cx, &ScriptedIndirectProxyHandler::singleton, michael@0: priv, proto, parent); michael@0: if (!proxy) michael@0: return false; michael@0: michael@0: args.rval().setObject(*proxy); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: proxy_createFunction(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: if (args.length() < 2) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, michael@0: "createFunction", "1", ""); michael@0: return false; michael@0: } michael@0: RootedObject handler(cx, NonNullObject(cx, args[0])); michael@0: if (!handler) michael@0: return false; michael@0: RootedObject proto(cx), parent(cx); michael@0: parent = args.callee().getParent(); michael@0: proto = parent->global().getOrCreateFunctionPrototype(cx); michael@0: if (!proto) michael@0: return false; michael@0: parent = proto->getParent(); michael@0: michael@0: RootedObject call(cx, ValueToCallable(cx, args[1], args.length() - 2)); michael@0: if (!call) michael@0: return false; michael@0: RootedObject construct(cx, nullptr); michael@0: if (args.length() > 2) { michael@0: construct = ValueToCallable(cx, args[2], args.length() - 3); michael@0: if (!construct) michael@0: return false; michael@0: } else { michael@0: construct = call; michael@0: } michael@0: michael@0: // Stash the call and construct traps on a holder object that we can stick michael@0: // in a slot on the proxy. michael@0: RootedObject ccHolder(cx, JS_NewObjectWithGivenProto(cx, Jsvalify(&CallConstructHolder), michael@0: js::NullPtr(), cx->global())); michael@0: if (!ccHolder) michael@0: return false; michael@0: ccHolder->setReservedSlot(0, ObjectValue(*call)); michael@0: ccHolder->setReservedSlot(1, ObjectValue(*construct)); michael@0: michael@0: RootedValue priv(cx, ObjectValue(*handler)); michael@0: ProxyOptions options; michael@0: options.selectDefaultClass(true); michael@0: JSObject *proxy = michael@0: ProxyObject::New(cx, &ScriptedIndirectProxyHandler::singleton, michael@0: priv, TaggedProto(proto), parent, options); michael@0: if (!proxy) michael@0: return false; michael@0: proxy->as().setExtra(0, ObjectValue(*ccHolder)); michael@0: michael@0: args.rval().setObject(*proxy); michael@0: return true; michael@0: } michael@0: michael@0: JS_FRIEND_API(JSObject *) michael@0: js_InitProxyClass(JSContext *cx, HandleObject obj) michael@0: { michael@0: static const JSFunctionSpec static_methods[] = { michael@0: JS_FN("create", proxy_create, 2, 0), michael@0: JS_FN("createFunction", proxy_createFunction, 3, 0), michael@0: JS_FS_END michael@0: }; michael@0: michael@0: Rooted global(cx, &obj->as()); michael@0: RootedFunction ctor(cx); michael@0: ctor = global->createConstructor(cx, proxy, cx->names().Proxy, 2); michael@0: if (!ctor) michael@0: return nullptr; michael@0: michael@0: if (!JS_DefineFunctions(cx, ctor, static_methods)) michael@0: return nullptr; michael@0: if (!JS_DefineProperty(cx, obj, "Proxy", ctor, 0, michael@0: JS_PropertyStub, JS_StrictPropertyStub)) { michael@0: return nullptr; michael@0: } michael@0: michael@0: global->setConstructor(JSProto_Proxy, ObjectValue(*ctor)); michael@0: return ctor; michael@0: }