diff --git a/NativeScript/NativeScript.mm b/NativeScript/NativeScript.mm index 0aea242c..11f8ae80 100644 --- a/NativeScript/NativeScript.mm +++ b/NativeScript/NativeScript.mm @@ -1,15 +1,20 @@ -#include #include "NativeScript.h" +#include #include "inspector/JsV8InspectorClient.h" #include "runtime/Console.h" -#include "runtime/RuntimeConfig.h" #include "runtime/Helpers.h" #include "runtime/Runtime.h" +#include "runtime/RuntimeConfig.h" #include "runtime/Tasks.h" using namespace v8; using namespace tns; +namespace tns { +// External flag from Runtime.mm to track JavaScript errors +extern bool jsErrorOccurred; +} + @implementation Config @synthesize BaseDir; @@ -23,99 +28,110 @@ @implementation NativeScript extern char defaultStartOfMetadataSection __asm("section$start$__DATA$__TNSMetadata"); -- (void)runScriptString: (NSString*) script runLoop: (BOOL) runLoop { +- (void)runScriptString:(NSString*)script runLoop:(BOOL)runLoop { + std::string cppString = std::string([script UTF8String]); + runtime_->RunScript(cppString); - std::string cppString = std::string([script UTF8String]); - runtime_->RunScript(cppString); - - if (runLoop) { - CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true); - } - - - tns::Tasks::Drain(); + if (runLoop) { + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true); + } + tns::Tasks::Drain(); } std::unique_ptr runtime_; - (void)runMainApplication { - runtime_->RunMainScript(); + runtime_->RunMainScript(); - CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true); - tns::Tasks::Drain(); + // In debug mode, if JavaScript errors occurred, keep the app alive indefinitely + // This prevents iOS from terminating the app and allows hot-reload to work + if (RuntimeConfig.IsDebug && jsErrorOccurred) { + // Log(@"Debug mode - JavaScript errors detected, entering infinite run loop to prevent app termination"); + + while (true) { + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, true); + tns::Tasks::Drain(); + } + // Note: This line is never reached in debug mode with errors + } + + // Normal execution path (no errors or release mode) + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true); + tns::Tasks::Drain(); } - (bool)liveSync { - if (runtime_ == nullptr) { - return false; - } + if (runtime_ == nullptr) { + return false; + } - Isolate* isolate = runtime_->GetIsolate(); - return tns::LiveSync(isolate); + Isolate* isolate = runtime_->GetIsolate(); + return tns::LiveSync(isolate); } - (void)shutdownRuntime { - if (RuntimeConfig.IsDebug) { - Console::DetachInspectorClient(); - } - tns::Tasks::ClearTasks(); - if (runtime_ != nullptr) { - runtime_ = nullptr; - } + if (RuntimeConfig.IsDebug) { + Console::DetachInspectorClient(); + } + tns::Tasks::ClearTasks(); + if (runtime_ != nullptr) { + runtime_ = nullptr; + } } - (instancetype)initializeWithConfig:(Config*)config { - if (self = [super init]) { - RuntimeConfig.BaseDir = [config.BaseDir UTF8String]; - if (config.ApplicationPath != nil) { - RuntimeConfig.ApplicationPath = [[config.BaseDir stringByAppendingPathComponent:config.ApplicationPath] UTF8String]; - } else { - RuntimeConfig.ApplicationPath = [[config.BaseDir stringByAppendingPathComponent:@"app"] UTF8String]; - } - if (config.MetadataPtr != nil) { - RuntimeConfig.MetadataPtr = [config MetadataPtr]; - } else { - RuntimeConfig.MetadataPtr = &defaultStartOfMetadataSection; - } - RuntimeConfig.IsDebug = [config IsDebug]; - RuntimeConfig.LogToSystemConsole = [config LogToSystemConsole]; - - Runtime::Initialize(); - runtime_ = nullptr; - runtime_ = std::make_unique(); - - std::chrono::high_resolution_clock::time_point t1 = std::chrono::high_resolution_clock::now(); - Isolate* isolate = runtime_->CreateIsolate(); - v8::Locker l(isolate); - runtime_->Init(isolate); - std::chrono::high_resolution_clock::time_point t2 = std::chrono::high_resolution_clock::now(); - auto duration = std::chrono::duration_cast(t2 - t1).count(); - printf("Runtime initialization took %llims (version %s, V8 version %s)\n", duration, NATIVESCRIPT_VERSION, V8::GetVersion()); - - if (config.IsDebug) { - Isolate::Scope isolate_scope(isolate); - HandleScope handle_scope(isolate); - v8_inspector::JsV8InspectorClient* inspectorClient = new v8_inspector::JsV8InspectorClient(runtime_.get()); - inspectorClient->init(); - inspectorClient->registerModules(); - inspectorClient->connect([config ArgumentsCount], [config Arguments]); - Console::AttachInspectorClient(inspectorClient); - } + if (self = [super init]) { + RuntimeConfig.BaseDir = [config.BaseDir UTF8String]; + if (config.ApplicationPath != nil) { + RuntimeConfig.ApplicationPath = + [[config.BaseDir stringByAppendingPathComponent:config.ApplicationPath] UTF8String]; + } else { + RuntimeConfig.ApplicationPath = + [[config.BaseDir stringByAppendingPathComponent:@"app"] UTF8String]; + } + if (config.MetadataPtr != nil) { + RuntimeConfig.MetadataPtr = [config MetadataPtr]; + } else { + RuntimeConfig.MetadataPtr = &defaultStartOfMetadataSection; + } + RuntimeConfig.IsDebug = [config IsDebug]; + RuntimeConfig.LogToSystemConsole = [config LogToSystemConsole]; + + Runtime::Initialize(); + runtime_ = nullptr; + runtime_ = std::make_unique(); + + std::chrono::high_resolution_clock::time_point t1 = std::chrono::high_resolution_clock::now(); + Isolate* isolate = runtime_->CreateIsolate(); + v8::Locker l(isolate); + runtime_->Init(isolate); + std::chrono::high_resolution_clock::time_point t2 = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::duration_cast(t2 - t1).count(); + printf("Runtime initialization took %llims (version %s, V8 version %s)\n", duration, + NATIVESCRIPT_VERSION, V8::GetVersion()); + + if (config.IsDebug) { + Isolate::Scope isolate_scope(isolate); + HandleScope handle_scope(isolate); + v8_inspector::JsV8InspectorClient* inspectorClient = + new v8_inspector::JsV8InspectorClient(runtime_.get()); + inspectorClient->init(); + inspectorClient->registerModules(); + inspectorClient->connect([config ArgumentsCount], [config Arguments]); + Console::AttachInspectorClient(inspectorClient); } - return self; - + } + return self; } - (instancetype)initWithConfig:(Config*)config { - return [self initializeWithConfig:config]; + return [self initializeWithConfig:config]; } - (void)restartWithConfig:(Config*)config { - [self shutdownRuntime]; - [self initializeWithConfig:config]; + [self shutdownRuntime]; + [self initializeWithConfig:config]; } - - @end diff --git a/NativeScript/inspector/JsV8InspectorClient.mm b/NativeScript/inspector/JsV8InspectorClient.mm index 2b4e404c..bd13422b 100644 --- a/NativeScript/inspector/JsV8InspectorClient.mm +++ b/NativeScript/inspector/JsV8InspectorClient.mm @@ -12,6 +12,7 @@ #include "Helpers.h" #include "InspectorServer.h" #include "JsV8InspectorClient.h" +#include "RuntimeConfig.h" #include "include/libplatform/libplatform.h" #include "utils.h" @@ -392,7 +393,26 @@ { v8::Locker locker(isolate); TryCatch tc(isolate); - runtime_->RunModule("inspector_modules"); + + // Check for ES module (.mjs) first, then fallback to CommonJS (.js) + NSString* appPath = [NSString stringWithUTF8String:RuntimeConfig.ApplicationPath.c_str()]; + NSString* mjsPath = + [[appPath stringByAppendingPathComponent:@"tns_modules/inspector_modules.mjs"] + stringByStandardizingPath]; + NSString* jsPath = [[appPath stringByAppendingPathComponent:@"tns_modules/inspector_modules.js"] + stringByStandardizingPath]; + + std::string modulePath; + if ([[NSFileManager defaultManager] fileExistsAtPath:mjsPath]) { + modulePath = [mjsPath UTF8String]; + } else if ([[NSFileManager defaultManager] fileExistsAtPath:jsPath]) { + modulePath = [jsPath UTF8String]; + } else { + // No inspector modules found, skip loading + return; + } + + runtime_->RunModule(modulePath); // FIXME: This triggers some DCHECK failures, due to the entered v8::Context in // Runtime::init(). } diff --git a/NativeScript/runtime/Console.cpp b/NativeScript/runtime/Console.cpp index c355587c..f352c885 100644 --- a/NativeScript/runtime/Console.cpp +++ b/NativeScript/runtime/Console.cpp @@ -1,8 +1,14 @@ #include "Console.h" -#include "Caches.h" + #include #include +#include +#include +#include + +#include "Caches.h" #include "Helpers.h" +#include "NativeScriptException.h" #include "RuntimeConfig.h" // #include "v8-log-agent-impl.h" #include @@ -12,383 +18,461 @@ using namespace v8; namespace tns { void Console::Init(Local context) { - Isolate* isolate = context->GetIsolate(); - Context::Scope context_scope(context); - Local console = Object::New(isolate); - bool success = console->SetPrototype(context, Object::New(isolate)).FromMaybe(false); - tns::Assert(success, isolate); + Isolate* isolate = context->GetIsolate(); + Context::Scope context_scope(context); + Local console = Object::New(isolate); + bool success = + console->SetPrototype(context, Object::New(isolate)).FromMaybe(false); + tns::Assert(success, isolate); + + Console::AttachLogFunction(context, console, "log"); + Console::AttachLogFunction(context, console, "info"); + Console::AttachLogFunction(context, console, "error"); + Console::AttachLogFunction(context, console, "warn"); + Console::AttachLogFunction(context, console, "trace"); + Console::AttachLogFunction(context, console, "assert", AssertCallback); + Console::AttachLogFunction(context, console, "dir", DirCallback); + Console::AttachLogFunction(context, console, "time", TimeCallback); + Console::AttachLogFunction(context, console, "timeEnd", TimeEndCallback); + + Local global = context->Global(); + PropertyAttribute readOnlyFlags = static_cast( + PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); + if (!global + ->DefineOwnProperty(context, tns::ToV8String(isolate, "console"), + console, readOnlyFlags) + .FromMaybe(false)) { + tns::Assert(false, isolate); + } +} - Console::AttachLogFunction(context, console, "log"); - Console::AttachLogFunction(context, console, "info"); - Console::AttachLogFunction(context, console, "error"); - Console::AttachLogFunction(context, console, "warn"); - Console::AttachLogFunction(context, console, "trace"); - Console::AttachLogFunction(context, console, "assert", AssertCallback); - Console::AttachLogFunction(context, console, "dir", DirCallback); - Console::AttachLogFunction(context, console, "time", TimeCallback); - Console::AttachLogFunction(context, console, "timeEnd", TimeEndCallback); - - Local global = context->Global(); - PropertyAttribute readOnlyFlags = static_cast(PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); - if (!global->DefineOwnProperty(context, tns::ToV8String(isolate, "console"), console, readOnlyFlags).FromMaybe(false)) { - tns::Assert(false, isolate); - } +void Console::AttachInspectorClient( + v8_inspector::JsV8InspectorClient* aInspector) { + inspector = aInspector; } -void Console::AttachInspectorClient(v8_inspector::JsV8InspectorClient* aInspector) { - inspector = aInspector; +void Console::DetachInspectorClient() { inspector = nullptr; } + +bool isErrorMessage(const std::string& line) { + return line.find("Error") != std::string::npos; } -void Console::DetachInspectorClient() { - inspector = nullptr; +bool isStackFrame(const std::string& line) { + // Recognize both styles: + // " at foo (/path/to/file.ts:123:45)" -> with parentheses + // " at /path/to/file.ts:123:45" -> bare location + static const std::regex withParens(R"(\s+at\s+.*\(.+?:\d+:\d+\))"); + static const std::regex bare(R"(\s+at\s+[^\s\(\)]+:\d+:\d+)"); + return std::regex_search(line, withParens) || std::regex_search(line, bare); } void Console::LogCallback(const FunctionCallbackInfo& args) { - // TODO: implement 'forceLog' override option like android has, to force logs in prod if desired - if (!RuntimeConfig.LogToSystemConsole) { - return; + // TODO: implement 'forceLog' override option like android has, to force logs + // in prod if desired + if (!RuntimeConfig.LogToSystemConsole) { + return; + } + + Isolate* isolate = args.GetIsolate(); + std::string stringResult = BuildStringFromArgs(args); + // Log("stringResult %s", stringResult.c_str()); + + Local data = args.Data().As(); + std::string verbosityLevel = tns::ToString(isolate, data); + + // Compute remapped payload ONCE and use it for both the modal and terminal + // so they always match exactly. + bool hasStackTrace = isStackFrame(stringResult); + std::string processedStringResult = stringResult; + if (hasStackTrace) { + processedStringResult = tns::RemapStackTraceIfAvailable(isolate, processedStringResult); + } + + std::string verbosityLevelUpper = verbosityLevel; + std::transform(verbosityLevelUpper.begin(), verbosityLevelUpper.end(), + verbosityLevelUpper.begin(), ::toupper); + + std::stringstream ss; + ss << processedStringResult; + + if (verbosityLevel == "trace") { + std::string stacktrace = tns::GetStackTrace(isolate); + ss << std::endl << stacktrace << std::endl; + } + + std::string msgToLog = ss.str(); + + ConsoleAPIType method = VerbosityToInspectorMethod(verbosityLevel); + SendToDevToolsFrontEnd(method, args); + std::string msgWithVerbosity = + "CONSOLE " + verbosityLevelUpper + ": " + msgToLog; + Log("%s", msgWithVerbosity.c_str()); + + if (RuntimeConfig.IsDebug && Runtime::showErrorDisplay() && verbosityLevel == "error" && hasStackTrace) { + try { + // Log("Console.cpp: Forwarding console payload to error display: %s", msgToLog.c_str()); + NativeScriptException::SubmitConsoleErrorPayload(isolate, msgToLog); + } catch (const std::exception& e) { + Log("Console.cpp: Exception updating modal: %s", e.what()); + } catch (...) { + Log("Console.cpp: Unknown exception updating modal"); } + } +} - Isolate* isolate = args.GetIsolate(); - std::string stringResult = BuildStringFromArgs(args); +void Console::AssertCallback(const FunctionCallbackInfo& args) { + if (!RuntimeConfig.LogToSystemConsole) { + return; + } - Local data = args.Data().As(); - std::string verbosityLevel = tns::ToString(isolate, data); - std::string verbosityLevelUpper = verbosityLevel; - std::transform(verbosityLevelUpper.begin(), verbosityLevelUpper.end(), verbosityLevelUpper.begin(), ::toupper); + Isolate* isolate = args.GetIsolate(); + int argsLength = args.Length(); + bool expressionPasses = argsLength > 0 && args[0]->BooleanValue(isolate); + if (!expressionPasses) { std::stringstream ss; - ss << stringResult; - - if (verbosityLevel == "trace") { - std::string stacktrace = tns::GetStackTrace(isolate); - ss << std::endl << stacktrace << std::endl; - } - std::string msgToLog = ss.str(); - - ConsoleAPIType method = VerbosityToInspectorMethod(verbosityLevel); - SendToDevToolsFrontEnd(method, args); - std::string msgWithVerbosity = "CONSOLE " + verbosityLevelUpper + ": " + msgToLog; - Log("%s", msgWithVerbosity.c_str()); -} + ss << "Assertion failed: "; -void Console::AssertCallback(const FunctionCallbackInfo& args) { - if (!RuntimeConfig.LogToSystemConsole) { - return; + if (argsLength > 1) { + ss << BuildStringFromArgs(args, 1); + } else { + ss << "console.assert"; } - Isolate* isolate = args.GetIsolate(); - - int argsLength = args.Length(); - bool expressionPasses = argsLength > 0 && args[0]->BooleanValue(isolate); - if (!expressionPasses) { - std::stringstream ss; - - ss << "Assertion failed: "; + std::string log = ss.str(); - if (argsLength > 1) { - ss << BuildStringFromArgs(args, 1); - } else { - ss << "console.assert"; - } - - std::string log = ss.str(); - - SendToDevToolsFrontEnd(ConsoleAPIType::kAssert, args); - Log("%s", log.c_str()); - } + SendToDevToolsFrontEnd(ConsoleAPIType::kAssert, args); + Log("%s", log.c_str()); + } } void Console::DirCallback(const FunctionCallbackInfo& args) { - if (!RuntimeConfig.LogToSystemConsole) { - return; - } - - int argsLen = args.Length(); - Isolate* isolate = args.GetIsolate(); - Local context = isolate->GetCurrentContext(); - - std::stringstream ss; - std::string scriptUrl = tns::GetCurrentScriptUrl(isolate); - ss << scriptUrl << ":"; + if (!RuntimeConfig.LogToSystemConsole) { + return; + } + + int argsLen = args.Length(); + Isolate* isolate = args.GetIsolate(); + Local context = isolate->GetCurrentContext(); + + std::stringstream ss; + std::string scriptUrl = tns::GetCurrentScriptUrl(isolate); + ss << scriptUrl << ":"; + + if (argsLen > 0) { + if (!args[0]->IsObject()) { + std::string logString = BuildStringFromArgs(args); + ss << " " << logString; + } else { + ss << std::endl << "==== object dump start ====" << std::endl; + Local argObject = args[0].As(); + + Local propNames; + bool success = argObject->GetPropertyNames(context).ToLocal(&propNames); + tns::Assert(success, isolate); + uint32_t propertiesLength = propNames->Length(); + for (uint32_t i = 0; i < propertiesLength; i++) { + Local propertyName = propNames->Get(context, i).ToLocalChecked(); + Local propertyValue; + bool success = + argObject->Get(context, propertyName).ToLocal(&propertyValue); + if (!success || propertyValue.IsEmpty() || + propertyValue->IsUndefined()) { + continue; + } - if (argsLen > 0) { - if (!args[0]->IsObject()) { - std::string logString = BuildStringFromArgs(args); - ss << " " << logString; + bool propIsFunction = propertyValue->IsFunction(); + + ss << tns::ToString(isolate, + propertyName->ToString(context).ToLocalChecked()) + << ": "; + + if (propIsFunction) { + ss << "()"; + } else if (propertyValue->IsArray()) { + Local stringResult = + BuildStringFromArg(context, propertyValue); + std::string jsonStringifiedArray = + tns::ToString(isolate, stringResult); + ss << jsonStringifiedArray; + } else if (propertyValue->IsObject()) { + Local obj = propertyValue->ToObject(context).ToLocalChecked(); + Local objString = TransformJSObject(obj); + std::string jsonStringifiedObject = tns::ToString(isolate, objString); + // if object prints out as the error string for circular references, + // replace with #CR instead for brevity + if (jsonStringifiedObject.find("circular structure") != + std::string::npos) { + jsonStringifiedObject = "#CR"; + } + ss << jsonStringifiedObject; } else { - ss << std::endl << "==== object dump start ====" << std::endl; - Local argObject = args[0].As(); - - Local propNames; - bool success = argObject->GetPropertyNames(context).ToLocal(&propNames); - tns::Assert(success, isolate); - uint32_t propertiesLength = propNames->Length(); - for (uint32_t i = 0; i < propertiesLength; i++) { - Local propertyName = propNames->Get(context, i).ToLocalChecked(); - Local propertyValue; - bool success = argObject->Get(context, propertyName).ToLocal(&propertyValue); - if (!success || propertyValue.IsEmpty() || propertyValue->IsUndefined()) { - continue; - } - - bool propIsFunction = propertyValue->IsFunction(); - - ss << tns::ToString(isolate, propertyName->ToString(context).ToLocalChecked()) << ": "; - - if (propIsFunction) { - ss << "()"; - } else if (propertyValue->IsArray()) { - Local stringResult = BuildStringFromArg(context, propertyValue); - std::string jsonStringifiedArray = tns::ToString(isolate, stringResult); - ss << jsonStringifiedArray; - } else if (propertyValue->IsObject()) { - Local obj = propertyValue->ToObject(context).ToLocalChecked(); - Local objString = TransformJSObject(obj); - std::string jsonStringifiedObject = tns::ToString(isolate, objString); - // if object prints out as the error string for circular references, replace with #CR instead for brevity - if (jsonStringifiedObject.find("circular structure") != std::string::npos) { - jsonStringifiedObject = "#CR"; - } - ss << jsonStringifiedObject; - } else { - ss << "\"" << tns::ToString(isolate, propertyValue->ToDetailString(context).ToLocalChecked()) << "\""; - } - - ss << std::endl; - } - - ss << "==== object dump end ====" << std::endl; + ss << "\"" + << tns::ToString( + isolate, + propertyValue->ToDetailString(context).ToLocalChecked()) + << "\""; } - } else { - ss << ""; + + ss << std::endl; + } + + ss << "==== object dump end ====" << std::endl; } + } else { + ss << ""; + } - std::string msgToLog = ss.str(); - SendToDevToolsFrontEnd(ConsoleAPIType::kDir, args); - Log("%s", msgToLog.c_str()); + std::string msgToLog = ss.str(); + SendToDevToolsFrontEnd(ConsoleAPIType::kDir, args); + Log("%s", msgToLog.c_str()); } void Console::TimeCallback(const FunctionCallbackInfo& args) { - if (!RuntimeConfig.LogToSystemConsole) { - return; - } + if (!RuntimeConfig.LogToSystemConsole) { + return; + } - Isolate* isolate = args.GetIsolate(); - Local context = isolate->GetCurrentContext(); - std::string label = "default"; + Isolate* isolate = args.GetIsolate(); + Local context = isolate->GetCurrentContext(); + std::string label = "default"; - Local labelString; - if (args.Length() > 0 && args[0]->ToString(context).ToLocal(&labelString)) { - label = tns::ToString(isolate, labelString); - } + Local labelString; + if (args.Length() > 0 && args[0]->ToString(context).ToLocal(&labelString)) { + label = tns::ToString(isolate, labelString); + } - std::shared_ptr cache = Caches::Get(isolate); + std::shared_ptr cache = Caches::Get(isolate); - auto nano = std::chrono::time_point_cast(std::chrono::system_clock::now()); - double timeStamp = nano.time_since_epoch().count(); + auto nano = std::chrono::time_point_cast( + std::chrono::system_clock::now()); + double timeStamp = nano.time_since_epoch().count(); - cache->Timers.emplace(label, timeStamp); + cache->Timers.emplace(label, timeStamp); } void Console::TimeEndCallback(const FunctionCallbackInfo& args) { - if (!RuntimeConfig.LogToSystemConsole) { - return; - } - - Isolate* isolate = args.GetIsolate(); - Local context = isolate->GetCurrentContext(); - std::string label = "default"; - - Local labelString; - if (args.Length() > 0 && args[0]->ToString(context).ToLocal(&labelString)) { - label = tns::ToString(isolate, labelString); - } - - std::shared_ptr cache = Caches::Get(isolate); - auto itTimersMap = cache->Timers.find(label); - if (itTimersMap == cache->Timers.end()) { - std::string warning = std::string("No such label '" + label + "' for console.timeEnd()"); - Log("%s", warning.c_str()); - return; - } - - auto nano = std::chrono::time_point_cast(std::chrono::system_clock::now()); - double endTimeStamp = nano.time_since_epoch().count(); - double startTimeStamp = itTimersMap->second; - - cache->Timers.erase(label); - - double diffMicroseconds = endTimeStamp - startTimeStamp; - double diffMilliseconds = diffMicroseconds / 1000.0; - - std::stringstream ss; - ss << "CONSOLE INFO " << label << ": " << std::fixed << std::setprecision(3) << diffMilliseconds << "ms" ; - - std::string msgToLog = ss.str(); - SendToDevToolsFrontEnd(isolate, ConsoleAPIType::kTimeEnd, msgToLog); - Log("%s", msgToLog.c_str()); + if (!RuntimeConfig.LogToSystemConsole) { + return; + } + + Isolate* isolate = args.GetIsolate(); + Local context = isolate->GetCurrentContext(); + std::string label = "default"; + + Local labelString; + if (args.Length() > 0 && args[0]->ToString(context).ToLocal(&labelString)) { + label = tns::ToString(isolate, labelString); + } + + std::shared_ptr cache = Caches::Get(isolate); + auto itTimersMap = cache->Timers.find(label); + if (itTimersMap == cache->Timers.end()) { + std::string warning = + std::string("No such label '" + label + "' for console.timeEnd()"); + Log("%s", warning.c_str()); + return; + } + + auto nano = std::chrono::time_point_cast( + std::chrono::system_clock::now()); + double endTimeStamp = nano.time_since_epoch().count(); + double startTimeStamp = itTimersMap->second; + + cache->Timers.erase(label); + + double diffMicroseconds = endTimeStamp - startTimeStamp; + double diffMilliseconds = diffMicroseconds / 1000.0; + + std::stringstream ss; + ss << "CONSOLE INFO " << label << ": " << std::fixed << std::setprecision(3) + << diffMilliseconds << "ms"; + + std::string msgToLog = ss.str(); + SendToDevToolsFrontEnd(isolate, ConsoleAPIType::kTimeEnd, msgToLog); + Log("%s", msgToLog.c_str()); } -void Console::AttachLogFunction(Local context, Local console, const std::string name, v8::FunctionCallback callback) { - Isolate* isolate = context->GetIsolate(); - - Local func; - if (!Function::New(context, callback, tns::ToV8String(isolate, name), 0, ConstructorBehavior::kThrow).ToLocal(&func)) { - tns::Assert(false, isolate); - } - - Local logFuncName = tns::ToV8String(isolate, name); - func->SetName(logFuncName); - if (!console->CreateDataProperty(context, logFuncName, func).FromMaybe(false)) { - tns::Assert(false, isolate); - } +void Console::AttachLogFunction(Local context, Local console, + const std::string name, + v8::FunctionCallback callback) { + Isolate* isolate = context->GetIsolate(); + + Local func; + if (!Function::New(context, callback, tns::ToV8String(isolate, name), 0, + ConstructorBehavior::kThrow) + .ToLocal(&func)) { + tns::Assert(false, isolate); + } + + Local logFuncName = tns::ToV8String(isolate, name); + func->SetName(logFuncName); + if (!console->CreateDataProperty(context, logFuncName, func) + .FromMaybe(false)) { + tns::Assert(false, isolate); + } } -std::string Console::BuildStringFromArgs(const FunctionCallbackInfo& args, int startingIndex) { - Isolate* isolate = args.GetIsolate(); - Local context = isolate->GetCurrentContext(); - int argLen = args.Length(); - std::stringstream ss; +std::string Console::BuildStringFromArgs( + const FunctionCallbackInfo& args, int startingIndex) { + Isolate* isolate = args.GetIsolate(); + Local context = isolate->GetCurrentContext(); + int argLen = args.Length(); + std::stringstream ss; - if (argLen > 0) { - for (int i = startingIndex; i < argLen; i++) { - Local argString; + if (argLen > 0) { + for (int i = startingIndex; i < argLen; i++) { + Local argString; - argString = BuildStringFromArg(context, args[i]); + argString = BuildStringFromArg(context, args[i]); - // separate args with a space - if (i != startingIndex) { - ss << " "; - } + // separate args with a space + if (i != startingIndex) { + ss << " "; + } - ss << tns::ToString(isolate, argString); - } - } else { - ss << std::endl; + ss << tns::ToString(isolate, argString); } + } else { + ss << std::endl; + } - std::string stringResult = ss.str(); - return stringResult; + std::string stringResult = ss.str(); + return stringResult; } -const Local Console::BuildStringFromArg(Local context, const Local& val) { - Isolate* isolate = context->GetIsolate(); - Local argString; - if (val->IsFunction()) { - bool success = val->ToDetailString(context).ToLocal(&argString); - tns::Assert(success, isolate); - } else if (val->IsArray()) { - Local cachedSelf = val; - Local array = val->ToObject(context).ToLocalChecked(); - Local arrayEntryKeys = array->GetPropertyNames(context).ToLocalChecked(); +const Local Console::BuildStringFromArg(Local context, + const Local& val) { + Isolate* isolate = context->GetIsolate(); + Local argString; + if (val->IsFunction()) { + bool success = val->ToDetailString(context).ToLocal(&argString); + tns::Assert(success, isolate); + } else if (val->IsArray()) { + Local cachedSelf = val; + Local array = val->ToObject(context).ToLocalChecked(); + Local arrayEntryKeys = + array->GetPropertyNames(context).ToLocalChecked(); - uint32_t arrayLength = arrayEntryKeys->Length(); + uint32_t arrayLength = arrayEntryKeys->Length(); - argString = tns::ToV8String(isolate, "["); + argString = tns::ToV8String(isolate, "["); - for (int i = 0; i < arrayLength; i++) { - Local propertyName = arrayEntryKeys->Get(context, i).ToLocalChecked(); + for (int i = 0; i < arrayLength; i++) { + Local propertyName = + arrayEntryKeys->Get(context, i).ToLocalChecked(); - Local propertyValue = array->Get(context, propertyName).ToLocalChecked(); + Local propertyValue = + array->Get(context, propertyName).ToLocalChecked(); - // avoid bottomless recursion with cyclic reference to the same array - if (propertyValue->StrictEquals(cachedSelf)) { - argString = v8::String::Concat(isolate, argString, tns::ToV8String(isolate, "[Circular]")); - continue; - } + // avoid bottomless recursion with cyclic reference to the same array + if (propertyValue->StrictEquals(cachedSelf)) { + argString = v8::String::Concat(isolate, argString, + tns::ToV8String(isolate, "[Circular]")); + continue; + } - Local objectString = BuildStringFromArg(context, propertyValue); + Local objectString = + BuildStringFromArg(context, propertyValue); - argString = v8::String::Concat(isolate, argString, objectString); + argString = v8::String::Concat(isolate, argString, objectString); - if (i != arrayLength - 1) { - argString = v8::String::Concat(isolate, argString, tns::ToV8String(isolate, ", ")); - } - } + if (i != arrayLength - 1) { + argString = v8::String::Concat(isolate, argString, + tns::ToV8String(isolate, ", ")); + } + } - argString = v8::String::Concat(isolate, argString, tns::ToV8String(isolate, "]")); - } else if (val->IsObject()) { - Local obj = val.As(); + argString = + v8::String::Concat(isolate, argString, tns::ToV8String(isolate, "]")); + } else if (val->IsObject()) { + Local obj = val.As(); - argString = TransformJSObject(obj); - } else { - bool success = val->ToDetailString(isolate->GetCurrentContext()).ToLocal(&argString); - tns::Assert(success, isolate); - } + argString = TransformJSObject(obj); + } else { + bool success = + val->ToDetailString(isolate->GetCurrentContext()).ToLocal(&argString); + tns::Assert(success, isolate); + } - return argString; + return argString; } const Local Console::TransformJSObject(Local object) { - Local context; - bool success = object->GetCreationContext().ToLocal(&context); - tns::Assert(success); - Isolate* isolate = context->GetIsolate(); - Local value; - { - TryCatch tc(isolate); - bool success = object->ToString(context).ToLocal(&value); - if (!success) { - return tns::ToV8String(isolate, ""); - } + Local context; + bool success = object->GetCreationContext().ToLocal(&context); + tns::Assert(success); + Isolate* isolate = context->GetIsolate(); + Local value; + { + TryCatch tc(isolate); + bool success = object->ToString(context).ToLocal(&value); + if (!success) { + return tns::ToV8String(isolate, ""); } - Local objToString = value.As(); + } + Local objToString = value.As(); - Local resultString; - bool hasCustomToStringImplementation = tns::ToString(isolate, objToString).find("[object Object]") == std::string::npos; + Local resultString; + bool hasCustomToStringImplementation = + tns::ToString(isolate, objToString).find("[object Object]") == + std::string::npos; - if (hasCustomToStringImplementation) { - resultString = objToString; - } else { - resultString = tns::JsonStringifyObject(context, object); - } + if (hasCustomToStringImplementation) { + resultString = objToString; + } else { + resultString = tns::JsonStringifyObject(context, object); + } - return resultString; + return resultString; } -v8_inspector::ConsoleAPIType Console::VerbosityToInspectorMethod(const std::string level) { - if (level == "error") { - return ConsoleAPIType::kError; - } else if (level == "warn") { - return ConsoleAPIType::kWarning; - } else if (level == "info") { - return ConsoleAPIType::kInfo; - } else if (level == "trace") { - return ConsoleAPIType::kTrace; - } - - assert(level == "log"); - return ConsoleAPIType::kLog; +v8_inspector::ConsoleAPIType Console::VerbosityToInspectorMethod( + const std::string level) { + if (level == "error") { + return ConsoleAPIType::kError; + } else if (level == "warn") { + return ConsoleAPIType::kWarning; + } else if (level == "info") { + return ConsoleAPIType::kInfo; + } else if (level == "trace") { + return ConsoleAPIType::kTrace; + } + + assert(level == "log"); + return ConsoleAPIType::kLog; } -void Console::SendToDevToolsFrontEnd(ConsoleAPIType method, - const v8::FunctionCallbackInfo& args) { - if (!inspector) { - return; - } - - std::vector> arg_vector; - unsigned nargs = args.Length(); - arg_vector.reserve(nargs); - for (unsigned ix = 0; ix < nargs; ix++) - arg_vector.push_back(args[ix]); - - inspector->consoleLog(args.GetIsolate(), method, arg_vector); -} +void Console::SendToDevToolsFrontEnd( + ConsoleAPIType method, const v8::FunctionCallbackInfo& args) { + if (!inspector) { + return; + } -void Console::SendToDevToolsFrontEnd(v8::Isolate* isolate, ConsoleAPIType method, const std::string& msg) { - if (!inspector) { - return; - } + std::vector> arg_vector; + unsigned nargs = args.Length(); + arg_vector.reserve(nargs); + for (unsigned ix = 0; ix < nargs; ix++) arg_vector.push_back(args[ix]); + + inspector->consoleLog(args.GetIsolate(), method, arg_vector); +} - v8::Local v8str = v8::String::NewFromUtf8( - isolate, msg.c_str(), v8::NewStringType::kNormal, -1).ToLocalChecked(); - std::vector> args{v8str}; - inspector->consoleLog(isolate, method, args); +void Console::SendToDevToolsFrontEnd(v8::Isolate* isolate, + ConsoleAPIType method, + const std::string& msg) { + if (!inspector) { + return; + } + + v8::Local v8str = + v8::String::NewFromUtf8(isolate, msg.c_str(), v8::NewStringType::kNormal, + -1) + .ToLocalChecked(); + std::vector> args{v8str}; + inspector->consoleLog(isolate, method, args); } v8_inspector::JsV8InspectorClient* Console::inspector = nullptr; -} +} // namespace tns diff --git a/NativeScript/runtime/DataWrapper.h b/NativeScript/runtime/DataWrapper.h index 9b9c05f3..ceb47b17 100644 --- a/NativeScript/runtime/DataWrapper.h +++ b/NativeScript/runtime/DataWrapper.h @@ -659,6 +659,10 @@ class WorkerWrapper: public BaseDataWrapper { void Start(std::shared_ptr> poWorker, std::function func); void CallOnErrorHandlers(v8::TryCatch& tc); void PassUncaughtExceptionFromWorkerToMain(v8::Local context, v8::TryCatch& tc, bool async = true); + // Overload to pass a pre-built error payload when a TryCatch isn't available + // Note: this overload accepts only primitive types to avoid passing V8 handles + // across isolates/threads. + void PassUncaughtExceptionFromWorkerToMain(const std::string& message, const std::string& source, const std::string& stackTrace, int lineNumber, bool async = true); void PostMessage(std::shared_ptr message); void Close(); void Terminate(); diff --git a/NativeScript/runtime/Helpers.h b/NativeScript/runtime/Helpers.h index d24a43a9..41b725d6 100644 --- a/NativeScript/runtime/Helpers.h +++ b/NativeScript/runtime/Helpers.h @@ -333,6 +333,17 @@ const std::string BuildStacktraceFrameMessage(v8::Isolate* isolate, const std::string GetStackTrace(v8::Isolate* isolate); const std::string GetCurrentScriptUrl(v8::Isolate* isolate); +// Returns stack trace string remapped to original sources using global __ns_remapStack if present. +std::string RemapStackTraceIfAvailable(v8::Isolate* isolate, const std::string& stackTrace); + +// Smart stack extraction that prefers: +// 1) exception.stack if provided +// 2) TryCatch.StackTrace / Message()->GetStackTrace when TryCatch provided +// 3) Current stack via GetStackTrace +std::string GetSmartStackTrace(v8::Isolate* isolate, + v8::TryCatch* tryCatch = nullptr, + v8::Local exception = v8::Local()); + bool LiveSync(v8::Isolate* isolate); void Assert(bool condition, v8::Isolate* isolate = nullptr, diff --git a/NativeScript/runtime/Helpers.mm b/NativeScript/runtime/Helpers.mm index 12f7709a..321edb9c 100644 --- a/NativeScript/runtime/Helpers.mm +++ b/NativeScript/runtime/Helpers.mm @@ -1,334 +1,339 @@ +#include "Helpers.h" #include +#include #include -#include +#include #include -#include +#include +#include #include +#include #include -#include #include -#include -#include -#include "RuntimeConfig.h" -#include "Runtime.h" -#include "Helpers.h" #include "Caches.h" +#include "NativeScriptException.h" +#include "Runtime.h" +#include "RuntimeConfig.h" using namespace v8; namespace { - const int BUFFER_SIZE = 1024 * 1024; - char* Buffer = new char[BUFFER_SIZE]; - uint8_t* BinBuffer = new uint8_t[BUFFER_SIZE]; +const int BUFFER_SIZE = 1024 * 1024; +char* Buffer = new char[BUFFER_SIZE]; +uint8_t* BinBuffer = new uint8_t[BUFFER_SIZE]; } std::u16string tns::ToUtf16String(Isolate* isolate, const Local& value) { - std::string valueStr = tns::ToString(isolate, value); + std::string valueStr = tns::ToString(isolate, value); #pragma GCC diagnostic ignored "-Wdeprecated-declarations" - // FIXME: std::codecvt_utf8_utf16 is deprecated - std::wstring_convert, char16_t> convert; - std::u16string value16 = convert.from_bytes(valueStr); + // FIXME: std::codecvt_utf8_utf16 is deprecated + std::wstring_convert, char16_t> convert; + std::u16string value16 = convert.from_bytes(valueStr); - return value16; + return value16; } std::vector tns::ToVector(const std::string& value) { #pragma GCC diagnostic ignored "-Wdeprecated-declarations" - // FIXME: std::codecvt_utf8_utf16 is deprecated - std::wstring_convert, char16_t> convert; - std::u16string value16 = convert.from_bytes(value); + // FIXME: std::codecvt_utf8_utf16 is deprecated + std::wstring_convert, char16_t> convert; + std::u16string value16 = convert.from_bytes(value); - const uint16_t *begin = reinterpret_cast(value16.data()); - const uint16_t *end = reinterpret_cast(value16.data() + value16.size()); - std::vector vector(begin, end); - return vector; + const uint16_t* begin = reinterpret_cast(value16.data()); + const uint16_t* end = reinterpret_cast(value16.data() + value16.size()); + std::vector vector(begin, end); + return vector; } bool tns::Exists(const char* fullPath) { - struct stat statbuf; - mode_t mode = S_IFDIR | S_IFREG; - if (stat(fullPath, &statbuf) == 0) { - return (statbuf.st_mode & S_IFMT) & mode; - } + struct stat statbuf; + mode_t mode = S_IFDIR | S_IFREG; + if (stat(fullPath, &statbuf) == 0) { + return (statbuf.st_mode & S_IFMT) & mode; + } - return false; + return false; } -Local tns::ReadModule(Isolate* isolate, const std::string &filePath) { - struct stat finfo; +Local tns::ReadModule(Isolate* isolate, const std::string& filePath) { + struct stat finfo; - int file = open(filePath.c_str(), O_RDONLY); - if (file < 0) { - tns::Assert(false); - } + int file = open(filePath.c_str(), O_RDONLY); + if (file < 0) { + throw NativeScriptException(isolate, "Cannot read module " + filePath); + } - fstat(file, &finfo); - long length = finfo.st_size; + fstat(file, &finfo); + long length = finfo.st_size; - char* newBuffer = new char[length + 128]; - strcpy(newBuffer, "(function(module, exports, require, __filename, __dirname) { "); // 61 Characters - read(file, &newBuffer[61], length); - close(file); - length += 61; + char* newBuffer = new char[length + 128]; + strcpy(newBuffer, + "(function(module, exports, require, __filename, __dirname) { "); // 61 Characters + read(file, &newBuffer[61], length); + close(file); + length += 61; - // Add the closing "\n})" - newBuffer[length] = 10; - ++length; - newBuffer[length] = '}'; - ++length; - newBuffer[length] = ')'; - ++length; - newBuffer[length] = 0; + // Add the closing "\n})" + newBuffer[length] = 10; + ++length; + newBuffer[length] = '}'; + ++length; + newBuffer[length] = ')'; + ++length; + newBuffer[length] = 0; - Local str = v8::String::NewFromUtf8(isolate, newBuffer, NewStringType::kNormal, (int)length).ToLocalChecked(); - delete[] newBuffer; + Local str = + v8::String::NewFromUtf8(isolate, newBuffer, NewStringType::kNormal, (int)length) + .ToLocalChecked(); + delete[] newBuffer; - return str; + return str; } const char* tns::ReadText(const std::string& filePath, long& length, bool& isNew) { - FILE* file = fopen(filePath.c_str(), "rb"); - if (file == nullptr) { - tns::Assert(false); - } + FILE* file = fopen(filePath.c_str(), "rb"); + if (file == nullptr) { + tns::Assert(false); + } - fseek(file, 0, SEEK_END); + fseek(file, 0, SEEK_END); - length = ftell(file); - isNew = length > BUFFER_SIZE; + length = ftell(file); + isNew = length > BUFFER_SIZE; - rewind(file); + rewind(file); - if (isNew) { - char* newBuffer = new char[length]; - fread(newBuffer, 1, length, file); - fclose(file); + if (isNew) { + char* newBuffer = new char[length]; + fread(newBuffer, 1, length, file); + fclose(file); - return newBuffer; - } + return newBuffer; + } - fread(Buffer, 1, length, file); - fclose(file); + fread(Buffer, 1, length, file); + fclose(file); - return Buffer; + return Buffer; } std::string tns::ReadText(const std::string& file) { - long length; - bool isNew; - const char* content = tns::ReadText(file, length, isNew); + long length; + bool isNew; + const char* content = tns::ReadText(file, length, isNew); - std::string result(content, length); + std::string result(content, length); - if (isNew) { - delete[] content; - } + if (isNew) { + delete[] content; + } - return result; + return result; } uint8_t* tns::ReadBinary(const std::string path, long& length, bool& isNew) { - length = 0; - std::ifstream ifs(path); - if (ifs.fail()) { - return nullptr; - } - - FILE* file = fopen(path.c_str(), "rb"); - if (!file) { - return nullptr; - } - - fseek(file, 0, SEEK_END); - length = ftell(file); - rewind(file); - - isNew = length > BUFFER_SIZE; - - if (isNew) { - uint8_t* data = new uint8_t[length]; - fread(data, sizeof(uint8_t), length, file); - fclose(file); - return data; - } - - fread(BinBuffer, 1, length, file); + length = 0; + std::ifstream ifs(path); + if (ifs.fail()) { + return nullptr; + } + + FILE* file = fopen(path.c_str(), "rb"); + if (!file) { + return nullptr; + } + + fseek(file, 0, SEEK_END); + length = ftell(file); + rewind(file); + + isNew = length > BUFFER_SIZE; + + if (isNew) { + uint8_t* data = new uint8_t[length]; + fread(data, sizeof(uint8_t), length, file); fclose(file); + return data; + } + + fread(BinBuffer, 1, length, file); + fclose(file); - return BinBuffer; + return BinBuffer; } bool tns::WriteBinary(const std::string& path, const void* data, long length) { - FILE* file = fopen(path.c_str(), "wb"); - if (!file) { - return false; - } + FILE* file = fopen(path.c_str(), "wb"); + if (!file) { + return false; + } - size_t writtenBytes = fwrite(data, sizeof(uint8_t), length, file); - fclose(file); + size_t writtenBytes = fwrite(data, sizeof(uint8_t), length, file); + fclose(file); - return writtenBytes == length; + return writtenBytes == length; } -void tns::SetPrivateValue(const Local& obj, const Local& propName, const Local& value) { - Local context; - bool success = obj->GetCreationContext().ToLocal(&context); - tns::Assert(success); - Isolate* isolate = context->GetIsolate(); - Local privateKey = Private::ForApi(isolate, propName); +void tns::SetPrivateValue(const Local& obj, const Local& propName, + const Local& value) { + Local context; + bool success = obj->GetCreationContext().ToLocal(&context); + tns::Assert(success); + Isolate* isolate = context->GetIsolate(); + Local privateKey = Private::ForApi(isolate, propName); - if (!obj->SetPrivate(context, privateKey, value).To(&success) || !success) { - tns::Assert(false, isolate); - } + if (!obj->SetPrivate(context, privateKey, value).To(&success) || !success) { + tns::Assert(false, isolate); + } } Local tns::GetPrivateValue(const Local& obj, const Local& propName) { - Local context; - bool success = obj->GetCreationContext().ToLocal(&context); - tns::Assert(success); - Isolate* isolate = context->GetIsolate(); - Local privateKey = Private::ForApi(isolate, propName); + Local context; + bool success = obj->GetCreationContext().ToLocal(&context); + tns::Assert(success); + Isolate* isolate = context->GetIsolate(); + Local privateKey = Private::ForApi(isolate, propName); - Maybe hasPrivate = obj->HasPrivate(context, privateKey); + Maybe hasPrivate = obj->HasPrivate(context, privateKey); - tns::Assert(!hasPrivate.IsNothing(), isolate); + tns::Assert(!hasPrivate.IsNothing(), isolate); - if (!hasPrivate.FromMaybe(false)) { - return Local(); - } + if (!hasPrivate.FromMaybe(false)) { + return Local(); + } - v8::Locker locker(isolate); - Local result; - if (!obj->GetPrivate(context, privateKey).ToLocal(&result)) { - tns::Assert(false, isolate); - } + v8::Locker locker(isolate); + Local result; + if (!obj->GetPrivate(context, privateKey).ToLocal(&result)) { + tns::Assert(false, isolate); + } - return result; + return result; } bool tns::DeleteWrapperIfUnused(Isolate* isolate, const Local& obj, BaseDataWrapper* value) { - if (GetValue(isolate, obj) != value) { - delete value; - return true; - } - return false; + if (GetValue(isolate, obj) != value) { + delete value; + return true; + } + return false; } void tns::SetValue(Isolate* isolate, const Local& obj, BaseDataWrapper* value) { - if (obj.IsEmpty() || obj->IsNullOrUndefined()) { - return; - } + if (obj.IsEmpty() || obj->IsNullOrUndefined()) { + return; + } - Local ext = External::New(isolate, value); + Local ext = External::New(isolate, value); - if (obj->InternalFieldCount() > 0) { - obj->SetInternalField(0, ext); - } else { - tns::SetPrivateValue(obj, tns::ToV8String(isolate, "metadata"), ext); - } + if (obj->InternalFieldCount() > 0) { + obj->SetInternalField(0, ext); + } else { + tns::SetPrivateValue(obj, tns::ToV8String(isolate, "metadata"), ext); + } } tns::BaseDataWrapper* tns::GetValue(Isolate* isolate, const Local& val) { - if (val.IsEmpty() || val->IsNullOrUndefined() || !val->IsObject()) { - return nullptr; - } + if (val.IsEmpty() || val->IsNullOrUndefined() || !val->IsObject()) { + return nullptr; + } - Local obj = val.As(); - if (obj->InternalFieldCount() > 0) { - Local field = obj->GetInternalField(0); - if (field.IsEmpty() || field->IsNullOrUndefined() || !field->IsExternal()) { - return nullptr; - } - - return static_cast(field.As()->Value()); + Local obj = val.As(); + if (obj->InternalFieldCount() > 0) { + Local field = obj->GetInternalField(0); + if (field.IsEmpty() || field->IsNullOrUndefined() || !field->IsExternal()) { + return nullptr; } - Local metadataProp = tns::GetPrivateValue(obj, tns::ToV8String(isolate, "metadata")); - if (metadataProp.IsEmpty() || metadataProp->IsNullOrUndefined() || !metadataProp->IsExternal()) { - return nullptr; - } + return static_cast(field.As()->Value()); + } + + Local metadataProp = tns::GetPrivateValue(obj, tns::ToV8String(isolate, "metadata")); + if (metadataProp.IsEmpty() || metadataProp->IsNullOrUndefined() || !metadataProp->IsExternal()) { + return nullptr; + } - return static_cast(metadataProp.As()->Value()); + return static_cast(metadataProp.As()->Value()); } void tns::DeleteValue(Isolate* isolate, const Local& val) { - if (val.IsEmpty() || val->IsNullOrUndefined() || !val->IsObject()) { - return; - } + if (val.IsEmpty() || val->IsNullOrUndefined() || !val->IsObject()) { + return; + } - Local obj = val.As(); - if (obj->InternalFieldCount() > 0) { - obj->SetInternalField(0, v8::Undefined(isolate)); - return; - } + Local obj = val.As(); + if (obj->InternalFieldCount() > 0) { + obj->SetInternalField(0, v8::Undefined(isolate)); + return; + } - Local metadataKey = tns::ToV8String(isolate, "metadata"); - Local metadataProp = tns::GetPrivateValue(obj, metadataKey); - if (metadataProp.IsEmpty() || metadataProp->IsNullOrUndefined() || !metadataProp->IsExternal()) { - return; - } + Local metadataKey = tns::ToV8String(isolate, "metadata"); + Local metadataProp = tns::GetPrivateValue(obj, metadataKey); + if (metadataProp.IsEmpty() || metadataProp->IsNullOrUndefined() || !metadataProp->IsExternal()) { + return; + } - Local context; - bool success = obj->GetCreationContext().ToLocal(&context); - tns::Assert(success, isolate); - Local privateKey = Private::ForApi(isolate, metadataKey); + Local context; + bool success = obj->GetCreationContext().ToLocal(&context); + tns::Assert(success, isolate); + Local privateKey = Private::ForApi(isolate, metadataKey); - success = obj->DeletePrivate(context, privateKey).FromMaybe(false); - tns::Assert(success, isolate); + success = obj->DeletePrivate(context, privateKey).FromMaybe(false); + tns::Assert(success, isolate); } std::vector> tns::ArgsToVector(const FunctionCallbackInfo& info) { - std::vector> args; - args.reserve(info.Length()); - for (int i = 0; i < info.Length(); i++) { - args.push_back(info[i]); - } - return args; + std::vector> args; + args.reserve(info.Length()); + for (int i = 0; i < info.Length(); i++) { + args.push_back(info[i]); + } + return args; } bool tns::IsArrayOrArrayLike(Isolate* isolate, const Local& value) { - if (value->IsArray()) { - return true; - } + if (value->IsArray()) { + return true; + } - if (!value->IsObject()) { - return false; - } + if (!value->IsObject()) { + return false; + } - Local obj = value.As(); - Local context; - bool success = obj->GetCreationContext().ToLocal(&context); - tns::Assert(success, isolate); - return obj->Has(context, ToV8String(isolate, "length")).FromMaybe(false); + Local obj = value.As(); + Local context; + bool success = obj->GetCreationContext().ToLocal(&context); + tns::Assert(success, isolate); + return obj->Has(context, ToV8String(isolate, "length")).FromMaybe(false); } void* tns::TryGetBufferFromArrayBuffer(const v8::Local& value, bool& isArrayBuffer) { - isArrayBuffer = false; + isArrayBuffer = false; - if (value.IsEmpty() || (!value->IsArrayBuffer() && !value->IsArrayBufferView())) { - return nullptr; - } + if (value.IsEmpty() || (!value->IsArrayBuffer() && !value->IsArrayBufferView())) { + return nullptr; + } - Local buffer; - if (value->IsArrayBufferView()) { - isArrayBuffer = true; - buffer = value.As()->Buffer(); - } else if (value->IsArrayBuffer()) { - isArrayBuffer = true; - buffer = value.As(); - } + Local buffer; + if (value->IsArrayBufferView()) { + isArrayBuffer = true; + buffer = value.As()->Buffer(); + } else if (value->IsArrayBuffer()) { + isArrayBuffer = true; + buffer = value.As(); + } - if (buffer.IsEmpty()) { - return nullptr; - } + if (buffer.IsEmpty()) { + return nullptr; + } - void* data = buffer->GetBackingStore()->Data(); - return data; + void* data = buffer->GetBackingStore()->Data(); + return data; } struct LockAndCV { - std::mutex m; - std::condition_variable cv; + std::mutex m; + std::condition_variable cv; }; void tns::ExecuteOnRunLoop(CFRunLoopRef queue, void (^func)(void), bool async) { @@ -355,604 +360,582 @@ } } -void tns::ExecuteOnDispatchQueue(dispatch_queue_t queue, std::function func, bool async) { - if (async) { - dispatch_async(queue, ^(void) { - func(); - }); - } else { - dispatch_sync(queue, ^(void) { - func(); - }); - } +void tns::ExecuteOnDispatchQueue(dispatch_queue_t queue, std::function func, bool async) { + if (async) { + dispatch_async(queue, ^(void) { + func(); + }); + } else { + dispatch_sync(queue, ^(void) { + func(); + }); + } } -void tns::ExecuteOnMainThread(std::function func, bool async) { - if (async) { - dispatch_async(dispatch_get_main_queue(), ^(void) { - func(); - }); - } else { - dispatch_sync(dispatch_get_main_queue(), ^(void) { - func(); - }); - } +void tns::ExecuteOnMainThread(std::function func, bool async) { + if (async) { + dispatch_async(dispatch_get_main_queue(), ^(void) { + func(); + }); + } else { + dispatch_sync(dispatch_get_main_queue(), ^(void) { + func(); + }); + } } void tns::LogError(Isolate* isolate, TryCatch& tc) { - if (!tc.HasCaught()) { - return; - } - - Log(@"Native stack trace:"); - LogBacktrace(); - - Local stack; - Local context = isolate->GetCurrentContext(); - bool success = tc.StackTrace(context).ToLocal(&stack); - if (!success || stack.IsEmpty()) { - return; - } - - Local stackV8Str; - success = stack->ToDetailString(context).ToLocal(&stackV8Str); - if (!success || stackV8Str.IsEmpty()) { - return; - } - - std::string stackTraceStr = tns::ToString(isolate, stackV8Str); - stackTraceStr = ReplaceAll(stackTraceStr, RuntimeConfig.BaseDir, ""); - - Log(@"JavaScript error:"); - Log(@"%s", stackTraceStr.c_str()); -} - -Local tns::JsonStringifyObject(Local context, Local value, bool handleCircularReferences) { - Isolate* isolate = context->GetIsolate(); - if (value.IsEmpty()) { - return v8::String::Empty(isolate); - } - - if (handleCircularReferences) { - Local smartJSONStringifyFunction = tns::GetSmartJSONStringifyFunction(isolate); - - if (!smartJSONStringifyFunction.IsEmpty()) { - if (value->IsObject()) { - Local resultValue; - TryCatch tc(isolate); - - Local args[] = { - value->ToObject(context).ToLocalChecked() - }; - bool success = smartJSONStringifyFunction->Call(context, v8::Undefined(isolate), 1, args).ToLocal(&resultValue); - - if (success && !tc.HasCaught()) { - return resultValue->ToString(context).ToLocalChecked(); - } - } + if (!tc.HasCaught()) { + return; + } + + Log(@"Native stack trace:"); + LogBacktrace(); + + Local stack; + Local context = isolate->GetCurrentContext(); + bool success = tc.StackTrace(context).ToLocal(&stack); + if (!success || stack.IsEmpty()) { + return; + } + + Local stackV8Str; + success = stack->ToDetailString(context).ToLocal(&stackV8Str); + if (!success || stackV8Str.IsEmpty()) { + return; + } + + std::string stackTraceStr = tns::ToString(isolate, stackV8Str); + stackTraceStr = ReplaceAll(stackTraceStr, RuntimeConfig.BaseDir, ""); + + Log(@"JavaScript error:"); + Log(@"%s", stackTraceStr.c_str()); +} + +Local tns::JsonStringifyObject(Local context, Local value, + bool handleCircularReferences) { + Isolate* isolate = context->GetIsolate(); + if (value.IsEmpty()) { + return v8::String::Empty(isolate); + } + + if (handleCircularReferences) { + Local smartJSONStringifyFunction = tns::GetSmartJSONStringifyFunction(isolate); + + if (!smartJSONStringifyFunction.IsEmpty()) { + if (value->IsObject()) { + Local resultValue; + TryCatch tc(isolate); + + Local args[] = {value->ToObject(context).ToLocalChecked()}; + bool success = smartJSONStringifyFunction->Call(context, v8::Undefined(isolate), 1, args) + .ToLocal(&resultValue); + + if (success && !tc.HasCaught()) { + return resultValue->ToString(context).ToLocalChecked(); } + } } + } - Local resultString; - TryCatch tc(isolate); - bool success = v8::JSON::Stringify(context, value->ToObject(context).ToLocalChecked()).ToLocal(&resultString); + Local resultString; + TryCatch tc(isolate); + bool success = v8::JSON::Stringify(context, value->ToObject(context).ToLocalChecked()) + .ToLocal(&resultString); - if (!success && tc.HasCaught()) { - tns::LogError(isolate, tc); - return Local(); - } + if (!success && tc.HasCaught()) { + tns::LogError(isolate, tc); + return Local(); + } - return resultString; + return resultString; } Local tns::GetSmartJSONStringifyFunction(Isolate* isolate) { - std::shared_ptr caches = Caches::Get(isolate); - if (caches->SmartJSONStringifyFunc != nullptr) { - return caches->SmartJSONStringifyFunc->Get(isolate); - } - - std::string smartStringifyFunctionScript = - "(function () {\n" - " function smartStringify(object) {\n" - " const seen = [];\n" - " var replacer = function (key, value) {\n" - " if (value != null && typeof value == \"object\") {\n" - " if (seen.indexOf(value) >= 0) {\n" - " if (key) {\n" - " return \"[Circular]\";\n" - " }\n" - " return;\n" - " }\n" - " seen.push(value);\n" - " }\n" - " return value;\n" - " };\n" - " return JSON.stringify(object, replacer, 2);\n" - " }\n" - " return smartStringify;\n" - "})();"; - - Local source = tns::ToV8String(isolate, smartStringifyFunctionScript); - Local context = isolate->GetCurrentContext(); - - Local