diff --git a/NativeScript/NativeScript-Prefix.pch b/NativeScript/NativeScript-Prefix.pch index 1ce57293..c5437ae1 100644 --- a/NativeScript/NativeScript-Prefix.pch +++ b/NativeScript/NativeScript-Prefix.pch @@ -1,7 +1,7 @@ #ifndef NativeScript_Prefix_pch #define NativeScript_Prefix_pch -#define NATIVESCRIPT_VERSION "8.9.3" +#define NATIVESCRIPT_VERSION "9.0.0-alpha.6" #ifdef DEBUG #define SIZEOF_OFF_T 8 diff --git a/NativeScript/NativeScript.mm b/NativeScript/NativeScript.mm index 0aea242c..67a0c4d4 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,113 @@ @implementation NativeScript extern char defaultStartOfMetadataSection __asm("section$start$__DATA$__TNSMetadata"); -- (void)runScriptString: (NSString*) script runLoop: (BOOL) runLoop { - - std::string cppString = std::string([script UTF8String]); - runtime_->RunScript(cppString); - - if (runLoop) { - CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true); - } +- (void)runScriptString:(NSString*)script runLoop:(BOOL)runLoop { + std::string cppString = std::string([script UTF8String]); + runtime_->RunScript(cppString); + if (runLoop) { + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true); + } - tns::Tasks::Drain(); - + tns::Tasks::Drain(); } std::unique_ptr runtime_; - (void)runMainApplication { - runtime_->RunMainScript(); + runtime_->RunMainScript(); + + // 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) { + // NSLog(@"🔧 Debug mode - JavaScript errors detected, hijacking main thread to keep app alive"); + // NSLog(@"🔧 Debug mode - Entering infinite run loop to prevent app termination"); + // NSLog(@"🔧 Debug mode - Hot-reload and error modal should work normally"); + + // Main thread hijack: Enter infinite run loop to keep app alive + while (true) { + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, true); + tns::Tasks::Drain(); + } + // Note: This line is never reached in debug mode with errors + } - CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true); - tns::Tasks::Drain(); + // 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]; } - return self; - + 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; } - (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 25c0c54f..1f1e13cf 100644 --- a/NativeScript/inspector/JsV8InspectorClient.mm +++ b/NativeScript/inspector/JsV8InspectorClient.mm @@ -8,526 +8,572 @@ #include "src/inspector/v8-runtime-agent-impl.h" #include "src/inspector/v8-stack-trace-impl.h" -#include "JsV8InspectorClient.h" +#include "Caches.h" +#include "Helpers.h" #include "InspectorServer.h" +#include "JsV8InspectorClient.h" +#include "RuntimeConfig.h" #include "include/libplatform/libplatform.h" -#include "Helpers.h" #include "utils.h" -#include "Caches.h" using namespace v8; namespace v8_inspector { -#define NOTIFICATION(name) \ -[[NSString stringWithFormat:@"%@:NativeScript.Debug.%s", \ - [[NSBundle mainBundle] bundleIdentifier], name] UTF8String] +#define NOTIFICATION(name) \ + [[NSString stringWithFormat:@"%@:NativeScript.Debug.%s", \ + [[NSBundle mainBundle] bundleIdentifier], name] UTF8String] -#define LOG_DEBUGGER_PORT(port) NSLog(@"NativeScript debugger has opened inspector socket on port %d for %@.", port, [[NSBundle mainBundle] bundleIdentifier]) +#define LOG_DEBUGGER_PORT(port) \ + NSLog(@"NativeScript debugger has opened inspector socket on port %d for %@.", port, \ + [[NSBundle mainBundle] bundleIdentifier]) JsV8InspectorClient::JsV8InspectorClient(tns::Runtime* runtime) - : runtime_(runtime), - isolate_(runtime_->GetIsolate()), - messages_(), - runningNestedLoops_(false) { - this->messagesQueue_ = dispatch_queue_create("NativeScript.v8.inspector.message_queue", DISPATCH_QUEUE_SERIAL); - this->messageLoopQueue_ = dispatch_queue_create("NativeScript.v8.inspector.message_loop_queue", DISPATCH_QUEUE_SERIAL); - this->messageArrived_ = dispatch_semaphore_create(0); + : runtime_(runtime), isolate_(runtime_->GetIsolate()), messages_(), runningNestedLoops_(false) { + this->messagesQueue_ = + dispatch_queue_create("NativeScript.v8.inspector.message_queue", DISPATCH_QUEUE_SERIAL); + this->messageLoopQueue_ = + dispatch_queue_create("NativeScript.v8.inspector.message_loop_queue", DISPATCH_QUEUE_SERIAL); + this->messageArrived_ = dispatch_semaphore_create(0); } void JsV8InspectorClient::enableInspector(int argc, char** argv) { - int waitForDebuggerSubscription; - notify_register_dispatch(NOTIFICATION("WaitForDebugger"), &waitForDebuggerSubscription, dispatch_get_main_queue(), ^(int token) { + int waitForDebuggerSubscription; + notify_register_dispatch( + NOTIFICATION("WaitForDebugger"), &waitForDebuggerSubscription, dispatch_get_main_queue(), + ^(int token) { this->isWaitingForDebugger_ = YES; dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 30); dispatch_after(delay, dispatch_get_main_queue(), ^{ - if (this->isWaitingForDebugger_) { - this->isWaitingForDebugger_ = NO; - NSLog(@"NativeScript waiting for debugger timeout elapsed. Continuing execution."); - } + if (this->isWaitingForDebugger_) { + this->isWaitingForDebugger_ = NO; + NSLog(@"NativeScript waiting for debugger timeout elapsed. Continuing execution."); + } }); NSLog(@"NativeScript waiting for debugger."); CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopDefaultMode, ^{ - do { - CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, false); - } while (this->isWaitingForDebugger_); + do { + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, false); + } while (this->isWaitingForDebugger_); }); CFRunLoopWakeUp(CFRunLoopGetMain()); - }); + }); - int attachRequestSubscription; - notify_register_dispatch(NOTIFICATION("AttachRequest"), &attachRequestSubscription, dispatch_get_main_queue(), ^(int token) { - in_port_t listenPort = InspectorServer::Init([this](std::function sender) { - this->onFrontendConnected(sender); - }, [this](std::string message) { - this->onFrontendMessageReceived(message); - }); + int attachRequestSubscription; + notify_register_dispatch( + NOTIFICATION("AttachRequest"), &attachRequestSubscription, dispatch_get_main_queue(), + ^(int token) { + in_port_t listenPort = InspectorServer::Init( + [this](std::function sender) { this->onFrontendConnected(sender); }, + [this](std::string message) { this->onFrontendMessageReceived(message); }); LOG_DEBUGGER_PORT(listenPort); notify_post(NOTIFICATION("ReadyForAttach")); - }); + }); - notify_post(NOTIFICATION("AppLaunching")); + notify_post(NOTIFICATION("AppLaunching")); - for (int i = 1; i < argc; i++) { - BOOL startListening = NO; - BOOL shouldWaitForDebugger = NO; + for (int i = 1; i < argc; i++) { + BOOL startListening = NO; + BOOL shouldWaitForDebugger = NO; - if (strcmp(argv[i], "--nativescript-debug-brk") == 0) { - shouldWaitForDebugger = YES; - } else if (strcmp(argv[i], "--nativescript-debug-start") == 0) { - startListening = YES; - } + if (strcmp(argv[i], "--nativescript-debug-brk") == 0) { + shouldWaitForDebugger = YES; + } else if (strcmp(argv[i], "--nativescript-debug-start") == 0) { + startListening = YES; + } - if (startListening || shouldWaitForDebugger) { - notify_post(NOTIFICATION("AttachRequest")); - if (shouldWaitForDebugger) { - notify_post(NOTIFICATION("WaitForDebugger")); - } + if (startListening || shouldWaitForDebugger) { + notify_post(NOTIFICATION("AttachRequest")); + if (shouldWaitForDebugger) { + notify_post(NOTIFICATION("WaitForDebugger")); + } - break; - } + break; } + } - CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.5, false); - notify_cancel(waitForDebuggerSubscription); + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.5, false); + notify_cancel(waitForDebuggerSubscription); } -void JsV8InspectorClient::onFrontendConnected(std::function sender) { - if (this->isWaitingForDebugger_) { - this->isWaitingForDebugger_ = NO; - CFRunLoopRef runloop = CFRunLoopGetMain(); - CFRunLoopPerformBlock(runloop, (__bridge CFTypeRef)(NSRunLoopCommonModes), ^{ - CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1, false); - this->scheduleBreak(); - }); - CFRunLoopWakeUp(runloop); - } +void JsV8InspectorClient::onFrontendConnected(std::function sender) { + if (this->isWaitingForDebugger_) { + this->isWaitingForDebugger_ = NO; + CFRunLoopRef runloop = CFRunLoopGetMain(); + CFRunLoopPerformBlock(runloop, (__bridge CFTypeRef)(NSRunLoopCommonModes), ^{ + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1, false); + this->scheduleBreak(); + }); + CFRunLoopWakeUp(runloop); + } - this->sender_ = sender; + this->sender_ = sender; - // this triggers a reconnection from the devtools so Debugger.scriptParsed etc. are all fired again - this->disconnect(); - this->isConnected_ = true; + // this triggers a reconnection from the devtools so Debugger.scriptParsed etc. are all fired + // again + this->disconnect(); + this->isConnected_ = true; } void JsV8InspectorClient::onFrontendMessageReceived(std::string message) { - dispatch_sync(this->messagesQueue_, ^{ - this->messages_.push(message); - dispatch_semaphore_signal(messageArrived_); - }); - - tns::ExecuteOnMainThread([this, message]() { - dispatch_sync(this->messageLoopQueue_, ^{ - // prevent execution if we're already pumping messages - if (runningNestedLoops_ && !terminated_) { - return; - }; - std::string message; - do { - message = this->PumpMessage(); - if (!message.empty()) { - this->dispatchMessage(message); - } - } while (!message.empty()); - }); + dispatch_sync(this->messagesQueue_, ^{ + this->messages_.push(message); + dispatch_semaphore_signal(messageArrived_); + }); + tns::ExecuteOnMainThread([this, message]() { + dispatch_sync(this->messageLoopQueue_, ^{ + // prevent execution if we're already pumping messages + if (runningNestedLoops_ && !terminated_) { + return; + }; + std::string message; + do { + message = this->PumpMessage(); + if (!message.empty()) { + this->dispatchMessage(message); + } + } while (!message.empty()); }); + }); } void JsV8InspectorClient::init() { - if (inspector_ != nullptr) { - return; - } + if (inspector_ != nullptr) { + return; + } - Isolate* isolate = isolate_; + Isolate* isolate = isolate_; - Local context = isolate->GetEnteredOrMicrotaskContext(); + Local context = isolate->GetEnteredOrMicrotaskContext(); - inspector_ = V8Inspector::create(isolate, this); + inspector_ = V8Inspector::create(isolate, this); - inspector_->contextCreated(v8_inspector::V8ContextInfo(context, JsV8InspectorClient::contextGroupId, {})); + inspector_->contextCreated( + v8_inspector::V8ContextInfo(context, JsV8InspectorClient::contextGroupId, {})); - context_.Reset(isolate, context); + context_.Reset(isolate, context); - this->createInspectorSession(); - - tracing_agent_.reset(new tns::inspector::TracingAgentImpl()); + this->createInspectorSession(); + + tracing_agent_.reset(new tns::inspector::TracingAgentImpl()); } void JsV8InspectorClient::connect(int argc, char** argv) { - this->isConnected_ = true; - this->enableInspector(argc, argv); + this->isConnected_ = true; + this->enableInspector(argc, argv); } void JsV8InspectorClient::createInspectorSession() { - this->session_ = this->inspector_->connect(JsV8InspectorClient::contextGroupId, this, {}); + this->session_ = this->inspector_->connect(JsV8InspectorClient::contextGroupId, this, {}); } void JsV8InspectorClient::disconnect() { - Isolate* isolate = isolate_; - v8::Locker locker(isolate); - Isolate::Scope isolate_scope(isolate); - HandleScope handle_scope(isolate); + Isolate* isolate = isolate_; + v8::Locker locker(isolate); + Isolate::Scope isolate_scope(isolate); + HandleScope handle_scope(isolate); - session_->resume(); - session_.reset(); + session_->resume(); + session_.reset(); - this->isConnected_ = false; + this->isConnected_ = false; - this->createInspectorSession(); + this->createInspectorSession(); } void JsV8InspectorClient::runMessageLoopOnPause(int contextGroupId) { - __block auto loopsRunning = false; - dispatch_sync(this->messageLoopQueue_, ^{ - loopsRunning = runningNestedLoops_; - terminated_ = false; - if (runningNestedLoops_) { - return; - } - this->runningNestedLoops_ = true; - }); - - if (loopsRunning) { - return; + __block auto loopsRunning = false; + dispatch_sync(this->messageLoopQueue_, ^{ + loopsRunning = runningNestedLoops_; + terminated_ = false; + if (runningNestedLoops_) { + return; + } + this->runningNestedLoops_ = true; + }); + + if (loopsRunning) { + return; + } + + bool shouldWait = false; + while (!terminated_) { + std::string message = this->PumpMessage(); + if (!message.empty()) { + this->dispatchMessage(message); + shouldWait = false; + } else { + shouldWait = true; } - - bool shouldWait = false; - while (!terminated_) { - std::string message = this->PumpMessage(); - if (!message.empty()) { - this->dispatchMessage(message); - shouldWait = false; - } else { - shouldWait = true; - } - std::shared_ptr platform = tns::Runtime::GetPlatform(); - Isolate* isolate = isolate_; - platform::PumpMessageLoop(platform.get(), isolate, platform::MessageLoopBehavior::kDoNotWait); - if(shouldWait && !terminated_) { - dispatch_semaphore_wait(messageArrived_, dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_MSEC)); // 1ms - } + std::shared_ptr platform = tns::Runtime::GetPlatform(); + Isolate* isolate = isolate_; + platform::PumpMessageLoop(platform.get(), isolate, platform::MessageLoopBehavior::kDoNotWait); + if (shouldWait && !terminated_) { + dispatch_semaphore_wait(messageArrived_, + dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_MSEC)); // 1ms } - - dispatch_sync(this->messageLoopQueue_, ^{ - terminated_ = false; - runningNestedLoops_ = false; - }); + } + + dispatch_sync(this->messageLoopQueue_, ^{ + terminated_ = false; + runningNestedLoops_ = false; + }); } void JsV8InspectorClient::quitMessageLoopOnPause() { - dispatch_sync(this->messageLoopQueue_, ^{ - terminated_ = true; - }); + dispatch_sync(this->messageLoopQueue_, ^{ + terminated_ = true; + }); } void JsV8InspectorClient::sendResponse(int callId, std::unique_ptr message) { - this->notify(std::move(message)); + this->notify(std::move(message)); } void JsV8InspectorClient::sendNotification(std::unique_ptr message) { - this->notify(std::move(message)); + this->notify(std::move(message)); } -void JsV8InspectorClient::flushProtocolNotifications() { -} +void JsV8InspectorClient::flushProtocolNotifications() {} void JsV8InspectorClient::notify(std::unique_ptr message) { - StringView stringView = message->string(); - std::string value = ToStdString(stringView); + StringView stringView = message->string(); + std::string value = ToStdString(stringView); - if (this->sender_) { - this->sender_(value); - } + if (this->sender_) { + this->sender_(value); + } } void JsV8InspectorClient::dispatchMessage(const std::string& message) { - std::vector vector = tns::ToVector(message); - StringView messageView(vector.data(), vector.size()); - Isolate* isolate = isolate_; - v8::Locker locker(isolate); - Isolate::Scope isolate_scope(isolate); - v8::HandleScope handle_scope(isolate); - Local context = tns::Caches::Get(isolate)->GetContext(); - bool success; - - // livesync uses the inspector socket for HMR/LiveSync... - if(message.find("Page.reload") != std::string::npos) { - success = tns::LiveSync(this->isolate_); - if (!success) { - NSLog(@"LiveSync failed"); - } - // todo: should we return here, or is it OK to pass onto a possible Page.reload domain handler? + std::vector vector = tns::ToVector(message); + StringView messageView(vector.data(), vector.size()); + Isolate* isolate = isolate_; + v8::Locker locker(isolate); + Isolate::Scope isolate_scope(isolate); + v8::HandleScope handle_scope(isolate); + Local context = tns::Caches::Get(isolate)->GetContext(); + bool success; + + // livesync uses the inspector socket for HMR/LiveSync... + if (message.find("Page.reload") != std::string::npos) { + success = tns::LiveSync(this->isolate_); + if (!success) { + NSLog(@"LiveSync failed"); } - - if(message.find("Tracing.start") != std::string::npos) { - tracing_agent_->start(); - - // echo back the request to notify frontend the action was a success - // todo: send an empty response for the incoming message id instead. - this->sendNotification(StringBuffer::create(messageView)); - return; - } - - if(message.find("Tracing.end") != std::string::npos) { - tracing_agent_->end(); - std::string res = tracing_agent_->getLastTrace(); - tracing_agent_->SendToDevtools(context, res); - return; + // todo: should we return here, or is it OK to pass onto a possible Page.reload domain handler? + } + + if (message.find("Tracing.start") != std::string::npos) { + tracing_agent_->start(); + + // echo back the request to notify frontend the action was a success + // todo: send an empty response for the incoming message id instead. + this->sendNotification(StringBuffer::create(messageView)); + return; + } + + if (message.find("Tracing.end") != std::string::npos) { + tracing_agent_->end(); + std::string res = tracing_agent_->getLastTrace(); + tracing_agent_->SendToDevtools(context, res); + return; + } + + // parse incoming message as JSON + Local arg; + success = v8::JSON::Parse(context, tns::ToV8String(isolate, message)).ToLocal(&arg); + + // stop processing invalid messages + if (!success) { + NSLog(@"Inspector failed to parse incoming message: %s", message.c_str()); + // ignore failures to parse. + return; + } + + // Pass incoming message to a registerd domain handler if any + if (!arg.IsEmpty() && arg->IsObject()) { + Local domainDebugger; + Local argObject = arg.As(); + Local domainMethodFunc = + v8_inspector::GetDebuggerFunctionFromObject(context, argObject, domainDebugger); + + Local result; + success = this->CallDomainHandlerFunction(context, domainMethodFunc, argObject, domainDebugger, + result); + + if (success) { + auto requestId = + arg.As()->Get(context, tns::ToV8String(isolate, "id")).ToLocalChecked(); + auto returnString = GetReturnMessageFromDomainHandlerResult(result, requestId); + + if (returnString.size() > 0) { + std::vector vector = tns::ToVector(returnString); + StringView messageView(vector.data(), vector.size()); + auto msg = StringBuffer::create(messageView); + this->sendNotification(std::move(msg)); + } + return; } + } - // parse incoming message as JSON - Local arg; - success = v8::JSON::Parse(context, tns::ToV8String(isolate, message)).ToLocal(&arg); - - // stop processing invalid messages - if(!success) { - NSLog(@"Inspector failed to parse incoming message: %s", message.c_str()); - // ignore failures to parse. - return; - } - - // Pass incoming message to a registerd domain handler if any - if(!arg.IsEmpty() && arg->IsObject()) { - Local domainDebugger; - Local argObject = arg.As(); - Local domainMethodFunc = v8_inspector::GetDebuggerFunctionFromObject(context, argObject, domainDebugger); - - Local result; - success = this->CallDomainHandlerFunction(context, domainMethodFunc, argObject, domainDebugger, result); - - if(success) { - auto requestId = arg.As()->Get(context, tns::ToV8String(isolate, "id")).ToLocalChecked(); - auto returnString = GetReturnMessageFromDomainHandlerResult(result, requestId); - - if(returnString.size() > 0) { - std::vector vector = tns::ToVector(returnString); - StringView messageView(vector.data(), vector.size()); - auto msg = StringBuffer::create(messageView); - this->sendNotification(std::move(msg)); - } - return; - } - } + // if no handler handled the message successfully, fall-through to the default V8 implementation + this->session_->dispatchProtocolMessage(messageView); - // if no handler handled the message successfully, fall-through to the default V8 implementation - this->session_->dispatchProtocolMessage(messageView); - - // TODO: check why this is needed (it should trigger automatically when script depth is 0) - isolate->PerformMicrotaskCheckpoint(); + // TODO: check why this is needed (it should trigger automatically when script depth is 0) + isolate->PerformMicrotaskCheckpoint(); } Local JsV8InspectorClient::ensureDefaultContextInGroup(int contextGroupId) { - return context_.Get(isolate_); + return context_.Get(isolate_); } std::string JsV8InspectorClient::PumpMessage() { - __block std::string result; - dispatch_sync(this->messagesQueue_, ^{ - if (this->messages_.size() > 0) { - result = this->messages_.front(); - this->messages_.pop(); - } - }); + __block std::string result; + dispatch_sync(this->messagesQueue_, ^{ + if (this->messages_.size() > 0) { + result = this->messages_.front(); + this->messages_.pop(); + } + }); - return result; + return result; } void JsV8InspectorClient::scheduleBreak() { - Isolate* isolate = isolate_; - v8::Locker locker(isolate); - Isolate::Scope isolate_scope(isolate); - HandleScope handle_scope(isolate); - auto context = isolate->GetCurrentContext(); - Context::Scope context_scope(context); - - if(!this->hasScheduledDebugBreak_) { - this->hasScheduledDebugBreak_ = true; - // hack: force a debugger; statement in ModuleInternal to actually break before loading the next (main) script... - // FIXME: find a proper fix to not need to resort to this hack. - context->Global()->Set(context, tns::ToV8String(isolate, "__pauseOnNextRequire"), v8::Boolean::New(isolate, true)).ToChecked(); - } - - this->session_->schedulePauseOnNextStatement({}, {}); + Isolate* isolate = isolate_; + v8::Locker locker(isolate); + Isolate::Scope isolate_scope(isolate); + HandleScope handle_scope(isolate); + auto context = isolate->GetCurrentContext(); + Context::Scope context_scope(context); + + if (!this->hasScheduledDebugBreak_) { + this->hasScheduledDebugBreak_ = true; + // hack: force a debugger; statement in ModuleInternal to actually break before loading the next + // (main) script... + // FIXME: find a proper fix to not need to resort to this hack. + context->Global() + ->Set(context, tns::ToV8String(isolate, "__pauseOnNextRequire"), + v8::Boolean::New(isolate, true)) + .ToChecked(); + } + + this->session_->schedulePauseOnNextStatement({}, {}); } void JsV8InspectorClient::registerModules() { - Isolate* isolate = isolate_; - Local context = isolate->GetEnteredOrMicrotaskContext(); - Local global = context->Global(); - Local inspectorObject = Object::New(isolate); - - assert(global->Set(context, tns::ToV8String(isolate, "__inspector"), inspectorObject).FromMaybe(false)); - Local func; - bool success = v8::Function::New(context, registerDomainDispatcherCallback).ToLocal(&func); - assert(success && global->Set(context, tns::ToV8String(isolate, "__registerDomainDispatcher"), func).FromMaybe(false)); - - Local data = External::New(isolate, this); - success = v8::Function::New(context, inspectorSendEventCallback, data).ToLocal(&func); - assert(success && global->Set(context, tns::ToV8String(isolate, "__inspectorSendEvent"), func).FromMaybe(false)); - - success = v8::Function::New(context, inspectorTimestampCallback).ToLocal(&func); - assert(success && global->Set(context, tns::ToV8String(isolate, "__inspectorTimestamp"), func).FromMaybe(false)); - - { - v8::Locker locker(isolate); - TryCatch tc(isolate); - runtime_->RunModule("inspector_modules"); - // FIXME: This triggers some DCHECK failures, due to the entered v8::Context in - // Runtime::init(). + Isolate* isolate = isolate_; + Local context = isolate->GetEnteredOrMicrotaskContext(); + Local global = context->Global(); + Local inspectorObject = Object::New(isolate); + + assert(global->Set(context, tns::ToV8String(isolate, "__inspector"), inspectorObject) + .FromMaybe(false)); + Local func; + bool success = v8::Function::New(context, registerDomainDispatcherCallback).ToLocal(&func); + assert(success && + global->Set(context, tns::ToV8String(isolate, "__registerDomainDispatcher"), func) + .FromMaybe(false)); + + Local data = External::New(isolate, this); + success = v8::Function::New(context, inspectorSendEventCallback, data).ToLocal(&func); + assert(success && global->Set(context, tns::ToV8String(isolate, "__inspectorSendEvent"), func) + .FromMaybe(false)); + + success = v8::Function::New(context, inspectorTimestampCallback).ToLocal(&func); + assert(success && global->Set(context, tns::ToV8String(isolate, "__inspectorTimestamp"), func) + .FromMaybe(false)); + + { + v8::Locker locker(isolate); + TryCatch tc(isolate); + + // 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(). + } } -void JsV8InspectorClient::registerDomainDispatcherCallback(const FunctionCallbackInfo& args) { - Isolate* isolate = args.GetIsolate(); - std::string domain = tns::ToString(isolate, args[0].As()); - auto it = Domains.find(domain); - if (it == Domains.end()) { - Local domainCtorFunc = args[1].As(); - Local context = isolate->GetCurrentContext(); - Local ctorArgs[0]; - Local domainInstance; - bool success = domainCtorFunc->CallAsConstructor(context, 0, ctorArgs).ToLocal(&domainInstance); - assert(success && domainInstance->IsObject()); - - Local domainObj = domainInstance.As(); - Persistent* poDomainObj = new Persistent(isolate, domainObj); - Domains.emplace(domain, poDomainObj); - } +void JsV8InspectorClient::registerDomainDispatcherCallback( + const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + std::string domain = tns::ToString(isolate, args[0].As()); + auto it = Domains.find(domain); + if (it == Domains.end()) { + Local domainCtorFunc = args[1].As(); + Local context = isolate->GetCurrentContext(); + Local ctorArgs[0]; + Local domainInstance; + bool success = domainCtorFunc->CallAsConstructor(context, 0, ctorArgs).ToLocal(&domainInstance); + assert(success && domainInstance->IsObject()); + + Local domainObj = domainInstance.As(); + Persistent* poDomainObj = new Persistent(isolate, domainObj); + Domains.emplace(domain, poDomainObj); + } } void JsV8InspectorClient::inspectorSendEventCallback(const FunctionCallbackInfo& args) { - Local data = args.Data().As(); - v8_inspector::JsV8InspectorClient* client = static_cast(data->Value()); - Isolate* isolate = args.GetIsolate(); - Local arg = args[0].As(); - std::string message = tns::ToString(isolate, arg); - - - std::vector vector = tns::ToVector(message); - StringView messageView(vector.data(), vector.size()); - auto msg = StringBuffer::create(messageView); - client->sendNotification(std::move(msg)); + Local data = args.Data().As(); + v8_inspector::JsV8InspectorClient* client = + static_cast(data->Value()); + Isolate* isolate = args.GetIsolate(); + Local arg = args[0].As(); + std::string message = tns::ToString(isolate, arg); + + std::vector vector = tns::ToVector(message); + StringView messageView(vector.data(), vector.size()); + auto msg = StringBuffer::create(messageView); + client->sendNotification(std::move(msg)); } void JsV8InspectorClient::inspectorTimestampCallback(const FunctionCallbackInfo& args) { - double timestamp = std::chrono::seconds(std::chrono::seconds(std::time(NULL))).count(); - args.GetReturnValue().Set(timestamp); + double timestamp = std::chrono::seconds(std::chrono::seconds(std::time(NULL))).count(); + args.GetReturnValue().Set(timestamp); } void JsV8InspectorClient::consoleLog(v8::Isolate* isolate, ConsoleAPIType method, const std::vector>& args) { - if (!isConnected_) { - return; - } + if (!isConnected_) { + return; + } - // Note, here we access private API - auto* impl = reinterpret_cast(inspector_.get()); - auto* session = reinterpret_cast(session_.get()); - - if(impl->isolate() != isolate) { - // we don't currently support logging from a worker thread/isolate - return; - } + // Note, here we access private API + auto* impl = reinterpret_cast(inspector_.get()); + auto* session = reinterpret_cast(session_.get()); + + if (impl->isolate() != isolate) { + // we don't currently support logging from a worker thread/isolate + return; + } - v8::Local stack = v8::StackTrace::CurrentStackTrace( - isolate, 1, v8::StackTrace::StackTraceOptions::kDetailed); - std::unique_ptr stackImpl = impl->debugger()->createStackTrace(stack); - - v8::Local context = context_.Get(isolate); - const int contextId = V8ContextInfo::executionContextId(context); - - std::unique_ptr msg = - v8_inspector::V8ConsoleMessage::createForConsoleAPI( - context, contextId, contextGroupId, impl, currentTimeMS(), - method, args, String16{}, std::move(stackImpl)); - - session->runtimeAgent()->messageAdded(msg.get()); + v8::Local stack = + v8::StackTrace::CurrentStackTrace(isolate, 1, v8::StackTrace::StackTraceOptions::kDetailed); + std::unique_ptr stackImpl = impl->debugger()->createStackTrace(stack); + + v8::Local context = context_.Get(isolate); + const int contextId = V8ContextInfo::executionContextId(context); + + std::unique_ptr msg = + v8_inspector::V8ConsoleMessage::createForConsoleAPI(context, contextId, contextGroupId, impl, + currentTimeMS(), method, args, String16{}, + std::move(stackImpl)); + + session->runtimeAgent()->messageAdded(msg.get()); } -bool JsV8InspectorClient::CallDomainHandlerFunction(Local context, Local domainMethodFunc, const Local& arg, Local& domainDebugger, Local& result) { - if(domainMethodFunc.IsEmpty() || !domainMethodFunc->IsFunction()) { - return false; - } - - bool success; - Isolate* isolate = this->isolate_; - TryCatch tc(isolate); - - Local params; - success = arg.As()->Get(context, tns::ToV8String(isolate, "params")).ToLocal(¶ms); - - if(!success) { - return false; - } - - Local args[2] = { params, arg }; - success = domainMethodFunc->Call(context, domainDebugger, 2, args).ToLocal(&result); - - if (tc.HasCaught()) { - std::string error = tns::ToString(isolate, tc.Message()->Get()); - - // backwards compatibility - if(error.find("may be enabled at a time") != std::string::npos) { - // not returning false here because we are catching bogus errors from core... - // Uncaught Error: One XXX may be enabled at a time... - result = v8::Boolean::New(isolate, true); - return true; - } - - // log any other errors - they are caught, but still make them visible to the user. - tns::LogError(isolate, tc); - - return false; +bool JsV8InspectorClient::CallDomainHandlerFunction(Local context, + Local domainMethodFunc, + const Local& arg, + Local& domainDebugger, + Local& result) { + if (domainMethodFunc.IsEmpty() || !domainMethodFunc->IsFunction()) { + return false; + } + + bool success; + Isolate* isolate = this->isolate_; + TryCatch tc(isolate); + + Local params; + success = arg.As()->Get(context, tns::ToV8String(isolate, "params")).ToLocal(¶ms); + + if (!success) { + return false; + } + + Local args[2] = {params, arg}; + success = domainMethodFunc->Call(context, domainDebugger, 2, args).ToLocal(&result); + + if (tc.HasCaught()) { + std::string error = tns::ToString(isolate, tc.Message()->Get()); + + // backwards compatibility + if (error.find("may be enabled at a time") != std::string::npos) { + // not returning false here because we are catching bogus errors from core... + // Uncaught Error: One XXX may be enabled at a time... + result = v8::Boolean::New(isolate, true); + return true; } - - return success; + + // log any other errors - they are caught, but still make them visible to the user. + tns::LogError(isolate, tc); + + return false; + } + + return success; } -std::string JsV8InspectorClient::GetReturnMessageFromDomainHandlerResult(const Local& result, const Local& requestId) { - if(result.IsEmpty() || !(result->IsBoolean() || result->IsObject() || result->IsNullOrUndefined())) { - return ""; - } - - Isolate* isolate = this->isolate_; - - if(!result->IsObject()) { - // if there return value is a "true" boolean or undefined/null we send back an "ack" response with an empty result object - if(result->IsNullOrUndefined() || result->BooleanValue(isolate_)) { - return "{ \"id\":" + tns::ToString(isolate, requestId) + ", \"result\": {} }"; - } - - return ""; - } - - Local context = tns::Caches::Get(isolate)->GetContext(); - Local resObject = result.As(); - Local stringified; - - bool success = true; - // already a { result: ... } object - if(resObject->Has(context, tns::ToV8String(isolate, "result")).ToChecked()) { - success = JSON::Stringify(context, result).ToLocal(&stringified); - } else { - // backwards compatibility - we wrap the response in a new object with the { id, result } keys - // since the returned response only contained the result part. - Context::Scope context_scope(context); - - Local newResObject = v8::Object::New(isolate); - success = success && newResObject->Set(context, tns::ToV8String(isolate, "id"), requestId).ToChecked(); - success = success && newResObject->Set(context, tns::ToV8String(isolate, "result"), resObject).ToChecked(); - success = success && JSON::Stringify(context, newResObject).ToLocal(&stringified); - } - - if(!success) { - return ""; +std::string JsV8InspectorClient::GetReturnMessageFromDomainHandlerResult( + const Local& result, const Local& requestId) { + if (result.IsEmpty() || + !(result->IsBoolean() || result->IsObject() || result->IsNullOrUndefined())) { + return ""; + } + + Isolate* isolate = this->isolate_; + + if (!result->IsObject()) { + // if there return value is a "true" boolean or undefined/null we send back an "ack" response + // with an empty result object + if (result->IsNullOrUndefined() || result->BooleanValue(isolate_)) { + return "{ \"id\":" + tns::ToString(isolate, requestId) + ", \"result\": {} }"; } - - return tns::ToString(isolate, stringified); + + return ""; + } + + Local context = tns::Caches::Get(isolate)->GetContext(); + Local resObject = result.As(); + Local stringified; + + bool success = true; + // already a { result: ... } object + if (resObject->Has(context, tns::ToV8String(isolate, "result")).ToChecked()) { + success = JSON::Stringify(context, result).ToLocal(&stringified); + } else { + // backwards compatibility - we wrap the response in a new object with the { id, result } keys + // since the returned response only contained the result part. + Context::Scope context_scope(context); + + Local newResObject = v8::Object::New(isolate); + success = success && + newResObject->Set(context, tns::ToV8String(isolate, "id"), requestId).ToChecked(); + success = success && + newResObject->Set(context, tns::ToV8String(isolate, "result"), resObject).ToChecked(); + success = success && JSON::Stringify(context, newResObject).ToLocal(&stringified); + } + + if (!success) { + return ""; + } + + return tns::ToString(isolate, stringified); } std::map*> JsV8InspectorClient::Domains; diff --git a/NativeScript/runtime/Console.cpp b/NativeScript/runtime/Console.cpp index c355587c..fa76d56f 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 @@ -11,384 +17,531 @@ using namespace v8; namespace tns { +// Flag to prevent duplicate error modals from OnUncaughtError +bool consoleModalShown = false; + 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) { + // e.g. " at foo (/path/to/file.ts:123:45)" + static const std::regex stackRe(R"(\s+at\s+\S+:\d+:\d+)"); + return std::regex_search(line, stackRe); } -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; +std::vector filterErrorLines( + const std::vector& allLines) { + std::vector result; + result.reserve(allLines.size()); + for (auto& line : allLines) { + if (isErrorMessage(line) || isStackFrame(line)) { + result.push_back(line); } + } + return result; +} - Isolate* isolate = args.GetIsolate(); - std::string stringResult = BuildStringFromArgs(args); +std::string Console::RemapStackTrace(v8::Isolate* isolate, const std::string& stackTrace) { + // Get the current context from the isolate + Local context = isolate->GetCurrentContext(); - 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); + // Get the global object + Local global = context->Global(); - std::stringstream ss; - ss << stringResult; + // Get the __ns_remapStack function from global + Local remapStackValue; + bool success = + global->Get(context, tns::ToV8String(isolate, "__ns_remapStack")) + .ToLocal(&remapStackValue); - if (verbosityLevel == "trace") { - std::string stacktrace = tns::GetStackTrace(isolate); - ss << std::endl << stacktrace << std::endl; - } + if (success && remapStackValue->IsFunction()) { + Local remapStackFunction = + remapStackValue.As(); - std::string msgToLog = ss.str(); + // Prepare arguments - convert your string to V8 string + Local args[] = {tns::ToV8String(isolate, stackTrace)}; - ConsoleAPIType method = VerbosityToInspectorMethod(verbosityLevel); - SendToDevToolsFrontEnd(method, args); - std::string msgWithVerbosity = "CONSOLE " + verbosityLevelUpper + ": " + msgToLog; - Log("%s", msgWithVerbosity.c_str()); -} + // Call the function + Local result; + bool callSuccess = + remapStackFunction->Call(context, global, 1, args).ToLocal(&result); -void Console::AssertCallback(const FunctionCallbackInfo& args) { - if (!RuntimeConfig.LogToSystemConsole) { - return; + if (callSuccess && result->IsString()) { + // If the function returns a modified string, use it + return tns::ToString(isolate, result); } + } - Isolate* isolate = args.GetIsolate(); - - int argsLength = args.Length(); - bool expressionPasses = argsLength > 0 && args[0]->BooleanValue(isolate); - if (!expressionPasses) { - std::stringstream ss; - - ss << "Assertion failed: "; - - if (argsLength > 1) { - ss << BuildStringFromArgs(args, 1); - } else { - ss << "console.assert"; - } + // Return original string if remapping failed or function not available + return stackTrace; +} - std::string log = ss.str(); - - SendToDevToolsFrontEnd(ConsoleAPIType::kAssert, args); - Log("%s", log.c_str()); +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; + } + + 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); + + if (RuntimeConfig.IsDebug && Runtime::showErrorDisplay() && + (verbosityLevel == "error" || verbosityLevel == "log")) { + // Show in-flight error display when enabled + // Simple universal error detection - any error with stack trace + bool hasStackTrace = isStackFrame(stringResult); + + if (hasStackTrace && !consoleModalShown) { + std::stringstream stackTraceLines; + stackTraceLines << stringResult; + + std::string stacktrace = tns::GetStackTrace(isolate); + stackTraceLines << std::endl << stacktrace << std::endl; + + std::string errorToDisplay = stackTraceLines.str(); + + // Extract error details + std::string errorTitle = "JavaScript Error"; + + // Apply source map remapping to the error display + errorToDisplay = RemapStackTrace(isolate, errorToDisplay); + + try { + NativeScriptException::ShowErrorModal(errorTitle, errorToDisplay, + errorToDisplay); + consoleModalShown = true; // Prevent duplicate modals + + } catch (const std::exception& e) { + Log("Console.cpp: Exception showing modal: %s", e.what()); + } catch (...) { + Log("Console.cpp: Unknown exception showing modal"); + } } + } + std::string verbosityLevelUpper = verbosityLevel; + std::transform(verbosityLevelUpper.begin(), verbosityLevelUpper.end(), + verbosityLevelUpper.begin(), ::toupper); + + std::stringstream ss; + std::string processedStringResult = stringResult; + + // Apply source map remapping if this contains a stack trace + bool hasStackTrace = isStackFrame(stringResult); + if (hasStackTrace) { + processedStringResult = RemapStackTrace(isolate, processedStringResult); + } + + 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()); } -void Console::DirCallback(const FunctionCallbackInfo& args) { - if (!RuntimeConfig.LogToSystemConsole) { - return; - } +void Console::AssertCallback(const FunctionCallbackInfo& args) { + if (!RuntimeConfig.LogToSystemConsole) { + return; + } - int argsLen = args.Length(); - Isolate* isolate = args.GetIsolate(); - Local context = isolate->GetCurrentContext(); + Isolate* isolate = args.GetIsolate(); + int argsLength = args.Length(); + bool expressionPasses = argsLength > 0 && args[0]->BooleanValue(isolate); + if (!expressionPasses) { 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; - } - - 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; - } - } else { - ss << ""; - } - std::string msgToLog = ss.str(); - SendToDevToolsFrontEnd(ConsoleAPIType::kDir, args); - Log("%s", msgToLog.c_str()); -} + ss << "Assertion failed: "; -void Console::TimeCallback(const FunctionCallbackInfo& args) { - if (!RuntimeConfig.LogToSystemConsole) { - return; + if (argsLength > 1) { + ss << BuildStringFromArgs(args, 1); + } else { + ss << "console.assert"; } - Isolate* isolate = args.GetIsolate(); - Local context = isolate->GetCurrentContext(); - std::string label = "default"; + std::string log = ss.str(); - Local labelString; - if (args.Length() > 0 && args[0]->ToString(context).ToLocal(&labelString)) { - label = tns::ToString(isolate, labelString); - } + SendToDevToolsFrontEnd(ConsoleAPIType::kAssert, args); + Log("%s", log.c_str()); + } +} - std::shared_ptr cache = Caches::Get(isolate); +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 (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; + } - auto nano = std::chrono::time_point_cast(std::chrono::system_clock::now()); - double timeStamp = nano.time_since_epoch().count(); + 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()) + << "\""; + } - cache->Timers.emplace(label, timeStamp); -} + ss << std::endl; + } -void Console::TimeEndCallback(const FunctionCallbackInfo& args) { - if (!RuntimeConfig.LogToSystemConsole) { - return; + ss << "==== object dump end ====" << std::endl; } + } else { + ss << ""; + } - 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::string msgToLog = ss.str(); + SendToDevToolsFrontEnd(ConsoleAPIType::kDir, args); + Log("%s", msgToLog.c_str()); +} - 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; - } +void Console::TimeCallback(const FunctionCallbackInfo& args) { + if (!RuntimeConfig.LogToSystemConsole) { + return; + } - auto nano = std::chrono::time_point_cast(std::chrono::system_clock::now()); - double endTimeStamp = nano.time_since_epoch().count(); - double startTimeStamp = itTimersMap->second; + Isolate* isolate = args.GetIsolate(); + Local context = isolate->GetCurrentContext(); + std::string label = "default"; - cache->Timers.erase(label); + Local labelString; + if (args.Length() > 0 && args[0]->ToString(context).ToLocal(&labelString)) { + label = tns::ToString(isolate, labelString); + } - double diffMicroseconds = endTimeStamp - startTimeStamp; - double diffMilliseconds = diffMicroseconds / 1000.0; + std::shared_ptr cache = Caches::Get(isolate); - std::stringstream ss; - ss << "CONSOLE INFO " << label << ": " << std::fixed << std::setprecision(3) << diffMilliseconds << "ms" ; + auto nano = std::chrono::time_point_cast( + std::chrono::system_clock::now()); + double timeStamp = nano.time_since_epoch().count(); - std::string msgToLog = ss.str(); - SendToDevToolsFrontEnd(isolate, ConsoleAPIType::kTimeEnd, msgToLog); - Log("%s", msgToLog.c_str()); + cache->Timers.emplace(label, timeStamp); } -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); - } +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()); +} - 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/Console.h b/NativeScript/runtime/Console.h index f4b23a7b..9b83c5f1 100644 --- a/NativeScript/runtime/Console.h +++ b/NativeScript/runtime/Console.h @@ -25,6 +25,7 @@ class Console { static const v8::Local BuildStringFromArg(v8::Local context, const v8::Local& val); static const v8::Local TransformJSObject(v8::Local object); static ConsoleAPIType VerbosityToInspectorMethod(const std::string level); + static std::string RemapStackTrace(v8::Isolate* isolate, const std::string& stackTrace); static void SendToDevToolsFrontEnd(ConsoleAPIType method, const v8::FunctionCallbackInfo& args); 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.mm b/NativeScript/runtime/Helpers.mm index db93fcb2..73f5621a 100644 --- a/NativeScript/runtime/Helpers.mm +++ b/NativeScript/runtime/Helpers.mm @@ -1,961 +1,851 @@ +#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; - } - - Local obj = val.As(); - if (obj->InternalFieldCount() > 0) { - Local field = obj->GetInternalField(0); - if (field.IsEmpty() || field->IsNullOrUndefined() || !field->IsExternal()) { - return nullptr; - } + if (val.IsEmpty() || val->IsNullOrUndefined() || !val->IsObject()) { + 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()); + } - return static_cast(metadataProp.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()); } 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, std::function func, bool async) { - if(!async) { - bool __block finished = false; - auto v = new LockAndCV; - std::unique_lock lock(v->m); - CFRunLoopPerformBlock(queue, kCFRunLoopCommonModes, ^(void) { - func(); - { - std::unique_lock lk(v->m); - finished = true; - } - v->cv.notify_all(); - }); - CFRunLoopWakeUp(queue); - while(!finished) { - v->cv.wait(lock); - } - delete v; - } else { - CFRunLoopPerformBlock(queue, kCFRunLoopCommonModes, ^(void) { - func(); - }); - CFRunLoopWakeUp(queue); - } - -} - -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::ExecuteOnRunLoop(CFRunLoopRef queue, std::function func, bool async) { + if (!async) { + bool __block finished = false; + auto v = new LockAndCV; + std::unique_lock lock(v->m); + CFRunLoopPerformBlock(queue, kCFRunLoopCommonModes, ^(void) { + func(); + { + std::unique_lock lk(v->m); + finished = true; + } + v->cv.notify_all(); + }); + CFRunLoopWakeUp(queue); + while (!finished) { + v->cv.wait(lock); + } + delete v; + } else { + CFRunLoopPerformBlock(queue, kCFRunLoopCommonModes, ^(void) { + func(); + }); + CFRunLoopWakeUp(queue); + } +} + +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::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