blob: 0608c4defc6d3e313754042e016fd6364446dc00 [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/devtools_session.h"
#include <string>
#include <utility>
#include <vector>
#include "third_party/blink/renderer/bindings/core/v8/script_controller.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/inspector/devtools_agent.h"
#include "third_party/blink/renderer/core/inspector/inspected_frames.h"
#include "third_party/blink/renderer/core/inspector/inspector_base_agent.h"
#include "third_party/blink/renderer/core/inspector/inspector_session_state.h"
#include "third_party/blink/renderer/core/inspector/inspector_task_runner.h"
#include "third_party/blink/renderer/core/inspector/protocol/Protocol.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/heap/persistent.h"
#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
#include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h"
#include "third_party/blink/renderer/platform/web_test_support.h"
#include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
#include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h"
#include "third_party/inspector_protocol/crdtp/cbor.h"
#include "third_party/inspector_protocol/crdtp/dispatch.h"
#include "third_party/inspector_protocol/crdtp/json.h"
namespace blink {
namespace {
const char kV8StateKey[] = "v8";
const char kSessionId[] = "sessionId";
bool ShouldInterruptForMethod(const String& method) {
return method != "Debugger.evaluateOnCallFrame" &&
method != "Runtime.evaluate" && method != "Runtime.callFunctionOn" &&
method != "Runtime.runScript";
}
std::vector<uint8_t> Get8BitStringFrom(v8_inspector::StringBuffer* msg) {
const v8_inspector::StringView& s = msg->string();
DCHECK(s.is8Bit());
return std::vector<uint8_t>(s.characters8(), s.characters8() + s.length());
}
} // namespace
// Created and stored in unique_ptr on UI.
// Binds request, receives messages and destroys on IO.
class DevToolsSession::IOSession : public mojom::blink::DevToolsSession {
public:
IOSession(scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
scoped_refptr<InspectorTaskRunner> inspector_task_runner,
CrossThreadWeakPersistent<::blink::DevToolsSession> session,
mojo::PendingReceiver<mojom::blink::DevToolsSession> receiver)
: io_task_runner_(io_task_runner),
inspector_task_runner_(inspector_task_runner),
session_(std::move(session)) {
PostCrossThreadTask(*io_task_runner, FROM_HERE,
CrossThreadBindOnce(&IOSession::BindInterface,
CrossThreadUnretained(this),
WTF::Passed(std::move(receiver))));
}
~IOSession() override = default;
void BindInterface(
mojo::PendingReceiver<mojom::blink::DevToolsSession> receiver) {
receiver_.Bind(std::move(receiver), io_task_runner_);
}
void DeleteSoon() { io_task_runner_->DeleteSoon(FROM_HERE, this); }
// mojom::blink::DevToolsSession implementation.
void DispatchProtocolCommand(int call_id,
const String& method,
base::span<const uint8_t> message) override {
TRACE_EVENT_WITH_FLOW1("devtools", "IOSession::DispatchProtocolCommand",
call_id,
TRACE_EVENT_FLAG_FLOW_OUT | TRACE_EVENT_FLAG_FLOW_IN,
"call_id", call_id);
// Crash renderer.
if (method == "Page.crash")
CHECK(false);
// Post a task to the worker or main renderer thread that will interrupt V8
// and be run immediately. Only methods that do not run JS code are safe.
Vector<uint8_t> message_copy;
message_copy.Append(message.data(), message.size());
if (ShouldInterruptForMethod(method)) {
inspector_task_runner_->AppendTask(CrossThreadBindOnce(
&::blink::DevToolsSession::DispatchProtocolCommandImpl, session_,
call_id, method, std::move(message_copy)));
} else {
inspector_task_runner_->AppendTaskDontInterrupt(CrossThreadBindOnce(
&::blink::DevToolsSession::DispatchProtocolCommandImpl, session_,
call_id, method, std::move(message_copy)));
}
}
private:
scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
scoped_refptr<InspectorTaskRunner> inspector_task_runner_;
CrossThreadWeakPersistent<::blink::DevToolsSession> session_;
mojo::Receiver<mojom::blink::DevToolsSession> receiver_{this};
DISALLOW_COPY_AND_ASSIGN(IOSession);
};
DevToolsSession::DevToolsSession(
DevToolsAgent* agent,
mojo::PendingAssociatedRemote<mojom::blink::DevToolsSessionHost>
host_remote,
mojo::PendingAssociatedReceiver<mojom::blink::DevToolsSession>
main_receiver,
mojo::PendingReceiver<mojom::blink::DevToolsSession> io_receiver,
mojom::blink::DevToolsSessionStatePtr reattach_session_state,
bool client_expects_binary_responses,
const String& session_id,
scoped_refptr<base::SequencedTaskRunner> mojo_task_runner)
: agent_(agent),
inspector_backend_dispatcher_(new protocol::UberDispatcher(this)),
session_state_(std::move(reattach_session_state)),
client_expects_binary_responses_(client_expects_binary_responses),
v8_session_state_(kV8StateKey),
v8_session_state_cbor_(&v8_session_state_, /*default_value=*/{}),
session_id_(session_id) {
receiver_.Bind(std::move(main_receiver), mojo_task_runner);
io_session_ = new IOSession(
agent_->io_task_runner_, agent_->inspector_task_runner_,
WrapCrossThreadWeakPersistent(this), std::move(io_receiver));
host_remote_.Bind(std::move(host_remote), mojo_task_runner);
host_remote_.set_disconnect_handler(
WTF::Bind(&DevToolsSession::Detach, WrapWeakPersistent(this)));
bool restore = !!session_state_.ReattachState();
v8_session_state_.InitFrom(&session_state_);
agent_->client_->AttachSession(this, restore);
agent_->probe_sink_->AddDevToolsSession(this);
if (restore) {
for (wtf_size_t i = 0; i < agents_.size(); i++)
agents_[i]->Restore();
}
}
DevToolsSession::~DevToolsSession() {
DCHECK(IsDetached());
}
void DevToolsSession::ConnectToV8(v8_inspector::V8Inspector* inspector,
int context_group_id) {
const auto& cbor = v8_session_state_cbor_.Get();
v8_session_ =
inspector->connect(context_group_id, this,
v8_inspector::StringView(cbor.data(), cbor.size()));
}
bool DevToolsSession::IsDetached() {
return !io_session_;
}
void DevToolsSession::Append(InspectorAgent* agent) {
agents_.push_back(agent);
agent->Init(agent_->probe_sink_.Get(), inspector_backend_dispatcher_.get(),
&session_state_);
}
void DevToolsSession::Detach() {
agent_->client_->DebuggerTaskStarted();
agent_->client_->DetachSession(this);
agent_->sessions_.erase(this);
receiver_.reset();
host_remote_.reset();
CHECK(io_session_);
io_session_->DeleteSoon();
io_session_ = nullptr;
agent_->probe_sink_->RemoveDevToolsSession(this);
inspector_backend_dispatcher_.reset();
for (wtf_size_t i = agents_.size(); i > 0; i--)
agents_[i - 1]->Dispose();
agents_.clear();
v8_session_.reset();
agent_->client_->DebuggerTaskFinished();
}
void DevToolsSession::DispatchProtocolCommand(
int call_id,
const String& method,
base::span<const uint8_t> message) {
TRACE_EVENT_WITH_FLOW1(
"devtools", "DevToolsSession::DispatchProtocolCommand", call_id,
TRACE_EVENT_FLAG_FLOW_OUT | TRACE_EVENT_FLAG_FLOW_IN, "call_id", call_id);
return DispatchProtocolCommandImpl(call_id, method, message);
}
void DevToolsSession::DispatchProtocolCommandImpl(
int call_id,
const String& method,
base::span<const uint8_t> data) {
DCHECK(crdtp::cbor::IsCBORMessage(
crdtp::span<uint8_t>(data.data(), data.size())));
TRACE_EVENT_WITH_FLOW1(
"devtools", "DevToolsSession::DispatchProtocolCommandImpl", call_id,
TRACE_EVENT_FLAG_FLOW_OUT | TRACE_EVENT_FLAG_FLOW_IN, "call_id", call_id);
// IOSession does not provide ordering guarantees relative to
// Session, so a command may come to IOSession after Session is detached,
// and get posted to main thread to this method.
//
// At the same time, Session may not be garbage collected yet
// (even though already detached), and CrossThreadWeakPersistent<Session>
// will still be valid.
//
// Both these factors combined may lead to this method being called after
// detach, so we have to check it here.
if (IsDetached())
return;
agent_->client_->DebuggerTaskStarted();
if (v8_inspector::V8InspectorSession::canDispatchMethod(
ToV8InspectorStringView(method))) {
// Binary protocol messages are passed using 8-bit StringView.
v8_session_->dispatchProtocolMessage(
v8_inspector::StringView(data.data(), data.size()));
} else {
crdtp::Dispatchable dispatchable(crdtp::SpanFrom(data));
// This message has already been checked by content::DevToolsSession.
DCHECK(dispatchable.ok());
inspector_backend_dispatcher_->Dispatch(dispatchable).Run();
}
agent_->client_->DebuggerTaskFinished();
}
void DevToolsSession::DidStartProvisionalLoad(LocalFrame* frame) {
if (v8_session_ && agent_->inspected_frames_->Root() == frame) {
v8_session_->setSkipAllPauses(true);
v8_session_->resume();
}
}
void DevToolsSession::DidFailProvisionalLoad(LocalFrame* frame) {
if (v8_session_ && agent_->inspected_frames_->Root() == frame)
v8_session_->setSkipAllPauses(false);
}
void DevToolsSession::DidCommitLoad(LocalFrame* frame, DocumentLoader*) {
for (wtf_size_t i = 0; i < agents_.size(); i++)
agents_[i]->DidCommitLoadForLocalFrame(frame);
if (v8_session_ && agent_->inspected_frames_->Root() == frame)
v8_session_->setSkipAllPauses(false);
}
void DevToolsSession::PaintTiming(Document* document,
const char* name,
double timestamp) {
if (v8_session_ &&
agent_->inspected_frames_->Root()->GetDocument() == document) {
v8_session_->triggerPreciseCoverageDeltaUpdate(
ToV8InspectorStringView(name));
}
}
void DevToolsSession::DomContentLoadedEventFired(LocalFrame* local_frame) {
if (v8_session_ && agent_->inspected_frames_->Root() == local_frame) {
v8_session_->triggerPreciseCoverageDeltaUpdate(
ToV8InspectorStringView("DomContentLoaded"));
}
}
void DevToolsSession::SendProtocolResponse(
int call_id,
std::unique_ptr<protocol::Serializable> message) {
SendProtocolResponse(call_id, message->Serialize());
}
void DevToolsSession::FallThrough(int call_id,
crdtp::span<uint8_t> method,
crdtp::span<uint8_t> message) {
// There's no other layer to handle the command.
NOTREACHED();
}
void DevToolsSession::sendResponse(
int call_id,
std::unique_ptr<v8_inspector::StringBuffer> message) {
SendProtocolResponse(call_id, Get8BitStringFrom(message.get()));
}
void DevToolsSession::SendProtocolResponse(int call_id,
std::vector<uint8_t> message) {
TRACE_EVENT_WITH_FLOW1(
"devtools", "DevToolsSession::SendProtocolResponse", call_id,
TRACE_EVENT_FLAG_FLOW_OUT | TRACE_EVENT_FLAG_FLOW_IN, "call_id", call_id);
if (IsDetached())
return;
flushProtocolNotifications();
if (v8_session_)
v8_session_state_cbor_.Set(v8_session_->state());
// Make tests more predictable by flushing all sessions before sending
// protocol response in any of them.
if (WebTestSupport::IsRunningWebTest())
agent_->FlushProtocolNotifications();
host_remote_->DispatchProtocolResponse(FinalizeMessage(std::move(message)),
call_id, session_state_.TakeUpdates());
}
void DevToolsSession::SendProtocolNotification(
std::unique_ptr<protocol::Serializable> notification) {
if (IsDetached())
return;
notification_queue_.push_back(WTF::Bind(
[](std::unique_ptr<protocol::Serializable> notification) {
return notification->Serialize();
},
std::move(notification)));
}
void DevToolsSession::sendNotification(
std::unique_ptr<v8_inspector::StringBuffer> notification) {
if (IsDetached())
return;
notification_queue_.push_back(WTF::Bind(
[](std::unique_ptr<v8_inspector::StringBuffer> notification) {
return Get8BitStringFrom(notification.get());
},
std::move(notification)));
}
void DevToolsSession::flushProtocolNotifications() {
FlushProtocolNotifications();
}
void DevToolsSession::FlushProtocolNotifications() {
if (IsDetached())
return;
for (wtf_size_t i = 0; i < agents_.size(); i++)
agents_[i]->FlushPendingProtocolNotifications();
if (!notification_queue_.size())
return;
if (v8_session_)
v8_session_state_cbor_.Set(v8_session_->state());
for (wtf_size_t i = 0; i < notification_queue_.size(); ++i) {
host_remote_->DispatchProtocolNotification(
FinalizeMessage(std::move(notification_queue_[i]).Run()),
session_state_.TakeUpdates());
}
notification_queue_.clear();
}
void DevToolsSession::Trace(Visitor* visitor) const {
visitor->Trace(receiver_);
visitor->Trace(host_remote_);
visitor->Trace(agent_);
visitor->Trace(agents_);
}
blink::mojom::blink::DevToolsMessagePtr DevToolsSession::FinalizeMessage(
std::vector<uint8_t> message) const {
std::vector<uint8_t> message_to_send = std::move(message);
if (!session_id_.IsEmpty()) {
crdtp::Status status = crdtp::cbor::AppendString8EntryToCBORMap(
crdtp::SpanFrom(kSessionId), crdtp::SpanFrom(session_id_.Ascii()),
&message_to_send);
CHECK(status.ok()) << status.ToASCIIString();
}
if (!client_expects_binary_responses_) {
std::vector<uint8_t> json;
crdtp::Status status =
crdtp::json::ConvertCBORToJSON(crdtp::SpanFrom(message_to_send), &json);
CHECK(status.ok()) << status.ToASCIIString();
message_to_send = std::move(json);
}
auto mojo_msg = mojom::blink::DevToolsMessage::New();
mojo_msg->data = std::move(message_to_send);
return mojo_msg;
}
} // namespace blink