File: JSInterface_GUIProxy.h

package info (click to toggle)
0ad 0.0.26-3
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 130,460 kB
  • sloc: cpp: 261,824; ansic: 198,392; javascript: 19,067; python: 14,557; sh: 7,629; perl: 4,072; xml: 849; makefile: 741; java: 533; ruby: 229; php: 190; pascal: 30; sql: 21; tcl: 4
file content (222 lines) | stat: -rw-r--r-- 8,297 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
/* Copyright (C) 2021 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 <http://www.gnu.org/licenses/>.
 */

#ifndef INCLUDED_JSI_GUIPROXY
#define INCLUDED_JSI_GUIPROXY

#include "gui/ObjectBases/IGUIObject.h"
#include "scriptinterface/ScriptExtraHeaders.h"

#include <memory>
#include <utility>

class ScriptInterface;
class ScriptRequest;

template <typename T>
class JSI_GUIProxy;

// 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

/**
 * JS GUI proxies need to store some private data.
 * This class is responsible for deleting that data.
 */
class IGUIProxyObject final
{
	template<typename T>
	friend class JSI_GUIProxy;
	friend std::unique_ptr<IGUIProxyObject> std::make_unique<IGUIProxyObject>();
public:
	JSObject* Get() const
	{
		return m_Object.get();
	}

	using PrivateData = IGUIObject*;

	template<typename T>
	static T* FromPrivateSlot(JSObject* obj)
	{
		return static_cast<T*>(FromPrivateSlot<IGUIObject>(obj));
	}
protected:
	template<typename T>
	static T* UnsafeFromPrivateSlot(JSObject* obj)
	{
		return static_cast<T*>(static_cast<PrivateData>(js::GetProxyPrivate(obj).toPrivate()));
	}

	IGUIProxyObject() = default;
	IGUIProxyObject(const IGUIProxyObject&) = delete;
	IGUIProxyObject(IGUIProxyObject&&) = delete;

	JS::PersistentRootedObject m_Object;
	PrivateData m_Ptr;
};
// Declare the IGUIObject* specialization - it's defined in _impl.h
template<> IGUIObject* IGUIProxyObject::FromPrivateSlot<IGUIObject>(JSObject*);

/**
 * Proxies need to store some data whose lifetime is tied to an interface.
 * This is the virtual interface of that data.
 */
class GUIProxyProps
{
public:
	virtual ~GUIProxyProps() {};

	// @return true if @param name exists in this cache.
	virtual bool has(const std::string& name) const = 0;
	// @return the JSFunction matching @param name. Must call has() first as it can assume existence.
	virtual JSObject* get(const std::string& name) const = 0;
	virtual bool setFunction(const ScriptRequest& rq, const std::string& name, JSFunction* function) = 0;
};

/**
 * 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.
 *
 * Proxies can be used with prototypes and almost treated like regular JS objects.
 * However, the default implementation embarks a lot of code that we don't really need here,
 * since the only fanciness is that we cache JSFunction* properties.
 * As such, these GUI proxies don't have one and instead override get/set directly.
 *
 * To add a new JSI_GUIProxy, you'll need to:
 *  - overload CreateJSObject in your class header.
 *  - change the CGUI::AddObjectTypes method.
 *  - explicitly instantiate the template & CreateJSObject via DECLARE_GUIPROXY.
 *
 */
template<typename GUIObjectType>
class JSI_GUIProxy : public js::BaseProxyHandler
{
	// Need to friend other specializations so CreateFunctions() can call the IGUIObject version in all codepaths.
	template<typename T>
	friend class JSI_GUIProxy;
public:
	// For convenience, this is the single instantiated JSI_GUIProxy.
	static JSI_GUIProxy& Singleton();

	// Call this in CGUI::AddObjectTypes.
	static std::pair<const js::BaseProxyHandler*, GUIProxyProps*> CreateData(ScriptInterface& scriptInterface);

	// Create the JS object, the proxy, the data and wrap it in a convenient unique_ptr.
	static std::unique_ptr<IGUIProxyObject> CreateJSObject(const ScriptRequest& rq, GUIObjectType* ptr, GUIProxyProps* data);
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() {};

	static GUIObjectType* FromPrivateSlot(const ScriptRequest&, JS::CallArgs& args);

	// The default implementations need to know the type of the GUIProxyProps for this proxy type.
	// This is done by specializing this struct's alias type.
	struct PropCache;

	// Specialize this to define the custom properties of this type.
	static void CreateFunctions(const ScriptRequest& rq, GUIProxyProps* cache);

	// Convenience helper for the above.
	template<auto callable>
	static void CreateFunction(const ScriptRequest& rq, GUIProxyProps* cache, const std::string& name);

	// This handles returning custom properties. Specialize this if needed.
	bool PropGetter(JS::HandleObject proxy, 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<JS::PropertyDescriptor> 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<JS::PropertyDescriptor> UNUSED(desc), JS::ObjectOpResult& UNUSED(result)) const override
	{
		return false;
	}
	// No accessible properties.
	virtual bool ownPropertyKeys(JSContext* UNUSED(cx), JS::HandleObject UNUSED(proxy), JS::MutableHandleIdVector UNUSED(props)) const override
	{
		return true;
	}
	// Nothing to enumerate.
	virtual bool enumerate(JSContext* UNUSED(cx), JS::HandleObject UNUSED(proxy), JS::MutableHandleIdVector UNUSED(props)) 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