blob: 6d89e11e409dab377a9b36f539cd67c6226e1f99 [file] [log] [blame]
// Copyright 2016 The Chromium 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 "third_party/blink/renderer/core/inspector/thread_debugger.h"
#include <memory>
#include "third_party/blink/renderer/bindings/core/v8/script_source_code.h"
#include "third_party/blink/renderer/bindings/core/v8/source_location.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_blob.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_dom_exception.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_dom_token_list.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_event.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_event_listener.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_event_listener_info.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_html_all_collection.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_html_collection.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_node.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_node_list.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_script_runner.h"
#include "third_party/blink/renderer/core/dom/user_gesture_indicator.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/inspector/inspector_dom_debugger_agent.h"
#include "third_party/blink/renderer/core/inspector/inspector_trace_events.h"
#include "third_party/blink/renderer/core/inspector/v8_inspector_string.h"
#include "third_party/blink/renderer/core/probe/core_probes.h"
#include "third_party/blink/renderer/platform/bindings/script_forbidden_scope.h"
#include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
#include "third_party/blink/renderer/platform/wtf/time.h"
namespace blink {
ThreadDebugger::ThreadDebugger(v8::Isolate* isolate)
: isolate_(isolate),
v8_inspector_(v8_inspector::V8Inspector::create(isolate, this)) {}
ThreadDebugger::~ThreadDebugger() = default;
// static
ThreadDebugger* ThreadDebugger::From(v8::Isolate* isolate) {
if (!isolate)
return nullptr;
V8PerIsolateData* data = V8PerIsolateData::From(isolate);
return data ? static_cast<ThreadDebugger*>(data->ThreadDebugger()) : nullptr;
}
// static
MessageLevel ThreadDebugger::V8MessageLevelToMessageLevel(
v8::Isolate::MessageErrorLevel level) {
MessageLevel result = kInfoMessageLevel;
switch (level) {
case v8::Isolate::kMessageDebug:
result = kVerboseMessageLevel;
break;
case v8::Isolate::kMessageWarning:
result = kWarningMessageLevel;
break;
case v8::Isolate::kMessageError:
result = kErrorMessageLevel;
break;
case v8::Isolate::kMessageLog:
case v8::Isolate::kMessageInfo:
default:
result = kInfoMessageLevel;
break;
}
return result;
}
void ThreadDebugger::IdleStarted(v8::Isolate* isolate) {
if (ThreadDebugger* debugger = ThreadDebugger::From(isolate))
debugger->GetV8Inspector()->idleStarted();
}
void ThreadDebugger::IdleFinished(v8::Isolate* isolate) {
if (ThreadDebugger* debugger = ThreadDebugger::From(isolate))
debugger->GetV8Inspector()->idleFinished();
}
void ThreadDebugger::AsyncTaskScheduled(const StringView& operation_name,
void* task,
bool recurring) {
DCHECK_EQ(reinterpret_cast<intptr_t>(task) % 2, 0);
v8_inspector_->asyncTaskScheduled(ToV8InspectorStringView(operation_name),
task, recurring);
}
void ThreadDebugger::AsyncTaskCanceled(void* task) {
DCHECK_EQ(reinterpret_cast<intptr_t>(task) % 2, 0);
v8_inspector_->asyncTaskCanceled(task);
}
void ThreadDebugger::AllAsyncTasksCanceled() {
v8_inspector_->allAsyncTasksCanceled();
}
void ThreadDebugger::AsyncTaskStarted(void* task) {
DCHECK_EQ(reinterpret_cast<intptr_t>(task) % 2, 0);
v8_inspector_->asyncTaskStarted(task);
}
void ThreadDebugger::AsyncTaskFinished(void* task) {
DCHECK_EQ(reinterpret_cast<intptr_t>(task) % 2, 0);
v8_inspector_->asyncTaskFinished(task);
}
v8_inspector::V8StackTraceId ThreadDebugger::StoreCurrentStackTrace(
const StringView& description) {
return v8_inspector_->storeCurrentStackTrace(
ToV8InspectorStringView(description));
}
void ThreadDebugger::ExternalAsyncTaskStarted(
const v8_inspector::V8StackTraceId& parent) {
v8_inspector_->externalAsyncTaskStarted(parent);
}
void ThreadDebugger::ExternalAsyncTaskFinished(
const v8_inspector::V8StackTraceId& parent) {
v8_inspector_->externalAsyncTaskFinished(parent);
}
unsigned ThreadDebugger::PromiseRejected(
v8::Local<v8::Context> context,
const String& error_message,
v8::Local<v8::Value> exception,
std::unique_ptr<SourceLocation> location) {
const String default_message = "Uncaught (in promise)";
String message = error_message;
if (message.IsEmpty())
message = default_message;
else if (message.StartsWith("Uncaught "))
message = message.Substring(0, 8) + " (in promise)" + message.Substring(8);
ReportConsoleMessage(ToExecutionContext(context), kJSMessageSource,
kErrorMessageLevel, message, location.get());
String url = location->Url();
return GetV8Inspector()->exceptionThrown(
context, ToV8InspectorStringView(default_message), exception,
ToV8InspectorStringView(message), ToV8InspectorStringView(url),
location->LineNumber(), location->ColumnNumber(),
location->TakeStackTrace(), location->ScriptId());
}
void ThreadDebugger::PromiseRejectionRevoked(v8::Local<v8::Context> context,
unsigned promise_rejection_id) {
const String message = "Handler added to rejected promise";
GetV8Inspector()->exceptionRevoked(context, promise_rejection_id,
ToV8InspectorStringView(message));
}
void ThreadDebugger::beginUserGesture() {
ExecutionContext* ec = CurrentExecutionContext(isolate_);
Document* document = DynamicTo<Document>(ec);
user_gesture_indicator_ = LocalFrame::NotifyUserActivation(
document ? document->GetFrame() : nullptr);
}
void ThreadDebugger::endUserGesture() {
user_gesture_indicator_.reset();
}
std::unique_ptr<v8_inspector::StringBuffer> ThreadDebugger::valueSubtype(
v8::Local<v8::Value> value) {
static const char kNode[] = "node";
static const char kArray[] = "array";
static const char kError[] = "error";
static const char kBlob[] = "blob";
if (V8Node::HasInstance(value, isolate_))
return ToV8InspectorStringBuffer(kNode);
if (V8NodeList::HasInstance(value, isolate_) ||
V8DOMTokenList::HasInstance(value, isolate_) ||
V8HTMLCollection::HasInstance(value, isolate_) ||
V8HTMLAllCollection::HasInstance(value, isolate_)) {
return ToV8InspectorStringBuffer(kArray);
}
if (V8DOMException::HasInstance(value, isolate_))
return ToV8InspectorStringBuffer(kError);
if (V8Blob::HasInstance(value, isolate_))
return ToV8InspectorStringBuffer(kBlob);
return nullptr;
}
bool ThreadDebugger::formatAccessorsAsProperties(v8::Local<v8::Value> value) {
return V8DOMWrapper::IsWrapper(isolate_, value);
}
double ThreadDebugger::currentTimeMS() {
return WTF::CurrentTimeMS();
}
bool ThreadDebugger::isInspectableHeapObject(v8::Local<v8::Object> object) {
if (object->InternalFieldCount() < kV8DefaultWrapperInternalFieldCount)
return true;
v8::Local<v8::Value> wrapper =
object->GetInternalField(kV8DOMWrapperObjectIndex);
// Skip wrapper boilerplates which are like regular wrappers but don't have
// native object.
if (!wrapper.IsEmpty() && wrapper->IsUndefined())
return false;
return true;
}
static void ReturnDataCallback(
const v8::FunctionCallbackInfo<v8::Value>& info) {
info.GetReturnValue().Set(info.Data());
}
static v8::Maybe<bool> CreateDataProperty(v8::Local<v8::Context> context,
v8::Local<v8::Object> object,
v8::Local<v8::Name> key,
v8::Local<v8::Value> value) {
v8::TryCatch try_catch(context->GetIsolate());
v8::Isolate::DisallowJavascriptExecutionScope throw_js(
context->GetIsolate(),
v8::Isolate::DisallowJavascriptExecutionScope::THROW_ON_FAILURE);
return object->CreateDataProperty(context, key, value);
}
static void CreateFunctionPropertyWithData(
v8::Local<v8::Context> context,
v8::Local<v8::Object> object,
const char* name,
v8::FunctionCallback callback,
v8::Local<v8::Value> data,
const char* description,
v8::SideEffectType side_effect_type) {
v8::Local<v8::String> func_name = V8String(context->GetIsolate(), name);
v8::Local<v8::Function> func;
if (!v8::Function::New(context, callback, data, 0,
v8::ConstructorBehavior::kThrow, side_effect_type)
.ToLocal(&func))
return;
func->SetName(func_name);
v8::Local<v8::String> return_value =
V8String(context->GetIsolate(), description);
v8::Local<v8::Function> to_string_function;
if (v8::Function::New(context, ReturnDataCallback, return_value, 0,
v8::ConstructorBehavior::kThrow,
v8::SideEffectType::kHasNoSideEffect)
.ToLocal(&to_string_function))
CreateDataProperty(context, func,
V8AtomicString(context->GetIsolate(), "toString"),
to_string_function);
CreateDataProperty(context, object, func_name, func);
}
v8::Maybe<bool> ThreadDebugger::CreateDataPropertyInArray(
v8::Local<v8::Context> context,
v8::Local<v8::Array> array,
int index,
v8::Local<v8::Value> value) {
v8::TryCatch try_catch(context->GetIsolate());
v8::Isolate::DisallowJavascriptExecutionScope throw_js(
context->GetIsolate(),
v8::Isolate::DisallowJavascriptExecutionScope::THROW_ON_FAILURE);
return array->CreateDataProperty(context, index, value);
}
void ThreadDebugger::CreateFunctionProperty(
v8::Local<v8::Context> context,
v8::Local<v8::Object> object,
const char* name,
v8::FunctionCallback callback,
const char* description,
v8::SideEffectType side_effect_type) {
CreateFunctionPropertyWithData(context, object, name, callback,
v8::External::New(context->GetIsolate(), this),
description, side_effect_type);
}
void ThreadDebugger::installAdditionalCommandLineAPI(
v8::Local<v8::Context> context,
v8::Local<v8::Object> object) {
CreateFunctionProperty(
context, object, "getEventListeners",
ThreadDebugger::GetEventListenersCallback,
"function getEventListeners(node) { [Command Line API] }",
v8::SideEffectType::kHasNoSideEffect);
v8::Local<v8::Value> function_value;
bool success =
V8ScriptRunner::CompileAndRunInternalScript(
isolate_, ScriptState::From(context),
ScriptSourceCode("(function(e) { console.log(e.type, e); })",
ScriptSourceLocationType::kInternal, nullptr, KURL(),
TextPosition()))
.ToLocal(&function_value) &&
function_value->IsFunction();
DCHECK(success);
CreateFunctionPropertyWithData(
context, object, "monitorEvents", ThreadDebugger::MonitorEventsCallback,
function_value,
"function monitorEvents(object, [types]) { [Command Line API] }",
v8::SideEffectType::kHasSideEffect);
CreateFunctionPropertyWithData(
context, object, "unmonitorEvents",
ThreadDebugger::UnmonitorEventsCallback, function_value,
"function unmonitorEvents(object, [types]) { [Command Line API] }",
v8::SideEffectType::kHasSideEffect);
}
static Vector<String> NormalizeEventTypes(
const v8::FunctionCallbackInfo<v8::Value>& info) {
Vector<String> types;
if (info.Length() > 1 && info[1]->IsString())
types.push_back(ToCoreString(info[1].As<v8::String>()));
if (info.Length() > 1 && info[1]->IsArray()) {
v8::Local<v8::Array> types_array = v8::Local<v8::Array>::Cast(info[1]);
for (wtf_size_t i = 0; i < types_array->Length(); ++i) {
v8::Local<v8::Value> type_value;
if (!types_array->Get(info.GetIsolate()->GetCurrentContext(), i)
.ToLocal(&type_value) ||
!type_value->IsString())
continue;
types.push_back(ToCoreString(v8::Local<v8::String>::Cast(type_value)));
}
}
if (info.Length() == 1)
types.AppendVector(
Vector<String>({"mouse", "key", "touch",
"pointer", "control", "load",
"unload", "abort", "error",
"select", "input", "change",
"submit", "reset", "focus",
"blur", "resize", "scroll",
"search", "devicemotion", "deviceorientation"}));
Vector<String> output_types;
for (wtf_size_t i = 0; i < types.size(); ++i) {
if (types[i] == "mouse")
output_types.AppendVector(
Vector<String>({"auxclick", "click", "dblclick", "mousedown",
"mouseeenter", "mouseleave", "mousemove", "mouseout",
"mouseover", "mouseup", "mouseleave", "mousewheel"}));
else if (types[i] == "key")
output_types.AppendVector(
Vector<String>({"keydown", "keyup", "keypress", "textInput"}));
else if (types[i] == "touch")
output_types.AppendVector(Vector<String>(
{"touchstart", "touchmove", "touchend", "touchcancel"}));
else if (types[i] == "pointer")
output_types.AppendVector(Vector<String>(
{"pointerover", "pointerout", "pointerenter", "pointerleave",
"pointerdown", "pointerup", "pointermove", "pointercancel",
"gotpointercapture", "lostpointercapture"}));
else if (types[i] == "control")
output_types.AppendVector(
Vector<String>({"resize", "scroll", "zoom", "focus", "blur", "select",
"input", "change", "submit", "reset"}));
else
output_types.push_back(types[i]);
}
return output_types;
}
static EventTarget* FirstArgumentAsEventTarget(
const v8::FunctionCallbackInfo<v8::Value>& info) {
if (info.Length() < 1)
return nullptr;
if (EventTarget* target =
V8EventTarget::ToImplWithTypeCheck(info.GetIsolate(), info[0]))
return target;
return ToDOMWindow(info.GetIsolate(), info[0]);
}
void ThreadDebugger::SetMonitorEventsCallback(
const v8::FunctionCallbackInfo<v8::Value>& info,
bool enabled) {
EventTarget* event_target = FirstArgumentAsEventTarget(info);
if (!event_target)
return;
Vector<String> types = NormalizeEventTypes(info);
DCHECK(!info.Data().IsEmpty() && info.Data()->IsFunction());
V8EventListener* event_listener =
V8EventListener::Create(info.Data().As<v8::Function>());
for (wtf_size_t i = 0; i < types.size(); ++i) {
if (enabled)
event_target->addEventListener(AtomicString(types[i]), event_listener);
else
event_target->removeEventListener(AtomicString(types[i]), event_listener);
}
}
// static
void ThreadDebugger::MonitorEventsCallback(
const v8::FunctionCallbackInfo<v8::Value>& info) {
SetMonitorEventsCallback(info, true);
}
// static
void ThreadDebugger::UnmonitorEventsCallback(
const v8::FunctionCallbackInfo<v8::Value>& info) {
SetMonitorEventsCallback(info, false);
}
// static
void ThreadDebugger::GetEventListenersCallback(
const v8::FunctionCallbackInfo<v8::Value>& info) {
if (info.Length() < 1)
return;
ThreadDebugger* debugger = static_cast<ThreadDebugger*>(
v8::Local<v8::External>::Cast(info.Data())->Value());
DCHECK(debugger);
v8::Isolate* isolate = info.GetIsolate();
v8::Local<v8::Context> context = isolate->GetCurrentContext();
int group_id = debugger->ContextGroupId(ToExecutionContext(context));
V8EventListenerInfoList listener_info;
// eventListeners call can produce message on ErrorEvent during lazy event
// listener compilation.
if (group_id)
debugger->muteMetrics(group_id);
InspectorDOMDebuggerAgent::EventListenersInfoForTarget(isolate, info[0],
&listener_info);
if (group_id)
debugger->unmuteMetrics(group_id);
v8::Local<v8::Object> result = v8::Object::New(isolate);
AtomicString current_event_type;
v8::Local<v8::Array> listeners;
wtf_size_t output_index = 0;
for (auto& info : listener_info) {
if (current_event_type != info.event_type) {
current_event_type = info.event_type;
listeners = v8::Array::New(isolate);
output_index = 0;
CreateDataProperty(context, result,
V8AtomicString(isolate, current_event_type),
listeners);
}
v8::Local<v8::Object> listener_object = v8::Object::New(isolate);
CreateDataProperty(context, listener_object,
V8AtomicString(isolate, "listener"), info.handler);
CreateDataProperty(context, listener_object,
V8AtomicString(isolate, "useCapture"),
v8::Boolean::New(isolate, info.use_capture));
CreateDataProperty(context, listener_object,
V8AtomicString(isolate, "passive"),
v8::Boolean::New(isolate, info.passive));
CreateDataProperty(context, listener_object,
V8AtomicString(isolate, "once"),
v8::Boolean::New(isolate, info.once));
CreateDataProperty(context, listener_object,
V8AtomicString(isolate, "type"),
V8String(isolate, current_event_type));
CreateDataPropertyInArray(context, listeners, output_index++,
listener_object);
}
info.GetReturnValue().Set(result);
}
void ThreadDebugger::consoleTime(const v8_inspector::StringView& title) {
// TODO(dgozman): we can save on a copy here if trace macro would take a
// pointer with length.
TRACE_EVENT_COPY_ASYNC_BEGIN0("blink.console",
ToCoreString(title).Utf8().data(), this);
}
void ThreadDebugger::consoleTimeEnd(const v8_inspector::StringView& title) {
// TODO(dgozman): we can save on a copy here if trace macro would take a
// pointer with length.
TRACE_EVENT_COPY_ASYNC_END0("blink.console",
ToCoreString(title).Utf8().data(), this);
}
void ThreadDebugger::consoleTimeStamp(const v8_inspector::StringView& title) {
ExecutionContext* ec = CurrentExecutionContext(isolate_);
// TODO(dgozman): we can save on a copy here if TracedValue would take a
// StringView.
TRACE_EVENT_INSTANT1(
"devtools.timeline", "TimeStamp", TRACE_EVENT_SCOPE_THREAD, "data",
inspector_time_stamp_event::Data(ec, ToCoreString(title)));
probe::consoleTimeStamp(ec, ToCoreString(title));
}
void ThreadDebugger::startRepeatingTimer(
double interval,
V8InspectorClient::TimerCallback callback,
void* data) {
timer_data_.push_back(data);
timer_callbacks_.push_back(callback);
std::unique_ptr<TaskRunnerTimer<ThreadDebugger>> timer =
std::make_unique<TaskRunnerTimer<ThreadDebugger>>(
ThreadScheduler::Current()->V8TaskRunner(), this,
&ThreadDebugger::OnTimer);
TaskRunnerTimer<ThreadDebugger>* timer_ptr = timer.get();
timers_.push_back(std::move(timer));
timer_ptr->StartRepeating(TimeDelta::FromSecondsD(interval), FROM_HERE);
}
void ThreadDebugger::cancelTimer(void* data) {
for (wtf_size_t index = 0; index < timer_data_.size(); ++index) {
if (timer_data_[index] == data) {
timers_[index]->Stop();
timer_callbacks_.EraseAt(index);
timers_.EraseAt(index);
timer_data_.EraseAt(index);
return;
}
}
}
void ThreadDebugger::OnTimer(TimerBase* timer) {
for (wtf_size_t index = 0; index < timers_.size(); ++index) {
if (timers_[index].get() == timer) {
timer_callbacks_[index](timer_data_[index]);
return;
}
}
}
} // namespace blink