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 helper(info); 340 InspectedContext* context = helper.ensureInspectedContext(); 341 if (!context) return; 342 int contextGroupId = context->contextGroupId(); 343 if (V8InspectorClient* client = helper.ensureDebuggerClient()) 344 client->consoleClear(contextGroupId); 345 helper.reportCallWithDefaultArgument(ConsoleAPIType::kClear, 346 String16("console.clear")); 347 } 348 349 void V8Console::countCallback(const v8::FunctionCallbackInfo<v8::Value>& info) { 350 ConsoleHelper helper(info); 351 352 String16 title = helper.firstArgToString(String16()); 353 String16 identifier; 354 if (title.isEmpty()) { 355 std::unique_ptr<V8StackTraceImpl> stackTrace = 356 V8StackTraceImpl::capture(nullptr, 0, 1); 357 if (stackTrace && !stackTrace->isEmpty()) { 358 identifier = toString16(stackTrace->topSourceURL()) + ":" + 359 String16::fromInteger(stackTrace->topLineNumber()); 360 } 361 } else { 362 identifier = title + "@"; 363 } 364 365 v8::Local<v8::Map> countMap; 366 if (!helper.privateMap("V8Console#countMap").ToLocal(&countMap)) return; 367 int32_t count = helper.getIntFromMap(countMap, identifier, 0) + 1; 368 helper.setIntOnMap(countMap, identifier, count); 369 String16 countString = String16::fromInteger(count); 370 helper.reportCallWithArgument( 371 ConsoleAPIType::kCount, 372 title.isEmpty() ? countString : (title + ": " + countString)); 373 } 374 375 void V8Console::assertCallback( 376 const v8::FunctionCallbackInfo<v8::Value>& info) { 377 ConsoleHelper helper(info); 378 if (helper.firstArgToBoolean(false)) return; 379 380 std::vector<v8::Local<v8::Value>> arguments; 381 for (int i = 1; i < info.Length(); ++i) arguments.push_back(info[i]); 382 if (info.Length() < 2) 383 arguments.push_back( 384 toV8String(info.GetIsolate(), String16("console.assert"))); 385 helper.reportCall(ConsoleAPIType::kAssert, arguments); 386 387 if (V8DebuggerAgentImpl* debuggerAgent = helper.debuggerAgent()) 388 debuggerAgent->breakProgramOnException( 389 protocol::Debugger::Paused::ReasonEnum::Assert, nullptr); 390 } 391 392 void V8Console::markTimelineCallback( 393 const v8::FunctionCallbackInfo<v8::Value>& info) { 394 ConsoleHelper(info).reportDeprecatedCall("V8Console#markTimelineDeprecated", 395 "'console.markTimeline' is " 396 "deprecated. Please use " 397 "'console.timeStamp' instead."); 398 timeStampCallback(info); 399 } 400 401 void V8Console::profileCallback( 402 const v8::FunctionCallbackInfo<v8::Value>& info) { 403 ConsoleHelper helper(info); 404 if (V8ProfilerAgentImpl* profilerAgent = helper.profilerAgent()) 405 profilerAgent->consoleProfile(helper.firstArgToString(String16())); 406 } 407 408 void V8Console::profileEndCallback( 409 const v8::FunctionCallbackInfo<v8::Value>& info) { 410 ConsoleHelper helper(info); 411 if (V8ProfilerAgentImpl* profilerAgent = helper.profilerAgent()) 412 profilerAgent->consoleProfileEnd(helper.firstArgToString(String16())); 413 } 414 415 static void timeFunction(const v8::FunctionCallbackInfo<v8::Value>& info, 416 bool timelinePrefix) { 417 ConsoleHelper helper(info); 418 if (V8InspectorClient* client = helper.ensureDebuggerClient()) { 419 String16 protocolTitle = helper.firstArgToString("default"); 420 if (timelinePrefix) protocolTitle = "Timeline '" + protocolTitle + "'"; 421 client->consoleTime(toStringView(protocolTitle)); 422 423 v8::Local<v8::Map> timeMap; 424 if (!helper.privateMap("V8Console#timeMap").ToLocal(&timeMap)) return; 425 helper.setDoubleOnMap(timeMap, protocolTitle, client->currentTimeMS()); 426 } 427 } 428 429 static void timeEndFunction(const v8::FunctionCallbackInfo<v8::Value>& info, 430 bool timelinePrefix) { 431 ConsoleHelper helper(info); 432 if (V8InspectorClient* client = helper.ensureDebuggerClient()) { 433 String16 protocolTitle = helper.firstArgToString("default"); 434 if (timelinePrefix) protocolTitle = "Timeline '" + protocolTitle + "'"; 435 client->consoleTimeEnd(toStringView(protocolTitle)); 436 437 v8::Local<v8::Map> timeMap; 438 if (!helper.privateMap("V8Console#timeMap").ToLocal(&timeMap)) return; 439 double elapsed = client->currentTimeMS() - 440 helper.getDoubleFromMap(timeMap, protocolTitle, 0.0); 441 String16 message = 442 protocolTitle + ": " + String16::fromDouble(elapsed) + "ms"; 443 helper.reportCallWithArgument(ConsoleAPIType::kTimeEnd, message); 444 } 445 } 446 447 void V8Console::timelineCallback( 448 const v8::FunctionCallbackInfo<v8::Value>& info) { 449 ConsoleHelper(info).reportDeprecatedCall( 450 "V8Console#timeline", 451 "'console.timeline' is deprecated. Please use 'console.time' instead."); 452 timeFunction(info, true); 453 } 454 455 void V8Console::timelineEndCallback( 456 const v8::FunctionCallbackInfo<v8::Value>& info) { 457 ConsoleHelper(info).reportDeprecatedCall("V8Console#timelineEnd", 458 "'console.timelineEnd' is " 459 "deprecated. Please use " 460 "'console.timeEnd' instead."); 461 timeEndFunction(info, true); 462 } 463 464 void V8Console::timeCallback(const v8::FunctionCallbackInfo<v8::Value>& info) { 465 timeFunction(info, false); 466 } 467 468 void V8Console::timeEndCallback( 469 const v8::FunctionCallbackInfo<v8::Value>& info) { 470 timeEndFunction(info, false); 471 } 472 473 void V8Console::timeStampCallback( 474 const v8::FunctionCallbackInfo<v8::Value>& info) { 475 ConsoleHelper helper(info); 476 if (V8InspectorClient* client = helper.ensureDebuggerClient()) { 477 String16 title = helper.firstArgToString(String16()); 478 client->consoleTimeStamp(toStringView(title)); 479 } 480 } 481 482 void V8Console::memoryGetterCallback( 483 const v8::FunctionCallbackInfo<v8::Value>& info) { 484 if (V8InspectorClient* client = ConsoleHelper(info).ensureDebuggerClient()) { 485 v8::Local<v8::Value> memoryValue; 486 if (!client 487 ->memoryInfo(info.GetIsolate(), 488 info.GetIsolate()->GetCurrentContext()) 489 .ToLocal(&memoryValue)) 490 return; 491 info.GetReturnValue().Set(memoryValue); 492 } 493 } 494 495 void V8Console::memorySetterCallback( 496 const v8::FunctionCallbackInfo<v8::Value>& info) { 497 // We can't make the attribute readonly as it breaks existing code that relies 498 // on being able to assign to console.memory in strict mode. Instead, the 499 // setter just ignores the passed value. http://crbug.com/468611 500 } 501 502 void V8Console::keysCallback(const v8::FunctionCallbackInfo<v8::Value>& info) { 503 v8::Isolate* isolate = info.GetIsolate(); 504 info.GetReturnValue().Set(v8::Array::New(isolate)); 505 506 ConsoleHelper helper(info); 507 v8::Local<v8::Object> obj; 508 if (!helper.firstArgAsObject().ToLocal(&obj)) return; 509 v8::Local<v8::Array> names; 510 if (!obj->GetOwnPropertyNames(isolate->GetCurrentContext()).ToLocal(&names)) 511 return; 512 info.GetReturnValue().Set(names); 513 } 514 515 void V8Console::valuesCallback( 516 const v8::FunctionCallbackInfo<v8::Value>& info) { 517 v8::Isolate* isolate = info.GetIsolate(); 518 info.GetReturnValue().Set(v8::Array::New(isolate)); 519 520 ConsoleHelper helper(info); 521 v8::Local<v8::Object> obj; 522 if (!helper.firstArgAsObject().ToLocal(&obj)) return; 523 v8::Local<v8::Array> names; 524 v8::Local<v8::Context> context = isolate->GetCurrentContext(); 525 if (!obj->GetOwnPropertyNames(context).ToLocal(&names)) return; 526 v8::Local<v8::Array> values = v8::Array::New(isolate, names->Length()); 527 for (uint32_t i = 0; i < names->Length(); ++i) { 528 v8::Local<v8::Value> key; 529 if (!names->Get(context, i).ToLocal(&key)) continue; 530 v8::Local<v8::Value> value; 531 if (!obj->Get(context, key).ToLocal(&value)) continue; 532 createDataProperty(context, values, i, value); 533 } 534 info.GetReturnValue().Set(values); 535 } 536 537 static void setFunctionBreakpoint(ConsoleHelper& helper, 538 v8::Local<v8::Function> function, 539 V8DebuggerAgentImpl::BreakpointSource source, 540 const String16& condition, bool enable) { 541 V8DebuggerAgentImpl* debuggerAgent = helper.debuggerAgent(); 542 if (!debuggerAgent) return; 543 String16 scriptId = String16::fromInteger(function->ScriptId()); 544 int lineNumber = function->GetScriptLineNumber(); 545 int columnNumber = function->GetScriptColumnNumber(); 546 if (lineNumber == v8::Function::kLineOffsetNotFound || 547 columnNumber == v8::Function::kLineOffsetNotFound) 548 return; 549 if (enable) 550 debuggerAgent->setBreakpointAt(scriptId, lineNumber, columnNumber, source, 551 condition); 552 else 553 debuggerAgent->removeBreakpointAt(scriptId, lineNumber, columnNumber, 554 source); 555 } 556 557 void V8Console::debugFunctionCallback( 558 const v8::FunctionCallbackInfo<v8::Value>& info) { 559 ConsoleHelper helper(info); 560 v8::Local<v8::Function> function; 561 if (!helper.firstArgAsFunction().ToLocal(&function)) return; 562 setFunctionBreakpoint(helper, function, 563 V8DebuggerAgentImpl::DebugCommandBreakpointSource, 564 String16(), true); 565 } 566 567 void V8Console::undebugFunctionCallback( 568 const v8::FunctionCallbackInfo<v8::Value>& info) { 569 ConsoleHelper helper(info); 570 v8::Local<v8::Function> function; 571 if (!helper.firstArgAsFunction().ToLocal(&function)) return; 572 setFunctionBreakpoint(helper, function, 573 V8DebuggerAgentImpl::DebugCommandBreakpointSource, 574 String16(), false); 575 } 576 577 void V8Console::monitorFunctionCallback( 578 const v8::FunctionCallbackInfo<v8::Value>& info) { 579 ConsoleHelper helper(info); 580 v8::Local<v8::Function> function; 581 if (!helper.firstArgAsFunction().ToLocal(&function)) return; 582 v8::Local<v8::Value> name = function->GetName(); 583 if (!name->IsString() || !v8::Local<v8::String>::Cast(name)->Length()) 584 name = function->GetInferredName(); 585 String16 functionName = toProtocolStringWithTypeCheck(name); 586 String16Builder builder; 587 builder.append("console.log(\"function "); 588 if (functionName.isEmpty()) 589 builder.append("(anonymous function)"); 590 else 591 builder.append(functionName); 592 builder.append( 593 " called\" + (arguments.length > 0 ? \" with arguments: \" + " 594 "Array.prototype.join.call(arguments, \", \") : \"\")) && false"); 595 setFunctionBreakpoint(helper, function, 596 V8DebuggerAgentImpl::MonitorCommandBreakpointSource, 597 builder.toString(), true); 598 } 599 600 void V8Console::unmonitorFunctionCallback( 601 const v8::FunctionCallbackInfo<v8::Value>& info) { 602 ConsoleHelper helper(info); 603 v8::Local<v8::Function> function; 604 if (!helper.firstArgAsFunction().ToLocal(&function)) return; 605 setFunctionBreakpoint(helper, function, 606 V8DebuggerAgentImpl::MonitorCommandBreakpointSource, 607 String16(), false); 608 } 609 610 void V8Console::lastEvaluationResultCallback( 611 const v8::FunctionCallbackInfo<v8::Value>& info) { 612 ConsoleHelper helper(info); 613 InspectedContext* context = helper.ensureInspectedContext(); 614 if (!context) return; 615 if (InjectedScript* injectedScript = context->getInjectedScript()) 616 info.GetReturnValue().Set(injectedScript->lastEvaluationResult()); 617 } 618 619 static void inspectImpl(const v8::FunctionCallbackInfo<v8::Value>& info, 620 bool copyToClipboard) { 621 if (info.Length() < 1) return; 622 if (!copyToClipboard) info.GetReturnValue().Set(info[0]); 623 624 ConsoleHelper helper(info); 625 InspectedContext* context = helper.ensureInspectedContext(); 626 if (!context) return; 627 InjectedScript* injectedScript = context->getInjectedScript(); 628 if (!injectedScript) return; 629 std::unique_ptr<protocol::Runtime::RemoteObject> wrappedObject; 630 protocol::Response response = 631 injectedScript->wrapObject(info[0], "", false /** forceValueType */, 632 false /** generatePreview */, &wrappedObject); 633 if (!response.isSuccess()) return; 634 635 std::unique_ptr<protocol::DictionaryValue> hints = 636 protocol::DictionaryValue::create(); 637 if (copyToClipboard) hints->setBoolean("copyToClipboard", true); 638 if (V8InspectorSessionImpl* session = helper.currentSession()) 639 session->runtimeAgent()->inspect(std::move(wrappedObject), 640 std::move(hints)); 641 } 642 643 void V8Console::inspectCallback( 644 const v8::FunctionCallbackInfo<v8::Value>& info) { 645 inspectImpl(info, false); 646 } 647 648 void V8Console::copyCallback(const v8::FunctionCallbackInfo<v8::Value>& info) { 649 inspectImpl(info, true); 650 } 651 652 void V8Console::inspectedObject(const v8::FunctionCallbackInfo<v8::Value>& info, 653 unsigned num) { 654 DCHECK(num < V8InspectorSessionImpl::kInspectedObjectBufferSize); 655 ConsoleHelper helper(info); 656 if (V8InspectorSessionImpl* session = helper.currentSession()) { 657 V8InspectorSession::Inspectable* object = session->inspectedObject(num); 658 v8::Isolate* isolate = info.GetIsolate(); 659 if (object) 660 info.GetReturnValue().Set(object->get(isolate->GetCurrentContext())); 661 else 662 info.GetReturnValue().Set(v8::Undefined(isolate)); 663 } 664 } 665 666 v8::Local<v8::Object> V8Console::createConsole( 667 InspectedContext* inspectedContext, bool hasMemoryAttribute) { 668 v8::Local<v8::Context> context = inspectedContext->context(); 669 v8::Context::Scope contextScope(context); 670 v8::Isolate* isolate = context->GetIsolate(); 671 v8::MicrotasksScope microtasksScope(isolate, 672 v8::MicrotasksScope::kDoNotRunMicrotasks); 673 674 v8::Local<v8::Object> console = v8::Object::New(isolate); 675 bool success = 676 console->SetPrototype(context, v8::Object::New(isolate)).FromMaybe(false); 677 DCHECK(success); 678 USE(success); 679 680 createBoundFunctionProperty(context, console, "debug", 681 V8Console::debugCallback); 682 createBoundFunctionProperty(context, console, "error", 683 V8Console::errorCallback); 684 createBoundFunctionProperty(context, console, "info", 685 V8Console::infoCallback); 686 createBoundFunctionProperty(context, console, "log", V8Console::logCallback); 687 createBoundFunctionProperty(context, console, "warn", 688 V8Console::warnCallback); 689 createBoundFunctionProperty(context, console, "dir", V8Console::dirCallback); 690 createBoundFunctionProperty(context, console, "dirxml", 691 V8Console::dirxmlCallback); 692 createBoundFunctionProperty(context, console, "table", 693 V8Console::tableCallback); 694 createBoundFunctionProperty(context, console, "trace", 695 V8Console::traceCallback); 696 createBoundFunctionProperty(context, console, "group", 697 V8Console::groupCallback); 698 createBoundFunctionProperty(context, console, "groupCollapsed", 699 V8Console::groupCollapsedCallback); 700 createBoundFunctionProperty(context, console, "groupEnd", 701 V8Console::groupEndCallback); 702 createBoundFunctionProperty(context, console, "clear", 703 V8Console::clearCallback); 704 createBoundFunctionProperty(context, console, "count", 705 V8Console::countCallback); 706 createBoundFunctionProperty(context, console, "assert", 707 V8Console::assertCallback); 708 createBoundFunctionProperty(context, console, "markTimeline", 709 V8Console::markTimelineCallback); 710 createBoundFunctionProperty(context, console, "profile", 711 V8Console::profileCallback); 712 createBoundFunctionProperty(context, console, "profileEnd", 713 V8Console::profileEndCallback); 714 createBoundFunctionProperty(context, console, "timeline", 715 V8Console::timelineCallback); 716 createBoundFunctionProperty(context, console, "timelineEnd", 717 V8Console::timelineEndCallback); 718 createBoundFunctionProperty(context, console, "time", 719 V8Console::timeCallback); 720 createBoundFunctionProperty(context, console, "timeEnd", 721 V8Console::timeEndCallback); 722 createBoundFunctionProperty(context, console, "timeStamp", 723 V8Console::timeStampCallback); 724 725 const char* jsConsoleAssert = 726 "(function(){\n" 727 " var originAssert = this.assert;\n" 728 " originAssert.apply = Function.prototype.apply;\n" 729 " this.assert = assertWrapper;\n" 730 " assertWrapper.toString = () => originAssert.toString();\n" 731 " function assertWrapper(){\n" 732 " if (!!arguments[0]) return;\n" 733 " originAssert.apply(null, arguments);\n" 734 " }\n" 735 "})"; 736 737 v8::Local<v8::String> assertSource = toV8String(isolate, jsConsoleAssert); 738 V8InspectorImpl* inspector = inspectedContext->inspector(); 739 v8::Local<v8::Value> setupFunction; 740 if (inspector->compileAndRunInternalScript(context, assertSource) 741 .ToLocal(&setupFunction) && 742 setupFunction->IsFunction()) { 743 inspector->callInternalFunction( 744 v8::Local<v8::Function>::Cast(setupFunction), context, console, 0, 745 nullptr); 746 } 747 748 if (hasMemoryAttribute) 749 console->SetAccessorProperty( 750 toV8StringInternalized(isolate, "memory"), 751 v8::Function::New(context, V8Console::memoryGetterCallback, console, 0, 752 v8::ConstructorBehavior::kThrow) 753 .ToLocalChecked(), 754 v8::Function::New(context, V8Console::memorySetterCallback, 755 v8::Local<v8::Value>(), 0, 756 v8::ConstructorBehavior::kThrow) 757 .ToLocalChecked(), 758 static_cast<v8::PropertyAttribute>(v8::None), v8::DEFAULT); 759 760 console->SetPrivate(context, inspectedContextPrivateKey(isolate), 761 v8::External::New(isolate, inspectedContext)); 762 return console; 763 } 764 765 void V8Console::clearInspectedContextIfNeeded(v8::Local<v8::Context> context, 766 v8::Local<v8::Object> console) { 767 v8::Isolate* isolate = context->GetIsolate(); 768 console->SetPrivate(context, inspectedContextPrivateKey(isolate), 769 v8::External::New(isolate, nullptr)); 770 } 771 772 v8::Local<v8::Object> V8Console::createCommandLineAPI( 773 InspectedContext* inspectedContext) { 774 v8::Local<v8::Context> context = inspectedContext->context(); 775 v8::Isolate* isolate = context->GetIsolate(); 776 v8::MicrotasksScope microtasksScope(isolate, 777 v8::MicrotasksScope::kDoNotRunMicrotasks); 778 779 v8::Local<v8::Object> commandLineAPI = v8::Object::New(isolate); 780 bool success = 781 commandLineAPI->SetPrototype(context, v8::Null(isolate)).FromMaybe(false); 782 DCHECK(success); 783 USE(success); 784 785 createBoundFunctionProperty(context, commandLineAPI, "dir", 786 V8Console::dirCallback, 787 "function dir(value) { [Command Line API] }"); 788 createBoundFunctionProperty(context, commandLineAPI, "dirxml", 789 V8Console::dirxmlCallback, 790 "function dirxml(value) { [Command Line API] }"); 791 createBoundFunctionProperty(context, commandLineAPI, "profile", 792 V8Console::profileCallback, 793 "function profile(title) { [Command Line API] }"); 794 createBoundFunctionProperty( 795 context, commandLineAPI, "profileEnd", V8Console::profileEndCallback, 796 "function profileEnd(title) { [Command Line API] }"); 797 createBoundFunctionProperty(context, commandLineAPI, "clear", 798 V8Console::clearCallback, 799 "function clear() { [Command Line API] }"); 800 createBoundFunctionProperty( 801 context, commandLineAPI, "table", V8Console::tableCallback, 802 "function table(data, [columns]) { [Command Line API] }"); 803 804 createBoundFunctionProperty(context, commandLineAPI, "keys", 805 V8Console::keysCallback, 806 "function keys(object) { [Command Line API] }"); 807 createBoundFunctionProperty(context, commandLineAPI, "values", 808 V8Console::valuesCallback, 809 "function values(object) { [Command Line API] }"); 810 createBoundFunctionProperty( 811 context, commandLineAPI, "debug", V8Console::debugFunctionCallback, 812 "function debug(function) { [Command Line API] }"); 813 createBoundFunctionProperty( 814 context, commandLineAPI, "undebug", V8Console::undebugFunctionCallback, 815 "function undebug(function) { [Command Line API] }"); 816 createBoundFunctionProperty( 817 context, commandLineAPI, "monitor", V8Console::monitorFunctionCallback, 818 "function monitor(function) { [Command Line API] }"); 819 createBoundFunctionProperty( 820 context, commandLineAPI, "unmonitor", 821 V8Console::unmonitorFunctionCallback, 822 "function unmonitor(function) { [Command Line API] }"); 823 createBoundFunctionProperty( 824 context, commandLineAPI, "inspect", V8Console::inspectCallback, 825 "function inspect(object) { [Command Line API] }"); 826 createBoundFunctionProperty(context, commandLineAPI, "copy", 827 V8Console::copyCallback, 828 "function copy(value) { [Command Line API] }"); 829 createBoundFunctionProperty(context, commandLineAPI, "$_", 830 V8Console::lastEvaluationResultCallback); 831 createBoundFunctionProperty(context, commandLineAPI, "$0", 832 V8Console::inspectedObject0); 833 createBoundFunctionProperty(context, commandLineAPI, "$1", 834 V8Console::inspectedObject1); 835 createBoundFunctionProperty(context, commandLineAPI, "$2", 836 V8Console::inspectedObject2); 837 createBoundFunctionProperty(context, commandLineAPI, "$3", 838 V8Console::inspectedObject3); 839 createBoundFunctionProperty(context, commandLineAPI, "$4", 840 V8Console::inspectedObject4); 841 842 inspectedContext->inspector()->client()->installAdditionalCommandLineAPI( 843 context, commandLineAPI); 844 845 commandLineAPI->SetPrivate(context, inspectedContextPrivateKey(isolate), 846 v8::External::New(isolate, inspectedContext)); 847 return commandLineAPI; 848 } 849 850 static bool isCommandLineAPIGetter(const String16& name) { 851 if (name.length() != 2) return false; 852 // $0 ... $4, $_ 853 return name[0] == '$' && 854 ((name[1] >= '0' && name[1] <= '4') || name[1] == '_'); 855 } 856 857 void V8Console::CommandLineAPIScope::accessorGetterCallback( 858 v8::Local<v8::Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) { 859 CommandLineAPIScope* scope = static_cast<CommandLineAPIScope*>( 860 info.Data().As<v8::External>()->Value()); 861 DCHECK(scope); 862 863 v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext(); 864 if (scope->m_cleanup) { 865 bool removed = info.Holder()->Delete(context, name).FromMaybe(false); 866 DCHECK(removed); 867 USE(removed); 868 return; 869 } 870 v8::Local<v8::Object> commandLineAPI = scope->m_commandLineAPI; 871 872 v8::Local<v8::Value> value; 873 if (!commandLineAPI->Get(context, name).ToLocal(&value)) return; 874 if (isCommandLineAPIGetter(toProtocolStringWithTypeCheck(name))) { 875 DCHECK(value->IsFunction()); 876 v8::MicrotasksScope microtasks(info.GetIsolate(), 877 v8::MicrotasksScope::kDoNotRunMicrotasks); 878 if (value.As<v8::Function>() 879 ->Call(context, commandLineAPI, 0, nullptr) 880 .ToLocal(&value)) 881 info.GetReturnValue().Set(value); 882 } else { 883 info.GetReturnValue().Set(value); 884 } 885 } 886 887 void V8Console::CommandLineAPIScope::accessorSetterCallback( 888 v8::Local<v8::Name> name, v8::Local<v8::Value> value, 889 const v8::PropertyCallbackInfo<void>& info) { 890 CommandLineAPIScope* scope = static_cast<CommandLineAPIScope*>( 891 info.Data().As<v8::External>()->Value()); 892 v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext(); 893 if (!info.Holder()->Delete(context, name).FromMaybe(false)) return; 894 if (!info.Holder()->CreateDataProperty(context, name, value).FromMaybe(false)) 895 return; 896 bool removed = 897 scope->m_installedMethods->Delete(context, name).FromMaybe(false); 898 DCHECK(removed); 899 USE(removed); 900 } 901 902 V8Console::CommandLineAPIScope::CommandLineAPIScope( 903 v8::Local<v8::Context> context, v8::Local<v8::Object> commandLineAPI, 904 v8::Local<v8::Object> global) 905 : m_context(context), 906 m_commandLineAPI(commandLineAPI), 907 m_global(global), 908 m_installedMethods(v8::Set::New(context->GetIsolate())), 909 m_cleanup(false) { 910 v8::Local<v8::Array> names; 911 if (!m_commandLineAPI->GetOwnPropertyNames(context).ToLocal(&names)) return; 912 v8::Local<v8::External> externalThis = 913 v8::External::New(context->GetIsolate(), this); 914 for (uint32_t i = 0; i < names->Length(); ++i) { 915 v8::Local<v8::Value> name; 916 if (!names->Get(context, i).ToLocal(&name) || !name->IsName()) continue; 917 if (m_global->Has(context, name).FromMaybe(true)) continue; 918 if (!m_installedMethods->Add(context, name).ToLocal(&m_installedMethods)) 919 continue; 920 if (!m_global 921 ->SetAccessor(context, v8::Local<v8::Name>::Cast(name), 922 CommandLineAPIScope::accessorGetterCallback, 923 CommandLineAPIScope::accessorSetterCallback, 924 externalThis, v8::DEFAULT, v8::DontEnum) 925 .FromMaybe(false)) { 926 bool removed = m_installedMethods->Delete(context, name).FromMaybe(false); 927 DCHECK(removed); 928 USE(removed); 929 continue; 930 } 931 } 932 } 933 934 V8Console::CommandLineAPIScope::~CommandLineAPIScope() { 935 m_cleanup = true; 936 v8::Local<v8::Array> names = m_installedMethods->AsArray(); 937 for (uint32_t i = 0; i < names->Length(); ++i) { 938 v8::Local<v8::Value> name; 939 if (!names->Get(m_context, i).ToLocal(&name) || !name->IsName()) continue; 940 if (name->IsString()) { 941 v8::Local<v8::Value> descriptor; 942 bool success = m_global 943 ->GetOwnPropertyDescriptor( 944 m_context, v8::Local<v8::String>::Cast(name)) 945 .ToLocal(&descriptor); 946 DCHECK(success); 947 USE(success); 948 } 949 } 950 } 951 952 } // namespace v8_inspector 953