mirror of
https://gitea.wildfiregames.com/0ad/0ad.git
synced 2026-06-27 12:05:07 +00:00
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:
@@ -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>;
|
||||
|
||||
Reference in New Issue
Block a user