| // Copyright 2023 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "services/accessibility/features/devtools/os_devtools_session.h" |
| |
| #include <string_view> |
| #include <utility> |
| |
| #include "base/functional/bind.h" |
| #include "base/notreached.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "mojo/public/cpp/bindings/self_owned_receiver.h" |
| #include "services/accessibility/features/devtools/debug_command_queue.h" |
| #include "services/accessibility/features/devtools/os_devtools_agent.h" |
| #include "services/accessibility/features/v8_manager.h" |
| #include "third_party/inspector_protocol/crdtp/cbor.h" |
| #include "third_party/inspector_protocol/crdtp/json.h" |
| #include "v8/include/v8-microtask-queue.h" |
| |
| namespace { |
| std::vector<uint8_t> GetStringBytes(const v8_inspector::StringView& s) { |
| if (s.is8Bit()) { |
| return std::vector<uint8_t>(s.characters8(), s.characters8() + s.length()); |
| } |
| std::string converted = base::UTF16ToUTF8(std::u16string_view( |
| reinterpret_cast<const char16_t*>(s.characters16()), s.length())); |
| const uint8_t* data = reinterpret_cast<const uint8_t*>(converted.data()); |
| return std::vector<uint8_t>(data, data + converted.size()); |
| } |
| } // namespace |
| |
| namespace ax { |
| |
| // OSDevToolsSession::IOSession, which handles the pipe passed to the |
| // `io_session` parameter of DevToolsAgent::AttachDevToolsSession(), runs on a |
| // non-V8 sequence (except creation happens on the V8 thread). It's owned by the |
| // corresponding pipe. |
| // |
| // Its task is to forward messages to the v8 thread via DebugCommandQueue, with |
| // the `v8_thread_dispatch` callback being asked to run there to execute the |
| // command. The callback is responsible for dealing with possibility of the main |
| // session object being deleted. |
| class OSDevToolsSession::IOSession : public blink::mojom::DevToolsSession { |
| public: |
| using DispatchCallback = |
| base::RepeatingCallback<void(int32_t call_id, |
| const std::string& method, |
| std::vector<uint8_t> message)>; |
| |
| ~IOSession() override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(io_session_receiver_sequence_checker_); |
| } |
| |
| static void Create( |
| mojo::PendingReceiver<blink::mojom::DevToolsSession> io_session_receiver, |
| scoped_refptr<base::SequencedTaskRunner> io_session_receiver_sequence, |
| const scoped_refptr<DebugCommandQueue> debug_command_queue, |
| DispatchCallback v8_thread_dispatch) { |
| auto instance = base::WrapUnique( |
| new IOSession(debug_command_queue, std::move(v8_thread_dispatch))); |
| io_session_receiver_sequence->PostTask( |
| FROM_HERE, |
| base::BindOnce(&IOSession::ConnectReceiver, std::move(instance), |
| std::move(io_session_receiver))); |
| } |
| |
| // DevToolsSession implementation. |
| void DispatchProtocolCommand(int32_t call_id, |
| const std::string& method, |
| base::span<const uint8_t> message) override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(io_session_receiver_sequence_checker_); |
| debug_command_queue_->QueueTaskForV8Thread( |
| base::BindOnce(v8_thread_dispatch_, call_id, method, |
| std::vector<uint8_t>(message.begin(), message.end()))); |
| } |
| |
| private: |
| IOSession(const scoped_refptr<DebugCommandQueue> debug_command_queue, |
| DispatchCallback v8_thread_dispatch) |
| : debug_command_queue_(debug_command_queue), |
| v8_thread_dispatch_(std::move(v8_thread_dispatch)) { |
| DETACH_FROM_SEQUENCE(io_session_receiver_sequence_checker_); |
| } |
| |
| static void ConnectReceiver( |
| std::unique_ptr<IOSession> instance, |
| mojo::PendingReceiver<blink::mojom::DevToolsSession> |
| io_session_receiver) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE( |
| instance->io_session_receiver_sequence_checker_); |
| mojo::MakeSelfOwnedReceiver(std::move(instance), |
| std::move(io_session_receiver)); |
| } |
| |
| const scoped_refptr<DebugCommandQueue> debug_command_queue_; |
| DispatchCallback v8_thread_dispatch_; |
| |
| SEQUENCE_CHECKER(io_session_receiver_sequence_checker_); |
| }; |
| |
| OSDevToolsSession::OSDevToolsSession( |
| V8Environment& v8_env, |
| OSDevToolsAgent& agent, |
| const scoped_refptr<DebugCommandQueue> debug_command_queue, |
| const std::string& session_id, |
| bool client_expects_binary_responses, |
| bool session_waits_for_debugger, |
| mojo::PendingAssociatedRemote<blink::mojom::DevToolsSessionHost> host, |
| scoped_refptr<base::SequencedTaskRunner> io_session_receiver_sequence, |
| mojo::PendingReceiver<blink::mojom::DevToolsSession> io_session_receiver, |
| SessionDestroyedCallback on_delete_callback) |
| : v8_env_(v8_env), |
| debug_command_queue_(*debug_command_queue), |
| session_id_(session_id), |
| client_expects_binary_responses_(client_expects_binary_responses), |
| on_delete_callback_(std::move(on_delete_callback)) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_); |
| host_.Bind(std::move(host)); |
| // Connect v8 session. |
| v8_session_ = agent.ConnectSession(this, session_waits_for_debugger); |
| IOSession::Create( |
| std::move(io_session_receiver), io_session_receiver_sequence, |
| debug_command_queue, |
| base::BindRepeating(&OSDevToolsSession::DispatchProtocolCommandFromIO, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| OSDevToolsSession::~OSDevToolsSession() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_); |
| std::move(on_delete_callback_).Run(this); |
| v8_session_->stop(); |
| } |
| |
| base::OnceClosure OSDevToolsSession::MakeAbortPauseCallback() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_); |
| // Note that this can be cancelled by the weak pointer only if the session |
| // got unpaused by other means, since if it's paused it's not returning |
| // control to the event loop, so Mojo won't get a chance to delete `this`. |
| return base::BindOnce(&OSDevToolsSession::AbortDebuggerPause, |
| weak_ptr_factory_.GetWeakPtr()); |
| } |
| |
| void OSDevToolsSession::MaybeTriggerInstrumentationBreakpoint( |
| const std::string& name) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_); |
| // TODO(290815208) |
| } |
| |
| void OSDevToolsSession::DispatchProtocolCommandFromIO( |
| int32_t call_id, |
| const std::string& method, |
| std::vector<uint8_t> message) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_); |
| DispatchProtocolCommand(call_id, method, message); |
| } |
| |
| void OSDevToolsSession::DispatchProtocolCommand( |
| int32_t call_id, |
| const std::string& method, |
| base::span<const uint8_t> message) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_); |
| |
| // Binary is always used to talk to V8 so it also only talks binary back, |
| // making it easier to append session ID. That's also useful for |
| // crdtp::Dispatchable. |
| crdtp::span<uint8_t> message_span(message.data(), message.size()); |
| v8_inspector::StringView cbor_message; |
| std::vector<uint8_t> converted_cbor_out; |
| if (crdtp::cbor::IsCBORMessage(message_span)) { |
| cbor_message = v8_inspector::StringView(message.data(), message.size()); |
| } else { |
| crdtp::Status status = |
| crdtp::json::ConvertJSONToCBOR(message_span, &converted_cbor_out); |
| CHECK(status.ok()) << status.ToASCIIString(); |
| cbor_message = v8_inspector::StringView(converted_cbor_out.data(), |
| converted_cbor_out.size()); |
| } |
| |
| if (v8_inspector::V8InspectorSession::canDispatchMethod( |
| v8_inspector::StringView( |
| reinterpret_cast<const uint8_t*>(method.data()), |
| method.size()))) { |
| // Need v8 isolate access. |
| auto* isolate = v8_env_->GetIsolate(); |
| v8::Isolate::Scope isolate_scope(isolate); |
| v8::HandleScope handle_scope(isolate); |
| v8_session_->dispatchProtocolMessage(cbor_message); |
| // Run microtasks. |
| v8_env_->GetContext()->GetMicrotaskQueue()->PerformCheckpoint(isolate); |
| } else { |
| crdtp::Dispatchable dispatchable(crdtp::span<uint8_t>( |
| cbor_message.characters8(), cbor_message.length())); |
| fallback_dispatcher_.Dispatch(dispatchable).Run(); |
| } |
| } |
| |
| void OSDevToolsSession::sendResponse( |
| int call_id, |
| std::unique_ptr<v8_inspector::StringBuffer> message) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_); |
| SendProtocolResponseImpl(call_id, ::GetStringBytes(message.get()->string())); |
| } |
| |
| void OSDevToolsSession::sendNotification( |
| std::unique_ptr<v8_inspector::StringBuffer> message) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_); |
| SendNotificationImpl(::GetStringBytes(message.get()->string())); |
| } |
| |
| void OSDevToolsSession::flushProtocolNotifications() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_); |
| NOTIMPLEMENTED(); |
| } |
| |
| void OSDevToolsSession::SendProtocolResponse( |
| int call_id, |
| std::unique_ptr<crdtp::Serializable> message) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_); |
| SendProtocolResponseImpl(call_id, message->Serialize()); |
| } |
| |
| void OSDevToolsSession::SendProtocolNotification( |
| std::unique_ptr<crdtp::Serializable> message) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_); |
| SendNotificationImpl(message->Serialize()); |
| } |
| |
| void OSDevToolsSession::FallThrough(int call_id, |
| crdtp::span<uint8_t> method, |
| crdtp::span<uint8_t> message) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_); |
| NOTIMPLEMENTED(); |
| } |
| |
| void OSDevToolsSession::FlushProtocolNotifications() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_); |
| NOTIMPLEMENTED(); |
| } |
| |
| void OSDevToolsSession::AbortDebuggerPause() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_); |
| // Note that if the session got resumed by other means before execution got |
| // here V8 will simply ignore this call. |
| v8_session_->resume(/*setTerminateOnResume=*/true); |
| } |
| |
| void OSDevToolsSession::SendProtocolResponseImpl(int call_id, |
| std::vector<uint8_t> message) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_); |
| crdtp::span<uint8_t> msg_span(message.data(), message.size()); |
| host_->DispatchProtocolResponse(FinalizeMessage(std::move(message)), call_id, |
| nullptr); |
| } |
| |
| void OSDevToolsSession::SendNotificationImpl(std::vector<uint8_t> message) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_); |
| crdtp::span<uint8_t> msg_span(message.data(), message.size()); |
| host_->DispatchProtocolNotification(FinalizeMessage(std::move(message)), |
| nullptr); |
| } |
| |
| // Add session ID and maybe convert the message into JSON format, as |
| // documented as requirements in DevToolsAgent.AttachDevToolsSession mojo |
| // method documentation, and then encapsulate it inside a mojo |
| // DevToolsMessage. |
| // |
| // This is pretty much a copy-paste job from |
| // third_party/blink/renderer/core/inspector/devtools_session.cc. |
| // The primary difference is that namespacing of DevToolsMessage. |
| blink::mojom::DevToolsMessagePtr OSDevToolsSession::FinalizeMessage( |
| std::vector<uint8_t> message) const { |
| std::vector<uint8_t> message_to_send = std::move(message); |
| if (!session_id_.empty()) { |
| crdtp::Status status = crdtp::cbor::AppendString8EntryToCBORMap( |
| crdtp::SpanFrom("sessionId"), crdtp::SpanFrom(session_id_), |
| &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 = blink::mojom::DevToolsMessage::New(); |
| mojo_msg->data = std::move(message_to_send); |
| return mojo_msg; |
| } |
| |
| } // namespace ax |