| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/public/test/test_devtools_protocol_client.h" |
| |
| #include <memory> |
| #include <string_view> |
| |
| #include "base/auto_reset.h" |
| #include "base/json/json_reader.h" |
| #include "base/json/json_writer.h" |
| #include "base/run_loop.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "content/browser/devtools/render_frame_devtools_agent_host.h" |
| #include "content/browser/renderer_host/frame_tree_node.h" |
| #include "content/public/test/test_utils.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "third_party/blink/public/mojom/loader/mixed_content.mojom.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| const char kIdParam[] = "id"; |
| const char kSessionIdParam[] = "sessionId"; |
| const char kMethodParam[] = "method"; |
| const char kParamsParam[] = "params"; |
| |
| } // namespace |
| |
| TestDevToolsProtocolClient::TestDevToolsProtocolClient() = default; |
| TestDevToolsProtocolClient::~TestDevToolsProtocolClient() = default; |
| |
| const base::Value::Dict* TestDevToolsProtocolClient::SendSessionCommand( |
| const std::string method, |
| base::Value::Dict params, |
| const std::string session_id, |
| bool wait) { |
| response_.clear(); |
| base::AutoReset<bool> reset_in_dispatch(&in_dispatch_, true); |
| base::Value::Dict command; |
| command.Set(kIdParam, ++last_sent_id_); |
| command.Set(kMethodParam, std::move(method)); |
| if (params.size()) |
| command.Set(kParamsParam, std::move(params)); |
| if (!session_id.empty()) |
| command.Set(kSessionIdParam, std::move(session_id)); |
| |
| std::string json_command = |
| base::WriteJson(base::Value(std::move(command))).value_or(""); |
| agent_host_->DispatchProtocolMessage(this, base::as_byte_span(json_command)); |
| // Some messages are dispatched synchronously. |
| // Only run loop if we are not finished yet. |
| if (in_dispatch_ && wait) |
| WaitForResponse(); |
| return result(); |
| } |
| |
| void TestDevToolsProtocolClient::WaitForResponse() { |
| waiting_for_command_result_id_ = last_sent_id_; |
| RunLoopUpdatingQuitClosure(); |
| } |
| |
| void TestDevToolsProtocolClient::AttachToFrameTreeHost(RenderFrameHost* frame) { |
| FrameTreeNode* ftn = |
| FrameTreeNode::GloballyFindByID(frame->GetFrameTreeNodeId()); |
| agent_host_ = RenderFrameDevToolsAgentHost::GetOrCreateFor(ftn); |
| agent_host_->AttachClient(this); |
| } |
| |
| void TestDevToolsProtocolClient::AttachToWebContents(WebContents* wc) { |
| agent_host_ = DevToolsAgentHost::GetOrCreateFor(wc); |
| agent_host_->AttachClient(this); |
| } |
| |
| void TestDevToolsProtocolClient::AttachToTabTarget(WebContents* wc) { |
| agent_host_ = DevToolsAgentHost::GetOrCreateForTab(wc); |
| agent_host_->AttachClient(this); |
| } |
| |
| void TestDevToolsProtocolClient::AttachToBrowserTarget() { |
| // Tethering domain is not used in tests. |
| agent_host_ = DevToolsAgentHost::CreateForBrowser( |
| nullptr, DevToolsAgentHost::CreateServerSocketCallback()); |
| agent_host_->AttachClient(this); |
| } |
| |
| bool TestDevToolsProtocolClient::HasExistingNotification( |
| const std::string& search) const { |
| return HasExistingNotificationMatching( |
| [&search](const base::Value::Dict& notification) { |
| return *notification.FindString(kMethodParam) == search; |
| }); |
| } |
| |
| bool TestDevToolsProtocolClient::HasExistingNotificationMatching( |
| base::FunctionRef<bool(const base::Value::Dict&)> pred) const { |
| for (const auto& notification : notifications_) { |
| if (pred(notification)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| base::Value::Dict TestDevToolsProtocolClient::WaitForNotification( |
| const std::string& notification, |
| bool allow_existing) { |
| if (allow_existing) { |
| for (auto it = notifications_.begin(); it != notifications_.end(); ++it) { |
| if (*it->FindString(kMethodParam) != notification) |
| continue; |
| base::Value::Dict result; |
| if (base::Value::Dict* params = it->FindDict(kParamsParam)) |
| result = std::move(*params); |
| notifications_.erase(it); |
| return result; |
| } |
| } |
| |
| waiting_for_notification_ = notification; |
| RunLoopUpdatingQuitClosure(); |
| return std::move(received_notification_params_); |
| } |
| |
| base::Value::Dict TestDevToolsProtocolClient::WaitForMatchingNotification( |
| const std::string& notification, |
| const NotificationMatcher& matcher) { |
| for (auto it = notifications_.begin(); it != notifications_.end(); ++it) { |
| if (*it->FindString(kMethodParam) != notification) |
| continue; |
| base::Value* params = it->Find(kParamsParam); |
| if (!params || !matcher.Run(params->GetDict())) |
| continue; |
| base::Value::Dict result = std::move(*params).TakeDict(); |
| notifications_.erase(it); |
| return result; |
| } |
| |
| waiting_for_notification_ = notification; |
| waiting_for_notification_matcher_ = matcher; |
| RunLoopUpdatingQuitClosure(); |
| return std::move(received_notification_params_); |
| } |
| |
| const base::Value::Dict* TestDevToolsProtocolClient::result() const { |
| return response_.FindDict("result"); |
| } |
| |
| const base::Value::Dict* TestDevToolsProtocolClient::error() const { |
| return response_.FindDict("error"); |
| } |
| |
| void TestDevToolsProtocolClient::RunLoopUpdatingQuitClosure() { |
| base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed); |
| run_loop_quit_closure_ = run_loop.QuitClosure(); |
| run_loop.Run(); |
| } |
| |
| void TestDevToolsProtocolClient::DispatchProtocolMessage( |
| DevToolsAgentHost* agent_host, |
| base::span<const uint8_t> message) { |
| std::string_view message_str(reinterpret_cast<const char*>(message.data()), |
| message.size()); |
| base::Value parsed = *base::JSONReader::Read( |
| message_str, base::JSON_PARSE_CHROMIUM_EXTENSIONS); |
| if (std::optional<int> id = parsed.GetDict().FindInt("id")) { |
| received_responses_count_++; |
| response_ = std::move(parsed).TakeDict(); |
| in_dispatch_ = false; |
| if (*id && *id == waiting_for_command_result_id_) { |
| waiting_for_command_result_id_ = 0; |
| std::move(run_loop_quit_closure_).Run(); |
| } |
| } else { |
| const std::string* notification = parsed.GetDict().FindString("method"); |
| if (waiting_for_notification_ != *notification) { |
| notifications_.push_back(std::move(parsed).TakeDict()); |
| return; |
| } |
| base::Value* params = parsed.GetDict().Find(kParamsParam); |
| if (waiting_for_notification_matcher_.is_null() || |
| waiting_for_notification_matcher_.Run(params->GetDict())) { |
| waiting_for_notification_ = std::string(); |
| waiting_for_notification_matcher_ = NotificationMatcher(); |
| received_notification_params_ = std::move(*params).TakeDict(); |
| std::move(run_loop_quit_closure_).Run(); |
| } |
| } |
| } |
| |
| void TestDevToolsProtocolClient::AgentHostClosed( |
| DevToolsAgentHost* agent_host) { |
| if (!agent_host_can_close_) { |
| NOTREACHED(); |
| } |
| } |
| |
| bool TestDevToolsProtocolClient::AllowUnsafeOperations() { |
| return allow_unsafe_operations_; |
| } |
| |
| bool TestDevToolsProtocolClient::IsTrusted() { |
| return is_trusted_; |
| } |
| |
| bool TestDevToolsProtocolClient::MayReadLocalFiles() { |
| return may_read_local_files_; |
| } |
| |
| bool TestDevToolsProtocolClient::MayWriteLocalFiles() { |
| return may_write_local_files_; |
| } |
| |
| bool TestDevToolsProtocolClient::MayAttachToURL(const GURL& url, |
| bool is_webui) { |
| return not_attachable_hosts_.find(url.GetHost()) == |
| not_attachable_hosts_.end(); |
| } |
| |
| std::optional<url::Origin> |
| TestDevToolsProtocolClient::GetNavigationInitiatorOrigin() { |
| return navigation_initiator_origin_; |
| } |
| |
| } // namespace content |