/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "js/Proxy.h" #include #include "jsapi.h" #include "jscntxt.h" #include "jsfun.h" #include "jsgc.h" #include "jswrapper.h" #include "gc/Marking.h" #include "proxy/DeadObjectProxy.h" #include "proxy/ScriptedProxyHandler.h" #include "vm/WrapperObject.h" #include "jsatominlines.h" #include "jsobjinlines.h" #include "vm/NativeObject-inl.h" using namespace js; using namespace js::gc; void js::AutoEnterPolicy::reportErrorIfExceptionIsNotPending(JSContext* cx, jsid id) { if (JS_IsExceptionPending(cx)) return; if (JSID_IS_VOID(id)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_OBJECT_ACCESS_DENIED); } else { RootedValue idVal(cx, IdToValue(id)); JSString* str = ValueToSource(cx, idVal); if (!str) { return; } AutoStableStringChars chars(cx); const char16_t* prop = nullptr; if (str->ensureFlat(cx) && chars.initTwoByte(cx, str)) prop = chars.twoByteChars(); JS_ReportErrorNumberUC(cx, GetErrorMessage, nullptr, JSMSG_PROPERTY_ACCESS_DENIED, prop); } } #ifdef DEBUG void js::AutoEnterPolicy::recordEnter(JSContext* cx, HandleObject proxy, HandleId id, Action act) { if (allowed()) { context = cx; enteredProxy.emplace(proxy); enteredId.emplace(id); enteredAction = act; prev = cx->runtime()->enteredPolicy; cx->runtime()->enteredPolicy = this; } } void js::AutoEnterPolicy::recordLeave() { if (enteredProxy) { MOZ_ASSERT(context->runtime()->enteredPolicy == this); context->runtime()->enteredPolicy = prev; } } JS_FRIEND_API(void) js::assertEnteredPolicy(JSContext* cx, JSObject* proxy, jsid id, BaseProxyHandler::Action act) { MOZ_ASSERT(proxy->is()); MOZ_ASSERT(cx->runtime()->enteredPolicy); MOZ_ASSERT(cx->runtime()->enteredPolicy->enteredProxy->get() == proxy); MOZ_ASSERT(cx->runtime()->enteredPolicy->enteredId->get() == id); MOZ_ASSERT(cx->runtime()->enteredPolicy->enteredAction & act); } #endif bool Proxy::getPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id, MutableHandle desc) { JS_CHECK_RECURSION(cx, return false); const BaseProxyHandler* handler = proxy->as().handler(); desc.object().set(nullptr); // default result if we refuse to perform this action AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET_PROPERTY_DESCRIPTOR, true); if (!policy.allowed()) return policy.returnValue(); // Special case. See the comment on BaseProxyHandler::mHasPrototype. if (handler->hasPrototype()) return handler->BaseProxyHandler::getPropertyDescriptor(cx, proxy, id, desc); return handler->getPropertyDescriptor(cx, proxy, id, desc); } bool Proxy::getOwnPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id, MutableHandle desc) { JS_CHECK_RECURSION(cx, return false); const BaseProxyHandler* handler = proxy->as().handler(); desc.object().set(nullptr); // default result if we refuse to perform this action AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET_PROPERTY_DESCRIPTOR, true); if (!policy.allowed()) return policy.returnValue(); return handler->getOwnPropertyDescriptor(cx, proxy, id, desc); } bool Proxy::defineProperty(JSContext* cx, HandleObject proxy, HandleId id, Handle desc, ObjectOpResult& result) { JS_CHECK_RECURSION(cx, return false); const BaseProxyHandler* handler = proxy->as().handler(); AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::SET, true); if (!policy.allowed()) { if (!policy.returnValue()) return false; return result.succeed(); } return proxy->as().handler()->defineProperty(cx, proxy, id, desc, result); } bool Proxy::ownPropertyKeys(JSContext* cx, HandleObject proxy, AutoIdVector& props) { JS_CHECK_RECURSION(cx, return false); const BaseProxyHandler* handler = proxy->as().handler(); AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, BaseProxyHandler::ENUMERATE, true); if (!policy.allowed()) return policy.returnValue(); return proxy->as().handler()->ownPropertyKeys(cx, proxy, props); } bool Proxy::delete_(JSContext* cx, HandleObject proxy, HandleId id, ObjectOpResult& result) { JS_CHECK_RECURSION(cx, return false); const BaseProxyHandler* handler = proxy->as().handler(); AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::SET, true); if (!policy.allowed()) { bool ok = policy.returnValue(); if (ok) result.succeed(); return ok; } return proxy->as().handler()->delete_(cx, proxy, id, result); } JS_FRIEND_API(bool) js::AppendUnique(JSContext* cx, AutoIdVector& base, AutoIdVector& others) { AutoIdVector uniqueOthers(cx); if (!uniqueOthers.reserve(others.length())) return false; for (size_t i = 0; i < others.length(); ++i) { bool unique = true; for (size_t j = 0; j < base.length(); ++j) { if (others[i].get() == base[j]) { unique = false; break; } } if (unique) { if (!uniqueOthers.append(others[i])) return false; } } return base.appendAll(uniqueOthers); } /* static */ bool Proxy::getPrototype(JSContext* cx, HandleObject proxy, MutableHandleObject proto) { MOZ_ASSERT(proxy->hasDynamicPrototype()); JS_CHECK_RECURSION(cx, return false); return proxy->as().handler()->getPrototype(cx, proxy, proto); } /* static */ bool Proxy::setPrototype(JSContext* cx, HandleObject proxy, HandleObject proto, ObjectOpResult& result) { MOZ_ASSERT(proxy->hasDynamicPrototype()); JS_CHECK_RECURSION(cx, return false); return proxy->as().handler()->setPrototype(cx, proxy, proto, result); } /* static */ bool Proxy::getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary, MutableHandleObject proto) { JS_CHECK_RECURSION(cx, return false); return proxy->as().handler()->getPrototypeIfOrdinary(cx, proxy, isOrdinary, proto); } /* static */ bool Proxy::setImmutablePrototype(JSContext* cx, HandleObject proxy, bool* succeeded) { JS_CHECK_RECURSION(cx, return false); const BaseProxyHandler* handler = proxy->as().handler(); return handler->setImmutablePrototype(cx, proxy, succeeded); } /* static */ bool Proxy::preventExtensions(JSContext* cx, HandleObject proxy, ObjectOpResult& result) { JS_CHECK_RECURSION(cx, return false); const BaseProxyHandler* handler = proxy->as().handler(); return handler->preventExtensions(cx, proxy, result); } /* static */ bool Proxy::isExtensible(JSContext* cx, HandleObject proxy, bool* extensible) { JS_CHECK_RECURSION(cx, return false); return proxy->as().handler()->isExtensible(cx, proxy, extensible); } bool Proxy::has(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) { JS_CHECK_RECURSION(cx, return false); const BaseProxyHandler* handler = proxy->as().handler(); *bp = false; // default result if we refuse to perform this action AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET, true); if (!policy.allowed()) return policy.returnValue(); if (handler->hasPrototype()) { if (!handler->hasOwn(cx, proxy, id, bp)) return false; if (*bp) return true; RootedObject proto(cx); if (!GetPrototype(cx, proxy, &proto)) return false; if (!proto) return true; return HasProperty(cx, proto, id, bp); } return handler->has(cx, proxy, id, bp); } bool Proxy::hasOwn(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) { JS_CHECK_RECURSION(cx, return false); const BaseProxyHandler* handler = proxy->as().handler(); *bp = false; // default result if we refuse to perform this action AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET, true); if (!policy.allowed()) return policy.returnValue(); return handler->hasOwn(cx, proxy, id, bp); } static Value ValueToWindowProxyIfWindow(const Value& v) { if (v.isObject()) return ObjectValue(*ToWindowProxyIfWindow(&v.toObject())); return v; } bool Proxy::get(JSContext* cx, HandleObject proxy, HandleValue receiver_, HandleId id, MutableHandleValue vp) { JS_CHECK_RECURSION(cx, return false); const BaseProxyHandler* handler = proxy->as().handler(); vp.setUndefined(); // default result if we refuse to perform this action AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET, true); if (!policy.allowed()) return policy.returnValue(); // Use the WindowProxy as receiver if receiver_ is a Window. Proxy handlers // shouldn't have to know about the Window/WindowProxy distinction. RootedValue receiver(cx, ValueToWindowProxyIfWindow(receiver_)); if (handler->hasPrototype()) { bool own; if (!handler->hasOwn(cx, proxy, id, &own)) return false; if (!own) { RootedObject proto(cx); if (!GetPrototype(cx, proxy, &proto)) return false; if (!proto) return true; return GetProperty(cx, proto, receiver, id, vp); } } return handler->get(cx, proxy, receiver, id, vp); } bool Proxy::set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v, HandleValue receiver_, ObjectOpResult& result) { JS_CHECK_RECURSION(cx, return false); const BaseProxyHandler* handler = proxy->as().handler(); AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::SET, true); if (!policy.allowed()) { if (!policy.returnValue()) return false; return result.succeed(); } // Use the WindowProxy as receiver if receiver_ is a Window. Proxy handlers // shouldn't have to know about the Window/WindowProxy distinction. RootedValue receiver(cx, ValueToWindowProxyIfWindow(receiver_)); // Special case. See the comment on BaseProxyHandler::mHasPrototype. if (handler->hasPrototype()) return handler->BaseProxyHandler::set(cx, proxy, id, v, receiver, result); return handler->set(cx, proxy, id, v, receiver, result); } bool Proxy::getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject proxy, AutoIdVector& props) { JS_CHECK_RECURSION(cx, return false); const BaseProxyHandler* handler = proxy->as().handler(); AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, BaseProxyHandler::ENUMERATE, true); if (!policy.allowed()) return policy.returnValue(); return handler->getOwnEnumerablePropertyKeys(cx, proxy, props); } bool Proxy::enumerate(JSContext* cx, HandleObject proxy, MutableHandleObject objp) { JS_CHECK_RECURSION(cx, return false); const BaseProxyHandler* handler = proxy->as().handler(); objp.set(nullptr); // default result if we refuse to perform this action if (handler->hasPrototype()) { AutoIdVector props(cx); if (!Proxy::getOwnEnumerablePropertyKeys(cx, proxy, props)) return false; RootedObject proto(cx); if (!GetPrototype(cx, proxy, &proto)) return false; if (!proto) return EnumeratedIdVectorToIterator(cx, proxy, 0, props, objp); assertSameCompartment(cx, proxy, proto); AutoIdVector protoProps(cx); return GetPropertyKeys(cx, proto, 0, &protoProps) && AppendUnique(cx, props, protoProps) && EnumeratedIdVectorToIterator(cx, proxy, 0, props, objp); } AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, BaseProxyHandler::ENUMERATE, true); // If the policy denies access but wants us to return true, we need // to hand a valid (empty) iterator object to the caller. if (!policy.allowed()) { return policy.returnValue() && NewEmptyPropertyIterator(cx, 0, objp); } return handler->enumerate(cx, proxy, objp); } bool Proxy::call(JSContext* cx, HandleObject proxy, const CallArgs& args) { JS_CHECK_RECURSION(cx, return false); const BaseProxyHandler* handler = proxy->as().handler(); // Because vp[0] is JS_CALLEE on the way in and JS_RVAL on the way out, we // can only set our default value once we're sure that we're not calling the // trap. AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, BaseProxyHandler::CALL, true); if (!policy.allowed()) { args.rval().setUndefined(); return policy.returnValue(); } return handler->call(cx, proxy, args); } bool Proxy::construct(JSContext* cx, HandleObject proxy, const CallArgs& args) { JS_CHECK_RECURSION(cx, return false); const BaseProxyHandler* handler = proxy->as().handler(); // Because vp[0] is JS_CALLEE on the way in and JS_RVAL on the way out, we // can only set our default value once we're sure that we're not calling the // trap. AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, BaseProxyHandler::CALL, true); if (!policy.allowed()) { args.rval().setUndefined(); return policy.returnValue(); } return handler->construct(cx, proxy, args); } bool Proxy::nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl, const CallArgs& args) { JS_CHECK_RECURSION(cx, return false); RootedObject proxy(cx, &args.thisv().toObject()); // Note - we don't enter a policy here because our security architecture // guards against nativeCall by overriding the trap itself in the right // circumstances. return proxy->as().handler()->nativeCall(cx, test, impl, args); } bool Proxy::hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v, bool* bp) { JS_CHECK_RECURSION(cx, return false); const BaseProxyHandler* handler = proxy->as().handler(); *bp = false; // default result if we refuse to perform this action AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, BaseProxyHandler::GET, true); if (!policy.allowed()) return policy.returnValue(); return proxy->as().handler()->hasInstance(cx, proxy, v, bp); } bool Proxy::getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls) { JS_CHECK_RECURSION(cx, return false); return proxy->as().handler()->getBuiltinClass(cx, proxy, cls); } bool Proxy::isArray(JSContext* cx, HandleObject proxy, JS::IsArrayAnswer* answer) { JS_CHECK_RECURSION(cx, return false); return proxy->as().handler()->isArray(cx, proxy, answer); } const char* Proxy::className(JSContext* cx, HandleObject proxy) { // Check for unbounded recursion, but don't signal an error; className // needs to be infallible. int stackDummy; if (!JS_CHECK_STACK_SIZE(GetNativeStackLimit(cx), &stackDummy)) return "too much recursion"; const BaseProxyHandler* handler = proxy->as().handler(); AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, BaseProxyHandler::GET, /* mayThrow = */ false); // Do the safe thing if the policy rejects. if (!policy.allowed()) { return handler->BaseProxyHandler::className(cx, proxy); } return handler->className(cx, proxy); } JSString* Proxy::fun_toString(JSContext* cx, HandleObject proxy, unsigned indent) { JS_CHECK_RECURSION(cx, return nullptr); const BaseProxyHandler* handler = proxy->as().handler(); AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, BaseProxyHandler::GET, /* mayThrow = */ false); // Do the safe thing if the policy rejects. if (!policy.allowed()) return handler->BaseProxyHandler::fun_toString(cx, proxy, indent); return handler->fun_toString(cx, proxy, indent); } bool Proxy::regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) { JS_CHECK_RECURSION(cx, return false); return proxy->as().handler()->regexp_toShared(cx, proxy, g); } bool Proxy::boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp) { JS_CHECK_RECURSION(cx, return false); return proxy->as().handler()->boxedValue_unbox(cx, proxy, vp); } JSObject * const TaggedProto::LazyProto = reinterpret_cast(0x1); /* static */ bool Proxy::getElements(JSContext* cx, HandleObject proxy, uint32_t begin, uint32_t end, ElementAdder* adder) { JS_CHECK_RECURSION(cx, return false); const BaseProxyHandler* handler = proxy->as().handler(); AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, BaseProxyHandler::GET, /* mayThrow = */ true); if (!policy.allowed()) { if (policy.returnValue()) { MOZ_ASSERT(!cx->isExceptionPending()); return js::GetElementsWithAdder(cx, proxy, proxy, begin, end, adder); } return false; } return handler->getElements(cx, proxy, begin, end, adder); } /* static */ void Proxy::trace(JSTracer* trc, JSObject* proxy) { const BaseProxyHandler* handler = proxy->as().handler(); handler->trace(trc, proxy); } bool js::proxy_LookupProperty(JSContext* cx, HandleObject obj, HandleId id, MutableHandleObject objp, MutableHandleShape propp) { bool found; if (!Proxy::has(cx, obj, id, &found)) return false; if (found) { MarkNonNativePropertyFound(propp); objp.set(obj); } else { objp.set(nullptr); propp.set(nullptr); } return true; } bool js::proxy_DefineProperty(JSContext* cx, HandleObject obj, HandleId id, Handle desc, ObjectOpResult& result) { return Proxy::defineProperty(cx, obj, id, desc, result); } bool js::proxy_HasProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* foundp) { return Proxy::has(cx, obj, id, foundp); } bool js::proxy_GetProperty(JSContext* cx, HandleObject obj, HandleValue receiver, HandleId id, MutableHandleValue vp) { return Proxy::get(cx, obj, receiver, id, vp); } bool js::proxy_SetProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v, HandleValue receiver, ObjectOpResult& result) { return Proxy::set(cx, obj, id, v, receiver, result); } bool js::proxy_GetOwnPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id, MutableHandle desc) { return Proxy::getOwnPropertyDescriptor(cx, obj, id, desc); } bool js::proxy_DeleteProperty(JSContext* cx, HandleObject obj, HandleId id, ObjectOpResult& result) { if (!Proxy::delete_(cx, obj, id, result)) return false; return SuppressDeletedProperty(cx, obj, id); // XXX is this necessary? } void js::proxy_Trace(JSTracer* trc, JSObject* obj) { MOZ_ASSERT(obj->is()); ProxyObject::trace(trc, obj); } /* static */ void ProxyObject::trace(JSTracer* trc, JSObject* obj) { ProxyObject* proxy = &obj->as(); TraceEdge(trc, &proxy->shape_, "ProxyObject_shape"); #ifdef DEBUG if (trc->runtime()->gc.isStrictProxyCheckingEnabled() && proxy->is()) { JSObject* referent = MaybeForwarded(proxy->target()); if (referent->compartment() != proxy->compartment()) { /* * Assert that this proxy is tracked in the wrapper map. We maintain * the invariant that the wrapped object is the key in the wrapper map. */ Value key = ObjectValue(*referent); WrapperMap::Ptr p = proxy->compartment()->lookupWrapper(key); MOZ_ASSERT(p); MOZ_ASSERT(*p->value().unsafeGet() == ObjectValue(*proxy)); } } #endif // Note: If you add new slots here, make sure to change // nuke() to cope. TraceCrossCompartmentEdge(trc, obj, proxy->slotOfPrivate(), "private"); TraceEdge(trc, proxy->slotOfExtra(0), "extra0"); /* * The GC can use the second reserved slot to link the cross compartment * wrappers into a linked list, in which case we don't want to trace it. */ if (!proxy->is()) TraceEdge(trc, proxy->slotOfExtra(1), "extra1"); Proxy::trace(trc, obj); } JSObject* js::proxy_WeakmapKeyDelegate(JSObject* obj) { MOZ_ASSERT(obj->is()); return obj->as().handler()->weakmapKeyDelegate(obj); } void js::proxy_Finalize(FreeOp* fop, JSObject* obj) { // Suppress a bogus warning about finalize(). JS::AutoSuppressGCAnalysis nogc; MOZ_ASSERT(obj->is()); obj->as().handler()->finalize(fop, obj); js_free(detail::GetProxyDataLayout(obj)->values); } void js::proxy_ObjectMoved(JSObject* obj, const JSObject* old) { MOZ_ASSERT(obj->is()); obj->as().handler()->objectMoved(obj, old); } bool js::proxy_HasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v, bool* bp) { return Proxy::hasInstance(cx, proxy, v, bp); } bool js::proxy_Call(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); RootedObject proxy(cx, &args.callee()); MOZ_ASSERT(proxy->is()); return Proxy::call(cx, proxy, args); } bool js::proxy_Construct(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); RootedObject proxy(cx, &args.callee()); MOZ_ASSERT(proxy->is()); return Proxy::construct(cx, proxy, args); } bool js::proxy_GetElements(JSContext* cx, HandleObject proxy, uint32_t begin, uint32_t end, ElementAdder* adder) { return Proxy::getElements(cx, proxy, begin, end, adder); } JSString* js::proxy_FunToString(JSContext* cx, HandleObject proxy, unsigned indent) { return Proxy::fun_toString(cx, proxy, indent); } const ClassOps js::ProxyClassOps = { nullptr, /* addProperty */ nullptr, /* delProperty */ nullptr, /* getProperty */ nullptr, /* setProperty */ nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ js::proxy_Finalize, /* finalize */ nullptr, /* call */ js::proxy_HasInstance, /* hasInstance */ nullptr, /* construct */ js::proxy_Trace, /* trace */ }; const ClassExtension js::ProxyClassExtension = PROXY_MAKE_EXT( js::proxy_ObjectMoved ); const ObjectOps js::ProxyObjectOps = { js::proxy_LookupProperty, js::proxy_DefineProperty, js::proxy_HasProperty, js::proxy_GetProperty, js::proxy_SetProperty, js::proxy_GetOwnPropertyDescriptor, js::proxy_DeleteProperty, js::proxy_GetElements, nullptr, /* enumerate */ js::proxy_FunToString }; const Class js::ProxyObject::proxyClass = PROXY_CLASS_DEF("Proxy", JSCLASS_HAS_CACHED_PROTO(JSProto_Proxy)); const Class* const js::ProxyClassPtr = &js::ProxyObject::proxyClass; JS_FRIEND_API(JSObject*) js::NewProxyObject(JSContext* cx, const BaseProxyHandler* handler, HandleValue priv, JSObject* proto_, const ProxyOptions& options) { if (options.lazyProto()) { MOZ_ASSERT(!proto_); proto_ = TaggedProto::LazyProto; } return ProxyObject::New(cx, handler, priv, TaggedProto(proto_), options); } void ProxyObject::renew(const BaseProxyHandler* handler, const Value& priv) { MOZ_ASSERT(!IsInsideNursery(this)); MOZ_ASSERT_IF(IsCrossCompartmentWrapper(this), IsDeadProxyObject(this)); MOZ_ASSERT(getClass() == &ProxyObject::proxyClass); MOZ_ASSERT(!IsWindowProxy(this)); MOZ_ASSERT(hasDynamicPrototype()); setHandler(handler); setCrossCompartmentPrivate(priv); setExtra(0, UndefinedValue()); setExtra(1, UndefinedValue()); } JS_FRIEND_API(JSObject*) js::InitProxyClass(JSContext* cx, HandleObject obj) { static const JSFunctionSpec static_methods[] = { JS_FN("revocable", proxy_revocable, 2, 0), JS_FS_END }; Handle global = obj.as(); RootedFunction ctor(cx); ctor = GlobalObject::createConstructor(cx, proxy, cx->names().Proxy, 2); if (!ctor) return nullptr; if (!JS_DefineFunctions(cx, ctor, static_methods)) return nullptr; if (!JS_DefineProperty(cx, obj, "Proxy", ctor, JSPROP_RESOLVING, JS_STUBGETTER, JS_STUBSETTER)) return nullptr; global->setConstructor(JSProto_Proxy, ObjectValue(*ctor)); return ctor; }