// 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 context, const v8::TryCatch& tryCatch) { DCHECK(tryCatch.HasCaught()); v8::Isolate* isolate = context->GetIsolate(); V8InspectorImpl* inspector = static_cast(v8::debug::GetInspector(isolate)); int contextId = InspectedContext::contextId(context); int groupId = inspector->contextGroupId(contextId); v8::Local message = tryCatch.Message()->Get(); v8::Local prefix = toV8String(isolate, "Custom Formatter Failed: "); message = v8::String::Concat(isolate, prefix, message); std::vector> 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 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 context, int sessionId) { v8::Isolate* isolate = context->GetIsolate(); V8InspectorImpl* inspector = static_cast(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 context, v8::Local 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 firstValue; if (!jsonML->Get(context, 0).ToLocal(&firstValue)) { reportError(context, tryCatch); return false; } v8::Local objectLiteral = toV8String(isolate, "object"); if (jsonML->Length() == 2 && firstValue->IsString() && firstValue.As()->StringEquals(objectLiteral)) { v8::Local 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 attributes = attributesValue.As(); v8::Local 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 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 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 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 value; if (!jsonML->Get(context, i).ToLocal(&value)) { reportError(context, tryCatch); return false; } if (value->IsArray() && value.As()->Length() > 0 && !substituteObjectTags(sessionId, groupName, context, value.As(), maxDepth - 1)) { return false; } } } return true; } void bodyCallback(const v8::FunctionCallbackInfo& info) { v8::Isolate* isolate = info.GetIsolate(); v8::TryCatch tryCatch(isolate); v8::Local context = isolate->GetCurrentContext(); v8::Local bodyConfig = info.Data().As(); v8::Local 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 object = objectValue.As(); v8::Local 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 formatter = formatterValue.As(); v8::Local 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 bodyFunction = bodyValue.As(); v8::Local configValue; if (!bodyConfig->Get(context, toV8String(isolate, "config")) .ToLocal(&configValue)) { reportError(context, tryCatch); return; } v8::Local 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 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 formattedValue; v8::Local 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 jsonML = formattedValue.As(); if (jsonML->Length() && !substituteObjectTags( sessionIdValue.As()->Value(), toProtocolString(isolate, groupNameValue.As()), context, jsonML, kMaxCustomPreviewDepth)) { return; } info.GetReturnValue().Set(jsonML); } } // anonymous namespace void generateCustomPreview(int sessionId, const String16& groupName, v8::Local object, v8::MaybeLocal maybeConfig, int maxDepth, std::unique_ptr* preview) { v8::Local context = object->CreationContext(); v8::Isolate* isolate = context->GetIsolate(); v8::MicrotasksScope microtasksScope(isolate, v8::MicrotasksScope::kDoNotRunMicrotasks); v8::TryCatch tryCatch(isolate); v8::Local configValue; if (!maybeConfig.ToLocal(&configValue)) configValue = v8::Undefined(isolate); v8::Local global = context->Global(); v8::Local formattersValue; if (!global->Get(context, toV8String(isolate, "devtoolsFormatters")) .ToLocal(&formattersValue)) { reportError(context, tryCatch); return; } if (!formattersValue->IsArray()) return; v8::Local formatters = formattersValue.As(); v8::Local headerLiteral = toV8String(isolate, "header"); v8::Local hasBodyLiteral = toV8String(isolate, "hasBody"); for (uint32_t i = 0; i < formatters->Length(); ++i) { v8::Local 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 formatter = formatterValue.As(); v8::Local 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 headerFunction = headerValue.As(); v8::Local formattedValue; v8::Local args[] = {object, configValue}; if (!headerFunction->Call(context, formatter, 2, args) .ToLocal(&formattedValue)) { reportError(context, tryCatch); return; } if (!formattedValue->IsArray()) continue; v8::Local jsonML = formattedValue.As(); v8::Local hasBodyFunctionValue; if (!formatter->Get(context, hasBodyLiteral) .ToLocal(&hasBodyFunctionValue)) { reportError(context, tryCatch); return; } if (!hasBodyFunctionValue->IsFunction()) continue; v8::Local hasBodyFunction = hasBodyFunctionValue.As(); v8::Local 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 header; if (!v8::JSON::Stringify(context, jsonML).ToLocal(&header)) { reportError(context, tryCatch); return; } v8::Local bodyFunction; if (hasBody) { v8::Local 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