| // Copyright 2022 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/web_contents_devtools_agent_host.h" |
| |
| #include "base/unguessable_token.h" |
| #include "content/browser/devtools/protocol/target_auto_attacher.h" |
| #include "content/browser/devtools/protocol/target_handler.h" |
| #include "content/browser/devtools/render_frame_devtools_agent_host.h" |
| #include "content/browser/portal/portal.h" |
| #include "content/browser/renderer_host/frame_tree_node.h" |
| #include "content/browser/web_contents/web_contents_impl.h" |
| |
| namespace content { |
| |
| namespace { |
| using WebContentsDevToolsMap = |
| std::map<WebContents*, WebContentsDevToolsAgentHost*>; |
| base::LazyInstance<WebContentsDevToolsMap>::Leaky g_agent_host_instances = |
| LAZY_INSTANCE_INITIALIZER; |
| |
| WebContentsDevToolsAgentHost* FindAgentHost(WebContents* wc) { |
| if (!g_agent_host_instances.IsCreated()) |
| return nullptr; |
| auto it = g_agent_host_instances.Get().find(wc); |
| return it == g_agent_host_instances.Get().end() ? nullptr : it->second; |
| } |
| |
| bool ShouldCreateDevToolsAgentHost(WebContents* wc) { |
| return wc == wc->GetResponsibleWebContents(); |
| } |
| |
| } // namespace |
| |
| // static |
| scoped_refptr<DevToolsAgentHost> DevToolsAgentHost::GetOrCreateForTab( |
| WebContents* wc) { |
| return WebContentsDevToolsAgentHost::GetOrCreateFor(wc); |
| } |
| |
| class WebContentsDevToolsAgentHost::AutoAttacher |
| : public protocol::TargetAutoAttacher { |
| public: |
| explicit AutoAttacher(WebContents* web_contents) |
| : web_contents_(web_contents) {} |
| |
| void PortalActivated(const Portal& portal) { |
| if (web_contents_ == portal.GetPortalHostContents()) |
| web_contents_ = portal.GetPortalContents(); |
| UpdateChildFrameTrees(/* update_target_info= */ true); |
| } |
| |
| void UpdateChildFrameTrees(bool update_target_info) { |
| if (!auto_attach()) |
| return; |
| base::flat_set<scoped_refptr<DevToolsAgentHost>> pages = |
| UpdateAssociatedPages(); |
| if (update_target_info) { |
| for (auto& page : pages) |
| DispatchTargetInfoChanged(page.get()); |
| } |
| } |
| |
| void WillInitiatePrerender(FrameTreeNode* ftn) { |
| if (!auto_attach()) |
| return; |
| auto host = RenderFrameDevToolsAgentHost::GetOrCreateFor(ftn); |
| DispatchAutoAttach(host.get(), wait_for_debugger_on_start()); |
| } |
| |
| private: |
| void UpdateAutoAttach(base::OnceClosure callback) override { |
| UpdateAssociatedPages(); |
| protocol::TargetAutoAttacher::UpdateAutoAttach(std::move(callback)); |
| } |
| |
| base::flat_set<scoped_refptr<DevToolsAgentHost>> UpdateAssociatedPages() { |
| base::flat_set<scoped_refptr<DevToolsAgentHost>> hosts; |
| if (auto_attach()) { |
| auto* rfh = static_cast<RenderFrameHostImpl*>( |
| web_contents_->GetPrimaryMainFrame()); |
| for (auto* portal : rfh->GetPortals()) { |
| WebContentsImpl* wc = portal->GetPortalContents(); |
| // If the portal's WC is attached, we should get it through normal |
| // WC tree traversal. For this loop, we're only interested in the |
| // ones that are orphaned. |
| if (wc->GetOuterWebContents()) |
| break; |
| hosts.insert(RenderFrameDevToolsAgentHost::GetOrCreateFor( |
| wc->GetPrimaryFrameTree().root())); |
| } |
| web_contents_->ForEachRenderFrameHost( |
| [&hosts](RenderFrameHost* rfh) { AddFrame(hosts, rfh); }); |
| } |
| DispatchSetAttachedTargetsOfType(hosts, DevToolsAgentHost::kTypePage); |
| return hosts; |
| } |
| |
| static void AddFrame(base::flat_set<scoped_refptr<DevToolsAgentHost>>& hosts, |
| RenderFrameHost* rfh) { |
| RenderFrameHostImpl* rfhi = static_cast<RenderFrameHostImpl*>(rfh); |
| // We do not expose cached hosts as separate targets for now. |
| if (rfhi->IsInBackForwardCache()) |
| return; |
| FrameTreeNode* ftn = rfhi->frame_tree_node(); |
| // We're interested only in main frames, with the expcetion of fenced frames |
| // that are reported as regular subframes via FrameAutoAttacher. |
| if (!ftn->IsMainFrame()) |
| return; |
| if (ftn->IsFencedFrameRoot()) |
| return; |
| hosts.insert(RenderFrameDevToolsAgentHost::GetOrCreateFor(ftn)); |
| } |
| |
| WebContents* web_contents_; |
| }; |
| |
| // static |
| WebContentsDevToolsAgentHost* WebContentsDevToolsAgentHost::GetFor( |
| WebContents* web_contents) { |
| return FindAgentHost(web_contents->GetResponsibleWebContents()); |
| } |
| |
| // static |
| WebContentsDevToolsAgentHost* WebContentsDevToolsAgentHost::GetOrCreateFor( |
| WebContents* web_contents) { |
| web_contents = web_contents->GetResponsibleWebContents(); |
| if (auto* host = FindAgentHost(web_contents)) |
| return host; |
| return new WebContentsDevToolsAgentHost(web_contents); |
| } |
| |
| // static |
| void WebContentsDevToolsAgentHost::AddAllAgentHosts( |
| DevToolsAgentHost::List* result) { |
| for (WebContentsImpl* wc : WebContentsImpl::GetAllWebContents()) { |
| if (ShouldCreateDevToolsAgentHost(wc)) |
| result->push_back(GetOrCreateFor(wc)); |
| } |
| } |
| |
| WebContentsDevToolsAgentHost::WebContentsDevToolsAgentHost(WebContents* wc) |
| : DevToolsAgentHostImpl(base::UnguessableToken::Create().ToString()), |
| WebContentsObserver(wc), |
| auto_attacher_(std::make_unique<AutoAttacher>(wc)) { |
| DCHECK(web_contents()); |
| bool inserted = |
| g_agent_host_instances.Get().insert(std::make_pair(wc, this)).second; |
| DCHECK(inserted); |
| // Once created, persist till underlying WC is destroyed, so that |
| // the target id is retained. |
| AddRef(); |
| NotifyCreated(); |
| } |
| |
| void WebContentsDevToolsAgentHost::PortalActivated(const Portal& portal) { |
| if (web_contents() == portal.GetPortalHostContents()) { |
| WebContents* old_wc = web_contents(); |
| WebContents* new_wc = portal.GetPortalContents(); |
| // Assure instrumentation calls for the new WC would be routed here. |
| DCHECK(new_wc->GetResponsibleWebContents() == new_wc); |
| DCHECK(g_agent_host_instances.Get()[old_wc] == this); |
| |
| g_agent_host_instances.Get().erase(old_wc); |
| g_agent_host_instances.Get()[new_wc] = this; |
| Observe(portal.GetPortalContents()); |
| } |
| DCHECK(auto_attacher_); |
| auto_attacher_->PortalActivated(portal); |
| } |
| |
| void WebContentsDevToolsAgentHost::WillInitiatePrerender(FrameTreeNode* ftn) { |
| DCHECK(auto_attacher_); |
| auto_attacher_->WillInitiatePrerender(ftn); |
| } |
| |
| void WebContentsDevToolsAgentHost::UpdateChildFrameTrees( |
| bool update_target_info) { |
| DCHECK(auto_attacher_); |
| auto_attacher_->UpdateChildFrameTrees(update_target_info); |
| } |
| |
| void WebContentsDevToolsAgentHost::InspectElement(RenderFrameHost* frame_host, |
| int x, |
| int y) { |
| if (auto host = GetOrCreatePrimaryFrameAgent()) { |
| host->InspectElement(frame_host, x, y); |
| } |
| } |
| |
| WebContentsDevToolsAgentHost::~WebContentsDevToolsAgentHost() { |
| DCHECK(!web_contents()); |
| } |
| |
| void WebContentsDevToolsAgentHost::DisconnectWebContents() { |
| NOTREACHED(); |
| } |
| |
| void WebContentsDevToolsAgentHost::ConnectWebContents( |
| WebContents* web_contents) { |
| NOTREACHED(); |
| } |
| |
| BrowserContext* WebContentsDevToolsAgentHost::GetBrowserContext() { |
| return web_contents()->GetBrowserContext(); |
| } |
| |
| WebContents* WebContentsDevToolsAgentHost::GetWebContents() { |
| return web_contents(); |
| } |
| |
| std::string WebContentsDevToolsAgentHost::GetParentId() { |
| return std::string(); |
| } |
| |
| std::string WebContentsDevToolsAgentHost::GetOpenerId() { |
| if (DevToolsAgentHost* host = GetPrimaryFrameAgent()) |
| return host->GetOpenerId(); |
| return ""; |
| } |
| |
| std::string WebContentsDevToolsAgentHost::GetOpenerFrameId() { |
| if (DevToolsAgentHost* host = GetPrimaryFrameAgent()) |
| return host->GetOpenerFrameId(); |
| return ""; |
| } |
| |
| bool WebContentsDevToolsAgentHost::CanAccessOpener() { |
| if (DevToolsAgentHost* host = GetPrimaryFrameAgent()) |
| return host->CanAccessOpener(); |
| return false; |
| } |
| |
| std::string WebContentsDevToolsAgentHost::GetType() { |
| return kTypeTab; |
| } |
| |
| std::string WebContentsDevToolsAgentHost::GetTitle() { |
| if (DevToolsAgentHost* host = GetPrimaryFrameAgent()) |
| return host->GetTitle(); |
| return ""; |
| } |
| |
| std::string WebContentsDevToolsAgentHost::GetDescription() { |
| if (DevToolsAgentHost* host = GetPrimaryFrameAgent()) |
| return host->GetDescription(); |
| return ""; |
| } |
| |
| GURL WebContentsDevToolsAgentHost::GetURL() { |
| if (DevToolsAgentHost* host = GetPrimaryFrameAgent()) |
| return host->GetURL(); |
| return GURL(); |
| } |
| |
| GURL WebContentsDevToolsAgentHost::GetFaviconURL() { |
| if (DevToolsAgentHost* host = GetPrimaryFrameAgent()) |
| return host->GetFaviconURL(); |
| return GURL(); |
| } |
| |
| bool WebContentsDevToolsAgentHost::Activate() { |
| if (auto host = GetOrCreatePrimaryFrameAgent()) { |
| return host->Activate(); |
| } |
| return false; |
| } |
| |
| void WebContentsDevToolsAgentHost::Reload() { |
| if (auto host = GetOrCreatePrimaryFrameAgent()) { |
| host->Reload(); |
| } |
| } |
| |
| bool WebContentsDevToolsAgentHost::Close() { |
| if (auto host = GetOrCreatePrimaryFrameAgent()) { |
| return host->Close(); |
| } |
| return false; |
| } |
| |
| base::TimeTicks WebContentsDevToolsAgentHost::GetLastActivityTime() { |
| if (DevToolsAgentHost* host = GetPrimaryFrameAgent()) |
| return host->GetLastActivityTime(); |
| return base::TimeTicks(); |
| } |
| |
| absl::optional<network::CrossOriginEmbedderPolicy> |
| WebContentsDevToolsAgentHost::cross_origin_embedder_policy( |
| const std::string& id) { |
| NOTREACHED(); |
| return absl::nullopt; |
| } |
| |
| absl::optional<network::CrossOriginOpenerPolicy> |
| WebContentsDevToolsAgentHost::cross_origin_opener_policy( |
| const std::string& id) { |
| NOTREACHED(); |
| return absl::nullopt; |
| } |
| |
| DevToolsAgentHostImpl* WebContentsDevToolsAgentHost::GetPrimaryFrameAgent() { |
| if (WebContents* wc = web_contents()) { |
| return RenderFrameDevToolsAgentHost::GetFor( |
| static_cast<RenderFrameHostImpl*>(wc->GetPrimaryMainFrame())); |
| } |
| return nullptr; |
| } |
| |
| scoped_refptr<DevToolsAgentHost> |
| WebContentsDevToolsAgentHost::GetOrCreatePrimaryFrameAgent() { |
| if (WebContents* wc = web_contents()) { |
| return RenderFrameDevToolsAgentHost::GetOrCreateFor( |
| static_cast<WebContentsImpl*>(web_contents()) |
| ->GetPrimaryFrameTree() |
| .root()); |
| } |
| return nullptr; |
| } |
| |
| void WebContentsDevToolsAgentHost::WebContentsDestroyed() { |
| DCHECK_EQ(this, FindAgentHost(web_contents())); |
| ForceDetachAllSessions(); |
| auto_attacher_.reset(); |
| g_agent_host_instances.Get().erase(web_contents()); |
| Observe(nullptr); |
| // We may or may not be destruced here, depending on embedders |
| // potentially retaining references. |
| Release(); |
| } |
| |
| // DevToolsAgentHostImpl overrides. |
| DevToolsSession::Mode WebContentsDevToolsAgentHost::GetSessionMode() { |
| return DevToolsSession::Mode::kSupportsTabTarget; |
| } |
| |
| bool WebContentsDevToolsAgentHost::AttachSession(DevToolsSession* session, |
| bool acquire_wake_lock) { |
| // TODO(caseq): figure out if this can be a CHECK(). |
| if (!web_contents()) |
| return false; |
| session->SetBrowserOnly(true); |
| const bool may_attach_to_brower = session->GetClient()->IsTrusted(); |
| session->CreateAndAddHandler<protocol::TargetHandler>( |
| may_attach_to_brower |
| ? protocol::TargetHandler::AccessMode::kRegular |
| : protocol::TargetHandler::AccessMode::kAutoAttachOnly, |
| GetId(), auto_attacher_.get(), session); |
| return true; |
| } |
| |
| protocol::TargetAutoAttacher* WebContentsDevToolsAgentHost::auto_attacher() { |
| DCHECK(!!auto_attacher_ == !!web_contents()); |
| return auto_attacher_.get(); |
| } |
| |
| } // namespace content |