1// Copyright 2018 the V8 project authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "src/inspector/custom-preview.h" 6 7#include "../../third_party/inspector_protocol/crdtp/json.h" 8#include "include/v8-container.h" 9#include "include/v8-context.h" 10#include "include/v8-function.h" 11#include "include/v8-json.h" 12#include "include/v8-microtask-queue.h" 13#include "src/debug/debug-interface.h" 14#include "src/inspector/injected-script.h" 15#include "src/inspector/inspected-context.h" 16#include "src/inspector/string-util.h" 17#include "src/inspector/v8-console-message.h" 18#include "src/inspector/v8-inspector-impl.h" 19#include "src/inspector/v8-stack-trace-impl.h" 20 21namespace v8_inspector { 22 23using protocol::Runtime::CustomPreview; 24 25namespace { 26void reportError(v8::Local<v8::Context> context, const v8::TryCatch& tryCatch) { 27 DCHECK(tryCatch.HasCaught()); 28 v8::Isolate* isolate = context->GetIsolate(); 29 V8InspectorImpl* inspector = 30 static_cast<V8InspectorImpl*>(v8::debug::GetInspector(isolate)); 31 int contextId = InspectedContext::contextId(context); 32 int groupId = inspector->contextGroupId(contextId); 33 v8::Local<v8::String> message = tryCatch.Message()->Get(); 34 v8::Local<v8::String> prefix = 35 toV8String(isolate, "Custom Formatter Failed: "); 36 message = v8::String::Concat(isolate, prefix, message); 37 std::vector<v8::Local<v8::Value>> arguments; 38 arguments.push_back(message); 39 V8ConsoleMessageStorage* storage = 40 inspector->ensureConsoleMessageStorage(groupId); 41 if (!storage) return; 42 storage->addMessage(V8ConsoleMessage::createForConsoleAPI( 43 context, contextId, groupId, inspector, 44 inspector->client()->currentTimeMS(), ConsoleAPIType::kError, arguments, 45 String16(), nullptr)); 46} 47 48void reportError(v8::Local<v8::Context> context, const v8::TryCatch& tryCatch, 49 const String16& message) { 50 v8::Isolate* isolate = context->GetIsolate(); 51 isolate->ThrowException(toV8String(isolate, message)); 52 reportError(context, tryCatch); 53} 54 55InjectedScript* getInjectedScript(v8::Local<v8::Context> context, 56 int sessionId) { 57 v8::Isolate* isolate = context->GetIsolate(); 58 V8InspectorImpl* inspector = 59 static_cast<V8InspectorImpl*>(v8::debug::GetInspector(isolate)); 60 InspectedContext* inspectedContext = 61 inspector->getContext(InspectedContext::contextId(context)); 62 if (!inspectedContext) return nullptr; 63 return inspectedContext->getInjectedScript(sessionId); 64} 65 66bool substituteObjectTags(int sessionId, const String16& groupName, 67 v8::Local<v8::Context> context, 68 v8::Local<v8::Array> jsonML, int maxDepth) { 69 if (!jsonML->Length()) return true; 70 v8::Isolate* isolate = context->GetIsolate(); 71 v8::TryCatch tryCatch(isolate); 72 73 if (maxDepth <= 0) { 74 reportError(context, tryCatch, 75 "Too deep hierarchy of inlined custom previews"); 76 return false; 77 } 78 79 v8::Local<v8::Value> firstValue; 80 if (!jsonML->Get(context, 0).ToLocal(&firstValue)) { 81 reportError(context, tryCatch); 82 return false; 83 } 84 v8::Local<v8::String> objectLiteral = toV8String(isolate, "object"); 85 if (jsonML->Length() == 2 && firstValue->IsString() && 86 firstValue.As<v8::String>()->StringEquals(objectLiteral)) { 87 v8::Local<v8::Value> attributesValue; 88 if (!jsonML->Get(context, 1).ToLocal(&attributesValue)) { 89 reportError(context, tryCatch); 90 return false; 91 } 92 if (!attributesValue->IsObject()) { 93 reportError(context, tryCatch, "attributes should be an Object"); 94 return false; 95 } 96 v8::Local<v8::Object> attributes = attributesValue.As<v8::Object>(); 97 v8::Local<v8::Value> originValue; 98 if (!attributes->Get(context, objectLiteral).ToLocal(&originValue)) { 99 reportError(context, tryCatch); 100 return false; 101 } 102 if (originValue->IsUndefined()) { 103 reportError(context, tryCatch, 104 "obligatory attribute \"object\" isn't specified"); 105 return false; 106 } 107 108 v8::Local<v8::Value> configValue; 109 if (!attributes->Get(context, toV8String(isolate, "config")) 110 .ToLocal(&configValue)) { 111 reportError(context, tryCatch); 112 return false; 113 } 114 115 InjectedScript* injectedScript = getInjectedScript(context, sessionId); 116 if (!injectedScript) { 117 reportError(context, tryCatch, "cannot find context with specified id"); 118 return false; 119 } 120 std::unique_ptr<protocol::Runtime::RemoteObject> wrapper; 121 protocol::Response response = 122 injectedScript->wrapObject(originValue, groupName, WrapMode::kNoPreview, 123 configValue, maxDepth - 1, &wrapper); 124 if (!response.IsSuccess() || !wrapper) { 125 reportError(context, tryCatch, "cannot wrap value"); 126 return false; 127 } 128 std::vector<uint8_t> json; 129 v8_crdtp::json::ConvertCBORToJSON(v8_crdtp::SpanFrom(wrapper->Serialize()), 130 &json); 131 v8::Local<v8::Value> jsonWrapper; 132 v8_inspector::StringView serialized(json.data(), json.size()); 133 if (!v8::JSON::Parse(context, toV8String(isolate, serialized)) 134 .ToLocal(&jsonWrapper)) { 135 reportError(context, tryCatch, "cannot wrap value"); 136 return false; 137 } 138 if (jsonML->Set(context, 1, jsonWrapper).IsNothing()) { 139 reportError(context, tryCatch); 140 return false; 141 } 142 } else { 143 for (uint32_t i = 0; i < jsonML->Length(); ++i) { 144 v8::Local<v8::Value> value; 145 if (!jsonML->Get(context, i).ToLocal(&value)) { 146 reportError(context, tryCatch); 147 return false; 148 } 149 if (value->IsArray() && value.As<v8::Array>()->Length() > 0 && 150 !substituteObjectTags(sessionId, groupName, context, 151 value.As<v8::Array>(), maxDepth - 1)) { 152 return false; 153 } 154 } 155 } 156 return true; 157} 158 159void bodyCallback(const v8::FunctionCallbackInfo<v8::Value>& info) { 160 v8::Isolate* isolate = info.GetIsolate(); 161 v8::TryCatch tryCatch(isolate); 162 v8::Local<v8::Context> context = isolate->GetCurrentContext(); 163 v8::Local<v8::Object> bodyConfig = info.Data().As<v8::Object>(); 164 165 v8::Local<v8::Value> objectValue; 166 if (!bodyConfig->Get(context, toV8String(isolate, "object")) 167 .ToLocal(&objectValue)) { 168 reportError(context, tryCatch); 169 return; 170 } 171 if (!objectValue->IsObject()) { 172 reportError(context, tryCatch, "object should be an Object"); 173 return; 174 } 175 v8::Local<v8::Object> object = objectValue.As<v8::Object>(); 176 177 v8::Local<v8::Value> formatterValue; 178 if (!bodyConfig->Get(context, toV8String(isolate, "formatter")) 179 .ToLocal(&formatterValue)) { 180 reportError(context, tryCatch); 181 return; 182 } 183 if (!formatterValue->IsObject()) { 184 reportError(context, tryCatch, "formatter should be an Object"); 185 return; 186 } 187 v8::Local<v8::Object> formatter = formatterValue.As<v8::Object>(); 188 189 v8::Local<v8::Value> bodyValue; 190 if (!formatter->Get(context, toV8String(isolate, "body")) 191 .ToLocal(&bodyValue)) { 192 reportError(context, tryCatch); 193 return; 194 } 195 if (!bodyValue->IsFunction()) { 196 reportError(context, tryCatch, "body should be a Function"); 197 return; 198 } 199 v8::Local<v8::Function> bodyFunction = bodyValue.As<v8::Function>(); 200 201 v8::Local<v8::Value> configValue; 202 if (!bodyConfig->Get(context, toV8String(isolate, "config")) 203 .ToLocal(&configValue)) { 204 reportError(context, tryCatch); 205 return; 206 } 207 208 v8::Local<v8::Value> sessionIdValue; 209 if (!bodyConfig->Get(context, toV8String(isolate, "sessionId")) 210 .ToLocal(&sessionIdValue)) { 211 reportError(context, tryCatch); 212 return; 213 } 214 if (!sessionIdValue->IsInt32()) { 215 reportError(context, tryCatch, "sessionId should be an Int32"); 216 return; 217 } 218 219 v8::Local<v8::Value> groupNameValue; 220 if (!bodyConfig->Get(context, toV8String(isolate, "groupName")) 221 .ToLocal(&groupNameValue)) { 222 reportError(context, tryCatch); 223 return; 224 } 225 if (!groupNameValue->IsString()) { 226 reportError(context, tryCatch, "groupName should be a string"); 227 return; 228 } 229 230 v8::Local<v8::Value> formattedValue; 231 v8::Local<v8::Value> args[] = {object, configValue}; 232 if (!bodyFunction->Call(context, formatter, 2, args) 233 .ToLocal(&formattedValue)) { 234 reportError(context, tryCatch); 235 return; 236 } 237 if (!formattedValue->IsArray()) { 238 reportError(context, tryCatch, "body should return an Array"); 239 return; 240 } 241 v8::Local<v8::Array> jsonML = formattedValue.As<v8::Array>(); 242 if (jsonML->Length() && 243 !substituteObjectTags( 244 sessionIdValue.As<v8::Int32>()->Value(), 245 toProtocolString(isolate, groupNameValue.As<v8::String>()), context, 246 jsonML, kMaxCustomPreviewDepth)) { 247 return; 248 } 249 info.GetReturnValue().Set(jsonML); 250} 251} // anonymous namespace 252 253void generateCustomPreview(int sessionId, const String16& groupName, 254 v8::Local<v8::Object> object, 255 v8::MaybeLocal<v8::Value> maybeConfig, int maxDepth, 256 std::unique_ptr<CustomPreview>* preview) { 257 v8::Local<v8::Context> context; 258 if (!object->GetCreationContext().ToLocal(&context)) { 259 return; 260 } 261 262 v8::Isolate* isolate = context->GetIsolate(); 263 v8::MicrotasksScope microtasksScope(isolate, 264 v8::MicrotasksScope::kDoNotRunMicrotasks); 265 v8::TryCatch tryCatch(isolate); 266 267 v8::Local<v8::Value> configValue; 268 if (!maybeConfig.ToLocal(&configValue)) configValue = v8::Undefined(isolate); 269 270 v8::Local<v8::Object> global = context->Global(); 271 v8::Local<v8::Value> formattersValue; 272 if (!global->Get(context, toV8String(isolate, "devtoolsFormatters")) 273 .ToLocal(&formattersValue)) { 274 reportError(context, tryCatch); 275 return; 276 } 277 if (!formattersValue->IsArray()) return; 278 v8::Local<v8::Array> formatters = formattersValue.As<v8::Array>(); 279 v8::Local<v8::String> headerLiteral = toV8String(isolate, "header"); 280 v8::Local<v8::String> hasBodyLiteral = toV8String(isolate, "hasBody"); 281 for (uint32_t i = 0; i < formatters->Length(); ++i) { 282 v8::Local<v8::Value> formatterValue; 283 if (!formatters->Get(context, i).ToLocal(&formatterValue)) { 284 reportError(context, tryCatch); 285 return; 286 } 287 if (!formatterValue->IsObject()) { 288 reportError(context, tryCatch, "formatter should be an Object"); 289 return; 290 } 291 v8::Local<v8::Object> formatter = formatterValue.As<v8::Object>(); 292 293 v8::Local<v8::Value> headerValue; 294 if (!formatter->Get(context, headerLiteral).ToLocal(&headerValue)) { 295 reportError(context, tryCatch); 296 return; 297 } 298 if (!headerValue->IsFunction()) { 299 reportError(context, tryCatch, "header should be a Function"); 300 return; 301 } 302 v8::Local<v8::Function> headerFunction = headerValue.As<v8::Function>(); 303 304 v8::Local<v8::Value> formattedValue; 305 v8::Local<v8::Value> args[] = {object, configValue}; 306 if (!headerFunction->Call(context, formatter, 2, args) 307 .ToLocal(&formattedValue)) { 308 reportError(context, tryCatch); 309 return; 310 } 311 if (!formattedValue->IsArray()) continue; 312 v8::Local<v8::Array> jsonML = formattedValue.As<v8::Array>(); 313 314 v8::Local<v8::Value> hasBodyFunctionValue; 315 if (!formatter->Get(context, hasBodyLiteral) 316 .ToLocal(&hasBodyFunctionValue)) { 317 reportError(context, tryCatch); 318 return; 319 } 320 if (!hasBodyFunctionValue->IsFunction()) continue; 321 v8::Local<v8::Function> hasBodyFunction = 322 hasBodyFunctionValue.As<v8::Function>(); 323 v8::Local<v8::Value> hasBodyValue; 324 if (!hasBodyFunction->Call(context, formatter, 2, args) 325 .ToLocal(&hasBodyValue)) { 326 reportError(context, tryCatch); 327 return; 328 } 329 bool hasBody = hasBodyValue->ToBoolean(isolate)->Value(); 330 331 if (jsonML->Length() && !substituteObjectTags(sessionId, groupName, context, 332 jsonML, maxDepth)) { 333 return; 334 } 335 336 v8::Local<v8::String> header; 337 if (!v8::JSON::Stringify(context, jsonML).ToLocal(&header)) { 338 reportError(context, tryCatch); 339 return; 340 } 341 342 v8::Local<v8::Function> bodyFunction; 343 if (hasBody) { 344 v8::Local<v8::Object> bodyConfig = v8::Object::New(isolate); 345 if (bodyConfig 346 ->CreateDataProperty(context, toV8String(isolate, "sessionId"), 347 v8::Integer::New(isolate, sessionId)) 348 .IsNothing()) { 349 reportError(context, tryCatch); 350 return; 351 } 352 if (bodyConfig 353 ->CreateDataProperty(context, toV8String(isolate, "formatter"), 354 formatter) 355 .IsNothing()) { 356 reportError(context, tryCatch); 357 return; 358 } 359 if (bodyConfig 360 ->CreateDataProperty(context, toV8String(isolate, "groupName"), 361 toV8String(isolate, groupName)) 362 .IsNothing()) { 363 reportError(context, tryCatch); 364 return; 365 } 366 if (bodyConfig 367 ->CreateDataProperty(context, toV8String(isolate, "config"), 368 configValue) 369 .IsNothing()) { 370 reportError(context, tryCatch); 371 return; 372 } 373 if (bodyConfig 374 ->CreateDataProperty(context, toV8String(isolate, "object"), 375 object) 376 .IsNothing()) { 377 reportError(context, tryCatch); 378 return; 379 } 380 if (!v8::Function::New(context, bodyCallback, bodyConfig) 381 .ToLocal(&bodyFunction)) { 382 reportError(context, tryCatch); 383 return; 384 } 385 } 386 *preview = CustomPreview::create() 387 .setHeader(toProtocolString(isolate, header)) 388 .build(); 389 if (!bodyFunction.IsEmpty()) { 390 InjectedScript* injectedScript = getInjectedScript(context, sessionId); 391 if (!injectedScript) { 392 reportError(context, tryCatch, "cannot find context with specified id"); 393 return; 394 } 395 (*preview)->setBodyGetterId( 396 injectedScript->bindObject(bodyFunction, groupName)); 397 } 398 return; 399 } 400} 401} // namespace v8_inspector 402