[go: nahoru, domu]

blob: 8b643a8f726a4e39539eac051d626f59081cdc77 [file] [log] [blame]
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/devtools/frame_auto_attacher.h"
#include "base/time/time.h"
#include "content/browser/devtools/auction_worklet_devtools_agent_host.h"
#include "content/browser/devtools/devtools_renderer_channel.h"
#include "content/browser/devtools/render_frame_devtools_agent_host.h"
#include "content/browser/devtools/service_worker_devtools_agent_host.h"
#include "content/browser/renderer_host/frame_tree.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/browser/web_contents/web_contents_impl.h"
namespace content {
namespace {
using ScopeAgentsMap =
std::map<GURL, std::unique_ptr<ServiceWorkerDevToolsAgentHost::List>>;
void GetMatchingHostsByScopeMap(
const ServiceWorkerDevToolsAgentHost::List& agent_hosts,
const base::flat_set<GURL>& urls,
ScopeAgentsMap* scope_agents_map) {
base::flat_set<GURL> host_name_set;
for (const GURL& url : urls)
host_name_set.insert(url.DeprecatedGetOriginAsURL());
for (const auto& host : agent_hosts) {
if (host_name_set.find(host->scope().DeprecatedGetOriginAsURL()) ==
host_name_set.end())
continue;
const auto& it = scope_agents_map->find(host->scope());
if (it == scope_agents_map->end()) {
std::unique_ptr<ServiceWorkerDevToolsAgentHost::List> new_list(
new ServiceWorkerDevToolsAgentHost::List());
new_list->push_back(host);
(*scope_agents_map)[host->scope()] = std::move(new_list);
} else {
it->second->push_back(host);
}
}
}
void AddEligibleHosts(const ServiceWorkerDevToolsAgentHost::List& list,
ServiceWorkerDevToolsAgentHost::Map* result) {
base::Time last_installed_time;
base::Time last_doomed_time;
for (const auto& host : list) {
if (host->version_installed_time() > last_installed_time)
last_installed_time = host->version_installed_time();
if (host->version_doomed_time() > last_doomed_time)
last_doomed_time = host->version_doomed_time();
}
for (const auto& host : list) {
// We don't attech old redundant Service Workers when there is newer
// installed Service Worker.
if (host->version_doomed_time().is_null() ||
(last_installed_time < last_doomed_time &&
last_doomed_time == host->version_doomed_time())) {
(*result)[host->GetId()] = host;
}
}
}
ServiceWorkerDevToolsAgentHost::Map GetMatchingServiceWorkers(
BrowserContext* browser_context,
const base::flat_set<GURL>& urls) {
ServiceWorkerDevToolsAgentHost::Map result;
if (!browser_context)
return result;
ServiceWorkerDevToolsAgentHost::List agent_hosts;
ServiceWorkerDevToolsManager::GetInstance()
->AddAllAgentHostsForBrowserContext(browser_context, &agent_hosts);
ScopeAgentsMap scope_agents_map;
GetMatchingHostsByScopeMap(agent_hosts, urls, &scope_agents_map);
for (const auto& it : scope_agents_map)
AddEligibleHosts(*it.second.get(), &result);
return result;
}
base::flat_set<GURL> GetFrameUrls(RenderFrameHostImpl* render_frame_host) {
// We try to attach to a service worker in the following cases:
// 1. SW is created while user is inspecting frame (from WorkerCreated).
// 2. Frame has navigated and we are picking up new SW corresponding to new
// url (from DidFinishNavigation).
// 3. Frame is trying to navigate and it spawns a new SW which we pick up
// (from WorkerCreated). See also https://crbug.com/907072
//
// We are not attaching in the following case:
// 4. Frame is trying to navigate and we _should_ pick up an existing SW but
// we don't. We _could_ do this, but since we are not pausing the
// navigation, there is no principal difference between picking up SW
// earlier or later.
//
// We also try to detach from SW picked up for [3] if navigation has failed
// (from DidFinishNavigation).
base::flat_set<GURL> frame_urls;
if (render_frame_host) {
for (FrameTreeNode* node : render_frame_host->frame_tree()->Nodes()) {
frame_urls.insert(node->current_url());
// We use both old and new frame urls to support [3], where we attach
// while navigation is still ongoing.
if (node->navigation_request()) {
frame_urls.insert(node->navigation_request()->common_params().url);
}
}
}
return frame_urls;
}
} // namespace
FrameAutoAttacher::FrameAutoAttacher(DevToolsRendererChannel* renderer_channel)
: RendererAutoAttacherBase(renderer_channel) {}
FrameAutoAttacher::~FrameAutoAttacher() = default;
void FrameAutoAttacher::SetRenderFrameHost(
RenderFrameHostImpl* render_frame_host) {
render_frame_host_ = render_frame_host;
if (!auto_attach())
return;
UpdateFrames();
UpdatePages();
ReattachServiceWorkers();
}
void FrameAutoAttacher::DidFinishNavigation(
NavigationRequest* navigation_request) {
if (!render_frame_host_)
return;
if (navigation_request->frame_tree_node() ==
render_frame_host_->frame_tree_node()) {
ReattachServiceWorkers();
return;
}
// We only care about subframes that have |render_frame_host_| as their
// local root.
if (!navigation_request->HasCommitted())
return;
RenderFrameHostImpl* parent = navigation_request->GetParentFrame();
while (parent && !parent->is_local_root())
parent = parent->GetParent();
if (parent != render_frame_host_)
return;
// Some subframes may not be attached through
// TargetHandler::ResponseThrottle because DevTools wasn't attached when the
// navigation started, so no throttle was installed. We auto-attach them
// here instead (note that we cannot honor |wait_for_debugger_on_start_| in
// this case).
AutoAttachToFrame(navigation_request, false);
}
void FrameAutoAttacher::UpdatePages() {
if (!auto_attach())
return;
Hosts new_hosts;
if (render_frame_host_) {
render_frame_host_->ForEachRenderFrameHost(base::BindRepeating(
[](Hosts* new_hosts, RenderFrameHost* root, RenderFrameHost* rfh) {
RenderFrameHostImpl* rfhi = static_cast<RenderFrameHostImpl*>(rfh);
if (rfh == root)
return RenderFrameHost::FrameIterationAction::kContinue;
FrameTreeNode* frame_tree_node = rfhi->frame_tree_node();
if (frame_tree_node->IsMainFrame() &&
(frame_tree_node->IsFencedFrameRoot() ||
WebContentsImpl::FromFrameTreeNode(frame_tree_node)
->IsPortal())) {
scoped_refptr<DevToolsAgentHost> new_host =
RenderFrameDevToolsAgentHost::GetOrCreateFor(frame_tree_node);
new_hosts->insert(new_host);
return RenderFrameHost::FrameIterationAction::kSkipChildren;
}
if (rfhi->is_local_root())
return RenderFrameHost::FrameIterationAction::kSkipChildren;
return RenderFrameHost::FrameIterationAction::kContinue;
},
&new_hosts, render_frame_host_));
}
DispatchSetAttachedTargetsOfType(new_hosts, DevToolsAgentHost::kTypePage);
}
void FrameAutoAttacher::AutoAttachToPage(FrameTree* frame_tree,
bool wait_for_debugger_on_start) {
if (!auto_attach())
return;
scoped_refptr<DevToolsAgentHost> agent_host =
RenderFrameDevToolsAgentHost::GetOrCreateFor(frame_tree->root());
DispatchAutoAttach(agent_host.get(), wait_for_debugger_on_start);
}
void FrameAutoAttacher::UpdateAutoAttach(base::OnceClosure callback) {
if (auto_attach()) {
UpdateFrames();
UpdatePages();
if (render_frame_host_ && !render_frame_host_->GetParent() &&
!observing_service_workers_) {
observing_service_workers_ = true;
ServiceWorkerDevToolsManager::GetInstance()->AddObserver(this);
}
if (observing_service_workers_) {
// Update service workers even if we've already been observing them,
// to notify new clients about existing service workers.
// This is similar to frames and pages above.
ReattachServiceWorkers();
}
if (render_frame_host_ && !observing_auction_worklets_) {
observing_auction_worklets_ = true;
DebuggableAuctionWorkletTracker::GetInstance()->AddObserver(this);
}
} else {
if (observing_service_workers_) {
ServiceWorkerDevToolsManager::GetInstance()->RemoveObserver(this);
observing_service_workers_ = false;
}
if (observing_auction_worklets_) {
DebuggableAuctionWorkletTracker::GetInstance()->RemoveObserver(this);
observing_auction_worklets_ = false;
}
}
RendererAutoAttacherBase::UpdateAutoAttach(std::move(callback));
}
void FrameAutoAttacher::WorkerCreated(ServiceWorkerDevToolsAgentHost* host,
bool* should_pause_on_start) {
if (!render_frame_host_)
return;
BrowserContext* browser_context =
render_frame_host_->GetProcess()->GetBrowserContext();
auto hosts = GetMatchingServiceWorkers(browser_context,
GetFrameUrls(render_frame_host_));
if (hosts.find(host->GetId()) == hosts.end())
return;
*should_pause_on_start = wait_for_debugger_on_start();
DispatchAutoAttach(host, *should_pause_on_start);
}
void FrameAutoAttacher::WorkerDestroyed(ServiceWorkerDevToolsAgentHost* host) {
ReattachServiceWorkers();
}
void FrameAutoAttacher::AuctionWorkletCreated(DebuggableAuctionWorklet* worklet,
bool& should_pause_on_start) {
if (!render_frame_host_)
return;
if (!AuctionWorkletDevToolsAgentHost::IsRelevantTo(render_frame_host_,
worklet)) {
return;
}
should_pause_on_start = wait_for_debugger_on_start();
DispatchAutoAttach(AuctionWorkletDevToolsAgentHostManager::GetInstance()
.GetOrCreateFor(worklet)
.get(),
should_pause_on_start);
}
void FrameAutoAttacher::ReattachServiceWorkers() {
if (!observing_service_workers_ || !render_frame_host_)
return;
BrowserContext* browser_context =
render_frame_host_->GetProcess()->GetBrowserContext();
auto matching = GetMatchingServiceWorkers(browser_context,
GetFrameUrls(render_frame_host_));
Hosts new_hosts;
for (const auto& pair : matching)
new_hosts.insert(pair.second);
DispatchSetAttachedTargetsOfType(new_hosts,
DevToolsAgentHost::kTypeServiceWorker);
}
void FrameAutoAttacher::UpdateFrames() {
DCHECK(auto_attach());
Hosts new_hosts;
DevToolsAgentHost::List new_worklet_hosts;
if (render_frame_host_) {
base::queue<FrameTreeNode*> queue;
for (size_t i = 0; i < render_frame_host_->child_count(); ++i) {
queue.push(render_frame_host_->child_at(i));
}
while (!queue.empty()) {
FrameTreeNode* node = queue.front();
queue.pop();
bool should_create = node->current_frame_host()->is_local_root_subframe();
if (should_create) {
scoped_refptr<DevToolsAgentHost> new_host =
RenderFrameDevToolsAgentHost::GetOrCreateFor(node);
new_hosts.insert(new_host);
// Note: We don't add children of a local root to |queue|, as they
// will be looked at by a separate TargetAutoAttacher created for the
// local root.
} else {
for (size_t i = 0; i < node->child_count(); ++i)
queue.push(node->child_at(i));
}
}
AuctionWorkletDevToolsAgentHostManager::GetInstance().GetAllForFrame(
render_frame_host_, &new_worklet_hosts);
}
DispatchSetAttachedTargetsOfType(new_hosts, DevToolsAgentHost::kTypeFrame);
DispatchSetAttachedTargetsOfType(
TargetAutoAttacher::Hosts(new_worklet_hosts.begin(),
new_worklet_hosts.end()),
DevToolsAgentHost::kTypeAuctionWorklet);
}
} // namespace content