diff --git a/content/html/document/test/Makefile.in b/content/html/document/test/Makefile.in
index 73a216d0474..0cbab9038a4 100644
--- a/content/html/document/test/Makefile.in
+++ b/content/html/document/test/Makefile.in
@@ -35,6 +35,7 @@ MOCHITEST_FILES = test_bug1682.html \
test_viewport.html \
test_documentAll.html \
test_document-element-inserted.html \
+ test_document.watch.html \
$(filter disabled-temporarily--bug-559932, test_bug445004.html) \
bug445004-inner.js \
bug445004-outer-rel.html \
diff --git a/content/html/document/test/test_document.watch.html b/content/html/document/test/test_document.watch.html
new file mode 100644
index 00000000000..54509823bef
--- /dev/null
+++ b/content/html/document/test/test_document.watch.html
@@ -0,0 +1,129 @@
+
+
+
+
+
+ Test for Bug 903332
+
+
+
+
+
+Mozilla Bug 903332
+
+
+
+
+
+
+
+
diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp
index c551b8335e2..ccbe4306225 100644
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -604,6 +604,11 @@ public:
virtual bool enumerate(JSContext *cx, JS::Handle proxy,
JS::AutoIdVector &props) MOZ_OVERRIDE;
+ virtual bool watch(JSContext *cx, JS::Handle proxy,
+ JS::Handle id, JS::Handle callable) MOZ_OVERRIDE;
+ virtual bool unwatch(JSContext *cx, JS::Handle proxy,
+ JS::Handle id) MOZ_OVERRIDE;
+
// Derived traps
virtual bool has(JSContext *cx, JS::Handle proxy,
JS::Handle id, bool *bp) MOZ_OVERRIDE;
@@ -953,6 +958,20 @@ nsOuterWindowProxy::AppendIndexedPropertyNames(JSContext *cx, JSObject *proxy,
return true;
}
+bool
+nsOuterWindowProxy::watch(JSContext *cx, JS::Handle proxy,
+ JS::Handle id, JS::Handle callable)
+{
+ return js::WatchGuts(cx, proxy, id, callable);
+}
+
+bool
+nsOuterWindowProxy::unwatch(JSContext *cx, JS::Handle proxy,
+ JS::Handle id)
+{
+ return js::UnwatchGuts(cx, proxy, id);
+}
+
nsOuterWindowProxy
nsOuterWindowProxy::singleton;
diff --git a/dom/bindings/DOMJSProxyHandler.cpp b/dom/bindings/DOMJSProxyHandler.cpp
index 3bb3185d181..495d11abee3 100644
--- a/dom/bindings/DOMJSProxyHandler.cpp
+++ b/dom/bindings/DOMJSProxyHandler.cpp
@@ -235,6 +235,19 @@ BaseDOMProxyHandler::enumerate(JSContext* cx, JS::Handle proxy,
(!proto || js::GetPropertyNames(cx, proto, 0, &props));
}
+bool
+BaseDOMProxyHandler::watch(JSContext* cx, JS::Handle proxy, JS::Handle id,
+ JS::Handle callable)
+{
+ return js::WatchGuts(cx, proxy, id, callable);
+}
+
+bool
+BaseDOMProxyHandler::unwatch(JSContext* cx, JS::Handle proxy, JS::Handle id)
+{
+ return js::UnwatchGuts(cx, proxy, id);
+}
+
bool
DOMProxyHandler::has(JSContext* cx, JS::Handle proxy, JS::Handle id, bool* bp)
{
diff --git a/dom/bindings/DOMJSProxyHandler.h b/dom/bindings/DOMJSProxyHandler.h
index 9b27d082d0a..891f4d87533 100644
--- a/dom/bindings/DOMJSProxyHandler.h
+++ b/dom/bindings/DOMJSProxyHandler.h
@@ -58,6 +58,11 @@ public:
JS::Handle id,
JS::MutableHandle desc,
unsigned flags) MOZ_OVERRIDE;
+
+ bool watch(JSContext* cx, JS::Handle proxy, JS::Handle id,
+ JS::Handle callable) MOZ_OVERRIDE;
+ bool unwatch(JSContext* cx, JS::Handle proxy,
+ JS::Handle id) MOZ_OVERRIDE;
};
class DOMProxyHandler : public BaseDOMProxyHandler
diff --git a/js/public/Class.h b/js/public/Class.h
index 3c085170737..c3f97e83b9c 100644
--- a/js/public/Class.h
+++ b/js/public/Class.h
@@ -377,6 +377,11 @@ typedef bool
typedef bool
(* DeleteSpecialOp)(JSContext *cx, JS::HandleObject obj, HandleSpecialId sid, bool *succeeded);
+typedef bool
+(* WatchOp)(JSContext *cx, JS::HandleObject obj, JS::HandleId id, JS::HandleObject callable);
+
+typedef bool
+(* UnwatchOp)(JSContext *cx, JS::HandleObject obj, JS::HandleId id);
typedef JSObject *
(* ObjectOp)(JSContext *cx, JS::HandleObject obj);
@@ -465,6 +470,8 @@ struct ObjectOps
DeletePropertyOp deleteProperty;
DeleteElementOp deleteElement;
DeleteSpecialOp deleteSpecial;
+ WatchOp watch;
+ UnwatchOp unwatch;
JSNewEnumerateOp enumerate;
ObjectOp thisObject;
@@ -473,7 +480,7 @@ struct ObjectOps
#define JS_NULL_OBJECT_OPS \
{nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr, \
nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr, \
- nullptr,nullptr,nullptr,nullptr,nullptr,nullptr}
+ nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr}
} // namespace js
@@ -502,7 +509,7 @@ struct JSClass {
JSNative construct;
JSTraceOp trace;
- void *reserved[40];
+ void *reserved[42];
};
#define JSCLASS_HAS_PRIVATE (1<<0) // objects have private slot
diff --git a/js/src/builtin/Object.cpp b/js/src/builtin/Object.cpp
index f4c4bbb6933..9a74b057c93 100644
--- a/js/src/builtin/Object.cpp
+++ b/js/src/builtin/Object.cpp
@@ -525,9 +525,9 @@ obj_getPrototypeOf(JSContext *cx, unsigned argc, Value *vp)
#if JS_HAS_OBJ_WATCHPOINT
-static bool
-obj_watch_handler(JSContext *cx, JSObject *obj_, jsid id_, jsval old,
- jsval *nvp, void *closure)
+bool
+js::WatchHandler(JSContext *cx, JSObject *obj_, jsid id_, JS::Value old,
+ JS::Value *nvp, void *closure)
{
RootedObject obj(cx, obj_);
RootedId id(cx, id_);
@@ -577,9 +577,11 @@ obj_watch(JSContext *cx, unsigned argc, Value *vp)
if (!CheckAccess(cx, obj, propid, JSACC_WATCH, &tmp, &attrs))
return false;
- args.rval().setUndefined();
+ if (!JSObject::watch(cx, obj, propid, callable))
+ return false;
- return JS_SetWatchPoint(cx, obj, propid, obj_watch_handler, callable);
+ args.rval().setUndefined();
+ return true;
}
static bool
@@ -602,7 +604,7 @@ obj_unwatch(JSContext *cx, unsigned argc, Value *vp)
id = JSID_VOID;
}
- if (!JS_ClearWatchPoint(cx, obj, id, nullptr, nullptr))
+ if (!JSObject::unwatch(cx, obj, id))
return false;
args.rval().setUndefined();
diff --git a/js/src/builtin/Object.h b/js/src/builtin/Object.h
index 5e528f44aaa..39950d1b1b4 100644
--- a/js/src/builtin/Object.h
+++ b/js/src/builtin/Object.h
@@ -8,7 +8,8 @@
#define builtin_Object_h
#include "jsapi.h"
-#include "js/Value.h"
+
+namespace JS { class Value; }
namespace js {
@@ -25,6 +26,10 @@ JSString *
ObjectToSource(JSContext *cx, JS::HandleObject obj);
#endif // JS_HAS_TOSOURCE
+extern bool
+WatchHandler(JSContext *cx, JSObject *obj, jsid id, JS::Value old,
+ JS::Value *nvp, void *closure);
+
} /* namespace js */
#endif /* builtin_Object_h */
diff --git a/js/src/builtin/TypedObject.cpp b/js/src/builtin/TypedObject.cpp
index 583de578c5f..064c97fc6c0 100644
--- a/js/src/builtin/TypedObject.cpp
+++ b/js/src/builtin/TypedObject.cpp
@@ -2169,6 +2169,7 @@ const Class TypedObject::class_ = {
TypedDatum::obj_deleteProperty,
TypedDatum::obj_deleteElement,
TypedDatum::obj_deleteSpecial,
+ nullptr, nullptr, // watch/unwatch
TypedDatum::obj_enumerate,
nullptr, /* thisObject */
}
@@ -2259,6 +2260,7 @@ const Class TypedHandle::class_ = {
TypedDatum::obj_deleteProperty,
TypedDatum::obj_deleteElement,
TypedDatum::obj_deleteSpecial,
+ nullptr, nullptr, // watch/unwatch
TypedDatum::obj_enumerate,
nullptr, /* thisObject */
}
diff --git a/js/src/jsfriendapi.h b/js/src/jsfriendapi.h
index d0c13dfd941..2dda4f373b0 100644
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -1298,6 +1298,33 @@ JS_GetDataViewByteLength(JSObject *obj);
JS_FRIEND_API(void *)
JS_GetDataViewData(JSObject *obj);
+namespace js {
+
+/*
+ * Add a watchpoint -- in the Object.prototype.watch sense -- to |obj| for the
+ * property |id|, using the callable object |callable| as the function to be
+ * called for notifications.
+ *
+ * This is an internal function exposed -- temporarily -- only so that DOM
+ * proxies can be watchable. Don't use it! We'll soon kill off the
+ * Object.prototype.{,un}watch functions, at which point this will go too.
+ */
+extern JS_FRIEND_API(bool)
+WatchGuts(JSContext *cx, JS::HandleObject obj, JS::HandleId id, JS::HandleObject callable);
+
+/*
+ * Remove a watchpoint -- in the Object.prototype.watch sense -- from |obj| for
+ * the property |id|.
+ *
+ * This is an internal function exposed -- temporarily -- only so that DOM
+ * proxies can be watchable. Don't use it! We'll soon kill off the
+ * Object.prototype.{,un}watch functions, at which point this will go too.
+ */
+extern JS_FRIEND_API(bool)
+UnwatchGuts(JSContext *cx, JS::HandleObject obj, JS::HandleId id);
+
+} // namespace js
+
/*
* A class, expected to be passed by value, which represents the CallArgs for a
* JSJitGetterOp.
diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp
index 59a8ba06c80..5031d952ee6 100644
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -21,6 +21,7 @@
#include "jsarray.h"
#include "jsatom.h"
#include "jscntxt.h"
+#include "jsfriendapi.h"
#include "jsfun.h"
#include "jsgc.h"
#include "jsiter.h"
@@ -35,6 +36,7 @@
#include "jswatchpoint.h"
#include "jswrapper.h"
+#include "builtin/Object.h"
#include "frontend/BytecodeCompiler.h"
#include "gc/Marking.h"
#include "jit/AsmJSModule.h"
@@ -5010,6 +5012,70 @@ baseops::DeleteSpecial(JSContext *cx, HandleObject obj, HandleSpecialId sid, boo
return baseops::DeleteGeneric(cx, obj, id, succeeded);
}
+bool
+js::WatchGuts(JSContext *cx, JS::HandleObject origObj, JS::HandleId id, JS::HandleObject callable)
+{
+ RootedObject obj(cx, GetInnerObject(cx, origObj));
+ if (origObj != obj) {
+ // If by unwrapping and innerizing, we changed the object, check again
+ // to make sure that we're allowed to set a watch point.
+ RootedValue v(cx);
+ unsigned attrs;
+ if (!CheckAccess(cx, obj, id, JSACC_WATCH, &v, &attrs))
+ return false;
+ }
+
+ if (obj->isNative()) {
+ // Use sparse indexes for watched objects, as dense elements can be
+ // written to without checking the watchpoint map.
+ if (!JSObject::sparsifyDenseElements(cx, obj))
+ return false;
+
+ types::MarkTypePropertyConfigured(cx, obj, id);
+ }
+
+ WatchpointMap *wpmap = cx->compartment()->watchpointMap;
+ if (!wpmap) {
+ wpmap = cx->runtime()->new_();
+ if (!wpmap || !wpmap->init()) {
+ js_ReportOutOfMemory(cx);
+ return false;
+ }
+ cx->compartment()->watchpointMap = wpmap;
+ }
+
+ return wpmap->watch(cx, obj, id, js::WatchHandler, callable);
+}
+
+bool
+baseops::Watch(JSContext *cx, JS::HandleObject obj, JS::HandleId id, JS::HandleObject callable)
+{
+ if (!obj->isNative()) {
+ JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_WATCH,
+ obj->getClass()->name);
+ return false;
+ }
+
+ return WatchGuts(cx, obj, id, callable);
+}
+
+bool
+js::UnwatchGuts(JSContext *cx, JS::HandleObject origObj, JS::HandleId id)
+{
+ // Looking in the map for an unsupported object will never hit, so we don't
+ // need to check for nativeness or watchable-ness here.
+ RootedObject obj(cx, GetInnerObject(cx, origObj));
+ if (WatchpointMap *wpmap = cx->compartment()->watchpointMap)
+ wpmap->unwatch(obj, id, nullptr, nullptr);
+ return true;
+}
+
+bool
+baseops::Unwatch(JSContext *cx, JS::HandleObject obj, JS::HandleId id)
+{
+ return UnwatchGuts(cx, obj, id);
+}
+
bool
js::HasDataProperty(JSContext *cx, JSObject *obj, jsid id, Value *vp)
{
diff --git a/js/src/jsobj.h b/js/src/jsobj.h
index 03f216f3358..39a7c892213 100644
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -155,6 +155,12 @@ DeleteSpecial(JSContext *cx, HandleObject obj, HandleSpecialId sid, bool *succee
extern bool
DeleteGeneric(JSContext *cx, HandleObject obj, HandleId id, bool *succeeded);
+extern bool
+Watch(JSContext *cx, JS::HandleObject obj, JS::HandleId id, JS::HandleObject callable);
+
+extern bool
+Unwatch(JSContext *cx, JS::HandleObject obj, JS::HandleId id);
+
} /* namespace js::baseops */
extern const Class IntlClass;
@@ -1093,6 +1099,10 @@ class JSObject : public js::ObjectImpl
static bool deleteByValue(JSContext *cx, js::HandleObject obj,
const js::Value &property, bool *succeeded);
+ static inline bool watch(JSContext *cx, JS::HandleObject obj, JS::HandleId id,
+ JS::HandleObject callable);
+ static inline bool unwatch(JSContext *cx, JS::HandleObject obj, JS::HandleId id);
+
static bool enumerate(JSContext *cx, JS::HandleObject obj, JSIterateOp iterop,
JS::MutableHandleValue statep, JS::MutableHandleId idp)
{
diff --git a/js/src/jsobjinlines.h b/js/src/jsobjinlines.h
index 81f2377680e..35fd8dd10f2 100644
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -72,6 +72,21 @@ JSObject::deleteSpecial(JSContext *cx, js::HandleObject obj, js::HandleSpecialId
return (op ? op : js::baseops::DeleteSpecial)(cx, obj, sid, succeeded);
}
+/* static */ inline bool
+JSObject::watch(JSContext *cx, JS::HandleObject obj, JS::HandleId id,
+ JS::HandleObject callable)
+{
+ js::WatchOp op = obj->getOps()->watch;
+ return (op ? op : js::baseops::Watch)(cx, obj, id, callable);
+}
+
+/* static */ inline bool
+JSObject::unwatch(JSContext *cx, JS::HandleObject obj, JS::HandleId id)
+{
+ js::UnwatchOp op = obj->getOps()->unwatch;
+ return (op ? op : js::baseops::Unwatch)(cx, obj, id);
+}
+
inline void
JSObject::finalize(js::FreeOp *fop)
{
diff --git a/js/src/jsproxy.cpp b/js/src/jsproxy.cpp
index 871510bbc40..7b1ba62377a 100644
--- a/js/src/jsproxy.cpp
+++ b/js/src/jsproxy.cpp
@@ -371,6 +371,19 @@ BaseProxyHandler::getPrototypeOf(JSContext *cx, HandleObject proxy, MutableHandl
return true;
}
+bool
+BaseProxyHandler::watch(JSContext *cx, HandleObject proxy, HandleId id, HandleObject callable)
+{
+ JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_WATCH,
+ proxy->getClass()->name);
+ return false;
+}
+
+bool
+BaseProxyHandler::unwatch(JSContext *cx, HandleObject proxy, HandleId id)
+{
+ return true;
+}
bool
DirectProxyHandler::getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
@@ -2747,6 +2760,20 @@ Proxy::getPrototypeOf(JSContext *cx, HandleObject proxy, MutableHandleObject pro
JSObject * const Proxy::LazyProto = reinterpret_cast(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().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().handler()->unwatch(cx, proxy, id);
+}
+
static JSObject *
proxy_innerObject(JSContext *cx, HandleObject obj)
{
@@ -3046,6 +3073,18 @@ proxy_Construct(JSContext *cx, unsigned argc, Value *vp)
return Proxy::construct(cx, proxy, args);
}
+static bool
+proxy_Watch(JSContext *cx, JS::HandleObject obj, JS::HandleId id, JS::HandleObject callable)
+{
+ return Proxy::watch(cx, obj, id, callable);
+}
+
+static bool
+proxy_Unwatch(JSContext *cx, JS::HandleObject obj, JS::HandleId id)
+{
+ return Proxy::unwatch(cx, obj, id);
+}
+
#define PROXY_CLASS_EXT \
{ \
nullptr, /* outerObject */ \
@@ -3098,6 +3137,7 @@ proxy_Construct(JSContext *cx, unsigned argc, Value *vp)
proxy_DeleteProperty, \
proxy_DeleteElement, \
proxy_DeleteSpecial, \
+ proxy_Watch, proxy_Unwatch, \
nullptr, /* enumerate */ \
nullptr, /* thisObject */ \
} \
@@ -3155,6 +3195,7 @@ const Class js::OuterWindowProxyObject::class_ = {
proxy_DeleteProperty,
proxy_DeleteElement,
proxy_DeleteSpecial,
+ proxy_Watch, proxy_Unwatch,
nullptr, /* enumerate */
nullptr, /* thisObject */
}
diff --git a/js/src/jsproxy.h b/js/src/jsproxy.h
index aab5136f632..0f59cba2549 100644
--- a/js/src/jsproxy.h
+++ b/js/src/jsproxy.h
@@ -166,6 +166,12 @@ class JS_FRIEND_API(BaseProxyHandler)
uint32_t index, MutableHandleValue vp, bool *present);
virtual bool getPrototypeOf(JSContext *cx, HandleObject proxy, MutableHandleObject protop);
+ // These two hooks must be overridden, or not overridden, in tandem -- no
+ // overriding just one!
+ virtual bool watch(JSContext *cx, JS::HandleObject proxy, JS::HandleId id,
+ JS::HandleObject callable);
+ virtual bool unwatch(JSContext *cx, JS::HandleObject proxy, JS::HandleId id);
+
/* See comment for weakmapKeyDelegateOp in js/Class.h. */
virtual JSObject *weakmapKeyDelegate(JSObject *proxy);
};
@@ -275,6 +281,10 @@ class Proxy
static bool defaultValue(JSContext *cx, HandleObject obj, JSType hint, MutableHandleValue vp);
static bool getPrototypeOf(JSContext *cx, HandleObject proxy, MutableHandleObject protop);
+ static bool watch(JSContext *cx, JS::HandleObject proxy, JS::HandleId id,
+ JS::HandleObject callable);
+ static bool unwatch(JSContext *cx, JS::HandleObject proxy, JS::HandleId id);
+
/* IC entry path for handling __noSuchMethod__ on access. */
static bool callProp(JSContext *cx, HandleObject proxy, HandleObject reveiver, HandleId id,
MutableHandleValue vp);
diff --git a/js/src/vm/ScopeObject.cpp b/js/src/vm/ScopeObject.cpp
index 82b23cb7545..fd9df13c383 100644
--- a/js/src/vm/ScopeObject.cpp
+++ b/js/src/vm/ScopeObject.cpp
@@ -536,6 +536,7 @@ const Class WithObject::class_ = {
with_DeleteProperty,
with_DeleteElement,
with_DeleteSpecial,
+ nullptr, nullptr, /* watch/unwatch */
with_Enumerate,
with_ThisObject,
}
diff --git a/js/src/vm/TypedArrayObject.cpp b/js/src/vm/TypedArrayObject.cpp
index 439a7a29c6e..8f90340191a 100644
--- a/js/src/vm/TypedArrayObject.cpp
+++ b/js/src/vm/TypedArrayObject.cpp
@@ -3470,6 +3470,7 @@ const Class ArrayBufferObject::class_ = {
ArrayBufferObject::obj_deleteProperty,
ArrayBufferObject::obj_deleteElement,
ArrayBufferObject::obj_deleteSpecial,
+ nullptr, nullptr, /* watch/unwatch */
ArrayBufferObject::obj_enumerate,
nullptr, /* thisObject */
}
@@ -3632,6 +3633,7 @@ IMPL_TYPED_ARRAY_COMBINED_UNWRAPPERS(Float64, double, double)
_typedArray##Object::obj_deleteProperty, \
_typedArray##Object::obj_deleteElement, \
_typedArray##Object::obj_deleteSpecial, \
+ nullptr, nullptr, /* watch/unwatch */ \
_typedArray##Object::obj_enumerate, \
nullptr, /* thisObject */ \
} \
diff --git a/js/xpconnect/src/XPCWrappedNativeJSOps.cpp b/js/xpconnect/src/XPCWrappedNativeJSOps.cpp
index 53b6f7f72e9..a3a69cf20f2 100644
--- a/js/xpconnect/src/XPCWrappedNativeJSOps.cpp
+++ b/js/xpconnect/src/XPCWrappedNativeJSOps.cpp
@@ -739,6 +739,7 @@ const XPCWrappedNativeJSClass XPC_WN_NoHelper_JSClass = {
nullptr, // deleteProperty
nullptr, // deleteElement
nullptr, // deleteSpecial
+ nullptr, nullptr, // watch/unwatch
XPC_WN_JSOp_Enumerate,
XPC_WN_JSOp_ThisObject,
}
diff --git a/js/xpconnect/src/xpcprivate.h b/js/xpconnect/src/xpcprivate.h
index 1e6fe32a678..35f7722d067 100644
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -1172,6 +1172,7 @@ XPC_WN_JSOp_ThisObject(JSContext *cx, JS::HandleObject obj);
nullptr, /* deleteProperty */ \
nullptr, /* deleteElement */ \
nullptr, /* deleteSpecial */ \
+ nullptr, nullptr, /* watch/unwatch */ \
XPC_WN_JSOp_Enumerate, \
XPC_WN_JSOp_ThisObject, \
}
@@ -1200,6 +1201,7 @@ XPC_WN_JSOp_ThisObject(JSContext *cx, JS::HandleObject obj);
nullptr, /* deleteProperty */ \
nullptr, /* deleteElement */ \
nullptr, /* deleteSpecial */ \
+ nullptr, nullptr, /* watch/unwatch */ \
XPC_WN_JSOp_Enumerate, \
XPC_WN_JSOp_ThisObject, \
}