1
1
forked from 0ad/0ad

Make JS GUI Objects Proxy objects.

The JS-side counterparts of C++ objects are now Proxy objects, in
anticipation of SM60 changes that remove the get/set hooks entirely.
This makes semantic sense too as they are essentially phantom wrappers
around the C++ objects, with no proper JS representation.

By using different proxy handlers for different GUI object types, we can
further fix issues encountered in D2136 by defining the relevant
functions only on objects that should have them.

The main complexity with proxy handlers is that Spidermonkey assumes in
several places that they are static and data-less, so they cannot be
used directly to hold data. This diff works around that issue by storing
per-script-interface data in the CGui directly.

Further API changes in SM60 make this slightly cleaner.

Comments by: Itms
Refs #5859

Differential Revision: https://code.wildfiregames.com/D2768
This was SVN commit r24229.
This commit is contained in:
wraitii 2020-11-21 17:49:06 +00:00
parent 8a1b3d6769
commit 7c04ea0211
14 changed files with 537 additions and 322 deletions

View File

@ -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"

View File

@ -35,6 +35,7 @@
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <vector>
@ -46,6 +47,8 @@ class IGUIObject;
struct SGUIImageEffects;
struct SGUIScrollBarStyle;
namespace js { class BaseProxyHandler; }
/**
* The main object that represents a whole GUI page.
*/
@ -237,6 +240,8 @@ public:
*/
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<ScriptInterface> GetScriptInterface() { return m_ScriptInterface; };
private:
@ -598,6 +603,17 @@ private:
*/
std::map<CStr, ConstructObjectFunction> 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<const js::BaseProxyHandler*, void*> m_ProxyData;
/**
* Map from hotkey names to objects that listen to the hotkey.
* (This is an optimisation to avoid recursing over the whole GUI

View File

@ -14,8 +14,8 @@
* 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_GUIOBJECTTYPES
#define INCLUDED_GUIOBJECTTYPES
#include "precompiled.h"
#include "gui/ObjectTypes/CButton.h"
#include "gui/ObjectTypes/CChart.h"
@ -32,9 +32,13 @@
#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<IGUIObject>::CreateData(*m_ScriptInterface));
m_ProxyData.insert(JSI_GUIProxy<CText>::CreateData(*m_ScriptInterface));
AddObjectType("button", &CButton::ConstructObject);
AddObjectType("chart", &CChart::ConstructObject);
AddObjectType("checkbox", &CCheckBox::ConstructObject);
@ -52,5 +56,3 @@ void CGUI::AddObjectTypes()
AddObjectType("text", &CText::ConstructObject);
AddObjectType("tooltip", &CTooltip::ConstructObject);
}
#endif // INCLUDED_GUIOBJECTTYPES

View File

@ -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 @@ void IGUIObject::CreateJSObject()
{
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<IGUIObject>::ClassDefinition());
RegisterScriptFunctions();
JS::RootedValue cppObj(rq.cx);
m_JSObject.init(rq.cx, js::NewProxyObject(rq.cx, &JSI_GUIProxy<IGUIObject>::Singleton(), cppObj, nullptr, options));
JS_SetPrivate(m_JSObject.get(), this);
}
JSObject* IGUIObject::GetJSObject()
@ -468,6 +471,31 @@ JSObject* IGUIObject::GetJSObject()
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;

View File

@ -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 <map>
#include <string>
@ -39,6 +39,9 @@ class CGUI;
class IGUIObject;
class IGUISetting;
template <typename T>
class JSI_GUIProxy;
using map_pObjects = std::map<CStr, IGUIObject*>;
#define GUI_OBJECT(obj) \
@ -55,10 +58,8 @@ class IGUIObject
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 <typename T>
friend class JSI_GUIProxy;
public:
NONCOPYABLE(IGUIObject);
@ -230,16 +231,19 @@ public:
*/
void RegisterScriptHandler(const CStr& eventName, const CStr& Code, CGUI& pGUI);
/**
* Inheriting classes may append JS functions to the JS object representing this class.
*/
virtual void RegisterScriptFunctions() {}
/**
* Retrieves the JSObject representing this GUI object.
*/
JSObject* GetJSObject();
/**
* The following functions are called from JS.
*/
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 @@ private:
/**
* Creates the JS object representing this page upon first use.
*/
void CreateJSObject();
virtual void CreateJSObject();
/**
* Updates some internal data depending on the setting changed.

View File

@ -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 @@ bool CText::MouseOverIcon()
return false;
}
void CText::RegisterScriptFunctions()
void CText::CreateJSObject()
{
ScriptRequest rq(m_pGUI.GetScriptInterface());
JS_DefineFunctions(rq.cx, m_JSObject, CText::JSI_methods);
js::ProxyOptions options;
options.setClass(&JSI_GUIProxy<IGUIObject>::ClassDefinition());
JS::RootedValue cppObj(rq.cx);
m_JSObject.init(rq.cx, js::NewProxyObject(rq.cx, &JSI_GUIProxy<CText>::Singleton(), cppObj, nullptr, options));
JS_SetPrivate(m_JSObject.get(), this);
}
JSFunctionSpec CText::JSI_methods[] =
void CText::getTextSize(ScriptInterface& scriptInterface, JS::MutableHandleValue ret)
{
JS_FN("getTextSize", CText::GetTextSize, 0, 0),
JS_FS_END
};
bool CText::GetTextSize(JSContext* cx, uint argc, JS::Value* vp)
{
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
ScriptRequest rq(*ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface);
CText* thisObj = ScriptInterface::GetPrivate<CText>(rq, args, &JSI_IGUIObject::JSI_class);
if (!thisObj)
{
ScriptException::Raise(rq, "This is not a CText object!");
return false;
}
thisObj->UpdateText();
ScriptInterface::ToJSVal(rq, args.rval(), thisObj->m_GeneratedTexts[0].GetSize());
return true;
ScriptRequest rq(scriptInterface);
UpdateText();
ScriptInterface::ToJSVal(rq, ret, m_GeneratedTexts[0].GetSize());
}

View File

@ -30,6 +30,8 @@ class CText : public IGUIObject, public IGUIScrollBarOwner, public IGUITextOwner
{
GUI_OBJECT(CText)
friend JSI_GUIProxy<CText>;
public:
CText(CGUI& pGUI);
virtual ~CText();
@ -56,8 +58,6 @@ protected:
*/
void SetupText();
virtual void RegisterScriptFunctions();
/**
* @see IGUIObject#HandleMessage()
*/
@ -68,12 +68,9 @@ protected:
*/
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.

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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 <string>
// 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<CText>::funcGetter(CText* elem, const std::string& propName, JS::MutableHandleValue vp) const
{
const SData& data = *static_cast<const SData*>(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<const js::BaseProxyHandler*, void*> JSI_GUIProxy<CText>::CreateData(ScriptInterface& scriptInterface)
{
SData* data = new SData();
ScriptRequest rq(scriptInterface);
#define func(class, func) &apply_to<CText, class, &class::func>
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<CText>;

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef INCLUDED_JSI_GUIPROXY
#define INCLUDED_JSI_GUIPROXY
#include "scriptinterface/ScriptExtraHeaders.h"
#include <utility>
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<typename GUIObjectType>
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<const js::BaseProxyHandler*, void*> 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<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;
}
// 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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
// This file is included directly into actual implementation files.
template <typename T>
js::Class& JSI_GUIProxy<T>::ClassDefinition()
{
static js::Class c = PROXY_CLASS_DEF("GUIObjectProxy", JSCLASS_HAS_PRIVATE | JSCLASS_HAS_CACHED_PROTO(JSProto_Proxy));
return c;
}
template <typename T>
JSI_GUIProxy<T>& JSI_GUIProxy<T>::Singleton()
{
static JSI_GUIProxy<T> s;
return s;
}
namespace
{
template<class OG, class R, void (R::*funcptr)(ScriptInterface&, JS::MutableHandleValue)>
inline bool apply_to(JSContext* cx, uint argc, JS::Value* vp)
{
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
OG* e = static_cast<OG*>(JS_GetPrivate(args.thisv().toObjectOrNull()));
if (!e)
return false;
(static_cast<R*>(e)->*(funcptr))(*(ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface), args.rval());
return true;
}
}
template <typename T>
bool JSI_GUIProxy<T>::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<T*>(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<CStr, JS::Heap<JSObject*>>::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 <typename T>
bool JSI_GUIProxy<T>::set(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::HandleValue vp,
JS::HandleValue UNUSED(receiver), JS::ObjectOpResult& result) const
{
T* e = static_cast<T*>(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<typename T>
bool JSI_GUIProxy<T>::delete_(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::ObjectOpResult& result) const
{
T* e = static_cast<T*>(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);
}

View File

@ -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 <string>
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);
namespace {
struct SData
{
JS::PersistentRootedObject m_ToString;
JS::PersistentRootedObject m_Focus;
JS::PersistentRootedObject m_Blur;
JS::PersistentRootedObject m_GetComputedSize;
};
}
bool JSI_IGUIObject::getProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp)
template <>
bool JSI_GUIProxy<IGUIObject>::funcGetter(IGUIObject* elem, const std::string& propName, JS::MutableHandleValue vp) const
{
ScriptInterface* pScriptInterface = ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface;
ScriptRequest rq(*pScriptInterface);
IGUIObject* e = ScriptInterface::GetPrivate<IGUIObject>(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<CStr, JS::Heap<JSObject*>>::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());
const SData& data = *static_cast<const SData*>(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<const js::BaseProxyHandler*, void*> JSI_GUIProxy<IGUIObject>::CreateData(ScriptInterface& scriptInterface)
{
ScriptRequest rq(*ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface);
IGUIObject* e = ScriptInterface::GetPrivate<IGUIObject>(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);
SData* data = new SData();
ScriptRequest rq(scriptInterface);
#define func(class, func) &apply_to<IGUIObject, class, &class::func>
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::deleteProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::ObjectOpResult& result)
{
ScriptRequest rq(*ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface);
IGUIObject* e = ScriptInterface::GetPrivate<IGUIObject>(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<IGUIObject>(rq, args, &JSI_IGUIObject::JSI_class);
if (!e)
return false;
ScriptInterface::ToJSVal(rq, args.rval(), "[GUIObject: " + e->GetName() + "]");
return true;
}
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<IGUIObject>(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<IGUIObject>(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<IGUIObject>(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<IGUIObject>;

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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

View File

@ -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);

View File

@ -49,6 +49,7 @@
#include "js/Conversions.h"
#include "js/GCAPI.h"
#include "js/StructuredClone.h"
#include "js/Proxy.h"
#undef signbit