906 lines
30 KiB
C++
Raw Normal View History

2022-06-25 00:23:03 +08:00
/****************************************************************************
Copyright (c) 2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#include "ScriptEngine.hpp"
#include "platform/CCPlatformConfig.h"
#include "base/ccConfig.h"
#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8
#include "Object.hpp"
#include "Class.hpp"
#include "Utils.hpp"
#include "../State.hpp"
#include "../MappingUtils.hpp"
#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
#include <sys/sysctl.h>
#include <sys/types.h>
#include <mach/machine.h>
#include <string.h>
#include <iostream>
#endif
#if SE_ENABLE_INSPECTOR
#include "debugger/inspector_agent.h"
#include "debugger/env.h"
#include "debugger/node.h"
#endif
#include <sstream>
#define EXPOSE_GC "__jsb_gc__"
uint32_t __jsbInvocationCount = 0;
uint32_t __jsbStackFrameLimit = 20;
#define RETRUN_VAL_IF_FAIL(cond, val) \
if (!(cond)) return val
namespace se {
Class* __jsb_CCPrivateData_class = nullptr;
namespace {
ScriptEngine* __instance = nullptr;
void __log(const v8::FunctionCallbackInfo<v8::Value>& info)
{
if (info[0]->IsString())
{
v8::String::Utf8Value utf8(v8::Isolate::GetCurrent(), info[0]);
SE_LOGD("JS: %s\n", *utf8);
}
}
void __forceGC(const v8::FunctionCallbackInfo<v8::Value>& info)
{
ScriptEngine::getInstance()->garbageCollect();
}
std::string stackTraceToString(v8::Local<v8::StackTrace> stack)
{
std::string stackStr;
if (stack.IsEmpty())
return stackStr;
char tmp[100] = {0};
for (int i = 0, e = stack->GetFrameCount(); i < e; ++i)
{
v8::Local<v8::StackFrame> frame = stack->GetFrame(v8::Isolate::GetCurrent(), i);
v8::Local<v8::String> script = frame->GetScriptName();
std::string scriptName;
if (!script.IsEmpty())
{
scriptName = *v8::String::Utf8Value(v8::Isolate::GetCurrent(), script);
}
v8::Local<v8::String> func = frame->GetFunctionName();
std::string funcName;
if (!func.IsEmpty())
{
funcName = *v8::String::Utf8Value(v8::Isolate::GetCurrent(), func);
}
stackStr += "[";
snprintf(tmp, sizeof(tmp), "%d", i);
stackStr += tmp;
stackStr += "]";
stackStr += (funcName.empty() ? "anonymous" : funcName.c_str());
stackStr += "@";
stackStr += (scriptName.empty() ? "(no filename)" : scriptName.c_str());
stackStr += ":";
snprintf(tmp, sizeof(tmp), "%d", frame->GetLineNumber());
stackStr += tmp;
if (i < (e-1))
{
stackStr += "\n";
}
}
return stackStr;
}
se::Value __oldConsoleLog;
se::Value __oldConsoleDebug;
se::Value __oldConsoleInfo;
se::Value __oldConsoleWarn;
se::Value __oldConsoleError;
se::Value __oldConsoleAssert;
bool JSB_console_format_log(State& s, const char* prefix, int msgIndex = 0)
{
if (msgIndex < 0)
return false;
const auto& args = s.args();
int argc = (int)args.size();
if ((argc - msgIndex) == 1)
{
std::string msg = args[msgIndex].toStringForce();
SE_LOGD("JS: %s%s\n", prefix, msg.c_str());
}
else if (argc > 1)
{
std::string msg = args[msgIndex].toStringForce();
size_t pos;
for (int i = (msgIndex+1); i < argc; ++i)
{
pos = msg.find("%");
if (pos != std::string::npos && pos != (msg.length()-1) && (msg[pos+1] == 'd' || msg[pos+1] == 's' || msg[pos+1] == 'f'))
{
msg.replace(pos, 2, args[i].toStringForce());
}
else
{
msg += " " + args[i].toStringForce();
}
}
SE_LOGD("JS: %s%s\n", prefix, msg.c_str());
}
return true;
}
bool JSB_console_log(State& s)
{
JSB_console_format_log(s, "");
__oldConsoleLog.toObject()->call(s.args(), s.thisObject());
return true;
}
SE_BIND_FUNC(JSB_console_log)
bool JSB_console_debug(State& s)
{
JSB_console_format_log(s, "[DEBUG]: ");
__oldConsoleDebug.toObject()->call(s.args(), s.thisObject());
return true;
}
SE_BIND_FUNC(JSB_console_debug)
bool JSB_console_info(State& s)
{
JSB_console_format_log(s, "[INFO]: ");
__oldConsoleInfo.toObject()->call(s.args(), s.thisObject());
return true;
}
SE_BIND_FUNC(JSB_console_info)
bool JSB_console_warn(State& s)
{
JSB_console_format_log(s, "[WARN]: ");
__oldConsoleWarn.toObject()->call(s.args(), s.thisObject());
return true;
}
SE_BIND_FUNC(JSB_console_warn)
bool JSB_console_error(State& s)
{
JSB_console_format_log(s, "[ERROR]: ");
__oldConsoleError.toObject()->call(s.args(), s.thisObject());
return true;
}
SE_BIND_FUNC(JSB_console_error)
bool JSB_console_assert(State& s)
{
const auto& args = s.args();
if (!args.empty())
{
if (args[0].isBoolean() && !args[0].toBoolean())
{
JSB_console_format_log(s, "[ASSERT]: ", 1);
__oldConsoleAssert.toObject()->call(s.args(), s.thisObject());
}
}
return true;
}
SE_BIND_FUNC(JSB_console_assert)
#if CC_TARGET_PLATFORM == CC_PLATFORM_IOS
/**
* JIT is enabled on iOS 14.2+ & chipset A12+
* ref https://github.com/flutter/engine/pull/22377
*/
bool jitSupported() {
#if CC_IOS_FORCE_DISABLE_JIT
return false;
#elif TARGET_CPU_X86 || TARGET_CPU_X86_64
return true;
#else
// Check for arm64e.
cpu_type_t cpuType = 0;
size_t cpuTypeSize = sizeof(cpu_type_t);
if (::sysctlbyname("hw.cputype", &cpuType, &cpuTypeSize, nullptr, 0) < 0) {
SE_LOGD("Could not execute sysctl() to get CPU type: %s", strerror(errno));
}
cpu_subtype_t cpuSubType = 0;
if (::sysctlbyname("hw.cpusubtype", &cpuSubType, &cpuTypeSize, nullptr, 0) < 0) {
SE_LOGD("Could not execute sysctl() to get CPU subtype: %s", strerror(errno));
}
// Tracing is necessary unless the device is arm64e (A12 chip or higher).
if (cpuType != CPU_TYPE_ARM64 || cpuSubType != CPU_SUBTYPE_ARM64E) {
return false;
}
// Check for iOS 14.2 and higher.
size_t osVersionSize;
::sysctlbyname("kern.osversion", NULL, &osVersionSize, NULL, 0);
char osversionBuffer[osVersionSize];
if (::sysctlbyname("kern.osversion", osversionBuffer, &osVersionSize, NULL, 0) < 0) {
SE_LOGD("Could not execute sysctl() to get current OS version: %s", strerror(errno));
return false;
}
int majorVersion = 0;
char minorLetter = 'Z';
for (size_t index = 0; index < osVersionSize; index++) {
char version_char = osversionBuffer[index];
// Find the minor version build letter.
if (isalpha(version_char)) {
majorVersion = atoi((const char*)osversionBuffer);
minorLetter = toupper(version_char);
break;
}
}
// 18B92 is iOS 14.2 beta release candidate where tracing became unnecessary.
return majorVersion > 18 || (majorVersion == 18 && minorLetter >= 'B');
#endif //TARGET_CPU_X86 || TARGET_CPU_X86_64
}
#endif //CC_TARGET_PLATFORM == CC_PLATFORM_IOS
} // namespace {
void ScriptEngine::callExceptionCallback(const char* location, const char* message, const char *stack) {
if (_nativeExceptionCallback) {
_nativeExceptionCallback(location, message, stack);
}
if (_jsExceptionCallback) {
_jsExceptionCallback(location, message, stack);
}
}
void ScriptEngine::onFatalErrorCallback(const char* location, const char* message)
{
std::string errorStr = "[FATAL ERROR] location: ";
errorStr += location;
errorStr += ", message: ";
errorStr += message;
SE_LOGE("%s\n", errorStr.c_str());
getInstance()->callExceptionCallback(location, message, "(no stack information)");
}
void ScriptEngine::onOOMErrorCallback(const char* location, bool is_heap_oom)
{
std::string errorStr = "[OOM ERROR] location: ";
errorStr += location;
std::string message;
message = "is heap out of memory: ";
if (is_heap_oom)
message += "true";
else
message += "false";
errorStr += ", " + message;
SE_LOGE("%s\n", errorStr.c_str());
getInstance()->callExceptionCallback(location, message.c_str(), "(no stack information)");
}
void ScriptEngine::onMessageCallback(v8::Local<v8::Message> message, v8::Local<v8::Value> data)
{
ScriptEngine* thiz = getInstance();
v8::Local<v8::String> msg = message->Get();
Value msgVal;
internal::jsToSeValue(v8::Isolate::GetCurrent(), msg, &msgVal);
assert(msgVal.isString());
v8::ScriptOrigin origin = message->GetScriptOrigin();
Value resouceNameVal;
internal::jsToSeValue(v8::Isolate::GetCurrent(), origin.ResourceName(), &resouceNameVal);
Value line;
internal::jsToSeValue(v8::Isolate::GetCurrent(), origin.ResourceLineOffset(), &line);
Value column;
internal::jsToSeValue(v8::Isolate::GetCurrent(), origin.ResourceColumnOffset(), &column);
std::string location = resouceNameVal.toStringForce() + ":" + line.toStringForce() + ":" + column.toStringForce();
std::string errorStr = msgVal.toString() + ", location: " + location;
std::string stackStr = stackTraceToString(message->GetStackTrace());
if (!stackStr.empty())
{
if (line.toInt32() == 0)
{
location = "(see stack)";
}
errorStr += "\nSTACK:\n" + stackStr;
}
SE_LOGE("ERROR: %s\n", errorStr.c_str());
thiz->callExceptionCallback(location.c_str(), msgVal.toString().c_str(), stackStr.c_str());
if (!thiz->_isErrorHandleWorking)
{
thiz->_isErrorHandleWorking = true;
Value errorHandler;
if (thiz->_globalObj && thiz->_globalObj->getProperty("__errorHandler", &errorHandler) && errorHandler.isObject() && errorHandler.toObject()->isFunction())
{
ValueArray args;
args.push_back(resouceNameVal);
args.push_back(line);
args.push_back(msgVal);
args.push_back(Value(stackStr));
errorHandler.toObject()->call(args, thiz->_globalObj);
}
thiz->_isErrorHandleWorking = false;
}
else
{
SE_LOGE("ERROR: __errorHandler has exception\n");
}
}
void ScriptEngine::onPromiseRejectCallback(v8::PromiseRejectMessage msg)
{
v8::Isolate *isolate = getInstance()->_isolate;
v8::HandleScope scope(isolate);
std::stringstream ss;
auto event = msg.GetEvent();
auto value = msg.GetValue();
const char *eventName = "[invalidatePromiseEvent]";
if(event == v8::kPromiseRejectWithNoHandler) {
eventName = "unhandledRejectedPromise";
}else if(event == v8::kPromiseHandlerAddedAfterReject) {
eventName = "handlerAddedAfterPromiseRejected";
}else if(event == v8::kPromiseRejectAfterResolved) {
eventName = "rejectAfterPromiseResolved";
}else if( event == v8::kPromiseResolveAfterResolved) {
eventName = "resolveAfterPromiseResolved";
}
if(!value.IsEmpty()) {
// prepend error object to stack message
v8::Local<v8::String> str = value->ToString(isolate->GetCurrentContext()).ToLocalChecked();
v8::String::Utf8Value valueUtf8(isolate, str);
ss << *valueUtf8 << std::endl;
}
auto stackStr = getInstance()->getCurrentStackTrace();
ss << "stacktrace: " << std::endl;
ss << stackStr << std::endl;
getInstance()->callExceptionCallback("", eventName, ss.str().c_str());
}
void ScriptEngine::privateDataFinalize(void* nativeObj)
{
internal::PrivateData* p = (internal::PrivateData*)nativeObj;
Object::nativeObjectFinalizeHook(p->data);
assert(p->seObj->getRefCount() == 1);
p->seObj->decRef();
free(p);
}
ScriptEngine *ScriptEngine::getInstance()
{
if (__instance == nullptr)
{
__instance = new ScriptEngine();
}
return __instance;
}
void ScriptEngine::destroyInstance()
{
delete __instance;
__instance = nullptr;
}
ScriptEngine::ScriptEngine()
: _platform(nullptr)
, _isolate(nullptr)
, _handleScope(nullptr)
, _globalObj(nullptr)
#if SE_ENABLE_INSPECTOR
, _env(nullptr)
, _isolateData(nullptr)
#endif
, _debuggerServerPort(0)
, _vmId(0)
, _isValid(false)
, _isGarbageCollecting(false)
, _isInCleanup(false)
, _isErrorHandleWorking(false)
{
_platform = v8::platform::NewDefaultPlatform().release();
v8::V8::InitializePlatform(_platform);
std::string flags;
//NOTICE: spaces are required between flags
flags.append(" --expose-gc-as=" EXPOSE_GC);
// flags.append(" --trace-gc"); // v8 trace gc
#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
if(!jitSupported()) {
flags.append(" --jitless");
}
#endif
if(!flags.empty())
{
v8::V8::SetFlagsFromString(flags.c_str(), (int)flags.length());
}
bool ok = v8::V8::Initialize();
assert(ok);
}
ScriptEngine::~ScriptEngine()
{
cleanup();
v8::V8::Dispose();
v8::V8::ShutdownPlatform();
delete _platform;
}
bool ScriptEngine::init()
{
cleanup();
SE_LOGD("Initializing V8, version: %s\n", v8::V8::GetVersion());
++_vmId;
_engineThreadId = std::this_thread::get_id();
for (const auto& hook : _beforeInitHookArray)
{
hook();
}
_beforeInitHookArray.clear();
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator();
_isolate = v8::Isolate::New(create_params);
v8::HandleScope hs(_isolate);
_isolate->Enter();
_isolate->SetCaptureStackTraceForUncaughtExceptions(true, __jsbStackFrameLimit, v8::StackTrace::kOverview);
_isolate->SetFatalErrorHandler(onFatalErrorCallback);
_isolate->SetOOMErrorHandler(onOOMErrorCallback);
_isolate->AddMessageListener(onMessageCallback);
_isolate->SetPromiseRejectCallback(onPromiseRejectCallback);
_context.Reset(_isolate, v8::Context::New(_isolate));
_context.Get(_isolate)->Enter();
NativePtrToObjectMap::init();
NonRefNativePtrCreatedByCtorMap::init();
Object::setup();
Class::setIsolate(_isolate);
Object::setIsolate(_isolate);
_globalObj = Object::_createJSObject(nullptr, _context.Get(_isolate)->Global());
_globalObj->root();
_globalObj->setProperty("window", Value(_globalObj));
se::Value consoleVal;
if (_globalObj->getProperty("console", &consoleVal) && consoleVal.isObject())
{
consoleVal.toObject()->getProperty("log", &__oldConsoleLog);
consoleVal.toObject()->defineFunction("log", _SE(JSB_console_log));
consoleVal.toObject()->getProperty("debug", &__oldConsoleDebug);
consoleVal.toObject()->defineFunction("debug", _SE(JSB_console_debug));
consoleVal.toObject()->getProperty("info", &__oldConsoleInfo);
consoleVal.toObject()->defineFunction("info", _SE(JSB_console_info));
consoleVal.toObject()->getProperty("warn", &__oldConsoleWarn);
consoleVal.toObject()->defineFunction("warn", _SE(JSB_console_warn));
consoleVal.toObject()->getProperty("error", &__oldConsoleError);
consoleVal.toObject()->defineFunction("error", _SE(JSB_console_error));
consoleVal.toObject()->getProperty("assert", &__oldConsoleAssert);
consoleVal.toObject()->defineFunction("assert", _SE(JSB_console_assert));
}
_globalObj->setProperty("scriptEngineType", se::Value("V8"));
_globalObj->defineFunction("log", __log);
_globalObj->defineFunction("forceGC", __forceGC);
_globalObj->getProperty(EXPOSE_GC, &_gcFuncValue);
if(_gcFuncValue.isObject() && _gcFuncValue.toObject()->isFunction()) {
_gcFunc = _gcFuncValue.toObject();
} else {
_gcFunc = nullptr;
}
__jsb_CCPrivateData_class = Class::create("__PrivateData", _globalObj, nullptr, nullptr);
__jsb_CCPrivateData_class->defineFinalizeFunction(privateDataFinalize);
__jsb_CCPrivateData_class->setCreateProto(false);
__jsb_CCPrivateData_class->install();
_isValid = true;
for (const auto& hook : _afterInitHookArray)
{
hook();
}
_afterInitHookArray.clear();
return _isValid;
}
void ScriptEngine::cleanup()
{
if (!_isValid)
return;
SE_LOGD("ScriptEngine::cleanup begin ...\n");
_isInCleanup = true;
{
AutoHandleScope hs;
for (const auto& hook : _beforeCleanupHookArray)
{
hook();
}
_beforeCleanupHookArray.clear();
SAFE_DEC_REF(_globalObj);
Object::cleanup();
Class::cleanup();
garbageCollect();
__oldConsoleLog.setUndefined();
__oldConsoleDebug.setUndefined();
__oldConsoleInfo.setUndefined();
__oldConsoleWarn.setUndefined();
__oldConsoleError.setUndefined();
__oldConsoleAssert.setUndefined();
#if SE_ENABLE_INSPECTOR
if (_isolateData != nullptr)
{
node::FreeIsolateData(_isolateData);
_isolateData = nullptr;
}
if (_env != nullptr)
{
_env->inspector_agent()->Stop();
_env->CleanupHandles();
node::FreeEnvironment(_env);
_env = nullptr;
}
#endif
_context.Get(_isolate)->Exit();
_context.Reset();
_isolate->Exit();
}
_isolate->Dispose();
_isolate = nullptr;
_globalObj = nullptr;
_isValid = false;
_registerCallbackArray.clear();
for (const auto& hook : _afterCleanupHookArray)
{
hook();
}
_afterCleanupHookArray.clear();
_isInCleanup = false;
NativePtrToObjectMap::destroy();
NonRefNativePtrCreatedByCtorMap::destroy();
_gcFunc = nullptr;
SE_LOGD("ScriptEngine::cleanup end ...\n");
}
Object* ScriptEngine::getGlobalObject() const
{
return _globalObj;
}
void ScriptEngine::addBeforeInitHook(const std::function<void()>& hook)
{
_beforeInitHookArray.push_back(hook);
}
void ScriptEngine::addAfterInitHook(const std::function<void()>& hook)
{
_afterInitHookArray.push_back(hook);
}
void ScriptEngine::addBeforeCleanupHook(const std::function<void()>& hook)
{
_beforeCleanupHookArray.push_back(hook);
}
void ScriptEngine::addAfterCleanupHook(const std::function<void()>& hook)
{
_afterCleanupHookArray.push_back(hook);
}
void ScriptEngine::addRegisterCallback(RegisterCallback cb)
{
assert(std::find(_registerCallbackArray.begin(), _registerCallbackArray.end(), cb) == _registerCallbackArray.end());
_registerCallbackArray.push_back(cb);
}
bool ScriptEngine::start()
{
if (!init())
return false;
se::AutoHandleScope hs;
// debugger
if (isDebuggerEnabled())
{
#if SE_ENABLE_INSPECTOR
// V8 inspector stuff, most code are taken from NodeJS.
_isolateData = node::CreateIsolateData(_isolate, uv_default_loop());
_env = node::CreateEnvironment(_isolateData, _context.Get(_isolate), 0, nullptr, 0, nullptr);
node::DebugOptions options;
options.set_wait_for_connect(_isWaitForConnect);// the program will be hung up until debug attach if _isWaitForConnect = true
options.set_inspector_enabled(true);
options.set_port((int)_debuggerServerPort);
options.set_host_name(_debuggerServerAddr.c_str());
bool ok = _env->inspector_agent()->Start(_platform, "", options);
assert(ok);
#endif
}
//
bool ok = false;
_startTime = std::chrono::steady_clock::now();
for (auto cb : _registerCallbackArray)
{
ok = cb(_globalObj);
assert(ok);
if (!ok)
break;
}
// After ScriptEngine is started, _registerCallbackArray isn't needed. Therefore, clear it here.
_registerCallbackArray.clear();
return ok;
}
void ScriptEngine::garbageCollect()
{
int objSize = __objectMap ? (int)__objectMap->size() : -1;
SE_LOGD("GC begin ..., (js->native map) size: %d, all objects: %d\n", (int)NativePtrToObjectMap::size(), objSize);
if(_gcFunc == nullptr)
{
const double kLongIdlePauseInSeconds = 1.0;
_isolate->ContextDisposedNotification();
_isolate->IdleNotificationDeadline(_platform->MonotonicallyIncreasingTime() + kLongIdlePauseInSeconds);
// By sending a low memory notifications, we will try hard to collect all
// garbage and will therefore also invoke all weak callbacks of actually
// unreachable persistent handles.
_isolate->LowMemoryNotification();
}
else
{
_gcFunc->call({}, nullptr);
}
objSize = __objectMap ? (int)__objectMap->size() : -1;
SE_LOGD("GC end ..., (js->native map) size: %d, all objects: %d\n", (int)NativePtrToObjectMap::size(), objSize);
}
bool ScriptEngine::isGarbageCollecting()
{
return _isGarbageCollecting;
}
void ScriptEngine::_setGarbageCollecting(bool isGarbageCollecting)
{
_isGarbageCollecting = isGarbageCollecting;
}
bool ScriptEngine::isValid() const
{
return _isValid;
}
bool ScriptEngine::evalString(const char* script, ssize_t length/* = -1 */, Value* ret/* = nullptr */, const char* fileName/* = nullptr */)
{
if(_engineThreadId != std::this_thread::get_id())
{
// `evalString` should run in main thread
assert(false);
return false;
}
assert(script != nullptr);
if (length < 0)
length = strlen(script);
if (fileName == nullptr)
fileName = "(no filename)";
// Fix the source url is too long displayed in Chrome debugger.
std::string sourceUrl = fileName;
static const std::string prefixKey = "/temp/quick-scripts/";
size_t prefixPos = sourceUrl.find(prefixKey);
if (prefixPos != std::string::npos)
{
sourceUrl = sourceUrl.substr(prefixPos + prefixKey.length());
}
// It is needed, or will crash if invoked from non C++ context, such as invoked from objective-c context(for example, handler of UIKit).
v8::HandleScope handle_scope(_isolate);
std::string scriptStr(script, length);
v8::MaybeLocal<v8::String> source = v8::String::NewFromUtf8(_isolate, scriptStr.c_str(), v8::NewStringType::kNormal);
if (source.IsEmpty())
return false;
v8::MaybeLocal<v8::String> originStr = v8::String::NewFromUtf8(_isolate, sourceUrl.c_str(), v8::NewStringType::kNormal);
if (originStr.IsEmpty())
return false;
v8::ScriptOrigin origin(originStr.ToLocalChecked());
v8::MaybeLocal<v8::Script> maybeScript = v8::Script::Compile(_context.Get(_isolate), source.ToLocalChecked(), &origin);
bool success = false;
if (!maybeScript.IsEmpty())
{
v8::TryCatch block(_isolate);
v8::Local<v8::Script> v8Script = maybeScript.ToLocalChecked();
v8::MaybeLocal<v8::Value> maybeResult = v8Script->Run(_context.Get(_isolate));
if (!maybeResult.IsEmpty())
{
v8::Local<v8::Value> result = maybeResult.ToLocalChecked();
if (!result->IsUndefined() && ret != nullptr)
{
internal::jsToSeValue(_isolate, result, ret);
}
success = true;
}
if (block.HasCaught()) {
v8::Local<v8::Message> message = block.Message();
SE_LOGE("ScriptEngine::evalString catch exception:\n");
onMessageCallback(message, v8::Undefined(_isolate));
}
}
if (!success)
{
SE_LOGE("ScriptEngine::evalString script %s, failed!\n", fileName);
}
return success;
}
std::string ScriptEngine::getCurrentStackTrace()
{
if (!_isValid)
return std::string();
v8::HandleScope hs(_isolate);
v8::Local<v8::StackTrace> stack = v8::StackTrace::CurrentStackTrace(_isolate, __jsbStackFrameLimit, v8::StackTrace::kOverview);
return stackTraceToString(stack);
}
void ScriptEngine::setFileOperationDelegate(const FileOperationDelegate& delegate)
{
_fileOperationDelegate = delegate;
}
const ScriptEngine::FileOperationDelegate& ScriptEngine::getFileOperationDelegate() const
{
return _fileOperationDelegate;
}
bool ScriptEngine::runScript(const std::string& path, Value* ret/* = nullptr */)
{
assert(!path.empty());
assert(_fileOperationDelegate.isValid());
std::string scriptBuffer = _fileOperationDelegate.onGetStringFromFile(path);
if (!scriptBuffer.empty())
{
return evalString(scriptBuffer.c_str(), scriptBuffer.length(), ret, path.c_str());
}
SE_LOGE("ScriptEngine::runScript script %s, buffer is empty!\n", path.c_str());
return false;
}
void ScriptEngine::clearException()
{
//IDEA:
}
void ScriptEngine::setExceptionCallback(const ExceptionCallback& cb)
{
_nativeExceptionCallback = cb;
}
void ScriptEngine::setJSExceptionCallback(const ExceptionCallback& cb)
{
_jsExceptionCallback = cb;
}
v8::Local<v8::Context> ScriptEngine::_getContext() const
{
return _context.Get(_isolate);
}
void ScriptEngine::enableDebugger(const std::string& serverAddr, uint32_t port, bool isWait)
{
_debuggerServerAddr = serverAddr;
_debuggerServerPort = port;
_isWaitForConnect = isWait;
}
bool ScriptEngine::isDebuggerEnabled() const
{
return !_debuggerServerAddr.empty() && _debuggerServerPort > 0;
}
void ScriptEngine::mainLoopUpdate()
{
// empty implementation
}
} // namespace se {
#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8