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
|