Pyrogenesis  trunk
JSInterface_GUIProxy_impl.h
Go to the documentation of this file.
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"
25 #include "ps/CLogger.h"
29 
30 #include <string>
31 #include <string_view>
32 
33 template <typename T>
35 {
36  static JSI_GUIProxy<T> s;
37  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.
52 {
53 // All proxy objects share a class definition.
54 JSClass& ClassDefinition()
55 {
56  static JSClass c = PROXY_CLASS_DEF("GUIObjectProxy", JSCLASS_HAS_CACHED_PROTO(JSProto_Proxy) | JSCLASS_HAS_RESERVED_SLOTS(1));
57  return c;
58 }
59 
60 // Default implementation of the cache via unordered_map
61 class MapCache : public GUIProxyProps
62 {
63 public:
64  virtual ~MapCache() {};
65 
66  virtual bool has(const std::string& name) const override
67  {
68  return m_Functions.find(name) != m_Functions.end();
69  }
70 
71  virtual JSObject* get(const std::string& name) const override
72  {
73  return m_Functions.at(name).get();
74  }
75 
76  virtual bool setFunction(const ScriptRequest& rq, const std::string& name, JSFunction* function) override
77  {
78  m_Functions[name].init(rq.cx, JS_GetFunctionObject(function));
79  return true;
80  }
81 
82 protected:
83  std::unordered_map<std::string, JS::PersistentRootedObject> m_Functions;
84 };
85 }
86 
87 template<>
88 IGUIObject* IGUIProxyObject::FromPrivateSlot<IGUIObject>(JSObject* obj)
89 {
90  if (!obj)
91  return nullptr;
92  if (JS::GetClass(obj) != &JSInterface_GUIProxy::ClassDefinition())
93  return nullptr;
94  return UnsafeFromPrivateSlot<IGUIObject>(obj);
95 }
96 
97 // The default propcache is a MapCache.
98 template<typename T>
100 {
102 };
103 
104 template <typename T>
106 {
107  // Call the unsafe version - this is only ever called from actual proxy objects.
108  return IGUIProxyObject::UnsafeFromPrivateSlot<T>(args.thisv().toObjectOrNull());
109 }
110 
111 template <typename T>
112 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  const PropertyCache* data = static_cast<const PropertyCache*>(static_cast<const GUIProxyProps*>(js::GetProxyReservedSlot(proxy, 0).toPrivate()));
117  if (data->has(propName))
118  {
119  vp.setObjectOrNull(data->get(propName));
120  return true;
121  }
122  return false;
123 }
124 
125 template <typename T>
126 std::pair<const js::BaseProxyHandler*, GUIProxyProps*> JSI_GUIProxy<T>::CreateData(ScriptInterface& scriptInterface)
127 {
128  using PropertyCache = typename PropCache::type;
129  PropertyCache* data = new PropertyCache();
130  ScriptRequest rq(scriptInterface);
131 
132  // Functions common to all children of IGUIObject.
134 
135  // Let derived classes register their own interface.
136  if constexpr (!std::is_same_v<T, IGUIObject>)
137  CreateFunctions(rq, data);
138  return { &Singleton(), data };
139 }
140 
141 template<typename T>
142 template<auto callable>
143 void JSI_GUIProxy<T>::CreateFunction(const ScriptRequest& rq, GUIProxyProps* cache, const std::string& name)
144 {
145  cache->setFunction(rq, name, ScriptFunction::Create<callable, FromPrivateSlot>(rq, name.c_str()));
146 }
147 
148 template<typename T>
149 std::unique_ptr<IGUIProxyObject> JSI_GUIProxy<T>::CreateJSObject(const ScriptRequest& rq, T* ptr, GUIProxyProps* dataPtr)
150 {
151  js::ProxyOptions options;
152  options.setClass(&JSInterface_GUIProxy::ClassDefinition());
153 
154  auto ret = std::make_unique<IGUIProxyObject>();
155  ret->m_Ptr = static_cast<IGUIObject*>(ptr);
156 
157  JS::RootedValue cppObj(rq.cx), data(rq.cx);
158  cppObj.get().setPrivate(ret->m_Ptr);
159  data.get().setPrivate(static_cast<void*>(dataPtr));
160  ret->m_Object.init(rq.cx, js::NewProxyObject(rq.cx, &Singleton(), cppObj, nullptr, options));
161  js::SetProxyReservedSlot(ret->m_Object, 0, data);
162  return ret;
163 }
164 
165 template <typename T>
166 bool JSI_GUIProxy<T>::get(JSContext* cx, JS::HandleObject proxy, JS::HandleValue UNUSED(receiver), JS::HandleId id, JS::MutableHandleValue vp) const
167 {
168  ScriptRequest rq(cx);
169 
170  T* e = IGUIProxyObject::FromPrivateSlot<T>(proxy.get());
171  if (!e)
172  return false;
173 
174  JS::RootedValue idval(rq.cx);
175  if (!JS_IdToValue(rq.cx, id, &idval))
176  return false;
177 
178  std::string propName;
179  if (!Script::FromJSVal(rq, idval, propName))
180  return false;
181 
182  // Return function properties. Specializable.
183  if (PropGetter(proxy, propName, vp))
184  return true;
185 
186  // Use onWhatever to access event handlers
187  if (propName.substr(0, 2) == "on")
188  {
189  CStr eventName(propName.substr(2));
190  std::map<CStr, JS::Heap<JSObject*>>::iterator it = e->m_ScriptHandlers.find(eventName);
191  if (it == e->m_ScriptHandlers.end())
192  vp.setNull();
193  else
194  vp.setObject(*it->second.get());
195  return true;
196  }
197 
198  if (propName == "parent")
199  {
200  IGUIObject* parent = e->GetParent();
201 
202  if (parent)
203  vp.set(JS::ObjectValue(*parent->GetJSObject()));
204  else
205  vp.set(JS::NullValue());
206 
207  return true;
208  }
209  else if (propName == "children")
210  {
211  Script::CreateArray(rq, vp);
212 
213  for (size_t i = 0; i < e->m_Children.size(); ++i)
214  Script::SetPropertyInt(rq, vp, i, e->m_Children[i]);
215 
216  return true;
217  }
218  else if (propName == "name")
219  {
220  Script::ToJSVal(rq, vp, e->GetName());
221  return true;
222  }
223  else if (e->SettingExists(propName))
224  {
225  e->m_Settings[propName]->ToJSVal(rq, vp);
226  return true;
227  }
228 
229  LOGERROR("Property '%s' does not exist!", propName.c_str());
230  return false;
231 }
232 
233 
234 template <typename T>
235 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  T* e = IGUIProxyObject::FromPrivateSlot<T>(proxy.get());
239  if (!e)
240  {
241  LOGERROR("C++ GUI Object could not be found");
242  return result.fail(JSMSG_OBJECT_REQUIRED);
243  }
244 
245  ScriptRequest rq(cx);
246 
247  JS::RootedValue idval(rq.cx);
248  if (!JS_IdToValue(rq.cx, id, &idval))
249  return result.fail(JSMSG_BAD_PROP_ID);
250 
251  std::string propName;
252  if (!Script::FromJSVal(rq, idval, propName))
253  return result.fail(JSMSG_BAD_PROP_ID);
254 
255  if (propName == "name")
256  {
257  std::string value;
258  if (!Script::FromJSVal(rq, vp, value))
259  return result.fail(JSMSG_BAD_PROP_ID);
260  e->SetName(value);
261  return result.succeed();
262  }
263 
264  JS::RootedObject vpObj(cx);
265  if (vp.isObject())
266  vpObj = &vp.toObject();
267 
268  // Use onWhatever to set event handlers
269  if (propName.substr(0, 2) == "on")
270  {
271  if (vp.isPrimitive() || vp.isNull() || !JS_ObjectIsFunction(&vp.toObject()))
272  {
273  LOGERROR("on- event-handlers must be functions");
274  return result.fail(JSMSG_NOT_FUNCTION);
275  }
276 
277  CStr eventName(propName.substr(2));
278  e->SetScriptHandler(eventName, vpObj);
279 
280  return result.succeed();
281  }
282 
283  if (e->SettingExists(propName))
284  return e->m_Settings[propName]->FromJSVal(rq, vp, true) ? result.succeed() : result.fail(JSMSG_USER_DEFINED_ERROR);
285 
286  LOGERROR("Property '%s' does not exist!", propName.c_str());
287  return result.fail(JSMSG_BAD_PROP_ID);
288 }
289 
290 template<typename T>
291 bool JSI_GUIProxy<T>::delete_(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::ObjectOpResult& result) const
292 {
293  T* e = IGUIProxyObject::FromPrivateSlot<T>(proxy.get());
294  if (!e)
295  {
296  LOGERROR("C++ GUI Object could not be found");
297  return result.fail(JSMSG_OBJECT_REQUIRED);
298  }
299 
300  ScriptRequest rq(cx);
301 
302  JS::RootedValue idval(rq.cx);
303  if (!JS_IdToValue(rq.cx, id, &idval))
304  return result.fail(JSMSG_BAD_PROP_ID);
305 
306  std::string propName;
307  if (!Script::FromJSVal(rq, idval, propName))
308  return result.fail(JSMSG_BAD_PROP_ID);
309 
310  // event handlers
311  if (std::string_view{propName}.substr(0, 2) == "on")
312  {
313  CStr eventName(propName.substr(2));
314  e->UnsetScriptHandler(eventName);
315  return result.succeed();
316  }
317 
318  LOGERROR("Only event handlers can be deleted from GUI objects!");
319  return result.fail(JSMSG_BAD_PROP_ID);
320 }
virtual bool set(JSContext *cx, JS::HandleObject proxy, JS::HandleId id, JS::HandleValue vp, JS::HandleValue receiver, JS::ObjectOpResult &result) const final
Definition: JSInterface_GUIProxy_impl.h:235
bool PropGetter(JS::HandleObject proxy, const std::string &propName, JS::MutableHandleValue vp) const
Definition: JSInterface_GUIProxy_impl.h:112
JSContext * cx
Definition: ScriptRequest.h:92
virtual bool setFunction(const ScriptRequest &rq, const std::string &name, JSFunction *function) override
Definition: JSInterface_GUIProxy_impl.h:76
#define LOGERROR(...)
Definition: CLogger.h:36
#define UNUSED(param)
mark a function parameter as unused and avoid the corresponding compiler warning. ...
Definition: code_annotation.h:38
static void CreateFunctions(const ScriptRequest &rq, GUIProxyProps *cache)
Definition: JSInterface_GUIProxy_impl.h:51
Definition: JSInterface_GUIProxy_impl.h:99
static std::unique_ptr< IGUIProxyObject > CreateJSObject(const ScriptRequest &rq, GUIObjectType *ptr, GUIProxyProps *data)
Definition: JSInterface_GUIProxy_impl.h:149
virtual bool get(JSContext *cx, JS::HandleObject proxy, JS::HandleValue receiver, JS::HandleId id, JS::MutableHandleValue vp) const override final
Definition: JSInterface_GUIProxy_impl.h:166
GUI object such as a button or an input-box.
Definition: IGUIObject.h:59
IGUIObject * GetParent() const
NOTE! This will not just return m_pParent, when that is need use it! There is one exception to it...
Definition: IGUIObject.cpp:187
virtual ~MapCache()
Definition: JSInterface_GUIProxy_impl.h:64
static std::pair< const js::BaseProxyHandler *, GUIProxyProps * > CreateData(ScriptInterface &scriptInterface)
Definition: JSInterface_GUIProxy_impl.h:126
Proxies need to store some data whose lifetime is tied to an interface.
Definition: JSInterface_GUIProxy.h:85
static GUIObjectType * FromPrivateSlot(const ScriptRequest &, JS::CallArgs &args)
Definition: JSInterface_GUIProxy_impl.h:105
JSObject * GetJSObject()
Retrieves the JSObject representing this GUI object.
Definition: IGUIObject.cpp:427
void ToJSVal(const ScriptRequest &rq, JS::MutableHandleValue ret, T const &val)
Convert a C++ type to a JS::Value.
static void CreateFunction(const ScriptRequest &rq, GUIProxyProps *cache, const std::string &name)
Definition: JSInterface_GUIProxy_impl.h:143
#define T(string_literal)
Definition: secure_crt.cpp:77
Definition: JSInterface_GUIProxy_impl.h:61
Handles the js interface with C++ GUI objects.
Definition: IGUIObject.h:48
virtual bool has(const std::string &name) const override
Definition: JSInterface_GUIProxy_impl.h:66
bool SetPropertyInt(const ScriptRequest &rq, JS::HandleValue obj, int name, const T &value, bool constant=false, bool enumerable=true)
Definition: Object.h:129
bool CreateArray(const ScriptRequest &rq, JS::MutableHandleValue objectValue, size_t length=0)
Sets the given value to a new JS object or Null Value in case of out-of-memory.
Definition: Object.h:244
Template base class for singletons.
Definition: Singleton.h:33
Abstraction around a SpiderMonkey JS::Realm.
Definition: ScriptInterface.h:71
virtual bool setFunction(const ScriptRequest &rq, const std::string &name, JSFunction *function)=0
static JSI_GUIProxy & Singleton()
Definition: JSInterface_GUIProxy_impl.h:34
Spidermonkey maintains some &#39;local&#39; state via the JSContext* object.
Definition: ScriptRequest.h:59
JSClass & ClassDefinition()
Definition: JSInterface_GUIProxy_impl.h:54
std::unordered_map< std::string, JS::PersistentRootedObject > m_Functions
Definition: JSInterface_GUIProxy_impl.h:83
virtual bool delete_(JSContext *cx, JS::HandleObject proxy, JS::HandleId id, JS::ObjectOpResult &result) const override final
Definition: JSInterface_GUIProxy_impl.h:291
bool FromJSVal(const ScriptRequest &rq, const JS::HandleValue val, T &ret)
Convert a JS::Value to a C++ type.