| // 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/browser/devtools/devtools_renderer_channel.h" | |
| #include "base/functional/bind.h" | |
| #include "base/functional/callback_helpers.h" | |
| #include "base/memory/safety_checks.h" | |
| #include "content/browser/bad_message.h" | |
| #include "content/browser/devtools/dedicated_worker_devtools_agent_host.h" | |
| #include "content/browser/devtools/devtools_agent_host_impl.h" | |
| #include "content/browser/devtools/devtools_manager.h" | |
| #include "content/browser/devtools/devtools_session.h" | |
| #include "content/browser/devtools/protocol/devtools_domain_handler.h" | |
| #include "content/browser/devtools/worker_devtools_manager.h" | |
| #include "content/browser/devtools/worklet_devtools_agent_host.h" | |
| #include "content/common/features.h" | |
| #include "content/public/browser/child_process_host.h" | |
| #include "content/public/browser/render_process_host.h" | |
| #include "content/public/browser/web_contents.h" | |
| #include "content/public/browser/web_contents_delegate.h" | |
| #include "third_party/blink/public/common/features.h" | |
| #include "ui/gfx/geometry/point.h" | |
| namespace content { | |
| DevToolsRendererChannel::DevToolsRendererChannel(DevToolsAgentHostImpl* owner) | |
| : owner_(owner), | |
| process_id_(ChildProcessHost::kInvalidUniqueID) {} | |
| DevToolsRendererChannel::~DevToolsRendererChannel() = default; | |
| void DevToolsRendererChannel::SetRenderer( | |
| mojo::PendingRemote<blink::mojom::DevToolsAgent> agent_remote, | |
| mojo::PendingReceiver<blink::mojom::DevToolsAgentHost> host_receiver, | |
| int process_id, | |
| base::OnceClosure connection_error) { | |
| CleanupConnection(); | |
| blink::mojom::DevToolsAgent* agent = nullptr; | |
| if (agent_remote.is_valid()) { | |
| agent_remote_.Bind(std::move(agent_remote)); | |
| agent = agent_remote_.get(); | |
| } | |
| if (connection_error) | |
| agent_remote_.set_disconnect_handler(std::move(connection_error)); | |
| if (host_receiver) | |
| receiver_.Bind(std::move(host_receiver)); | |
| const bool force_using_io = true; | |
| SetRendererInternal(agent, process_id, nullptr, force_using_io); | |
| } | |
| void DevToolsRendererChannel::SetRendererAssociated( | |
| mojo::PendingAssociatedRemote<blink::mojom::DevToolsAgent> agent_remote, | |
| mojo::PendingAssociatedReceiver<blink::mojom::DevToolsAgentHost> | |
| host_receiver, | |
| int process_id, | |
| RenderFrameHostImpl* frame_host) { | |
| CleanupConnection(); | |
| blink::mojom::DevToolsAgent* agent = nullptr; | |
| if (agent_remote.is_valid()) { | |
| associated_agent_remote_.Bind(std::move(agent_remote)); | |
| agent = associated_agent_remote_.get(); | |
| } | |
| if (host_receiver) | |
| associated_receiver_.Bind(std::move(host_receiver)); | |
| const bool force_using_io = false; | |
| SetRendererInternal(agent, process_id, frame_host, force_using_io); | |
| } | |
| void DevToolsRendererChannel::CleanupConnection() { | |
| receiver_.reset(); | |
| associated_receiver_.reset(); | |
| associated_agent_remote_.reset(); | |
| agent_remote_.reset(); | |
| } | |
| void DevToolsRendererChannel::ForceDetachWorkerSessions() { | |
| for (WorkerOrWorkletDevToolsAgentHost* host : child_targets_) { | |
| host->ForceDetachAllSessions(); | |
| } | |
| } | |
| void DevToolsRendererChannel::SetRendererInternal( | |
| blink::mojom::DevToolsAgent* agent, | |
| int process_id, | |
| RenderFrameHostImpl* frame_host, | |
| bool force_using_io) { | |
| ReportChildTargetsCallback(); | |
| process_id_ = process_id; | |
| frame_host_ = frame_host; | |
| if (agent && child_target_created_callback_) { | |
| agent->ReportChildTargets(true /* report */, wait_for_debugger_, | |
| base::DoNothing()); | |
| } | |
| for (DevToolsSession* session : owner_->sessions()) { | |
| for (auto& pair : session->handlers()) | |
| pair.second->SetRenderer(process_id_, frame_host_); | |
| session->AttachToAgent(agent, force_using_io); | |
| } | |
| } | |
| void DevToolsRendererChannel::AttachSession(DevToolsSession* session) { | |
| if (!agent_remote_ && !associated_agent_remote_) | |
| owner_->UpdateRendererChannel(true /* force */); | |
| for (auto& pair : session->handlers()) | |
| pair.second->SetRenderer(process_id_, frame_host_); | |
| if (agent_remote_) | |
| session->AttachToAgent(agent_remote_.get(), true); | |
| else if (associated_agent_remote_) | |
| session->AttachToAgent(associated_agent_remote_.get(), false); | |
| } | |
| void DevToolsRendererChannel::InspectElement(const gfx::Point& point) { | |
| if (!agent_remote_ && !associated_agent_remote_) | |
| owner_->UpdateRendererChannel(true /* force */); | |
| // Previous call might update |agent_remote_| or |associated_agent_remote_| | |
| // via SetRenderer(), so we should check them again. | |
| if (agent_remote_) | |
| agent_remote_->InspectElement(point); | |
| else if (associated_agent_remote_) | |
| associated_agent_remote_->InspectElement(point); | |
| } | |
| void DevToolsRendererChannel::SetReportChildTargets( | |
| ChildTargetCreatedCallback report_callback, | |
| bool wait_for_debugger, | |
| base::OnceClosure completion_callback) { | |
| CHECK(report_callback || !wait_for_debugger, base::NotFatalUntil::M159); | |
| ReportChildTargetsCallback(); | |
| set_report_completion_callback_ = std::move(completion_callback); | |
| if (child_target_created_callback_ == report_callback && | |
| wait_for_debugger_ == wait_for_debugger) { | |
| ReportChildTargetsCallback(); | |
| return; | |
| } | |
| if (report_callback) { | |
| for (DevToolsAgentHostImpl* host : child_targets_) | |
| report_callback.Run(host, false /* waiting_for_debugger */); | |
| } | |
| child_target_created_callback_ = std::move(report_callback); | |
| wait_for_debugger_ = wait_for_debugger; | |
| if (agent_remote_) { | |
| agent_remote_->ReportChildTargets( | |
| !!child_target_created_callback_, wait_for_debugger_, | |
| base::BindOnce(&DevToolsRendererChannel::ReportChildTargetsCallback, | |
| base::Unretained(this))); | |
| } else if (associated_agent_remote_) { | |
| associated_agent_remote_->ReportChildTargets( | |
| !!child_target_created_callback_, wait_for_debugger_, | |
| base::BindOnce(&DevToolsRendererChannel::ReportChildTargetsCallback, | |
| base::Unretained(this))); | |
| } else { | |
| ReportChildTargetsCallback(); | |
| } | |
| } | |
| void DevToolsRendererChannel::ReportChildTargetsCallback() { | |
| if (set_report_completion_callback_) | |
| std::move(set_report_completion_callback_).Run(); | |
| } | |
| void DevToolsRendererChannel::ChildTargetCreated( | |
| mojo::PendingRemote<blink::mojom::DevToolsAgent> worker_devtools_agent, | |
| mojo::PendingReceiver<blink::mojom::DevToolsAgentHost> host_receiver, | |
| const GURL& url, | |
| const std::string& name, | |
| const base::UnguessableToken& devtools_worker_token, | |
| bool waiting_for_debugger, | |
| blink::mojom::DevToolsExecutionContextType context_type) { | |
| // This function is known to be heap allocation heavy and performance | |
| // critical. Extra memory safety checks can introduce regression | |
| // (https://crbug.com/414710225) and these are disabled here. | |
| base::ScopedSafetyChecksExclusion scoped_unsafe; | |
| RenderProcessHost* process = RenderProcessHost::FromID(process_id_); | |
| if (!process) { | |
| return; | |
| } | |
| GURL filtered_url = url; | |
| process->FilterURL(/*empty_allowed=*/true, &filtered_url); | |
| scoped_refptr<WorkerOrWorkletDevToolsAgentHost> agent_host; | |
| switch (context_type) { | |
| case blink::mojom::DevToolsExecutionContextType::kDedicatedWorker: { | |
| // WorkerDevToolsAgentHost for dedicated workers is already created in the | |
| // browser process. | |
| DedicatedWorkerDevToolsAgentHost* dedicated_worker_agent_host = | |
| WorkerDevToolsManager::GetInstance().GetDevToolsHostFromToken( | |
| devtools_worker_token); | |
| if (!dedicated_worker_agent_host || | |
| dedicated_worker_agent_host->state_terminating()) { | |
| // If `dedicated_worker_agent_host` is nullptr or terminating, we can | |
| // assume that `DedicatedWorkerHost` has been destructed while handling | |
| // `DedicatedWorker::ContinueStart`. We do not need to continue in that | |
| // case. | |
| return; | |
| } | |
| CHECK(content::DevToolsAgentHost::GetForId( | |
| devtools_worker_token.ToString())); | |
| if (base::FeatureList::IsEnabled( | |
| ::features::kWorkerOrWorkletAgentDoubleReleaseFix) && | |
| dedicated_worker_agent_host->child_worker_created()) { | |
| // If `child_worker_created()` is true, the renderer is attempting to | |
| // initialize the same worker again, which is not allowed. | |
| bad_message::ReceivedBadMessage( | |
| process, bad_message::DT_DUPLICATE_CHILD_TARGET_CREATED); | |
| return; | |
| } | |
| dedicated_worker_agent_host->ChildWorkerCreated( | |
| url, name, | |
| base::BindOnce(&DevToolsRendererChannel::ChildTargetDestroyed, | |
| weak_factory_.GetWeakPtr())); | |
| agent_host = dedicated_worker_agent_host; | |
| break; | |
| } | |
| case blink::mojom::DevToolsExecutionContextType::kWorklet: | |
| if (content::DevToolsAgentHost::GetForId( | |
| devtools_worker_token.ToString())) { | |
| mojo::ReportBadMessage("Workers should have unique tokens."); | |
| return; | |
| } | |
| agent_host = base::MakeRefCounted<WorkletDevToolsAgentHost>( | |
| process_id_, filtered_url, std::move(name), devtools_worker_token, | |
| owner_->GetId(), | |
| base::BindOnce(&DevToolsRendererChannel::ChildTargetDestroyed, | |
| weak_factory_.GetWeakPtr())); | |
| break; | |
| } | |
| agent_host->SetRenderer(process_id_, std::move(worker_devtools_agent), | |
| std::move(host_receiver)); | |
| child_targets_.insert(agent_host.get()); | |
| if (child_target_created_callback_) { | |
| child_target_created_callback_.Run(agent_host.get(), waiting_for_debugger); | |
| } | |
| } | |
| void DevToolsRendererChannel::ChildTargetDestroyed( | |
| DevToolsAgentHostImpl* host) { | |
| child_targets_.erase(host); | |
| } | |
| void DevToolsRendererChannel::MainThreadDebuggerPaused() { | |
| owner_->MainThreadDebuggerPaused(); | |
| } | |
| void DevToolsRendererChannel::MainThreadDebuggerResumed() { | |
| owner_->MainThreadDebuggerResumed(); | |
| } | |
| void DevToolsRendererChannel::BringToForeground() { | |
| DevToolsManager* manager = DevToolsManager::GetInstance(); | |
| if (manager->delegate()) { | |
| manager->delegate()->Activate(owner_); | |
| } | |
| } | |
| } // namespace content |