blob: f56562341cae7a65b29ad52329e95cb57a120795 [file] [log] [blame]
// Copyright 2018 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/inspector/custom-preview.h"
#include "src/debug/debug-interface.h"
#include "src/inspector/injected-script.h"
#include "src/inspector/inspected-context.h"
#include "src/inspector/string-util.h"
#include "src/inspector/v8-console-message.h"
#include "src/inspector/v8-inspector-impl.h"
#include "src/inspector/v8-stack-trace-impl.h"
namespace v8_inspector {
using protocol::Runtime::CustomPreview;
namespace {
void reportError(v8::Local<v8::Context> context, const v8::TryCatch& tryCatch) {
DCHECK(tryCatch.HasCaught());
v8::Isolate* isolate = context->GetIsolate();
V8InspectorImpl* inspector =
static_cast<V8InspectorImpl*>(v8::debug::GetInspector(isolate));
int contextId = InspectedContext::contextId(context);
int groupId = inspector->contextGroupId(contextId);
v8::Local<v8::String> message = tryCatch.Message()->Get();
v8::Local<v8::String> prefix =
toV8String(isolate, "Custom Formatter Failed: ");
message = v8::String::Concat(isolate, prefix, message);
std::vector<v8::Local<v8::Value>> arguments;
arguments.push_back(message);
V8ConsoleMessageStorage* storage =
inspector->ensureConsoleMessageStorage(groupId);
if (!storage) return;
storage->addMessage(V8ConsoleMessage::createForConsoleAPI(
context, contextId, groupId, inspector,
inspector->client()->currentTimeMS(), ConsoleAPIType::kError, arguments,
String16(), nullptr));
}
void reportError(v8::Local<v8::Context> context, const v8::TryCatch& tryCatch,
const String16& message) {
v8::Isolate* isolate = context->GetIsolate();
isolate->ThrowException(toV8String(isolate, message));
reportError(context, tryCatch);
}
InjectedScript* getInjectedScript(v8::Local<v8::Context> context,
int sessionId) {
v8::Isolate* isolate = context->GetIsolate();
V8InspectorImpl* inspector =
static_cast<V8InspectorImpl*>(v8::debug::GetInspector(isolate));
InspectedContext* inspectedContext =
inspector->getContext(InspectedContext::contextId(context));
if (!inspectedContext) return nullptr;
return inspectedContext->getInjectedScript(sessionId);
}
bool substituteObjectTags(int sessionId, const String16& groupName,
v8::Local<v8::Context> context,
v8::Local<v8::Array> jsonML, int maxDepth) {
if (!jsonML->Length()) return true;
v8::Isolate* isolate = context->GetIsolate();
v8::TryCatch tryCatch(isolate);
if (maxDepth <= 0) {
reportError(context, tryCatch,
"Too deep hierarchy of inlined custom previews");
return false;
}
v8::Local<v8::Value> firstValue;
if (!jsonML->Get(context, 0).ToLocal(&firstValue)) {
reportError(context, tryCatch);
return false;
}
v8::Local<v8::String> objectLiteral = toV8String(isolate, "object");
if (jsonML->Length() == 2 && firstValue->IsString() &&
firstValue.As<v8::String>()->StringEquals(objectLiteral)) {
v8::Local<v8::Value> attributesValue;
if (!jsonML->Get(context, 1).ToLocal(&attributesValue)) {
reportError(context, tryCatch);
return false;
}
if (!attributesValue->IsObject()) {
reportError(context, tryCatch, "attributes should be an Object");
return false;
}
v8::Local<v8::Object> attributes = attributesValue.As<v8::Object>();
v8::Local<v8::Value> originValue;
if (!attributes->Get(context, objectLiteral).ToLocal(&originValue)) {
reportError(context, tryCatch);
return false;
}
if (originValue->IsUndefined()) {
reportError(context, tryCatch,
"obligatory attribute \"object\" isn't specified");
return false;
}
v8::Local<v8::Value> configValue;
if (!attributes->Get(context, toV8String(isolate, "config"))
.ToLocal(&configValue)) {
reportError(context, tryCatch);
return false;
}
InjectedScript* injectedScript = getInjectedScript(context, sessionId);
if (!injectedScript) {
reportError(context, tryCatch, "cannot find context with specified id");
return false;
}
std::unique_ptr<protocol::Runtime::RemoteObject> wrapper;
protocol::Response response =
injectedScript->wrapObject(originValue, groupName, WrapMode::kNoPreview,
configValue, maxDepth - 1, &wrapper);
if (!response.isSuccess() || !wrapper) {
reportError(context, tryCatch, "cannot wrap value");
return false;
}
v8::Local<v8::Value> jsonWrapper;
String16 serialized = wrapper->toJSON();
if (!v8::JSON::Parse(context, toV8String(isolate, serialized))
.ToLocal(&jsonWrapper)) {
reportError(context, tryCatch, "cannot wrap value");
return false;
}
if (jsonML->Set(context, 1, jsonWrapper).IsNothing()) {
reportError(context, tryCatch);
return false;
}
} else {
for (uint32_t i = 0; i < jsonML->Length(); ++i) {
v8::Local<v8::Value> value;
if (!jsonML->Get(context, i).ToLocal(&value)) {
reportError(context, tryCatch);
return false;
}
if (value->IsArray() && value.As<v8::Array>()->Length() > 0 &&
!substituteObjectTags(sessionId, groupName, context,
value.As<v8::Array>(), maxDepth - 1)) {
return false;
}
}
}
return true;
}
void bodyCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
v8::Isolate* isolate = info.GetIsolate();
v8::TryCatch tryCatch(isolate);
v8::Local<v8::Context> context = isolate->GetCurrentContext();
v8::Local<v8::Object> bodyConfig = info.Data().As<v8::Object>();
v8::Local<v8::Value> objectValue;
if (!bodyConfig->Get(context, toV8String(isolate, "object"))
.ToLocal(&objectValue)) {
reportError(context, tryCatch);
return;
}
if (!objectValue->IsObject()) {
reportError(context, tryCatch, "object should be an Object");
return;
}
v8::Local<v8::Object> object = objectValue.As<v8::Object>();
v8::Local<v8::Value> formatterValue;
if (!bodyConfig->Get(context, toV8String(isolate, "formatter"))
.ToLocal(&formatterValue)) {
reportError(context, tryCatch);
return;
}
if (!formatterValue->IsObject()) {
reportError(context, tryCatch, "formatter should be an Object");
return;
}
v8::Local<v8::Object> formatter = formatterValue.As<v8::Object>();
v8::Local<v8::Value> bodyValue;
if (!formatter->Get(context, toV8String(isolate, "body"))
.ToLocal(&bodyValue)) {
reportError(context, tryCatch);
return;
}
if (!bodyValue->IsFunction()) {
reportError(context, tryCatch, "body should be a Function");
return;
}
v8::Local<v8::Function> bodyFunction = bodyValue.As<v8::Function>();
v8::Local<v8::Value> configValue;
if (!bodyConfig->Get(context, toV8String(isolate, "config"))
.ToLocal(&configValue)) {
reportError(context, tryCatch);
return;
}
v8::Local<v8::Value> sessionIdValue;
if (!bodyConfig->Get(context, toV8String(isolate, "sessionId"))
.ToLocal(&sessionIdValue)) {
reportError(context, tryCatch);
return;
}
if (!sessionIdValue->IsInt32()) {
reportError(context, tryCatch, "sessionId should be an Int32");
return;
}
v8::Local<v8::Value> groupNameValue;
if (!bodyConfig->Get(context, toV8String(isolate, "groupName"))
.ToLocal(&groupNameValue)) {
reportError(context, tryCatch);
return;
}
if (!groupNameValue->IsString()) {
reportError(context, tryCatch, "groupName should be a string");
return;
}
v8::Local<v8::Value> formattedValue;
v8::Local<v8::Value> args[] = {object, configValue};
if (!bodyFunction->Call(context, formatter, 2, args)
.ToLocal(&formattedValue)) {
reportError(context, tryCatch);
return;
}
if (!formattedValue->IsArray()) {
reportError(context, tryCatch, "body should return an Array");
return;
}
v8::Local<v8::Array> jsonML = formattedValue.As<v8::Array>();
if (jsonML->Length() &&
!substituteObjectTags(
sessionIdValue.As<v8::Int32>()->Value(),
toProtocolString(isolate, groupNameValue.As<v8::String>()), context,
jsonML, kMaxCustomPreviewDepth)) {
return;
}
info.GetReturnValue().Set(jsonML);
}
} // anonymous namespace
void generateCustomPreview(int sessionId, const String16& groupName,
v8::Local<v8::Context> context,
v8::Local<v8::Object> object,
v8::MaybeLocal<v8::Value> maybeConfig, int maxDepth,
std::unique_ptr<CustomPreview>* preview) {
v8::Isolate* isolate = context->GetIsolate();
v8::MicrotasksScope microtasksScope(isolate,
v8::MicrotasksScope::kDoNotRunMicrotasks);
v8::TryCatch tryCatch(isolate);
v8::Local<v8::Value> configValue;
if (!maybeConfig.ToLocal(&configValue)) configValue = v8::Undefined(isolate);
v8::Local<v8::Object> global = context->Global();
v8::Local<v8::Value> formattersValue;
if (!global->Get(context, toV8String(isolate, "devtoolsFormatters"))
.ToLocal(&formattersValue)) {
reportError(context, tryCatch);
return;
}
if (!formattersValue->IsArray()) return;
v8::Local<v8::Array> formatters = formattersValue.As<v8::Array>();
v8::Local<v8::String> headerLiteral = toV8String(isolate, "header");
v8::Local<v8::String> hasBodyLiteral = toV8String(isolate, "hasBody");
for (uint32_t i = 0; i < formatters->Length(); ++i) {
v8::Local<v8::Value> formatterValue;
if (!formatters->Get(context, i).ToLocal(&formatterValue)) {
reportError(context, tryCatch);
return;
}
if (!formatterValue->IsObject()) {
reportError(context, tryCatch, "formatter should be an Object");
return;
}
v8::Local<v8::Object> formatter = formatterValue.As<v8::Object>();
v8::Local<v8::Value> headerValue;
if (!formatter->Get(context, headerLiteral).ToLocal(&headerValue)) {
reportError(context, tryCatch);
return;
}
if (!headerValue->IsFunction()) {
reportError(context, tryCatch, "header should be a Function");
return;
}
v8::Local<v8::Function> headerFunction = headerValue.As<v8::Function>();
v8::Local<v8::Value> formattedValue;
v8::Local<v8::Value> args[] = {object, configValue};
if (!headerFunction->Call(context, formatter, 2, args)
.ToLocal(&formattedValue)) {
reportError(context, tryCatch);
return;
}
if (!formattedValue->IsArray()) continue;
v8::Local<v8::Array> jsonML = formattedValue.As<v8::Array>();
v8::Local<v8::Value> hasBodyFunctionValue;
if (!formatter->Get(context, hasBodyLiteral)
.ToLocal(&hasBodyFunctionValue)) {
reportError(context, tryCatch);
return;
}
if (!hasBodyFunctionValue->IsFunction()) continue;
v8::Local<v8::Function> hasBodyFunction =
hasBodyFunctionValue.As<v8::Function>();
v8::Local<v8::Value> hasBodyValue;
if (!hasBodyFunction->Call(context, formatter, 2, args)
.ToLocal(&hasBodyValue)) {
reportError(context, tryCatch);
return;
}
bool hasBody = hasBodyValue->ToBoolean(isolate)->Value();
if (jsonML->Length() && !substituteObjectTags(sessionId, groupName, context,
jsonML, maxDepth)) {
return;
}
v8::Local<v8::String> header;
if (!v8::JSON::Stringify(context, jsonML).ToLocal(&header)) {
reportError(context, tryCatch);
return;
}
v8::Local<v8::Function> bodyFunction;
if (hasBody) {
v8::Local<v8::Object> bodyConfig = v8::Object::New(isolate);
if (bodyConfig
->CreateDataProperty(context, toV8String(isolate, "sessionId"),
v8::Integer::New(isolate, sessionId))
.IsNothing()) {
reportError(context, tryCatch);
return;
}
if (bodyConfig
->CreateDataProperty(context, toV8String(isolate, "formatter"),
formatter)
.IsNothing()) {
reportError(context, tryCatch);
return;
}
if (bodyConfig
->CreateDataProperty(context, toV8String(isolate, "groupName"),
toV8String(isolate, groupName))
.IsNothing()) {
reportError(context, tryCatch);
return;
}
if (bodyConfig
->CreateDataProperty(context, toV8String(isolate, "config"),
configValue)
.IsNothing()) {
reportError(context, tryCatch);
return;
}
if (bodyConfig
->CreateDataProperty(context, toV8String(isolate, "object"),
object)
.IsNothing()) {
reportError(context, tryCatch);
return;
}
if (!v8::Function::New(context, bodyCallback, bodyConfig)
.ToLocal(&bodyFunction)) {
reportError(context, tryCatch);
return;
}
}
*preview = CustomPreview::create()
.setHeader(toProtocolString(isolate, header))
.build();
if (!bodyFunction.IsEmpty()) {
InjectedScript* injectedScript = getInjectedScript(context, sessionId);
if (!injectedScript) {
reportError(context, tryCatch, "cannot find context with specified id");
return;
}
(*preview)->setBodyGetterId(
injectedScript->bindObject(bodyFunction, groupName));
}
return;
}
}
} // namespace v8_inspector