Skip to content

Commit

Permalink
fix: Implement console.log inspector with Runtime protocol
Browse files Browse the repository at this point in the history
Console messages used to be implemented with the Log protocol, which is
no longer part of V8. The Runtime protocol provides an equivalent API,
Runtime.consoleAPICalled.

The Runtime protocol is part of the generated public sources, but the V8
inspector's RuntimeAgent is not. So, for now, we make use of V8's
private API, casting V8Inspector* to V8InspectorImpl* and
V8InspectorSession* to V8InspectorSessionImpl* in order to access their
methods (for which we pulled in the corresponding V8 header files in the
V8 sources update, earlier on this branch.)
  • Loading branch information
ptomato committed Mar 20, 2023
1 parent 14faf01 commit eaa8dd7
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 18 deletions.
2 changes: 2 additions & 0 deletions NativeScript/NativeScript.mm
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include <Foundation/Foundation.h>
#include "NativeScript.h"
#include "inspector/JsV8InspectorClient.h"
#include "runtime/Console.h"
#include "runtime/RuntimeConfig.h"
#include "runtime/Helpers.h"
#include "runtime/Runtime.h"
Expand Down Expand Up @@ -52,6 +53,7 @@ - (instancetype)initWithConfig:(Config*)config {
inspectorClient->init();
inspectorClient->registerModules();
inspectorClient->connect([config ArgumentsCount], [config Arguments]);
Console::AttachInspectorClient(inspectorClient);
}
}

Expand Down
5 changes: 5 additions & 0 deletions NativeScript/inspector/JsV8InspectorClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
#include <map>

#include "include/v8-inspector.h"
#include "src/inspector/v8-console-message.h"

#include "runtime/Runtime.h"

namespace v8_inspector {
Expand All @@ -32,6 +34,9 @@ class JsV8InspectorClient : V8InspectorClient, V8Inspector::Channel {
void scheduleBreak();
void registerModules();

void consoleLog(v8::Isolate* isolate, ConsoleAPIType method,
const std::vector<v8::Local<v8::Value>>& args);

static std::map<std::string, v8::Persistent<v8::Object>*> Domains;
private:
static constexpr int contextGroupId = 1;
Expand Down
35 changes: 34 additions & 1 deletion NativeScript/inspector/JsV8InspectorClient.mm
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
#include <Foundation/Foundation.h>
#include <notify.h>
#include <chrono>

#include "src/inspector/v8-console-message.h"
#include "src/inspector/v8-inspector-impl.h"
#include "src/inspector/v8-inspector-session-impl.h"
#include "src/inspector/v8-runtime-agent-impl.h"
#include "src/inspector/v8-stack-trace-impl.h"

#include "JsV8InspectorClient.h"
#include "InspectorServer.h"
#include "include/libplatform/libplatform.h"
//#include "src/inspector/v8-log-agent-impl.h"
#include "Helpers.h"
#include "utils.h"

Expand Down Expand Up @@ -287,6 +293,8 @@
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().
}
}

Expand All @@ -313,6 +321,31 @@
args.GetReturnValue().Set(timestamp);
}

void JsV8InspectorClient::consoleLog(v8::Isolate* isolate, ConsoleAPIType method,
const std::vector<v8::Local<v8::Value>>& args) {
if (!isConnected_) {
return;
}

// Note, here we access private API
auto* impl = reinterpret_cast<v8_inspector::V8InspectorImpl*>(inspector_.get());
auto* session = reinterpret_cast<v8_inspector::V8InspectorSessionImpl*>(session_.get());

v8::Local<v8::StackTrace> stack = v8::StackTrace::CurrentStackTrace(
isolate, 1, v8::StackTrace::StackTraceOptions::kDetailed);
std::unique_ptr<V8StackTraceImpl> stackImpl = impl->debugger()->createStackTrace(stack);

v8::Local<v8::Context> context = context_.Get(isolate);
const int contextId = V8ContextInfo::executionContextId(context);

std::unique_ptr<v8_inspector::V8ConsoleMessage> msg =
v8_inspector::V8ConsoleMessage::createForConsoleAPI(
context, contextId, contextGroupId, impl, currentTimeMS(),
method, args, String16{}, std::move(stackImpl));

session->runtimeAgent()->messageAdded(msg.get());
}

std::map<std::string, Persistent<Object>*> JsV8InspectorClient::Domains;

}
63 changes: 47 additions & 16 deletions NativeScript/runtime/Console.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ void Console::Init(Local<Context> context) {
}
}

void Console::AttachInspectorClient(v8_inspector::JsV8InspectorClient* aInspector) {
inspector = aInspector;
}

void Console::LogCallback(const FunctionCallbackInfo<Value>& args) {
// TODO: implement 'forceLog' override option like android has, to force logs in prod if desired
if (!RuntimeConfig.LogToSystemConsole) {
Expand All @@ -59,8 +63,8 @@ void Console::LogCallback(const FunctionCallbackInfo<Value>& args) {

std::string msgToLog = ss.str();

std::string level = VerbosityToInspectorVerbosity(verbosityLevel);
// v8_inspector::V8LogAgentImpl::EntryAdded(msgToLog, level, "", 0);
ConsoleAPIType method = VerbosityToInspectorMethod(verbosityLevel);
SendToDevToolsFrontEnd(method, args);
std::string msgWithVerbosity = "CONSOLE " + verbosityLevelUpper + ": " + msgToLog;
Log("%s", msgWithVerbosity.c_str());
}
Expand All @@ -86,7 +90,8 @@ void Console::AssertCallback(const FunctionCallbackInfo<Value>& args) {
}

std::string log = ss.str();
// v8_inspector::V8LogAgentImpl::EntryAdded(log, "error", "", 0);

SendToDevToolsFrontEnd(ConsoleAPIType::kAssert, args);
Log("%s", log.c_str());
}
}
Expand Down Expand Up @@ -157,11 +162,7 @@ void Console::DirCallback(const FunctionCallbackInfo<Value>& args) {
}

std::string msgToLog = ss.str();

Local<v8::String> data = args.Data().As<v8::String>();
std::string verbosityLevel = tns::ToString(isolate, data);
std::string level = VerbosityToInspectorVerbosity(verbosityLevel);
// v8_inspector::V8LogAgentImpl::EntryAdded(msgToLog, level, "", 0);
SendToDevToolsFrontEnd(ConsoleAPIType::kDir, args);
Log("%s", msgToLog.c_str());
}

Expand Down Expand Up @@ -221,11 +222,8 @@ void Console::TimeEndCallback(const FunctionCallbackInfo<Value>& args) {
std::stringstream ss;
ss << "CONSOLE INFO " << label << ": " << std::fixed << std::setprecision(3) << diffMilliseconds << "ms" ;

Local<v8::String> data = args.Data().As<v8::String>();
std::string verbosityLevel = tns::ToString(isolate, data);
std::string level = VerbosityToInspectorVerbosity(verbosityLevel);
std::string msgToLog = ss.str();
// v8_inspector::V8LogAgentImpl::EntryAdded(msgToLog, level, "", 0);
SendToDevToolsFrontEnd(isolate, ConsoleAPIType::kTimeEnd, msgToLog);
Log("%s", msgToLog.c_str());
}

Expand Down Expand Up @@ -346,14 +344,47 @@ const Local<v8::String> Console::TransformJSObject(Local<Object> object) {
return resultString;
}

const std::string Console::VerbosityToInspectorVerbosity(const std::string level) {
v8_inspector::ConsoleAPIType Console::VerbosityToInspectorMethod(const std::string level) {
if (level == "error") {
return "error";
return ConsoleAPIType::kError;
} else if (level == "warn") {
return "warning";
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<v8::Value>& args) {
if (!inspector) {
return;
}

std::vector<v8::Local<v8::Value>> 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(v8::Isolate* isolate, ConsoleAPIType method, const std::string& msg) {
if (!inspector) {
return;
}

return "info";
v8::Local<v8::String> v8str = v8::String::NewFromUtf8(
isolate, msg.c_str(), v8::NewStringType::kNormal, -1).ToLocalChecked();
std::vector<v8::Local<v8::Value>> args{v8str};
inspector->consoleLog(isolate, method, args);
}

v8_inspector::JsV8InspectorClient* Console::inspector = nullptr;

}
12 changes: 11 additions & 1 deletion NativeScript/runtime/Console.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@
#define Console_h

#include "Common.h"
#include "JSV8InspectorClient.h"
#include <string>

namespace tns {

class Console {
public:
static void Init(v8::Local<v8::Context> context);
static void AttachInspectorClient(v8_inspector::JsV8InspectorClient* inspector);
private:
using ConsoleAPIType = v8_inspector::ConsoleAPIType;

static void AttachLogFunction(v8::Local<v8::Context> context, v8::Local<v8::Object> console, const std::string name, v8::FunctionCallback callback = Console::LogCallback);
static void LogCallback(const v8::FunctionCallbackInfo<v8::Value>& args);
static void AssertCallback(const v8::FunctionCallbackInfo<v8::Value>& args);
Expand All @@ -19,7 +23,13 @@ class Console {
static std::string BuildStringFromArgs(const v8::FunctionCallbackInfo<v8::Value>& args, int startingIndex = 0);
static const v8::Local<v8::String> BuildStringFromArg(v8::Local<v8::Context> context, const v8::Local<v8::Value>& val);
static const v8::Local<v8::String> TransformJSObject(v8::Local<v8::Object> object);
static const std::string VerbosityToInspectorVerbosity(const std::string level);
static ConsoleAPIType VerbosityToInspectorMethod(const std::string level);

static void SendToDevToolsFrontEnd(ConsoleAPIType method,
const v8::FunctionCallbackInfo<v8::Value>& args);
static void SendToDevToolsFrontEnd(v8::Isolate* isolate, ConsoleAPIType method, const std::string& msg);

static v8_inspector::JsV8InspectorClient* inspector;
};

}
Expand Down

0 comments on commit eaa8dd7

Please sign in to comment.