diff --git a/source/gui/Scripting/JSInterface_GUIProxy.h b/source/gui/Scripting/JSInterface_GUIProxy.h index 7dadd69fe3..1cd1ce3aef 100644 --- a/source/gui/Scripting/JSInterface_GUIProxy.h +++ b/source/gui/Scripting/JSInterface_GUIProxy.h @@ -101,6 +101,7 @@ public: // @return the JSFunction matching @param name. Must call has() first as it can assume existence. virtual JSObject* get(const std::string& name) const = 0; virtual bool setFunction(const ScriptRequest& rq, const std::string& name, JSFunction* function) = 0; + virtual std::vector getPropsNames() const = 0; }; /** @@ -177,24 +178,17 @@ protected: // The following methods are not provided by BaseProxyHandler. // We provide defaults that do nothing (some raise JS exceptions). - // The JS code will see undefined when querying a property descriptor. - virtual bool getOwnPropertyDescriptor(JSContext*, JS::HandleObject /*proxy*/, JS::HandleId, - JS::MutableHandle>) const override - { - return true; - } + virtual bool getOwnPropertyDescriptor(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::MutableHandle> desc) const override final; + // Throw an exception is JS code attempts defining a property. virtual bool defineProperty(JSContext*, JS::HandleObject /*proxy*/, JS::HandleId, JS::Handle, JS::ObjectOpResult& /*result*/) const override { return false; } - // No accessible properties. - virtual bool ownPropertyKeys(JSContext*, JS::HandleObject /*proxy*/, - JS::MutableHandleIdVector) const override - { - return true; - } + + virtual bool ownPropertyKeys(JSContext* cx, JS::HandleObject proxy, JS::MutableHandleIdVector props) const override final; + // Nothing to enumerate. virtual bool enumerate(JSContext*, JS::HandleObject /*proxy*/, JS::MutableHandleIdVector /*props*/) const override diff --git a/source/gui/Scripting/JSInterface_GUIProxy_impl.h b/source/gui/Scripting/JSInterface_GUIProxy_impl.h index ab4a341e32..1a0c9dd3ac 100644 --- a/source/gui/Scripting/JSInterface_GUIProxy_impl.h +++ b/source/gui/Scripting/JSInterface_GUIProxy_impl.h @@ -104,6 +104,17 @@ public: return true; } + virtual std::vector getPropsNames() const override + { + std::vector result; + result.reserve(m_Functions.size()); + + for (const auto& [key, value] : m_Functions) + result.emplace_back(key); + + return result; + } + protected: std::unordered_map m_Functions; }; @@ -344,4 +355,83 @@ bool JSI_GUIProxy::delete_(JSContext* cx, JS::HandleObject proxy, JS::HandleI LOGERROR("Only event handlers can be deleted from GUI objects!"); return result.fail(JSMSG_BAD_PROP_ID); } + +template +bool JSI_GUIProxy::ownPropertyKeys(JSContext* cx, JS::HandleObject proxy, JS::MutableHandleIdVector props) const +{ + ScriptRequest rq(cx); + + T* e = IGUIProxyObject::FromPrivateSlot(proxy.get()); + if (!e) + return false; + + // Add common properties. + static constexpr std::array keys = { + "name", + "parent", + "children" + }; + + for (const char* key : keys) + { + JS::RootedString str(cx, JS_NewStringCopyZ(cx, key)); + if (!str) + return false; + + JS::RootedId id(cx); + if (!JS_StringToId(cx, str, &id)) + return false; + + if (!props.append(id)) + return false; + } + + // Add settings. + for (const auto& [name, setting] : e->m_Settings) + { + JS::RootedString str(cx, JS_NewStringCopyZ(cx, name.c_str())); + JS::RootedId id(cx); + if (!str || !JS_StringToId(cx, str, &id) || !props.append(id)) + return false; + } + + // Add script handlers. + for (const auto& [name, scriptHandler] : e->m_ScriptHandlers) + { + JS::RootedString str(cx, JS_NewStringCopyZ(cx, fmt::format("on{}", name).c_str())); + JS::RootedId id(cx); + if (!str || !JS_StringToId(cx, str, &id) || !props.append(id)) + return false; + } + + // Add properties from the cache. + using PropertyCache = typename PropCache::type; + const PropertyCache* data = static_cast(static_cast(js::GetProxyReservedSlot(proxy, 0).toPrivate())); + for (const auto& key : data->getPropsNames()) { + JS::RootedString str(cx, JS_NewStringCopyZ(cx, key.data())); + JS::RootedId id(cx); + if (!str || !JS_StringToId(cx, str, &id) || !props.append(id)) + return false; + } + + return true; +} + +template +bool JSI_GUIProxy::getOwnPropertyDescriptor(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::MutableHandle> desc) const +{ + JS::RootedValue value(cx); + + if (!this->get(cx, proxy, JS::UndefinedHandleValue, id, &value)) + return false; + + if (value.isUndefined()) + { + desc.set(mozilla::Nothing()); + return true; + } + + desc.set(mozilla::Some(JS::PropertyDescriptor::Data(value, JSPROP_ENUMERATE | JSPROP_READONLY))); + return true; +} #endif // INCLUDED_JSI_GUIPROXY_IMP