Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions NativeScript/runtime/BuiltinLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,9 @@ MaybeLocal<Value> CallBuiltin(Local<Context> context, BuiltinId id,
}

// Snapshot of the intrinsics, taken the first time any builtin runs in this
// isolate — during runtime init, before user code can replace a global. Later
// builtins (smart-stringify compiles lazily, on the first object logged) get
// the same pristine snapshot.
// isolate — during runtime init, before user code can replace a global.
// Builtins compiled later in the isolate's life get the same pristine
// snapshot.
MaybeLocal<Object> GetPrimordials(Local<Context> context) {
Isolate* isolate = v8::Isolate::GetCurrent();
std::shared_ptr<Caches> cache = Caches::Get(isolate);
Expand Down
3 changes: 2 additions & 1 deletion NativeScript/runtime/Caches.h
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,8 @@ class Caches {
std::unique_ptr<v8::Persistent<v8::Function>>(nullptr);
std::unique_ptr<v8::Persistent<v8::Function>> WeakRefClearFunc =
std::unique_ptr<v8::Persistent<v8::Function>>(nullptr);
std::unique_ptr<v8::Persistent<v8::Function>> SmartJSONStringifyFunc =
// console formatter (internal/inspect.js), initialized by Console::Init.
std::unique_ptr<v8::Persistent<v8::Function>> InspectFunc =
std::unique_ptr<v8::Persistent<v8::Function>>(nullptr);
std::unique_ptr<v8::Persistent<v8::Function>> InteropReferenceCtorFunc =
std::unique_ptr<v8::Persistent<v8::Function>>(nullptr);
Expand Down
222 changes: 94 additions & 128 deletions NativeScript/runtime/Console.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
#include <string>
#include <vector>

#include "BuiltinLoader.h"
#include "Caches.h"
#include "DataWrapper.h"
#include "Helpers.h"
#include "NativeScriptException.h"
#include "RuntimeConfig.h"
Expand Down Expand Up @@ -35,6 +37,8 @@ void Console::Init(Local<Context> context) {
Console::AttachLogFunction(context, console, "time", TimeCallback);
Console::AttachLogFunction(context, console, "timeEnd", TimeEndCallback);

Console::InitInspect(context);

Local<Object> global = context->Global();
PropertyAttribute readOnlyFlags = static_cast<PropertyAttribute>(
PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly);
Expand Down Expand Up @@ -155,74 +159,20 @@ void Console::DirCallback(const FunctionCallbackInfo<Value>& args) {
return;
}

int argsLen = args.Length();
Isolate* isolate = args.GetIsolate();
Local<Context> 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<Object> argObject = args[0].As<Object>();

Local<v8::Array> 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<Value> propertyName = propNames->Get(context, i).ToLocalChecked();
Local<Value> 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<v8::String> stringResult =
BuildStringFromArg(context, propertyValue);
std::string jsonStringifiedArray =
tns::ToString(isolate, stringResult);
ss << jsonStringifiedArray;
} else if (propertyValue->IsObject()) {
Local<Object> obj = propertyValue->ToObject(context).ToLocalChecked();
Local<v8::String> 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;
}
if (args.Length() > 0 && args[0]->IsObject()) {
ss << std::endl << "==== object dump start ====" << std::endl;
ss << tns::ToString(isolate, Console::InspectValue(context, args[0], 4))
<< std::endl;
ss << "==== object dump end ====" << std::endl;
} else if (args.Length() > 0) {
ss << " " << BuildStringFromArgs(args);
} else {
ss << "";
}
Expand Down Expand Up @@ -348,88 +298,104 @@ std::string Console::BuildStringFromArgs(
const Local<v8::String> Console::BuildStringFromArg(Local<Context> context,
const Local<Value>& val) {
Isolate* isolate = v8::Isolate::GetCurrent();
Local<v8::String> argString;
if (val->IsFunction()) {
bool success = val->ToDetailString(context).ToLocal(&argString);
tns::Assert(success, isolate);
} else if (val->IsArray()) {
Local<Value> cachedSelf = val;
Local<Object> array = val->ToObject(context).ToLocalChecked();
Local<v8::Array> arrayEntryKeys =
array->GetPropertyNames(context).ToLocalChecked();

uint32_t arrayLength = arrayEntryKeys->Length();

argString = tns::ToV8String(isolate, "[");

for (int i = 0; i < arrayLength; i++) {
Local<Value> propertyName =
arrayEntryKeys->Get(context, i).ToLocalChecked();

Local<Value> 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;
}
// Top-level strings print raw (console.log("hi") -> hi); everything else
// that can carry structure goes through the inspect builtin.
if (val->IsString()) {
return val.As<v8::String>();
}
if (val->IsObject() || val->IsFunction()) {
return Console::InspectValue(context, val);
}

Local<v8::String> objectString =
BuildStringFromArg(context, propertyValue);
Local<v8::String> argString;
bool success = val->ToDetailString(context).ToLocal(&argString);
tns::Assert(success, isolate);
return argString;
}

argString = v8::String::Concat(isolate, argString, objectString);
static void GetNativeWrapperHintCallback(
const FunctionCallbackInfo<Value>& info) {
Isolate* isolate = info.GetIsolate();
if (info.Length() < 1) {
return;
}
std::string hint = tns::GetNativeWrapperHint(isolate, info[0]);
if (!hint.empty()) {
info.GetReturnValue().Set(tns::ToV8String(isolate, hint));
}
}

if (i != arrayLength - 1) {
argString = v8::String::Concat(isolate, argString,
tns::ToV8String(isolate, ", "));
}
}
void Console::InitInspect(Local<Context> context) {
Isolate* isolate = v8::Isolate::GetCurrent();

argString =
v8::String::Concat(isolate, argString, tns::ToV8String(isolate, "]"));
} else if (val->IsObject()) {
Local<Object> obj = val.As<Object>();
Local<v8::Function> hintFunc;
if (!v8::Function::New(context, GetNativeWrapperHintCallback)
.ToLocal(&hintFunc)) {
Log("Warning: Console failed to create the native-hint binding");
return;
}
Local<Object> binding = Object::New(isolate);
if (!binding
->Set(context, tns::ToV8String(isolate, "getNativeWrapperHint"),
hintFunc)
.FromMaybe(false)) {
Log("Warning: Console failed to populate the inspect binding");
return;
}

argString = TransformJSObject(obj);
} else {
bool success =
val->ToDetailString(isolate->GetCurrentContext()).ToLocal(&argString);
tns::Assert(success, isolate);
TryCatch tc(isolate);
Local<Value> result;
if (!BuiltinLoader::RunBuiltin(context, BuiltinId::kInspect, binding)
.ToLocal(&result) ||
!result->IsFunction()) {
if (tc.HasCaught()) {
tns::LogError(isolate, tc);
}
Log("Warning: Console failed to initialize the inspect builtin");
return;
}

return argString;
Caches::Get(isolate)->InspectFunc =
std::make_unique<Persistent<v8::Function>>(isolate,
result.As<v8::Function>());
}

const Local<v8::String> Console::TransformJSObject(Local<Object> object) {
Local<Context> context;
bool success =
object->GetCreationContext(v8::Isolate::GetCurrent()).ToLocal(&context);
tns::Assert(success);
Local<v8::String> Console::InspectValue(Local<Context> context,
const Local<Value>& val, int depth) {
Isolate* isolate = v8::Isolate::GetCurrent();
Local<Value> value;
{
auto cache = Caches::Get(isolate);

if (cache->InspectFunc != nullptr) {
Local<v8::Function> inspect = cache->InspectFunc->Get(isolate);
Local<Value> arg = val;
Local<Value> args[2];
int argc = 1;
args[0] = arg;
if (depth >= 0) {
Local<Object> options = Object::New(isolate);
if (options
->Set(context, tns::ToV8String(isolate, "depth"),
v8::Number::New(isolate, depth))
.FromMaybe(false)) {
args[1] = options;
argc = 2;
}
}
TryCatch tc(isolate);
bool success = object->ToString(context).ToLocal(&value);
if (!success) {
return tns::ToV8String(isolate, "");
Local<Value> result;
if (inspect->Call(context, v8::Undefined(isolate), argc, args)
.ToLocal(&result) &&
result->IsString()) {
return result.As<v8::String>();
}
}
Local<v8::String> objToString = value.As<v8::String>();

Local<v8::String> resultString;
bool hasCustomToStringImplementation =
tns::ToString(isolate, objToString).find("[object Object]") ==
std::string::npos;

if (hasCustomToStringImplementation) {
resultString = objToString;
} else {
resultString = tns::JsonStringifyObject(context, object);
// Init failed or the formatter threw: degrade to V8's own short description.
Local<v8::String> fallback;
if (val->ToDetailString(context).ToLocal(&fallback)) {
return fallback;
}

return resultString;
return v8::String::Empty(isolate);
}

v8_inspector::ConsoleAPIType Console::VerbosityToInspectorMethod(
Expand Down
63 changes: 38 additions & 25 deletions NativeScript/runtime/Console.h
Original file line number Diff line number Diff line change
@@ -1,38 +1,51 @@
#ifndef Console_h
#define Console_h

#include <string>

#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);
static void DetachInspectorClient();
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);
static void DirCallback(const v8::FunctionCallbackInfo<v8::Value>& args);
static void TimeCallback(const v8::FunctionCallbackInfo<v8::Value>& args);
static void TimeEndCallback(const v8::FunctionCallbackInfo<v8::Value>& args);
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 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;
public:
static void Init(v8::Local<v8::Context> context);
static void AttachInspectorClient(
v8_inspector::JsV8InspectorClient* inspector);
static void DetachInspectorClient();

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);
static void DirCallback(const v8::FunctionCallbackInfo<v8::Value>& args);
static void TimeCallback(const v8::FunctionCallbackInfo<v8::Value>& args);
static void TimeEndCallback(const v8::FunctionCallbackInfo<v8::Value>& args);
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 v8::Local<v8::String> InspectValue(v8::Local<v8::Context> context,
const v8::Local<v8::Value>& val,
int depth = -1);
static void InitInspect(v8::Local<v8::Context> context);
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;
};

}
} // namespace tns

#endif /* Console_h */
7 changes: 3 additions & 4 deletions NativeScript/runtime/Helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -301,10 +301,9 @@ static inline void TNS_FormatAndLog(const char* fmt, ...) {
// Keep the existing Log(...) macro name for call-site compatibility.
#define Log(...) tns::TNS_FormatAndLog(__VA_ARGS__)

v8::Local<v8::String> JsonStringifyObject(v8::Local<v8::Context> context,
v8::Local<v8::Value> value,
bool handleCircularReferences = true);
v8::Local<v8::Function> GetSmartJSONStringifyFunction(v8::Isolate* isolate);
// Short identification for objects backed by a native wrapper (ObjC class
// name etc.); empty when the value is a plain JS object. Never runs JS.
std::string GetNativeWrapperHint(v8::Isolate* isolate, const v8::Local<v8::Value>& value);

std::string ReplaceAll(const std::string source, std::string find, std::string replacement);

Expand Down
Loading
Loading