Changeset View
Changeset View
Standalone View
Standalone View
source/gui/Scripting/JSInterface_IGUIObject.cpp
/* Copyright (C) 2020 Wildfire Games. | /* Copyright (C) 2020 Wildfire Games. | ||||
Stan: 2020 | |||||
* This file is part of 0 A.D. | * This file is part of 0 A.D. | ||||
* | * | ||||
* 0 A.D. is free software: you can redistribute it and/or modify | * 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 | * it under the terms of the GNU General Public License as published by | ||||
* the Free Software Foundation, either version 2 of the License, or | * the Free Software Foundation, either version 2 of the License, or | ||||
* (at your option) any later version. | * (at your option) any later version. | ||||
* | * | ||||
* 0 A.D. is distributed in the hope that it will be useful, | * 0 A.D. is distributed in the hope that it will be useful, | ||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
* GNU General Public License for more details. | * GNU General Public License for more details. | ||||
* | * | ||||
* You should have received a copy of the GNU General Public License | * 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/>. | * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. | ||||
*/ | */ | ||||
#include "precompiled.h" | #include "precompiled.h" | ||||
#include "JSInterface_IGUIObject.h" | #include "JSInterface_IGUIObject.h" | ||||
#include <type_traits> | |||||
#include "gui/CGUI.h" | #include "gui/CGUI.h" | ||||
#include "gui/CGUISetting.h" | #include "gui/CGUISetting.h" | ||||
#include "gui/ObjectBases/IGUIObject.h" | #include "gui/ObjectBases/IGUIObject.h" | ||||
#include "gui/ObjectTypes/CText.h" | |||||
#include "scriptinterface/ScriptExtraHeaders.h" | #include "scriptinterface/ScriptExtraHeaders.h" | ||||
#include "scriptinterface/ScriptInterface.h" | #include "scriptinterface/ScriptInterface.h" | ||||
JSClass JSI_IGUIObject::JSI_class = { | /** | ||||
"GUIObject", JSCLASS_HAS_PRIVATE, | * Convenient struct to get info on a [const] function pointer. | ||||
nullptr, | */ | ||||
JSI_IGUIObject::deleteProperty, | template<typename ptr> | ||||
JSI_IGUIObject::getProperty, | struct args_info; | ||||
JSI_IGUIObject::setProperty, | |||||
nullptr, nullptr, nullptr, nullptr, | template<typename C, typename R, typename ...Types> | ||||
nullptr, nullptr, nullptr, nullptr | struct args_info<R(C::*)(Types ...)> | ||||
{ | |||||
static const size_t nb_args = sizeof...(Types); | |||||
using return_type = R; | |||||
using object_type = C; | |||||
using args = std::tuple<typename std::remove_const<typename std::remove_reference<Types>::type>::type...>; | |||||
Done Inline ActionsCome C++14, I think these all become: wraitii: Come C++14, I think these all become:
`SetupHandler<&CText::GetTextSize>(scriptInterface. | |||||
Not Done Inline ActionsYou should put this comment with the future code instead of the vague TODO ? Itms: You should put this comment with the future code instead of the vague TODO ? | |||||
}; | }; | ||||
JSFunctionSpec JSI_IGUIObject::JSI_methods[] = | // TODO: would be nice to find a way around the duplication here. | ||||
template<typename C, typename R, typename ...Types> | |||||
struct args_info<R(C::*)(Types ...) const> | |||||
{ | |||||
static const size_t nb_args = sizeof...(Types); | |||||
using return_type = R; | |||||
using object_type = C; | |||||
using args = std::tuple<typename std::remove_const<typename std::remove_reference<Types>::type>::type...>; | |||||
}; | |||||
// Convenience wrapper since the code is a little verbose. | |||||
// TODO: I think c++14 makes this clean enough, with type deduction, that it could be removed. | |||||
#define SetupHandler(funcPtr, JSName) \ | |||||
m_FunctionHandlers[JSName].init( \ | |||||
scriptInterface.GetContext(), \ | |||||
JS_NewFunction(scriptInterface.GetContext(), &(scriptMethod<decltype(funcPtr), funcPtr>), args_info<decltype(funcPtr)>::nb_args, 0, JSName) \ | |||||
); | |||||
JSI_GUI::GUIObjectFactory::GUIObjectFactory(ScriptInterface& scriptInterface) | |||||
{ | { | ||||
JS_FN("toString", JSI_IGUIObject::toString, 0, 0), | JSAutoRequest rq(scriptInterface.GetContext()); | ||||
JS_FN("focus", JSI_IGUIObject::focus, 0, 0), | SetupHandler(&IGUIObject::toString, "toString"); | ||||
JS_FN("blur", JSI_IGUIObject::blur, 0, 0), | SetupHandler(&IGUIObject::toString, "toSource"); | ||||
JS_FN("getComputedSize", JSI_IGUIObject::getComputedSize, 0, 0), | SetupHandler(&IGUIObject::focus, "focus"); | ||||
JS_FS_END | SetupHandler(&IGUIObject::blur, "blur"); | ||||
SetupHandler(&IGUIObject::getComputedSize, "getComputedSize"); | |||||
} | |||||
JSI_GUI::TextObjectFactory::TextObjectFactory(ScriptInterface& scriptInterface) : JSI_GUI::GUIObjectFactory(scriptInterface) | |||||
{ | |||||
JSAutoRequest rq(scriptInterface.GetContext()); | |||||
SetupHandler(&CText::GetTextSize, "getTextSize"); | |||||
} | |||||
#undef SetupHandler | |||||
Done Inline Actions#include <string> Stan: ```lang=cpp
#include <string>
``` | |||||
/** | |||||
* Based on https://stackoverflow.com/a/32223343 | |||||
* make_index_sequence is not defined in C++11... Only C++14. | |||||
*/ | |||||
template <size_t... Ints> | |||||
struct index_sequence | |||||
{ | |||||
using type = index_sequence; | |||||
using value_type = size_t; | |||||
static constexpr std::size_t size() noexcept { return sizeof...(Ints); } | |||||
}; | |||||
template <class Sequence1, class Sequence2> | |||||
struct _merge_and_renumber; | |||||
template <size_t... I1, size_t... I2> | |||||
struct _merge_and_renumber<index_sequence<I1...>, index_sequence<I2...>> | |||||
: index_sequence<I1..., (sizeof...(I1)+I2)...> { }; | |||||
template <size_t N> | |||||
struct make_index_sequence | |||||
: _merge_and_renumber<typename make_index_sequence<N/2>::type, | |||||
typename make_index_sequence<N - N/2>::type> { }; | |||||
template<> struct make_index_sequence<0> : index_sequence<> { }; | |||||
template<> struct make_index_sequence<1> : index_sequence<0> { }; | |||||
/** | |||||
* This series of templates is a setup to transparently call a c++ function | |||||
* from a JS function (with CallArgs) and returning that value. | |||||
* The C++ code can have arbitrary arguments and arbitrary return types, so long | |||||
* as they can be converted to/from JS. | |||||
*/ | |||||
/** | |||||
* This helper is a recursive template call that converts the N-1th argument | |||||
* of the function from a JS value to its proper C++ type. | |||||
*/ | |||||
template<int N, typename tuple> | |||||
struct convertFromJS | |||||
{ | |||||
bool operator()(ScriptInterface& interface, JSContext* cx, JS::CallArgs& val, tuple& outs) | |||||
{ | |||||
if (!interface.FromJSVal(cx, val[N-1], std::get<N-1>(outs))) | |||||
return false; | |||||
return convertFromJS<N-1, tuple>()(interface, cx, val, outs); | |||||
} | |||||
}; | }; | ||||
// Specialization for the base case "no arguments". | |||||
template<typename tuple> | |||||
struct convertFromJS<0, tuple> | |||||
{ | |||||
bool operator()(ScriptInterface& UNUSED(interface), JSContext* UNUSED(cx), JS::CallArgs& UNUSED(val), tuple& UNUSED(outs)) | |||||
{ | |||||
return true; | |||||
} | |||||
}; | |||||
/** | |||||
* These two templates take a function pointer, its arguments, call it, | |||||
* and set the return value of the CallArgs to whatever it returned, if anything. | |||||
* It's tag-dispatched for the "returns_void" and the regular return case. | |||||
*/ | |||||
template <typename funcPtr, funcPtr callable, typename T, size_t... Is, typename... types> | |||||
void call(T* object, ScriptInterface* scriptInterface, JS::CallArgs& callArgs, std::tuple<types...>& args, std::false_type, index_sequence<Is...>) | |||||
{ | |||||
// This is perfectly readable, what are you talking about. | |||||
auto ret = ((*object).* callable)(std::get<Is>(args)...); | |||||
scriptInterface->ToJSVal(scriptInterface->GetContext(), callArgs.rval(), ret); | |||||
} | |||||
template <typename funcPtr, funcPtr callable, typename T, size_t... Is, typename... types> | |||||
void call(T* object, ScriptInterface* UNUSED(scriptInterface), JS::CallArgs& UNUSED(callArgs), std::tuple<types...>& args, std::true_type, index_sequence<Is...>) | |||||
{ | |||||
// Void return specialization, just call the function. | |||||
((*object).* callable)(std::get<Is>(args)...); | |||||
} | |||||
template <typename funcPtr, funcPtr callable, typename T, size_t N = args_info<funcPtr>::nb_args, typename tuple = typename args_info<funcPtr>::args> | |||||
bool JSToCppCall(T* object, JSContext* cx, JS::CallArgs& args) | |||||
{ | |||||
ScriptInterface* scriptInterface = ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface; | |||||
// This is where the magic happens: instantiate a tuple to store the converted JS arguments, | |||||
// then 'unpack' the tuple to call the C++ function, convert & store the return value. | |||||
tuple outs; | |||||
if (!convertFromJS<N, tuple>()(*scriptInterface, cx, args, outs)) | |||||
return false; | |||||
// TODO: We have no failure handling here. It's non trivial in a generic sense since we may return a value. | |||||
// We could either try-catch and throw exceptions, | |||||
// or come C++17 return an std::optional/maybe or some kind of [bool, val] structured binding. | |||||
using returns_void = std::is_same<typename args_info<funcPtr>::return_type, void>; | |||||
call<funcPtr, callable, T>(object, scriptInterface, args, outs, returns_void{}, make_index_sequence<N>{}); | |||||
return true; | |||||
} | |||||
// TODO: this can get rewritten as <auto> and deduced come c++14 | |||||
template <typename funcPtr, funcPtr callable> | |||||
bool JSI_GUI::GUIObjectFactory::scriptMethod(JSContext* cx, unsigned argc, JS::Value* vp) | |||||
{ | |||||
JSAutoRequest rq(cx); | |||||
JS::CallArgs args = JS::CallArgsFromVp(argc, vp); | |||||
using cppType = typename args_info<funcPtr>::object_type; | |||||
cppType* thisObj = static_cast<cppType*>(JS_GetPrivate(args.thisv().toObjectOrNull())); | |||||
if (!thisObj) | |||||
return false; | |||||
if (!JSToCppCall<decltype(callable), callable>(thisObj, cx, args)) | |||||
return false; | |||||
void JSI_IGUIObject::RegisterScriptClass(ScriptInterface& scriptInterface) | return true; | ||||
} | |||||
js::Class JSI_GUI::GUIObjectFactory::m_ProxyObjectClass = \ | |||||
PROXY_CLASS_DEF("GUIObjectProxy", JSCLASS_HAS_PRIVATE | JSCLASS_HAS_CACHED_PROTO(JSProto_Proxy)); | |||||
JSObject* JSI_GUI::GUIObjectFactory::CreateObject(JSContext* cx) | |||||
{ | { | ||||
scriptInterface.DefineCustomObjectType(&JSI_class, nullptr, 0, nullptr, JSI_methods, nullptr, nullptr); | JSAutoRequest rq(cx); | ||||
js::ProxyOptions options; | |||||
options.setClass(&m_ProxyObjectClass); | |||||
JS::RootedObject proxy(cx, js::NewProxyObject(cx, &JSI_GUI::GUIProxy::singleton, JS::NullHandleValue, nullptr, options)); | |||||
return proxy; | |||||
} | } | ||||
bool JSI_IGUIObject::getProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp) | JSI_GUI::GUIProxy JSI_GUI::GUIProxy::singleton; | ||||
// The family can't be nullptr because that's used for some DOM object and it crashes. | |||||
JSI_GUI::GUIProxy::GUIProxy() : BaseProxyHandler(this, false, false) {}; | |||||
bool JSI_GUI::GUIProxy::get(JSContext* cx, JS::HandleObject proxy, JS::HandleValue UNUSED(receiver), JS::HandleId id, JS::MutableHandleValue vp) const | |||||
{ | { | ||||
JSAutoRequest rq(cx); | JSAutoRequest rq(cx); | ||||
ScriptInterface* pScriptInterface = ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface; | ScriptInterface* pScriptInterface = ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface; | ||||
IGUIObject* e = ScriptInterface::GetPrivate<IGUIObject>(cx, obj, &JSI_IGUIObject::JSI_class); | IGUIObject* e = static_cast<IGUIObject*>(JS_GetPrivate(proxy.get())); | ||||
if (!e) | if (!e) | ||||
return false; | return false; | ||||
JS::RootedValue idval(cx); | JS::RootedValue idval(cx); | ||||
if (!JS_IdToValue(cx, id, &idval)) | if (!JS_IdToValue(cx, id, &idval)) | ||||
return false; | return false; | ||||
std::string propName; | std::string propName; | ||||
if (!ScriptInterface::FromJSVal(cx, idval, propName)) | if (!ScriptInterface::FromJSVal(cx, idval, propName)) | ||||
return false; | return false; | ||||
// Skip registered functions and inherited properties | // TODO: This is slightly inefficient (we're going through the GUI via a string lookup). | ||||
// including JSInterfaces of derived classes | // We could do better by templating the proxy with function names to look for in the Factory. | ||||
if (propName == "constructor" || | // (the find can't fail) | ||||
propName == "prototype" || | const std::map<std::string, JS::PersistentRootedFunction>& factory = e->m_pGUI.m_ObjectTypes.find(e->GetObjectType())->second.guiObjectFactory->m_FunctionHandlers; | ||||
propName == "toString" || | std::map<std::string, JS::PersistentRootedFunction>::const_iterator it = factory.find(propName); | ||||
propName == "toJSON" || | if (it != factory.end()) | ||||
propName == "focus" || | { | ||||
propName == "blur" || | JSObject* obj = JS_GetFunctionObject(it->second.get()); | ||||
propName == "getTextSize" || | vp.setObjectOrNull(obj); | ||||
propName == "getComputedSize" | |||||
) | |||||
return true; | return true; | ||||
} | |||||
// Use onWhatever to access event handlers | // Use onWhatever to access event handlers | ||||
if (propName.substr(0, 2) == "on") | if (propName.substr(0, 2) == "on") | ||||
{ | { | ||||
CStr eventName(propName.substr(2)); | CStr eventName(propName.substr(2)); | ||||
std::map<CStr, JS::Heap<JSObject*>>::iterator it = e->m_ScriptHandlers.find(eventName); | std::map<CStr, JS::Heap<JSObject*>>::iterator it = e->m_ScriptHandlers.find(eventName); | ||||
if (it == e->m_ScriptHandlers.end()) | if (it == e->m_ScriptHandlers.end()) | ||||
vp.setNull(); | vp.setNull(); | ||||
Show All 28 Lines | else if (propName == "name") | ||||
return true; | return true; | ||||
} | } | ||||
else if (e->SettingExists(propName)) | else if (e->SettingExists(propName)) | ||||
{ | { | ||||
e->m_Settings[propName]->ToJSVal(cx, vp); | e->m_Settings[propName]->ToJSVal(cx, vp); | ||||
return true; | return true; | ||||
} | } | ||||
JS_ReportError(cx, "Property '%s' does not exist!", propName.c_str()); | LOGERROR("Property '%s' does not exist!", propName.c_str()); | ||||
return false; | return false; | ||||
} | } | ||||
bool JSI_IGUIObject::setProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp, JS::ObjectOpResult& result) | bool JSI_GUI::GUIProxy::set(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::HandleValue vp, | ||||
JS::HandleValue UNUSED(receiver), JS::ObjectOpResult& result) const | |||||
{ | { | ||||
IGUIObject* e = ScriptInterface::GetPrivate<IGUIObject>(cx, obj, &JSI_IGUIObject::JSI_class); | JSAutoRequest rq(cx); | ||||
Not Done Inline ActionsI think you should add a new static method to ScriptInterface for the new use. The new calls to the proxy private and the static casts are a bit ugly. Itms: I think you should add a new static method to ScriptInterface for the new use. The new calls to… | |||||
IGUIObject* e = static_cast<IGUIObject*>(JS_GetPrivate(proxy.get())); | |||||
if (!e) | if (!e) | ||||
return result.fail(JSMSG_NOT_NONNULL_OBJECT); | return result.fail(JSMSG_NOT_NONNULL_OBJECT); | ||||
JSAutoRequest rq(cx); | |||||
JS::RootedValue idval(cx); | JS::RootedValue idval(cx); | ||||
if (!JS_IdToValue(cx, id, &idval)) | if (!JS_IdToValue(cx, id, &idval)) | ||||
return result.fail(JSMSG_NOT_NONNULL_OBJECT); | return result.fail(JSMSG_NOT_NONNULL_OBJECT); | ||||
std::string propName; | std::string propName; | ||||
if (!ScriptInterface::FromJSVal(cx, idval, propName)) | if (!ScriptInterface::FromJSVal(cx, idval, propName)) | ||||
return result.fail(JSMSG_UNDEFINED_PROP); | return result.fail(JSMSG_UNDEFINED_PROP); | ||||
Show All 10 Lines | bool JSI_GUI::GUIProxy::set(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::HandleValue vp, | ||||
if (vp.isObject()) | if (vp.isObject()) | ||||
vpObj = &vp.toObject(); | vpObj = &vp.toObject(); | ||||
// Use onWhatever to set event handlers | // Use onWhatever to set event handlers | ||||
if (propName.substr(0, 2) == "on") | if (propName.substr(0, 2) == "on") | ||||
{ | { | ||||
if (vp.isPrimitive() || vp.isNull() || !JS_ObjectIsFunction(cx, &vp.toObject())) | if (vp.isPrimitive() || vp.isNull() || !JS_ObjectIsFunction(cx, &vp.toObject())) | ||||
{ | { | ||||
JS_ReportError(cx, "on- event-handlers must be functions"); | LOGERROR("on- event-handlers must be functions"); | ||||
return result.fail(JSMSG_NOT_FUNCTION); | return result.fail(JSMSG_NOT_FUNCTION); | ||||
} | } | ||||
CStr eventName(propName.substr(2)); | CStr eventName(propName.substr(2)); | ||||
e->SetScriptHandler(eventName, vpObj); | e->SetScriptHandler(eventName, vpObj); | ||||
return result.succeed(); | return result.succeed(); | ||||
} | } | ||||
if (e->SettingExists(propName)) | if (e->SettingExists(propName)) | ||||
return e->m_Settings[propName]->FromJSVal(cx, vp, true) ? result.succeed() : result.fail(JSMSG_TYPE_ERR_BAD_ARGS); | return e->m_Settings[propName]->FromJSVal(cx, vp, true) ? result.succeed() : result.fail(JSMSG_TYPE_ERR_BAD_ARGS); | ||||
JS_ReportError(cx, "Property '%s' does not exist!", propName.c_str()); | LOGERROR("Property '%s' does not exist!", propName.c_str()); | ||||
return result.fail(JSMSG_UNDEFINED_PROP); | return result.fail(JSMSG_UNDEFINED_PROP); | ||||
} | } | ||||
bool JSI_IGUIObject::deleteProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::ObjectOpResult& result) | bool JSI_GUI::GUIProxy::delete_(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::ObjectOpResult& result) const | ||||
{ | { | ||||
IGUIObject* e = ScriptInterface::GetPrivate<IGUIObject>(cx, obj, &JSI_IGUIObject::JSI_class); | JSAutoRequest rq(cx); | ||||
IGUIObject* e = static_cast<IGUIObject*>(JS_GetPrivate(proxy.get())); | |||||
if (!e) | if (!e) | ||||
return result.fail(JSMSG_NOT_NONNULL_OBJECT); | return result.fail(JSMSG_NOT_NONNULL_OBJECT); | ||||
JSAutoRequest rq(cx); | |||||
JS::RootedValue idval(cx); | JS::RootedValue idval(cx); | ||||
if (!JS_IdToValue(cx, id, &idval)) | if (!JS_IdToValue(cx, id, &idval)) | ||||
return result.fail(JSMSG_NOT_NONNULL_OBJECT); | return result.fail(JSMSG_NOT_NONNULL_OBJECT); | ||||
std::string propName; | std::string propName; | ||||
if (!ScriptInterface::FromJSVal(cx, idval, propName)) | if (!ScriptInterface::FromJSVal(cx, idval, propName)) | ||||
return result.fail(JSMSG_UNDEFINED_PROP); | return result.fail(JSMSG_UNDEFINED_PROP); | ||||
// event handlers | // event handlers | ||||
if (propName.substr(0, 2) == "on") | if (propName.substr(0, 2) == "on") | ||||
{ | { | ||||
CStr eventName(propName.substr(2)); | CStr eventName(propName.substr(2)); | ||||
e->UnsetScriptHandler(eventName); | e->UnsetScriptHandler(eventName); | ||||
return result.succeed(); | return result.succeed(); | ||||
} | } | ||||
JS_ReportError(cx, "Only event handlers can be deleted from GUI objects!"); | LOGERROR("Only event handlers can be deleted from GUI objects! (trying to delete %s)", propName.c_str()); | ||||
return result.fail(JSMSG_UNDEFINED_PROP); | return result.fail(JSMSG_UNDEFINED_PROP); | ||||
} | } | ||||
bool JSI_IGUIObject::toString(JSContext* cx, uint argc, JS::Value* vp) | |||||
{ | |||||
// No JSAutoRequest needed for these calls | |||||
JS::CallArgs args = JS::CallArgsFromVp(argc, vp); | |||||
IGUIObject* e = ScriptInterface::GetPrivate<IGUIObject>(cx, args, &JSI_IGUIObject::JSI_class); | |||||
if (!e) | |||||
return false; | |||||
ScriptInterface::ToJSVal(cx, args.rval(), "[GUIObject: " + e->GetName() + "]"); | |||||
return true; | |||||
} | |||||
bool JSI_IGUIObject::focus(JSContext* cx, uint argc, JS::Value* vp) | |||||
{ | |||||
// No JSAutoRequest needed for these calls | |||||
JS::CallArgs args = JS::CallArgsFromVp(argc, vp); | |||||
IGUIObject* e = ScriptInterface::GetPrivate<IGUIObject>(cx, 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) | |||||
{ | |||||
// No JSAutoRequest needed for these calls | |||||
JS::CallArgs args = JS::CallArgsFromVp(argc, vp); | |||||
IGUIObject* e = ScriptInterface::GetPrivate<IGUIObject>(cx, 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) | |||||
{ | |||||
JSAutoRequest rq(cx); | |||||
JS::CallArgs args = JS::CallArgsFromVp(argc, vp); | |||||
IGUIObject* e = ScriptInterface::GetPrivate<IGUIObject>(cx, args, &JSI_IGUIObject::JSI_class); | |||||
if (!e) | |||||
return false; | |||||
e->UpdateCachedSize(); | |||||
ScriptInterface::ToJSVal(cx, args.rval(), e->m_CachedActualSize); | |||||
return true; | |||||
} |
Wildfire Games · Phabricator
2020