mirror of
https://gitea.wildfiregames.com/0ad/0ad.git
synced 2026-09-21 20:06:40 +00:00
GUIProxy: Support property introspection for debugging
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.
This commit is contained in:
@@ -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<std::string_view> 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<mozilla::Maybe<JS::PropertyDescriptor>>) const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
virtual bool getOwnPropertyDescriptor(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> 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::PropertyDescriptor>, 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
|
||||
|
||||
@@ -104,6 +104,17 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual std::vector<std::string_view> getPropsNames() const override
|
||||
{
|
||||
std::vector<std::string_view> result;
|
||||
result.reserve(m_Functions.size());
|
||||
|
||||
for (const auto& [key, value] : m_Functions)
|
||||
result.emplace_back(key);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::unordered_map<std::string, JS::PersistentRootedObject> m_Functions;
|
||||
};
|
||||
@@ -344,4 +355,83 @@ bool JSI_GUIProxy<T>::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<typename T>
|
||||
bool JSI_GUIProxy<T>::ownPropertyKeys(JSContext* cx, JS::HandleObject proxy, JS::MutableHandleIdVector props) const
|
||||
{
|
||||
ScriptRequest rq(cx);
|
||||
|
||||
T* e = IGUIProxyObject::FromPrivateSlot<T>(proxy.get());
|
||||
if (!e)
|
||||
return false;
|
||||
|
||||
// Add common properties.
|
||||
static constexpr std::array<const char*, 3> 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<const PropertyCache*>(static_cast<const GUIProxyProps*>(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<typename T>
|
||||
bool JSI_GUIProxy<T>::getOwnPropertyDescriptor(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> 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
|
||||
|
||||
Reference in New Issue
Block a user