Changeset View
Changeset View
Standalone View
Standalone View
ps/trunk/source/scriptinterface/ScriptInterface.cpp
Show First 20 Lines • Show All 56 Lines • ▼ Show 20 Lines | struct ScriptInterface_impl | ||||
ScriptInterface_impl(const char* nativeScopeName, const shared_ptr<ScriptRuntime>& runtime); | ScriptInterface_impl(const char* nativeScopeName, const shared_ptr<ScriptRuntime>& runtime); | ||||
~ScriptInterface_impl(); | ~ScriptInterface_impl(); | ||||
void Register(const char* name, JSNative fptr, uint nargs) const; | void Register(const char* name, JSNative fptr, uint nargs) const; | ||||
// Take care to keep this declaration before heap rooted members. Destructors of heap rooted | // Take care to keep this declaration before heap rooted members. Destructors of heap rooted | ||||
// members have to be called before the runtime destructor. | // members have to be called before the runtime destructor. | ||||
shared_ptr<ScriptRuntime> m_runtime; | shared_ptr<ScriptRuntime> m_runtime; | ||||
JSContext* m_cx; | |||||
JS::PersistentRootedObject m_glob; // global scope object | JS::PersistentRootedObject m_glob; // global scope object | ||||
JSCompartment* m_formerCompartment; | |||||
boost::rand48* m_rng; | boost::rand48* m_rng; | ||||
JS::PersistentRootedObject m_nativeScope; // native function scope object | JS::PersistentRootedObject m_nativeScope; // native function scope object | ||||
friend ScriptInterface::Request; | |||||
private: | |||||
JSContext* m_cx; | |||||
JSCompartment* m_formerCompartment; | |||||
}; | }; | ||||
ScriptInterface::Request::Request(const ScriptInterface& scriptInterface) : | |||||
cx(scriptInterface.m->m_cx) | |||||
{ | |||||
JS_BeginRequest(cx); | |||||
} | |||||
ScriptInterface::Request::~Request() | |||||
{ | |||||
JS_EndRequest(cx); | |||||
} | |||||
namespace | namespace | ||||
{ | { | ||||
JSClass global_class = { | JSClass global_class = { | ||||
"global", JSCLASS_GLOBAL_FLAGS, | "global", JSCLASS_GLOBAL_FLAGS, | ||||
nullptr, nullptr, | nullptr, nullptr, | ||||
nullptr, nullptr, | nullptr, nullptr, | ||||
nullptr, nullptr, nullptr, | nullptr, nullptr, nullptr, | ||||
nullptr, nullptr, nullptr, nullptr, | nullptr, nullptr, nullptr, nullptr, | ||||
JS_GlobalObjectTraceHook | JS_GlobalObjectTraceHook | ||||
}; | }; | ||||
void ErrorReporter(JSContext* cx, const char* message, JSErrorReport* report) | void ErrorReporter(JSContext* cx, const char* message, JSErrorReport* report) | ||||
{ | { | ||||
JSAutoRequest rq(cx); | ScriptInterface::Request rq(*ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface); | ||||
std::stringstream msg; | std::stringstream msg; | ||||
bool isWarning = JSREPORT_IS_WARNING(report->flags); | bool isWarning = JSREPORT_IS_WARNING(report->flags); | ||||
msg << (isWarning ? "JavaScript warning: " : "JavaScript error: "); | msg << (isWarning ? "JavaScript warning: " : "JavaScript error: "); | ||||
if (report->filename) | if (report->filename) | ||||
{ | { | ||||
msg << report->filename; | msg << report->filename; | ||||
msg << " line " << report->lineno << "\n"; | msg << " line " << report->lineno << "\n"; | ||||
} | } | ||||
msg << message; | msg << message; | ||||
// If there is an exception, then print its stack trace | // If there is an exception, then print its stack trace | ||||
JS::RootedValue excn(cx); | JS::RootedValue excn(cx); | ||||
if (JS_GetPendingException(cx, &excn) && excn.isObject()) | if (JS_GetPendingException(cx, &excn) && excn.isObject()) | ||||
{ | { | ||||
JS::RootedValue stackVal(cx); | JS::RootedValue stackVal(cx); | ||||
JS::RootedObject excnObj(cx, &excn.toObject()); | JS::RootedObject excnObj(cx, &excn.toObject()); | ||||
JS_GetProperty(cx, excnObj, "stack", &stackVal); | JS_GetProperty(cx, excnObj, "stack", &stackVal); | ||||
std::string stackText; | std::string stackText; | ||||
ScriptInterface::FromJSVal(cx, stackVal, stackText); | ScriptInterface::FromJSVal(rq, stackVal, stackText); | ||||
std::istringstream stream(stackText); | std::istringstream stream(stackText); | ||||
for (std::string line; std::getline(stream, line);) | for (std::string line; std::getline(stream, line);) | ||||
msg << "\n " << line; | msg << "\n " << line; | ||||
} | } | ||||
if (isWarning) | if (isWarning) | ||||
LOGWARNING("%s", msg.str().c_str()); | LOGWARNING("%s", msg.str().c_str()); | ||||
else | else | ||||
LOGERROR("%s", msg.str().c_str()); | LOGERROR("%s", msg.str().c_str()); | ||||
// When running under Valgrind, print more information in the error message | // When running under Valgrind, print more information in the error message | ||||
// VALGRIND_PRINTF_BACKTRACE("->"); | // VALGRIND_PRINTF_BACKTRACE("->"); | ||||
} | } | ||||
// Functions in the global namespace: | // Functions in the global namespace: | ||||
bool print(JSContext* cx, uint argc, JS::Value* vp) | bool print(JSContext* cx, uint argc, JS::Value* vp) | ||||
{ | { | ||||
JS::CallArgs args = JS::CallArgsFromVp(argc, vp); | JS::CallArgs args = JS::CallArgsFromVp(argc, vp); | ||||
ScriptInterface::Request rq(*ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface); \ | |||||
for (uint i = 0; i < args.length(); ++i) | for (uint i = 0; i < args.length(); ++i) | ||||
{ | { | ||||
std::wstring str; | std::wstring str; | ||||
if (!ScriptInterface::FromJSVal(cx, args[i], str)) | if (!ScriptInterface::FromJSVal(rq, args[i], str)) | ||||
return false; | return false; | ||||
debug_printf("%s", utf8_from_wstring(str).c_str()); | debug_printf("%s", utf8_from_wstring(str).c_str()); | ||||
} | } | ||||
fflush(stdout); | fflush(stdout); | ||||
args.rval().setUndefined(); | args.rval().setUndefined(); | ||||
return true; | return true; | ||||
} | } | ||||
bool logmsg(JSContext* cx, uint argc, JS::Value* vp) | bool logmsg(JSContext* cx, uint argc, JS::Value* vp) | ||||
{ | { | ||||
JS::CallArgs args = JS::CallArgsFromVp(argc, vp); | JS::CallArgs args = JS::CallArgsFromVp(argc, vp); | ||||
if (args.length() < 1) | if (args.length() < 1) | ||||
{ | { | ||||
args.rval().setUndefined(); | args.rval().setUndefined(); | ||||
return true; | return true; | ||||
} | } | ||||
ScriptInterface::Request rq(*ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface); \ | |||||
std::wstring str; | std::wstring str; | ||||
if (!ScriptInterface::FromJSVal(cx, args[0], str)) | if (!ScriptInterface::FromJSVal(rq, args[0], str)) | ||||
return false; | return false; | ||||
LOGMESSAGE("%s", utf8_from_wstring(str)); | LOGMESSAGE("%s", utf8_from_wstring(str)); | ||||
args.rval().setUndefined(); | args.rval().setUndefined(); | ||||
return true; | return true; | ||||
} | } | ||||
bool warn(JSContext* cx, uint argc, JS::Value* vp) | bool warn(JSContext* cx, uint argc, JS::Value* vp) | ||||
{ | { | ||||
JS::CallArgs args = JS::CallArgsFromVp(argc, vp); | JS::CallArgs args = JS::CallArgsFromVp(argc, vp); | ||||
if (args.length() < 1) | if (args.length() < 1) | ||||
{ | { | ||||
args.rval().setUndefined(); | args.rval().setUndefined(); | ||||
return true; | return true; | ||||
} | } | ||||
ScriptInterface::Request rq(*ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface); \ | |||||
std::wstring str; | std::wstring str; | ||||
if (!ScriptInterface::FromJSVal(cx, args[0], str)) | if (!ScriptInterface::FromJSVal(rq, args[0], str)) | ||||
return false; | return false; | ||||
LOGWARNING("%s", utf8_from_wstring(str)); | LOGWARNING("%s", utf8_from_wstring(str)); | ||||
args.rval().setUndefined(); | args.rval().setUndefined(); | ||||
return true; | return true; | ||||
} | } | ||||
bool error(JSContext* cx, uint argc, JS::Value* vp) | bool error(JSContext* cx, uint argc, JS::Value* vp) | ||||
{ | { | ||||
JS::CallArgs args = JS::CallArgsFromVp(argc, vp); | JS::CallArgs args = JS::CallArgsFromVp(argc, vp); | ||||
if (args.length() < 1) | if (args.length() < 1) | ||||
{ | { | ||||
args.rval().setUndefined(); | args.rval().setUndefined(); | ||||
return true; | return true; | ||||
} | } | ||||
ScriptInterface::Request rq(*ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface); \ | |||||
std::wstring str; | std::wstring str; | ||||
if (!ScriptInterface::FromJSVal(cx, args[0], str)) | if (!ScriptInterface::FromJSVal(rq, args[0], str)) | ||||
return false; | return false; | ||||
LOGERROR("%s", utf8_from_wstring(str)); | LOGERROR("%s", utf8_from_wstring(str)); | ||||
args.rval().setUndefined(); | args.rval().setUndefined(); | ||||
return true; | return true; | ||||
} | } | ||||
bool deepcopy(JSContext* cx, uint argc, JS::Value* vp) | bool deepcopy(JSContext* cx, uint argc, JS::Value* vp) | ||||
{ | { | ||||
JSAutoRequest rq(cx); | |||||
JS::CallArgs args = JS::CallArgsFromVp(argc, vp); | JS::CallArgs args = JS::CallArgsFromVp(argc, vp); | ||||
if (args.length() < 1) | if (args.length() < 1) | ||||
{ | { | ||||
args.rval().setUndefined(); | args.rval().setUndefined(); | ||||
return true; | return true; | ||||
} | } | ||||
ScriptInterface::Request rq(*ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface); \ | |||||
JS::RootedValue ret(cx); | JS::RootedValue ret(cx); | ||||
if (!JS_StructuredClone(cx, args[0], &ret, NULL, NULL)) | if (!JS_StructuredClone(rq.cx, args[0], &ret, NULL, NULL)) | ||||
return false; | return false; | ||||
args.rval().set(ret); | args.rval().set(ret); | ||||
return true; | return true; | ||||
} | } | ||||
bool deepfreeze(JSContext* cx, uint argc, JS::Value* vp) | bool deepfreeze(JSContext* cx, uint argc, JS::Value* vp) | ||||
{ | { | ||||
JS::CallArgs args = JS::CallArgsFromVp(argc, vp); | JS::CallArgs args = JS::CallArgsFromVp(argc, vp); | ||||
ScriptInterface::Request rq(*ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface); \ | |||||
if (args.length() != 1 || !args.get(0).isObject()) | if (args.length() != 1 || !args.get(0).isObject()) | ||||
{ | { | ||||
JSAutoRequest rq(cx); | |||||
JS_ReportError(cx, "deepfreeze requires exactly one object as an argument."); | JS_ReportError(cx, "deepfreeze requires exactly one object as an argument."); | ||||
return false; | return false; | ||||
} | } | ||||
ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface->FreezeObject(args.get(0), true); | ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface->FreezeObject(args.get(0), true); | ||||
args.rval().set(args.get(0)); | args.rval().set(args.get(0)); | ||||
return true; | return true; | ||||
} | } | ||||
bool ProfileStart(JSContext* cx, uint argc, JS::Value* vp) | bool ProfileStart(JSContext* cx, uint argc, JS::Value* vp) | ||||
{ | { | ||||
const char* name = "(ProfileStart)"; | const char* name = "(ProfileStart)"; | ||||
JS::CallArgs args = JS::CallArgsFromVp(argc, vp); | JS::CallArgs args = JS::CallArgsFromVp(argc, vp); | ||||
ScriptInterface::Request rq(*ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface); \ | |||||
if (args.length() >= 1) | if (args.length() >= 1) | ||||
{ | { | ||||
std::string str; | std::string str; | ||||
if (!ScriptInterface::FromJSVal(cx, args[0], str)) | if (!ScriptInterface::FromJSVal(rq, args[0], str)) | ||||
return false; | return false; | ||||
typedef boost::flyweight< | typedef boost::flyweight< | ||||
std::string, | std::string, | ||||
boost::flyweights::no_tracking, | boost::flyweights::no_tracking, | ||||
boost::flyweights::no_locking | boost::flyweights::no_locking | ||||
> StringFlyweight; | > StringFlyweight; | ||||
Show All 21 Lines | bool ProfileStop(JSContext* UNUSED(cx), uint argc, JS::Value* vp) | ||||
return true; | return true; | ||||
} | } | ||||
bool ProfileAttribute(JSContext* cx, uint argc, JS::Value* vp) | bool ProfileAttribute(JSContext* cx, uint argc, JS::Value* vp) | ||||
{ | { | ||||
const char* name = "(ProfileAttribute)"; | const char* name = "(ProfileAttribute)"; | ||||
JS::CallArgs args = JS::CallArgsFromVp(argc, vp); | JS::CallArgs args = JS::CallArgsFromVp(argc, vp); | ||||
ScriptInterface::Request rq(*ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface); \ | |||||
if (args.length() >= 1) | if (args.length() >= 1) | ||||
{ | { | ||||
std::string str; | std::string str; | ||||
if (!ScriptInterface::FromJSVal(cx, args[0], str)) | if (!ScriptInterface::FromJSVal(rq, args[0], str)) | ||||
return false; | return false; | ||||
typedef boost::flyweight< | typedef boost::flyweight< | ||||
std::string, | std::string, | ||||
boost::flyweights::no_tracking, | boost::flyweights::no_tracking, | ||||
boost::flyweights::no_locking | boost::flyweights::no_locking | ||||
> StringFlyweight; | > StringFlyweight; | ||||
▲ Show 20 Lines • Show All 119 Lines • ▼ Show 20 Lines | |||||
{ | { | ||||
// Profiler stats table isn't thread-safe, so only enable this on the main thread | // Profiler stats table isn't thread-safe, so only enable this on the main thread | ||||
if (ThreadUtil::IsMainThread()) | if (ThreadUtil::IsMainThread()) | ||||
{ | { | ||||
if (g_ScriptStatsTable) | if (g_ScriptStatsTable) | ||||
g_ScriptStatsTable->Add(this, debugName); | g_ScriptStatsTable->Add(this, debugName); | ||||
} | } | ||||
Request rq(this); | |||||
m_CxPrivate.pScriptInterface = this; | m_CxPrivate.pScriptInterface = this; | ||||
JS_SetContextPrivate(m->m_cx, (void*)&m_CxPrivate); | JS_SetContextPrivate(rq.cx, (void*)&m_CxPrivate); | ||||
} | } | ||||
ScriptInterface::~ScriptInterface() | ScriptInterface::~ScriptInterface() | ||||
{ | { | ||||
if (ThreadUtil::IsMainThread()) | if (ThreadUtil::IsMainThread()) | ||||
{ | { | ||||
if (g_ScriptStatsTable) | if (g_ScriptStatsTable) | ||||
g_ScriptStatsTable->Remove(this); | g_ScriptStatsTable->Remove(this); | ||||
Show All 28 Lines | if (!LoadGlobalScriptFile(path)) | ||||
return false; | return false; | ||||
} | } | ||||
return true; | return true; | ||||
} | } | ||||
bool ScriptInterface::ReplaceNondeterministicRNG(boost::rand48& rng) | bool ScriptInterface::ReplaceNondeterministicRNG(boost::rand48& rng) | ||||
{ | { | ||||
JSAutoRequest rq(m->m_cx); | Request rq(this); | ||||
JS::RootedValue math(m->m_cx); | JS::RootedValue math(rq.cx); | ||||
JS::RootedObject global(m->m_cx, m->m_glob); | JS::RootedObject global(rq.cx, m->m_glob); | ||||
if (JS_GetProperty(m->m_cx, global, "Math", &math) && math.isObject()) | if (JS_GetProperty(rq.cx, global, "Math", &math) && math.isObject()) | ||||
{ | { | ||||
JS::RootedObject mathObj(m->m_cx, &math.toObject()); | JS::RootedObject mathObj(rq.cx, &math.toObject()); | ||||
JS::RootedFunction random(m->m_cx, JS_DefineFunction(m->m_cx, mathObj, "random", Math_random, 0, | JS::RootedFunction random(rq.cx, JS_DefineFunction(rq.cx, mathObj, "random", Math_random, 0, | ||||
JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)); | JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)); | ||||
if (random) | if (random) | ||||
{ | { | ||||
m->m_rng = &rng; | m->m_rng = &rng; | ||||
return true; | return true; | ||||
} | } | ||||
} | } | ||||
LOGERROR("ReplaceNondeterministicRNG: failed to replace Math.random"); | LOGERROR("ReplaceNondeterministicRNG: failed to replace Math.random"); | ||||
return false; | return false; | ||||
} | } | ||||
void ScriptInterface::Register(const char* name, JSNative fptr, size_t nargs) const | void ScriptInterface::Register(const char* name, JSNative fptr, size_t nargs) const | ||||
{ | { | ||||
m->Register(name, fptr, (uint)nargs); | m->Register(name, fptr, (uint)nargs); | ||||
} | } | ||||
JSContext* ScriptInterface::GetContext() const | |||||
{ | |||||
return m->m_cx; | |||||
} | |||||
JSRuntime* ScriptInterface::GetJSRuntime() const | JSRuntime* ScriptInterface::GetJSRuntime() const | ||||
{ | { | ||||
return m->m_runtime->m_rt; | return m->m_runtime->m_rt; | ||||
} | } | ||||
shared_ptr<ScriptRuntime> ScriptInterface::GetRuntime() const | shared_ptr<ScriptRuntime> ScriptInterface::GetRuntime() const | ||||
{ | { | ||||
return m->m_runtime; | return m->m_runtime; | ||||
} | } | ||||
void ScriptInterface::CallConstructor(JS::HandleValue ctor, JS::HandleValueArray argv, JS::MutableHandleValue out) const | void ScriptInterface::CallConstructor(JS::HandleValue ctor, JS::HandleValueArray argv, JS::MutableHandleValue out) const | ||||
{ | { | ||||
JSAutoRequest rq(m->m_cx); | Request rq(this); | ||||
if (!ctor.isObject()) | if (!ctor.isObject()) | ||||
{ | { | ||||
LOGERROR("CallConstructor: ctor is not an object"); | LOGERROR("CallConstructor: ctor is not an object"); | ||||
out.setNull(); | out.setNull(); | ||||
return; | return; | ||||
} | } | ||||
JS::RootedObject ctorObj(m->m_cx, &ctor.toObject()); | JS::RootedObject ctorObj(rq.cx, &ctor.toObject()); | ||||
out.setObjectOrNull(JS_New(m->m_cx, ctorObj, argv)); | out.setObjectOrNull(JS_New(rq.cx, ctorObj, argv)); | ||||
} | } | ||||
void ScriptInterface::DefineCustomObjectType(JSClass *clasp, JSNative constructor, uint minArgs, JSPropertySpec *ps, JSFunctionSpec *fs, JSPropertySpec *static_ps, JSFunctionSpec *static_fs) | void ScriptInterface::DefineCustomObjectType(JSClass *clasp, JSNative constructor, uint minArgs, JSPropertySpec *ps, JSFunctionSpec *fs, JSPropertySpec *static_ps, JSFunctionSpec *static_fs) | ||||
{ | { | ||||
JSAutoRequest rq(m->m_cx); | Request rq(this); | ||||
std::string typeName = clasp->name; | std::string typeName = clasp->name; | ||||
if (m_CustomObjectTypes.find(typeName) != m_CustomObjectTypes.end()) | if (m_CustomObjectTypes.find(typeName) != m_CustomObjectTypes.end()) | ||||
{ | { | ||||
// This type already exists | // This type already exists | ||||
throw PSERROR_Scripting_DefineType_AlreadyExists(); | throw PSERROR_Scripting_DefineType_AlreadyExists(); | ||||
} | } | ||||
JS::RootedObject global(m->m_cx, m->m_glob); | JS::RootedObject global(rq.cx, m->m_glob); | ||||
JS::RootedObject obj(m->m_cx, JS_InitClass(m->m_cx, global, nullptr, | JS::RootedObject obj(rq.cx, JS_InitClass(rq.cx, global, nullptr, | ||||
clasp, | clasp, | ||||
constructor, minArgs, // Constructor, min args | constructor, minArgs, // Constructor, min args | ||||
ps, fs, // Properties, methods | ps, fs, // Properties, methods | ||||
static_ps, static_fs)); // Constructor properties, methods | static_ps, static_fs)); // Constructor properties, methods | ||||
if (obj == NULL) | if (obj == NULL) | ||||
throw PSERROR_Scripting_DefineType_CreationFailed(); | throw PSERROR_Scripting_DefineType_CreationFailed(); | ||||
CustomType& type = m_CustomObjectTypes[typeName]; | CustomType& type = m_CustomObjectTypes[typeName]; | ||||
type.m_Prototype.init(m->m_cx, obj); | type.m_Prototype.init(rq.cx, obj); | ||||
type.m_Class = clasp; | type.m_Class = clasp; | ||||
type.m_Constructor = constructor; | type.m_Constructor = constructor; | ||||
} | } | ||||
JSObject* ScriptInterface::CreateCustomObject(const std::string& typeName) const | JSObject* ScriptInterface::CreateCustomObject(const std::string& typeName) const | ||||
{ | { | ||||
std::map<std::string, CustomType>::const_iterator it = m_CustomObjectTypes.find(typeName); | std::map<std::string, CustomType>::const_iterator it = m_CustomObjectTypes.find(typeName); | ||||
if (it == m_CustomObjectTypes.end()) | if (it == m_CustomObjectTypes.end()) | ||||
throw PSERROR_Scripting_TypeDoesNotExist(); | throw PSERROR_Scripting_TypeDoesNotExist(); | ||||
JS::RootedObject prototype(m->m_cx, it->second.m_Prototype.get()); | Request rq(this); | ||||
return JS_NewObjectWithGivenProto(m->m_cx, it->second.m_Class, prototype); | JS::RootedObject prototype(rq.cx, it->second.m_Prototype.get()); | ||||
return JS_NewObjectWithGivenProto(rq.cx, it->second.m_Class, prototype); | |||||
} | } | ||||
bool ScriptInterface::CallFunction_(JS::HandleValue val, const char* name, JS::HandleValueArray argv, JS::MutableHandleValue ret) const | bool ScriptInterface::CallFunction_(JS::HandleValue val, const char* name, JS::HandleValueArray argv, JS::MutableHandleValue ret) const | ||||
{ | { | ||||
JSAutoRequest rq(m->m_cx); | Request rq(this); | ||||
JS::RootedObject obj(m->m_cx); | JS::RootedObject obj(rq.cx); | ||||
if (!JS_ValueToObject(m->m_cx, val, &obj) || !obj) | if (!JS_ValueToObject(rq.cx, val, &obj) || !obj) | ||||
return false; | return false; | ||||
// Check that the named function actually exists, to avoid ugly JS error reports | // Check that the named function actually exists, to avoid ugly JS error reports | ||||
// when calling an undefined value | // when calling an undefined value | ||||
bool found; | bool found; | ||||
if (!JS_HasProperty(m->m_cx, obj, name, &found) || !found) | if (!JS_HasProperty(rq.cx, obj, name, &found) || !found) | ||||
return false; | return false; | ||||
return JS_CallFunctionName(m->m_cx, obj, name, argv, ret); | return JS_CallFunctionName(rq.cx, obj, name, argv, ret); | ||||
} | } | ||||
bool ScriptInterface::CreateObject_(JSContext* cx, JS::MutableHandleObject object) | bool ScriptInterface::CreateObject_(const Request& rq, JS::MutableHandleObject object) | ||||
{ | { | ||||
// JSAutoRequest is the responsibility of the caller | object.set(JS_NewPlainObject(rq.cx)); | ||||
object.set(JS_NewPlainObject(cx)); | |||||
if (!object) | if (!object) | ||||
throw PSERROR_Scripting_CreateObjectFailed(); | throw PSERROR_Scripting_CreateObjectFailed(); | ||||
return true; | return true; | ||||
} | } | ||||
void ScriptInterface::CreateArray(JSContext* cx, JS::MutableHandleValue objectValue, size_t length) | void ScriptInterface::CreateArray(const Request& rq, JS::MutableHandleValue objectValue, size_t length) | ||||
{ | { | ||||
JSAutoRequest rq(cx); | objectValue.setObjectOrNull(JS_NewArrayObject(rq.cx, length)); | ||||
objectValue.setObjectOrNull(JS_NewArrayObject(cx, length)); | |||||
if (!objectValue.isObject()) | if (!objectValue.isObject()) | ||||
throw PSERROR_Scripting_CreateObjectFailed(); | throw PSERROR_Scripting_CreateObjectFailed(); | ||||
} | } | ||||
JS::Value ScriptInterface::GetGlobalObject() const | JS::Value ScriptInterface::GetGlobalObject() const | ||||
{ | { | ||||
JSAutoRequest rq(m->m_cx); | Request rq(this); | ||||
return JS::ObjectValue(*JS::CurrentGlobalOrNull(m->m_cx)); | return JS::ObjectValue(*JS::CurrentGlobalOrNull(rq.cx)); | ||||
} | } | ||||
bool ScriptInterface::SetGlobal_(const char* name, JS::HandleValue value, bool replace, bool constant, bool enumerate) | bool ScriptInterface::SetGlobal_(const char* name, JS::HandleValue value, bool replace, bool constant, bool enumerate) | ||||
{ | { | ||||
JSAutoRequest rq(m->m_cx); | Request rq(this); | ||||
JS::RootedObject global(m->m_cx, m->m_glob); | JS::RootedObject global(rq.cx, m->m_glob); | ||||
bool found; | bool found; | ||||
if (!JS_HasProperty(m->m_cx, global, name, &found)) | if (!JS_HasProperty(rq.cx, global, name, &found)) | ||||
return false; | return false; | ||||
if (found) | if (found) | ||||
{ | { | ||||
JS::Rooted<JSPropertyDescriptor> desc(m->m_cx); | JS::Rooted<JSPropertyDescriptor> desc(rq.cx); | ||||
if (!JS_GetOwnPropertyDescriptor(m->m_cx, global, name, &desc)) | if (!JS_GetOwnPropertyDescriptor(rq.cx, global, name, &desc)) | ||||
return false; | return false; | ||||
if (!desc.writable()) | if (!desc.writable()) | ||||
{ | { | ||||
if (!replace) | if (!replace) | ||||
{ | { | ||||
JS_ReportError(m->m_cx, "SetGlobal \"%s\" called multiple times", name); | JS_ReportError(rq.cx, "SetGlobal \"%s\" called multiple times", name); | ||||
return false; | return false; | ||||
} | } | ||||
// This is not supposed to happen, unless the user has called SetProperty with constant = true on the global object | // This is not supposed to happen, unless the user has called SetProperty with constant = true on the global object | ||||
// instead of using SetGlobal. | // instead of using SetGlobal. | ||||
if (!desc.configurable()) | if (!desc.configurable()) | ||||
{ | { | ||||
JS_ReportError(m->m_cx, "The global \"%s\" is permanent and cannot be hotloaded", name); | JS_ReportError(rq.cx, "The global \"%s\" is permanent and cannot be hotloaded", name); | ||||
return false; | return false; | ||||
} | } | ||||
LOGMESSAGE("Hotloading new value for global \"%s\".", name); | LOGMESSAGE("Hotloading new value for global \"%s\".", name); | ||||
ENSURE(JS_DeleteProperty(m->m_cx, global, name)); | ENSURE(JS_DeleteProperty(rq.cx, global, name)); | ||||
} | } | ||||
} | } | ||||
uint attrs = 0; | uint attrs = 0; | ||||
if (constant) | if (constant) | ||||
attrs |= JSPROP_READONLY; | attrs |= JSPROP_READONLY; | ||||
if (enumerate) | if (enumerate) | ||||
attrs |= JSPROP_ENUMERATE; | attrs |= JSPROP_ENUMERATE; | ||||
return JS_DefineProperty(m->m_cx, global, name, value, attrs); | return JS_DefineProperty(rq.cx, global, name, value, attrs); | ||||
} | } | ||||
bool ScriptInterface::SetProperty_(JS::HandleValue obj, const char* name, JS::HandleValue value, bool constant, bool enumerate) const | bool ScriptInterface::SetProperty_(JS::HandleValue obj, const char* name, JS::HandleValue value, bool constant, bool enumerate) const | ||||
{ | { | ||||
JSAutoRequest rq(m->m_cx); | Request rq(this); | ||||
uint attrs = 0; | uint attrs = 0; | ||||
if (constant) | if (constant) | ||||
attrs |= JSPROP_READONLY | JSPROP_PERMANENT; | attrs |= JSPROP_READONLY | JSPROP_PERMANENT; | ||||
if (enumerate) | if (enumerate) | ||||
attrs |= JSPROP_ENUMERATE; | attrs |= JSPROP_ENUMERATE; | ||||
if (!obj.isObject()) | if (!obj.isObject()) | ||||
return false; | return false; | ||||
JS::RootedObject object(m->m_cx, &obj.toObject()); | JS::RootedObject object(rq.cx, &obj.toObject()); | ||||
if (!JS_DefineProperty(m->m_cx, object, name, value, attrs)) | if (!JS_DefineProperty(rq.cx, object, name, value, attrs)) | ||||
return false; | return false; | ||||
return true; | return true; | ||||
} | } | ||||
bool ScriptInterface::SetProperty_(JS::HandleValue obj, const wchar_t* name, JS::HandleValue value, bool constant, bool enumerate) const | bool ScriptInterface::SetProperty_(JS::HandleValue obj, const wchar_t* name, JS::HandleValue value, bool constant, bool enumerate) const | ||||
{ | { | ||||
JSAutoRequest rq(m->m_cx); | Request rq(this); | ||||
uint attrs = 0; | uint attrs = 0; | ||||
if (constant) | if (constant) | ||||
attrs |= JSPROP_READONLY | JSPROP_PERMANENT; | attrs |= JSPROP_READONLY | JSPROP_PERMANENT; | ||||
if (enumerate) | if (enumerate) | ||||
attrs |= JSPROP_ENUMERATE; | attrs |= JSPROP_ENUMERATE; | ||||
if (!obj.isObject()) | if (!obj.isObject()) | ||||
return false; | return false; | ||||
JS::RootedObject object(m->m_cx, &obj.toObject()); | JS::RootedObject object(rq.cx, &obj.toObject()); | ||||
utf16string name16(name, name + wcslen(name)); | utf16string name16(name, name + wcslen(name)); | ||||
if (!JS_DefineUCProperty(m->m_cx, object, reinterpret_cast<const char16_t*>(name16.c_str()), name16.length(), value, attrs)) | if (!JS_DefineUCProperty(rq.cx, object, reinterpret_cast<const char16_t*>(name16.c_str()), name16.length(), value, attrs)) | ||||
return false; | return false; | ||||
return true; | return true; | ||||
} | } | ||||
bool ScriptInterface::SetPropertyInt_(JS::HandleValue obj, int name, JS::HandleValue value, bool constant, bool enumerate) const | bool ScriptInterface::SetPropertyInt_(JS::HandleValue obj, int name, JS::HandleValue value, bool constant, bool enumerate) const | ||||
{ | { | ||||
JSAutoRequest rq(m->m_cx); | Request rq(this); | ||||
uint attrs = 0; | uint attrs = 0; | ||||
if (constant) | if (constant) | ||||
attrs |= JSPROP_READONLY | JSPROP_PERMANENT; | attrs |= JSPROP_READONLY | JSPROP_PERMANENT; | ||||
if (enumerate) | if (enumerate) | ||||
attrs |= JSPROP_ENUMERATE; | attrs |= JSPROP_ENUMERATE; | ||||
if (!obj.isObject()) | if (!obj.isObject()) | ||||
return false; | return false; | ||||
JS::RootedObject object(m->m_cx, &obj.toObject()); | JS::RootedObject object(rq.cx, &obj.toObject()); | ||||
JS::RootedId id(m->m_cx, INT_TO_JSID(name)); | JS::RootedId id(rq.cx, INT_TO_JSID(name)); | ||||
if (!JS_DefinePropertyById(m->m_cx, object, id, value, attrs)) | if (!JS_DefinePropertyById(rq.cx, object, id, value, attrs)) | ||||
return false; | return false; | ||||
return true; | return true; | ||||
} | } | ||||
bool ScriptInterface::GetProperty(JS::HandleValue obj, const char* name, JS::MutableHandleValue out) const | bool ScriptInterface::GetProperty(JS::HandleValue obj, const char* name, JS::MutableHandleValue out) const | ||||
{ | { | ||||
return GetProperty_(obj, name, out); | return GetProperty_(obj, name, out); | ||||
} | } | ||||
bool ScriptInterface::GetProperty(JS::HandleValue obj, const char* name, JS::MutableHandleObject out) const | bool ScriptInterface::GetProperty(JS::HandleValue obj, const char* name, JS::MutableHandleObject out) const | ||||
{ | { | ||||
JSContext* cx = GetContext(); | Request rq(this); | ||||
JSAutoRequest rq(cx); | JS::RootedValue val(rq.cx); | ||||
JS::RootedValue val(cx); | |||||
if (!GetProperty_(obj, name, &val)) | if (!GetProperty_(obj, name, &val)) | ||||
return false; | return false; | ||||
if (!val.isObject()) | if (!val.isObject()) | ||||
{ | { | ||||
LOGERROR("GetProperty failed: trying to get an object, but the property is not an object!"); | LOGERROR("GetProperty failed: trying to get an object, but the property is not an object!"); | ||||
return false; | return false; | ||||
} | } | ||||
out.set(&val.toObject()); | out.set(&val.toObject()); | ||||
return true; | return true; | ||||
} | } | ||||
bool ScriptInterface::GetPropertyInt(JS::HandleValue obj, int name, JS::MutableHandleValue out) const | bool ScriptInterface::GetPropertyInt(JS::HandleValue obj, int name, JS::MutableHandleValue out) const | ||||
{ | { | ||||
return GetPropertyInt_(obj, name, out); | return GetPropertyInt_(obj, name, out); | ||||
} | } | ||||
bool ScriptInterface::GetProperty_(JS::HandleValue obj, const char* name, JS::MutableHandleValue out) const | bool ScriptInterface::GetProperty_(JS::HandleValue obj, const char* name, JS::MutableHandleValue out) const | ||||
{ | { | ||||
JSAutoRequest rq(m->m_cx); | Request rq(this); | ||||
if (!obj.isObject()) | if (!obj.isObject()) | ||||
return false; | return false; | ||||
JS::RootedObject object(m->m_cx, &obj.toObject()); | JS::RootedObject object(rq.cx, &obj.toObject()); | ||||
if (!JS_GetProperty(m->m_cx, object, name, out)) | if (!JS_GetProperty(rq.cx, object, name, out)) | ||||
return false; | return false; | ||||
return true; | return true; | ||||
} | } | ||||
bool ScriptInterface::GetPropertyInt_(JS::HandleValue obj, int name, JS::MutableHandleValue out) const | bool ScriptInterface::GetPropertyInt_(JS::HandleValue obj, int name, JS::MutableHandleValue out) const | ||||
{ | { | ||||
JSAutoRequest rq(m->m_cx); | Request rq(this); | ||||
JS::RootedId nameId(m->m_cx, INT_TO_JSID(name)); | JS::RootedId nameId(rq.cx, INT_TO_JSID(name)); | ||||
if (!obj.isObject()) | if (!obj.isObject()) | ||||
return false; | return false; | ||||
JS::RootedObject object(m->m_cx, &obj.toObject()); | JS::RootedObject object(rq.cx, &obj.toObject()); | ||||
if (!JS_GetPropertyById(m->m_cx, object, nameId, out)) | if (!JS_GetPropertyById(rq.cx, object, nameId, out)) | ||||
return false; | return false; | ||||
return true; | return true; | ||||
} | } | ||||
bool ScriptInterface::HasProperty(JS::HandleValue obj, const char* name) const | bool ScriptInterface::HasProperty(JS::HandleValue obj, const char* name) const | ||||
{ | { | ||||
// TODO: proper errorhandling | // TODO: proper errorhandling | ||||
JSAutoRequest rq(m->m_cx); | Request rq(this); | ||||
if (!obj.isObject()) | if (!obj.isObject()) | ||||
return false; | return false; | ||||
JS::RootedObject object(m->m_cx, &obj.toObject()); | JS::RootedObject object(rq.cx, &obj.toObject()); | ||||
bool found; | bool found; | ||||
if (!JS_HasProperty(m->m_cx, object, name, &found)) | if (!JS_HasProperty(rq.cx, object, name, &found)) | ||||
return false; | return false; | ||||
return found; | return found; | ||||
} | } | ||||
bool ScriptInterface::EnumeratePropertyNames(JS::HandleValue objVal, bool enumerableOnly, std::vector<std::string>& out) const | bool ScriptInterface::EnumeratePropertyNames(JS::HandleValue objVal, bool enumerableOnly, std::vector<std::string>& out) const | ||||
{ | { | ||||
JSAutoRequest rq(m->m_cx); | Request rq(this); | ||||
if (!objVal.isObjectOrNull()) | if (!objVal.isObjectOrNull()) | ||||
{ | { | ||||
LOGERROR("EnumeratePropertyNames expected object type!"); | LOGERROR("EnumeratePropertyNames expected object type!"); | ||||
return false; | return false; | ||||
} | } | ||||
JS::RootedObject obj(m->m_cx, &objVal.toObject()); | JS::RootedObject obj(rq.cx, &objVal.toObject()); | ||||
JS::AutoIdVector props(m->m_cx); | JS::AutoIdVector props(rq.cx); | ||||
// This recurses up the prototype chain on its own. | // This recurses up the prototype chain on its own. | ||||
if (!js::GetPropertyKeys(m->m_cx, obj, enumerableOnly? 0 : JSITER_HIDDEN, &props)) | if (!js::GetPropertyKeys(rq.cx, obj, enumerableOnly? 0 : JSITER_HIDDEN, &props)) | ||||
return false; | return false; | ||||
out.reserve(out.size() + props.length()); | out.reserve(out.size() + props.length()); | ||||
for (size_t i = 0; i < props.length(); ++i) | for (size_t i = 0; i < props.length(); ++i) | ||||
{ | { | ||||
JS::RootedId id(m->m_cx, props[i]); | JS::RootedId id(rq.cx, props[i]); | ||||
JS::RootedValue val(m->m_cx); | JS::RootedValue val(rq.cx); | ||||
if (!JS_IdToValue(m->m_cx, id, &val)) | if (!JS_IdToValue(rq.cx, id, &val)) | ||||
return false; | return false; | ||||
// Ignore integer properties for now. | // Ignore integer properties for now. | ||||
// TODO: is this actually a thing in ECMAScript 6? | // TODO: is this actually a thing in ECMAScript 6? | ||||
if (!val.isString()) | if (!val.isString()) | ||||
continue; | continue; | ||||
std::string propName; | std::string propName; | ||||
if (!FromJSVal(m->m_cx, val, propName)) | if (!FromJSVal(rq, val, propName)) | ||||
return false; | return false; | ||||
out.emplace_back(std::move(propName)); | out.emplace_back(std::move(propName)); | ||||
} | } | ||||
return true; | return true; | ||||
} | } | ||||
bool ScriptInterface::SetPrototype(JS::HandleValue objVal, JS::HandleValue protoVal) | bool ScriptInterface::SetPrototype(JS::HandleValue objVal, JS::HandleValue protoVal) | ||||
{ | { | ||||
JSAutoRequest rq(m->m_cx); | Request rq(this); | ||||
if (!objVal.isObject() || !protoVal.isObject()) | if (!objVal.isObject() || !protoVal.isObject()) | ||||
return false; | return false; | ||||
JS::RootedObject obj(m->m_cx, &objVal.toObject()); | JS::RootedObject obj(rq.cx, &objVal.toObject()); | ||||
JS::RootedObject proto(m->m_cx, &protoVal.toObject()); | JS::RootedObject proto(rq.cx, &protoVal.toObject()); | ||||
return JS_SetPrototype(m->m_cx, obj, proto); | return JS_SetPrototype(rq.cx, obj, proto); | ||||
} | } | ||||
bool ScriptInterface::FreezeObject(JS::HandleValue objVal, bool deep) const | bool ScriptInterface::FreezeObject(JS::HandleValue objVal, bool deep) const | ||||
{ | { | ||||
JSAutoRequest rq(m->m_cx); | Request rq(this); | ||||
if (!objVal.isObject()) | if (!objVal.isObject()) | ||||
return false; | return false; | ||||
JS::RootedObject obj(m->m_cx, &objVal.toObject()); | JS::RootedObject obj(rq.cx, &objVal.toObject()); | ||||
if (deep) | if (deep) | ||||
return JS_DeepFreezeObject(m->m_cx, obj); | return JS_DeepFreezeObject(rq.cx, obj); | ||||
else | else | ||||
return JS_FreezeObject(m->m_cx, obj); | return JS_FreezeObject(rq.cx, obj); | ||||
} | } | ||||
bool ScriptInterface::LoadScript(const VfsPath& filename, const std::string& code) const | bool ScriptInterface::LoadScript(const VfsPath& filename, const std::string& code) const | ||||
{ | { | ||||
JSAutoRequest rq(m->m_cx); | Request rq(this); | ||||
JS::RootedObject global(m->m_cx, m->m_glob); | JS::RootedObject global(rq.cx, m->m_glob); | ||||
utf16string codeUtf16(code.begin(), code.end()); | utf16string codeUtf16(code.begin(), code.end()); | ||||
uint lineNo = 1; | uint lineNo = 1; | ||||
// CompileOptions does not copy the contents of the filename string pointer. | // CompileOptions does not copy the contents of the filename string pointer. | ||||
// Passing a temporary string there will cause undefined behaviour, so we create a separate string to avoid the temporary. | // Passing a temporary string there will cause undefined behaviour, so we create a separate string to avoid the temporary. | ||||
std::string filenameStr = filename.string8(); | std::string filenameStr = filename.string8(); | ||||
JS::CompileOptions options(m->m_cx); | JS::CompileOptions options(rq.cx); | ||||
options.setFileAndLine(filenameStr.c_str(), lineNo); | options.setFileAndLine(filenameStr.c_str(), lineNo); | ||||
options.setIsRunOnce(false); | options.setIsRunOnce(false); | ||||
JS::RootedFunction func(m->m_cx); | JS::RootedFunction func(rq.cx); | ||||
JS::AutoObjectVector emptyScopeChain(m->m_cx); | JS::AutoObjectVector emptyScopeChain(rq.cx); | ||||
if (!JS::CompileFunction(m->m_cx, emptyScopeChain, options, NULL, 0, NULL, | if (!JS::CompileFunction(rq.cx, emptyScopeChain, options, NULL, 0, NULL, | ||||
reinterpret_cast<const char16_t*>(codeUtf16.c_str()), (uint)(codeUtf16.length()), &func)) | reinterpret_cast<const char16_t*>(codeUtf16.c_str()), (uint)(codeUtf16.length()), &func)) | ||||
return false; | return false; | ||||
JS::RootedValue rval(m->m_cx); | JS::RootedValue rval(rq.cx); | ||||
return JS_CallFunction(m->m_cx, nullptr, func, JS::HandleValueArray::empty(), &rval); | return JS_CallFunction(rq.cx, nullptr, func, JS::HandleValueArray::empty(), &rval); | ||||
} | } | ||||
bool ScriptInterface::LoadGlobalScript(const VfsPath& filename, const std::wstring& code) const | bool ScriptInterface::LoadGlobalScript(const VfsPath& filename, const std::wstring& code) const | ||||
{ | { | ||||
JSAutoRequest rq(m->m_cx); | Request rq(this); | ||||
utf16string codeUtf16(code.begin(), code.end()); | utf16string codeUtf16(code.begin(), code.end()); | ||||
uint lineNo = 1; | uint lineNo = 1; | ||||
// CompileOptions does not copy the contents of the filename string pointer. | // CompileOptions does not copy the contents of the filename string pointer. | ||||
// Passing a temporary string there will cause undefined behaviour, so we create a separate string to avoid the temporary. | // Passing a temporary string there will cause undefined behaviour, so we create a separate string to avoid the temporary. | ||||
std::string filenameStr = filename.string8(); | std::string filenameStr = filename.string8(); | ||||
JS::RootedValue rval(m->m_cx); | JS::RootedValue rval(rq.cx); | ||||
JS::CompileOptions opts(m->m_cx); | JS::CompileOptions opts(rq.cx); | ||||
opts.setFileAndLine(filenameStr.c_str(), lineNo); | opts.setFileAndLine(filenameStr.c_str(), lineNo); | ||||
return JS::Evaluate(m->m_cx, opts, | return JS::Evaluate(rq.cx, opts, | ||||
reinterpret_cast<const char16_t*>(codeUtf16.c_str()), (uint)(codeUtf16.length()), &rval); | reinterpret_cast<const char16_t*>(codeUtf16.c_str()), (uint)(codeUtf16.length()), &rval); | ||||
} | } | ||||
bool ScriptInterface::LoadGlobalScriptFile(const VfsPath& path) const | bool ScriptInterface::LoadGlobalScriptFile(const VfsPath& path) const | ||||
{ | { | ||||
JSAutoRequest rq(m->m_cx); | Request rq(this); | ||||
if (!VfsFileExists(path)) | if (!VfsFileExists(path)) | ||||
{ | { | ||||
LOGERROR("File '%s' does not exist", path.string8()); | LOGERROR("File '%s' does not exist", path.string8()); | ||||
return false; | return false; | ||||
} | } | ||||
CVFSFile file; | CVFSFile file; | ||||
PSRETURN ret = file.Load(g_VFS, path); | PSRETURN ret = file.Load(g_VFS, path); | ||||
if (ret != PSRETURN_OK) | if (ret != PSRETURN_OK) | ||||
{ | { | ||||
LOGERROR("Failed to load file '%s': %s", path.string8(), GetErrorString(ret)); | LOGERROR("Failed to load file '%s': %s", path.string8(), GetErrorString(ret)); | ||||
return false; | return false; | ||||
} | } | ||||
std::wstring code = wstring_from_utf8(file.DecodeUTF8()); // assume it's UTF-8 | std::wstring code = wstring_from_utf8(file.DecodeUTF8()); // assume it's UTF-8 | ||||
utf16string codeUtf16(code.begin(), code.end()); | utf16string codeUtf16(code.begin(), code.end()); | ||||
uint lineNo = 1; | uint lineNo = 1; | ||||
// CompileOptions does not copy the contents of the filename string pointer. | // CompileOptions does not copy the contents of the filename string pointer. | ||||
// Passing a temporary string there will cause undefined behaviour, so we create a separate string to avoid the temporary. | // Passing a temporary string there will cause undefined behaviour, so we create a separate string to avoid the temporary. | ||||
std::string filenameStr = path.string8(); | std::string filenameStr = path.string8(); | ||||
JS::RootedValue rval(m->m_cx); | JS::RootedValue rval(rq.cx); | ||||
JS::CompileOptions opts(m->m_cx); | JS::CompileOptions opts(rq.cx); | ||||
opts.setFileAndLine(filenameStr.c_str(), lineNo); | opts.setFileAndLine(filenameStr.c_str(), lineNo); | ||||
return JS::Evaluate(m->m_cx, opts, | return JS::Evaluate(rq.cx, opts, | ||||
reinterpret_cast<const char16_t*>(codeUtf16.c_str()), (uint)(codeUtf16.length()), &rval); | reinterpret_cast<const char16_t*>(codeUtf16.c_str()), (uint)(codeUtf16.length()), &rval); | ||||
} | } | ||||
bool ScriptInterface::Eval(const char* code) const | bool ScriptInterface::Eval(const char* code) const | ||||
{ | { | ||||
JSAutoRequest rq(m->m_cx); | Request rq(this); | ||||
JS::RootedValue rval(m->m_cx); | JS::RootedValue rval(rq.cx); | ||||
return Eval_(code, &rval); | return Eval_(code, &rval); | ||||
} | } | ||||
bool ScriptInterface::Eval_(const char* code, JS::MutableHandleValue rval) const | bool ScriptInterface::Eval_(const char* code, JS::MutableHandleValue rval) const | ||||
{ | { | ||||
JSAutoRequest rq(m->m_cx); | Request rq(this); | ||||
utf16string codeUtf16(code, code+strlen(code)); | utf16string codeUtf16(code, code+strlen(code)); | ||||
JS::CompileOptions opts(m->m_cx); | JS::CompileOptions opts(rq.cx); | ||||
opts.setFileAndLine("(eval)", 1); | opts.setFileAndLine("(eval)", 1); | ||||
return JS::Evaluate(m->m_cx, opts, reinterpret_cast<const char16_t*>(codeUtf16.c_str()), (uint)codeUtf16.length(), rval); | return JS::Evaluate(rq.cx, opts, reinterpret_cast<const char16_t*>(codeUtf16.c_str()), (uint)codeUtf16.length(), rval); | ||||
} | } | ||||
bool ScriptInterface::Eval_(const wchar_t* code, JS::MutableHandleValue rval) const | bool ScriptInterface::Eval_(const wchar_t* code, JS::MutableHandleValue rval) const | ||||
{ | { | ||||
JSAutoRequest rq(m->m_cx); | Request rq(this); | ||||
utf16string codeUtf16(code, code+wcslen(code)); | utf16string codeUtf16(code, code+wcslen(code)); | ||||
JS::CompileOptions opts(m->m_cx); | JS::CompileOptions opts(rq.cx); | ||||
opts.setFileAndLine("(eval)", 1); | opts.setFileAndLine("(eval)", 1); | ||||
return JS::Evaluate(m->m_cx, opts, reinterpret_cast<const char16_t*>(codeUtf16.c_str()), (uint)codeUtf16.length(), rval); | return JS::Evaluate(rq.cx, opts, reinterpret_cast<const char16_t*>(codeUtf16.c_str()), (uint)codeUtf16.length(), rval); | ||||
} | } | ||||
bool ScriptInterface::ParseJSON(const std::string& string_utf8, JS::MutableHandleValue out) const | bool ScriptInterface::ParseJSON(const std::string& string_utf8, JS::MutableHandleValue out) const | ||||
{ | { | ||||
JSAutoRequest rq(m->m_cx); | Request rq(this); | ||||
std::wstring attrsW = wstring_from_utf8(string_utf8); | std::wstring attrsW = wstring_from_utf8(string_utf8); | ||||
utf16string string(attrsW.begin(), attrsW.end()); | utf16string string(attrsW.begin(), attrsW.end()); | ||||
if (JS_ParseJSON(m->m_cx, reinterpret_cast<const char16_t*>(string.c_str()), (u32)string.size(), out)) | if (JS_ParseJSON(rq.cx, reinterpret_cast<const char16_t*>(string.c_str()), (u32)string.size(), out)) | ||||
return true; | return true; | ||||
LOGERROR("JS_ParseJSON failed!"); | LOGERROR("JS_ParseJSON failed!"); | ||||
if (!JS_IsExceptionPending(m->m_cx)) | if (!JS_IsExceptionPending(rq.cx)) | ||||
return false; | return false; | ||||
JS::RootedValue exc(m->m_cx); | JS::RootedValue exc(rq.cx); | ||||
if (!JS_GetPendingException(m->m_cx, &exc)) | if (!JS_GetPendingException(rq.cx, &exc)) | ||||
return false; | return false; | ||||
JS_ClearPendingException(m->m_cx); | JS_ClearPendingException(rq.cx); | ||||
// We expect an object of type SyntaxError | // We expect an object of type SyntaxError | ||||
if (!exc.isObject()) | if (!exc.isObject()) | ||||
return false; | return false; | ||||
JS::RootedValue rval(m->m_cx); | JS::RootedValue rval(rq.cx); | ||||
JS::RootedObject excObj(m->m_cx, &exc.toObject()); | JS::RootedObject excObj(rq.cx, &exc.toObject()); | ||||
if (!JS_CallFunctionName(m->m_cx, excObj, "toString", JS::HandleValueArray::empty(), &rval)) | if (!JS_CallFunctionName(rq.cx, excObj, "toString", JS::HandleValueArray::empty(), &rval)) | ||||
return false; | return false; | ||||
std::wstring error; | std::wstring error; | ||||
ScriptInterface::FromJSVal(m->m_cx, rval, error); | ScriptInterface::FromJSVal(rq, rval, error); | ||||
LOGERROR("%s", utf8_from_wstring(error)); | LOGERROR("%s", utf8_from_wstring(error)); | ||||
return false; | return false; | ||||
} | } | ||||
void ScriptInterface::ReadJSONFile(const VfsPath& path, JS::MutableHandleValue out) const | void ScriptInterface::ReadJSONFile(const VfsPath& path, JS::MutableHandleValue out) const | ||||
{ | { | ||||
if (!VfsFileExists(path)) | if (!VfsFileExists(path)) | ||||
{ | { | ||||
Show All 31 Lines | struct Stringifier | ||||
std::stringstream stream; | std::stringstream stream; | ||||
}; | }; | ||||
// TODO: It's not quite clear why JS_Stringify needs JS::MutableHandleValue. |obj| should not get modified. | // TODO: It's not quite clear why JS_Stringify needs JS::MutableHandleValue. |obj| should not get modified. | ||||
// It probably has historical reasons and could be changed by SpiderMonkey in the future. | // It probably has historical reasons and could be changed by SpiderMonkey in the future. | ||||
std::string ScriptInterface::StringifyJSON(JS::MutableHandleValue obj, bool indent) const | std::string ScriptInterface::StringifyJSON(JS::MutableHandleValue obj, bool indent) const | ||||
{ | { | ||||
JSAutoRequest rq(m->m_cx); | Request rq(this); | ||||
Stringifier str; | Stringifier str; | ||||
JS::RootedValue indentVal(m->m_cx, indent ? JS::Int32Value(2) : JS::UndefinedValue()); | JS::RootedValue indentVal(rq.cx, indent ? JS::Int32Value(2) : JS::UndefinedValue()); | ||||
if (!JS_Stringify(m->m_cx, obj, nullptr, indentVal, &Stringifier::callback, &str)) | if (!JS_Stringify(rq.cx, obj, nullptr, indentVal, &Stringifier::callback, &str)) | ||||
{ | { | ||||
JS_ClearPendingException(m->m_cx); | JS_ClearPendingException(rq.cx); | ||||
LOGERROR("StringifyJSON failed"); | LOGERROR("StringifyJSON failed"); | ||||
return std::string(); | return std::string(); | ||||
} | } | ||||
return str.stream.str(); | return str.stream.str(); | ||||
} | } | ||||
std::string ScriptInterface::ToString(JS::MutableHandleValue obj, bool pretty) const | std::string ScriptInterface::ToString(JS::MutableHandleValue obj, bool pretty) const | ||||
{ | { | ||||
JSAutoRequest rq(m->m_cx); | Request rq(this); | ||||
if (obj.isUndefined()) | if (obj.isUndefined()) | ||||
return "(void 0)"; | return "(void 0)"; | ||||
// Try to stringify as JSON if possible | // Try to stringify as JSON if possible | ||||
// (TODO: this is maybe a bad idea since it'll drop 'undefined' values silently) | // (TODO: this is maybe a bad idea since it'll drop 'undefined' values silently) | ||||
if (pretty) | if (pretty) | ||||
{ | { | ||||
Stringifier str; | Stringifier str; | ||||
JS::RootedValue indentVal(m->m_cx, JS::Int32Value(2)); | JS::RootedValue indentVal(rq.cx, JS::Int32Value(2)); | ||||
// Temporary disable the error reporter, so we don't print complaints about cyclic values | // Temporary disable the error reporter, so we don't print complaints about cyclic values | ||||
JSErrorReporter er = JS_SetErrorReporter(m->m_runtime->m_rt, NULL); | JSErrorReporter er = JS_SetErrorReporter(m->m_runtime->m_rt, NULL); | ||||
bool ok = JS_Stringify(m->m_cx, obj, nullptr, indentVal, &Stringifier::callback, &str); | bool ok = JS_Stringify(rq.cx, obj, nullptr, indentVal, &Stringifier::callback, &str); | ||||
// Restore error reporter | // Restore error reporter | ||||
JS_SetErrorReporter(m->m_runtime->m_rt, er); | JS_SetErrorReporter(m->m_runtime->m_rt, er); | ||||
if (ok) | if (ok) | ||||
return str.stream.str(); | return str.stream.str(); | ||||
// Clear the exception set when Stringify failed | // Clear the exception set when Stringify failed | ||||
JS_ClearPendingException(m->m_cx); | JS_ClearPendingException(rq.cx); | ||||
} | } | ||||
// Caller didn't want pretty output, or JSON conversion failed (e.g. due to cycles), | // Caller didn't want pretty output, or JSON conversion failed (e.g. due to cycles), | ||||
// so fall back to obj.toSource() | // so fall back to obj.toSource() | ||||
std::wstring source = L"(error)"; | std::wstring source = L"(error)"; | ||||
CallFunction(obj, "toSource", source); | CallFunction(obj, "toSource", source); | ||||
return utf8_from_wstring(source); | return utf8_from_wstring(source); | ||||
} | } | ||||
void ScriptInterface::ReportError(const char* msg) const | void ScriptInterface::ReportError(const char* msg) const | ||||
{ | { | ||||
JSAutoRequest rq(m->m_cx); | Request rq(this); | ||||
// JS_ReportError by itself doesn't seem to set a JS-style exception, and so | // JS_ReportError by itself doesn't seem to set a JS-style exception, and so | ||||
// script callers will be unable to catch anything. So use JS_SetPendingException | // script callers will be unable to catch anything. So use JS_SetPendingException | ||||
// to make sure there really is a script-level exception. But just set it to undefined | // to make sure there really is a script-level exception. But just set it to undefined | ||||
// because there's not much value yet in throwing a real exception object. | // because there's not much value yet in throwing a real exception object. | ||||
JS_SetPendingException(m->m_cx, JS::UndefinedHandleValue); | JS_SetPendingException(rq.cx, JS::UndefinedHandleValue); | ||||
// And report the actual error | // And report the actual error | ||||
JS_ReportError(m->m_cx, "%s", msg); | JS_ReportError(rq.cx, "%s", msg); | ||||
// TODO: Why doesn't JS_ReportPendingException(m->m_cx); work? | // TODO: Why doesn't JS_ReportPendingException(cx); work? | ||||
} | } | ||||
bool ScriptInterface::IsExceptionPending(JSContext* cx) | bool ScriptInterface::IsExceptionPending(const Request& rq) | ||||
{ | { | ||||
JSAutoRequest rq(cx); | return JS_IsExceptionPending(rq.cx) ? true : false; | ||||
return JS_IsExceptionPending(cx) ? true : false; | |||||
} | } | ||||
JS::Value ScriptInterface::CloneValueFromOtherContext(const ScriptInterface& otherContext, JS::HandleValue val) const | JS::Value ScriptInterface::CloneValueFromOtherContext(const ScriptInterface& otherContext, JS::HandleValue val) const | ||||
{ | { | ||||
PROFILE("CloneValueFromOtherContext"); | PROFILE("CloneValueFromOtherContext"); | ||||
JSAutoRequest rq(m->m_cx); | Request rq(this); | ||||
JS::RootedValue out(m->m_cx); | JS::RootedValue out(rq.cx); | ||||
shared_ptr<StructuredClone> structuredClone = otherContext.WriteStructuredClone(val); | shared_ptr<StructuredClone> structuredClone = otherContext.WriteStructuredClone(val); | ||||
ReadStructuredClone(structuredClone, &out); | ReadStructuredClone(structuredClone, &out); | ||||
return out.get(); | return out.get(); | ||||
} | } | ||||
ScriptInterface::StructuredClone::StructuredClone() : | ScriptInterface::StructuredClone::StructuredClone() : | ||||
m_Data(NULL), m_Size(0) | m_Data(NULL), m_Size(0) | ||||
{ | { | ||||
} | } | ||||
ScriptInterface::StructuredClone::~StructuredClone() | ScriptInterface::StructuredClone::~StructuredClone() | ||||
{ | { | ||||
if (m_Data) | if (m_Data) | ||||
JS_ClearStructuredClone(m_Data, m_Size, NULL, NULL); | JS_ClearStructuredClone(m_Data, m_Size, NULL, NULL); | ||||
} | } | ||||
shared_ptr<ScriptInterface::StructuredClone> ScriptInterface::WriteStructuredClone(JS::HandleValue v) const | shared_ptr<ScriptInterface::StructuredClone> ScriptInterface::WriteStructuredClone(JS::HandleValue v) const | ||||
{ | { | ||||
JSAutoRequest rq(m->m_cx); | Request rq(this); | ||||
u64* data = NULL; | u64* data = NULL; | ||||
size_t nbytes = 0; | size_t nbytes = 0; | ||||
if (!JS_WriteStructuredClone(m->m_cx, v, &data, &nbytes, NULL, NULL, JS::UndefinedHandleValue)) | if (!JS_WriteStructuredClone(rq.cx, v, &data, &nbytes, NULL, NULL, JS::UndefinedHandleValue)) | ||||
{ | { | ||||
debug_warn(L"Writing a structured clone with JS_WriteStructuredClone failed!"); | debug_warn(L"Writing a structured clone with JS_WriteStructuredClone failed!"); | ||||
return shared_ptr<StructuredClone>(); | return shared_ptr<StructuredClone>(); | ||||
} | } | ||||
shared_ptr<StructuredClone> ret(new StructuredClone); | shared_ptr<StructuredClone> ret(new StructuredClone); | ||||
ret->m_Data = data; | ret->m_Data = data; | ||||
ret->m_Size = nbytes; | ret->m_Size = nbytes; | ||||
return ret; | return ret; | ||||
} | } | ||||
void ScriptInterface::ReadStructuredClone(const shared_ptr<ScriptInterface::StructuredClone>& ptr, JS::MutableHandleValue ret) const | void ScriptInterface::ReadStructuredClone(const shared_ptr<ScriptInterface::StructuredClone>& ptr, JS::MutableHandleValue ret) const | ||||
{ | { | ||||
JSAutoRequest rq(m->m_cx); | Request rq(this); | ||||
JS_ReadStructuredClone(m->m_cx, ptr->m_Data, ptr->m_Size, JS_STRUCTURED_CLONE_VERSION, ret, NULL, NULL); | JS_ReadStructuredClone(rq.cx, ptr->m_Data, ptr->m_Size, JS_STRUCTURED_CLONE_VERSION, ret, NULL, NULL); | ||||
} | } |
Wildfire Games · Phabricator