From 0f156e35441209ad4c402a361102fe1ef7fd7d1c Mon Sep 17 00:00:00 2001 From: trompetin17 Date: Wed, 25 Jun 2025 08:05:32 -0500 Subject: [PATCH] GUIProxy: Support property introspection for debugging MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit enables proper property enumeration and inspection for GUI proxy objects in debugging sessions using the SpiderMonkey Debugger API. Interface (IGUIProxyObject): - Added a pure virtual method getPropsNames() to expose cached property names from the GUI object implementation. Proxy handler (JSI_GUIProxy): - Implemented ownPropertyKeys() to enumerate all visible properties of the proxy, including: -- Built-in GUI fields: "name", "parent", "children". -- Dynamic settings stored in m_Settings. -- Script event handlers prefixed with "on" from m_ScriptHandlers. -- Function properties returned by getPropsNames(). - Implemented getOwnPropertyDescriptor() to synthesize descriptors for debugger queries: -- Returns undefined if the property is not defined. -- Returns a read-only enumerable descriptor otherwise. - Both methods are marked final and override SpiderMonkey's BaseProxyHandler. Why: - SpiderMonkey’s Debugger API requires ownPropertyKeys and getOwnPropertyDescriptor for proxy objects to be introspectable in dev tools like VS Code. - Without these, properties of GUI objects are hidden during debugging. - This change improves the developer experience by making all meaningful GUI object fields visible and explorable at runtime. --- source/gui/Scripting/JSInterface_GUIProxy.h | 18 ++-- .../gui/Scripting/JSInterface_GUIProxy_impl.h | 90 +++++++++++++++++++ 2 files changed, 96 insertions(+), 12 deletions(-) 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