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 c7123c716b4..70dd5d2161b 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_); @@ -579,7 +579,7 @@ obj_watch(JSContext *cx, unsigned argc, Value *vp) if (!CheckAccess(cx, obj, propid, JSACC_WATCH, &tmp, &attrs)) return false; - if (!JS_SetWatchPoint(cx, obj, propid, obj_watch_handler, callable)) + if (!JSObject::watch(cx, obj, propid, callable)) return false; args.rval().setUndefined(); @@ -608,7 +608,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 7e05a78fdf2..53aa651fafb 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" @@ -4984,6 +4986,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 1d73720e26b..8909ec0bae4 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; @@ -1090,6 +1096,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 edc32e4c755..6d148542f49 100644 --- a/js/src/jsobjinlines.h +++ b/js/src/jsobjinlines.h @@ -69,6 +69,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, \ } diff --git a/testing/mochitest/b2g.json b/testing/mochitest/b2g.json index 970c5617e48..ac311ca2f9f 100644 --- a/testing/mochitest/b2g.json +++ b/testing/mochitest/b2g.json @@ -258,6 +258,7 @@ "content/base/test/test_bug424359-2.html":"", "content/base/test/test_mixed_content_blocker_bug803225.html":"", "content/html/document/test/test_non-ascii-cookie.html":"", + "content/html/document/test/test_document.watch.html":"expects document.cookie setting to work", "docshell/test/navigation/test_bug13871.html":"", "docshell/test/navigation/test_bug270414.html":"",