LCOV - code coverage report
Current view: top level - source/gui/Scripting - JSInterface_GUIProxy_impl.h (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 63 137 46.0 %
Date: 2023-01-19 00:18:29 Functions: 27 56 48.2 %

          Line data    Source code
       1             : /* Copyright (C) 2023 Wildfire Games.
       2             :  * This file is part of 0 A.D.
       3             :  *
       4             :  * 0 A.D. is free software: you can redistribute it and/or modify
       5             :  * it under the terms of the GNU General Public License as published by
       6             :  * the Free Software Foundation, either version 2 of the License, or
       7             :  * (at your option) any later version.
       8             :  *
       9             :  * 0 A.D. is distributed in the hope that it will be useful,
      10             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             :  * GNU General Public License for more details.
      13             :  *
      14             :  * You should have received a copy of the GNU General Public License
      15             :  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : // This file is included directly into actual implementation files.
      19             : 
      20             : #include "JSInterface_GUIProxy.h"
      21             : 
      22             : #include "gui/CGUI.h"
      23             : #include "gui/CGUISetting.h"
      24             : #include "gui/ObjectBases/IGUIObject.h"
      25             : #include "ps/CLogger.h"
      26             : #include "scriptinterface/FunctionWrapper.h"
      27             : #include "scriptinterface/ScriptExtraHeaders.h"
      28             : #include "scriptinterface/ScriptRequest.h"
      29             : 
      30             : #include <string>
      31             : #include <string_view>
      32             : 
      33             : template <typename T>
      34          38 : JSI_GUIProxy<T>& JSI_GUIProxy<T>::Singleton()
      35             : {
      36          38 :     static JSI_GUIProxy<T> s;
      37          38 :     return s;
      38             : }
      39             : 
      40             : // Call this for every specialised type. You will need to override IGUIObject::CreateJSObject() in your class interface.
      41             : #define DECLARE_GUIPROXY(Type) \
      42             : void Type::CreateJSObject() \
      43             : { \
      44             :     ScriptRequest rq(m_pGUI.GetScriptInterface()); \
      45             :     using ProxyHandler = JSI_GUIProxy<std::remove_pointer_t<decltype(this)>>; \
      46             :     m_JSObject = ProxyHandler::CreateJSObject(rq, this, GetGUI().GetProxyData(&ProxyHandler::Singleton())); \
      47             : } \
      48             : template class JSI_GUIProxy<Type>;
      49             : 
      50             : // Use a common namespace to avoid duplicating the symbols un-necessarily.
      51             : namespace JSInterface_GUIProxy
      52             : {
      53             : // All proxy objects share a class definition.
      54          13 : JSClass& ClassDefinition()
      55             : {
      56             :     static JSClass c = PROXY_CLASS_DEF("GUIObjectProxy", JSCLASS_HAS_CACHED_PROTO(JSProto_Proxy) | JSCLASS_HAS_RESERVED_SLOTS(1));
      57          13 :     return c;
      58             : }
      59             : 
      60             : // Default implementation of the cache via unordered_map
      61          30 : class MapCache : public GUIProxyProps
      62             : {
      63             : public:
      64          60 :     virtual ~MapCache() {};
      65             : 
      66           0 :     virtual bool has(const std::string& name) const override
      67             :     {
      68           0 :         return m_Functions.find(name) != m_Functions.end();
      69             :     }
      70             : 
      71           0 :     virtual JSObject* get(const std::string& name) const override
      72             :     {
      73           0 :         return m_Functions.at(name).get();
      74             :     }
      75             : 
      76         174 :     virtual bool setFunction(const ScriptRequest& rq, const std::string& name, JSFunction* function) override
      77             :     {
      78         174 :         m_Functions[name].init(rq.cx, JS_GetFunctionObject(function));
      79         174 :         return true;
      80             :     }
      81             : 
      82             : protected:
      83             :     std::unordered_map<std::string, JS::PersistentRootedObject> m_Functions;
      84             : };
      85             : }
      86             : 
      87             : template<>
      88           9 : IGUIObject* IGUIProxyObject::FromPrivateSlot<IGUIObject>(JSObject* obj)
      89             : {
      90           9 :     if (!obj)
      91           0 :         return nullptr;
      92           9 :     if (JS::GetClass(obj) != &JSInterface_GUIProxy::ClassDefinition())
      93           0 :         return nullptr;
      94           9 :     return UnsafeFromPrivateSlot<IGUIObject>(obj);
      95             : }
      96             : 
      97             : // The default propcache is a MapCache.
      98             : template<typename T>
      99             : struct JSI_GUIProxy<T>::PropCache
     100             : {
     101             :     using type = JSInterface_GUIProxy::MapCache;
     102             : };
     103             : 
     104             : template <typename T>
     105           0 : T* JSI_GUIProxy<T>::FromPrivateSlot(const ScriptRequest&, JS::CallArgs& args)
     106             : {
     107             :     // Call the unsafe version - this is only ever called from actual proxy objects.
     108           0 :     return IGUIProxyObject::UnsafeFromPrivateSlot<T>(args.thisv().toObjectOrNull());
     109             : }
     110             : 
     111             : template <typename T>
     112           0 : bool JSI_GUIProxy<T>::PropGetter(JS::HandleObject proxy, const std::string& propName, JS::MutableHandleValue vp) const
     113             : {
     114             :     using PropertyCache = typename PropCache::type;
     115             :     // Since we know at compile time what the type actually is, avoid the virtual call.
     116           0 :     const PropertyCache* data = static_cast<const PropertyCache*>(static_cast<const GUIProxyProps*>(js::GetProxyReservedSlot(proxy, 0).toPrivate()));
     117           0 :     if (data->has(propName))
     118             :     {
     119           0 :         vp.setObjectOrNull(data->get(propName));
     120           0 :         return true;
     121             :     }
     122           0 :     return false;
     123             : }
     124             : 
     125             : template <typename T>
     126          30 : std::pair<const js::BaseProxyHandler*, GUIProxyProps*> JSI_GUIProxy<T>::CreateData(ScriptInterface& scriptInterface)
     127             : {
     128             :     using PropertyCache = typename PropCache::type;
     129          30 :     PropertyCache* data = new PropertyCache();
     130          60 :     ScriptRequest rq(scriptInterface);
     131             : 
     132             :     // Functions common to all children of IGUIObject.
     133          30 :     JSI_GUIProxy<IGUIObject>::CreateFunctions(rq, data);
     134             : 
     135             :     // Let derived classes register their own interface.
     136             :     if constexpr (!std::is_same_v<T, IGUIObject>)
     137          24 :         CreateFunctions(rq, data);
     138          60 :     return { &Singleton(), data };
     139             : }
     140             : 
     141             : template<typename T>
     142             : template<auto callable>
     143         174 : void JSI_GUIProxy<T>::CreateFunction(const ScriptRequest& rq, GUIProxyProps* cache, const std::string& name)
     144             : {
     145         174 :     cache->setFunction(rq, name, ScriptFunction::Create<callable, FromPrivateSlot>(rq, name.c_str()));
     146         174 : }
     147             : 
     148             : template<typename T>
     149           4 : std::unique_ptr<IGUIProxyObject> JSI_GUIProxy<T>::CreateJSObject(const ScriptRequest& rq, T* ptr, GUIProxyProps* dataPtr)
     150             : {
     151           4 :     js::ProxyOptions options;
     152           4 :     options.setClass(&JSInterface_GUIProxy::ClassDefinition());
     153             : 
     154           4 :     auto ret = std::make_unique<IGUIProxyObject>();
     155           4 :     ret->m_Ptr = static_cast<IGUIObject*>(ptr);
     156             : 
     157           8 :     JS::RootedValue cppObj(rq.cx), data(rq.cx);
     158           4 :     cppObj.get().setPrivate(ret->m_Ptr);
     159           4 :     data.get().setPrivate(static_cast<void*>(dataPtr));
     160           4 :     ret->m_Object.init(rq.cx, js::NewProxyObject(rq.cx, &Singleton(), cppObj, nullptr, options));
     161           4 :     js::SetProxyReservedSlot(ret->m_Object, 0, data);
     162           8 :     return ret;
     163             : }
     164             : 
     165             : template <typename T>
     166           0 : bool JSI_GUIProxy<T>::get(JSContext* cx, JS::HandleObject proxy, JS::HandleValue UNUSED(receiver), JS::HandleId id, JS::MutableHandleValue vp) const
     167             : {
     168           0 :     ScriptRequest rq(cx);
     169             : 
     170           0 :     T* e = IGUIProxyObject::FromPrivateSlot<T>(proxy.get());
     171           0 :     if (!e)
     172           0 :         return false;
     173             : 
     174           0 :     JS::RootedValue idval(rq.cx);
     175           0 :     if (!JS_IdToValue(rq.cx, id, &idval))
     176           0 :         return false;
     177             : 
     178           0 :     std::string propName;
     179           0 :     if (!Script::FromJSVal(rq, idval, propName))
     180           0 :         return false;
     181             : 
     182             :     // Return function properties. Specializable.
     183           0 :     if (PropGetter(proxy, propName, vp))
     184           0 :         return true;
     185             : 
     186             :     // Use onWhatever to access event handlers
     187           0 :     if (propName.substr(0, 2) == "on")
     188             :     {
     189           0 :         CStr eventName(propName.substr(2));
     190           0 :         std::map<CStr, JS::Heap<JSObject*>>::iterator it = e->m_ScriptHandlers.find(eventName);
     191           0 :         if (it == e->m_ScriptHandlers.end())
     192           0 :             vp.setNull();
     193             :         else
     194           0 :             vp.setObject(*it->second.get());
     195           0 :         return true;
     196             :     }
     197             : 
     198           0 :     if (propName == "parent")
     199             :     {
     200           0 :         IGUIObject* parent = e->GetParent();
     201             : 
     202           0 :         if (parent)
     203           0 :             vp.set(JS::ObjectValue(*parent->GetJSObject()));
     204             :         else
     205           0 :             vp.set(JS::NullValue());
     206             : 
     207           0 :         return true;
     208             :     }
     209           0 :     else if (propName == "children")
     210             :     {
     211           0 :         Script::CreateArray(rq, vp);
     212             : 
     213           0 :         for (size_t i = 0; i < e->m_Children.size(); ++i)
     214           0 :             Script::SetPropertyInt(rq, vp, i, e->m_Children[i]);
     215             : 
     216           0 :         return true;
     217             :     }
     218           0 :     else if (propName == "name")
     219             :     {
     220           0 :         Script::ToJSVal(rq, vp, e->GetName());
     221           0 :         return true;
     222             :     }
     223           0 :     else if (e->SettingExists(propName))
     224             :     {
     225           0 :         e->m_Settings[propName]->ToJSVal(rq, vp);
     226           0 :         return true;
     227             :     }
     228             : 
     229           0 :     LOGERROR("Property '%s' does not exist!", propName.c_str());
     230           0 :     return false;
     231             : }
     232             : 
     233             : 
     234             : template <typename T>
     235           5 : bool JSI_GUIProxy<T>::set(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::HandleValue vp,
     236             :                             JS::HandleValue UNUSED(receiver), JS::ObjectOpResult& result) const
     237             : {
     238           5 :     T* e = IGUIProxyObject::FromPrivateSlot<T>(proxy.get());
     239           5 :     if (!e)
     240             :     {
     241           0 :         LOGERROR("C++ GUI Object could not be found");
     242           0 :         return result.fail(JSMSG_OBJECT_REQUIRED);
     243             :     }
     244             : 
     245          10 :     ScriptRequest rq(cx);
     246             : 
     247          10 :     JS::RootedValue idval(rq.cx);
     248           5 :     if (!JS_IdToValue(rq.cx, id, &idval))
     249           0 :         return result.fail(JSMSG_BAD_PROP_ID);
     250             : 
     251          10 :     std::string propName;
     252           5 :     if (!Script::FromJSVal(rq, idval, propName))
     253           0 :         return result.fail(JSMSG_BAD_PROP_ID);
     254             : 
     255           5 :     if (propName == "name")
     256             :     {
     257           0 :         std::string value;
     258           0 :         if (!Script::FromJSVal(rq, vp, value))
     259           0 :             return result.fail(JSMSG_BAD_PROP_ID);
     260           0 :         e->SetName(value);
     261           0 :         return result.succeed();
     262             :     }
     263             : 
     264          10 :     JS::RootedObject vpObj(cx);
     265           5 :     if (vp.isObject())
     266           5 :         vpObj = &vp.toObject();
     267             : 
     268             :     // Use onWhatever to set event handlers
     269           5 :     if (propName.substr(0, 2) == "on")
     270             :     {
     271           5 :         if (vp.isPrimitive() || vp.isNull() || !JS_ObjectIsFunction(&vp.toObject()))
     272             :         {
     273           0 :             LOGERROR("on- event-handlers must be functions");
     274           0 :             return result.fail(JSMSG_NOT_FUNCTION);
     275             :         }
     276             : 
     277          10 :         CStr eventName(propName.substr(2));
     278           5 :         e->SetScriptHandler(eventName, vpObj);
     279             : 
     280           5 :         return result.succeed();
     281             :     }
     282             : 
     283           0 :     if (e->SettingExists(propName))
     284           0 :         return e->m_Settings[propName]->FromJSVal(rq, vp, true) ? result.succeed() : result.fail(JSMSG_USER_DEFINED_ERROR);
     285             : 
     286           0 :     LOGERROR("Property '%s' does not exist!", propName.c_str());
     287           0 :     return result.fail(JSMSG_BAD_PROP_ID);
     288             : }
     289             : 
     290             : template<typename T>
     291           4 : bool JSI_GUIProxy<T>::delete_(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::ObjectOpResult& result) const
     292             : {
     293           4 :     T* e = IGUIProxyObject::FromPrivateSlot<T>(proxy.get());
     294           4 :     if (!e)
     295             :     {
     296           0 :         LOGERROR("C++ GUI Object could not be found");
     297           0 :         return result.fail(JSMSG_OBJECT_REQUIRED);
     298             :     }
     299             : 
     300           8 :     ScriptRequest rq(cx);
     301             : 
     302           8 :     JS::RootedValue idval(rq.cx);
     303           4 :     if (!JS_IdToValue(rq.cx, id, &idval))
     304           0 :         return result.fail(JSMSG_BAD_PROP_ID);
     305             : 
     306           8 :     std::string propName;
     307           4 :     if (!Script::FromJSVal(rq, idval, propName))
     308           0 :         return result.fail(JSMSG_BAD_PROP_ID);
     309             : 
     310             :     // event handlers
     311           4 :     if (std::string_view{propName}.substr(0, 2) == "on")
     312             :     {
     313           8 :         CStr eventName(propName.substr(2));
     314           4 :         e->UnsetScriptHandler(eventName);
     315           4 :         return result.succeed();
     316             :     }
     317             : 
     318           0 :     LOGERROR("Only event handlers can be deleted from GUI objects!");
     319           0 :     return result.fail(JSMSG_BAD_PROP_ID);
     320             : }

Generated by: LCOV version 1.13