| // Copyright 2012 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/render_frame_devtools_agent_host.h" |
| |
| #include <set> |
| #include <string> |
| #include <tuple> |
| #include <utility> |
| |
| #include "base/functional/bind.h" |
| #include "base/json/json_reader.h" |
| #include "base/lazy_instance.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "build/build_config.h" |
| #include "components/viz/common/buildflags.h" |
| #include "content/browser/bad_message.h" |
| #include "content/browser/child_process_security_policy_impl.h" |
| #include "content/browser/devtools/devtools_manager.h" |
| #include "content/browser/devtools/devtools_renderer_channel.h" |
| #include "content/browser/devtools/devtools_session.h" |
| #include "content/browser/devtools/frame_auto_attacher.h" |
| #include "content/browser/devtools/protocol/audits_handler.h" |
| #include "content/browser/devtools/protocol/background_service_handler.h" |
| #include "content/browser/devtools/protocol/browser_handler.h" |
| #include "content/browser/devtools/protocol/device_access_handler.h" |
| #include "content/browser/devtools/protocol/dom_handler.h" |
| #include "content/browser/devtools/protocol/emulation_handler.h" |
| #include "content/browser/devtools/protocol/fedcm_handler.h" |
| #include "content/browser/devtools/protocol/fetch_handler.h" |
| #include "content/browser/devtools/protocol/handler_helpers.h" |
| #include "content/browser/devtools/protocol/input_handler.h" |
| #include "content/browser/devtools/protocol/inspector_handler.h" |
| #include "content/browser/devtools/protocol/io_handler.h" |
| #include "content/browser/devtools/protocol/log_handler.h" |
| #include "content/browser/devtools/protocol/memory_handler.h" |
| #include "content/browser/devtools/protocol/network_handler.h" |
| #include "content/browser/devtools/protocol/overlay_handler.h" |
| #include "content/browser/devtools/protocol/page_handler.h" |
| #include "content/browser/devtools/protocol/preload_handler.h" |
| #include "content/browser/devtools/protocol/protocol.h" |
| #include "content/browser/devtools/protocol/schema_handler.h" |
| #include "content/browser/devtools/protocol/security_handler.h" |
| #include "content/browser/devtools/protocol/service_worker_handler.h" |
| #include "content/browser/devtools/protocol/storage_handler.h" |
| #include "content/browser/devtools/protocol/system_info_handler.h" |
| #include "content/browser/devtools/protocol/target_handler.h" |
| #include "content/browser/devtools/protocol/tracing_handler.h" |
| #include "content/browser/fenced_frame/fenced_frame.h" |
| #include "content/browser/renderer_host/navigation_request.h" |
| #include "content/browser/renderer_host/render_frame_host_impl.h" |
| #include "content/browser/renderer_host/render_process_host_impl.h" |
| #include "content/browser/renderer_host/render_view_host_impl.h" |
| #include "content/browser/site_instance_impl.h" |
| #include "content/browser/web_contents/web_contents_impl.h" |
| #include "content/browser/web_package/signed_exchange_envelope.h" |
| #include "content/public/browser/back_forward_cache.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/content_browser_client.h" |
| #include "content/public/browser/navigation_handle.h" |
| #include "content/public/browser/network_service_instance.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_widget_host_iterator.h" |
| #include "content/public/browser/web_contents_delegate.h" |
| #include "services/network/public/mojom/network_service.mojom.h" |
| #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h" |
| #include "third_party/blink/public/mojom/devtools/devtools_agent.mojom.h" |
| |
| #if BUILDFLAG(IS_ANDROID) |
| #include "content/browser/renderer_host/compositor_impl_android.h" |
| #include "content/public/browser/render_widget_host_view.h" |
| #include "mojo/public/cpp/bindings/pending_receiver.h" |
| #include "services/device/public/mojom/wake_lock_context.mojom.h" |
| #else |
| #include "content/browser/devtools/protocol/webauthn_handler.h" |
| #endif |
| |
| #if BUILDFLAG(USE_VIZ_DEBUGGER) |
| #include "content/browser/devtools/protocol/visual_debugger_handler.h" |
| #endif |
| |
| namespace content { |
| |
| namespace { |
| using RenderFrameDevToolsMap = |
| std::map<FrameTreeNode*, RenderFrameDevToolsAgentHost*>; |
| base::LazyInstance<RenderFrameDevToolsMap>::Leaky g_agent_host_instances = |
| LAZY_INSTANCE_INITIALIZER; |
| |
| RenderFrameDevToolsAgentHost* FindAgentHost(FrameTreeNode* frame_tree_node) { |
| if (!g_agent_host_instances.IsCreated()) |
| return nullptr; |
| auto it = g_agent_host_instances.Get().find(frame_tree_node); |
| return it == g_agent_host_instances.Get().end() ? nullptr : it->second; |
| } |
| |
| bool ShouldCreateDevToolsForNode(FrameTreeNode* ftn) { |
| return !ftn->parent() || |
| (ftn->current_frame_host() && |
| RenderFrameDevToolsAgentHost::ShouldCreateDevToolsForHost( |
| ftn->current_frame_host())); |
| } |
| |
| } // namespace |
| |
| FrameTreeNode* GetFrameTreeNodeAncestor(FrameTreeNode* frame_tree_node) { |
| while (frame_tree_node && !ShouldCreateDevToolsForNode(frame_tree_node)) |
| frame_tree_node = FrameTreeNode::From(frame_tree_node->parent()); |
| DCHECK(frame_tree_node); |
| return frame_tree_node; |
| } |
| |
| // static |
| scoped_refptr<DevToolsAgentHost> DevToolsAgentHost::GetOrCreateFor( |
| WebContents* web_contents) { |
| FrameTreeNode* node = |
| static_cast<WebContentsImpl*>(web_contents)->GetPrimaryFrameTree().root(); |
| // TODO(dgozman): this check should not be necessary. See |
| // http://crbug.com/489664. |
| if (!node) |
| return nullptr; |
| return RenderFrameDevToolsAgentHost::GetOrCreateFor(node); |
| } |
| |
| // static |
| DevToolsAgentHostImpl* RenderFrameDevToolsAgentHost::GetFor( |
| FrameTreeNode* frame_tree_node) { |
| frame_tree_node = GetFrameTreeNodeAncestor(frame_tree_node); |
| return FindAgentHost(frame_tree_node); |
| } |
| |
| // static |
| DevToolsAgentHostImpl* RenderFrameDevToolsAgentHost::GetFor( |
| RenderFrameHostImpl* rfh) { |
| return ShouldCreateDevToolsForHost(rfh) |
| ? FindAgentHost(rfh->frame_tree_node()) |
| : GetFor(rfh->frame_tree_node()); |
| } |
| |
| scoped_refptr<DevToolsAgentHost> RenderFrameDevToolsAgentHost::GetOrCreateFor( |
| FrameTreeNode* frame_tree_node) { |
| frame_tree_node = GetFrameTreeNodeAncestor(frame_tree_node); |
| RenderFrameDevToolsAgentHost* result = FindAgentHost(frame_tree_node); |
| if (!result) { |
| result = new RenderFrameDevToolsAgentHost( |
| frame_tree_node, frame_tree_node->current_frame_host()); |
| } |
| return result; |
| } |
| |
| // static |
| bool RenderFrameDevToolsAgentHost::ShouldCreateDevToolsForHost( |
| RenderFrameHostImpl* rfh) { |
| DCHECK(rfh); |
| return rfh->is_local_root(); |
| } |
| |
| // static |
| scoped_refptr<RenderFrameDevToolsAgentHost> |
| RenderFrameDevToolsAgentHost::CreateForLocalRootOrEmbeddedPageNavigation( |
| NavigationRequest* request) { |
| // Note that this method does not use FrameTreeNode::current_frame_host(), |
| // since it is used while the frame host may not be set as current yet, |
| // for example right before commit time. Instead target frame from the |
| // navigation handle is used. When this method is invoked it's already known |
| // that the navigation will commit to the new frame host. |
| FrameTreeNode* frame_tree_node = request->frame_tree_node(); |
| DCHECK(!FindAgentHost(frame_tree_node)); |
| return new RenderFrameDevToolsAgentHost(frame_tree_node, |
| request->GetRenderFrameHost()); |
| } |
| |
| // static |
| scoped_refptr<RenderFrameDevToolsAgentHost> |
| RenderFrameDevToolsAgentHost::FindForDangling(FrameTreeNode* frame_tree_node) { |
| return FindAgentHost(frame_tree_node); |
| } |
| |
| // static |
| bool DevToolsAgentHost::HasFor(WebContents* web_contents) { |
| FrameTreeNode* node = |
| static_cast<WebContentsImpl*>(web_contents)->GetPrimaryFrameTree().root(); |
| return node ? FindAgentHost(node) != nullptr : false; |
| } |
| |
| // static |
| bool DevToolsAgentHost::IsDebuggerAttached(WebContents* web_contents) { |
| FrameTreeNode* node = |
| static_cast<WebContentsImpl*>(web_contents)->GetPrimaryFrameTree().root(); |
| RenderFrameDevToolsAgentHost* host = node ? FindAgentHost(node) : nullptr; |
| return host && host->IsAttached(); |
| } |
| |
| // static |
| void RenderFrameDevToolsAgentHost::AddAllAgentHosts( |
| DevToolsAgentHost::List* result) { |
| for (WebContentsImpl* wc : WebContentsImpl::GetAllWebContents()) { |
| // Inner web contents such as portals or guestviews are already handled by |
| // ForEachRenderFrameHost. |
| if (wc->GetOutermostWebContents() != wc) |
| continue; |
| wc->GetPrimaryMainFrame()->ForEachRenderFrameHost( |
| [result](RenderFrameHostImpl* render_frame_host) { |
| FrameTreeNode* node = FrameTreeNode::From(render_frame_host); |
| if (!ShouldCreateDevToolsForNode(node)) |
| return; |
| result->push_back(RenderFrameDevToolsAgentHost::GetOrCreateFor(node)); |
| }); |
| } |
| } |
| |
| // static |
| void RenderFrameDevToolsAgentHost::AttachToWebContents( |
| WebContents* web_contents) { |
| if (ShouldForceCreation()) { |
| // Force agent hosts. |
| DevToolsAgentHost::GetOrCreateForTab(web_contents); |
| DevToolsAgentHost::GetOrCreateFor(web_contents); |
| } |
| } |
| |
| // static |
| void RenderFrameDevToolsAgentHost::UpdateRawHeadersAccess( |
| RenderFrameHostImpl* rfh) { |
| if (!rfh) { |
| return; |
| } |
| RenderProcessHost* rph = rfh->GetProcess(); |
| std::set<url::Origin> process_origins; |
| for (const auto& entry : g_agent_host_instances.Get()) { |
| RenderFrameHostImpl* frame_host = entry.second->frame_host_; |
| if (!frame_host) |
| continue; |
| // Do not skip the nodes if they're about to get attached. |
| if (!entry.second->IsAttached() && entry.first != rfh->frame_tree_node()) { |
| continue; |
| } |
| RenderProcessHost* process_host = frame_host->GetProcess(); |
| if (process_host == rph) |
| process_origins.insert(frame_host->GetLastCommittedOrigin()); |
| } |
| GetNetworkService()->SetRawHeadersAccess( |
| rph->GetID(), |
| std::vector<url::Origin>(process_origins.begin(), process_origins.end())); |
| } |
| |
| RenderFrameDevToolsAgentHost::RenderFrameDevToolsAgentHost( |
| FrameTreeNode* frame_tree_node, |
| RenderFrameHostImpl* frame_host) |
| : DevToolsAgentHostImpl(frame_host->devtools_frame_token().ToString()), |
| auto_attacher_(std::make_unique<FrameAutoAttacher>(GetRendererChannel())), |
| frame_tree_node_(nullptr) { |
| auto* wc = WebContentsImpl::FromRenderFrameHostImpl(frame_host); |
| WebContentsObserver::Observe(wc); |
| SetFrameTreeNode(frame_tree_node); |
| ChangeFrameHostAndObservedProcess(frame_host); |
| render_frame_alive_ = frame_host_ && frame_host_->IsRenderFrameLive(); |
| if (frame_tree_node->GetFrameType() != FrameType::kPrimaryMainFrame && |
| frame_tree_node->GetFrameType() != FrameType::kPrerenderMainFrame) { |
| render_frame_crashed_ = !render_frame_alive_; |
| } else { |
| WebContents* web_contents = WebContents::FromRenderFrameHost(frame_host); |
| render_frame_crashed_ = web_contents && web_contents->IsCrashed(); |
| } |
| AddRef(); // Balanced in DestroyOnRenderFrameGone. |
| NotifyCreated(); |
| } |
| |
| void RenderFrameDevToolsAgentHost::SetFrameTreeNode( |
| FrameTreeNode* frame_tree_node) { |
| if (frame_tree_node_) |
| g_agent_host_instances.Get().erase(frame_tree_node_); |
| frame_tree_node_ = frame_tree_node; |
| if (frame_tree_node_) { |
| DCHECK(web_contents() == |
| WebContentsImpl::FromFrameTreeNode(frame_tree_node)); |
| // TODO(dgozman): with ConnectWebContents/DisconnectWebContents, |
| // we may get two agent hosts for the same FrameTreeNode. |
| // That is definitely a bug, and we should fix that, and DCHECK |
| // here that there is no other agent host. |
| g_agent_host_instances.Get()[frame_tree_node] = this; |
| } |
| } |
| |
| BrowserContext* RenderFrameDevToolsAgentHost::GetBrowserContext() { |
| WebContents* contents = web_contents(); |
| return contents ? contents->GetBrowserContext() : nullptr; |
| } |
| |
| WebContents* RenderFrameDevToolsAgentHost::GetWebContents() { |
| return web_contents(); |
| } |
| |
| bool RenderFrameDevToolsAgentHost::AttachSession(DevToolsSession* session, |
| bool acquire_wake_lock) { |
| if (!ShouldAllowSession(frame_host_, session)) { |
| return false; |
| } |
| |
| if (frame_tree_node_ && !frame_tree_node_->parent() && |
| frame_tree_node_->is_on_initial_empty_document()) { |
| // Since the DevTools protocol can be used to modify the initial empty |
| // document of a tab, notify the browser that the pending URL shouldn't be |
| // displayed anymore to eliminate a URL spoof risk. |
| frame_host_->DidAccessInitialMainDocument(); |
| } |
| |
| session->CreateAndAddHandler<protocol::AuditsHandler>(); |
| session->CreateAndAddHandler<protocol::BackgroundServiceHandler>(); |
| auto* browser_handler = |
| session->CreateAndAddHandler<protocol::BrowserHandler>( |
| session->GetClient()->MayWriteLocalFiles()); |
| session->CreateAndAddHandler<protocol::DeviceAccessHandler>(); |
| session->CreateAndAddHandler<protocol::DOMHandler>( |
| session->GetClient()->MayReadLocalFiles()); |
| auto* emulation_handler = |
| session->CreateAndAddHandler<protocol::EmulationHandler>(); |
| session->CreateAndAddHandler<protocol::InputHandler>( |
| session->GetClient()->MayReadLocalFiles(), |
| session->GetClient()->IsTrusted()); |
| session->CreateAndAddHandler<protocol::InspectorHandler>(); |
| session->CreateAndAddHandler<protocol::IOHandler>(GetIOContext()); |
| session->CreateAndAddHandler<protocol::MemoryHandler>(); |
| #if BUILDFLAG(USE_VIZ_DEBUGGER) |
| session->CreateAndAddHandler<protocol::VisualDebuggerHandler>(); |
| #endif |
| if (!frame_tree_node_ || !frame_tree_node_->parent()) |
| session->CreateAndAddHandler<protocol::OverlayHandler>(); |
| session->CreateAndAddHandler<protocol::NetworkHandler>( |
| GetId(), |
| frame_host_ ? frame_host_->devtools_frame_token() |
| : base::UnguessableToken(), |
| GetIOContext(), |
| base::BindRepeating( |
| &RenderFrameDevToolsAgentHost::UpdateResourceLoaderFactories, |
| base::Unretained(this)), |
| session->GetClient()->MayReadLocalFiles()); |
| session->CreateAndAddHandler<protocol::FetchHandler>( |
| GetIOContext(), base::BindRepeating( |
| [](RenderFrameDevToolsAgentHost* self, |
| base::OnceClosure done_callback) { |
| self->UpdateResourceLoaderFactories(); |
| std::move(done_callback).Run(); |
| }, |
| base::Unretained(this))); |
| session->CreateAndAddHandler<protocol::SchemaHandler>(); |
| const bool may_attach_to_brower = session->GetClient()->IsTrusted(); |
| session->CreateAndAddHandler<protocol::ServiceWorkerHandler>( |
| /* allow_inspect_worker= */ may_attach_to_brower); |
| session->CreateAndAddHandler<protocol::StorageHandler>( |
| session->GetClient()->IsTrusted()); |
| session->CreateAndAddHandler<protocol::SystemInfoHandler>( |
| /* is_browser_session= */ false); |
| auto* target_handler = session->CreateAndAddHandler<protocol::TargetHandler>( |
| may_attach_to_brower |
| ? protocol::TargetHandler::AccessMode::kRegular |
| : protocol::TargetHandler::AccessMode::kAutoAttachOnly, |
| GetId(), auto_attacher_.get(), session); |
| if (session->session_mode() != |
| DevToolsSession::Mode::kDoesNotSupportTabTarget) { |
| target_handler->DisableAutoAttachOfPortals(); |
| } |
| session->CreateAndAddHandler<protocol::PreloadHandler>(); |
| session->CreateAndAddHandler<protocol::PageHandler>( |
| emulation_handler, browser_handler, |
| session->GetClient()->AllowUnsafeOperations(), |
| session->GetClient()->IsTrusted(), |
| session->GetClient()->GetNavigationInitiatorOrigin(), |
| session->GetClient()->MayReadLocalFiles()); |
| session->CreateAndAddHandler<protocol::SecurityHandler>(); |
| if (!frame_tree_node_ || !frame_tree_node_->parent()) { |
| auto* tracing_handler = |
| session->CreateAndAddHandler<protocol::TracingHandler>( |
| protocol::TracingHandler::kFrame, GetIOContext()); |
| tracing_handler->ConnectWebContents(web_contents()); |
| } |
| session->CreateAndAddHandler<protocol::LogHandler>(); |
| session->CreateAndAddHandler<protocol::FedCmHandler>(); |
| #if !BUILDFLAG(IS_ANDROID) |
| session->CreateAndAddHandler<protocol::WebAuthnHandler>(); |
| #endif // !BUILDFLAG(IS_ANDROID) |
| |
| if (sessions().empty()) { |
| UpdateRawHeadersAccess(frame_host_); |
| #if BUILDFLAG(IS_ANDROID) |
| if (acquire_wake_lock) |
| GetWakeLock()->RequestWakeLock(); |
| #endif |
| } |
| return true; |
| } |
| |
| void RenderFrameDevToolsAgentHost::DetachSession(DevToolsSession* session) { |
| // Destroying session automatically detaches in renderer. |
| if (sessions().empty()) { |
| UpdateRawHeadersAccess(frame_host_); |
| #if BUILDFLAG(IS_ANDROID) |
| GetWakeLock()->CancelWakeLock(); |
| #endif |
| } |
| } |
| |
| void RenderFrameDevToolsAgentHost::InspectElement(RenderFrameHost* frame_host, |
| int x, |
| int y) { |
| FrameTreeNode* ftn = |
| static_cast<RenderFrameHostImpl*>(frame_host)->frame_tree_node(); |
| RenderFrameDevToolsAgentHost* host = |
| static_cast<RenderFrameDevToolsAgentHost*>(GetOrCreateFor(ftn).get()); |
| |
| gfx::Point point(x, y); |
| // The renderer expects coordinates relative to the local frame root, |
| // so we need to transform the coordinates from the root space |
| // to the local frame root widget's space. |
| if (host->frame_host_) { |
| if (RenderWidgetHostView* view = host->frame_host_->GetView()) { |
| point = gfx::ToRoundedPoint( |
| view->TransformRootPointToViewCoordSpace(gfx::PointF(point))); |
| } |
| } |
| host->GetRendererChannel()->InspectElement(point); |
| } |
| |
| void RenderFrameDevToolsAgentHost::GetUniqueFormControlId( |
| int node_id, |
| GetUniqueFormControlIdCallback callback) { |
| GetRendererChannel()->GetUniqueFormControlId(node_id, std::move(callback)); |
| } |
| |
| RenderFrameDevToolsAgentHost::~RenderFrameDevToolsAgentHost() { |
| SetFrameTreeNode(nullptr); |
| ChangeFrameHostAndObservedProcess(nullptr); |
| } |
| |
| void RenderFrameDevToolsAgentHost::ReadyToCommitNavigation( |
| NavigationHandle* navigation_handle) { |
| if (!frame_tree_node_) { |
| return; |
| } |
| NavigationRequest* request = NavigationRequest::From(navigation_handle); |
| for (auto* tracing : protocol::TracingHandler::ForAgentHost(this)) |
| tracing->ReadyToCommitNavigation(request); |
| |
| if (request->frame_tree_node() != frame_tree_node_) { |
| if (ShouldForceCreation() && request->GetRenderFrameHost() && |
| request->GetRenderFrameHost()->is_local_root_subframe()) { |
| // An agent may have been created earlier if auto attach is on. |
| if (!FindAgentHost(request->frame_tree_node())) |
| CreateForLocalRootOrEmbeddedPageNavigation(request); |
| } |
| return; |
| } |
| |
| // Child workers will eventually disconnect, but timing depends on the |
| // renderer process. To ensure consistent view over protocol, disconnect them |
| // right now. |
| GetRendererChannel()->ForceDetachWorkerSessions(); |
| UpdateFrameHost(request->GetRenderFrameHost()); |
| // UpdateFrameHost may destruct |this|. |
| } |
| |
| void RenderFrameDevToolsAgentHost::DidFinishNavigation( |
| NavigationHandle* navigation_handle) { |
| NavigationRequest* request = NavigationRequest::From(navigation_handle); |
| // If we opt for retaning self within the conditional block below, do so |
| // till the end of the function, as we require |this| after the conditional. |
| scoped_refptr<RenderFrameDevToolsAgentHost> protect; |
| if (request->frame_tree_node() == frame_tree_node_) { |
| navigation_requests_.erase(request); |
| if (request->HasCommitted()) |
| NotifyNavigated(); |
| |
| if (IsAttached()) { |
| UpdateRawHeadersAccess(frame_tree_node_->current_frame_host()); |
| } |
| // UpdateFrameHost may destruct |this|. |
| protect = this; |
| UpdateFrameHost(frame_tree_node_->current_frame_host()); |
| |
| if (navigation_requests_.empty()) { |
| for (DevToolsSession* session : sessions()) |
| session->ResumeSendingMessagesToAgent(); |
| } |
| } |
| auto_attacher_->DidFinishNavigation( |
| NavigationRequest::From(navigation_handle)); |
| } |
| |
| void RenderFrameDevToolsAgentHost::UpdateFrameHost( |
| RenderFrameHostImpl* frame_host) { |
| if (frame_host == frame_host_) { |
| if (frame_host && !render_frame_alive_) |
| UpdateFrameAlive(); |
| return; |
| } |
| |
| if (frame_host && !ShouldCreateDevToolsForHost(frame_host)) { |
| DestroyOnRenderFrameGone(); |
| // |this| may be deleted at this point. |
| return; |
| } |
| |
| RenderFrameHostImpl* old_host = frame_host_; |
| ChangeFrameHostAndObservedProcess(frame_host); |
| if (IsAttached()) |
| UpdateRawHeadersAccess(old_host); |
| |
| std::vector<DevToolsSession*> restricted_sessions; |
| for (DevToolsSession* session : sessions()) { |
| if (!ShouldAllowSession(frame_host_, session)) { |
| restricted_sessions.push_back(session); |
| } |
| } |
| scoped_refptr<RenderFrameDevToolsAgentHost> protect; |
| if (!restricted_sessions.empty()) { |
| protect = this; |
| ForceDetachRestrictedSessions(restricted_sessions); |
| } |
| |
| UpdateFrameAlive(); |
| } |
| |
| void RenderFrameDevToolsAgentHost::DidStartNavigation( |
| NavigationHandle* navigation_handle) { |
| NavigationRequest* request = NavigationRequest::From(navigation_handle); |
| if (request->frame_tree_node() != frame_tree_node_) |
| return; |
| if (navigation_requests_.empty()) { |
| for (DevToolsSession* session : sessions()) |
| session->SuspendSendingMessagesToAgent(); |
| } |
| navigation_requests_.insert(request); |
| } |
| |
| void RenderFrameDevToolsAgentHost::RenderFrameHostChanged( |
| RenderFrameHost* old_host, |
| RenderFrameHost* new_host) { |
| auto* new_host_impl = static_cast<RenderFrameHostImpl*>(new_host); |
| FrameTreeNode* frame_tree_node = new_host_impl->frame_tree_node(); |
| if (frame_tree_node != frame_tree_node_) |
| return; |
| UpdateFrameHost(new_host_impl); |
| // UpdateFrameHost may destruct |this|. |
| } |
| |
| void RenderFrameDevToolsAgentHost::FrameDeleted(int frame_tree_node_id) { |
| for (auto* tracing : protocol::TracingHandler::ForAgentHost(this)) |
| tracing->FrameDeleted(frame_tree_node_id); |
| if (frame_tree_node_ && |
| frame_tree_node_id == frame_tree_node_->frame_tree_node_id()) { |
| DestroyOnRenderFrameGone(); |
| // |this| may be deleted at this point. |
| } |
| } |
| |
| void RenderFrameDevToolsAgentHost::RenderFrameDeleted(RenderFrameHost* rfh) { |
| if (rfh == frame_host_) { |
| if (frame_tree_node_) { |
| render_frame_alive_ = false; |
| UpdateRendererChannel(IsAttached()); |
| } else { |
| // We're already detached from FTN, so this must be a cached |
| // instance going away. |
| DestroyOnRenderFrameGone(); |
| } |
| } |
| } |
| |
| void RenderFrameDevToolsAgentHost::DestroyOnRenderFrameGone() { |
| scoped_refptr<DevToolsAgentHost> retain_this; |
| if (IsAttached()) { |
| retain_this = ForceDetachAllSessionsImpl(); |
| UpdateRawHeadersAccess(frame_host_); |
| } |
| WebContentsObserver::Observe(nullptr); |
| ChangeFrameHostAndObservedProcess(nullptr); |
| UpdateRendererChannel(IsAttached()); |
| SetFrameTreeNode(nullptr); |
| Release(); |
| } |
| |
| #if BUILDFLAG(IS_ANDROID) |
| device::mojom::WakeLock* RenderFrameDevToolsAgentHost::GetWakeLock() { |
| // Here is a lazy binding, and will not reconnect after connection error. |
| if (!wake_lock_) { |
| mojo::PendingReceiver<device::mojom::WakeLock> receiver = |
| wake_lock_.BindNewPipeAndPassReceiver(); |
| device::mojom::WakeLockContext* wake_lock_context = |
| web_contents()->GetWakeLockContext(); |
| if (wake_lock_context) { |
| wake_lock_context->GetWakeLock( |
| device::mojom::WakeLockType::kPreventDisplaySleep, |
| device::mojom::WakeLockReason::kOther, "DevTools", |
| std::move(receiver)); |
| } |
| } |
| return wake_lock_.get(); |
| } |
| #endif |
| |
| void RenderFrameDevToolsAgentHost::ChangeFrameHostAndObservedProcess( |
| RenderFrameHostImpl* frame_host) { |
| if (frame_host_) |
| frame_host_->GetProcess()->RemoveObserver(this); |
| frame_host_ = frame_host; |
| if (frame_host_) { |
| DCHECK(WebContentsImpl::FromRenderFrameHostImpl(frame_host_) == |
| web_contents()); |
| frame_host_->GetProcess()->AddObserver(this); |
| } |
| } |
| |
| void RenderFrameDevToolsAgentHost::UpdateFrameAlive() { |
| render_frame_alive_ = frame_host_ && frame_host_->IsRenderFrameLive(); |
| if (render_frame_alive_ && render_frame_crashed_) { |
| render_frame_crashed_ = false; |
| for (DevToolsSession* session : sessions()) |
| session->ClearPendingMessages(/*did_crash=*/true); |
| for (auto* inspector : protocol::InspectorHandler::ForAgentHost(this)) |
| inspector->TargetReloadedAfterCrash(); |
| } |
| UpdateRendererChannel(IsAttached()); |
| } |
| |
| void RenderFrameDevToolsAgentHost::RenderProcessExited( |
| RenderProcessHost* host, |
| const ChildProcessTerminationInfo& info) { |
| switch (info.status) { |
| case base::TERMINATION_STATUS_ABNORMAL_TERMINATION: |
| case base::TERMINATION_STATUS_PROCESS_WAS_KILLED: |
| #if BUILDFLAG(IS_CHROMEOS) |
| case base::TERMINATION_STATUS_PROCESS_WAS_KILLED_BY_OOM: |
| #endif |
| case base::TERMINATION_STATUS_PROCESS_CRASHED: |
| #if BUILDFLAG(IS_ANDROID) |
| case base::TERMINATION_STATUS_OOM_PROTECTED: |
| #endif |
| case base::TERMINATION_STATUS_LAUNCH_FAILED: |
| for (auto* inspector : protocol::InspectorHandler::ForAgentHost(this)) |
| inspector->TargetCrashed(); |
| NotifyCrashed(info.status); |
| render_frame_crashed_ = true; |
| break; |
| default: |
| for (auto* inspector : protocol::InspectorHandler::ForAgentHost(this)) |
| inspector->TargetDetached("Render process gone."); |
| break; |
| } |
| } |
| |
| void RenderFrameDevToolsAgentHost::OnVisibilityChanged( |
| content::Visibility visibility) { |
| #if BUILDFLAG(IS_ANDROID) |
| if (!sessions().empty()) { |
| if (visibility == content::Visibility::HIDDEN) { |
| GetWakeLock()->CancelWakeLock(); |
| } else { |
| GetWakeLock()->RequestWakeLock(); |
| } |
| } |
| #endif |
| } |
| |
| void RenderFrameDevToolsAgentHost::OnNavigationRequestWillBeSent( |
| const NavigationRequest& navigation_request) { |
| GURL url = navigation_request.common_params().url; |
| if (url.SchemeIs(url::kJavaScriptScheme) && frame_host_) |
| url = frame_host_->GetLastCommittedURL(); |
| std::vector<DevToolsSession*> restricted_sessions; |
| bool is_webui = frame_host_ && frame_host_->web_ui(); |
| for (DevToolsSession* session : sessions()) { |
| if (!session->GetClient()->MayAttachToURL(url, is_webui)) |
| restricted_sessions.push_back(session); |
| } |
| if (!restricted_sessions.empty()) |
| ForceDetachRestrictedSessions(restricted_sessions); |
| } |
| |
| void RenderFrameDevToolsAgentHost::UpdatePortals() { |
| auto_attacher_->UpdatePages(); |
| } |
| |
| void RenderFrameDevToolsAgentHost::DidCreateFencedFrame( |
| FencedFrame* fenced_frame) { |
| auto_attacher_->AutoAttachToPage(fenced_frame->GetInnerRoot()->frame_tree(), |
| true); |
| } |
| |
| void RenderFrameDevToolsAgentHost::DisconnectWebContents() { |
| WebContentsObserver::Observe(nullptr); |
| navigation_requests_.clear(); |
| for (auto* tracing : protocol::TracingHandler::ForAgentHost(this)) { |
| tracing->DisconnectWebContents(); |
| } |
| SetFrameTreeNode(nullptr); |
| // UpdateFrameHost may destruct |this|. |
| scoped_refptr<RenderFrameDevToolsAgentHost> protect(this); |
| UpdateFrameHost(nullptr); |
| for (DevToolsSession* session : sessions()) |
| session->ResumeSendingMessagesToAgent(); |
| } |
| |
| void RenderFrameDevToolsAgentHost::ConnectWebContents(WebContents* wc) { |
| RenderFrameHostImpl* host = |
| static_cast<RenderFrameHostImpl*>(wc->GetPrimaryMainFrame()); |
| DCHECK(host); |
| WebContentsObserver::Observe(wc); |
| for (auto* tracing : protocol::TracingHandler::ForAgentHost(this)) { |
| tracing->ConnectWebContents(wc); |
| } |
| SetFrameTreeNode(host->frame_tree_node()); |
| UpdateFrameHost(host); |
| // UpdateFrameHost may destruct |this|. |
| } |
| |
| std::string RenderFrameDevToolsAgentHost::GetParentId() { |
| if (IsChildFrame()) { |
| FrameTreeNode* frame_tree_node = |
| GetFrameTreeNodeAncestor(frame_tree_node_->parent()->frame_tree_node()); |
| return RenderFrameDevToolsAgentHost::GetOrCreateFor(frame_tree_node) |
| ->GetId(); |
| } |
| |
| WebContentsImpl* contents = static_cast<WebContentsImpl*>(web_contents()); |
| if (!contents) |
| return ""; |
| contents = contents->GetOuterWebContents(); |
| if (contents) |
| return DevToolsAgentHost::GetOrCreateFor(contents)->GetId(); |
| return ""; |
| } |
| |
| std::string RenderFrameDevToolsAgentHost::GetOpenerId() { |
| if (!frame_tree_node_) |
| return std::string(); |
| FrameTreeNode* opener = |
| frame_tree_node_->first_live_main_frame_in_original_opener_chain(); |
| return opener |
| ? opener->current_frame_host()->devtools_frame_token().ToString() |
| : std::string(); |
| } |
| |
| std::string RenderFrameDevToolsAgentHost::GetOpenerFrameId() { |
| if (!frame_tree_node_) |
| return std::string(); |
| const absl::optional<base::UnguessableToken>& opener_devtools_frame_token = |
| frame_tree_node_->opener_devtools_frame_token(); |
| return opener_devtools_frame_token ? opener_devtools_frame_token->ToString() |
| : std::string(); |
| } |
| |
| bool RenderFrameDevToolsAgentHost::CanAccessOpener() { |
| return (frame_tree_node_ && frame_tree_node_->opener()); |
| } |
| |
| std::string RenderFrameDevToolsAgentHost::GetType() { |
| if (IsChildFrame()) |
| return kTypeFrame; |
| if (frame_tree_node_ && frame_tree_node_->IsFencedFrameRoot()) |
| return kTypeFrame; |
| if (web_contents() && |
| static_cast<WebContentsImpl*>(web_contents())->IsPortal()) { |
| return kTypePage; |
| } |
| if (web_contents() && |
| static_cast<WebContentsImpl*>(web_contents())->GetOuterWebContents()) { |
| return kTypeGuest; |
| } |
| DevToolsManager* manager = DevToolsManager::GetInstance(); |
| if (manager->delegate() && web_contents()) { |
| std::string type = manager->delegate()->GetTargetType(web_contents()); |
| if (!type.empty()) |
| return type; |
| } |
| return kTypePage; |
| } |
| |
| std::string RenderFrameDevToolsAgentHost::GetTitle() { |
| DevToolsManager* manager = DevToolsManager::GetInstance(); |
| if (manager->delegate() && web_contents()) { |
| std::string title = manager->delegate()->GetTargetTitle(web_contents()); |
| if (!title.empty()) |
| return title; |
| } |
| if (frame_host_) { |
| if (IsChildFrame()) |
| return frame_host_->GetLastCommittedURL().spec(); |
| if (!frame_host_->GetPage().IsPrimary()) { |
| NavigationEntryImpl* entry = frame_host_->frame_tree_node() |
| ->frame_tree() |
| .controller() |
| .GetLastCommittedEntry(); |
| return entry ? base::UTF16ToUTF8(entry->GetTitleForDisplay()) |
| : std::string(); |
| } |
| } |
| if (web_contents()) |
| return base::UTF16ToUTF8(web_contents()->GetTitle()); |
| return GetURL().spec(); |
| } |
| |
| std::string RenderFrameDevToolsAgentHost::GetDescription() { |
| DevToolsManager* manager = DevToolsManager::GetInstance(); |
| if (manager->delegate() && web_contents()) |
| return manager->delegate()->GetTargetDescription(web_contents()); |
| return std::string(); |
| } |
| |
| GURL RenderFrameDevToolsAgentHost::GetURL() { |
| // Order is important here. |
| if (frame_host_ && (IsChildFrame() || !frame_host_->GetPage().IsPrimary())) |
| return frame_host_->GetLastCommittedURL(); |
| WebContents* web_contents = GetWebContents(); |
| if (web_contents) { |
| return web_contents->GetVisibleURL(); |
| } |
| return GURL(); |
| } |
| |
| GURL RenderFrameDevToolsAgentHost::GetFaviconURL() { |
| WebContents* wc = web_contents(); |
| if (!wc) |
| return GURL(); |
| NavigationEntry* entry = wc->GetController().GetLastCommittedEntry(); |
| if (entry) |
| return entry->GetFavicon().url; |
| return GURL(); |
| } |
| |
| bool RenderFrameDevToolsAgentHost::Activate() { |
| WebContentsImpl* wc = static_cast<WebContentsImpl*>(web_contents()); |
| if (wc) { |
| wc->Activate(); |
| return true; |
| } |
| return false; |
| } |
| |
| void RenderFrameDevToolsAgentHost::Reload() { |
| WebContentsImpl* wc = static_cast<WebContentsImpl*>(web_contents()); |
| if (wc) |
| wc->GetController().Reload(ReloadType::NORMAL, true); |
| } |
| |
| bool RenderFrameDevToolsAgentHost::Close() { |
| if (web_contents()) { |
| web_contents()->ClosePage(); |
| return true; |
| } |
| return false; |
| } |
| |
| base::TimeTicks RenderFrameDevToolsAgentHost::GetLastActivityTime() { |
| if (WebContents* contents = web_contents()) |
| return contents->GetLastActiveTime(); |
| return base::TimeTicks(); |
| } |
| |
| void RenderFrameDevToolsAgentHost::UpdateRendererChannel(bool force) { |
| mojo::PendingAssociatedRemote<blink::mojom::DevToolsAgent> agent_remote; |
| mojo::PendingAssociatedReceiver<blink::mojom::DevToolsAgentHost> |
| host_receiver; |
| if (frame_host_ && render_frame_alive_ && force) { |
| mojo::PendingAssociatedRemote<blink::mojom::DevToolsAgentHost> host_remote; |
| host_receiver = host_remote.InitWithNewEndpointAndPassReceiver(); |
| frame_host_->BindDevToolsAgent( |
| std::move(host_remote), |
| agent_remote.InitWithNewEndpointAndPassReceiver()); |
| } |
| int process_id = frame_host_ ? frame_host_->GetProcess()->GetID() |
| : ChildProcessHost::kInvalidUniqueID; |
| GetRendererChannel()->SetRendererAssociated(std::move(agent_remote), |
| std::move(host_receiver), |
| process_id, frame_host_); |
| auto_attacher_->SetRenderFrameHost(frame_host_); |
| } |
| |
| protocol::TargetAutoAttacher* RenderFrameDevToolsAgentHost::auto_attacher() { |
| return auto_attacher_.get(); |
| } |
| |
| namespace { |
| |
| constexpr char kSubtypeDisconnected[] = "disconnected"; |
| constexpr char kSubtypePortal[] = "portal"; |
| constexpr char kSubtypePrerender[] = "prerender"; |
| constexpr char kSubtypeFenced[] = "fenced"; |
| |
| } // namespace |
| std::string RenderFrameDevToolsAgentHost::GetSubtype() { |
| if (!frame_tree_node_) |
| return kSubtypeDisconnected; |
| |
| switch (frame_tree_node_->GetFrameType()) { |
| case FrameType::kPrimaryMainFrame: |
| if (WebContentsImpl::FromFrameTreeNode(frame_tree_node_)->IsPortal()) { |
| return kSubtypePortal; |
| } |
| [[fallthrough]]; |
| // TODO(caseq): figure out what's best to return for subframes in a tree |
| // other than primary. |
| case FrameType::kSubframe: |
| return ""; |
| case FrameType::kPrerenderMainFrame: |
| return kSubtypePrerender; |
| case FrameType::kFencedFrameRoot: |
| return kSubtypeFenced; |
| } |
| } |
| |
| bool RenderFrameDevToolsAgentHost::IsChildFrame() { |
| return frame_tree_node_ && frame_tree_node_->parent(); |
| } |
| |
| bool RenderFrameDevToolsAgentHost::ShouldAllowSession( |
| RenderFrameHost* frame_host, |
| DevToolsSession* session) { |
| // There's not much we can say if there's not host yet, but we'll |
| // check again when host is updated. |
| if (!frame_host) { |
| return true; |
| } |
| DevToolsManager* manager = DevToolsManager::GetInstance(); |
| if (manager->delegate() && |
| !manager->delegate()->AllowInspectingRenderFrameHost(frame_host)) { |
| return false; |
| } |
| return session->GetClient()->MayAttachToRenderFrameHost(frame_host); |
| } |
| |
| void RenderFrameDevToolsAgentHost::UpdateResourceLoaderFactories() { |
| if (!frame_host_) |
| return; |
| |
| frame_host_->ForEachRenderFrameHostWithAction([this]( |
| RenderFrameHostImpl* rfh) { |
| if (frame_host_ != rfh && (rfh->is_local_root_subframe() || |
| &frame_host_->GetPage() != &rfh->GetPage())) { |
| return content::RenderFrameHost::FrameIterationAction::kSkipChildren; |
| } |
| rfh->UpdateSubresourceLoaderFactories(); |
| return content::RenderFrameHost::FrameIterationAction::kContinue; |
| }); |
| } |
| |
| absl::optional<network::CrossOriginEmbedderPolicy> |
| RenderFrameDevToolsAgentHost::cross_origin_embedder_policy( |
| const std::string& id) { |
| FrameTreeNode* frame_tree_node = |
| protocol::FrameTreeNodeFromDevToolsFrameToken( |
| frame_host_->frame_tree_node(), id); |
| if (!frame_tree_node) { |
| return absl::nullopt; |
| } |
| RenderFrameHostImpl* rfhi = frame_tree_node->current_frame_host(); |
| return rfhi->cross_origin_embedder_policy(); |
| } |
| |
| absl::optional<network::CrossOriginOpenerPolicy> |
| RenderFrameDevToolsAgentHost::cross_origin_opener_policy( |
| const std::string& id) { |
| FrameTreeNode* frame_tree_node = |
| protocol::FrameTreeNodeFromDevToolsFrameToken( |
| frame_host_->frame_tree_node(), id); |
| if (!frame_tree_node) { |
| return absl::nullopt; |
| } |
| RenderFrameHostImpl* rfhi = frame_tree_node->current_frame_host(); |
| return rfhi->cross_origin_opener_policy(); |
| } |
| |
| absl::optional<std::vector<network::mojom::ContentSecurityPolicyHeader>> |
| RenderFrameDevToolsAgentHost::content_security_policy(const std::string& id) { |
| FrameTreeNode* frame_tree_node = |
| protocol::FrameTreeNodeFromDevToolsFrameToken( |
| frame_host_->frame_tree_node(), id); |
| if (!frame_tree_node) { |
| return absl::nullopt; |
| } |
| RenderFrameHostImpl* rfhi = frame_tree_node->current_frame_host(); |
| const PolicyContainerPolicies& policies = |
| rfhi->policy_container_host()->policies(); |
| if (policies.content_security_policies.empty()) { |
| return absl::nullopt; |
| } else { |
| std::vector<network::mojom::ContentSecurityPolicyHeader> csp_headers; |
| for (const auto& content_security_policy : |
| policies.content_security_policies) { |
| csp_headers.push_back(*content_security_policy->header); |
| } |
| return csp_headers; |
| } |
| } |
| |
| bool RenderFrameDevToolsAgentHost::HasSessionsWithoutTabTargetSupport() const { |
| const std::vector<DevToolsSession*>& sessions = |
| DevToolsAgentHostImpl::sessions(); |
| |
| return std::any_of(sessions.begin(), sessions.end(), [](DevToolsSession* s) { |
| return s->session_mode() == DevToolsSession::Mode::kDoesNotSupportTabTarget; |
| }); |
| } |
| |
| } // namespace content |