summaryrefslogtreecommitdiffstats
path: root/js/xpconnect/wrappers/XrayWrapper.h
diff options
context:
space:
mode:
Diffstat (limited to 'js/xpconnect/wrappers/XrayWrapper.h')
-rw-r--r--js/xpconnect/wrappers/XrayWrapper.h495
1 files changed, 495 insertions, 0 deletions
diff --git a/js/xpconnect/wrappers/XrayWrapper.h b/js/xpconnect/wrappers/XrayWrapper.h
new file mode 100644
index 0000000000..fb0c8b36c6
--- /dev/null
+++ b/js/xpconnect/wrappers/XrayWrapper.h
@@ -0,0 +1,495 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 XrayWrapper_h
+#define XrayWrapper_h
+
+#include "mozilla/Maybe.h"
+
+#include "WrapperFactory.h"
+
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "js/friend/XrayJitInfo.h" // JS::XrayJitInfo
+#include "js/Object.h" // JS::GetReservedSlot
+#include "js/Proxy.h"
+#include "js/Wrapper.h"
+
+// Slot where Xray functions for Web IDL methods store a pointer to
+// the Xray wrapper they're associated with.
+#define XRAY_DOM_FUNCTION_PARENT_WRAPPER_SLOT 0
+// Slot where in debug builds Xray functions for Web IDL methods store
+// a pointer to their themselves, just so we can assert that they're the
+// sort of functions we expect.
+#define XRAY_DOM_FUNCTION_NATIVE_SLOT_FOR_SELF 1
+
+// Xray wrappers re-resolve the original native properties on the native
+// object and always directly access to those properties.
+// Because they work so differently from the rest of the wrapper hierarchy,
+// we pull them out of the Wrapper inheritance hierarchy and create a
+// little world around them.
+
+class nsIPrincipal;
+
+namespace xpc {
+
+enum XrayType {
+ XrayForDOMObject,
+ XrayForJSObject,
+ XrayForOpaqueObject,
+ NotXray
+};
+
+class XrayTraits {
+ public:
+ constexpr XrayTraits() = default;
+
+ static JSObject* getTargetObject(JSObject* wrapper) {
+ JSObject* target =
+ js::UncheckedUnwrap(wrapper, /* stopAtWindowProxy = */ false);
+ if (target) {
+ JS::ExposeObjectToActiveJS(target);
+ }
+ return target;
+ }
+
+ // NB: resolveOwnProperty may decide whether or not to cache what it finds
+ // on the holder. If the result is not cached, the lookup will happen afresh
+ // for each access, which is the right thing for things like dynamic NodeList
+ // properties.
+ virtual bool resolveOwnProperty(
+ JSContext* cx, JS::HandleObject wrapper, JS::HandleObject target,
+ JS::HandleObject holder, JS::HandleId id,
+ JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc);
+
+ bool delete_(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id,
+ JS::ObjectOpResult& result) {
+ return result.succeed();
+ }
+
+ static bool getBuiltinClass(JSContext* cx, JS::HandleObject wrapper,
+ const js::Wrapper& baseInstance,
+ js::ESClass* cls) {
+ return baseInstance.getBuiltinClass(cx, wrapper, cls);
+ }
+
+ static const char* className(JSContext* cx, JS::HandleObject wrapper,
+ const js::Wrapper& baseInstance) {
+ return baseInstance.className(cx, wrapper);
+ }
+
+ virtual void preserveWrapper(JSObject* target) = 0;
+
+ bool getExpandoObject(JSContext* cx, JS::HandleObject target,
+ JS::HandleObject consumer,
+ JS::MutableHandleObject expandObject);
+ JSObject* ensureExpandoObject(JSContext* cx, JS::HandleObject wrapper,
+ JS::HandleObject target);
+
+ // Slots for holder objects.
+ enum {
+ HOLDER_SLOT_CACHED_PROTO = 0,
+ HOLDER_SLOT_EXPANDO = 1,
+ HOLDER_SHARED_SLOT_COUNT
+ };
+
+ static JSObject* getHolder(JSObject* wrapper);
+ JSObject* ensureHolder(JSContext* cx, JS::HandleObject wrapper);
+ virtual JSObject* createHolder(JSContext* cx, JSObject* wrapper) = 0;
+
+ JSObject* getExpandoChain(JS::HandleObject obj);
+ JSObject* detachExpandoChain(JS::HandleObject obj);
+ bool setExpandoChain(JSContext* cx, JS::HandleObject obj,
+ JS::HandleObject chain);
+ bool cloneExpandoChain(JSContext* cx, JS::HandleObject dst,
+ JS::HandleObject srcChain);
+
+ protected:
+ static const JSClass HolderClass;
+
+ // Get the JSClass we should use for our expando object.
+ virtual const JSClass* getExpandoClass(JSContext* cx,
+ JS::HandleObject target) const;
+
+ private:
+ bool expandoObjectMatchesConsumer(JSContext* cx,
+ JS::HandleObject expandoObject,
+ nsIPrincipal* consumerOrigin);
+
+ // |expandoChain| is the expando chain in the wrapped object's compartment.
+ // |exclusiveWrapper| is any xray that has exclusive use of the expando.
+ // |cx| may be in any compartment.
+ bool getExpandoObjectInternal(JSContext* cx, JSObject* expandoChain,
+ JS::HandleObject exclusiveWrapper,
+ nsIPrincipal* origin,
+ JS::MutableHandleObject expandoObject);
+
+ // |cx| is in the target's compartment, and |exclusiveWrapper| is any xray
+ // that has exclusive use of the expando. |exclusiveWrapperGlobal| is the
+ // caller's global and must be same-compartment with |exclusiveWrapper|.
+ JSObject* attachExpandoObject(JSContext* cx, JS::HandleObject target,
+ JS::HandleObject exclusiveWrapper,
+ JS::HandleObject exclusiveWrapperGlobal,
+ nsIPrincipal* origin);
+
+ XrayTraits(XrayTraits&) = delete;
+ const XrayTraits& operator=(XrayTraits&) = delete;
+};
+
+class DOMXrayTraits : public XrayTraits {
+ public:
+ constexpr DOMXrayTraits() = default;
+
+ static const XrayType Type = XrayForDOMObject;
+
+ virtual bool resolveOwnProperty(
+ JSContext* cx, JS::HandleObject wrapper, JS::HandleObject target,
+ JS::HandleObject holder, JS::HandleId id,
+ JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc) override;
+
+ bool delete_(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id,
+ JS::ObjectOpResult& result);
+
+ bool defineProperty(
+ JSContext* cx, JS::HandleObject wrapper, JS::HandleId id,
+ JS::Handle<JS::PropertyDescriptor> desc,
+ JS::Handle<mozilla::Maybe<JS::PropertyDescriptor>> existingDesc,
+ JS::Handle<JSObject*> existingHolder, JS::ObjectOpResult& result,
+ bool* done);
+ virtual bool enumerateNames(JSContext* cx, JS::HandleObject wrapper,
+ unsigned flags, JS::MutableHandleIdVector props);
+ static bool call(JSContext* cx, JS::HandleObject wrapper,
+ const JS::CallArgs& args, const js::Wrapper& baseInstance);
+ static bool construct(JSContext* cx, JS::HandleObject wrapper,
+ const JS::CallArgs& args,
+ const js::Wrapper& baseInstance);
+
+ static bool getPrototype(JSContext* cx, JS::HandleObject wrapper,
+ JS::HandleObject target,
+ JS::MutableHandleObject protop);
+
+ virtual void preserveWrapper(JSObject* target) override;
+
+ virtual JSObject* createHolder(JSContext* cx, JSObject* wrapper) override;
+
+ static DOMXrayTraits singleton;
+
+ protected:
+ virtual const JSClass* getExpandoClass(
+ JSContext* cx, JS::HandleObject target) const override;
+};
+
+class JSXrayTraits : public XrayTraits {
+ public:
+ static const XrayType Type = XrayForJSObject;
+
+ virtual bool resolveOwnProperty(
+ JSContext* cx, JS::HandleObject wrapper, JS::HandleObject target,
+ JS::HandleObject holder, JS::HandleId id,
+ JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc) override;
+
+ bool delete_(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id,
+ JS::ObjectOpResult& result);
+
+ bool defineProperty(
+ JSContext* cx, JS::HandleObject wrapper, JS::HandleId id,
+ JS::Handle<JS::PropertyDescriptor> desc,
+ JS::Handle<mozilla::Maybe<JS::PropertyDescriptor>> existingDesc,
+ JS::Handle<JSObject*> existingHolder, JS::ObjectOpResult& result,
+ bool* defined);
+
+ virtual bool enumerateNames(JSContext* cx, JS::HandleObject wrapper,
+ unsigned flags, JS::MutableHandleIdVector props);
+
+ static bool call(JSContext* cx, JS::HandleObject wrapper,
+ const JS::CallArgs& args, const js::Wrapper& baseInstance) {
+ JSXrayTraits& self = JSXrayTraits::singleton;
+ JS::RootedObject holder(cx, self.ensureHolder(cx, wrapper));
+ if (!holder) {
+ return false;
+ }
+ JSProtoKey key = xpc::JSXrayTraits::getProtoKey(holder);
+ if (key == JSProto_Function || key == JSProto_BoundFunction) {
+ return baseInstance.call(cx, wrapper, args);
+ }
+
+ JS::RootedValue v(cx, JS::ObjectValue(*wrapper));
+ js::ReportIsNotFunction(cx, v);
+ return false;
+ }
+
+ static bool construct(JSContext* cx, JS::HandleObject wrapper,
+ const JS::CallArgs& args,
+ const js::Wrapper& baseInstance);
+
+ bool getPrototype(JSContext* cx, JS::HandleObject wrapper,
+ JS::HandleObject target, JS::MutableHandleObject protop) {
+ JS::RootedObject holder(cx, ensureHolder(cx, wrapper));
+ if (!holder) {
+ return false;
+ }
+ JSProtoKey key = getProtoKey(holder);
+ if (isPrototype(holder)) {
+ JSProtoKey protoKey = js::InheritanceProtoKeyForStandardClass(key);
+ if (protoKey == JSProto_Null) {
+ protop.set(nullptr);
+ return true;
+ }
+ key = protoKey;
+ }
+
+ {
+ JSAutoRealm ar(cx, target);
+ if (!JS_GetClassPrototype(cx, key, protop)) {
+ return false;
+ }
+ }
+ return JS_WrapObject(cx, protop);
+ }
+
+ virtual void preserveWrapper(JSObject* target) override {
+ // In the case of pure JS objects, there is no underlying object, and
+ // the target is the canonical representation of state. If it gets
+ // collected, then expandos and such should be collected too. So there's
+ // nothing to do here.
+ }
+
+ enum {
+ SLOT_PROTOKEY = HOLDER_SHARED_SLOT_COUNT,
+ SLOT_ISPROTOTYPE,
+ SLOT_CONSTRUCTOR_FOR,
+ SLOT_COUNT
+ };
+ virtual JSObject* createHolder(JSContext* cx, JSObject* wrapper) override;
+
+ static JSProtoKey getProtoKey(JSObject* holder) {
+ int32_t key = JS::GetReservedSlot(holder, SLOT_PROTOKEY).toInt32();
+ return static_cast<JSProtoKey>(key);
+ }
+
+ static bool isPrototype(JSObject* holder) {
+ return JS::GetReservedSlot(holder, SLOT_ISPROTOTYPE).toBoolean();
+ }
+
+ static JSProtoKey constructorFor(JSObject* holder) {
+ int32_t key = JS::GetReservedSlot(holder, SLOT_CONSTRUCTOR_FOR).toInt32();
+ return static_cast<JSProtoKey>(key);
+ }
+
+ // Operates in the wrapper compartment.
+ static bool getOwnPropertyFromWrapperIfSafe(
+ JSContext* cx, JS::HandleObject wrapper, JS::HandleId id,
+ JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc);
+
+ // Like the above, but operates in the target compartment. wrapperGlobal is
+ // the caller's global (must be in the wrapper compartment).
+ static bool getOwnPropertyFromTargetIfSafe(
+ JSContext* cx, JS::HandleObject target, JS::HandleObject wrapper,
+ JS::HandleObject wrapperGlobal, JS::HandleId id,
+ JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc);
+
+ static const JSClass HolderClass;
+ static JSXrayTraits singleton;
+};
+
+// These traits are used when the target is not Xrayable and we therefore want
+// to make it opaque modulo the usual Xray machinery (like expandos and
+// .wrappedJSObject).
+class OpaqueXrayTraits : public XrayTraits {
+ public:
+ static const XrayType Type = XrayForOpaqueObject;
+
+ virtual bool resolveOwnProperty(
+ JSContext* cx, JS::HandleObject wrapper, JS::HandleObject target,
+ JS::HandleObject holder, JS::HandleId id,
+ JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc) override;
+
+ bool defineProperty(
+ JSContext* cx, JS::HandleObject wrapper, JS::HandleId id,
+ JS::Handle<JS::PropertyDescriptor> desc,
+ JS::Handle<mozilla::Maybe<JS::PropertyDescriptor>> existingDesc,
+ JS::Handle<JSObject*> existingHolder, JS::ObjectOpResult& result,
+ bool* defined) {
+ *defined = false;
+ return true;
+ }
+
+ virtual bool enumerateNames(JSContext* cx, JS::HandleObject wrapper,
+ unsigned flags, JS::MutableHandleIdVector props) {
+ return true;
+ }
+
+ static bool call(JSContext* cx, JS::HandleObject wrapper,
+ const JS::CallArgs& args, const js::Wrapper& baseInstance) {
+ JS::RootedValue v(cx, JS::ObjectValue(*wrapper));
+ js::ReportIsNotFunction(cx, v);
+ return false;
+ }
+
+ static bool construct(JSContext* cx, JS::HandleObject wrapper,
+ const JS::CallArgs& args,
+ const js::Wrapper& baseInstance) {
+ JS::RootedValue v(cx, JS::ObjectValue(*wrapper));
+ js::ReportIsNotFunction(cx, v);
+ return false;
+ }
+
+ bool getPrototype(JSContext* cx, JS::HandleObject wrapper,
+ JS::HandleObject target, JS::MutableHandleObject protop) {
+ // Opaque wrappers just get targetGlobal.Object.prototype as their
+ // prototype. This is preferable to using a null prototype because it
+ // lets things like |toString| and |__proto__| work.
+ {
+ JSAutoRealm ar(cx, target);
+ if (!JS_GetClassPrototype(cx, JSProto_Object, protop)) {
+ return false;
+ }
+ }
+ return JS_WrapObject(cx, protop);
+ }
+
+ static bool getBuiltinClass(JSContext* cx, JS::HandleObject wrapper,
+ const js::Wrapper& baseInstance,
+ js::ESClass* cls) {
+ *cls = js::ESClass::Other;
+ return true;
+ }
+
+ static const char* className(JSContext* cx, JS::HandleObject wrapper,
+ const js::Wrapper& baseInstance) {
+ return "Opaque";
+ }
+
+ virtual void preserveWrapper(JSObject* target) override {}
+
+ virtual JSObject* createHolder(JSContext* cx, JSObject* wrapper) override {
+ return JS_NewObjectWithGivenProto(cx, &HolderClass, nullptr);
+ }
+
+ static OpaqueXrayTraits singleton;
+};
+
+XrayType GetXrayType(JSObject* obj);
+XrayTraits* GetXrayTraits(JSObject* obj);
+
+template <typename Base, typename Traits>
+class XrayWrapper : public Base {
+ static_assert(std::is_base_of_v<js::BaseProxyHandler, Base>,
+ "Base *must* derive from js::BaseProxyHandler");
+
+ public:
+ constexpr explicit XrayWrapper(unsigned flags)
+ : Base(flags | WrapperFactory::IS_XRAY_WRAPPER_FLAG,
+ /* aHasPrototype = */ true){};
+
+ /* Standard internal methods. */
+ virtual bool getOwnPropertyDescriptor(
+ JSContext* cx, JS::Handle<JSObject*> wrapper, JS::Handle<jsid> id,
+ JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc)
+ const override;
+ virtual bool defineProperty(JSContext* cx, JS::Handle<JSObject*> wrapper,
+ JS::Handle<jsid> id,
+ JS::Handle<JS::PropertyDescriptor> desc,
+ JS::ObjectOpResult& result) const override;
+ virtual bool ownPropertyKeys(JSContext* cx, JS::Handle<JSObject*> wrapper,
+ JS::MutableHandleIdVector props) const override;
+ virtual bool delete_(JSContext* cx, JS::Handle<JSObject*> wrapper,
+ JS::Handle<jsid> id,
+ JS::ObjectOpResult& result) const override;
+ virtual bool enumerate(JSContext* cx, JS::Handle<JSObject*> wrapper,
+ JS::MutableHandleIdVector props) const override;
+ virtual bool getPrototype(JSContext* cx, JS::HandleObject wrapper,
+ JS::MutableHandleObject protop) const override;
+ virtual bool setPrototype(JSContext* cx, JS::HandleObject wrapper,
+ JS::HandleObject proto,
+ JS::ObjectOpResult& result) const override;
+ virtual bool getPrototypeIfOrdinary(
+ JSContext* cx, JS::HandleObject wrapper, bool* isOrdinary,
+ JS::MutableHandleObject protop) const override;
+ virtual bool setImmutablePrototype(JSContext* cx, JS::HandleObject wrapper,
+ bool* succeeded) const override;
+ virtual bool preventExtensions(JSContext* cx, JS::Handle<JSObject*> wrapper,
+ JS::ObjectOpResult& result) const override;
+ virtual bool isExtensible(JSContext* cx, JS::Handle<JSObject*> wrapper,
+ bool* extensible) const override;
+ virtual bool has(JSContext* cx, JS::Handle<JSObject*> wrapper,
+ JS::Handle<jsid> id, bool* bp) const override;
+ virtual bool get(JSContext* cx, JS::Handle<JSObject*> wrapper,
+ JS::HandleValue receiver, JS::Handle<jsid> id,
+ JS::MutableHandle<JS::Value> vp) const override;
+ virtual bool set(JSContext* cx, JS::Handle<JSObject*> wrapper,
+ JS::Handle<jsid> id, JS::Handle<JS::Value> v,
+ JS::Handle<JS::Value> receiver,
+ JS::ObjectOpResult& result) const override;
+ virtual bool call(JSContext* cx, JS::Handle<JSObject*> wrapper,
+ const JS::CallArgs& args) const override;
+ virtual bool construct(JSContext* cx, JS::Handle<JSObject*> wrapper,
+ const JS::CallArgs& args) const override;
+
+ /* SpiderMonkey extensions. */
+ virtual bool hasOwn(JSContext* cx, JS::Handle<JSObject*> wrapper,
+ JS::Handle<jsid> id, bool* bp) const override;
+ virtual bool getOwnEnumerablePropertyKeys(
+ JSContext* cx, JS::Handle<JSObject*> wrapper,
+ JS::MutableHandleIdVector props) const override;
+
+ virtual bool getBuiltinClass(JSContext* cx, JS::HandleObject wapper,
+ js::ESClass* cls) const override;
+ virtual const char* className(JSContext* cx,
+ JS::HandleObject proxy) const override;
+
+ static const XrayWrapper singleton;
+
+ protected:
+ bool getPropertyKeys(JSContext* cx, JS::Handle<JSObject*> wrapper,
+ unsigned flags, JS::MutableHandleIdVector props) const;
+};
+
+#define PermissiveXrayDOM \
+ xpc::XrayWrapper<js::CrossCompartmentWrapper, xpc::DOMXrayTraits>
+#define PermissiveXrayJS \
+ xpc::XrayWrapper<js::CrossCompartmentWrapper, xpc::JSXrayTraits>
+#define PermissiveXrayOpaque \
+ xpc::XrayWrapper<js::CrossCompartmentWrapper, xpc::OpaqueXrayTraits>
+
+extern template class PermissiveXrayDOM;
+extern template class PermissiveXrayJS;
+extern template class PermissiveXrayOpaque;
+
+/*
+ * Slots for Xray expando objects. See comments in XrayWrapper.cpp for details
+ * of how these get used; we mostly want the value of JSSLOT_EXPANDO_COUNT here.
+ */
+enum ExpandoSlots {
+ JSSLOT_EXPANDO_NEXT = 0,
+ JSSLOT_EXPANDO_ORIGIN,
+ JSSLOT_EXPANDO_EXCLUSIVE_WRAPPER_HOLDER,
+ JSSLOT_EXPANDO_PROTOTYPE,
+ JSSLOT_EXPANDO_COUNT
+};
+
+extern const JSClassOps XrayExpandoObjectClassOps;
+
+/*
+ * Clear the given slot on all Xray expandos for the given object.
+ *
+ * No-op when called on non-main threads (where Xrays don't exist).
+ */
+void ClearXrayExpandoSlots(JSObject* target, size_t slotIndex);
+
+/*
+ * Ensure the given wrapper has an expando object and return it. This can
+ * return null on failure. Will only be called when "wrapper" is an Xray for a
+ * DOM object.
+ */
+JSObject* EnsureXrayExpandoObject(JSContext* cx, JS::HandleObject wrapper);
+
+// Information about xrays for use by the JITs.
+extern JS::XrayJitInfo gXrayJitInfo;
+
+} // namespace xpc
+
+#endif