1 // Copyright 2016 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/v8-console.h" 6 7 #include "src/base/macros.h" 8 #include "src/inspector/injected-script.h" 9 #include "src/inspector/inspected-context.h" 10 #include "src/inspector/string-util.h" 11 #include "src/inspector/v8-console-message.h" 12 #include "src/inspector/v8-debugger-agent-impl.h" 13 #include "src/inspector/v8-inspector-impl.h" 14 #include "src/inspector/v8-inspector-session-impl.h" 15 #include "src/inspector/v8-profiler-agent-impl.h" 16 #include "src/inspector/v8-runtime-agent-impl.h" 17 #include "src/inspector/v8-stack-trace-impl.h" 18 #include "src/inspector/v8-value-copier.h" 19 20 #include "include/v8-inspector.h" 21 22 namespace v8_inspector { 23 24 namespace { 25 26 v8::Local<v8::Private> inspectedContextPrivateKey(v8::Isolate* isolate) { 27 return v8::Private::ForApi( 28 isolate, toV8StringInternalized(isolate, "V8Console#InspectedContext")); 29 } 30 31 class ConsoleHelper { 32 public: 33 explicit ConsoleHelper(const v8::FunctionCallbackInfo<v8::Value>& info) 34 : m_info(info), 35 m_isolate(info.GetIsolate()), 36 m_context(info.GetIsolate()->GetCurrentContext()), 37 m_inspectedContext(nullptr), 38 m_inspectorClient(nullptr) {} 39 40 v8::Local<v8::Object> ensureConsole() { 41 if (m_console.IsEmpty()) { 42 DCHECK(!m_info.Data().IsEmpty()); 43 DCHECK(!m_info.Data()->IsUndefined()); 44 m_console = m_info.Data().As<v8::Object>(); 45 } 46 return m_console; 47 } 48 49 InspectedContext* ensureInspectedContext() { 50 if (m_inspectedContext) return m_inspectedContext; 51 v8::Local<v8::Object> console = ensureConsole(); 52 53 v8::Local<v8::Private> key = inspectedContextPrivateKey(m_isolate); 54 v8::Local<v8::Value> inspectedContextValue; 55 if (!console->GetPrivate(m_context, key).ToLocal(&inspectedContextValue)) 56 return nullptr; 57 DCHECK(inspectedContextValue->IsExternal()); 58 m_inspectedContext = static_cast<InspectedContext*>( 59 inspectedContextValue.As<v8::External>()->Value()); 60 return m_inspectedContext; 61 } 62 63 V8InspectorClient* ensureDebuggerClient() { 64 if (m_inspectorClient) return m_inspectorClient; 65 InspectedContext* inspectedContext = ensureInspectedContext(); 66 if (!inspectedContext) return nullptr; 67 m_inspectorClient = inspectedContext->inspector()->client(); 68 return m_inspectorClient; 69 } 70 71 void reportCall(ConsoleAPIType type) { 72 if (!m_info.Length()) return; 73 std::vector<v8::Local<v8::Value>> arguments; 74 for (int i = 0; i < m_info.Length(); ++i) arguments.push_back(m_info[i]); 75 reportCall(type, arguments); 76 } 77 78 void reportCallWithDefaultArgument(ConsoleAPIType type, 79 const String16& message) { 80 std::vector<v8::Local<v8::Value>> arguments; 81 for (int i = 0; i < m_info.Length(); ++i) arguments.push_back(m_info[i]); 82 if (!m_info.Length()) arguments.push_back(toV8String(m_isolate, message)); 83 reportCall(type, arguments); 84 } 85 86 void reportCallWithArgument(ConsoleAPIType type, const String16& message) { 87 std::vector<v8::Local<v8::Value>> arguments(1, 88 toV8String(m_isolate, message)); 89 reportCall(type, arguments); 90 } 91 92 void reportCall(ConsoleAPIType type, 93 const std::vector<v8::Local<v8::Value>>& arguments) { 94 InspectedContext* inspectedContext = ensureInspectedContext(); 95 if (!inspectedContext) return; 96 int contextGroupId = inspectedContext->contextGroupId(); 97 V8InspectorImpl* inspector = inspectedContext->inspector(); 98 std::unique_ptr<V8ConsoleMessage> message = 99 V8ConsoleMessage::createForConsoleAPI( 100 inspector->client()->currentTimeMS(), type, arguments, 101 inspector->debugger()->captureStackTrace(false), inspectedContext); 102 inspector->ensureConsoleMessageStorage(contextGroupId) 103 ->addMessage(std::move(message)); 104 } 105 106 void reportDeprecatedCall(const char* id, const String16& message) { 107 if (checkAndSetPrivateFlagOnConsole(id, false)) return; 108 std::vector<v8::Local<v8::Value>> arguments(1, 109 toV8String(m_isolate, message)); 110 reportCall(ConsoleAPIType::kWarning, arguments); 111 } 112 113 bool firstArgToBoolean(bool defaultValue) { 114 if (m_info.Length() < 1) return defaultValue; 115 if (m_info[0]->IsBoolean()) return m_info[0].As<v8::Boolean>()->Value(); 116 return m_info[0]->BooleanValue(m_context).FromMaybe(defaultValue); 117 } 118 119 String16 firstArgToString(const String16& defaultValue) { 120 if (m_info.Length() < 1) return defaultValue; 121 v8::Local<v8::String> titleValue; 122 if (m_info[0]->IsObject()) { 123 if (!m_info[0].As<v8::Object>()->ObjectProtoToString(m_context).ToLocal( 124 &titleValue)) 125 return defaultValue; 126 } else { 127 if (!m_info[0]->ToString(m_context).ToLocal(&titleValue)) 128 return defaultValue; 129 } 130 return toProtocolString(titleValue); 131 } 132 133 v8::MaybeLocal<v8::Object> firstArgAsObject() { 134 if (m_info.Length() < 1 || !m_info[0]->IsObject()) 135 return v8::MaybeLocal<v8::Object>(); 136 return m_info[0].As<v8::Object>(); 137 } 138 139 v8::MaybeLocal<v8::Function> firstArgAsFunction() { 140 if (m_info.Length() < 1 || !m_info[0]->IsFunction()) 141 return v8::MaybeLocal<v8::Function>(); 142 v8::Local<v8::Function> func = m_info[0].As<v8::Function>(); 143 while (func->GetBoundFunction()->IsFunction()) 144 func = func->GetBoundFunction().As<v8::Function>(); 145 return func; 146 } 147 148 v8::MaybeLocal<v8::Map> privateMap(const char* name) { 149 v8::Local<v8::Object> console = ensureConsole(); 150 v8::Local<v8::Private> privateKey = 151 v8::Private::ForApi(m_isolate, toV8StringInternalized(m_isolate, name)); 152 v8::Local<v8::Value> mapValue; 153 if (!console->GetPrivate(m_context, privateKey).ToLocal(&mapValue)) 154 return v8::MaybeLocal<v8::Map>(); 155 if (mapValue->IsUndefined()) { 156 v8::Local<v8::Map> map = v8::Map::New(m_isolate); 157 if (!console->SetPrivate(m_context, privateKey, map).FromMaybe(false)) 158 return v8::MaybeLocal<v8::Map>(); 159 return map; 160 } 161 return mapValue->IsMap() ? mapValue.As<v8::Map>() 162 : v8::MaybeLocal<v8::Map>(); 163 } 164 165 int32_t getIntFromMap(v8::Local<v8::Map> map, const String16& key, 166 int32_t defaultValue) { 167 v8::Local<v8::String> v8Key = toV8String(m_isolate, key); 168 if (!map->Has(m_context, v8Key).FromMaybe(false)) return defaultValue; 169 v8::Local<v8::Value> intValue; 170 if (!map->Get(m_context, v8Key).ToLocal(&intValue)) return defaultValue; 171 return static_cast<int32_t>(intValue.As<v8::Integer>()->Value()); 172 } 173 174 void setIntOnMap(v8::Local<v8::Map> map, const String16& key, int32_t value) { 175 v8::Local<v8::String> v8Key = toV8String(m_isolate, key); 176 if (!map->Set(m_context, v8Key, v8::Integer::New(m_isolate, value)) 177 .ToLocal(&map)) 178 return; 179 } 180 181 double getDoubleFromMap(v8::Local<v8::Map> map, const String16& key, 182 double defaultValue) { 183 v8::Local<v8::String> v8Key = toV8String(m_isolate, key); 184 if (!map->Has(m_context, v8Key).FromMaybe(false)) return defaultValue; 185 v8::Local<v8::Value> intValue; 186 if (!map->Get(m_context, v8Key).ToLocal(&intValue)) return defaultValue; 187 return intValue.As<v8::Number>()->Value(); 188 } 189 190 void setDoubleOnMap(v8::Local<v8::Map> map, const String16& key, 191 double value) { 192 v8::Local<v8::String> v8Key = toV8String(m_isolate, key); 193 if (!map->Set(m_context, v8Key, v8::Number::New(m_isolate, value)) 194 .ToLocal(&map)) 195 return; 196 } 197 198 V8ProfilerAgentImpl* profilerAgent() { 199 if (V8InspectorSessionImpl* session = currentSession()) { 200 if (session && session->profilerAgent()->enabled()) 201 return session->profilerAgent(); 202 } 203 return nullptr; 204 } 205 206 V8DebuggerAgentImpl* debuggerAgent() { 207 if (V8InspectorSessionImpl* session = currentSession()) { 208 if (session && session->debuggerAgent()->enabled()) 209 return session->debuggerAgent(); 210 } 211 return nullptr; 212 } 213 214 V8InspectorSessionImpl* currentSession() { 215 InspectedContext* inspectedContext = ensureInspectedContext(); 216 if (!inspectedContext) return nullptr; 217 return inspectedContext->inspector()->sessionForContextGroup( 218 inspectedContext->contextGroupId()); 219 } 220 221 private: 222 const v8::FunctionCallbackInfo<v8::Value>& m_info; 223 v8::Isolate* m_isolate; 224 v8::Local<v8::Context> m_context; 225 v8::Local<v8::Object> m_console; 226 InspectedContext* m_inspectedContext; 227 V8InspectorClient* m_inspectorClient; 228 229 bool checkAndSetPrivateFlagOnConsole(const char* name, bool defaultValue) { 230 v8::Local<v8::Object> console = ensureConsole(); 231 v8::Local<v8::Private> key = 232 v8::Private::ForApi(m_isolate, toV8StringInternalized(m_isolate, name)); 233 v8::Local<v8::Value> flagValue; 234 if (!console->GetPrivate(m_context, key).ToLocal(&flagValue)) 235 return defaultValue; 236 DCHECK(flagValue->IsUndefined() || flagValue->IsBoolean()); 237 if (flagValue->IsBoolean()) { 238 DCHECK(flagValue.As<v8::Boolean>()->Value()); 239 return true; 240 } 241 if (!console->SetPrivate(m_context, key, v8::True(m_isolate)) 242 .FromMaybe(false)) 243 return defaultValue; 244 return false; 245 } 246 247 DISALLOW_COPY_AND_ASSIGN(ConsoleHelper); 248 }; 249 250 void returnDataCallback(const v8::FunctionCallbackInfo<v8::Value>& info) { 251 info.GetReturnValue().Set(info.Data()); 252 } 253 254 void createBoundFunctionProperty(v8::Local<v8::Context> context, 255 v8::Local<v8::Object> console, 256 const char* name, 257 v8::FunctionCallback callback, 258 const char* description = nullptr) { 259 v8::Local<v8::String> funcName = 260 toV8StringInternalized(context->GetIsolate(), name); 261 v8::Local<v8::Function> func; 262 if (!v8::Function::New(context, callback, console, 0, 263 v8::ConstructorBehavior::kThrow) 264 .ToLocal(&func)) 265 return; 266 func->SetName(funcName); 267 if (description) { 268 v8::Local<v8::String> returnValue = 269 toV8String(context->GetIsolate(), description); 270 v8::Local<v8::Function> toStringFunction; 271 if (v8::Function::New(context, returnDataCallback, returnValue, 0, 272 v8::ConstructorBehavior::kThrow) 273 .ToLocal(&toStringFunction)) 274 createDataProperty(context, func, toV8StringInternalized( 275 context->GetIsolate(), "toString"), 276 toStringFunction); 277 } 278 createDataProperty(context, console, funcName, func); 279 } 280 281 } // namespace 282 283 void V8Console::debugCallback(const v8::FunctionCallbackInfo<v8::Value>& info) { 284 ConsoleHelper(info).reportCall(ConsoleAPIType::kDebug); 285 } 286 287 void V8Console::errorCallback(const v8::FunctionCallbackInfo<v8::Value>& info) { 288 ConsoleHelper(info).reportCall(ConsoleAPIType::kError); 289 } 290 291 void V8Console::infoCallback(const v8::FunctionCallbackInfo<v8::Value>& info) { 292 ConsoleHelper(info).reportCall(ConsoleAPIType::kInfo); 293 } 294 295 void V8Console::logCallback(const v8::FunctionCallbackInfo<v8::Value>& info) { 296 ConsoleHelper(info).reportCall(ConsoleAPIType::kLog); 297 } 298 299 void V8Console::warnCallback(const v8::FunctionCallbackInfo<v8::Value>& info) { 300 ConsoleHelper(info).reportCall(ConsoleAPIType::kWarning); 301 } 302 303 void V8Console::dirCallback(const v8::FunctionCallbackInfo<v8::Value>& info) { 304 ConsoleHelper(info).reportCall(ConsoleAPIType::kDir); 305 } 306 307 void V8Console::dirxmlCallback( 308 const v8::FunctionCallbackInfo<v8::Value>& info) { 309 ConsoleHelper(info).reportCall(ConsoleAPIType::kDirXML); 310 } 311 312 void V8Console::tableCallback(const v8::FunctionCallbackInfo<v8::Value>& info) { 313 ConsoleHelper(info).reportCall(ConsoleAPIType::kTable); 314 } 315 316 void V8Console::traceCallback(const v8::FunctionCallbackInfo<v8::Value>& info) { 317 ConsoleHelper(info).reportCallWithDefaultArgument(ConsoleAPIType::kTrace, 318 String16("console.trace")); 319 } 320 321 void V8Console::groupCallback(const v8::FunctionCallbackInfo<v8::Value>& info) { 322 ConsoleHelper(info).reportCallWithDefaultArgument(ConsoleAPIType::kStartGroup, 323 String16("console.group")); 324 } 325 326 void V8Console::groupCollapsedCallback( 327 const v8::FunctionCallbackInfo<v8::Value>& info) { 328 ConsoleHelper(info).reportCallWithDefaultArgument( 329 ConsoleAPIType::kStartGroupCollapsed, String16("console.groupCollapsed")); 330 } 331 332 void V8Console::groupEndCallback( 333 const v8::FunctionCallbackInfo<v8::Value>& info) { 334 ConsoleHelper(info).reportCallWithDefaultArgument( 335 ConsoleAPIType::kEndGroup, String16("console.groupEnd")); 336 } 337 338 void V8Console::clearCallback(const v8::FunctionCallbackInfo<v8::Value>& info) { 339 ConsoleHelper(info).reportCallWithDefaultArgument(ConsoleAPIType::kClear, 340 String16("console.clear")); 341 } 342 343 void V8Console::countCallback(const v8::FunctionCallbackInfo<v8::Value>& info) { 344 ConsoleHelper helper(info); 345 346 String16 title = helper.firstArgToString(String16()); 347 String16 identifier; 348 if (title.isEmpty()) { 349 std::unique_ptr<V8StackTraceImpl> stackTrace = 350 V8StackTraceImpl::capture(nullptr, 0, 1); 351 if (stackTrace && !stackTrace->isEmpty()) { 352 identifier = toString16(stackTrace->topSourceURL()) + ":" + 353 String16::fromInteger(stackTrace->topLineNumber()); 354 } 355 } else { 356 identifier = title + "@"; 357 } 358 359 v8::Local<v8::Map> countMap; 360 if (!helper.privateMap("V8Console#countMap").ToLocal(&countMap)) return; 361 int32_t count = helper.getIntFromMap(countMap, identifier, 0) + 1; 362 helper.setIntOnMap(countMap, identifier, count); 363 helper.reportCallWithArgument(ConsoleAPIType::kCount, 364 title + ": " + String16::fromInteger(count)); 365 } 366 367 void V8Console::assertCallback( 368 const v8::FunctionCallbackInfo<v8::Value>& info) { 369 ConsoleHelper helper(info); 370 if (helper.firstArgToBoolean(false)) return; 371 372 std::vector<v8::Local<v8::Value>> arguments; 373 for (int i = 1; i < info.Length(); ++i) arguments.push_back(info[i]); 374 if (info.Length() < 2) 375 arguments.push_back( 376 toV8String(info.GetIsolate(), String16("console.assert"))); 377 helper.reportCall(ConsoleAPIType::kAssert, arguments); 378 379 if (V8DebuggerAgentImpl* debuggerAgent = helper.debuggerAgent()) 380 debuggerAgent->breakProgramOnException( 381 protocol::Debugger::Paused::ReasonEnum::Assert, nullptr); 382 } 383 384 void V8Console::markTimelineCallback( 385 const v8::FunctionCallbackInfo<v8::Value>& info) { 386 ConsoleHelper(info).reportDeprecatedCall("V8Console#markTimelineDeprecated", 387 "'console.markTimeline' is " 388 "deprecated. Please use " 389 "'console.timeStamp' instead."); 390 timeStampCallback(info); 391 } 392 393 void V8Console::profileCallback( 394 const v8::FunctionCallbackInfo<v8::Value>& info) { 395 ConsoleHelper helper(info); 396 if (V8ProfilerAgentImpl* profilerAgent = helper.profilerAgent()) 397 profilerAgent->consoleProfile(helper.firstArgToString(String16())); 398 } 399 400 void V8Console::profileEndCallback( 401 const v8::FunctionCallbackInfo<v8::Value>& info) { 402 ConsoleHelper helper(info); 403 if (V8ProfilerAgentImpl* profilerAgent = helper.profilerAgent()) 404 profilerAgent->consoleProfileEnd(helper.firstArgToString(String16())); 405 } 406 407 static void timeFunction(const v8::FunctionCallbackInfo<v8::Value>& info, 408 bool timelinePrefix) { 409 ConsoleHelper helper(info); 410 if (V8InspectorClient* client = helper.ensureDebuggerClient()) { 411 String16 protocolTitle = helper.firstArgToString("default"); 412 if (timelinePrefix) protocolTitle = "Timeline '" + protocolTitle + "'"; 413 client->consoleTime(toStringView(protocolTitle)); 414 415 v8::Local<v8::Map> timeMap; 416 if (!helper.privateMap("V8Console#timeMap").ToLocal(&timeMap)) return; 417 helper.setDoubleOnMap(timeMap, protocolTitle, client->currentTimeMS()); 418 } 419 } 420 421 static void timeEndFunction(const v8::FunctionCallbackInfo<v8::Value>& info, 422 bool timelinePrefix) { 423 ConsoleHelper helper(info); 424 if (V8InspectorClient* client = helper.ensureDebuggerClient()) { 425 String16 protocolTitle = helper.firstArgToString("default"); 426 if (timelinePrefix) protocolTitle = "Timeline '" + protocolTitle + "'"; 427 client->consoleTimeEnd(toStringView(protocolTitle)); 428 429 v8::Local<v8::Map> timeMap; 430 if (!helper.privateMap("V8Console#timeMap").ToLocal(&timeMap)) return; 431 double elapsed = client->currentTimeMS() - 432 helper.getDoubleFromMap(timeMap, protocolTitle, 0.0); 433 String16 message = 434 protocolTitle + ": " + String16::fromDouble(elapsed, 3) + "ms"; 435 helper.reportCallWithArgument(ConsoleAPIType::kTimeEnd, message); 436 } 437 } 438 439 void V8Console::timelineCallback( 440 const v8::FunctionCallbackInfo<v8::Value>& info) { 441 ConsoleHelper(info).reportDeprecatedCall( 442 "V8Console#timeline", 443 "'console.timeline' is deprecated. Please use 'console.time' instead."); 444 timeFunction(info, true); 445 } 446 447 void V8Console::timelineEndCallback( 448 const v8::FunctionCallbackInfo<v8::Value>& info) { 449 ConsoleHelper(info).reportDeprecatedCall("V8Console#timelineEnd", 450 "'console.timelineEnd' is " 451 "deprecated. Please use " 452 "'console.timeEnd' instead."); 453 timeEndFunction(info, true); 454 } 455 456 void V8Console::timeCallback(const v8::FunctionCallbackInfo<v8::Value>& info) { 457 timeFunction(info, false); 458 } 459 460 void V8Console::timeEndCallback( 461 const v8::FunctionCallbackInfo<v8::Value>& info) { 462 timeEndFunction(info, false); 463 } 464 465 void V8Console::timeStampCallback( 466 const v8::FunctionCallbackInfo<v8::Value>& info) { 467 ConsoleHelper helper(info); 468 if (V8InspectorClient* client = helper.ensureDebuggerClient()) { 469 String16 title = helper.firstArgToString(String16()); 470 client->consoleTimeStamp(toStringView(title)); 471 } 472 } 473 474 void V8Console::memoryGetterCallback( 475 const v8::FunctionCallbackInfo<v8::Value>& info) { 476 if (V8InspectorClient* client = ConsoleHelper(info).ensureDebuggerClient()) { 477 v8::Local<v8::Value> memoryValue; 478 if (!client 479 ->memoryInfo(info.GetIsolate(), 480 info.GetIsolate()->GetCurrentContext()) 481 .ToLocal(&memoryValue)) 482 return; 483 info.GetReturnValue().Set(memoryValue); 484 } 485 } 486 487 void V8Console::memorySetterCallback( 488 const v8::FunctionCallbackInfo<v8::Value>& info) { 489 // We can't make the attribute readonly as it breaks existing code that relies 490 // on being able to assign to console.memory in strict mode. Instead, the 491 // setter just ignores the passed value. http://crbug.com/468611 492 } 493 494 void V8Console::keysCallback(const v8::FunctionCallbackInfo<v8::Value>& info) { 495 v8::Isolate* isolate = info.GetIsolate(); 496 info.GetReturnValue().Set(v8::Array::New(isolate)); 497 498 ConsoleHelper helper(info); 499 v8::Local<v8::Object> obj; 500 if (!helper.firstArgAsObject().ToLocal(&obj)) return; 501 v8::Local<v8::Array> names; 502 if (!obj->GetOwnPropertyNames(isolate->GetCurrentContext()).ToLocal(&names)) 503 return; 504 info.GetReturnValue().Set(names); 505 } 506 507 void V8Console::valuesCallback( 508 const v8::FunctionCallbackInfo<v8::Value>& info) { 509 v8::Isolate* isolate = info.GetIsolate(); 510 info.GetReturnValue().Set(v8::Array::New(isolate)); 511 512 ConsoleHelper helper(info); 513 v8::Local<v8::Object> obj; 514 if (!helper.firstArgAsObject().ToLocal(&obj)) return; 515 v8::Local<v8::Array> names; 516 v8::Local<v8::Context> context = isolate->GetCurrentContext(); 517 if (!obj->GetOwnPropertyNames(context).ToLocal(&names)) return; 518 v8::Local<v8::Array> values = v8::Array::New(isolate, names->Length()); 519 for (uint32_t i = 0; i < names->Length(); ++i) { 520 v8::Local<v8::Value> key; 521 if (!names->Get(context, i).ToLocal(&key)) continue; 522 v8::Local<v8::Value> value; 523 if (!obj->Get(context, key).ToLocal(&value)) continue; 524 createDataProperty(context, values, i, value); 525 } 526 info.GetReturnValue().Set(values); 527 } 528 529 static void setFunctionBreakpoint(ConsoleHelper& helper, 530 v8::Local<v8::Function> function, 531 V8DebuggerAgentImpl::BreakpointSource source, 532 const String16& condition, bool enable) { 533 V8DebuggerAgentImpl* debuggerAgent = helper.debuggerAgent(); 534 if (!debuggerAgent) return; 535 String16 scriptId = String16::fromInteger(function->ScriptId()); 536 int lineNumber = function->GetScriptLineNumber(); 537 int columnNumber = function->GetScriptColumnNumber(); 538 if (lineNumber == v8::Function::kLineOffsetNotFound || 539 columnNumber == v8::Function::kLineOffsetNotFound) 540 return; 541 if (enable) 542 debuggerAgent->setBreakpointAt(scriptId, lineNumber, columnNumber, source, 543 condition); 544 else 545 debuggerAgent->removeBreakpointAt(scriptId, lineNumber, columnNumber, 546 source); 547 } 548 549 void V8Console::debugFunctionCallback( 550 const v8::FunctionCallbackInfo<v8::Value>& info) { 551 ConsoleHelper helper(info); 552 v8::Local<v8::Function> function; 553 if (!helper.firstArgAsFunction().ToLocal(&function)) return; 554 setFunctionBreakpoint(helper, function, 555 V8DebuggerAgentImpl::DebugCommandBreakpointSource, 556 String16(), true); 557 } 558 559 void V8Console::undebugFunctionCallback( 560 const v8::FunctionCallbackInfo<v8::Value>& info) { 561 ConsoleHelper helper(info); 562 v8::Local<v8::Function> function; 563 if (!helper.firstArgAsFunction().ToLocal(&function)) return; 564 setFunctionBreakpoint(helper, function, 565 V8DebuggerAgentImpl::DebugCommandBreakpointSource, 566 String16(), false); 567 } 568 569 void V8Console::monitorFunctionCallback( 570 const v8::FunctionCallbackInfo<v8::Value>& info) { 571 ConsoleHelper helper(info); 572 v8::Local<v8::Function> function; 573 if (!helper.firstArgAsFunction().ToLocal(&function)) return; 574 v8::Local<v8::Value> name = function->GetName(); 575 if (!name->IsString() || !v8::Local<v8::String>::Cast(name)->Length()) 576 name = function->GetInferredName(); 577 String16 functionName = toProtocolStringWithTypeCheck(name); 578 String16Builder builder; 579 builder.append("console.log(\"function "); 580 if (functionName.isEmpty()) 581 builder.append("(anonymous function)"); 582 else 583 builder.append(functionName); 584 builder.append( 585 " called\" + (arguments.length > 0 ? \" with arguments: \" + " 586 "Array.prototype.join.call(arguments, \", \") : \"\")) && false"); 587 setFunctionBreakpoint(helper, function, 588 V8DebuggerAgentImpl::MonitorCommandBreakpointSource, 589 builder.toString(), true); 590 } 591 592 void V8Console::unmonitorFunctionCallback( 593 const v8::FunctionCallbackInfo<v8::Value>& info) { 594 ConsoleHelper helper(info); 595 v8::Local<v8::Function> function; 596 if (!helper.firstArgAsFunction().ToLocal(&function)) return; 597 setFunctionBreakpoint(helper, function, 598 V8DebuggerAgentImpl::MonitorCommandBreakpointSource, 599 String16(), false); 600 } 601 602 void V8Console::lastEvaluationResultCallback( 603 const v8::FunctionCallbackInfo<v8::Value>& info) { 604 ConsoleHelper helper(info); 605 InspectedContext* context = helper.ensureInspectedContext(); 606 if (!context) return; 607 if (InjectedScript* injectedScript = context->getInjectedScript()) 608 info.GetReturnValue().Set(injectedScript->lastEvaluationResult()); 609 } 610 611 static void inspectImpl(const v8::FunctionCallbackInfo<v8::Value>& info, 612 bool copyToClipboard) { 613 if (info.Length() < 1) return; 614 if (!copyToClipboard) info.GetReturnValue().Set(info[0]); 615 616 ConsoleHelper helper(info); 617 InspectedContext* context = helper.ensureInspectedContext(); 618 if (!context) return; 619 InjectedScript* injectedScript = context->getInjectedScript(); 620 if (!injectedScript) return; 621 std::unique_ptr<protocol::Runtime::RemoteObject> wrappedObject; 622 protocol::Response response = 623 injectedScript->wrapObject(info[0], "", false /** forceValueType */, 624 false /** generatePreview */, &wrappedObject); 625 if (!response.isSuccess()) return; 626 627 std::unique_ptr<protocol::DictionaryValue> hints = 628 protocol::DictionaryValue::create(); 629 if (copyToClipboard) hints->setBoolean("copyToClipboard", true); 630 if (V8InspectorSessionImpl* session = helper.currentSession()) 631 session->runtimeAgent()->inspect(std::move(wrappedObject), 632 std::move(hints)); 633 } 634 635 void V8Console::inspectCallback( 636 const v8::FunctionCallbackInfo<v8::Value>& info) { 637 inspectImpl(info, false); 638 } 639 640 void V8Console::copyCallback(const v8::FunctionCallbackInfo<v8::Value>& info) { 641 inspectImpl(info, true); 642 } 643 644 void V8Console::inspectedObject(const v8::FunctionCallbackInfo<v8::Value>& info, 645 unsigned num) { 646 DCHECK(num < V8InspectorSessionImpl::kInspectedObjectBufferSize); 647 ConsoleHelper helper(info); 648 if (V8InspectorSessionImpl* session = helper.currentSession()) { 649 V8InspectorSession::Inspectable* object = session->inspectedObject(num); 650 v8::Isolate* isolate = info.GetIsolate(); 651 if (object) 652 info.GetReturnValue().Set(object->get(isolate->GetCurrentContext())); 653 else 654 info.GetReturnValue().Set(v8::Undefined(isolate)); 655 } 656 } 657 658 v8::Local<v8::Object> V8Console::createConsole( 659 InspectedContext* inspectedContext, bool hasMemoryAttribute) { 660 v8::Local<v8::Context> context = inspectedContext->context(); 661 v8::Context::Scope contextScope(context); 662 v8::Isolate* isolate = context->GetIsolate(); 663 v8::MicrotasksScope microtasksScope(isolate, 664 v8::MicrotasksScope::kDoNotRunMicrotasks); 665 666 v8::Local<v8::Object> console = v8::Object::New(isolate); 667 bool success = 668 console->SetPrototype(context, v8::Object::New(isolate)).FromMaybe(false); 669 DCHECK(success); 670 USE(success); 671 672 createBoundFunctionProperty(context, console, "debug", 673 V8Console::debugCallback); 674 createBoundFunctionProperty(context, console, "error", 675 V8Console::errorCallback); 676 createBoundFunctionProperty(context, console, "info", 677 V8Console::infoCallback); 678 createBoundFunctionProperty(context, console, "log", V8Console::logCallback); 679 createBoundFunctionProperty(context, console, "warn", 680 V8Console::warnCallback); 681 createBoundFunctionProperty(context, console, "dir", V8Console::dirCallback); 682 createBoundFunctionProperty(context, console, "dirxml", 683 V8Console::dirxmlCallback); 684 createBoundFunctionProperty(context, console, "table", 685 V8Console::tableCallback); 686 createBoundFunctionProperty(context, console, "trace", 687 V8Console::traceCallback); 688 createBoundFunctionProperty(context, console, "group", 689 V8Console::groupCallback); 690 createBoundFunctionProperty(context, console, "groupCollapsed", 691 V8Console::groupCollapsedCallback); 692 createBoundFunctionProperty(context, console, "groupEnd", 693 V8Console::groupEndCallback); 694 createBoundFunctionProperty(context, console, "clear", 695 V8Console::clearCallback); 696 createBoundFunctionProperty(context, console, "count", 697 V8Console::countCallback); 698 createBoundFunctionProperty(context, console, "assert", 699 V8Console::assertCallback); 700 createBoundFunctionProperty(context, console, "markTimeline", 701 V8Console::markTimelineCallback); 702 createBoundFunctionProperty(context, console, "profile", 703 V8Console::profileCallback); 704 createBoundFunctionProperty(context, console, "profileEnd", 705 V8Console::profileEndCallback); 706 createBoundFunctionProperty(context, console, "timeline", 707 V8Console::timelineCallback); 708 createBoundFunctionProperty(context, console, "timelineEnd", 709 V8Console::timelineEndCallback); 710 createBoundFunctionProperty(context, console, "time", 711 V8Console::timeCallback); 712 createBoundFunctionProperty(context, console, "timeEnd", 713 V8Console::timeEndCallback); 714 createBoundFunctionProperty(context, console, "timeStamp", 715 V8Console::timeStampCallback); 716 717 if (hasMemoryAttribute) 718 console->SetAccessorProperty( 719 toV8StringInternalized(isolate, "memory"), 720 v8::Function::New(context, V8Console::memoryGetterCallback, console, 0, 721 v8::ConstructorBehavior::kThrow) 722 .ToLocalChecked(), 723 v8::Function::New(context, V8Console::memorySetterCallback, 724 v8::Local<v8::Value>(), 0, 725 v8::ConstructorBehavior::kThrow) 726 .ToLocalChecked(), 727 static_cast<v8::PropertyAttribute>(v8::None), v8::DEFAULT); 728 729 console->SetPrivate(context, inspectedContextPrivateKey(isolate), 730 v8::External::New(isolate, inspectedContext)); 731 return console; 732 } 733 734 void V8Console::clearInspectedContextIfNeeded(v8::Local<v8::Context> context, 735 v8::Local<v8::Object> console) { 736 v8::Isolate* isolate = context->GetIsolate(); 737 console->SetPrivate(context, inspectedContextPrivateKey(isolate), 738 v8::External::New(isolate, nullptr)); 739 } 740 741 v8::Local<v8::Object> V8Console::createCommandLineAPI( 742 InspectedContext* inspectedContext) { 743 v8::Local<v8::Context> context = inspectedContext->context(); 744 v8::Isolate* isolate = context->GetIsolate(); 745 v8::MicrotasksScope microtasksScope(isolate, 746 v8::MicrotasksScope::kDoNotRunMicrotasks); 747 748 v8::Local<v8::Object> commandLineAPI = v8::Object::New(isolate); 749 bool success = 750 commandLineAPI->SetPrototype(context, v8::Null(isolate)).FromMaybe(false); 751 DCHECK(success); 752 USE(success); 753 754 createBoundFunctionProperty(context, commandLineAPI, "dir", 755 V8Console::dirCallback, 756 "function dir(value) { [Command Line API] }"); 757 createBoundFunctionProperty(context, commandLineAPI, "dirxml", 758 V8Console::dirxmlCallback, 759 "function dirxml(value) { [Command Line API] }"); 760 createBoundFunctionProperty(context, commandLineAPI, "profile", 761 V8Console::profileCallback, 762 "function profile(title) { [Command Line API] }"); 763 createBoundFunctionProperty( 764 context, commandLineAPI, "profileEnd", V8Console::profileEndCallback, 765 "function profileEnd(title) { [Command Line API] }"); 766 createBoundFunctionProperty(context, commandLineAPI, "clear", 767 V8Console::clearCallback, 768 "function clear() { [Command Line API] }"); 769 createBoundFunctionProperty( 770 context, commandLineAPI, "table", V8Console::tableCallback, 771 "function table(data, [columns]) { [Command Line API] }"); 772 773 createBoundFunctionProperty(context, commandLineAPI, "keys", 774 V8Console::keysCallback, 775 "function keys(object) { [Command Line API] }"); 776 createBoundFunctionProperty(context, commandLineAPI, "values", 777 V8Console::valuesCallback, 778 "function values(object) { [Command Line API] }"); 779 createBoundFunctionProperty( 780 context, commandLineAPI, "debug", V8Console::debugFunctionCallback, 781 "function debug(function) { [Command Line API] }"); 782 createBoundFunctionProperty( 783 context, commandLineAPI, "undebug", V8Console::undebugFunctionCallback, 784 "function undebug(function) { [Command Line API] }"); 785 createBoundFunctionProperty( 786 context, commandLineAPI, "monitor", V8Console::monitorFunctionCallback, 787 "function monitor(function) { [Command Line API] }"); 788 createBoundFunctionProperty( 789 context, commandLineAPI, "unmonitor", 790 V8Console::unmonitorFunctionCallback, 791 "function unmonitor(function) { [Command Line API] }"); 792 createBoundFunctionProperty( 793 context, commandLineAPI, "inspect", V8Console::inspectCallback, 794 "function inspect(object) { [Command Line API] }"); 795 createBoundFunctionProperty(context, commandLineAPI, "copy", 796 V8Console::copyCallback, 797 "function copy(value) { [Command Line API] }"); 798 createBoundFunctionProperty(context, commandLineAPI, "$_", 799 V8Console::lastEvaluationResultCallback); 800 createBoundFunctionProperty(context, commandLineAPI, "$0", 801 V8Console::inspectedObject0); 802 createBoundFunctionProperty(context, commandLineAPI, "$1", 803 V8Console::inspectedObject1); 804 createBoundFunctionProperty(context, commandLineAPI, "$2", 805 V8Console::inspectedObject2); 806 createBoundFunctionProperty(context, commandLineAPI, "$3", 807 V8Console::inspectedObject3); 808 createBoundFunctionProperty(context, commandLineAPI, "$4", 809 V8Console::inspectedObject4); 810 811 inspectedContext->inspector()->client()->installAdditionalCommandLineAPI( 812 context, commandLineAPI); 813 814 commandLineAPI->SetPrivate(context, inspectedContextPrivateKey(isolate), 815 v8::External::New(isolate, inspectedContext)); 816 return commandLineAPI; 817 } 818 819 static bool isCommandLineAPIGetter(const String16& name) { 820 if (name.length() != 2) return false; 821 // $0 ... $4, $_ 822 return name[0] == '$' && 823 ((name[1] >= '0' && name[1] <= '4') || name[1] == '_'); 824 } 825 826 void V8Console::CommandLineAPIScope::accessorGetterCallback( 827 v8::Local<v8::Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) { 828 CommandLineAPIScope* scope = static_cast<CommandLineAPIScope*>( 829 info.Data().As<v8::External>()->Value()); 830 DCHECK(scope); 831 832 v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext(); 833 if (scope->m_cleanup) { 834 bool removed = info.Holder()->Delete(context, name).FromMaybe(false); 835 DCHECK(removed); 836 USE(removed); 837 return; 838 } 839 v8::Local<v8::Object> commandLineAPI = scope->m_commandLineAPI; 840 841 v8::Local<v8::Value> value; 842 if (!commandLineAPI->Get(context, name).ToLocal(&value)) return; 843 if (isCommandLineAPIGetter(toProtocolStringWithTypeCheck(name))) { 844 DCHECK(value->IsFunction()); 845 v8::MicrotasksScope microtasks(info.GetIsolate(), 846 v8::MicrotasksScope::kDoNotRunMicrotasks); 847 if (value.As<v8::Function>() 848 ->Call(context, commandLineAPI, 0, nullptr) 849 .ToLocal(&value)) 850 info.GetReturnValue().Set(value); 851 } else { 852 info.GetReturnValue().Set(value); 853 } 854 } 855 856 void V8Console::CommandLineAPIScope::accessorSetterCallback( 857 v8::Local<v8::Name> name, v8::Local<v8::Value> value, 858 const v8::PropertyCallbackInfo<void>& info) { 859 CommandLineAPIScope* scope = static_cast<CommandLineAPIScope*>( 860 info.Data().As<v8::External>()->Value()); 861 v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext(); 862 if (!info.Holder()->Delete(context, name).FromMaybe(false)) return; 863 if (!info.Holder()->CreateDataProperty(context, name, value).FromMaybe(false)) 864 return; 865 bool removed = 866 scope->m_installedMethods->Delete(context, name).FromMaybe(false); 867 DCHECK(removed); 868 USE(removed); 869 } 870 871 V8Console::CommandLineAPIScope::CommandLineAPIScope( 872 v8::Local<v8::Context> context, v8::Local<v8::Object> commandLineAPI, 873 v8::Local<v8::Object> global) 874 : m_context(context), 875 m_commandLineAPI(commandLineAPI), 876 m_global(global), 877 m_installedMethods(v8::Set::New(context->GetIsolate())), 878 m_cleanup(false) { 879 v8::Local<v8::Array> names; 880 if (!m_commandLineAPI->GetOwnPropertyNames(context).ToLocal(&names)) return; 881 v8::Local<v8::External> externalThis = 882 v8::External::New(context->GetIsolate(), this); 883 for (uint32_t i = 0; i < names->Length(); ++i) { 884 v8::Local<v8::Value> name; 885 if (!names->Get(context, i).ToLocal(&name) || !name->IsName()) continue; 886 if (m_global->Has(context, name).FromMaybe(true)) continue; 887 if (!m_installedMethods->Add(context, name).ToLocal(&m_installedMethods)) 888 continue; 889 if (!m_global 890 ->SetAccessor(context, v8::Local<v8::Name>::Cast(name), 891 CommandLineAPIScope::accessorGetterCallback, 892 CommandLineAPIScope::accessorSetterCallback, 893 externalThis, v8::DEFAULT, v8::DontEnum) 894 .FromMaybe(false)) { 895 bool removed = m_installedMethods->Delete(context, name).FromMaybe(false); 896 DCHECK(removed); 897 USE(removed); 898 continue; 899 } 900 } 901 } 902 903 V8Console::CommandLineAPIScope::~CommandLineAPIScope() { 904 m_cleanup = true; 905 v8::Local<v8::Array> names = m_installedMethods->AsArray(); 906 for (uint32_t i = 0; i < names->Length(); ++i) { 907 v8::Local<v8::Value> name; 908 if (!names->Get(m_context, i).ToLocal(&name) || !name->IsName()) continue; 909 if (name->IsString()) { 910 v8::Local<v8::Value> descriptor; 911 bool success = m_global 912 ->GetOwnPropertyDescriptor( 913 m_context, v8::Local<v8::String>::Cast(name)) 914 .ToLocal(&descriptor); 915 DCHECK(success); 916 USE(success); 917 } 918 } 919 } 920 921 } // namespace v8_inspector 922