Index: source/gui/CGUI.h =================================================================== --- source/gui/CGUI.h +++ source/gui/CGUI.h @@ -35,6 +35,7 @@ #include #include +#include #include #include @@ -46,6 +47,8 @@ struct SGUIImageEffects; struct SGUIScrollBarStyle; +namespace js { class BaseProxyHandler; } + /** * The main object that represents a whole GUI page. */ @@ -237,6 +240,8 @@ */ const CGUIColor& GetPreDefinedColor(const CStr& name) const { return m_PreDefinedColors.at(name); } + const void* GetProxyData(const js::BaseProxyHandler* ptr) const { return m_ProxyData.at(ptr); } + shared_ptr GetScriptInterface() { return m_ScriptInterface; }; private: @@ -598,6 +603,17 @@ */ std::map m_ObjectTypes; + /** + * This is intended to store the JS Functions returned when accessing some object properties. + * It's not a great solution, but I can't find a better one at the moment. + * The problem is that these functions are per-scriptInterface, and proxy handlers aren't. + * So we know what we want to store, but we don't really have anywhere to store it. + * It would be simpler to recreate the functions on every JS call, but that is slower + * (this may or may not matter now and in the future). + * Another alternative would be to store them on each proxied object, but that wastes memory. + */ + std::unordered_map m_ProxyData; + /** * Map from hotkey names to objects that listen to the hotkey. * (This is an optimisation to avoid recursing over the whole GUI Index: source/gui/CGUI.cpp =================================================================== --- source/gui/CGUI.cpp +++ source/gui/CGUI.cpp @@ -19,8 +19,8 @@ #include "CGUI.h" -#include "gui/GUIObjectTypes.h" #include "gui/IGUIScrollBar.h" +#include "gui/ObjectTypes/CTooltip.h" #include "gui/Scripting/ScriptFunctions.h" #include "i18n/L10n.h" #include "lib/bits.h" Index: source/gui/GUIObjectTypes.cpp =================================================================== --- source/gui/GUIObjectTypes.cpp +++ source/gui/GUIObjectTypes.cpp @@ -14,8 +14,8 @@ * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ -#ifndef INCLUDED_GUIOBJECTTYPES -#define INCLUDED_GUIOBJECTTYPES + +#include "precompiled.h" #include "gui/ObjectTypes/CButton.h" #include "gui/ObjectTypes/CChart.h" @@ -32,9 +32,13 @@ #include "gui/ObjectTypes/CSlider.h" #include "gui/ObjectTypes/CText.h" #include "gui/ObjectTypes/CTooltip.h" +#include "gui/Scripting/JSInterface_GUIProxy.h" void CGUI::AddObjectTypes() { + m_ProxyData.insert(JSI_GUIProxy::CreateData(*m_ScriptInterface)); + m_ProxyData.insert(JSI_GUIProxy::CreateData(*m_ScriptInterface)); + AddObjectType("button", &CButton::ConstructObject); AddObjectType("chart", &CChart::ConstructObject); AddObjectType("checkbox", &CCheckBox::ConstructObject); @@ -52,5 +56,3 @@ AddObjectType("text", &CText::ConstructObject); AddObjectType("tooltip", &CTooltip::ConstructObject); } - -#endif // INCLUDED_GUIOBJECTTYPES Index: source/gui/ObjectBases/IGUIObject.h =================================================================== --- source/gui/ObjectBases/IGUIObject.h +++ source/gui/ObjectBases/IGUIObject.h @@ -25,7 +25,6 @@ #ifndef INCLUDED_IGUIOBJECT #define INCLUDED_IGUIOBJECT -#include "gui/Scripting/JSInterface_IGUIObject.h" #include "gui/SettingTypes/CGUISize.h" #include "gui/SGUIMessage.h" #include "lib/input.h" // just for IN_PASS @@ -39,6 +38,9 @@ class IGUIObject; class IGUISetting; +template +class JSI_GUIProxy; + using map_pObjects = std::map; #define GUI_OBJECT(obj) \ @@ -55,10 +57,8 @@ friend class CGUI; // Allow getProperty to access things like GetParent() - friend bool JSI_IGUIObject::getProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp); - friend bool JSI_IGUIObject::setProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp, JS::ObjectOpResult& result); - friend bool JSI_IGUIObject::deleteProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::ObjectOpResult& result); - friend bool JSI_IGUIObject::getComputedSize(JSContext* cx, uint argc, JS::Value* vp); + template + friend class JSI_GUIProxy; public: NONCOPYABLE(IGUIObject); @@ -230,16 +230,19 @@ */ void RegisterScriptHandler(const CStr& eventName, const CStr& Code, CGUI& pGUI); - /** - * Inheriting classes may append JS functions to the JS object representing this class. - */ - virtual void RegisterScriptFunctions() {} - /** * Retrieves the JSObject representing this GUI object. */ JSObject* GetJSObject(); + /** + * The following functions are called from JS. + */ + void toString(ScriptInterface& scriptInterface, JS::MutableHandleValue ret); + void focus(ScriptInterface& scriptInterface, JS::MutableHandleValue ret); + void blur(ScriptInterface& scriptInterface, JS::MutableHandleValue ret); + void getComputedSize(ScriptInterface& scriptInterface, JS::MutableHandleValue ret); + //@} protected: //-------------------------------------------------------- @@ -458,7 +461,7 @@ /** * Creates the JS object representing this page upon first use. */ - void CreateJSObject(); + virtual void CreateJSObject(); /** * Updates some internal data depending on the setting changed. Index: source/gui/ObjectBases/IGUIObject.cpp =================================================================== --- source/gui/ObjectBases/IGUIObject.cpp +++ source/gui/ObjectBases/IGUIObject.cpp @@ -21,6 +21,7 @@ #include "gui/CGUI.h" #include "gui/CGUISetting.h" +#include "gui/Scripting/JSInterface_GUIProxy.h" #include "js/Conversions.h" #include "ps/CLogger.h" #include "ps/GameSetup/Config.h" @@ -452,10 +453,12 @@ { ScriptRequest rq(m_pGUI.GetScriptInterface()); - m_JSObject.init(rq.cx, m_pGUI.GetScriptInterface()->CreateCustomObject("GUIObject")); + js::ProxyOptions options; + options.setClass(&JSI_GUIProxy::ClassDefinition()); + + JS::RootedValue cppObj(rq.cx); + m_JSObject.init(rq.cx, js::NewProxyObject(rq.cx, &JSI_GUIProxy::Singleton(), cppObj, nullptr, options)); JS_SetPrivate(m_JSObject.get(), this); - - RegisterScriptFunctions(); } JSObject* IGUIObject::GetJSObject() @@ -468,6 +471,31 @@ return m_JSObject.get(); } +void IGUIObject::toString(ScriptInterface& scriptInterface, JS::MutableHandleValue ret) +{ + ScriptRequest rq(scriptInterface); + ScriptInterface::ToJSVal(rq, ret, "[GUIObject: " + GetName() + "]"); +} + +void IGUIObject::focus(ScriptInterface& UNUSED(scriptInterface), JS::MutableHandleValue ret) +{ + GetGUI().SetFocusedObject(this); + ret.setUndefined(); +} + +void IGUIObject::blur(ScriptInterface& UNUSED(scriptInterface), JS::MutableHandleValue ret) +{ + GetGUI().SetFocusedObject(nullptr); + ret.setUndefined(); +} + +void IGUIObject::getComputedSize(ScriptInterface& scriptInterface, JS::MutableHandleValue ret) +{ + UpdateCachedSize(); + ScriptRequest rq(scriptInterface); + ScriptInterface::ToJSVal(rq, ret, m_CachedActualSize); +} + bool IGUIObject::IsEnabled() const { return m_Enabled; Index: source/gui/ObjectTypes/CText.h =================================================================== --- source/gui/ObjectTypes/CText.h +++ source/gui/ObjectTypes/CText.h @@ -30,6 +30,8 @@ { GUI_OBJECT(CText) + friend JSI_GUIProxy; + public: CText(CGUI& pGUI); virtual ~CText(); @@ -56,8 +58,6 @@ */ void SetupText(); - virtual void RegisterScriptFunctions(); - /** * @see IGUIObject#HandleMessage() */ @@ -68,12 +68,9 @@ */ virtual void Draw(); - /** - * Script accessors to this GUI object. - */ - static JSFunctionSpec JSI_methods[]; + virtual void CreateJSObject(); - static bool GetTextSize(JSContext* cx, uint argc, JS::Value* vp); + void getTextSize(ScriptInterface& scriptInterface, JS::MutableHandleValue ret); /** * Placement of text. Ignored when scrollbars are active. Index: source/gui/ObjectTypes/CText.cpp =================================================================== --- source/gui/ObjectTypes/CText.cpp +++ source/gui/ObjectTypes/CText.cpp @@ -22,6 +22,7 @@ #include "gui/CGUI.h" #include "gui/CGUIScrollBarVertical.h" #include "gui/CGUIText.h" +#include "gui/Scripting/JSInterface_GUIProxy.h" #include "scriptinterface/ScriptInterface.h" CText::CText(CGUI& pGUI) @@ -252,32 +253,22 @@ return false; } -void CText::RegisterScriptFunctions() + +void CText::CreateJSObject() { ScriptRequest rq(m_pGUI.GetScriptInterface()); - JS_DefineFunctions(rq.cx, m_JSObject, CText::JSI_methods); -} - -JSFunctionSpec CText::JSI_methods[] = -{ - JS_FN("getTextSize", CText::GetTextSize, 0, 0), - JS_FS_END -}; -bool CText::GetTextSize(JSContext* cx, uint argc, JS::Value* vp) -{ - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + js::ProxyOptions options; + options.setClass(&JSI_GUIProxy::ClassDefinition()); - ScriptRequest rq(*ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface); - CText* thisObj = ScriptInterface::GetPrivate(rq, args, &JSI_IGUIObject::JSI_class); - if (!thisObj) - { - ScriptException::Raise(rq, "This is not a CText object!"); - return false; - } - - thisObj->UpdateText(); + JS::RootedValue cppObj(rq.cx); + m_JSObject.init(rq.cx, js::NewProxyObject(rq.cx, &JSI_GUIProxy::Singleton(), cppObj, nullptr, options)); + JS_SetPrivate(m_JSObject.get(), this); +} - ScriptInterface::ToJSVal(rq, args.rval(), thisObj->m_GeneratedTexts[0].GetSize()); - return true; +void CText::getTextSize(ScriptInterface& scriptInterface, JS::MutableHandleValue ret) +{ + ScriptRequest rq(scriptInterface); + UpdateText(); + ScriptInterface::ToJSVal(rq, ret, m_GeneratedTexts[0].GetSize()); } Index: source/gui/Scripting/JSInterface_CText.cpp =================================================================== --- /dev/null +++ source/gui/Scripting/JSInterface_CText.cpp @@ -0,0 +1,80 @@ +/* Copyright (C) 2020 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "precompiled.h" + +#include "JSInterface_GUIProxy.h" + +#include "gui/CGUI.h" +#include "gui/CGUISetting.h" +#include "gui/ObjectBases/IGUIObject.h" +#include "gui/ObjectTypes/CText.h" +#include "ps/CLogger.h" +#include "scriptinterface/ScriptExtraHeaders.h" +#include "scriptinterface/ScriptInterface.h" + +#include + +// Include the definition of the generic templates. +#include "JSInterface_GUIProxy_impl.h" + +namespace { + struct SData + { + JS::PersistentRootedObject m_ToString; + JS::PersistentRootedObject m_Focus; + JS::PersistentRootedObject m_Blur; + JS::PersistentRootedObject m_GetComputedSize; + JS::PersistentRootedObject m_GetTextSize; + }; +} + +template <> +bool JSI_GUIProxy::funcGetter(CText* elem, const std::string& propName, JS::MutableHandleValue vp) const +{ + const SData& data = *static_cast(elem->GetGUI().GetProxyData(this)); + if (propName == "toString") + return vp.setObjectOrNull(data.m_ToString), true; + if (propName == "focus") + return vp.setObjectOrNull(data.m_Focus), true; + if (propName == "blur") + return vp.setObjectOrNull(data.m_Blur), true; + if (propName == "getComputedSize") + return vp.setObjectOrNull(data.m_GetComputedSize), true; + if (propName == "getTextSize") + return vp.setObjectOrNull(data.m_GetTextSize), true; + return false; +} + +template <> +std::pair JSI_GUIProxy::CreateData(ScriptInterface& scriptInterface) +{ + SData* data = new SData(); + ScriptRequest rq(scriptInterface); + +#define func(class, func) &apply_to + data->m_ToString.init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, func(IGUIObject, toString), 0, 0, "toString"))); + data->m_Focus.init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, func(IGUIObject, focus), 0, 0, "focus"))); + data->m_Blur.init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, func(IGUIObject, blur), 0, 0, "blur"))); + data->m_GetComputedSize.init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, func(IGUIObject, getComputedSize), 0, 0, "getComputedSize"))); + data->m_GetTextSize.init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, func(CText, getTextSize), 0, 0, "getTextSize"))); +#undef func + + return { &Singleton(), data }; +} + +template class JSI_GUIProxy; Index: source/gui/Scripting/JSInterface_GUIProxy.h =================================================================== --- /dev/null +++ source/gui/Scripting/JSInterface_GUIProxy.h @@ -0,0 +1,133 @@ +/* Copyright (C) 2019 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_JSI_GUIPROXY +#define INCLUDED_JSI_GUIPROXY + +#include "scriptinterface/ScriptExtraHeaders.h" + +#include + +class ScriptInterface; + +// See JSI_GuiProxy below +#if GCC_VERSION +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#elif MSC_VERSION +# pragma warning(push, 1) +# pragma warning(disable: 4265) +#endif + +/** + * Handles the js interface with C++ GUI objects. + * Proxy handlers must live for at least as long as the JS runtime + * where a proxy object with that handler was created. The reason is that + * proxy handlers are called during GC, such as on runtime destruction. + * In practical terms, this means "just keep them static and store no data". + * + * GUI Objects only exist in C++ and have no JS-only properties. + * As such, there is no "target" JS object that this proxy could point to, + * and thus we should inherit from BaseProxyHandler and not js::Wrapper. + */ +template +class JSI_GUIProxy : public js::BaseProxyHandler +{ +public: + // Access the js::Class of the Proxy. + static js::Class& ClassDefinition(); + + // For convenience, this is the single instantiated JSI_GUIProxy. + static JSI_GUIProxy& Singleton(); + + static std::pair CreateData(ScriptInterface& scriptInterface); +protected: + // @param family can't be nullptr because that's used for some DOM object and it crashes. + JSI_GUIProxy() : BaseProxyHandler(this, false, false) {}; + // Note: SM provides no virtual destructor for baseProxyHandler. + // This also enforces making proxy handlers dataless static variables. + ~JSI_GUIProxy() {}; + + // This handles returning function properties. + // Specialize this. + bool funcGetter(GUIObjectType* elem, const std::string& propName, JS::MutableHandleValue vp) const; +protected: + // BaseProxyHandler interface below + + // Handler for `object.x` + virtual bool get(JSContext* cx, JS::HandleObject proxy, JS::HandleValue receiver, JS::HandleId id, JS::MutableHandleValue vp) const override final; + // Handler for `object.x = y;` + virtual bool set(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::HandleValue vp, + JS::HandleValue receiver, JS::ObjectOpResult& result) const final; + // Handler for `delete object.x;` + virtual bool delete_(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::ObjectOpResult& result) const override final; + + // 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* UNUSED(cx), JS::HandleObject UNUSED(proxy), JS::HandleId UNUSED(id), + JS::MutableHandle UNUSED(desc)) const override + { + return true; + } + // Throw an exception is JS code attempts defining a property. + virtual bool defineProperty(JSContext* UNUSED(cx), JS::HandleObject UNUSED(proxy), JS::HandleId UNUSED(id), + JS::Handle UNUSED(desc), JS::ObjectOpResult& UNUSED(result)) const override + { + return false; + } + // Return nothing. + virtual bool ownPropertyKeys(JSContext* UNUSED(cx), JS::HandleObject UNUSED(proxy), JS::AutoIdVector& UNUSED(props)) const override + { + return true; + } + // Return nothing. + virtual bool enumerate(JSContext* UNUSED(cx), JS::HandleObject UNUSED(proxy), JS::MutableHandleObject UNUSED(objp)) const override + { + return true; + } + // Throw an exception is JS attempts to query the prototype. + virtual bool getPrototypeIfOrdinary(JSContext* UNUSED(cx), JS::HandleObject UNUSED(proxy), bool* UNUSED(isOrdinary), JS::MutableHandleObject UNUSED(protop)) const override + { + return false; + } + // Throw an exception - no prototype to set. + virtual bool setImmutablePrototype(JSContext* UNUSED(cx), JS::HandleObject UNUSED(proxy), bool* UNUSED(succeeded)) const override + { + return false; + } + // We are not extensible. + virtual bool preventExtensions(JSContext* UNUSED(cx), JS::HandleObject UNUSED(proxy), JS::ObjectOpResult& UNUSED(result)) const override + { + return true; + } + virtual bool isExtensible(JSContext* UNUSED(cx), JS::HandleObject UNUSED(proxy), bool* extensible) const override + { + *extensible = false; + return true; + } +}; + +#if GCC_VERSION +# pragma GCC diagnostic pop +#elif MSC_VERSION +# pragma warning(pop) +#endif + + +#endif // INCLUDED_JSI_GUIPROXY Index: source/gui/Scripting/JSInterface_GUIProxy_impl.h =================================================================== --- source/gui/Scripting/JSInterface_GUIProxy_impl.h +++ source/gui/Scripting/JSInterface_GUIProxy_impl.h @@ -15,50 +15,45 @@ * along with 0 A.D. If not, see . */ -#include "precompiled.h" +// This file is included directly into actual implementation files. -#include "JSInterface_IGUIObject.h" - -#include "gui/CGUI.h" -#include "gui/CGUISetting.h" -#include "gui/ObjectBases/IGUIObject.h" -#include "ps/CLogger.h" -#include "scriptinterface/ScriptExtraHeaders.h" -#include "scriptinterface/ScriptInterface.h" - -JSClass JSI_IGUIObject::JSI_class = { - "GUIObject", JSCLASS_HAS_PRIVATE, &JSI_IGUIObject::JSI_classops -}; - -JSClassOps JSI_IGUIObject::JSI_classops = { - nullptr, - JSI_IGUIObject::deleteProperty, - JSI_IGUIObject::getProperty, - JSI_IGUIObject::setProperty, - nullptr, nullptr, nullptr, nullptr, - nullptr, nullptr, nullptr, nullptr -}; +template +js::Class& JSI_GUIProxy::ClassDefinition() +{ + static js::Class c = PROXY_CLASS_DEF("GUIObjectProxy", JSCLASS_HAS_PRIVATE | JSCLASS_HAS_CACHED_PROTO(JSProto_Proxy)); + return c; +} -JSFunctionSpec JSI_IGUIObject::JSI_methods[] = +template +JSI_GUIProxy& JSI_GUIProxy::Singleton() { - JS_FN("toString", JSI_IGUIObject::toString, 0, 0), - JS_FN("focus", JSI_IGUIObject::focus, 0, 0), - JS_FN("blur", JSI_IGUIObject::blur, 0, 0), - JS_FN("getComputedSize", JSI_IGUIObject::getComputedSize, 0, 0), - JS_FS_END -}; + static JSI_GUIProxy s; + return s; +} -void JSI_IGUIObject::RegisterScriptClass(ScriptInterface& scriptInterface) +namespace +{ +template +inline bool apply_to(JSContext* cx, uint argc, JS::Value* vp) { - scriptInterface.DefineCustomObjectType(&JSI_class, nullptr, 0, nullptr, JSI_methods, nullptr, nullptr); + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + OG* e = static_cast(JS_GetPrivate(args.thisv().toObjectOrNull())); + if (!e) + return false; + + (static_cast(e)->*(funcptr))(*(ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface), args.rval()); + + return true; +} } -bool JSI_IGUIObject::getProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp) +template +bool JSI_GUIProxy::get(JSContext* cx, JS::HandleObject proxy, JS::HandleValue UNUSED(receiver), JS::HandleId id, JS::MutableHandleValue vp) const { ScriptInterface* pScriptInterface = ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface; ScriptRequest rq(*pScriptInterface); - IGUIObject* e = ScriptInterface::GetPrivate(rq, obj, &JSI_IGUIObject::JSI_class); + T* e = static_cast(JS_GetPrivate(proxy.get())); if (!e) return false; @@ -70,17 +65,8 @@ if (!ScriptInterface::FromJSVal(rq, idval, propName)) return false; - // Skip registered functions and inherited properties - // including JSInterfaces of derived classes - if (propName == "constructor" || - propName == "prototype" || - propName == "toString" || - propName == "toJSON" || - propName == "focus" || - propName == "blur" || - propName == "getTextSize" || - propName == "getComputedSize" - ) + // Return function properties. Specializable. + if (funcGetter(e, propName, vp)) return true; // Use onWhatever to access event handlers @@ -130,14 +116,17 @@ return false; } -bool JSI_IGUIObject::setProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp, JS::ObjectOpResult& result) + +template +bool JSI_GUIProxy::set(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::HandleValue vp, + JS::HandleValue UNUSED(receiver), JS::ObjectOpResult& result) const { - ScriptRequest rq(*ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface); - - IGUIObject* e = ScriptInterface::GetPrivate(rq, obj, &JSI_IGUIObject::JSI_class); + T* e = static_cast(JS_GetPrivate(proxy.get())); if (!e) return result.fail(JSMSG_NOT_NONNULL_OBJECT); + ScriptRequest rq(*ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface); + JS::RootedValue idval(rq.cx); if (!JS_IdToValue(rq.cx, id, &idval)) return result.fail(JSMSG_NOT_NONNULL_OBJECT); @@ -155,7 +144,7 @@ return result.succeed(); } - JS::RootedObject vpObj(rq.cx); + JS::RootedObject vpObj(cx); if (vp.isObject()) vpObj = &vp.toObject(); @@ -181,14 +170,15 @@ return result.fail(JSMSG_UNDEFINED_PROP); } -bool JSI_IGUIObject::deleteProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::ObjectOpResult& result) +template +bool JSI_GUIProxy::delete_(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::ObjectOpResult& result) const { - ScriptRequest rq(*ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface); - - IGUIObject* e = ScriptInterface::GetPrivate(rq, obj, &JSI_IGUIObject::JSI_class); + T* e = static_cast(JS_GetPrivate(proxy.get())); if (!e) return result.fail(JSMSG_NOT_NONNULL_OBJECT); + ScriptRequest rq(*ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface); + JS::RootedValue idval(rq.cx); if (!JS_IdToValue(rq.cx, id, &idval)) return result.fail(JSMSG_NOT_NONNULL_OBJECT); @@ -208,60 +198,3 @@ LOGERROR("Only event handlers can be deleted from GUI objects!"); return result.fail(JSMSG_UNDEFINED_PROP); } - -bool JSI_IGUIObject::toString(JSContext* cx, uint argc, JS::Value* vp) -{ - ScriptRequest rq(*ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface); - - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - IGUIObject* e = ScriptInterface::GetPrivate(rq, args, &JSI_IGUIObject::JSI_class); - if (!e) - return false; - - - ScriptInterface::ToJSVal(rq, args.rval(), "[GUIObject: " + e->GetName() + "]"); - return true; -} - -bool JSI_IGUIObject::focus(JSContext* cx, uint argc, JS::Value* vp) -{ - ScriptRequest rq(*ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface); - - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - IGUIObject* e = ScriptInterface::GetPrivate(rq, args, &JSI_IGUIObject::JSI_class); - if (!e) - return false; - - e->GetGUI().SetFocusedObject(e); - args.rval().setUndefined(); - return true; -} - -bool JSI_IGUIObject::blur(JSContext* cx, uint argc, JS::Value* vp) -{ - ScriptRequest rq(*ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface); - - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - IGUIObject* e = ScriptInterface::GetPrivate(rq, args, &JSI_IGUIObject::JSI_class); - if (!e) - return false; - - e->GetGUI().SetFocusedObject(nullptr); - args.rval().setUndefined(); - return true; -} - -bool JSI_IGUIObject::getComputedSize(JSContext* cx, uint argc, JS::Value* vp) -{ - ScriptRequest rq(*ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface); - - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - IGUIObject* e = ScriptInterface::GetPrivate(rq, args, &JSI_IGUIObject::JSI_class); - if (!e) - return false; - - e->UpdateCachedSize(); - ScriptInterface::ToJSVal(rq, args.rval(), e->m_CachedActualSize); - - return true; -} Index: source/gui/Scripting/JSInterface_IGUIObject.h =================================================================== --- source/gui/Scripting/JSInterface_IGUIObject.h +++ /dev/null @@ -1,41 +0,0 @@ -/* Copyright (C) 2020 Wildfire Games. - * This file is part of 0 A.D. - * - * 0 A.D. is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * 0 A.D. is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with 0 A.D. If not, see . - */ - -#ifndef INCLUDED_JSI_IGUIOBJECT -#define INCLUDED_JSI_IGUIOBJECT - -#include "scriptinterface/ScriptInterface.h" - -namespace JSI_IGUIObject -{ - extern JSClass JSI_class; - extern JSClassOps JSI_classops; - extern JSFunctionSpec JSI_methods[]; - - void RegisterScriptClass(ScriptInterface& scriptInterface); - - bool getProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp); - bool setProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp, JS::ObjectOpResult& result); - bool deleteProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::ObjectOpResult& result); - bool toString(JSContext* cx, uint argc, JS::Value* vp); - bool focus(JSContext* cx, uint argc, JS::Value* vp); - bool blur(JSContext* cx, uint argc, JS::Value* vp); - bool getComputedSize(JSContext* cx, uint argc, JS::Value* vp); - bool getTextSize(JSContext* cx, uint argc, JS::Value* vp); -} - -#endif // INCLUDED_JSI_IGUIOBJECT Index: source/gui/Scripting/JSInterface_IGUIObject.cpp =================================================================== --- source/gui/Scripting/JSInterface_IGUIObject.cpp +++ source/gui/Scripting/JSInterface_IGUIObject.cpp @@ -17,251 +17,58 @@ #include "precompiled.h" -#include "JSInterface_IGUIObject.h" +#include "JSInterface_GUIProxy.h" #include "gui/CGUI.h" #include "gui/CGUISetting.h" #include "gui/ObjectBases/IGUIObject.h" +#include "gui/ObjectTypes/CText.h" #include "ps/CLogger.h" #include "scriptinterface/ScriptExtraHeaders.h" #include "scriptinterface/ScriptInterface.h" -JSClass JSI_IGUIObject::JSI_class = { - "GUIObject", JSCLASS_HAS_PRIVATE, &JSI_IGUIObject::JSI_classops -}; +#include -JSClassOps JSI_IGUIObject::JSI_classops = { - nullptr, - JSI_IGUIObject::deleteProperty, - JSI_IGUIObject::getProperty, - JSI_IGUIObject::setProperty, - nullptr, nullptr, nullptr, nullptr, - nullptr, nullptr, nullptr, nullptr -}; +// Include the definition of the generic templates. +#include "JSInterface_GUIProxy_impl.h" -JSFunctionSpec JSI_IGUIObject::JSI_methods[] = -{ - JS_FN("toString", JSI_IGUIObject::toString, 0, 0), - JS_FN("focus", JSI_IGUIObject::focus, 0, 0), - JS_FN("blur", JSI_IGUIObject::blur, 0, 0), - JS_FN("getComputedSize", JSI_IGUIObject::getComputedSize, 0, 0), - JS_FS_END -}; - -void JSI_IGUIObject::RegisterScriptClass(ScriptInterface& scriptInterface) -{ - scriptInterface.DefineCustomObjectType(&JSI_class, nullptr, 0, nullptr, JSI_methods, nullptr, nullptr); +namespace { + struct SData + { + JS::PersistentRootedObject m_ToString; + JS::PersistentRootedObject m_Focus; + JS::PersistentRootedObject m_Blur; + JS::PersistentRootedObject m_GetComputedSize; + }; } -bool JSI_IGUIObject::getProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp) +template <> +bool JSI_GUIProxy::funcGetter(IGUIObject* elem, const std::string& propName, JS::MutableHandleValue vp) const { - ScriptInterface* pScriptInterface = ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface; - ScriptRequest rq(*pScriptInterface); - - IGUIObject* e = ScriptInterface::GetPrivate(rq, obj, &JSI_IGUIObject::JSI_class); - if (!e) - return false; - - JS::RootedValue idval(rq.cx); - if (!JS_IdToValue(rq.cx, id, &idval)) - return false; - - std::string propName; - if (!ScriptInterface::FromJSVal(rq, idval, propName)) - return false; - - // Skip registered functions and inherited properties - // including JSInterfaces of derived classes - if (propName == "constructor" || - propName == "prototype" || - propName == "toString" || - propName == "toJSON" || - propName == "focus" || - propName == "blur" || - propName == "getTextSize" || - propName == "getComputedSize" - ) - return true; - - // Use onWhatever to access event handlers - if (propName.substr(0, 2) == "on") - { - CStr eventName(propName.substr(2)); - std::map>::iterator it = e->m_ScriptHandlers.find(eventName); - if (it == e->m_ScriptHandlers.end()) - vp.setNull(); - else - vp.setObject(*it->second.get()); - return true; - } - - if (propName == "parent") - { - IGUIObject* parent = e->GetParent(); - - if (parent) - vp.set(JS::ObjectValue(*parent->GetJSObject())); - else - vp.set(JS::NullValue()); - - return true; - } - else if (propName == "children") - { - ScriptInterface::CreateArray(rq, vp); - - for (size_t i = 0; i < e->m_Children.size(); ++i) - pScriptInterface->SetPropertyInt(vp, i, e->m_Children[i]); - - return true; - } - else if (propName == "name") - { - ScriptInterface::ToJSVal(rq, vp, e->GetName()); - return true; - } - else if (e->SettingExists(propName)) - { - e->m_Settings[propName]->ToJSVal(rq, vp); - return true; - } - - LOGERROR("Property '%s' does not exist!", propName.c_str()); + const SData& data = *static_cast(elem->GetGUI().GetProxyData(this)); + if (propName == "toString") + return vp.setObjectOrNull(data.m_ToString), true; + if (propName == "focus") + return vp.setObjectOrNull(data.m_Focus), true; + if (propName == "blur") + return vp.setObjectOrNull(data.m_Blur), true; + if (propName == "getComputedSize") + return vp.setObjectOrNull(data.m_GetComputedSize), true; return false; } -bool JSI_IGUIObject::setProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp, JS::ObjectOpResult& result) +template <> +std::pair JSI_GUIProxy::CreateData(ScriptInterface& scriptInterface) { - ScriptRequest rq(*ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface); - - IGUIObject* e = ScriptInterface::GetPrivate(rq, obj, &JSI_IGUIObject::JSI_class); - if (!e) - return result.fail(JSMSG_NOT_NONNULL_OBJECT); - - JS::RootedValue idval(rq.cx); - if (!JS_IdToValue(rq.cx, id, &idval)) - return result.fail(JSMSG_NOT_NONNULL_OBJECT); - - std::string propName; - if (!ScriptInterface::FromJSVal(rq, idval, propName)) - return result.fail(JSMSG_UNDEFINED_PROP); - - if (propName == "name") - { - std::string value; - if (!ScriptInterface::FromJSVal(rq, vp, value)) - return result.fail(JSMSG_UNDEFINED_PROP); - e->SetName(value); - return result.succeed(); - } - - JS::RootedObject vpObj(rq.cx); - if (vp.isObject()) - vpObj = &vp.toObject(); - - // Use onWhatever to set event handlers - if (propName.substr(0, 2) == "on") - { - if (vp.isPrimitive() || vp.isNull() || !JS_ObjectIsFunction(rq.cx, &vp.toObject())) - { - LOGERROR("on- event-handlers must be functions"); - return result.fail(JSMSG_NOT_FUNCTION); - } - - CStr eventName(propName.substr(2)); - e->SetScriptHandler(eventName, vpObj); - - return result.succeed(); - } - - if (e->SettingExists(propName)) - return e->m_Settings[propName]->FromJSVal(rq, vp, true) ? result.succeed() : result.fail(JSMSG_USER_DEFINED_ERROR); - - LOGERROR("Property '%s' does not exist!", propName.c_str()); - return result.fail(JSMSG_UNDEFINED_PROP); + SData* data = new SData(); + ScriptRequest rq(scriptInterface); +#define func(class, func) &apply_to + data->m_ToString.init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, func(IGUIObject, toString), 0, 0, "toString"))); + data->m_Focus.init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, func(IGUIObject, focus), 0, 0, "focus"))); + data->m_Blur.init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, func(IGUIObject, blur), 0, 0, "blur"))); + data->m_GetComputedSize.init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, func(IGUIObject, getComputedSize), 0, 0, "getComputedSize"))); +#undef func + return { &Singleton(), data }; } -bool JSI_IGUIObject::deleteProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::ObjectOpResult& result) -{ - ScriptRequest rq(*ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface); - - IGUIObject* e = ScriptInterface::GetPrivate(rq, obj, &JSI_IGUIObject::JSI_class); - if (!e) - return result.fail(JSMSG_NOT_NONNULL_OBJECT); - - JS::RootedValue idval(rq.cx); - if (!JS_IdToValue(rq.cx, id, &idval)) - return result.fail(JSMSG_NOT_NONNULL_OBJECT); - - std::string propName; - if (!ScriptInterface::FromJSVal(rq, idval, propName)) - return result.fail(JSMSG_UNDEFINED_PROP); - - // event handlers - if (propName.substr(0, 2) == "on") - { - CStr eventName(propName.substr(2)); - e->UnsetScriptHandler(eventName); - return result.succeed(); - } - - LOGERROR("Only event handlers can be deleted from GUI objects!"); - return result.fail(JSMSG_UNDEFINED_PROP); -} - -bool JSI_IGUIObject::toString(JSContext* cx, uint argc, JS::Value* vp) -{ - ScriptRequest rq(*ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface); - - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - IGUIObject* e = ScriptInterface::GetPrivate(rq, args, &JSI_IGUIObject::JSI_class); - if (!e) - return false; - - - ScriptInterface::ToJSVal(rq, args.rval(), "[GUIObject: " + e->GetName() + "]"); - return true; -} - -bool JSI_IGUIObject::focus(JSContext* cx, uint argc, JS::Value* vp) -{ - ScriptRequest rq(*ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface); - - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - IGUIObject* e = ScriptInterface::GetPrivate(rq, args, &JSI_IGUIObject::JSI_class); - if (!e) - return false; - - e->GetGUI().SetFocusedObject(e); - args.rval().setUndefined(); - return true; -} - -bool JSI_IGUIObject::blur(JSContext* cx, uint argc, JS::Value* vp) -{ - ScriptRequest rq(*ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface); - - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - IGUIObject* e = ScriptInterface::GetPrivate(rq, args, &JSI_IGUIObject::JSI_class); - if (!e) - return false; - - e->GetGUI().SetFocusedObject(nullptr); - args.rval().setUndefined(); - return true; -} - -bool JSI_IGUIObject::getComputedSize(JSContext* cx, uint argc, JS::Value* vp) -{ - ScriptRequest rq(*ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface); - - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - IGUIObject* e = ScriptInterface::GetPrivate(rq, args, &JSI_IGUIObject::JSI_class); - if (!e) - return false; - - e->UpdateCachedSize(); - ScriptInterface::ToJSVal(rq, args.rval(), e->m_CachedActualSize); - - return true; -} +template class JSI_GUIProxy; Index: source/gui/Scripting/ScriptFunctions.cpp =================================================================== --- source/gui/Scripting/ScriptFunctions.cpp +++ source/gui/Scripting/ScriptFunctions.cpp @@ -22,7 +22,6 @@ #include "graphics/scripting/JSInterface_GameView.h" #include "gui/Scripting/JSInterface_GUIManager.h" #include "gui/Scripting/JSInterface_GUISize.h" -#include "gui/Scripting/JSInterface_IGUIObject.h" #include "i18n/scripting/JSInterface_L10n.h" #include "lobby/scripting/JSInterface_Lobby.h" #include "network/scripting/JSInterface_Network.h" @@ -52,8 +51,6 @@ void GuiScriptingInit(ScriptInterface& scriptInterface) { JSI_GUISize::RegisterScriptClass(scriptInterface); - JSI_IGUIObject::RegisterScriptClass(scriptInterface); - JSI_ConfigDB::RegisterScriptFunctions(scriptInterface); JSI_Console::RegisterScriptFunctions(scriptInterface); JSI_Debug::RegisterScriptFunctions(scriptInterface); Index: source/scriptinterface/ScriptExtraHeaders.h =================================================================== --- source/scriptinterface/ScriptExtraHeaders.h +++ source/scriptinterface/ScriptExtraHeaders.h @@ -49,6 +49,7 @@ #include "js/Conversions.h" #include "js/GCAPI.h" #include "js/StructuredClone.h" +#include "js/Proxy.h" #undef signbit