Index: ps/trunk/source/gui/CGUI.h =================================================================== --- ps/trunk/source/gui/CGUI.h +++ ps/trunk/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: @@ -599,6 +604,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 * tree every time a hotkey is pressed). Index: ps/trunk/source/gui/CGUI.cpp =================================================================== --- ps/trunk/source/gui/CGUI.cpp +++ ps/trunk/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: ps/trunk/source/gui/GUIObjectTypes.h =================================================================== --- ps/trunk/source/gui/GUIObjectTypes.h +++ ps/trunk/source/gui/GUIObjectTypes.h @@ -1,56 +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_GUIOBJECTTYPES -#define INCLUDED_GUIOBJECTTYPES - -#include "gui/ObjectTypes/CButton.h" -#include "gui/ObjectTypes/CChart.h" -#include "gui/ObjectTypes/CCheckBox.h" -#include "gui/ObjectTypes/CDropDown.h" -#include "gui/ObjectTypes/CHotkeyPicker.h" -#include "gui/ObjectTypes/CImage.h" -#include "gui/ObjectTypes/CInput.h" -#include "gui/ObjectTypes/CList.h" -#include "gui/ObjectTypes/CMiniMap.h" -#include "gui/ObjectTypes/COList.h" -#include "gui/ObjectTypes/CProgressBar.h" -#include "gui/ObjectTypes/CRadioButton.h" -#include "gui/ObjectTypes/CSlider.h" -#include "gui/ObjectTypes/CText.h" -#include "gui/ObjectTypes/CTooltip.h" - -void CGUI::AddObjectTypes() -{ - AddObjectType("button", &CButton::ConstructObject); - AddObjectType("chart", &CChart::ConstructObject); - AddObjectType("checkbox", &CCheckBox::ConstructObject); - AddObjectType("dropdown", &CDropDown::ConstructObject); - AddObjectType("empty", &CGUIDummyObject::ConstructObject); - AddObjectType("hotkeypicker", &CHotkeyPicker::ConstructObject); - AddObjectType("image", &CImage::ConstructObject); - AddObjectType("input", &CInput::ConstructObject); - AddObjectType("list", &CList::ConstructObject); - AddObjectType("minimap", &CMiniMap::ConstructObject); - AddObjectType("olist", &COList::ConstructObject); - AddObjectType("progressbar", &CProgressBar::ConstructObject); - AddObjectType("radiobutton", &CRadioButton::ConstructObject); - AddObjectType("slider", &CSlider::ConstructObject); - AddObjectType("text", &CText::ConstructObject); - AddObjectType("tooltip", &CTooltip::ConstructObject); -} - -#endif // INCLUDED_GUIOBJECTTYPES Index: ps/trunk/source/gui/GUIObjectTypes.cpp =================================================================== --- ps/trunk/source/gui/GUIObjectTypes.cpp +++ ps/trunk/source/gui/GUIObjectTypes.cpp @@ -0,0 +1,58 @@ +/* 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 . + */ + +#include "precompiled.h" + +#include "gui/ObjectTypes/CButton.h" +#include "gui/ObjectTypes/CChart.h" +#include "gui/ObjectTypes/CCheckBox.h" +#include "gui/ObjectTypes/CDropDown.h" +#include "gui/ObjectTypes/CHotkeyPicker.h" +#include "gui/ObjectTypes/CImage.h" +#include "gui/ObjectTypes/CInput.h" +#include "gui/ObjectTypes/CList.h" +#include "gui/ObjectTypes/CMiniMap.h" +#include "gui/ObjectTypes/COList.h" +#include "gui/ObjectTypes/CProgressBar.h" +#include "gui/ObjectTypes/CRadioButton.h" +#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); + AddObjectType("dropdown", &CDropDown::ConstructObject); + AddObjectType("empty", &CGUIDummyObject::ConstructObject); + AddObjectType("hotkeypicker", &CHotkeyPicker::ConstructObject); + AddObjectType("image", &CImage::ConstructObject); + AddObjectType("input", &CInput::ConstructObject); + AddObjectType("list", &CList::ConstructObject); + AddObjectType("minimap", &CMiniMap::ConstructObject); + AddObjectType("olist", &COList::ConstructObject); + AddObjectType("progressbar", &CProgressBar::ConstructObject); + AddObjectType("radiobutton", &CRadioButton::ConstructObject); + AddObjectType("slider", &CSlider::ConstructObject); + AddObjectType("text", &CText::ConstructObject); + AddObjectType("tooltip", &CTooltip::ConstructObject); +} Index: ps/trunk/source/gui/ObjectBases/IGUIObject.h =================================================================== --- ps/trunk/source/gui/ObjectBases/IGUIObject.h +++ ps/trunk/source/gui/ObjectBases/IGUIObject.h @@ -25,11 +25,11 @@ #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 #include "ps/XML/Xeromyces.h" +#include "scriptinterface/ScriptTypes.h" #include #include @@ -39,6 +39,9 @@ class IGUIObject; class IGUISetting; +template +class JSI_GUIProxy; + using map_pObjects = std::map; #define GUI_OBJECT(obj) \ @@ -55,10 +58,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); @@ -231,14 +232,17 @@ void RegisterScriptHandler(const CStr& eventName, const CStr& Code, CGUI& pGUI); /** - * Inheriting classes may append JS functions to the JS object representing this class. + * Retrieves the JSObject representing this GUI object. */ - virtual void RegisterScriptFunctions() {} + JSObject* GetJSObject(); /** - * Retrieves the JSObject representing this GUI object. + * The following functions are called from JS. */ - JSObject* GetJSObject(); + 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 +462,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: ps/trunk/source/gui/ObjectBases/IGUIObject.cpp =================================================================== --- ps/trunk/source/gui/ObjectBases/IGUIObject.cpp +++ ps/trunk/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_SetPrivate(m_JSObject.get(), this); + js::ProxyOptions options; + options.setClass(&JSI_GUIProxy::ClassDefinition()); - RegisterScriptFunctions(); + 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); } 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: ps/trunk/source/gui/ObjectTypes/CText.h =================================================================== --- ps/trunk/source/gui/ObjectTypes/CText.h +++ ps/trunk/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: ps/trunk/source/gui/ObjectTypes/CText.cpp =================================================================== --- ps/trunk/source/gui/ObjectTypes/CText.cpp +++ ps/trunk/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() -{ - 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) +void CText::CreateJSObject() { - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + ScriptRequest rq(m_pGUI.GetScriptInterface()); - 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; - } + js::ProxyOptions options; + options.setClass(&JSI_GUIProxy::ClassDefinition()); - 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: ps/trunk/source/gui/Scripting/JSInterface_CText.cpp =================================================================== --- ps/trunk/source/gui/Scripting/JSInterface_CText.cpp +++ ps/trunk/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: ps/trunk/source/gui/Scripting/JSInterface_GUIProxy.h =================================================================== --- ps/trunk/source/gui/Scripting/JSInterface_GUIProxy.h +++ ps/trunk/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: ps/trunk/source/gui/Scripting/JSInterface_GUIProxy_impl.h =================================================================== --- ps/trunk/source/gui/Scripting/JSInterface_GUIProxy_impl.h +++ ps/trunk/source/gui/Scripting/JSInterface_GUIProxy_impl.h @@ -0,0 +1,200 @@ +/* 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 . + */ + +// This file is included directly into actual implementation files. + +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; +} + +template +JSI_GUIProxy& JSI_GUIProxy::Singleton() +{ + static JSI_GUIProxy s; + return s; +} + +namespace +{ +template +inline bool apply_to(JSContext* cx, uint argc, JS::Value* vp) +{ + 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; +} +} + +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); + + T* e = static_cast(JS_GetPrivate(proxy.get())); + 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; + + // Return function properties. Specializable. + if (funcGetter(e, propName, vp)) + 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()); + return false; +} + + +template +bool JSI_GUIProxy::set(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::HandleValue vp, + JS::HandleValue UNUSED(receiver), JS::ObjectOpResult& result) const +{ + 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); + + 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(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); +} + +template +bool JSI_GUIProxy::delete_(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::ObjectOpResult& result) const +{ + 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); + + 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); +} Index: ps/trunk/source/gui/Scripting/JSInterface_IGUIObject.h =================================================================== --- ps/trunk/source/gui/Scripting/JSInterface_IGUIObject.h +++ ps/trunk/source/gui/Scripting/JSInterface_IGUIObject.h @@ -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: ps/trunk/source/gui/Scripting/JSInterface_IGUIObject.cpp =================================================================== --- ps/trunk/source/gui/Scripting/JSInterface_IGUIObject.cpp +++ ps/trunk/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); -} - -bool JSI_IGUIObject::getProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp) -{ - 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") +namespace { + struct SData { - 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()); + JS::PersistentRootedObject m_ToString; + JS::PersistentRootedObject m_Focus; + JS::PersistentRootedObject m_Blur; + JS::PersistentRootedObject m_GetComputedSize; + }; +} + +template <> +bool JSI_GUIProxy::funcGetter(IGUIObject* 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; 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); -} - -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; + 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::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: ps/trunk/source/gui/Scripting/ScriptFunctions.cpp =================================================================== --- ps/trunk/source/gui/Scripting/ScriptFunctions.cpp +++ ps/trunk/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: ps/trunk/source/scriptinterface/ScriptExtraHeaders.h =================================================================== --- ps/trunk/source/scriptinterface/ScriptExtraHeaders.h +++ ps/trunk/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