Index: source/gui/GUIObjectTypes.h =================================================================== --- source/gui/GUIObjectTypes.h +++ source/gui/GUIObjectTypes.h @@ -34,6 +34,9 @@ void CGUI::AddObjectTypes() { + JSI_GUIProxy::GUIProxy::RegisterFunctions(*m_ScriptInterface.get()); + JSI_GUIProxy::GUIProxy::RegisterFunctions(*m_ScriptInterface.get()); + AddObjectType("button", &CButton::ConstructObject); AddObjectType("chart", &CChart::ConstructObject); AddObjectType("checkbox", &CCheckBox::ConstructObject); Index: source/gui/ObjectBases/IGUIObject.h =================================================================== --- source/gui/ObjectBases/IGUIObject.h +++ source/gui/ObjectBases/IGUIObject.h @@ -25,7 +25,7 @@ #ifndef INCLUDED_IGUIOBJECT #define INCLUDED_IGUIOBJECT -#include "gui/Scripting/JSInterface_IGUIObject.h" +#include "gui/Scripting/JSInterface_GUIProxy.h" #include "gui/SettingTypes/CGUISize.h" #include "gui/SGUIMessage.h" #include "lib/input.h" // just for IN_PASS @@ -55,10 +55,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::GUIProxy; public: NONCOPYABLE(IGUIObject); @@ -230,16 +228,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. + */ + static bool toString(JSContext* cx, uint argc, JS::Value* vp); + static bool focus(JSContext* cx, uint argc, JS::Value* vp); + static bool blur(JSContext* cx, uint argc, JS::Value* vp); + static bool getComputedSize(JSContext* cx, uint argc, JS::Value* vp); + //@} protected: //-------------------------------------------------------- @@ -439,6 +440,13 @@ //-------------------------------------------------------- //@{ + /** + * Assigns "this" to the private data of the proxy. + * This is virtual otherwise derived classes will write the IGUIObject* this, + * which may not be the same as the derived this. + */ + virtual void SetPrivateData(); + /** * Creates the JS object representing this page upon first use. */ Index: source/gui/ObjectBases/IGUIObject.cpp =================================================================== --- source/gui/ObjectBases/IGUIObject.cpp +++ source/gui/ObjectBases/IGUIObject.cpp @@ -437,15 +437,26 @@ return JS::ToBoolean(result); } -void IGUIObject::CreateJSObject() +void IGUIObject::SetPrivateData() { - JSContext* cx = m_pGUI.GetScriptInterface()->GetContext(); - JSAutoRequest rq(cx); - - m_JSObject.init(cx, m_pGUI.GetScriptInterface()->CreateCustomObject("GUIObject")); JS_SetPrivate(m_JSObject.get(), this); +} - RegisterScriptFunctions(); +void IGUIObject::CreateJSObject() +{ + JSContext* cx = m_pGUI.GetScriptInterface()->GetContext(); + JSAutoRequest rq(cx); + + // TODO: actually use this when we can use GetProxyPrivate + JS::RootedValue priv(cx); + //priv.get().setPrivate(this); -> need to use a virtual func. + + js::ProxyOptions options; + options.setClass(&JSI_GUIProxy::ClassDefinition); + m_JSObject.init(cx, js::NewProxyObject(cx, &JSI_GUIProxy::GUIProxy::singleton, priv, nullptr, options)); + + // TODO: remove this when we can use GetProxyPrivate + SetPrivateData(); } JSObject* IGUIObject::GetJSObject() @@ -458,6 +469,59 @@ return m_JSObject.get(); } +bool IGUIObject::toString(JSContext* cx, uint argc, JS::Value* vp) +{ + // No JSAutoRequest needed for these calls + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + IGUIObject* e = static_cast(JS_GetPrivate(args.thisv().toObjectOrNull())); + if (!e) + return false; + + ScriptInterface::ToJSVal(cx, args.rval(), "[GUIObject: " + e->GetName() + "]"); + return true; +} + +bool IGUIObject::focus(JSContext*, uint argc, JS::Value* vp) +{ + // No JSAutoRequest needed for these calls + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + IGUIObject* e = static_cast(JS_GetPrivate(args.thisv().toObjectOrNull())); + if (!e) + return false; + + e->GetGUI().SetFocusedObject(e); + args.rval().setUndefined(); + return true; +} + +bool IGUIObject::blur(JSContext*, uint argc, JS::Value* vp) +{ + // No JSAutoRequest needed for these calls + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + IGUIObject* e = static_cast(JS_GetPrivate(args.thisv().toObjectOrNull())); + if (!e) + return false; + + e->GetGUI().SetFocusedObject(nullptr); + args.rval().setUndefined(); + return true; +} + +bool IGUIObject::getComputedSize(JSContext* cx, uint argc, JS::Value* vp) +{ + JSAutoRequest rq(cx); + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + IGUIObject* e = static_cast(JS_GetPrivate(args.thisv().toObjectOrNull())); + if (!e) + return false; + + e->UpdateCachedSize(); + ScriptInterface::ToJSVal(cx, args.rval(), e->m_CachedActualSize); + + return true; +} + 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::GUIProxy; + public: CText(CGUI& pGUI); virtual ~CText(); @@ -56,8 +58,6 @@ */ void SetupText(); - virtual void RegisterScriptFunctions(); - /** * @see IGUIObject#HandleMessage() */ @@ -68,12 +68,14 @@ */ virtual void Draw(); - /** - * Script accessors to this GUI object. - */ - static JSFunctionSpec JSI_methods[]; + virtual void SetPrivateData(); - static bool GetTextSize(JSContext* cx, uint argc, JS::Value* vp); + + static bool toString(JSContext* cx, uint argc, JS::Value* vp); + static bool focus(JSContext* cx, uint argc, JS::Value* vp); + static bool blur(JSContext* cx, uint argc, JS::Value* vp); + static bool getComputedSize(JSContext* cx, uint argc, JS::Value* vp); + static bool getTextSize(JSContext* cx, uint argc, JS::Value* vp); /** * Placement of text. Ignored when scrollbars are active. Index: source/gui/ObjectTypes/CText.cpp =================================================================== --- source/gui/ObjectTypes/CText.cpp +++ source/gui/ObjectTypes/CText.cpp @@ -252,24 +252,69 @@ return false; } -void CText::RegisterScriptFunctions() +void CText::SetPrivateData() +{ + JS_SetPrivate(m_JSObject.get(), this); +} + +bool CText::toString(JSContext* cx, uint argc, JS::Value* vp) +{ + // No JSAutoRequest needed for these calls + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + IGUIObject* e = static_cast(JS_GetPrivate(args.thisv().toObjectOrNull())); + if (!e) + return false; + + ScriptInterface::ToJSVal(cx, args.rval(), "[GUIObject: " + e->GetName() + "]"); + return true; +} + +bool CText::focus(JSContext*, uint argc, JS::Value* vp) +{ + // No JSAutoRequest needed for these calls + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + IGUIObject* e = static_cast(JS_GetPrivate(args.thisv().toObjectOrNull())); + if (!e) + return false; + + e->GetGUI().SetFocusedObject(e); + args.rval().setUndefined(); + return true; +} + +bool CText::blur(JSContext*, uint argc, JS::Value* vp) +{ + // No JSAutoRequest needed for these calls + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + IGUIObject* e = static_cast(JS_GetPrivate(args.thisv().toObjectOrNull())); + if (!e) + return false; + + e->GetGUI().SetFocusedObject(nullptr); + args.rval().setUndefined(); + return true; +} + +bool CText::getComputedSize(JSContext* cx, uint argc, JS::Value* vp) { - JSContext* cx = m_pGUI.GetScriptInterface()->GetContext(); JSAutoRequest rq(cx); - JS_DefineFunctions(cx, m_JSObject, CText::JSI_methods); + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + CText* e = static_cast(JS_GetPrivate(args.thisv().toObjectOrNull())); + if (!e) + return false; + + e->UpdateCachedSize(); + ScriptInterface::ToJSVal(cx, args.rval(), e->m_CachedActualSize); + + return true; } -JSFunctionSpec CText::JSI_methods[] = -{ - JS_FN("getTextSize", CText::GetTextSize, 0, 0), - JS_FS_END -}; - -bool CText::GetTextSize(JSContext* cx, uint argc, JS::Value* vp) +bool CText::getTextSize(JSContext* cx, uint argc, JS::Value* vp) { // No JSAutoRequest needed for these calls JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - CText* thisObj = ScriptInterface::GetPrivate(cx, args, &JSI_IGUIObject::JSI_class); + CText* thisObj = static_cast(JS_GetPrivate(args.thisv().toObjectOrNull())); if (!thisObj) { JSAutoRequest rq(cx); Index: source/gui/Scripting/JSInterface_CText.cpp =================================================================== --- /dev/null +++ source/gui/Scripting/JSInterface_CText.cpp @@ -0,0 +1,69 @@ +/* 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 the definition of the generic templates. +#include "JSInterface_GUIProxy_impl.h" + +template <> +JSI_GUIProxy::GUIProxy JSI_GUIProxy::GUIProxy::singleton = JSI_GUIProxy::GUIProxy(); + +template <> +bool JSI_GUIProxy::GUIProxy::funcGetter(ScriptInterface& scriptInterface, const std::string& propName, JS::MutableHandleValue vp) const +{ + if (propName == "toString" || + propName == "focus" || + propName == "blur" || + propName == "getComputedSize" || + propName == "getTextSize") + { + JS::RootedFunction func(scriptInterface.GetContext()); + std::string temp = "ctext_" + propName; + scriptInterface.GetFunctionFromStorage(temp, &func); + vp.setObjectOrNull(JS_GetFunctionObject(func)); + return true; + } + return false; +} + +template <> +void JSI_GUIProxy::GUIProxy::RegisterFunctions(ScriptInterface& scriptInterface) +{ + JSAutoRequest rq(scriptInterface.GetContext()); +#define REGISTERFUNC(func) \ + {\ + JS::RootedFunction obj(scriptInterface.GetContext(), JS_NewFunction(scriptInterface.GetContext(), &CText::func, 0, 0, #func));\ + scriptInterface.AddFunctionToStorage("ctext_"#func, obj);\ + } + REGISTERFUNC(toString); + REGISTERFUNC(focus); + REGISTERFUNC(blur); + REGISTERFUNC(getComputedSize); + REGISTERFUNC(getTextSize); +#undef REGISTERFUNC +} Index: source/gui/Scripting/JSInterface_GUIProxy.h =================================================================== --- /dev/null +++ source/gui/Scripting/JSInterface_GUIProxy.h @@ -0,0 +1,104 @@ +/* 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_IGUIOBJECT +#define INCLUDED_JSI_IGUIOBJECT + +#include "js/Proxy.h" + +#include "scriptinterface/ScriptInterface.h" + +namespace JSI_GUIProxy +{ + static js::Class ClassDefinition = \ + PROXY_CLASS_DEF("GUIObjectProxy", JSCLASS_HAS_PRIVATE | JSCLASS_HAS_CACHED_PROTO(JSProto_Proxy)); + + /** + * 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 GUIProxy : public js::BaseProxyHandler + { + public: + static void RegisterFunctions(ScriptInterface& scriptInterface); + + // For convenience, this is the single instantiated GUIProxy. + static GUIProxy singleton; + protected: + // @param family can't be nullptr because that's used for some DOM object and it crashes. + GUIProxy() : BaseProxyHandler(this, false, false) {}; + virtual ~GUIProxy() {}; + + // This handles returning function properties. + // Overload this in your subclass. + virtual bool funcGetter(ScriptInterface& scriptInterface, const std::string& propName, JS::MutableHandleValue vp) const; + + // Handler for `object.x` + virtual bool get(JSContext* cx, JS::HandleObject proxy, JS::HandleValue receiver, JS::HandleId id, JS::MutableHandleValue vp) const 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 final; + + // The following methods are not provided by BaseProxyHandler. + // We provide a default that does nothing. + + // 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 + { + 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 + { + return false; + } + // Return nothing. + virtual bool ownPropertyKeys(JSContext* UNUSED(cx), JS::HandleObject UNUSED(proxy), JS::AutoIdVector& UNUSED(props)) const + { + return true; + } + // Return nothing. + virtual bool enumerate(JSContext* UNUSED(cx), JS::HandleObject UNUSED(proxy), JS::MutableHandleObject UNUSED(objp)) const + { + return true; + } + // We are not extensible. + virtual bool preventExtensions(JSContext* UNUSED(cx), JS::HandleObject UNUSED(proxy), JS::ObjectOpResult& UNUSED(result)) const + { + return true; + } + virtual bool isExtensible(JSContext* UNUSED(cx), JS::HandleObject UNUSED(proxy), bool* extensible) const + { + *extensible = false; + return true; + } + }; +} + +#endif // INCLUDED_JSI_IGUIOBJECT Index: source/gui/Scripting/JSInterface_GUIProxy_impl.h =================================================================== --- source/gui/Scripting/JSInterface_GUIProxy_impl.h +++ source/gui/Scripting/JSInterface_GUIProxy_impl.h @@ -15,47 +15,15 @@ * 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, - nullptr, - JSI_IGUIObject::deleteProperty, - JSI_IGUIObject::getProperty, - JSI_IGUIObject::setProperty, - nullptr, nullptr, nullptr, nullptr, - nullptr, nullptr, nullptr, nullptr -}; - -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); -} - -bool JSI_IGUIObject::getProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp) +template +bool JSI_GUIProxy::GUIProxy::get(JSContext* cx, JS::HandleObject proxy, JS::HandleValue UNUSED(receiver), JS::HandleId id, JS::MutableHandleValue vp) const { JSAutoRequest rq(cx); ScriptInterface* pScriptInterface = ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface; - IGUIObject* e = ScriptInterface::GetPrivate(cx, obj, &JSI_IGUIObject::JSI_class); + T* e = static_cast(JS_GetPrivate(proxy.get())); if (!e) return false; @@ -67,17 +35,8 @@ if (!ScriptInterface::FromJSVal(cx, 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. Overloadable. + if (funcGetter(*pScriptInterface, propName, vp)) return true; // Use onWhatever to access event handlers @@ -127,13 +86,16 @@ return false; } -bool JSI_IGUIObject::setProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp, JS::ObjectOpResult& result) + +template +bool JSI_GUIProxy::GUIProxy::set(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::HandleValue vp, + JS::HandleValue UNUSED(receiver), JS::ObjectOpResult& result) const { - IGUIObject* e = ScriptInterface::GetPrivate(cx, obj, &JSI_IGUIObject::JSI_class); + JSAutoRequest rq(cx); + T* e = static_cast(JS_GetPrivate(proxy.get())); if (!e) return result.fail(JSMSG_NOT_NONNULL_OBJECT); - JSAutoRequest rq(cx); JS::RootedValue idval(cx); if (!JS_IdToValue(cx, id, &idval)) return result.fail(JSMSG_NOT_NONNULL_OBJECT); @@ -177,9 +139,10 @@ 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::GUIProxy::delete_(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::ObjectOpResult& result) const { - IGUIObject* e = ScriptInterface::GetPrivate(cx, obj, &JSI_IGUIObject::JSI_class); + T* e = static_cast(JS_GetPrivate(proxy.get())); if (!e) return result.fail(JSMSG_NOT_NONNULL_OBJECT); @@ -203,56 +166,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) -{ - // No JSAutoRequest needed for these calls - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - IGUIObject* e = ScriptInterface::GetPrivate(cx, args, &JSI_IGUIObject::JSI_class); - if (!e) - return false; - - ScriptInterface::ToJSVal(cx, args.rval(), "[GUIObject: " + e->GetName() + "]"); - return true; -} - -bool JSI_IGUIObject::focus(JSContext* cx, uint argc, JS::Value* vp) -{ - // No JSAutoRequest needed for these calls - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - IGUIObject* e = ScriptInterface::GetPrivate(cx, 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) -{ - // No JSAutoRequest needed for these calls - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - IGUIObject* e = ScriptInterface::GetPrivate(cx, 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) -{ - JSAutoRequest rq(cx); - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - - IGUIObject* e = ScriptInterface::GetPrivate(cx, args, &JSI_IGUIObject::JSI_class); - if (!e) - return false; - - e->UpdateCachedSize(); - ScriptInterface::ToJSVal(cx, args.rval(), e->m_CachedActualSize); - - return true; -} Index: source/gui/Scripting/JSInterface_IGUIObject.h =================================================================== --- source/gui/Scripting/JSInterface_IGUIObject.h +++ /dev/null @@ -1,40 +0,0 @@ -/* 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_IGUIOBJECT -#define INCLUDED_JSI_IGUIOBJECT - -#include "scriptinterface/ScriptInterface.h" - -namespace JSI_IGUIObject -{ - extern JSClass JSI_class; - 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,242 +17,50 @@ #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, - 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); -} +template <> +JSI_GUIProxy::GUIProxy JSI_GUIProxy::GUIProxy::singleton = JSI_GUIProxy::GUIProxy(); -bool JSI_IGUIObject::getProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp) +template <> +bool JSI_GUIProxy::GUIProxy::funcGetter(ScriptInterface& scriptInterface, const std::string& propName, JS::MutableHandleValue vp) const { - JSAutoRequest rq(cx); - ScriptInterface* pScriptInterface = ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface; - - IGUIObject* e = ScriptInterface::GetPrivate(cx, obj, &JSI_IGUIObject::JSI_class); - if (!e) - return false; - - JS::RootedValue idval(cx); - if (!JS_IdToValue(cx, id, &idval)) - return false; - - std::string propName; - if (!ScriptInterface::FromJSVal(cx, 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") + if (propName == "toString" || + propName == "focus" || + propName == "blur" || + propName == "getComputedSize") { - 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()); + JS::RootedFunction func(scriptInterface.GetContext()); + scriptInterface.GetFunctionFromStorage(propName, &func); + vp.setObjectOrNull(JS_GetFunctionObject(func)); 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(cx, 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(cx, vp, e->GetName()); - return true; - } - else if (e->SettingExists(propName)) - { - e->m_Settings[propName]->ToJSVal(cx, vp); - return true; - } - - LOGERROR("Property '%s' does not exist!", propName.c_str()); return false; } -bool JSI_IGUIObject::setProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp, JS::ObjectOpResult& result) -{ - IGUIObject* e = ScriptInterface::GetPrivate(cx, obj, &JSI_IGUIObject::JSI_class); - if (!e) - return result.fail(JSMSG_NOT_NONNULL_OBJECT); - - JSAutoRequest rq(cx); - JS::RootedValue idval(cx); - if (!JS_IdToValue(cx, id, &idval)) - return result.fail(JSMSG_NOT_NONNULL_OBJECT); - - std::string propName; - if (!ScriptInterface::FromJSVal(cx, idval, propName)) - return result.fail(JSMSG_UNDEFINED_PROP); - - if (propName == "name") - { - std::string value; - if (!ScriptInterface::FromJSVal(cx, vp, value)) - return result.fail(JSMSG_UNDEFINED_PROP); - e->SetName(value); - return result.succeed(); - } - - JS::RootedObject vpObj(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(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(); +template <> +void JSI_GUIProxy::GUIProxy::RegisterFunctions(ScriptInterface& scriptInterface) +{ + JSAutoRequest rq(scriptInterface.GetContext()); +#define REGISTERFUNC(func) \ + {\ + JS::RootedFunction obj(scriptInterface.GetContext(), JS_NewFunction(scriptInterface.GetContext(), &IGUIObject::func, 0, 0, #func));\ + scriptInterface.AddFunctionToStorage(#func, obj);\ } - - if (e->SettingExists(propName)) - return e->m_Settings[propName]->FromJSVal(cx, vp, true) ? result.succeed() : result.fail(JSMSG_TYPE_ERR_BAD_ARGS); - - LOGERROR("Property '%s' does not exist!", propName.c_str()); - return result.fail(JSMSG_UNDEFINED_PROP); -} - -bool JSI_IGUIObject::deleteProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::ObjectOpResult& result) -{ - IGUIObject* e = ScriptInterface::GetPrivate(cx, obj, &JSI_IGUIObject::JSI_class); - if (!e) - return result.fail(JSMSG_NOT_NONNULL_OBJECT); - - JSAutoRequest rq(cx); - JS::RootedValue idval(cx); - if (!JS_IdToValue(cx, id, &idval)) - return result.fail(JSMSG_NOT_NONNULL_OBJECT); - - std::string propName; - if (!ScriptInterface::FromJSVal(cx, 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) -{ - // No JSAutoRequest needed for these calls - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - IGUIObject* e = ScriptInterface::GetPrivate(cx, args, &JSI_IGUIObject::JSI_class); - if (!e) - return false; - - ScriptInterface::ToJSVal(cx, args.rval(), "[GUIObject: " + e->GetName() + "]"); - return true; -} - -bool JSI_IGUIObject::focus(JSContext* cx, uint argc, JS::Value* vp) -{ - // No JSAutoRequest needed for these calls - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - IGUIObject* e = ScriptInterface::GetPrivate(cx, 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) -{ - // No JSAutoRequest needed for these calls - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - IGUIObject* e = ScriptInterface::GetPrivate(cx, 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) -{ - JSAutoRequest rq(cx); - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - - IGUIObject* e = ScriptInterface::GetPrivate(cx, args, &JSI_IGUIObject::JSI_class); - if (!e) - return false; - - e->UpdateCachedSize(); - ScriptInterface::ToJSVal(cx, args.rval(), e->m_CachedActualSize); - - return true; + REGISTERFUNC(toString); + REGISTERFUNC(focus); + REGISTERFUNC(blur); + REGISTERFUNC(getComputedSize); +#undef REGISTERFUNC } 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" @@ -51,8 +50,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/ScriptInterface.h =================================================================== --- source/scriptinterface/ScriptInterface.h +++ source/scriptinterface/ScriptInterface.h @@ -25,6 +25,7 @@ #include #include +#include ERROR_GROUP(Scripting); ERROR_TYPE(Scripting, SetupFailed); @@ -127,6 +128,13 @@ */ void CallConstructor(JS::HandleValue ctor, JS::HandleValueArray argv, JS::MutableHandleValue out) const; + /** + * API wrapper around m_StoredFunctions; + */ + bool AddFunctionToStorage(const std::string& key, JS::HandleFunction obj); + bool GetFunctionFromStorage(const std::string& key, JS::MutableHandleFunction obj) const; + bool RemoveFunctionFromStorage(const std::string& key); + JSObject* CreateCustomObject(const std::string & typeName) const; void DefineCustomObjectType(JSClass *clasp, JSNative constructor, uint minArgs, JSPropertySpec *ps, JSFunctionSpec *fs, JSPropertySpec *static_ps, JSFunctionSpec *static_fs); @@ -441,6 +449,14 @@ }; void Register(const char* name, JSNative fptr, size_t nargs) const; + + static void Trace(JSTracer* trc, void* data) + { + reinterpret_cast(data)->TraceMember(trc); + } + + void TraceMember(JSTracer* trc); + // Take care to keep this declaration before heap rooted members. Destructors of heap rooted // members have to be called before the runtime destructor. std::unique_ptr m; @@ -448,6 +464,9 @@ boost::rand48* m_rng; std::map m_CustomObjectTypes; + // Store objects and keep them protected from the GC. + std::unordered_map> m_StoredFunctions; + // The nasty macro/template bits are split into a separate file so you don't have to look at them public: #include "NativeWrapperDecls.h" Index: source/scriptinterface/ScriptInterface.cpp =================================================================== --- source/scriptinterface/ScriptInterface.cpp +++ source/scriptinterface/ScriptInterface.cpp @@ -414,6 +414,8 @@ m_CxPrivate.pScriptInterface = this; JS_SetContextPrivate(m->m_cx, (void*)&m_CxPrivate); + + JS_AddExtraGCRootsTracer(m->m_runtime->m_rt, Trace, this); } ScriptInterface::~ScriptInterface() @@ -423,6 +425,8 @@ if (g_ScriptStatsTable) g_ScriptStatsTable->Remove(this); } + + JS_RemoveExtraGCRootsTracer(m->m_runtime->m_rt, Trace, this); } void ScriptInterface::SetCallbackData(void* pCBData) @@ -482,6 +486,12 @@ m->Register(name, fptr, (uint)nargs); } +void ScriptInterface::TraceMember(JSTracer* trc) +{ + for (std::pair>& handler : m_StoredFunctions) + JS_CallFunctionTracer(trc, &handler.second, "ScriptInterface::m_StoredFunctions"); +} + JSContext* ScriptInterface::GetContext() const { return m->m_cx; @@ -511,6 +521,27 @@ out.setObjectOrNull(JS_New(m->m_cx, ctorObj, argv)); } +bool ScriptInterface::AddFunctionToStorage(const std::string& key, JS::HandleFunction obj) +{ + JSAutoRequest rq(m->m_cx); + return m_StoredFunctions.emplace(key, obj).second; +} + +bool ScriptInterface::GetFunctionFromStorage(const std::string& key, JS::MutableHandleFunction obj) const +{ + decltype(m_StoredFunctions)::const_iterator it = m_StoredFunctions.find(key); + if (it == m_StoredFunctions.end()) + return false; + obj.set(it->second.get()); + return true; +} + +bool ScriptInterface::RemoveFunctionFromStorage(const std::string& key) +{ + return m_StoredFunctions.erase(key) > 0; +} + + void ScriptInterface::DefineCustomObjectType(JSClass *clasp, JSNative constructor, uint minArgs, JSPropertySpec *ps, JSFunctionSpec *fs, JSPropertySpec *static_ps, JSFunctionSpec *static_fs) { JSAutoRequest rq(m->m_cx);