summaryrefslogtreecommitdiffstats
path: root/js/src/proxy
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/proxy')
-rw-r--r--js/src/proxy/BaseProxyHandler.cpp454
-rw-r--r--js/src/proxy/CrossCompartmentWrapper.cpp685
-rw-r--r--js/src/proxy/DeadObjectProxy.cpp175
-rw-r--r--js/src/proxy/DeadObjectProxy.h66
-rw-r--r--js/src/proxy/OpaqueCrossCompartmentWrapper.cpp194
-rw-r--r--js/src/proxy/Proxy.cpp812
-rw-r--r--js/src/proxy/Proxy.h79
-rw-r--r--js/src/proxy/ScriptedProxyHandler.cpp1428
-rw-r--r--js/src/proxy/ScriptedProxyHandler.h107
-rw-r--r--js/src/proxy/SecurityWrapper.cpp153
-rw-r--r--js/src/proxy/Wrapper.cpp420
11 files changed, 4573 insertions, 0 deletions
diff --git a/js/src/proxy/BaseProxyHandler.cpp b/js/src/proxy/BaseProxyHandler.cpp
new file mode 100644
index 000000000..8d5f30a19
--- /dev/null
+++ b/js/src/proxy/BaseProxyHandler.cpp
@@ -0,0 +1,454 @@
+/* -*- 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 "vm/ProxyObject.h"
+
+#include "jscntxtinlines.h"
+#include "jsobjinlines.h"
+
+using namespace js;
+
+using JS::IsArrayAnswer;
+
+bool
+BaseProxyHandler::enter(JSContext* cx, HandleObject wrapper, HandleId id, Action act,
+ bool* bp) const
+{
+ *bp = true;
+ return true;
+}
+
+bool
+BaseProxyHandler::has(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) const
+{
+ assertEnteredPolicy(cx, proxy, id, GET);
+
+ // This method is not covered by any spec, but we follow ES 2016
+ // (February 11, 2016) 9.1.7.1 fairly closely.
+
+ // Step 2. (Step 1 is a superfluous assertion.)
+ // Non-standard: Use our faster hasOwn trap.
+ if (!hasOwn(cx, proxy, id, bp))
+ return false;
+
+ // Step 3.
+ if (*bp)
+ return true;
+
+ // The spec calls this variable "parent", but that word has weird
+ // connotations in SpiderMonkey, so let's go with "proto".
+ // Step 4.
+ RootedObject proto(cx);
+ if (!GetPrototype(cx, proxy, &proto))
+ return false;
+
+ // Step 5.,5.a.
+ if (proto)
+ return HasProperty(cx, proto, id, bp);
+
+ // Step 6.
+ *bp = false;
+ return true;
+}
+
+bool
+BaseProxyHandler::getPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
+ MutableHandle<PropertyDescriptor> desc) const
+{
+ assertEnteredPolicy(cx, proxy, id, GET | SET | GET_PROPERTY_DESCRIPTOR);
+
+ if (!getOwnPropertyDescriptor(cx, proxy, id, desc))
+ return false;
+ if (desc.object())
+ return true;
+
+ RootedObject proto(cx);
+ if (!GetPrototype(cx, proxy, &proto))
+ return false;
+ if (!proto) {
+ MOZ_ASSERT(!desc.object());
+ return true;
+ }
+ return GetPropertyDescriptor(cx, proto, id, desc);
+}
+
+
+bool
+BaseProxyHandler::hasOwn(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) const
+{
+ assertEnteredPolicy(cx, proxy, id, GET);
+ Rooted<PropertyDescriptor> desc(cx);
+ if (!getOwnPropertyDescriptor(cx, proxy, id, &desc))
+ return false;
+ *bp = !!desc.object();
+ return true;
+}
+
+bool
+BaseProxyHandler::get(JSContext* cx, HandleObject proxy, HandleValue receiver,
+ HandleId id, MutableHandleValue vp) const
+{
+ assertEnteredPolicy(cx, proxy, id, GET);
+
+ // This method is not covered by any spec, but we follow ES 2016
+ // (January 21, 2016) 9.1.8 fairly closely.
+
+ // Step 2. (Step 1 is a superfluous assertion.)
+ Rooted<PropertyDescriptor> desc(cx);
+ if (!getOwnPropertyDescriptor(cx, proxy, id, &desc))
+ return false;
+ desc.assertCompleteIfFound();
+
+ // Step 3.
+ if (!desc.object()) {
+ // The spec calls this variable "parent", but that word has weird
+ // connotations in SpiderMonkey, so let's go with "proto".
+ // Step 3.a.
+ RootedObject proto(cx);
+ if (!GetPrototype(cx, proxy, &proto))
+ return false;
+
+ // Step 3.b.
+ if (!proto) {
+ vp.setUndefined();
+ return true;
+ }
+
+ // Step 3.c.
+ return GetProperty(cx, proto, receiver, id, vp);
+ }
+
+ // Step 4.
+ if (desc.isDataDescriptor()) {
+ vp.set(desc.value());
+ return true;
+ }
+
+ // Step 5.
+ MOZ_ASSERT(desc.isAccessorDescriptor());
+ RootedObject getter(cx, desc.getterObject());
+
+ // Step 6.
+ if (!getter) {
+ vp.setUndefined();
+ return true;
+ }
+
+ // Step 7.
+ RootedValue getterFunc(cx, ObjectValue(*getter));
+ return CallGetter(cx, receiver, getterFunc, vp);
+}
+
+bool
+BaseProxyHandler::set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v,
+ HandleValue receiver, ObjectOpResult& result) const
+{
+ assertEnteredPolicy(cx, proxy, id, SET);
+
+ // This method is not covered by any spec, but we follow ES6 draft rev 28
+ // (2014 Oct 14) 9.1.9 fairly closely, adapting it slightly for
+ // SpiderMonkey's particular foibles.
+
+ // Steps 2-3. (Step 1 is a superfluous assertion.)
+ Rooted<PropertyDescriptor> ownDesc(cx);
+ if (!getOwnPropertyDescriptor(cx, proxy, id, &ownDesc))
+ return false;
+ ownDesc.assertCompleteIfFound();
+
+ // The rest is factored out into a separate function with a weird name.
+ // This algorithm continues just below.
+ return SetPropertyIgnoringNamedGetter(cx, proxy, id, v, receiver, ownDesc, result);
+}
+
+bool
+js::SetPropertyIgnoringNamedGetter(JSContext* cx, HandleObject obj, HandleId id, HandleValue v,
+ HandleValue receiver, Handle<PropertyDescriptor> ownDesc_,
+ ObjectOpResult& result)
+{
+ Rooted<PropertyDescriptor> ownDesc(cx, ownDesc_);
+
+ // Step 4.
+ if (!ownDesc.object()) {
+ // The spec calls this variable "parent", but that word has weird
+ // connotations in SpiderMonkey, so let's go with "proto".
+ RootedObject proto(cx);
+ if (!GetPrototype(cx, obj, &proto))
+ return false;
+ if (proto)
+ return SetProperty(cx, proto, id, v, receiver, result);
+
+ // Step 4.d.
+ ownDesc.setDataDescriptor(UndefinedHandleValue, JSPROP_ENUMERATE);
+ }
+
+ // Step 5.
+ if (ownDesc.isDataDescriptor()) {
+ // Steps 5.a-b.
+ if (!ownDesc.writable())
+ return result.fail(JSMSG_READ_ONLY);
+ if (!receiver.isObject())
+ return result.fail(JSMSG_SET_NON_OBJECT_RECEIVER);
+ RootedObject receiverObj(cx, &receiver.toObject());
+
+ // Nonstandard SpiderMonkey special case: setter ops.
+ SetterOp setter = ownDesc.setter();
+ MOZ_ASSERT(setter != JS_StrictPropertyStub);
+ if (setter && setter != JS_StrictPropertyStub) {
+ RootedValue valCopy(cx, v);
+ return CallJSSetterOp(cx, setter, receiverObj, id, &valCopy, result);
+ }
+
+ // Steps 5.c-d.
+ Rooted<PropertyDescriptor> existingDescriptor(cx);
+ if (!GetOwnPropertyDescriptor(cx, receiverObj, id, &existingDescriptor))
+ return false;
+
+ // Step 5.e.
+ if (existingDescriptor.object()) {
+ // Step 5.e.i.
+ if (existingDescriptor.isAccessorDescriptor())
+ return result.fail(JSMSG_OVERWRITING_ACCESSOR);
+
+ // Step 5.e.ii.
+ if (!existingDescriptor.writable())
+ return result.fail(JSMSG_READ_ONLY);
+ }
+
+
+ // Steps 5.e.iii-iv. and 5.f.i.
+ unsigned attrs =
+ existingDescriptor.object()
+ ? JSPROP_IGNORE_ENUMERATE | JSPROP_IGNORE_READONLY | JSPROP_IGNORE_PERMANENT
+ : JSPROP_ENUMERATE;
+
+ // A very old nonstandard SpiderMonkey extension: default to the Class
+ // getter and setter ops.
+ const Class* clasp = receiverObj->getClass();
+ MOZ_ASSERT(clasp->getGetProperty() != JS_PropertyStub);
+ MOZ_ASSERT(clasp->getSetProperty() != JS_StrictPropertyStub);
+ return DefineProperty(cx, receiverObj, id, v,
+ clasp->getGetProperty(), clasp->getSetProperty(), attrs, result);
+ }
+
+ // Step 6.
+ MOZ_ASSERT(ownDesc.isAccessorDescriptor());
+ RootedObject setter(cx);
+ if (ownDesc.hasSetterObject())
+ setter = ownDesc.setterObject();
+ if (!setter)
+ return result.fail(JSMSG_GETTER_ONLY);
+ RootedValue setterValue(cx, ObjectValue(*setter));
+ if (!CallSetter(cx, receiver, setterValue, v))
+ return false;
+ return result.succeed();
+}
+
+bool
+BaseProxyHandler::getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject proxy,
+ AutoIdVector& props) const
+{
+ assertEnteredPolicy(cx, proxy, JSID_VOID, ENUMERATE);
+ MOZ_ASSERT(props.length() == 0);
+
+ if (!ownPropertyKeys(cx, proxy, props))
+ return false;
+
+ /* Select only the enumerable properties through in-place iteration. */
+ RootedId id(cx);
+ size_t i = 0;
+ for (size_t j = 0, len = props.length(); j < len; j++) {
+ MOZ_ASSERT(i <= j);
+ id = props[j];
+ if (JSID_IS_SYMBOL(id))
+ continue;
+
+ AutoWaivePolicy policy(cx, proxy, id, BaseProxyHandler::GET);
+ Rooted<PropertyDescriptor> desc(cx);
+ if (!getOwnPropertyDescriptor(cx, proxy, id, &desc))
+ return false;
+ desc.assertCompleteIfFound();
+
+ if (desc.object() && desc.enumerable())
+ props[i++].set(id);
+ }
+
+ MOZ_ASSERT(i <= props.length());
+ if (!props.resize(i))
+ return false;
+
+ return true;
+}
+
+bool
+BaseProxyHandler::enumerate(JSContext* cx, HandleObject proxy, MutableHandleObject objp) const
+{
+ assertEnteredPolicy(cx, proxy, JSID_VOID, ENUMERATE);
+
+ // GetPropertyKeys will invoke getOwnEnumerablePropertyKeys along the proto
+ // chain for us.
+ AutoIdVector props(cx);
+ if (!GetPropertyKeys(cx, proxy, 0, &props))
+ return false;
+
+ return EnumeratedIdVectorToIterator(cx, proxy, 0, props, objp);
+}
+
+bool
+BaseProxyHandler::call(JSContext* cx, HandleObject proxy, const CallArgs& args) const
+{
+ MOZ_CRASH("callable proxies should implement call trap");
+}
+
+bool
+BaseProxyHandler::construct(JSContext* cx, HandleObject proxy, const CallArgs& args) const
+{
+ MOZ_CRASH("callable proxies should implement construct trap");
+}
+
+const char*
+BaseProxyHandler::className(JSContext* cx, HandleObject proxy) const
+{
+ return proxy->isCallable() ? "Function" : "Object";
+}
+
+JSString*
+BaseProxyHandler::fun_toString(JSContext* cx, HandleObject proxy, unsigned indent) const
+{
+ if (proxy->isCallable())
+ return JS_NewStringCopyZ(cx, "function () {\n [native code]\n}");
+ RootedValue v(cx, ObjectValue(*proxy));
+ ReportIsNotFunction(cx, v);
+ return nullptr;
+}
+
+bool
+BaseProxyHandler::regexp_toShared(JSContext* cx, HandleObject proxy,
+ RegExpGuard* g) const
+{
+ MOZ_CRASH("This should have been a wrapped regexp");
+}
+
+bool
+BaseProxyHandler::boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp) const
+{
+ vp.setUndefined();
+ return true;
+}
+
+bool
+BaseProxyHandler::nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl,
+ const CallArgs& args) const
+{
+ ReportIncompatible(cx, args);
+ return false;
+}
+
+bool
+BaseProxyHandler::hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v,
+ bool* bp) const
+{
+ assertEnteredPolicy(cx, proxy, JSID_VOID, GET);
+ RootedValue val(cx, ObjectValue(*proxy.get()));
+ ReportValueError(cx, JSMSG_BAD_INSTANCEOF_RHS,
+ JSDVG_SEARCH_STACK, val, nullptr);
+ return false;
+}
+
+bool
+BaseProxyHandler::getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls) const
+{
+ *cls = ESClass::Other;
+ return true;
+}
+
+bool
+BaseProxyHandler::isArray(JSContext* cx, HandleObject proxy, IsArrayAnswer* answer) const
+{
+ *answer = IsArrayAnswer::NotArray;
+ return true;
+}
+
+void
+BaseProxyHandler::trace(JSTracer* trc, JSObject* proxy) const
+{
+}
+
+void
+BaseProxyHandler::finalize(JSFreeOp* fop, JSObject* proxy) const
+{
+}
+
+void
+BaseProxyHandler::objectMoved(JSObject* proxy, const JSObject* old) const
+{
+}
+
+JSObject*
+BaseProxyHandler::weakmapKeyDelegate(JSObject* proxy) const
+{
+ return nullptr;
+}
+
+bool
+BaseProxyHandler::getPrototype(JSContext* cx, HandleObject proxy, MutableHandleObject protop) const
+{
+ MOZ_CRASH("must override getPrototype with dynamic prototype");
+}
+
+bool
+BaseProxyHandler::setPrototype(JSContext* cx, HandleObject proxy, HandleObject proto,
+ ObjectOpResult& result) const
+{
+ // Disallow sets of protos on proxies with dynamic prototypes but no hook.
+ // This keeps us away from the footgun of having the first proto set opt
+ // you out of having dynamic protos altogether.
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_SET_PROTO_OF,
+ "incompatible Proxy");
+ return false;
+}
+
+bool
+BaseProxyHandler::setImmutablePrototype(JSContext* cx, HandleObject proxy, bool* succeeded) const
+{
+ *succeeded = false;
+ return true;
+}
+
+bool
+BaseProxyHandler::watch(JSContext* cx, HandleObject proxy, HandleId id, HandleObject callable) const
+{
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_WATCH,
+ proxy->getClass()->name);
+ return false;
+}
+
+bool
+BaseProxyHandler::unwatch(JSContext* cx, HandleObject proxy, HandleId id) const
+{
+ return true;
+}
+
+bool
+BaseProxyHandler::getElements(JSContext* cx, HandleObject proxy, uint32_t begin, uint32_t end,
+ ElementAdder* adder) const
+{
+ assertEnteredPolicy(cx, proxy, JSID_VOID, GET);
+
+ return js::GetElementsWithAdder(cx, proxy, proxy, begin, end, adder);
+}
+
+bool
+BaseProxyHandler::isCallable(JSObject* obj) const
+{
+ return false;
+}
+
+bool
+BaseProxyHandler::isConstructor(JSObject* obj) const
+{
+ return false;
+}
diff --git a/js/src/proxy/CrossCompartmentWrapper.cpp b/js/src/proxy/CrossCompartmentWrapper.cpp
new file mode 100644
index 000000000..2a6cb5400
--- /dev/null
+++ b/js/src/proxy/CrossCompartmentWrapper.cpp
@@ -0,0 +1,685 @@
+/* -*- 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 "jsiter.h"
+#include "jswrapper.h"
+
+#include "proxy/DeadObjectProxy.h"
+#include "vm/WrapperObject.h"
+
+#include "jscompartmentinlines.h"
+#include "jsobjinlines.h"
+
+using namespace js;
+
+#define PIERCE(cx, wrapper, pre, op, post) \
+ JS_BEGIN_MACRO \
+ bool ok; \
+ { \
+ AutoCompartment call(cx, wrappedObject(wrapper)); \
+ ok = (pre) && (op); \
+ } \
+ return ok && (post); \
+ JS_END_MACRO
+
+#define NOTHING (true)
+
+bool
+CrossCompartmentWrapper::getPropertyDescriptor(JSContext* cx, HandleObject wrapper, HandleId id,
+ MutableHandle<PropertyDescriptor> desc) const
+{
+ PIERCE(cx, wrapper,
+ NOTHING,
+ Wrapper::getPropertyDescriptor(cx, wrapper, id, desc),
+ cx->compartment()->wrap(cx, desc));
+}
+
+bool
+CrossCompartmentWrapper::getOwnPropertyDescriptor(JSContext* cx, HandleObject wrapper, HandleId id,
+ MutableHandle<PropertyDescriptor> desc) const
+{
+ PIERCE(cx, wrapper,
+ NOTHING,
+ Wrapper::getOwnPropertyDescriptor(cx, wrapper, id, desc),
+ cx->compartment()->wrap(cx, desc));
+}
+
+bool
+CrossCompartmentWrapper::defineProperty(JSContext* cx, HandleObject wrapper, HandleId id,
+ Handle<PropertyDescriptor> desc,
+ ObjectOpResult& result) const
+{
+ Rooted<PropertyDescriptor> desc2(cx, desc);
+ PIERCE(cx, wrapper,
+ cx->compartment()->wrap(cx, &desc2),
+ Wrapper::defineProperty(cx, wrapper, id, desc2, result),
+ NOTHING);
+}
+
+bool
+CrossCompartmentWrapper::ownPropertyKeys(JSContext* cx, HandleObject wrapper,
+ AutoIdVector& props) const
+{
+ PIERCE(cx, wrapper,
+ NOTHING,
+ Wrapper::ownPropertyKeys(cx, wrapper, props),
+ NOTHING);
+}
+
+bool
+CrossCompartmentWrapper::delete_(JSContext* cx, HandleObject wrapper, HandleId id,
+ ObjectOpResult& result) const
+{
+ PIERCE(cx, wrapper,
+ NOTHING,
+ Wrapper::delete_(cx, wrapper, id, result),
+ NOTHING);
+}
+
+bool
+CrossCompartmentWrapper::getPrototype(JSContext* cx, HandleObject wrapper,
+ MutableHandleObject protop) const
+{
+ {
+ RootedObject wrapped(cx, wrappedObject(wrapper));
+ AutoCompartment call(cx, wrapped);
+ if (!GetPrototype(cx, wrapped, protop))
+ return false;
+ if (protop) {
+ if (!protop->setDelegate(cx))
+ return false;
+ }
+ }
+
+ return cx->compartment()->wrap(cx, protop);
+}
+
+bool
+CrossCompartmentWrapper::setPrototype(JSContext* cx, HandleObject wrapper,
+ HandleObject proto, ObjectOpResult& result) const
+{
+ RootedObject protoCopy(cx, proto);
+ PIERCE(cx, wrapper,
+ cx->compartment()->wrap(cx, &protoCopy),
+ Wrapper::setPrototype(cx, wrapper, protoCopy, result),
+ NOTHING);
+}
+
+bool
+CrossCompartmentWrapper::getPrototypeIfOrdinary(JSContext* cx, HandleObject wrapper,
+ bool* isOrdinary, MutableHandleObject protop) const
+{
+ {
+ RootedObject wrapped(cx, wrappedObject(wrapper));
+ AutoCompartment call(cx, wrapped);
+ if (!GetPrototypeIfOrdinary(cx, wrapped, isOrdinary, protop))
+ return false;
+
+ if (!*isOrdinary)
+ return true;
+
+ if (protop) {
+ if (!protop->setDelegate(cx))
+ return false;
+ }
+ }
+
+ return cx->compartment()->wrap(cx, protop);
+}
+
+bool
+CrossCompartmentWrapper::setImmutablePrototype(JSContext* cx, HandleObject wrapper, bool* succeeded) const
+{
+ PIERCE(cx, wrapper,
+ NOTHING,
+ Wrapper::setImmutablePrototype(cx, wrapper, succeeded),
+ NOTHING);
+}
+
+bool
+CrossCompartmentWrapper::preventExtensions(JSContext* cx, HandleObject wrapper,
+ ObjectOpResult& result) const
+{
+ PIERCE(cx, wrapper,
+ NOTHING,
+ Wrapper::preventExtensions(cx, wrapper, result),
+ NOTHING);
+}
+
+bool
+CrossCompartmentWrapper::isExtensible(JSContext* cx, HandleObject wrapper, bool* extensible) const
+{
+ PIERCE(cx, wrapper,
+ NOTHING,
+ Wrapper::isExtensible(cx, wrapper, extensible),
+ NOTHING);
+}
+
+bool
+CrossCompartmentWrapper::has(JSContext* cx, HandleObject wrapper, HandleId id, bool* bp) const
+{
+ PIERCE(cx, wrapper,
+ NOTHING,
+ Wrapper::has(cx, wrapper, id, bp),
+ NOTHING);
+}
+
+bool
+CrossCompartmentWrapper::hasOwn(JSContext* cx, HandleObject wrapper, HandleId id, bool* bp) const
+{
+ PIERCE(cx, wrapper,
+ NOTHING,
+ Wrapper::hasOwn(cx, wrapper, id, bp),
+ NOTHING);
+}
+
+static bool
+WrapReceiver(JSContext* cx, HandleObject wrapper, MutableHandleValue receiver)
+{
+ // Usually the receiver is the wrapper and we can just unwrap it. If the
+ // wrapped object is also a wrapper, things are more complicated and we
+ // fall back to the slow path (it calls UncheckedUnwrap to unwrap all
+ // wrappers).
+ if (ObjectValue(*wrapper) == receiver) {
+ JSObject* wrapped = Wrapper::wrappedObject(wrapper);
+ if (!IsWrapper(wrapped)) {
+ MOZ_ASSERT(wrapped->compartment() == cx->compartment());
+ MOZ_ASSERT(!IsWindow(wrapped));
+ receiver.setObject(*wrapped);
+ return true;
+ }
+ }
+
+ return cx->compartment()->wrap(cx, receiver);
+}
+
+bool
+CrossCompartmentWrapper::get(JSContext* cx, HandleObject wrapper, HandleValue receiver,
+ HandleId id, MutableHandleValue vp) const
+{
+ RootedValue receiverCopy(cx, receiver);
+ {
+ AutoCompartment call(cx, wrappedObject(wrapper));
+ if (!WrapReceiver(cx, wrapper, &receiverCopy))
+ return false;
+
+ if (!Wrapper::get(cx, wrapper, receiverCopy, id, vp))
+ return false;
+ }
+ return cx->compartment()->wrap(cx, vp);
+}
+
+bool
+CrossCompartmentWrapper::set(JSContext* cx, HandleObject wrapper, HandleId id, HandleValue v,
+ HandleValue receiver, ObjectOpResult& result) const
+{
+ RootedValue valCopy(cx, v);
+ RootedValue receiverCopy(cx, receiver);
+ PIERCE(cx, wrapper,
+ cx->compartment()->wrap(cx, &valCopy) &&
+ WrapReceiver(cx, wrapper, &receiverCopy),
+ Wrapper::set(cx, wrapper, id, valCopy, receiverCopy, result),
+ NOTHING);
+}
+
+bool
+CrossCompartmentWrapper::getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject wrapper,
+ AutoIdVector& props) const
+{
+ PIERCE(cx, wrapper,
+ NOTHING,
+ Wrapper::getOwnEnumerablePropertyKeys(cx, wrapper, props),
+ NOTHING);
+}
+
+/*
+ * We can reify non-escaping iterator objects instead of having to wrap them. This
+ * allows fast iteration over objects across a compartment boundary.
+ */
+static bool
+CanReify(HandleObject obj)
+{
+ return obj->is<PropertyIteratorObject>() &&
+ (obj->as<PropertyIteratorObject>().getNativeIterator()->flags & JSITER_ENUMERATE);
+}
+
+struct AutoCloseIterator
+{
+ AutoCloseIterator(JSContext* cx, JSObject* obj) : cx(cx), obj(cx, obj) {}
+
+ ~AutoCloseIterator() { if (obj) CloseIterator(cx, obj); }
+
+ void clear() { obj = nullptr; }
+
+ private:
+ JSContext* cx;
+ RootedObject obj;
+};
+
+static bool
+Reify(JSContext* cx, JSCompartment* origin, MutableHandleObject objp)
+{
+ Rooted<PropertyIteratorObject*> iterObj(cx, &objp->as<PropertyIteratorObject>());
+ NativeIterator* ni = iterObj->getNativeIterator();
+
+ AutoCloseIterator close(cx, iterObj);
+
+ /* Wrap the iteratee. */
+ RootedObject obj(cx, ni->obj);
+ if (!origin->wrap(cx, &obj))
+ return false;
+
+ /*
+ * Wrap the elements in the iterator's snapshot.
+ * N.B. the order of closing/creating iterators is important due to the
+ * implicit cx->enumerators state.
+ */
+ size_t length = ni->numKeys();
+ AutoIdVector keys(cx);
+ if (length > 0) {
+ if (!keys.reserve(length))
+ return false;
+ for (size_t i = 0; i < length; ++i) {
+ RootedId id(cx);
+ RootedValue v(cx, StringValue(ni->begin()[i]));
+ if (!ValueToId<CanGC>(cx, v, &id))
+ return false;
+ keys.infallibleAppend(id);
+ }
+ }
+
+ close.clear();
+ if (!CloseIterator(cx, iterObj))
+ return false;
+
+ return EnumeratedIdVectorToIterator(cx, obj, ni->flags, keys, objp);
+}
+
+bool
+CrossCompartmentWrapper::enumerate(JSContext* cx, HandleObject wrapper,
+ MutableHandleObject objp) const
+{
+ {
+ AutoCompartment call(cx, wrappedObject(wrapper));
+ if (!Wrapper::enumerate(cx, wrapper, objp))
+ return false;
+ }
+
+ if (CanReify(objp))
+ return Reify(cx, cx->compartment(), objp);
+ return cx->compartment()->wrap(cx, objp);
+}
+
+bool
+CrossCompartmentWrapper::call(JSContext* cx, HandleObject wrapper, const CallArgs& args) const
+{
+ RootedObject wrapped(cx, wrappedObject(wrapper));
+
+ {
+ AutoCompartment call(cx, wrapped);
+
+ args.setCallee(ObjectValue(*wrapped));
+ if (!cx->compartment()->wrap(cx, args.mutableThisv()))
+ return false;
+
+ for (size_t n = 0; n < args.length(); ++n) {
+ if (!cx->compartment()->wrap(cx, args[n]))
+ return false;
+ }
+
+ if (!Wrapper::call(cx, wrapper, args))
+ return false;
+ }
+
+ return cx->compartment()->wrap(cx, args.rval());
+}
+
+bool
+CrossCompartmentWrapper::construct(JSContext* cx, HandleObject wrapper, const CallArgs& args) const
+{
+ RootedObject wrapped(cx, wrappedObject(wrapper));
+ {
+ AutoCompartment call(cx, wrapped);
+
+ for (size_t n = 0; n < args.length(); ++n) {
+ if (!cx->compartment()->wrap(cx, args[n]))
+ return false;
+ }
+ if (!cx->compartment()->wrap(cx, args.newTarget()))
+ return false;
+ if (!Wrapper::construct(cx, wrapper, args))
+ return false;
+ }
+ return cx->compartment()->wrap(cx, args.rval());
+}
+
+bool
+CrossCompartmentWrapper::nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl,
+ const CallArgs& srcArgs) const
+{
+ RootedObject wrapper(cx, &srcArgs.thisv().toObject());
+ MOZ_ASSERT(srcArgs.thisv().isMagic(JS_IS_CONSTRUCTING) ||
+ !UncheckedUnwrap(wrapper)->is<CrossCompartmentWrapperObject>());
+
+ RootedObject wrapped(cx, wrappedObject(wrapper));
+ {
+ AutoCompartment call(cx, wrapped);
+ InvokeArgs dstArgs(cx);
+ if (!dstArgs.init(cx, srcArgs.length()))
+ return false;
+
+ Value* src = srcArgs.base();
+ Value* srcend = srcArgs.array() + srcArgs.length();
+ Value* dst = dstArgs.base();
+
+ RootedValue source(cx);
+ for (; src < srcend; ++src, ++dst) {
+ source = *src;
+ if (!cx->compartment()->wrap(cx, &source))
+ return false;
+ *dst = source.get();
+
+ // Handle |this| specially. When we rewrap on the other side of the
+ // membrane, we might apply a same-compartment security wrapper that
+ // will stymie this whole process. If that happens, unwrap the wrapper.
+ // This logic can go away when same-compartment security wrappers go away.
+ if ((src == srcArgs.base() + 1) && dst->isObject()) {
+ RootedObject thisObj(cx, &dst->toObject());
+ if (thisObj->is<WrapperObject>() &&
+ Wrapper::wrapperHandler(thisObj)->hasSecurityPolicy())
+ {
+ MOZ_ASSERT(!thisObj->is<CrossCompartmentWrapperObject>());
+ *dst = ObjectValue(*Wrapper::wrappedObject(thisObj));
+ }
+ }
+ }
+
+ if (!CallNonGenericMethod(cx, test, impl, dstArgs))
+ return false;
+
+ srcArgs.rval().set(dstArgs.rval());
+ }
+ return cx->compartment()->wrap(cx, srcArgs.rval());
+}
+
+bool
+CrossCompartmentWrapper::hasInstance(JSContext* cx, HandleObject wrapper, MutableHandleValue v,
+ bool* bp) const
+{
+ AutoCompartment call(cx, wrappedObject(wrapper));
+ if (!cx->compartment()->wrap(cx, v))
+ return false;
+ return Wrapper::hasInstance(cx, wrapper, v, bp);
+}
+
+const char*
+CrossCompartmentWrapper::className(JSContext* cx, HandleObject wrapper) const
+{
+ AutoCompartment call(cx, wrappedObject(wrapper));
+ return Wrapper::className(cx, wrapper);
+}
+
+JSString*
+CrossCompartmentWrapper::fun_toString(JSContext* cx, HandleObject wrapper, unsigned indent) const
+{
+ RootedString str(cx);
+ {
+ AutoCompartment call(cx, wrappedObject(wrapper));
+ str = Wrapper::fun_toString(cx, wrapper, indent);
+ if (!str)
+ return nullptr;
+ }
+ if (!cx->compartment()->wrap(cx, &str))
+ return nullptr;
+ return str;
+}
+
+bool
+CrossCompartmentWrapper::regexp_toShared(JSContext* cx, HandleObject wrapper, RegExpGuard* g) const
+{
+ RegExpGuard wrapperGuard(cx);
+ {
+ AutoCompartment call(cx, wrappedObject(wrapper));
+ if (!Wrapper::regexp_toShared(cx, wrapper, &wrapperGuard))
+ return false;
+ }
+
+ // Get an equivalent RegExpShared associated with the current compartment.
+ RegExpShared* re = wrapperGuard.re();
+ return cx->compartment()->regExps.get(cx, re->getSource(), re->getFlags(), g);
+}
+
+bool
+CrossCompartmentWrapper::boxedValue_unbox(JSContext* cx, HandleObject wrapper, MutableHandleValue vp) const
+{
+ PIERCE(cx, wrapper,
+ NOTHING,
+ Wrapper::boxedValue_unbox(cx, wrapper, vp),
+ cx->compartment()->wrap(cx, vp));
+}
+
+const CrossCompartmentWrapper CrossCompartmentWrapper::singleton(0u);
+
+bool
+js::IsCrossCompartmentWrapper(JSObject* obj)
+{
+ return IsWrapper(obj) &&
+ !!(Wrapper::wrapperHandler(obj)->flags() & Wrapper::CROSS_COMPARTMENT);
+}
+
+static void
+NukeRemovedCrossCompartmentWrapper(JSContext* cx, JSObject* wrapper)
+{
+ MOZ_ASSERT(wrapper->is<CrossCompartmentWrapperObject>());
+
+ NotifyGCNukeWrapper(wrapper);
+
+ wrapper->as<ProxyObject>().nuke();
+
+ MOZ_ASSERT(IsDeadProxyObject(wrapper));
+}
+
+JS_FRIEND_API(void)
+js::NukeCrossCompartmentWrapper(JSContext* cx, JSObject* wrapper)
+{
+ JSCompartment* comp = wrapper->compartment();
+ auto ptr = comp->lookupWrapper(ObjectValue(*Wrapper::wrappedObject(wrapper)));
+ if (ptr)
+ comp->removeWrapper(ptr);
+ NukeRemovedCrossCompartmentWrapper(cx, wrapper);
+}
+
+/*
+ * NukeChromeCrossCompartmentWrappersForGlobal reaches into chrome and cuts
+ * all of the cross-compartment wrappers that point to objects parented to
+ * obj's global. The snag here is that we need to avoid cutting wrappers that
+ * point to the window object on page navigation (inner window destruction)
+ * and only do that on tab close (outer window destruction). Thus the
+ * option of how to handle the global object.
+ */
+JS_FRIEND_API(bool)
+js::NukeCrossCompartmentWrappers(JSContext* cx,
+ const CompartmentFilter& sourceFilter,
+ const CompartmentFilter& targetFilter,
+ js::NukeReferencesToWindow nukeReferencesToWindow)
+{
+ CHECK_REQUEST(cx);
+ JSRuntime* rt = cx->runtime();
+
+ rt->gc.evictNursery(JS::gcreason::EVICT_NURSERY);
+
+ // Iterate through scopes looking for system cross compartment wrappers
+ // that point to an object that shares a global with obj.
+
+ for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) {
+ if (!sourceFilter.match(c))
+ continue;
+
+ // Iterate the wrappers looking for anything interesting.
+ for (JSCompartment::WrapperEnum e(c); !e.empty(); e.popFront()) {
+ // Some cross-compartment wrappers are for strings. We're not
+ // interested in those.
+ const CrossCompartmentKey& k = e.front().key();
+ if (!k.is<JSObject*>())
+ continue;
+
+ AutoWrapperRooter wobj(cx, WrapperValue(e));
+ JSObject* wrapped = UncheckedUnwrap(wobj);
+
+ if (nukeReferencesToWindow == DontNukeWindowReferences &&
+ IsWindowProxy(wrapped))
+ {
+ continue;
+ }
+
+ if (targetFilter.match(wrapped->compartment())) {
+ // We found a wrapper to nuke.
+ e.removeFront();
+ NukeRemovedCrossCompartmentWrapper(cx, wobj);
+ }
+ }
+ }
+
+ return true;
+}
+
+// Given a cross-compartment wrapper |wobj|, update it to point to
+// |newTarget|. This recomputes the wrapper with JS_WrapValue, and thus can be
+// useful even if wrapper already points to newTarget.
+// This operation crashes on failure rather than leaving the heap in an
+// inconsistent state.
+void
+js::RemapWrapper(JSContext* cx, JSObject* wobjArg, JSObject* newTargetArg)
+{
+ MOZ_ASSERT(!IsInsideNursery(wobjArg));
+ MOZ_ASSERT(!IsInsideNursery(newTargetArg));
+
+ RootedObject wobj(cx, wobjArg);
+ RootedObject newTarget(cx, newTargetArg);
+ MOZ_ASSERT(wobj->is<CrossCompartmentWrapperObject>());
+ MOZ_ASSERT(!newTarget->is<CrossCompartmentWrapperObject>());
+ JSObject* origTarget = Wrapper::wrappedObject(wobj);
+ MOZ_ASSERT(origTarget);
+ Value origv = ObjectValue(*origTarget);
+ JSCompartment* wcompartment = wobj->compartment();
+
+ AutoDisableProxyCheck adpc(cx->runtime());
+
+ // If we're mapping to a different target (as opposed to just recomputing
+ // for the same target), we must not have an existing wrapper for the new
+ // target, otherwise this will break.
+ MOZ_ASSERT_IF(origTarget != newTarget,
+ !wcompartment->lookupWrapper(ObjectValue(*newTarget)));
+
+ // The old value should still be in the cross-compartment wrapper map, and
+ // the lookup should return wobj.
+ WrapperMap::Ptr p = wcompartment->lookupWrapper(origv);
+ MOZ_ASSERT(&p->value().unsafeGet()->toObject() == wobj);
+ wcompartment->removeWrapper(p);
+
+ // When we remove origv from the wrapper map, its wrapper, wobj, must
+ // immediately cease to be a cross-compartment wrapper. Nuke it.
+ NukeCrossCompartmentWrapper(cx, wobj);
+
+ // First, we wrap it in the new compartment. We try to use the existing
+ // wrapper, |wobj|, since it's been nuked anyway. The wrap() function has
+ // the choice to reuse |wobj| or not.
+ RootedObject tobj(cx, newTarget);
+ AutoCompartment ac(cx, wobj);
+ if (!wcompartment->rewrap(cx, &tobj, wobj))
+ MOZ_CRASH();
+
+ // If wrap() reused |wobj|, it will have overwritten it and returned with
+ // |tobj == wobj|. Otherwise, |tobj| will point to a new wrapper and |wobj|
+ // will still be nuked. In the latter case, we replace |wobj| with the
+ // contents of the new wrapper in |tobj|.
+ if (tobj != wobj) {
+ // Now, because we need to maintain object identity, we do a brain
+ // transplant on the old object so that it contains the contents of the
+ // new one.
+ if (!JSObject::swap(cx, wobj, tobj))
+ MOZ_CRASH();
+ }
+
+ // Before swapping, this wrapper came out of wrap(), which enforces the
+ // invariant that the wrapper in the map points directly to the key.
+ MOZ_ASSERT(Wrapper::wrappedObject(wobj) == newTarget);
+
+ // Update the entry in the compartment's wrapper map to point to the old
+ // wrapper, which has now been updated (via reuse or swap).
+ MOZ_ASSERT(wobj->is<WrapperObject>());
+ if (!wcompartment->putWrapper(cx, CrossCompartmentKey(newTarget), ObjectValue(*wobj)))
+ MOZ_CRASH();
+}
+
+// Remap all cross-compartment wrappers pointing to |oldTarget| to point to
+// |newTarget|. All wrappers are recomputed.
+JS_FRIEND_API(bool)
+js::RemapAllWrappersForObject(JSContext* cx, JSObject* oldTargetArg,
+ JSObject* newTargetArg)
+{
+ MOZ_ASSERT(!IsInsideNursery(oldTargetArg));
+ MOZ_ASSERT(!IsInsideNursery(newTargetArg));
+
+ RootedValue origv(cx, ObjectValue(*oldTargetArg));
+ RootedObject newTarget(cx, newTargetArg);
+
+ AutoWrapperVector toTransplant(cx);
+ if (!toTransplant.reserve(cx->runtime()->numCompartments))
+ return false;
+
+ for (CompartmentsIter c(cx->runtime(), SkipAtoms); !c.done(); c.next()) {
+ if (WrapperMap::Ptr wp = c->lookupWrapper(origv)) {
+ // We found a wrapper. Remember and root it.
+ toTransplant.infallibleAppend(WrapperValue(wp));
+ }
+ }
+
+ for (const WrapperValue& v : toTransplant)
+ RemapWrapper(cx, &v.toObject(), newTarget);
+
+ return true;
+}
+
+JS_FRIEND_API(bool)
+js::RecomputeWrappers(JSContext* cx, const CompartmentFilter& sourceFilter,
+ const CompartmentFilter& targetFilter)
+{
+ // Drop any nursery-allocated wrappers.
+ cx->runtime()->gc.evictNursery(JS::gcreason::EVICT_NURSERY);
+
+ AutoWrapperVector toRecompute(cx);
+ for (CompartmentsIter c(cx->runtime(), SkipAtoms); !c.done(); c.next()) {
+ // Filter by source compartment.
+ if (!sourceFilter.match(c))
+ continue;
+
+ // Iterate over the wrappers, filtering appropriately.
+ for (JSCompartment::WrapperEnum e(c); !e.empty(); e.popFront()) {
+ // Filter out non-objects.
+ CrossCompartmentKey& k = e.front().mutableKey();
+ if (!k.is<JSObject*>())
+ continue;
+
+ // Filter by target compartment.
+ if (!targetFilter.match(k.compartment()))
+ continue;
+
+ // Add it to the list.
+ if (!toRecompute.append(WrapperValue(e)))
+ return false;
+ }
+ }
+
+ // Recompute all the wrappers in the list.
+ for (const WrapperValue& v : toRecompute) {
+ JSObject* wrapper = &v.toObject();
+ JSObject* wrapped = Wrapper::wrappedObject(wrapper);
+ RemapWrapper(cx, wrapper, wrapped);
+ }
+
+ return true;
+}
diff --git a/js/src/proxy/DeadObjectProxy.cpp b/js/src/proxy/DeadObjectProxy.cpp
new file mode 100644
index 000000000..00ca034f3
--- /dev/null
+++ b/js/src/proxy/DeadObjectProxy.cpp
@@ -0,0 +1,175 @@
+/* -*- 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 "proxy/DeadObjectProxy.h"
+
+#include "jsapi.h"
+#include "jsfun.h" // XXXefaust Bug 1064662
+
+#include "proxy/ScriptedProxyHandler.h"
+#include "vm/ProxyObject.h"
+
+using namespace js;
+using namespace js::gc;
+
+static void
+ReportDead(JSContext *cx)
+{
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT);
+}
+
+bool
+DeadObjectProxy::getOwnPropertyDescriptor(JSContext* cx, HandleObject wrapper, HandleId id,
+ MutableHandle<PropertyDescriptor> desc) const
+{
+ ReportDead(cx);
+ return false;
+}
+
+bool
+DeadObjectProxy::defineProperty(JSContext* cx, HandleObject wrapper, HandleId id,
+ Handle<PropertyDescriptor> desc,
+ ObjectOpResult& result) const
+{
+ ReportDead(cx);
+ return false;
+}
+
+bool
+DeadObjectProxy::ownPropertyKeys(JSContext* cx, HandleObject wrapper,
+ AutoIdVector& props) const
+{
+ ReportDead(cx);
+ return false;
+}
+
+bool
+DeadObjectProxy::delete_(JSContext* cx, HandleObject wrapper, HandleId id,
+ ObjectOpResult& result) const
+{
+ ReportDead(cx);
+ return false;
+}
+
+bool
+DeadObjectProxy::getPrototype(JSContext* cx, HandleObject proxy, MutableHandleObject protop) const
+{
+ protop.set(nullptr);
+ return true;
+}
+
+bool
+DeadObjectProxy::getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary,
+ MutableHandleObject protop) const
+{
+ *isOrdinary = false;
+ return true;
+}
+
+bool
+DeadObjectProxy::preventExtensions(JSContext* cx, HandleObject proxy, ObjectOpResult& result) const
+{
+ ReportDead(cx);
+ return false;
+}
+
+bool
+DeadObjectProxy::isExtensible(JSContext* cx, HandleObject proxy, bool* extensible) const
+{
+ // This is kind of meaningless, but dead-object semantics aside,
+ // [[Extensible]] always being true is consistent with other proxy types.
+ *extensible = true;
+ return true;
+}
+
+bool
+DeadObjectProxy::call(JSContext* cx, HandleObject wrapper, const CallArgs& args) const
+{
+ ReportDead(cx);
+ return false;
+}
+
+bool
+DeadObjectProxy::construct(JSContext* cx, HandleObject wrapper, const CallArgs& args) const
+{
+ ReportDead(cx);
+ return false;
+}
+
+bool
+DeadObjectProxy::nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl,
+ const CallArgs& args) const
+{
+ ReportDead(cx);
+ return false;
+}
+
+bool
+DeadObjectProxy::hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v,
+ bool* bp) const
+{
+ ReportDead(cx);
+ return false;
+}
+
+bool
+DeadObjectProxy::getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls) const
+{
+ ReportDead(cx);
+ return false;
+}
+
+bool
+DeadObjectProxy::isArray(JSContext* cx, HandleObject obj, JS::IsArrayAnswer* answer) const
+{
+ ReportDead(cx);
+ return false;
+}
+
+const char*
+DeadObjectProxy::className(JSContext* cx, HandleObject wrapper) const
+{
+ return "DeadObject";
+}
+
+JSString*
+DeadObjectProxy::fun_toString(JSContext* cx, HandleObject proxy, unsigned indent) const
+{
+ ReportDead(cx);
+ return nullptr;
+}
+
+bool
+DeadObjectProxy::regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const
+{
+ ReportDead(cx);
+ return false;
+}
+
+bool
+DeadObjectProxy::isCallable(JSObject* obj) const
+{
+ static const uint32_t slot = ScriptedProxyHandler::IS_CALLCONSTRUCT_EXTRA;
+ uint32_t callConstruct = obj->as<ProxyObject>().extra(slot).toPrivateUint32();
+ return !!(callConstruct & ScriptedProxyHandler::IS_CALLABLE);
+}
+
+bool
+DeadObjectProxy::isConstructor(JSObject* obj) const
+{
+ static const uint32_t slot = ScriptedProxyHandler::IS_CALLCONSTRUCT_EXTRA;
+ uint32_t callConstruct = obj->as<ProxyObject>().extra(slot).toPrivateUint32();
+ return !!(callConstruct & ScriptedProxyHandler::IS_CONSTRUCTOR);
+}
+
+const char DeadObjectProxy::family = 0;
+const DeadObjectProxy DeadObjectProxy::singleton;
+
+bool
+js::IsDeadProxyObject(JSObject* obj)
+{
+ return IsDerivedProxyObject(obj, &DeadObjectProxy::singleton);
+}
diff --git a/js/src/proxy/DeadObjectProxy.h b/js/src/proxy/DeadObjectProxy.h
new file mode 100644
index 000000000..032b3ce38
--- /dev/null
+++ b/js/src/proxy/DeadObjectProxy.h
@@ -0,0 +1,66 @@
+/* -*- 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/. */
+
+#ifndef proxy_DeadObjectProxy_h
+#define proxy_DeadObjectProxy_h
+
+#include "js/Proxy.h"
+
+namespace js {
+
+class DeadObjectProxy : public BaseProxyHandler
+{
+ public:
+ explicit constexpr DeadObjectProxy()
+ : BaseProxyHandler(&family)
+ { }
+
+ /* Standard internal methods. */
+ virtual bool getOwnPropertyDescriptor(JSContext* cx, HandleObject wrapper, HandleId id,
+ MutableHandle<PropertyDescriptor> desc) const override;
+ virtual bool defineProperty(JSContext* cx, HandleObject wrapper, HandleId id,
+ Handle<PropertyDescriptor> desc,
+ ObjectOpResult& result) const override;
+ virtual bool ownPropertyKeys(JSContext* cx, HandleObject wrapper,
+ AutoIdVector& props) const override;
+ virtual bool delete_(JSContext* cx, HandleObject wrapper, HandleId id,
+ ObjectOpResult& result) const override;
+ virtual bool getPrototype(JSContext* cx, HandleObject proxy,
+ MutableHandleObject protop) const override;
+ virtual bool getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary,
+ MutableHandleObject protop) const override;
+ virtual bool preventExtensions(JSContext* cx, HandleObject proxy,
+ ObjectOpResult& result) const override;
+ virtual bool isExtensible(JSContext* cx, HandleObject proxy, bool* extensible) const override;
+ virtual bool call(JSContext* cx, HandleObject proxy, const CallArgs& args) const override;
+ virtual bool construct(JSContext* cx, HandleObject proxy, const CallArgs& args) const override;
+
+ /* SpiderMonkey extensions. */
+ // BaseProxyHandler::getPropertyDescriptor will throw by calling getOwnPropertyDescriptor.
+ // BaseProxyHandler::enumerate will throw by calling ownKeys.
+ virtual bool nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl,
+ const CallArgs& args) const override;
+ virtual bool hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v,
+ bool* bp) const override;
+ virtual bool getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls) const override;
+ virtual bool isArray(JSContext* cx, HandleObject proxy, JS::IsArrayAnswer* answer) const override;
+ virtual const char* className(JSContext* cx, HandleObject proxy) const override;
+ virtual JSString* fun_toString(JSContext* cx, HandleObject proxy, unsigned indent) const override;
+ virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const override;
+
+ virtual bool isCallable(JSObject* obj) const override;
+ virtual bool isConstructor(JSObject* obj) const override;
+
+ static const char family;
+ static const DeadObjectProxy singleton;
+};
+
+bool
+IsDeadProxyObject(JSObject* obj);
+
+} /* namespace js */
+
+#endif /* proxy_DeadObjectProxy_h */
diff --git a/js/src/proxy/OpaqueCrossCompartmentWrapper.cpp b/js/src/proxy/OpaqueCrossCompartmentWrapper.cpp
new file mode 100644
index 000000000..ff3f4145c
--- /dev/null
+++ b/js/src/proxy/OpaqueCrossCompartmentWrapper.cpp
@@ -0,0 +1,194 @@
+/* -*- 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 "jswrapper.h"
+
+#include "jsobjinlines.h"
+
+using namespace js;
+
+bool
+OpaqueCrossCompartmentWrapper::getOwnPropertyDescriptor(JSContext* cx,
+ HandleObject wrapper,
+ HandleId id,
+ MutableHandle<PropertyDescriptor> desc) const
+{
+ desc.object().set(nullptr);
+ return true;
+}
+
+bool
+OpaqueCrossCompartmentWrapper::defineProperty(JSContext* cx, HandleObject wrapper, HandleId id,
+ Handle<PropertyDescriptor> desc,
+ ObjectOpResult& result) const
+{
+ return result.succeed();
+}
+
+bool
+OpaqueCrossCompartmentWrapper::ownPropertyKeys(JSContext* cx, HandleObject wrapper,
+ AutoIdVector& props) const
+{
+ return true;
+}
+
+bool
+OpaqueCrossCompartmentWrapper::delete_(JSContext* cx, HandleObject wrapper, HandleId id,
+ ObjectOpResult& result) const
+{
+ return result.succeed();
+}
+
+bool
+OpaqueCrossCompartmentWrapper::enumerate(JSContext* cx, HandleObject wrapper,
+ MutableHandleObject objp) const
+{
+ return BaseProxyHandler::enumerate(cx, wrapper, objp);
+}
+
+bool
+OpaqueCrossCompartmentWrapper::getPrototype(JSContext* cx, HandleObject proxy,
+ MutableHandleObject protop) const
+{
+ protop.set(nullptr);
+ return true;
+}
+
+bool
+OpaqueCrossCompartmentWrapper::setPrototype(JSContext* cx, HandleObject proxy, HandleObject proto,
+ ObjectOpResult& result) const
+{
+ return result.succeed();
+}
+
+bool
+OpaqueCrossCompartmentWrapper::getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy,
+ bool* isOrdinary,
+ MutableHandleObject protop) const
+{
+ *isOrdinary = false;
+ return true;
+}
+
+bool
+OpaqueCrossCompartmentWrapper::setImmutablePrototype(JSContext* cx, HandleObject proxy,
+ bool* succeeded) const
+{
+ *succeeded = false;
+ return true;
+}
+
+bool
+OpaqueCrossCompartmentWrapper::preventExtensions(JSContext* cx, HandleObject wrapper,
+ ObjectOpResult& result) const
+{
+ return result.failCantPreventExtensions();
+}
+
+bool
+OpaqueCrossCompartmentWrapper::isExtensible(JSContext* cx, HandleObject wrapper,
+ bool* extensible) const
+{
+ *extensible = true;
+ return true;
+}
+
+bool
+OpaqueCrossCompartmentWrapper::has(JSContext* cx, HandleObject wrapper, HandleId id,
+ bool* bp) const
+{
+ return BaseProxyHandler::has(cx, wrapper, id, bp);
+}
+
+bool
+OpaqueCrossCompartmentWrapper::get(JSContext* cx, HandleObject wrapper, HandleValue receiver,
+ HandleId id, MutableHandleValue vp) const
+{
+ return BaseProxyHandler::get(cx, wrapper, receiver, id, vp);
+}
+
+bool
+OpaqueCrossCompartmentWrapper::set(JSContext* cx, HandleObject wrapper, HandleId id,
+ HandleValue v, HandleValue receiver,
+ ObjectOpResult& result) const
+{
+ return BaseProxyHandler::set(cx, wrapper, id, v, receiver, result);
+}
+
+bool
+OpaqueCrossCompartmentWrapper::call(JSContext* cx, HandleObject wrapper,
+ const CallArgs& args) const
+{
+ RootedValue v(cx, ObjectValue(*wrapper));
+ ReportIsNotFunction(cx, v);
+ return false;
+}
+
+bool
+OpaqueCrossCompartmentWrapper::construct(JSContext* cx, HandleObject wrapper,
+ const CallArgs& args) const
+{
+ RootedValue v(cx, ObjectValue(*wrapper));
+ ReportIsNotFunction(cx, v);
+ return false;
+}
+
+bool
+OpaqueCrossCompartmentWrapper::getPropertyDescriptor(JSContext* cx,
+ HandleObject wrapper,
+ HandleId id,
+ MutableHandle<PropertyDescriptor> desc) const
+{
+ return BaseProxyHandler::getPropertyDescriptor(cx, wrapper, id, desc);
+}
+
+bool
+OpaqueCrossCompartmentWrapper::hasOwn(JSContext* cx, HandleObject wrapper, HandleId id,
+ bool* bp) const
+{
+ return BaseProxyHandler::hasOwn(cx, wrapper, id, bp);
+}
+
+bool
+OpaqueCrossCompartmentWrapper::getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject wrapper,
+ AutoIdVector& props) const
+{
+ return BaseProxyHandler::getOwnEnumerablePropertyKeys(cx, wrapper, props);
+}
+
+bool
+OpaqueCrossCompartmentWrapper::getBuiltinClass(JSContext* cx, HandleObject wrapper,
+ ESClass* cls) const
+{
+ *cls = ESClass::Other;
+ return true;
+}
+
+bool
+OpaqueCrossCompartmentWrapper::isArray(JSContext* cx, HandleObject obj,
+ JS::IsArrayAnswer* answer) const
+{
+ *answer = JS::IsArrayAnswer::NotArray;
+ return true;
+}
+
+const char*
+OpaqueCrossCompartmentWrapper::className(JSContext* cx,
+ HandleObject proxy) const
+{
+ return "Opaque";
+}
+
+JSString*
+OpaqueCrossCompartmentWrapper::fun_toString(JSContext* cx, HandleObject proxy,
+ unsigned indent) const
+{
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
+ js_Function_str, js_toString_str, "object");
+ return nullptr;
+}
+
+const OpaqueCrossCompartmentWrapper OpaqueCrossCompartmentWrapper::singleton;
diff --git a/js/src/proxy/Proxy.cpp b/js/src/proxy/Proxy.cpp
new file mode 100644
index 000000000..b43fd02d2
--- /dev/null
+++ b/js/src/proxy/Proxy.cpp
@@ -0,0 +1,812 @@
+/* -*- 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 <string.h>
+
+#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<ProxyObject>());
+ 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<PropertyDescriptor> desc)
+{
+ JS_CHECK_RECURSION(cx, return false);
+ const BaseProxyHandler* handler = proxy->as<ProxyObject>().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<PropertyDescriptor> desc)
+{
+ JS_CHECK_RECURSION(cx, return false);
+
+ const BaseProxyHandler* handler = proxy->as<ProxyObject>().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<PropertyDescriptor> desc, ObjectOpResult& result)
+{
+ JS_CHECK_RECURSION(cx, return false);
+ const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
+ AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::SET, true);
+ if (!policy.allowed()) {
+ if (!policy.returnValue())
+ return false;
+ return result.succeed();
+ }
+ return proxy->as<ProxyObject>().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<ProxyObject>().handler();
+ AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, BaseProxyHandler::ENUMERATE, true);
+ if (!policy.allowed())
+ return policy.returnValue();
+ return proxy->as<ProxyObject>().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<ProxyObject>().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<ProxyObject>().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<ProxyObject>().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<ProxyObject>().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<ProxyObject>().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<ProxyObject>().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<ProxyObject>().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<ProxyObject>().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<ProxyObject>().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<ProxyObject>().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<ProxyObject>().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<ProxyObject>().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<ProxyObject>().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<ProxyObject>().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<ProxyObject>().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<ProxyObject>().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<ProxyObject>().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<ProxyObject>().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<ProxyObject>().handler()->hasInstance(cx, proxy, v, bp);
+}
+
+bool
+Proxy::getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls)
+{
+ JS_CHECK_RECURSION(cx, return false);
+ return proxy->as<ProxyObject>().handler()->getBuiltinClass(cx, proxy, cls);
+}
+
+bool
+Proxy::isArray(JSContext* cx, HandleObject proxy, JS::IsArrayAnswer* answer)
+{
+ JS_CHECK_RECURSION(cx, return false);
+ return proxy->as<ProxyObject>().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<ProxyObject>().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<ProxyObject>().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<ProxyObject>().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<ProxyObject>().handler()->boxedValue_unbox(cx, proxy, vp);
+}
+
+JSObject * const TaggedProto::LazyProto = reinterpret_cast<JSObject*>(0x1);
+
+/* static */ bool
+Proxy::watch(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::HandleObject callable)
+{
+ JS_CHECK_RECURSION(cx, return false);
+ return proxy->as<ProxyObject>().handler()->watch(cx, proxy, id, callable);
+}
+
+/* static */ bool
+Proxy::unwatch(JSContext* cx, JS::HandleObject proxy, JS::HandleId id)
+{
+ JS_CHECK_RECURSION(cx, return false);
+ return proxy->as<ProxyObject>().handler()->unwatch(cx, proxy, id);
+}
+
+/* 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<ProxyObject>().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<ProxyObject>().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<CanGC>(propp);
+ objp.set(obj);
+ } else {
+ objp.set(nullptr);
+ propp.set(nullptr);
+ }
+ return true;
+}
+
+bool
+js::proxy_DefineProperty(JSContext* cx, HandleObject obj, HandleId id,
+ Handle<PropertyDescriptor> 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<PropertyDescriptor> 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>());
+ ProxyObject::trace(trc, obj);
+}
+
+/* static */ void
+ProxyObject::trace(JSTracer* trc, JSObject* obj)
+{
+ ProxyObject* proxy = &obj->as<ProxyObject>();
+
+ TraceEdge(trc, &proxy->shape_, "ProxyObject_shape");
+
+#ifdef DEBUG
+ if (trc->runtime()->gc.isStrictProxyCheckingEnabled() && proxy->is<WrapperObject>()) {
+ 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<CrossCompartmentWrapperObject>())
+ TraceEdge(trc, proxy->slotOfExtra(1), "extra1");
+
+ Proxy::trace(trc, obj);
+}
+
+JSObject*
+js::proxy_WeakmapKeyDelegate(JSObject* obj)
+{
+ MOZ_ASSERT(obj->is<ProxyObject>());
+ return obj->as<ProxyObject>().handler()->weakmapKeyDelegate(obj);
+}
+
+void
+js::proxy_Finalize(FreeOp* fop, JSObject* obj)
+{
+ // Suppress a bogus warning about finalize().
+ JS::AutoSuppressGCAnalysis nogc;
+
+ MOZ_ASSERT(obj->is<ProxyObject>());
+ obj->as<ProxyObject>().handler()->finalize(fop, obj);
+ js_free(detail::GetProxyDataLayout(obj)->values);
+}
+
+void
+js::proxy_ObjectMoved(JSObject* obj, const JSObject* old)
+{
+ MOZ_ASSERT(obj->is<ProxyObject>());
+ obj->as<ProxyObject>().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<ProxyObject>());
+ 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<ProxyObject>());
+ return Proxy::construct(cx, proxy, args);
+}
+
+bool
+js::proxy_Watch(JSContext* cx, HandleObject obj, HandleId id, HandleObject callable)
+{
+ return Proxy::watch(cx, obj, id, callable);
+}
+
+bool
+js::proxy_Unwatch(JSContext* cx, HandleObject obj, HandleId id)
+{
+ return Proxy::unwatch(cx, obj, id);
+}
+
+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_Watch, js::proxy_Unwatch,
+ 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(JSContext* cx, 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
+ };
+
+ Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
+ RootedFunction ctor(cx);
+ ctor = global->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;
+}
diff --git a/js/src/proxy/Proxy.h b/js/src/proxy/Proxy.h
new file mode 100644
index 000000000..89909a085
--- /dev/null
+++ b/js/src/proxy/Proxy.h
@@ -0,0 +1,79 @@
+/* -*- 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/. */
+
+#ifndef proxy_Proxy_h
+#define proxy_Proxy_h
+
+#include "NamespaceImports.h"
+
+#include "js/Class.h"
+
+namespace js {
+
+class RegExpGuard;
+
+/*
+ * Dispatch point for handlers that executes the appropriate C++ or scripted traps.
+ *
+ * Important: All proxy methods need either (a) an AutoEnterPolicy in their
+ * Proxy::foo entry point below or (b) an override in SecurityWrapper. See bug
+ * 945826 comment 0.
+ */
+class Proxy
+{
+ public:
+ /* Standard internal methods. */
+ static bool getOwnPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
+ MutableHandle<JS::PropertyDescriptor> desc);
+ static bool defineProperty(JSContext* cx, HandleObject proxy, HandleId id,
+ Handle<JS::PropertyDescriptor> desc, ObjectOpResult& result);
+ static bool ownPropertyKeys(JSContext* cx, HandleObject proxy, AutoIdVector& props);
+ static bool delete_(JSContext* cx, HandleObject proxy, HandleId id, ObjectOpResult& result);
+ static bool enumerate(JSContext* cx, HandleObject proxy, MutableHandleObject objp);
+ static bool isExtensible(JSContext* cx, HandleObject proxy, bool* extensible);
+ static bool preventExtensions(JSContext* cx, HandleObject proxy, ObjectOpResult& result);
+ static bool getPrototype(JSContext* cx, HandleObject proxy, MutableHandleObject protop);
+ static bool setPrototype(JSContext* cx, HandleObject proxy, HandleObject proto,
+ ObjectOpResult& result);
+ static bool getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary,
+ MutableHandleObject protop);
+ static bool setImmutablePrototype(JSContext* cx, HandleObject proxy, bool* succeeded);
+ static bool has(JSContext* cx, HandleObject proxy, HandleId id, bool* bp);
+ static bool get(JSContext* cx, HandleObject proxy, HandleValue receiver, HandleId id,
+ MutableHandleValue vp);
+ static bool set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v,
+ HandleValue receiver, ObjectOpResult& result);
+ static bool call(JSContext* cx, HandleObject proxy, const CallArgs& args);
+ static bool construct(JSContext* cx, HandleObject proxy, const CallArgs& args);
+
+ /* SpiderMonkey extensions. */
+ static bool getPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
+ MutableHandle<JS::PropertyDescriptor> desc);
+ static bool hasOwn(JSContext* cx, HandleObject proxy, HandleId id, bool* bp);
+ static bool getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject proxy,
+ AutoIdVector& props);
+ static bool nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl,
+ const CallArgs& args);
+ static bool hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v, bool* bp);
+ static bool getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls);
+ static bool isArray(JSContext* cx, HandleObject proxy, JS::IsArrayAnswer* answer);
+ static const char* className(JSContext* cx, HandleObject proxy);
+ static JSString* fun_toString(JSContext* cx, HandleObject proxy, unsigned indent);
+ static bool regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g);
+ static bool boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp);
+
+ static bool watch(JSContext* cx, HandleObject proxy, HandleId id, HandleObject callable);
+ static bool unwatch(JSContext* cx, HandleObject proxy, HandleId id);
+
+ static bool getElements(JSContext* cx, HandleObject obj, uint32_t begin, uint32_t end,
+ ElementAdder* adder);
+
+ static void trace(JSTracer* trc, JSObject* obj);
+};
+
+} /* namespace js */
+
+#endif /* proxy_Proxy_h */
diff --git a/js/src/proxy/ScriptedProxyHandler.cpp b/js/src/proxy/ScriptedProxyHandler.cpp
new file mode 100644
index 000000000..776547337
--- /dev/null
+++ b/js/src/proxy/ScriptedProxyHandler.cpp
@@ -0,0 +1,1428 @@
+/* -*- 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 "proxy/ScriptedProxyHandler.h"
+
+#include "jsapi.h"
+
+#include "vm/Interpreter.h" // For InstanceOfOperator
+
+#include "jsobjinlines.h"
+#include "vm/NativeObject-inl.h"
+
+using namespace js;
+
+using JS::IsArrayAnswer;
+using mozilla::ArrayLength;
+
+// ES8 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93
+// 9.1.6.2 IsCompatiblePropertyDescriptor. BUT that method just calls
+// 9.1.6.3 ValidateAndApplyPropertyDescriptor with two additional constant
+// arguments. Therefore step numbering is from the latter method, and
+// resulting dead code has been removed.
+static bool
+IsCompatiblePropertyDescriptor(JSContext* cx, bool extensible, Handle<PropertyDescriptor> desc,
+ Handle<PropertyDescriptor> current, bool* bp)
+{
+ // Step 2.
+ if (!current.object()) {
+ // Step 2a-b,e. As |O| is always undefined, steps 2c-d fall away.
+ *bp = extensible;
+ return true;
+ }
+
+ // Step 3.
+ if (!desc.hasValue() && !desc.hasWritable() &&
+ !desc.hasGetterObject() && !desc.hasSetterObject() &&
+ !desc.hasEnumerable() && !desc.hasConfigurable())
+ {
+ *bp = true;
+ return true;
+ }
+
+ // Step 4.
+ if ((!desc.hasWritable() ||
+ (current.hasWritable() && desc.writable() == current.writable())) &&
+ (!desc.hasGetterObject() || desc.getter() == current.getter()) &&
+ (!desc.hasSetterObject() || desc.setter() == current.setter()) &&
+ (!desc.hasEnumerable() || desc.enumerable() == current.enumerable()) &&
+ (!desc.hasConfigurable() || desc.configurable() == current.configurable()))
+ {
+ if (!desc.hasValue()) {
+ *bp = true;
+ return true;
+ }
+ bool same = false;
+ if (!SameValue(cx, desc.value(), current.value(), &same))
+ return false;
+ if (same) {
+ *bp = true;
+ return true;
+ }
+ }
+
+ // Step 5.
+ if (!current.configurable()) {
+ // Step 5a.
+ if (desc.hasConfigurable() && desc.configurable()) {
+ *bp = false;
+ return true;
+ }
+
+ // Step 5b.
+ if (desc.hasEnumerable() && desc.enumerable() != current.enumerable()) {
+ *bp = false;
+ return true;
+ }
+ }
+
+ // Step 6.
+ if (desc.isGenericDescriptor()) {
+ *bp = true;
+ return true;
+ }
+
+ // Step 7.
+ if (current.isDataDescriptor() != desc.isDataDescriptor()) {
+ // Steps 7a, 11. As |O| is always undefined, steps 2b-c fall away.
+ *bp = current.configurable();
+ return true;
+ }
+
+ // Step 8.
+ if (current.isDataDescriptor()) {
+ MOZ_ASSERT(desc.isDataDescriptor()); // by step 7
+ if (!current.configurable() && !current.writable()) {
+ if (desc.hasWritable() && desc.writable()) {
+ *bp = false;
+ return true;
+ }
+
+ if (desc.hasValue()) {
+ bool same;
+ if (!SameValue(cx, desc.value(), current.value(), &same))
+ return false;
+ if (!same) {
+ *bp = false;
+ return true;
+ }
+ }
+ }
+
+ *bp = true;
+ return true;
+ }
+
+ // Step 9.
+ MOZ_ASSERT(current.isAccessorDescriptor()); // by step 8
+ MOZ_ASSERT(desc.isAccessorDescriptor()); // by step 7
+ *bp = (current.configurable() ||
+ ((!desc.hasSetterObject() || desc.setter() == current.setter()) &&
+ (!desc.hasGetterObject() || desc.getter() == current.getter())));
+ return true;
+}
+
+// Get the [[ProxyHandler]] of a scripted proxy.
+/* static */ JSObject*
+ScriptedProxyHandler::handlerObject(const JSObject* proxy)
+{
+ MOZ_ASSERT(proxy->as<ProxyObject>().handler() == &ScriptedProxyHandler::singleton);
+ return proxy->as<ProxyObject>().extra(ScriptedProxyHandler::HANDLER_EXTRA).toObjectOrNull();
+}
+
+// ES8 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93 7.3.9 GetMethod,
+// reimplemented for proxy handler trap-getting to produce better error
+// messages.
+static bool
+GetProxyTrap(JSContext* cx, HandleObject handler, HandlePropertyName name, MutableHandleValue func)
+{
+ // Steps 2, 5.
+ if (!GetProperty(cx, handler, handler, name, func))
+ return false;
+
+ // Step 3.
+ if (func.isUndefined())
+ return true;
+
+ if (func.isNull()) {
+ func.setUndefined();
+ return true;
+ }
+
+ // Step 4.
+ if (!IsCallable(func)) {
+ JSAutoByteString bytes(cx, name);
+ if (!bytes)
+ return false;
+
+ JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, JSMSG_BAD_TRAP, bytes.ptr());
+ return false;
+ }
+
+ return true;
+}
+
+// ES8 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93 9.5.1 Proxy.[[GetPrototypeOf]].
+bool
+ScriptedProxyHandler::getPrototype(JSContext* cx, HandleObject proxy,
+ MutableHandleObject protop) const
+{
+ // Steps 1-3.
+ RootedObject handler(cx, ScriptedProxyHandler::handlerObject(proxy));
+ if (!handler) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_PROXY_REVOKED);
+ return false;
+ }
+
+ // Step 4.
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ MOZ_ASSERT(target);
+
+ // Step 5.
+ RootedValue trap(cx);
+ if (!GetProxyTrap(cx, handler, cx->names().getPrototypeOf, &trap))
+ return false;
+
+ // Step 6.
+ if (trap.isUndefined())
+ return GetPrototype(cx, target, protop);
+
+ // Step 7.
+ RootedValue handlerProto(cx);
+ {
+ FixedInvokeArgs<1> args(cx);
+
+ args[0].setObject(*target);
+
+ handlerProto.setObject(*handler);
+
+ if (!js::Call(cx, trap, handlerProto, args, &handlerProto))
+ return false;
+ }
+
+ // Step 8.
+ if (!handlerProto.isObjectOrNull()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_BAD_GETPROTOTYPEOF_TRAP_RETURN);
+ return false;
+ }
+
+ // Step 9.
+ bool extensibleTarget;
+ if (!IsExtensible(cx, target, &extensibleTarget))
+ return false;
+
+ // Step 10.
+ if (extensibleTarget) {
+ protop.set(handlerProto.toObjectOrNull());
+ return true;
+ }
+
+ // Step 11.
+ RootedObject targetProto(cx);
+ if (!GetPrototype(cx, target, &targetProto))
+ return false;
+
+ // Step 12.
+ if (handlerProto.toObjectOrNull() != targetProto) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_INCONSISTENT_GETPROTOTYPEOF_TRAP);
+ return false;
+ }
+
+ // Step 13.
+ protop.set(handlerProto.toObjectOrNull());
+ return true;
+}
+
+// ES8 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93 9.5.2 Proxy.[[SetPrototypeOf]].
+bool
+ScriptedProxyHandler::setPrototype(JSContext* cx, HandleObject proxy, HandleObject proto,
+ ObjectOpResult& result) const
+{
+ // Steps 1-4.
+ RootedObject handler(cx, ScriptedProxyHandler::handlerObject(proxy));
+ if (!handler) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_PROXY_REVOKED);
+ return false;
+ }
+
+ // Step 5.
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ MOZ_ASSERT(target);
+
+ // Step 6.
+ RootedValue trap(cx);
+ if (!GetProxyTrap(cx, handler, cx->names().setPrototypeOf, &trap))
+ return false;
+
+ // Step 7.
+ if (trap.isUndefined())
+ return SetPrototype(cx, target, proto, result);
+
+ // Step 8.
+ bool booleanTrapResult;
+ {
+ FixedInvokeArgs<2> args(cx);
+
+ args[0].setObject(*target);
+ args[1].setObjectOrNull(proto);
+
+ RootedValue hval(cx, ObjectValue(*handler));
+ if (!js::Call(cx, trap, hval, args, &hval))
+ return false;
+
+ booleanTrapResult = ToBoolean(hval);
+ }
+
+ // Step 9.
+ if (!booleanTrapResult)
+ return result.fail(JSMSG_PROXY_SETPROTOTYPEOF_RETURNED_FALSE);
+
+ // Step 10.
+ bool extensibleTarget;
+ if (!IsExtensible(cx, target, &extensibleTarget))
+ return false;
+
+ // Step 11.
+ if (extensibleTarget)
+ return result.succeed();
+
+ // Step 12.
+ RootedObject targetProto(cx);
+ if (!GetPrototype(cx, target, &targetProto))
+ return false;
+
+ // Step 13.
+ if (proto != targetProto) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_INCONSISTENT_SETPROTOTYPEOF_TRAP);
+ return false;
+ }
+
+ // Step 14.
+ return result.succeed();
+}
+
+bool
+ScriptedProxyHandler::getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary,
+ MutableHandleObject protop) const
+{
+ *isOrdinary = false;
+ return true;
+}
+
+// Not yet part of ES6, but hopefully to be standards-tracked -- and needed to
+// handle revoked proxies in any event.
+bool
+ScriptedProxyHandler::setImmutablePrototype(JSContext* cx, HandleObject proxy,
+ bool* succeeded) const
+{
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ if (!target) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_PROXY_REVOKED);
+ return false;
+ }
+
+ return SetImmutablePrototype(cx, target, succeeded);
+}
+
+// ES8 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93 9.5.4 Proxy.[[PreventExtensions]]()
+bool
+ScriptedProxyHandler::preventExtensions(JSContext* cx, HandleObject proxy,
+ ObjectOpResult& result) const
+{
+ // Steps 1-3.
+ RootedObject handler(cx, ScriptedProxyHandler::handlerObject(proxy));
+ if (!handler) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_PROXY_REVOKED);
+ return false;
+ }
+
+ // Step 4.
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ MOZ_ASSERT(target);
+
+ // Step 5.
+ RootedValue trap(cx);
+ if (!GetProxyTrap(cx, handler, cx->names().preventExtensions, &trap))
+ return false;
+
+ // Step 6.
+ if (trap.isUndefined())
+ return PreventExtensions(cx, target, result);
+
+ // Step 7.
+ bool booleanTrapResult;
+ {
+ RootedValue arg(cx, ObjectValue(*target));
+ RootedValue trapResult(cx);
+ if (!Call(cx, trap, handler, arg, &trapResult))
+ return false;
+
+ booleanTrapResult = ToBoolean(trapResult);
+ }
+
+ // Step 8.
+ if (booleanTrapResult) {
+ // Step 8a.
+ bool targetIsExtensible;
+ if (!IsExtensible(cx, target, &targetIsExtensible))
+ return false;
+
+ if (targetIsExtensible) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_CANT_REPORT_AS_NON_EXTENSIBLE);
+ return false;
+ }
+
+ // Step 9.
+ return result.succeed();
+ }
+
+ // Also step 9.
+ return result.fail(JSMSG_PROXY_PREVENTEXTENSIONS_RETURNED_FALSE);
+}
+
+// ES8 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93 9.5.3 Proxy.[[IsExtensible]]()
+bool
+ScriptedProxyHandler::isExtensible(JSContext* cx, HandleObject proxy, bool* extensible) const
+{
+ // Steps 1-3.
+ RootedObject handler(cx, ScriptedProxyHandler::handlerObject(proxy));
+ if (!handler) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_PROXY_REVOKED);
+ return false;
+ }
+
+ // Step 4.
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ MOZ_ASSERT(target);
+
+ // Step 5.
+ RootedValue trap(cx);
+ if (!GetProxyTrap(cx, handler, cx->names().isExtensible, &trap))
+ return false;
+
+ // Step 6.
+ if (trap.isUndefined())
+ return IsExtensible(cx, target, extensible);
+
+ // Step 7.
+ bool booleanTrapResult;
+ {
+ RootedValue arg(cx, ObjectValue(*target));
+ RootedValue trapResult(cx);
+ if (!Call(cx, trap, handler, arg, &trapResult))
+ return false;
+
+ booleanTrapResult = ToBoolean(trapResult);
+ }
+
+ // Steps 8.
+ bool targetResult;
+ if (!IsExtensible(cx, target, &targetResult))
+ return false;
+
+ // Step 9.
+ if (targetResult != booleanTrapResult) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_PROXY_EXTENSIBILITY);
+ return false;
+ }
+
+ // Step 10.
+ *extensible = booleanTrapResult;
+ return true;
+}
+
+// ES8 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93 9.5.5 Proxy.[[GetOwnProperty]](P)
+bool
+ScriptedProxyHandler::getOwnPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
+ MutableHandle<PropertyDescriptor> desc) const
+{
+ // Steps 2-4.
+ RootedObject handler(cx, ScriptedProxyHandler::handlerObject(proxy));
+ if (!handler) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_PROXY_REVOKED);
+ return false;
+ }
+
+ // Step 5.
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ MOZ_ASSERT(target);
+
+ // Step 6.
+ RootedValue trap(cx);
+ if (!GetProxyTrap(cx, handler, cx->names().getOwnPropertyDescriptor, &trap))
+ return false;
+
+ // Step 7.
+ if (trap.isUndefined())
+ return GetOwnPropertyDescriptor(cx, target, id, desc);
+
+ // Step 8.
+ RootedValue propKey(cx);
+ if (!IdToStringOrSymbol(cx, id, &propKey))
+ return false;
+
+ RootedValue trapResult(cx);
+ RootedValue targetVal(cx, ObjectValue(*target));
+ if (!Call(cx, trap, handler, targetVal, propKey, &trapResult))
+ return false;
+
+ // Step 9.
+ if (!trapResult.isUndefined() && !trapResult.isObject()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_PROXY_GETOWN_OBJORUNDEF);
+ return false;
+ }
+
+ // Step 10.
+ Rooted<PropertyDescriptor> targetDesc(cx);
+ if (!GetOwnPropertyDescriptor(cx, target, id, &targetDesc))
+ return false;
+
+ // Step 11.
+ if (trapResult.isUndefined()) {
+ // Step 11a.
+ if (!targetDesc.object()) {
+ desc.object().set(nullptr);
+ return true;
+ }
+
+ // Step 11b.
+ if (!targetDesc.configurable()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_REPORT_NC_AS_NE);
+ return false;
+ }
+
+ // Steps 11c-d.
+ bool extensibleTarget;
+ if (!IsExtensible(cx, target, &extensibleTarget))
+ return false;
+
+ // Step 11e.
+ if (!extensibleTarget) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_REPORT_E_AS_NE);
+ return false;
+ }
+
+ // Step 11f.
+ desc.object().set(nullptr);
+ return true;
+ }
+
+ // Step 12.
+ bool extensibleTarget;
+ if (!IsExtensible(cx, target, &extensibleTarget))
+ return false;
+
+ // Step 13.
+ Rooted<PropertyDescriptor> resultDesc(cx);
+ if (!ToPropertyDescriptor(cx, trapResult, true, &resultDesc))
+ return false;
+
+ // Step 14.
+ CompletePropertyDescriptor(&resultDesc);
+
+ // Step 15.
+ bool valid;
+ if (!IsCompatiblePropertyDescriptor(cx, extensibleTarget, resultDesc, targetDesc, &valid))
+ return false;
+
+ // Step 16.
+ if (!valid) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_REPORT_INVALID);
+ return false;
+ }
+
+ // Step 17.
+ if (!resultDesc.configurable()) {
+ if (!targetDesc.object()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_REPORT_NE_AS_NC);
+ return false;
+ }
+
+ if (targetDesc.configurable()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_REPORT_C_AS_NC);
+ return false;
+ }
+ }
+
+ // Step 18.
+ desc.set(resultDesc);
+ desc.object().set(proxy);
+ return true;
+}
+
+// ES8 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93 9.5.6 Proxy.[[DefineOwnProperty]](P, Desc)
+bool
+ScriptedProxyHandler::defineProperty(JSContext* cx, HandleObject proxy, HandleId id,
+ Handle<PropertyDescriptor> desc, ObjectOpResult& result) const
+{
+ // Steps 2-4.
+ RootedObject handler(cx, ScriptedProxyHandler::handlerObject(proxy));
+ if (!handler) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_PROXY_REVOKED);
+ return false;
+ }
+
+ // Step 5.
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ MOZ_ASSERT(target);
+
+ // Step 6.
+ RootedValue trap(cx);
+ if (!GetProxyTrap(cx, handler, cx->names().defineProperty, &trap))
+ return false;
+
+ // Step 7.
+ if (trap.isUndefined())
+ return DefineProperty(cx, target, id, desc, result);
+
+ // Step 8.
+ RootedValue descObj(cx);
+ if (!FromPropertyDescriptorToObject(cx, desc, &descObj))
+ return false;
+
+ // Step 9.
+ RootedValue propKey(cx);
+ if (!IdToStringOrSymbol(cx, id, &propKey))
+ return false;
+
+ RootedValue trapResult(cx);
+ {
+ FixedInvokeArgs<3> args(cx);
+
+ args[0].setObject(*target);
+ args[1].set(propKey);
+ args[2].set(descObj);
+
+ RootedValue thisv(cx, ObjectValue(*handler));
+ if (!Call(cx, trap, thisv, args, &trapResult))
+ return false;
+ }
+
+ // Step 10.
+ if (!ToBoolean(trapResult))
+ return result.fail(JSMSG_PROXY_DEFINE_RETURNED_FALSE);
+
+ // Step 11.
+ Rooted<PropertyDescriptor> targetDesc(cx);
+ if (!GetOwnPropertyDescriptor(cx, target, id, &targetDesc))
+ return false;
+
+ // Step 12.
+ bool extensibleTarget;
+ if (!IsExtensible(cx, target, &extensibleTarget))
+ return false;
+
+ // Steps 13-14.
+ bool settingConfigFalse = desc.hasConfigurable() && !desc.configurable();
+
+ // Steps 15-16.
+ if (!targetDesc.object()) {
+ // Step 15a.
+ if (!extensibleTarget) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_DEFINE_NEW);
+ return false;
+ }
+
+ // Step 15b.
+ if (settingConfigFalse) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_DEFINE_NE_AS_NC);
+ return false;
+ }
+ } else {
+ // Steps 16a-b.
+ bool valid;
+ if (!IsCompatiblePropertyDescriptor(cx, extensibleTarget, desc, targetDesc, &valid))
+ return false;
+
+ if (!valid || (settingConfigFalse && targetDesc.configurable())) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_DEFINE_INVALID);
+ return false;
+ }
+ }
+
+ // Step 17.
+ return result.succeed();
+}
+
+// ES8 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93
+// 7.3.17 CreateListFromArrayLike with elementTypes fixed to symbol/string.
+static bool
+CreateFilteredListFromArrayLike(JSContext* cx, HandleValue v, AutoIdVector& props)
+{
+ // Step 2.
+ RootedObject obj(cx, NonNullObject(cx, v));
+ if (!obj)
+ return false;
+
+ // Step 3.
+ uint32_t len;
+ if (!GetLengthProperty(cx, obj, &len))
+ return false;
+
+ // Steps 4-6.
+ RootedValue next(cx);
+ RootedId id(cx);
+ uint32_t index = 0;
+ while (index < len) {
+ // Steps 6a-b.
+ if (!GetElement(cx, obj, obj, index, &next))
+ return false;
+
+ // Step 6c.
+ if (!next.isString() && !next.isSymbol()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ONWKEYS_STR_SYM);
+ return false;
+ }
+
+ if (!ValueToId<CanGC>(cx, next, &id))
+ return false;
+
+ // Step 6d.
+ if (!props.append(id))
+ return false;
+
+ // Step 6e.
+ index++;
+ }
+
+ // Step 7.
+ return true;
+}
+
+
+// ES8 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93 9.5.11 Proxy.[[OwnPropertyKeys]]()
+bool
+ScriptedProxyHandler::ownPropertyKeys(JSContext* cx, HandleObject proxy, AutoIdVector& props) const
+{
+ // Steps 1-3.
+ RootedObject handler(cx, ScriptedProxyHandler::handlerObject(proxy));
+ if (!handler) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_PROXY_REVOKED);
+ return false;
+ }
+
+ // Step 4.
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ MOZ_ASSERT(target);
+
+ // Step 5.
+ RootedValue trap(cx);
+ if (!GetProxyTrap(cx, handler, cx->names().ownKeys, &trap))
+ return false;
+
+ // Step 6.
+ if (trap.isUndefined())
+ return GetPropertyKeys(cx, target, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, &props);
+
+ // Step 7.
+ RootedValue trapResultArray(cx);
+ RootedValue targetVal(cx, ObjectValue(*target));
+ if (!Call(cx, trap, handler, targetVal, &trapResultArray))
+ return false;
+
+ // Step 8.
+ AutoIdVector trapResult(cx);
+ if (!CreateFilteredListFromArrayLike(cx, trapResultArray, trapResult))
+ return false;
+
+ // Step 9.
+ bool extensibleTarget;
+ if (!IsExtensible(cx, target, &extensibleTarget))
+ return false;
+
+ // Steps 10-11.
+ AutoIdVector targetKeys(cx);
+ if (!GetPropertyKeys(cx, target, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, &targetKeys))
+ return false;
+
+ // Steps 12-13.
+ AutoIdVector targetConfigurableKeys(cx);
+ AutoIdVector targetNonconfigurableKeys(cx);
+
+ // Step 14.
+ Rooted<PropertyDescriptor> desc(cx);
+ for (size_t i = 0; i < targetKeys.length(); ++i) {
+ // Step 14a.
+ if (!GetOwnPropertyDescriptor(cx, target, targetKeys[i], &desc))
+ return false;
+
+ // Steps 14b-c.
+ if (desc.object() && !desc.configurable()) {
+ if (!targetNonconfigurableKeys.append(targetKeys[i]))
+ return false;
+ } else {
+ if (!targetConfigurableKeys.append(targetKeys[i]))
+ return false;
+ }
+ }
+
+ // Step 15.
+ if (extensibleTarget && targetNonconfigurableKeys.empty())
+ return props.appendAll(trapResult);
+
+ // Step 16.
+ // The algorithm below always removes all occurences of the same key
+ // at once, so we can use a set here.
+ Rooted<GCHashSet<jsid>> uncheckedResultKeys(cx, GCHashSet<jsid>(cx));
+ if (!uncheckedResultKeys.init(trapResult.length()))
+ return false;
+
+ for (size_t i = 0, len = trapResult.length(); i < len; i++) {
+ MOZ_ASSERT(!JSID_IS_VOID(trapResult[i]));
+
+ if (!uncheckedResultKeys.put(trapResult[i]))
+ return false;
+ }
+
+ // Step 17.
+ for (size_t i = 0; i < targetNonconfigurableKeys.length(); ++i) {
+ MOZ_ASSERT(!JSID_IS_VOID(targetNonconfigurableKeys[i]));
+
+ auto ptr = uncheckedResultKeys.lookup(targetNonconfigurableKeys[i]);
+
+ // Step 17a.
+ if (!ptr) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_SKIP_NC);
+ return false;
+ }
+
+ // Step 17b.
+ uncheckedResultKeys.remove(ptr);
+ }
+
+ // Step 18.
+ if (extensibleTarget)
+ return props.appendAll(trapResult);
+
+ // Step 19.
+ for (size_t i = 0; i < targetConfigurableKeys.length(); ++i) {
+ MOZ_ASSERT(!JSID_IS_VOID(targetConfigurableKeys[i]));
+
+ auto ptr = uncheckedResultKeys.lookup(targetConfigurableKeys[i]);
+
+ // Step 19a.
+ if (!ptr) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_REPORT_E_AS_NE);
+ return false;
+ }
+
+ // Step 19b.
+ uncheckedResultKeys.remove(ptr);
+ }
+
+ // Step 20.
+ if (!uncheckedResultKeys.empty()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_REPORT_NEW);
+ return false;
+ }
+
+ // Step 21.
+ return props.appendAll(trapResult);
+}
+
+// ES8 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93 9.5.10 Proxy.[[Delete]](P)
+bool
+ScriptedProxyHandler::delete_(JSContext* cx, HandleObject proxy, HandleId id,
+ ObjectOpResult& result) const
+{
+ // Steps 2-4.
+ RootedObject handler(cx, ScriptedProxyHandler::handlerObject(proxy));
+ if (!handler) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_PROXY_REVOKED);
+ return false;
+ }
+
+ // Step 5.
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ MOZ_ASSERT(target);
+
+ // Step 6.
+ RootedValue trap(cx);
+ if (!GetProxyTrap(cx, handler, cx->names().deleteProperty, &trap))
+ return false;
+
+ // Step 7.
+ if (trap.isUndefined())
+ return DeleteProperty(cx, target, id, result);
+
+ // Step 8.
+ bool booleanTrapResult;
+ {
+ RootedValue value(cx);
+ if (!IdToStringOrSymbol(cx, id, &value))
+ return false;
+
+ RootedValue targetVal(cx, ObjectValue(*target));
+ RootedValue trapResult(cx);
+ if (!Call(cx, trap, handler, targetVal, value, &trapResult))
+ return false;
+
+ booleanTrapResult = ToBoolean(trapResult);
+ }
+
+ // Step 9.
+ if (!booleanTrapResult)
+ return result.fail(JSMSG_PROXY_DELETE_RETURNED_FALSE);
+
+ // Step 10.
+ Rooted<PropertyDescriptor> desc(cx);
+ if (!GetOwnPropertyDescriptor(cx, target, id, &desc))
+ return false;
+
+ // Step 12.
+ if (desc.object() && !desc.configurable()) {
+ RootedValue v(cx, IdToValue(id));
+ ReportValueError(cx, JSMSG_CANT_DELETE, JSDVG_IGNORE_STACK, v, nullptr);
+ return false;
+ }
+
+ // Steps 11,13.
+ return result.succeed();
+}
+
+// ES8 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93 9.5.7 Proxy.[[HasProperty]](P)
+bool
+ScriptedProxyHandler::has(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) const
+{
+ // Steps 2-4.
+ RootedObject handler(cx, ScriptedProxyHandler::handlerObject(proxy));
+ if (!handler) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_PROXY_REVOKED);
+ return false;
+ }
+
+ // Step 5.
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ MOZ_ASSERT(target);
+
+ // Step 6.
+ RootedValue trap(cx);
+ if (!GetProxyTrap(cx, handler, cx->names().has, &trap))
+ return false;
+
+ // Step 7.
+ if (trap.isUndefined())
+ return HasProperty(cx, target, id, bp);
+
+ // Step 8.
+ RootedValue value(cx);
+ if (!IdToStringOrSymbol(cx, id, &value))
+ return false;
+
+ RootedValue trapResult(cx);
+ RootedValue targetVal(cx, ObjectValue(*target));
+ if (!Call(cx, trap, handler, targetVal, value, &trapResult))
+ return false;
+
+ bool booleanTrapResult = ToBoolean(trapResult);
+
+ // Step 9.
+ if (!booleanTrapResult) {
+ // Step 9a.
+ Rooted<PropertyDescriptor> desc(cx);
+ if (!GetOwnPropertyDescriptor(cx, target, id, &desc))
+ return false;
+
+ // Step 9b.
+ if (desc.object()) {
+ // Step 9b(i).
+ if (!desc.configurable()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_REPORT_NC_AS_NE);
+ return false;
+ }
+
+ // Step 9b(ii).
+ bool extensible;
+ if (!IsExtensible(cx, target, &extensible))
+ return false;
+
+ // Step 9b(iii).
+ if (!extensible) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_REPORT_E_AS_NE);
+ return false;
+ }
+ }
+ }
+
+ // Step 10.
+ *bp = booleanTrapResult;
+ return true;
+}
+
+// ES8 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93 9.5.8 Proxy.[[GetP]](P, Receiver)
+bool
+ScriptedProxyHandler::get(JSContext* cx, HandleObject proxy, HandleValue receiver, HandleId id,
+ MutableHandleValue vp) const
+{
+ // Steps 2-4.
+ RootedObject handler(cx, ScriptedProxyHandler::handlerObject(proxy));
+ if (!handler) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_PROXY_REVOKED);
+ return false;
+ }
+
+ // Step 5.
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ MOZ_ASSERT(target);
+
+ // Steps 6.
+ RootedValue trap(cx);
+ if (!GetProxyTrap(cx, handler, cx->names().get, &trap))
+ return false;
+
+ // Step 7.
+ if (trap.isUndefined())
+ return GetProperty(cx, target, receiver, id, vp);
+
+ // Step 8.
+ RootedValue value(cx);
+ if (!IdToStringOrSymbol(cx, id, &value))
+ return false;
+
+ RootedValue trapResult(cx);
+ {
+ FixedInvokeArgs<3> args(cx);
+
+ args[0].setObject(*target);
+ args[1].set(value);
+ args[2].set(receiver);
+
+ RootedValue thisv(cx, ObjectValue(*handler));
+ if (!Call(cx, trap, thisv, args, &trapResult))
+ return false;
+ }
+
+ // Step 9.
+ Rooted<PropertyDescriptor> desc(cx);
+ if (!GetOwnPropertyDescriptor(cx, target, id, &desc))
+ return false;
+
+ // Step 10.
+ if (desc.object()) {
+ // Step 10a.
+ if (desc.isDataDescriptor() && !desc.configurable() && !desc.writable()) {
+ bool same;
+ if (!SameValue(cx, trapResult, desc.value(), &same))
+ return false;
+ if (!same) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_MUST_REPORT_SAME_VALUE);
+ return false;
+ }
+ }
+
+ // Step 10b.
+ if (desc.isAccessorDescriptor() && !desc.configurable() && desc.getterObject() == nullptr) {
+ if (!trapResult.isUndefined()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_MUST_REPORT_UNDEFINED);
+ return false;
+ }
+ }
+ }
+
+ // Step 11.
+ vp.set(trapResult);
+ return true;
+}
+
+// ES8 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93 9.5.9 Proxy.[[Set]](P, V, Receiver)
+bool
+ScriptedProxyHandler::set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v,
+ HandleValue receiver, ObjectOpResult& result) const
+{
+ // Steps 2-4.
+ RootedObject handler(cx, ScriptedProxyHandler::handlerObject(proxy));
+ if (!handler) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_PROXY_REVOKED);
+ return false;
+ }
+
+ // Step 5.
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ MOZ_ASSERT(target);
+
+ // Step 6.
+ RootedValue trap(cx);
+ if (!GetProxyTrap(cx, handler, cx->names().set, &trap))
+ return false;
+
+ // Step 7.
+ if (trap.isUndefined())
+ return SetProperty(cx, target, id, v, receiver, result);
+
+ // Step 8.
+ RootedValue value(cx);
+ if (!IdToStringOrSymbol(cx, id, &value))
+ return false;
+
+ RootedValue trapResult(cx);
+ {
+ FixedInvokeArgs<4> args(cx);
+
+ args[0].setObject(*target);
+ args[1].set(value);
+ args[2].set(v);
+ args[3].set(receiver);
+
+ RootedValue thisv(cx, ObjectValue(*handler));
+ if (!Call(cx, trap, thisv, args, &trapResult))
+ return false;
+ }
+
+ // Step 9.
+ if (!ToBoolean(trapResult))
+ return result.fail(JSMSG_PROXY_SET_RETURNED_FALSE);
+
+ // Step 10.
+ Rooted<PropertyDescriptor> desc(cx);
+ if (!GetOwnPropertyDescriptor(cx, target, id, &desc))
+ return false;
+
+ // Step 11.
+ if (desc.object()) {
+ // Step 11a.
+ if (desc.isDataDescriptor() && !desc.configurable() && !desc.writable()) {
+ bool same;
+ if (!SameValue(cx, v, desc.value(), &same))
+ return false;
+ if (!same) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_SET_NW_NC);
+ return false;
+ }
+ }
+
+ // Step 11b.
+ if (desc.isAccessorDescriptor() && !desc.configurable() && desc.setterObject() == nullptr) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_SET_WO_SETTER);
+ return false;
+ }
+ }
+
+ // Step 12.
+ return result.succeed();
+}
+
+// ES7 0c1bd3004329336774cbc90de727cd0cf5f11e93 9.5.13 Proxy.[[Call]]
+bool
+ScriptedProxyHandler::call(JSContext* cx, HandleObject proxy, const CallArgs& args) const
+{
+ // Steps 1-3.
+ RootedObject handler(cx, ScriptedProxyHandler::handlerObject(proxy));
+ if (!handler) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_PROXY_REVOKED);
+ return false;
+ }
+
+ // Step 4.
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ MOZ_ASSERT(target);
+ MOZ_ASSERT(target->isCallable());
+
+ // Step 5.
+ RootedValue trap(cx);
+ if (!GetProxyTrap(cx, handler, cx->names().apply, &trap))
+ return false;
+
+ // Step 6.
+ if (trap.isUndefined()) {
+ InvokeArgs iargs(cx);
+ if (!FillArgumentsFromArraylike(cx, iargs, args))
+ return false;
+
+ RootedValue fval(cx, ObjectValue(*target));
+ return js::Call(cx, fval, args.thisv(), iargs, args.rval());
+ }
+
+ // Step 7.
+ RootedObject argArray(cx, NewDenseCopiedArray(cx, args.length(), args.array()));
+ if (!argArray)
+ return false;
+
+ // Step 8.
+ FixedInvokeArgs<3> iargs(cx);
+
+ iargs[0].setObject(*target);
+ iargs[1].set(args.thisv());
+ iargs[2].setObject(*argArray);
+
+ RootedValue thisv(cx, ObjectValue(*handler));
+ return js::Call(cx, trap, thisv, iargs, args.rval());
+}
+
+// ES7 0c1bd3004329336774cbc90de727cd0cf5f11e93 9.5.14 Proxy.[[Construct]]
+bool
+ScriptedProxyHandler::construct(JSContext* cx, HandleObject proxy, const CallArgs& args) const
+{
+ // Steps 1-3.
+ RootedObject handler(cx, ScriptedProxyHandler::handlerObject(proxy));
+ if (!handler) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_PROXY_REVOKED);
+ return false;
+ }
+
+ // Step 4.
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ MOZ_ASSERT(target);
+ MOZ_ASSERT(target->isConstructor());
+
+ // Step 5.
+ RootedValue trap(cx);
+ if (!GetProxyTrap(cx, handler, cx->names().construct, &trap))
+ return false;
+
+ // Step 6.
+ if (trap.isUndefined()) {
+ ConstructArgs cargs(cx);
+ if (!FillArgumentsFromArraylike(cx, cargs, args))
+ return false;
+
+ RootedValue targetv(cx, ObjectValue(*target));
+ RootedObject obj(cx);
+ if (!Construct(cx, targetv, cargs, args.newTarget(), &obj))
+ return false;
+
+ args.rval().setObject(*obj);
+ return true;
+ }
+
+ // Step 7.
+ RootedObject argArray(cx, NewDenseCopiedArray(cx, args.length(), args.array()));
+ if (!argArray)
+ return false;
+
+ // Steps 8, 10.
+ {
+ FixedInvokeArgs<3> iargs(cx);
+
+ iargs[0].setObject(*target);
+ iargs[1].setObject(*argArray);
+ iargs[2].set(args.newTarget());
+
+ RootedValue thisv(cx, ObjectValue(*handler));
+ if (!Call(cx, trap, thisv, iargs, args.rval()))
+ return false;
+ }
+
+ // Step 9.
+ if (!args.rval().isObject()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_PROXY_CONSTRUCT_OBJECT);
+ return false;
+ }
+
+ return true;
+}
+
+bool
+ScriptedProxyHandler::nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl,
+ const CallArgs& args) const
+{
+ ReportIncompatible(cx, args);
+ return false;
+}
+
+bool
+ScriptedProxyHandler::hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v,
+ bool* bp) const
+{
+ return InstanceOfOperator(cx, proxy, v, bp);
+}
+
+bool
+ScriptedProxyHandler::getBuiltinClass(JSContext* cx, HandleObject proxy,
+ ESClass* cls) const
+{
+ *cls = ESClass::Other;
+ return true;
+}
+
+bool
+ScriptedProxyHandler::isArray(JSContext* cx, HandleObject proxy, IsArrayAnswer* answer) const
+{
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ if (target)
+ return JS::IsArray(cx, target, answer);
+
+ *answer = IsArrayAnswer::RevokedProxy;
+ return true;
+}
+
+const char*
+ScriptedProxyHandler::className(JSContext* cx, HandleObject proxy) const
+{
+ // Right now the caller is not prepared to handle failures.
+ return BaseProxyHandler::className(cx, proxy);
+}
+
+JSString*
+ScriptedProxyHandler::fun_toString(JSContext* cx, HandleObject proxy, unsigned indent) const
+{
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
+ js_Function_str, js_toString_str, "object");
+ return nullptr;
+}
+
+bool
+ScriptedProxyHandler::regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const
+{
+ MOZ_CRASH("Should not end up in ScriptedProxyHandler::regexp_toShared");
+ return false;
+}
+
+bool
+ScriptedProxyHandler::boxedValue_unbox(JSContext* cx, HandleObject proxy,
+ MutableHandleValue vp) const
+{
+ MOZ_CRASH("Should not end up in ScriptedProxyHandler::boxedValue_unbox");
+ return false;
+}
+
+bool
+ScriptedProxyHandler::isCallable(JSObject* obj) const
+{
+ MOZ_ASSERT(obj->as<ProxyObject>().handler() == &ScriptedProxyHandler::singleton);
+ uint32_t callConstruct = obj->as<ProxyObject>().extra(IS_CALLCONSTRUCT_EXTRA).toPrivateUint32();
+ return !!(callConstruct & IS_CALLABLE);
+}
+
+bool
+ScriptedProxyHandler::isConstructor(JSObject* obj) const
+{
+ MOZ_ASSERT(obj->as<ProxyObject>().handler() == &ScriptedProxyHandler::singleton);
+ uint32_t callConstruct = obj->as<ProxyObject>().extra(IS_CALLCONSTRUCT_EXTRA).toPrivateUint32();
+ return !!(callConstruct & IS_CONSTRUCTOR);
+}
+
+const char ScriptedProxyHandler::family = 0;
+const ScriptedProxyHandler ScriptedProxyHandler::singleton;
+
+bool
+IsRevokedScriptedProxy(JSObject* obj)
+{
+ obj = CheckedUnwrap(obj);
+ return obj && IsScriptedProxy(obj) && !obj->as<ProxyObject>().target();
+}
+
+// ES8 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93 9.5.14 ProxyCreate.
+static bool
+ProxyCreate(JSContext* cx, CallArgs& args, const char* callerName)
+{
+ if (args.length() < 2) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
+ callerName, "1", "s");
+ return false;
+ }
+
+ // Step 1.
+ RootedObject target(cx, NonNullObject(cx, args[0]));
+ if (!target)
+ return false;
+
+ // Step 2.
+ if (IsRevokedScriptedProxy(target)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_PROXY_ARG_REVOKED, "1");
+ return false;
+ }
+
+ // Step 3.
+ RootedObject handler(cx, NonNullObject(cx, args[1]));
+ if (!handler)
+ return false;
+
+ // Step 4.
+ if (IsRevokedScriptedProxy(handler)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_PROXY_ARG_REVOKED, "2");
+ return false;
+ }
+
+ // Steps 5-6, 8.
+ RootedValue priv(cx, ObjectValue(*target));
+ JSObject* proxy_ =
+ NewProxyObject(cx, &ScriptedProxyHandler::singleton, priv, TaggedProto::LazyProto);
+ if (!proxy_)
+ return false;
+
+ // Step 9 (reordered).
+ Rooted<ProxyObject*> proxy(cx, &proxy_->as<ProxyObject>());
+ proxy->setExtra(ScriptedProxyHandler::HANDLER_EXTRA, ObjectValue(*handler));
+
+ // Step 7.
+ uint32_t callable = target->isCallable() ? ScriptedProxyHandler::IS_CALLABLE : 0;
+ uint32_t constructor = target->isConstructor() ? ScriptedProxyHandler::IS_CONSTRUCTOR : 0;
+ proxy->setExtra(ScriptedProxyHandler::IS_CALLCONSTRUCT_EXTRA,
+ PrivateUint32Value(callable | constructor));
+
+ // Step 10.
+ args.rval().setObject(*proxy);
+ return true;
+}
+
+bool
+js::proxy(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!ThrowIfNotConstructing(cx, args, "Proxy"))
+ return false;
+
+ return ProxyCreate(cx, args, "Proxy");
+}
+
+static bool
+RevokeProxy(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ RootedFunction func(cx, &args.callee().as<JSFunction>());
+ RootedObject p(cx, func->getExtendedSlot(ScriptedProxyHandler::REVOKE_SLOT).toObjectOrNull());
+
+ if (p) {
+ func->setExtendedSlot(ScriptedProxyHandler::REVOKE_SLOT, NullValue());
+
+ MOZ_ASSERT(p->is<ProxyObject>());
+
+ p->as<ProxyObject>().setSameCompartmentPrivate(NullValue());
+ p->as<ProxyObject>().setExtra(ScriptedProxyHandler::HANDLER_EXTRA, NullValue());
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+bool
+js::proxy_revocable(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!ProxyCreate(cx, args, "Proxy.revocable"))
+ return false;
+
+ RootedValue proxyVal(cx, args.rval());
+ MOZ_ASSERT(proxyVal.toObject().is<ProxyObject>());
+
+ RootedObject revoker(cx, NewFunctionByIdWithReserved(cx, RevokeProxy, 0, 0,
+ AtomToId(cx->names().revoke)));
+ if (!revoker)
+ return false;
+
+ revoker->as<JSFunction>().initExtendedSlot(ScriptedProxyHandler::REVOKE_SLOT, proxyVal);
+
+ RootedPlainObject result(cx, NewBuiltinClassInstance<PlainObject>(cx));
+ if (!result)
+ return false;
+
+ RootedValue revokeVal(cx, ObjectValue(*revoker));
+ if (!DefineProperty(cx, result, cx->names().proxy, proxyVal) ||
+ !DefineProperty(cx, result, cx->names().revoke, revokeVal))
+ {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
diff --git a/js/src/proxy/ScriptedProxyHandler.h b/js/src/proxy/ScriptedProxyHandler.h
new file mode 100644
index 000000000..5e4c316c2
--- /dev/null
+++ b/js/src/proxy/ScriptedProxyHandler.h
@@ -0,0 +1,107 @@
+/* -*- 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/. */
+
+#ifndef proxy_ScriptedProxyHandler_h
+#define proxy_ScriptedProxyHandler_h
+
+#include "js/Proxy.h"
+
+namespace js {
+
+/* Derived class for all scripted proxy handlers. */
+class ScriptedProxyHandler : public BaseProxyHandler
+{
+ public:
+ constexpr ScriptedProxyHandler()
+ : BaseProxyHandler(&family)
+ { }
+
+ /* Standard internal methods. */
+ virtual bool getOwnPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
+ MutableHandle<PropertyDescriptor> desc) const override;
+ virtual bool defineProperty(JSContext* cx, HandleObject proxy, HandleId id,
+ Handle<PropertyDescriptor> desc,
+ ObjectOpResult& result) const override;
+ virtual bool ownPropertyKeys(JSContext* cx, HandleObject proxy,
+ AutoIdVector& props) const override;
+ virtual bool delete_(JSContext* cx, HandleObject proxy, HandleId id,
+ ObjectOpResult& result) const override;
+
+ virtual bool getPrototype(JSContext* cx, HandleObject proxy,
+ MutableHandleObject protop) const override;
+ virtual bool setPrototype(JSContext* cx, HandleObject proxy, HandleObject proto,
+ ObjectOpResult& result) const override;
+ /* Non-standard, but needed to correctly implement OrdinaryGetPrototypeOf. */
+ virtual bool getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary,
+ MutableHandleObject protop) const override;
+ /* Non-standard, but needed to handle revoked proxies. */
+ virtual bool setImmutablePrototype(JSContext* cx, HandleObject proxy,
+ bool* succeeded) const override;
+
+ virtual bool preventExtensions(JSContext* cx, HandleObject proxy,
+ ObjectOpResult& result) const override;
+ virtual bool isExtensible(JSContext* cx, HandleObject proxy, bool* extensible) const override;
+
+ virtual bool has(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) const override;
+ virtual bool get(JSContext* cx, HandleObject proxy, HandleValue receiver, HandleId id,
+ MutableHandleValue vp) const override;
+ virtual bool set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v,
+ HandleValue receiver, ObjectOpResult& result) const override;
+ virtual bool call(JSContext* cx, HandleObject proxy, const CallArgs& args) const override;
+ virtual bool construct(JSContext* cx, HandleObject proxy, const CallArgs& args) const override;
+
+ /* SpiderMonkey extensions. */
+ virtual bool hasOwn(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) const override {
+ return BaseProxyHandler::hasOwn(cx, proxy, id, bp);
+ }
+
+ // A scripted proxy should not be treated as generic in most contexts.
+ virtual bool nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl,
+ const CallArgs& args) const override;
+ virtual bool hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v,
+ bool* bp) const override;
+ virtual bool getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls) const override;
+ virtual bool isArray(JSContext* cx, HandleObject proxy,
+ JS::IsArrayAnswer* answer) const override;
+ virtual const char* className(JSContext* cx, HandleObject proxy) const override;
+ virtual JSString* fun_toString(JSContext* cx, HandleObject proxy,
+ unsigned indent) const override;
+ virtual bool regexp_toShared(JSContext* cx, HandleObject proxy,
+ RegExpGuard* g) const override;
+ virtual bool boxedValue_unbox(JSContext* cx, HandleObject proxy,
+ MutableHandleValue vp) const override;
+
+ virtual bool isCallable(JSObject* obj) const override;
+ virtual bool isConstructor(JSObject* obj) const override;
+
+ virtual bool isScripted() const override { return true; }
+
+ static const char family;
+ static const ScriptedProxyHandler singleton;
+
+ // The "proxy extra" slot index in which the handler is stored. Revocable proxies need to set
+ // this at revocation time.
+ static const int HANDLER_EXTRA = 0;
+ static const int IS_CALLCONSTRUCT_EXTRA = 1;
+ // Bitmasks for the "call/construct" slot
+ static const int IS_CALLABLE = 1 << 0;
+ static const int IS_CONSTRUCTOR = 1 << 1;
+ // The "function extended" slot index in which the revocation object is stored. Per spec, this
+ // is to be cleared during the first revocation.
+ static const int REVOKE_SLOT = 0;
+
+ static JSObject* handlerObject(const JSObject* proxy);
+};
+
+bool
+proxy(JSContext* cx, unsigned argc, Value* vp);
+
+bool
+proxy_revocable(JSContext* cx, unsigned argc, Value* vp);
+
+} /* namespace js */
+
+#endif /* proxy_ScriptedProxyHandler_h */
diff --git a/js/src/proxy/SecurityWrapper.cpp b/js/src/proxy/SecurityWrapper.cpp
new file mode 100644
index 000000000..710faf9b0
--- /dev/null
+++ b/js/src/proxy/SecurityWrapper.cpp
@@ -0,0 +1,153 @@
+/* -*- 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 "jsapi.h"
+#include "jswrapper.h"
+
+#include "jsatominlines.h"
+
+using namespace js;
+
+static void
+ReportUnwrapDenied(JSContext *cx)
+{
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_UNWRAP_DENIED);
+}
+
+template <class Base>
+bool
+SecurityWrapper<Base>::enter(JSContext* cx, HandleObject wrapper, HandleId id,
+ Wrapper::Action act, bool* bp) const
+{
+ ReportUnwrapDenied(cx);
+ *bp = false;
+ return false;
+}
+
+template <class Base>
+bool
+SecurityWrapper<Base>::nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl,
+ const CallArgs& args) const
+{
+ ReportUnwrapDenied(cx);
+ return false;
+}
+
+template <class Base>
+bool
+SecurityWrapper<Base>::setPrototype(JSContext* cx, HandleObject wrapper, HandleObject proto,
+ ObjectOpResult& result) const
+{
+ ReportUnwrapDenied(cx);
+ return false;
+}
+
+template <class Base>
+bool
+SecurityWrapper<Base>::setImmutablePrototype(JSContext* cx, HandleObject wrapper,
+ bool* succeeded) const
+{
+ ReportUnwrapDenied(cx);
+ return false;
+}
+
+template <class Base>
+bool
+SecurityWrapper<Base>::preventExtensions(JSContext* cx, HandleObject wrapper,
+ ObjectOpResult& result) const
+{
+ // Just like BaseProxyHandler, SecurityWrappers claim by default to always
+ // be extensible, so as not to leak information about the state of the
+ // underlying wrapped thing.
+ return result.fail(JSMSG_CANT_CHANGE_EXTENSIBILITY);
+}
+
+template <class Base>
+bool
+SecurityWrapper<Base>::isExtensible(JSContext* cx, HandleObject wrapper, bool* extensible) const
+{
+ // See above.
+ *extensible = true;
+ return true;
+}
+
+template <class Base>
+bool
+SecurityWrapper<Base>::getBuiltinClass(JSContext* cx, HandleObject wrapper,
+ ESClass* cls) const
+{
+ *cls = ESClass::Other;
+ return true;
+}
+
+template <class Base>
+bool
+SecurityWrapper<Base>::isArray(JSContext* cx, HandleObject obj, JS::IsArrayAnswer* answer) const
+{
+ // This should ReportUnwrapDenied(cx), but bug 849730 disagrees. :-(
+ *answer = JS::IsArrayAnswer::NotArray;
+ return true;
+}
+
+template <class Base>
+bool
+SecurityWrapper<Base>::regexp_toShared(JSContext* cx, HandleObject obj, RegExpGuard* g) const
+{
+ return Base::regexp_toShared(cx, obj, g);
+}
+
+template <class Base>
+bool
+SecurityWrapper<Base>::boxedValue_unbox(JSContext* cx, HandleObject obj, MutableHandleValue vp) const
+{
+ vp.setUndefined();
+ return true;
+}
+
+template <class Base>
+bool
+SecurityWrapper<Base>::defineProperty(JSContext* cx, HandleObject wrapper, HandleId id,
+ Handle<PropertyDescriptor> desc,
+ ObjectOpResult& result) const
+{
+ if (desc.getter() || desc.setter()) {
+ RootedValue idVal(cx, IdToValue(id));
+ JSString* str = ValueToSource(cx, idVal);
+ if (!str)
+ return false;
+ 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_ACCESSOR_DEF_DENIED, prop);
+ return false;
+ }
+
+ return Base::defineProperty(cx, wrapper, id, desc, result);
+}
+
+template <class Base>
+bool
+SecurityWrapper<Base>::watch(JSContext* cx, HandleObject proxy,
+ HandleId id, HandleObject callable) const
+{
+ ReportUnwrapDenied(cx);
+ return false;
+}
+
+template <class Base>
+bool
+SecurityWrapper<Base>::unwatch(JSContext* cx, HandleObject proxy,
+ HandleId id) const
+{
+ ReportUnwrapDenied(cx);
+ return false;
+}
+
+
+template class js::SecurityWrapper<Wrapper>;
+template class js::SecurityWrapper<CrossCompartmentWrapper>;
diff --git a/js/src/proxy/Wrapper.cpp b/js/src/proxy/Wrapper.cpp
new file mode 100644
index 000000000..43d559ff3
--- /dev/null
+++ b/js/src/proxy/Wrapper.cpp
@@ -0,0 +1,420 @@
+/* -*- 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 "jscntxt.h"
+#include "jscompartment.h"
+#include "jsexn.h"
+#include "jswrapper.h"
+
+#include "js/Proxy.h"
+#include "vm/ErrorObject.h"
+#include "vm/ProxyObject.h"
+#include "vm/WrapperObject.h"
+
+#include "jsobjinlines.h"
+
+#include "vm/NativeObject-inl.h"
+
+using namespace js;
+
+bool
+Wrapper::finalizeInBackground(const Value& priv) const
+{
+ if (!priv.isObject())
+ return true;
+
+ /*
+ * Make the 'background-finalized-ness' of the wrapper the same as the
+ * wrapped object, to allow transplanting between them.
+ *
+ * If the wrapped object is in the nursery then we know it doesn't have a
+ * finalizer, and so background finalization is ok.
+ */
+ if (IsInsideNursery(&priv.toObject()))
+ return true;
+ return IsBackgroundFinalized(priv.toObject().asTenured().getAllocKind());
+}
+
+bool
+Wrapper::getOwnPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
+ MutableHandle<PropertyDescriptor> desc) const
+{
+ assertEnteredPolicy(cx, proxy, id, GET | SET | GET_PROPERTY_DESCRIPTOR);
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return GetOwnPropertyDescriptor(cx, target, id, desc);
+}
+
+bool
+Wrapper::defineProperty(JSContext* cx, HandleObject proxy, HandleId id,
+ Handle<PropertyDescriptor> desc, ObjectOpResult& result) const
+{
+ assertEnteredPolicy(cx, proxy, id, SET);
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return DefineProperty(cx, target, id, desc, result);
+}
+
+bool
+Wrapper::ownPropertyKeys(JSContext* cx, HandleObject proxy, AutoIdVector& props) const
+{
+ assertEnteredPolicy(cx, proxy, JSID_VOID, ENUMERATE);
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return GetPropertyKeys(cx, target, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, &props);
+}
+
+bool
+Wrapper::delete_(JSContext* cx, HandleObject proxy, HandleId id, ObjectOpResult& result) const
+{
+ assertEnteredPolicy(cx, proxy, id, SET);
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return DeleteProperty(cx, target, id, result);
+}
+
+bool
+Wrapper::enumerate(JSContext* cx, HandleObject proxy, MutableHandleObject objp) const
+{
+ assertEnteredPolicy(cx, proxy, JSID_VOID, ENUMERATE);
+ MOZ_ASSERT(!hasPrototype()); // Should never be called if there's a prototype.
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return GetIterator(cx, target, 0, objp);
+}
+
+bool
+Wrapper::getPrototype(JSContext* cx, HandleObject proxy, MutableHandleObject protop) const
+{
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return GetPrototype(cx, target, protop);
+}
+
+bool
+Wrapper::setPrototype(JSContext* cx, HandleObject proxy, HandleObject proto,
+ ObjectOpResult& result) const
+{
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return SetPrototype(cx, target, proto, result);
+}
+
+bool
+Wrapper::getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy,
+ bool* isOrdinary, MutableHandleObject protop) const
+{
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return GetPrototypeIfOrdinary(cx, target, isOrdinary, protop);
+}
+
+bool
+Wrapper::setImmutablePrototype(JSContext* cx, HandleObject proxy, bool* succeeded) const
+{
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return SetImmutablePrototype(cx, target, succeeded);
+}
+
+bool
+Wrapper::preventExtensions(JSContext* cx, HandleObject proxy, ObjectOpResult& result) const
+{
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return PreventExtensions(cx, target, result);
+}
+
+bool
+Wrapper::isExtensible(JSContext* cx, HandleObject proxy, bool* extensible) const
+{
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return IsExtensible(cx, target, extensible);
+}
+
+bool
+Wrapper::has(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) const
+{
+ assertEnteredPolicy(cx, proxy, id, GET);
+ MOZ_ASSERT(!hasPrototype()); // Should never be called if there's a prototype.
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return HasProperty(cx, target, id, bp);
+}
+
+bool
+Wrapper::get(JSContext* cx, HandleObject proxy, HandleValue receiver, HandleId id,
+ MutableHandleValue vp) const
+{
+ assertEnteredPolicy(cx, proxy, id, GET);
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return GetProperty(cx, target, receiver, id, vp);
+}
+
+bool
+Wrapper::set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v, HandleValue receiver,
+ ObjectOpResult& result) const
+{
+ assertEnteredPolicy(cx, proxy, id, SET);
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return SetProperty(cx, target, id, v, receiver, result);
+}
+
+bool
+Wrapper::call(JSContext* cx, HandleObject proxy, const CallArgs& args) const
+{
+ assertEnteredPolicy(cx, proxy, JSID_VOID, CALL);
+ RootedValue target(cx, proxy->as<ProxyObject>().private_());
+
+ InvokeArgs iargs(cx);
+ if (!FillArgumentsFromArraylike(cx, iargs, args))
+ return false;
+
+ return js::Call(cx, target, args.thisv(), iargs, args.rval());
+}
+
+bool
+Wrapper::construct(JSContext* cx, HandleObject proxy, const CallArgs& args) const
+{
+ assertEnteredPolicy(cx, proxy, JSID_VOID, CALL);
+
+ RootedValue target(cx, proxy->as<ProxyObject>().private_());
+ if (!IsConstructor(target)) {
+ ReportValueError(cx, JSMSG_NOT_CONSTRUCTOR, JSDVG_IGNORE_STACK, target, nullptr);
+ return false;
+ }
+
+ ConstructArgs cargs(cx);
+ if (!FillArgumentsFromArraylike(cx, cargs, args))
+ return false;
+
+ RootedObject obj(cx);
+ if (!Construct(cx, target, cargs, args.newTarget(), &obj))
+ return false;
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+bool
+Wrapper::getPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
+ MutableHandle<PropertyDescriptor> desc) const
+{
+ assertEnteredPolicy(cx, proxy, id, GET | SET | GET_PROPERTY_DESCRIPTOR);
+ MOZ_ASSERT(!hasPrototype()); // Should never be called if there's a prototype.
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return GetPropertyDescriptor(cx, target, id, desc);
+}
+
+bool
+Wrapper::hasOwn(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) const
+{
+ assertEnteredPolicy(cx, proxy, id, GET);
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return HasOwnProperty(cx, target, id, bp);
+}
+
+bool
+Wrapper::getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject proxy,
+ AutoIdVector& props) const
+{
+ assertEnteredPolicy(cx, proxy, JSID_VOID, ENUMERATE);
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return GetPropertyKeys(cx, target, JSITER_OWNONLY, &props);
+}
+
+bool
+Wrapper::nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl,
+ const CallArgs& args) const
+{
+ args.setThis(ObjectValue(*args.thisv().toObject().as<ProxyObject>().target()));
+ if (!test(args.thisv())) {
+ ReportIncompatible(cx, args);
+ return false;
+ }
+
+ return CallNativeImpl(cx, impl, args);
+}
+
+bool
+Wrapper::hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v,
+ bool* bp) const
+{
+ assertEnteredPolicy(cx, proxy, JSID_VOID, GET);
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return HasInstance(cx, target, v, bp);
+}
+
+bool
+Wrapper::getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls) const
+{
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return GetBuiltinClass(cx, target, cls);
+}
+
+bool
+Wrapper::isArray(JSContext* cx, HandleObject proxy, JS::IsArrayAnswer* answer) const
+{
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return IsArray(cx, target, answer);
+}
+
+const char*
+Wrapper::className(JSContext* cx, HandleObject proxy) const
+{
+ assertEnteredPolicy(cx, proxy, JSID_VOID, GET);
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return GetObjectClassName(cx, target);
+}
+
+JSString*
+Wrapper::fun_toString(JSContext* cx, HandleObject proxy, unsigned indent) const
+{
+ assertEnteredPolicy(cx, proxy, JSID_VOID, GET);
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return fun_toStringHelper(cx, target, indent);
+}
+
+bool
+Wrapper::regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const
+{
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return RegExpToShared(cx, target, g);
+}
+
+bool
+Wrapper::boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp) const
+{
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return Unbox(cx, target, vp);
+}
+
+bool
+Wrapper::isCallable(JSObject* obj) const
+{
+ JSObject * target = obj->as<ProxyObject>().target();
+ return target->isCallable();
+}
+
+bool
+Wrapper::isConstructor(JSObject* obj) const
+{
+ // For now, all wrappers are constructable if they are callable. We will want to eventually
+ // decouple this behavior, but none of the Wrapper infrastructure is currently prepared for
+ // that.
+ return isCallable(obj);
+}
+
+JSObject*
+Wrapper::weakmapKeyDelegate(JSObject* proxy) const
+{
+ return UncheckedUnwrap(proxy);
+}
+
+JSObject*
+Wrapper::New(JSContext* cx, JSObject* obj, const Wrapper* handler,
+ const WrapperOptions& options)
+{
+ RootedValue priv(cx, ObjectValue(*obj));
+ return NewProxyObject(cx, handler, priv, options.proto(), options);
+}
+
+JSObject*
+Wrapper::Renew(JSContext* cx, JSObject* existing, JSObject* obj, const Wrapper* handler)
+{
+ existing->as<ProxyObject>().renew(cx, handler, ObjectValue(*obj));
+ return existing;
+}
+
+const Wrapper*
+Wrapper::wrapperHandler(JSObject* wrapper)
+{
+ MOZ_ASSERT(wrapper->is<WrapperObject>());
+ return static_cast<const Wrapper*>(wrapper->as<ProxyObject>().handler());
+}
+
+JSObject*
+Wrapper::wrappedObject(JSObject* wrapper)
+{
+ MOZ_ASSERT(wrapper->is<WrapperObject>());
+ JSObject* target = wrapper->as<ProxyObject>().target();
+ if (target)
+ JS::ExposeObjectToActiveJS(target);
+ return target;
+}
+
+JS_FRIEND_API(JSObject*)
+js::UncheckedUnwrap(JSObject* wrapped, bool stopAtWindowProxy, unsigned* flagsp)
+{
+ unsigned flags = 0;
+ while (true) {
+ if (!wrapped->is<WrapperObject>() ||
+ MOZ_UNLIKELY(stopAtWindowProxy && IsWindowProxy(wrapped)))
+ {
+ break;
+ }
+ flags |= Wrapper::wrapperHandler(wrapped)->flags();
+ wrapped = wrapped->as<ProxyObject>().private_().toObjectOrNull();
+
+ // This can be called from Wrapper::weakmapKeyDelegate() on a wrapper
+ // whose referent has been moved while it is still unmarked.
+ if (wrapped)
+ wrapped = MaybeForwarded(wrapped);
+ }
+ if (flagsp)
+ *flagsp = flags;
+ return wrapped;
+}
+
+JS_FRIEND_API(JSObject*)
+js::CheckedUnwrap(JSObject* obj, bool stopAtWindowProxy)
+{
+ while (true) {
+ JSObject* wrapper = obj;
+ obj = UnwrapOneChecked(obj, stopAtWindowProxy);
+ if (!obj || obj == wrapper)
+ return obj;
+ }
+}
+
+JS_FRIEND_API(JSObject*)
+js::UnwrapOneChecked(JSObject* obj, bool stopAtWindowProxy)
+{
+ if (!obj->is<WrapperObject>() ||
+ MOZ_UNLIKELY(IsWindowProxy(obj) && stopAtWindowProxy))
+ {
+ return obj;
+ }
+
+ const Wrapper* handler = Wrapper::wrapperHandler(obj);
+ return handler->hasSecurityPolicy() ? nullptr : Wrapper::wrappedObject(obj);
+}
+
+const char Wrapper::family = 0;
+const Wrapper Wrapper::singleton((unsigned)0);
+const Wrapper Wrapper::singletonWithPrototype((unsigned)0, true);
+JSObject* Wrapper::defaultProto = TaggedProto::LazyProto;
+
+/* Compartments. */
+
+JSObject*
+js::TransparentObjectWrapper(JSContext* cx, HandleObject existing, HandleObject obj)
+{
+ // Allow wrapping outer window proxies.
+ MOZ_ASSERT(!obj->is<WrapperObject>() || IsWindowProxy(obj));
+ return Wrapper::New(cx, obj, &CrossCompartmentWrapper::singleton);
+}
+
+ErrorCopier::~ErrorCopier()
+{
+ JSContext* cx = ac->context()->asJSContext();
+
+ // The provenance of Debugger.DebuggeeWouldRun is the topmost locking
+ // debugger compartment; it should not be copied around.
+ if (ac->origin() != cx->compartment() &&
+ cx->isExceptionPending() &&
+ !cx->isThrowingDebuggeeWouldRun())
+ {
+ RootedValue exc(cx);
+ if (cx->getPendingException(&exc) && exc.isObject() && exc.toObject().is<ErrorObject>()) {
+ cx->clearPendingException();
+ ac.reset();
+ Rooted<ErrorObject*> errObj(cx, &exc.toObject().as<ErrorObject>());
+ JSObject* copyobj = CopyErrorObject(cx, errObj);
+ if (copyobj)
+ cx->setPendingException(ObjectValue(*copyobj));
+ }
+ }
+}