blob: f771973298aff4602e290f3c8ada38dea6e96955 [file] [log] [blame]
// Copyright 2014 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/service_worker_devtools_manager.h"
#include <algorithm>
#include <optional>
#include "base/containers/contains.h"
#include "base/no_destructor.h"
#include "base/observer_list.h"
#include "content/browser/devtools/devtools_instrumentation.h"
#include "content/browser/devtools/protocol/network_handler.h"
#include "content/browser/devtools/protocol/page_handler.h"
#include "content/browser/devtools/service_worker_devtools_agent_host.h"
#include "content/browser/service_worker/service_worker_context_wrapper.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_process_host.h"
#include "services/network/public/cpp/devtools_observer_util.h"
#include "services/network/public/mojom/devtools_observer.mojom.h"
namespace content {
// static
ServiceWorkerDevToolsManager* ServiceWorkerDevToolsManager::GetInstance() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
static base::NoDestructor<ServiceWorkerDevToolsManager> instance;
return &*instance;
}
ServiceWorkerDevToolsAgentHost*
ServiceWorkerDevToolsManager::GetDevToolsAgentHostForWorker(
int worker_process_id,
int worker_route_id) {
auto it = live_hosts_.find(WorkerId(worker_process_id, worker_route_id));
return it == live_hosts_.end() ? nullptr : it->second.get();
}
ServiceWorkerDevToolsAgentHost*
ServiceWorkerDevToolsManager::GetDevToolsAgentHostForNewInstallingWorker(
const ServiceWorkerContextWrapper* context_wrapper,
int64_t version_id) {
auto it = std::ranges::find_if(
new_installing_hosts_,
[&context_wrapper, &version_id](
const scoped_refptr<ServiceWorkerDevToolsAgentHost>& agent_host) {
return agent_host->context_wrapper() == context_wrapper &&
agent_host->version_id() == version_id;
});
if (it == new_installing_hosts_.end())
return nullptr;
return it->get();
}
void ServiceWorkerDevToolsManager::AddAllAgentHosts(
ServiceWorkerDevToolsAgentHost::List* result) {
for (auto& it : live_hosts_)
result->push_back(it.second.get());
}
void ServiceWorkerDevToolsManager::AddAllAgentHostsForBrowserContext(
BrowserContext* browser_context,
ServiceWorkerDevToolsAgentHost::List* result) {
for (auto& it : live_hosts_) {
if (it.second->GetBrowserContext() == browser_context)
result->push_back(it.second.get());
}
for (auto& it : new_installing_hosts_) {
if (it->GetBrowserContext() == browser_context)
result->push_back(it.get());
}
}
void ServiceWorkerDevToolsManager::WorkerMainScriptFetchingStarting(
scoped_refptr<ServiceWorkerContextWrapper> context_wrapper,
int64_t version_id,
const GURL& url,
const GURL& scope,
const GlobalRenderFrameHostId& requesting_frame_id,
scoped_refptr<DevToolsThrottleHandle> throttle_handle) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// Verify that we are not getting a similar host that's already in a stopped
// state. This should never happen, we are installing a new SW, we cannot
// have the same one that was started and stopped.
ServiceWorkerContextWrapper* context_wrapper_ptr = context_wrapper.get();
scoped_refptr<ServiceWorkerDevToolsAgentHost> agent_host =
TakeStoppedHost(context_wrapper_ptr, version_id);
DCHECK(!agent_host);
scoped_refptr<ServiceWorkerDevToolsAgentHost> host =
base::MakeRefCounted<ServiceWorkerDevToolsAgentHost>(
-1, -1, std::move(context_wrapper), version_id, url, scope,
/*is_installed_version=*/false,
/*client_security_state=*/nullptr,
/*coep_reporter=*/mojo::NullRemote(),
/*dip_reporter=*/mojo::NullRemote(),
base::UnguessableToken::Create());
ServiceWorkerDevToolsAgentHost* host_ptr = host.get();
new_installing_hosts_.insert(std::move(host));
for (auto& observer : observer_list_) {
bool should_pause_on_start = false;
observer.WorkerCreated(host_ptr, &should_pause_on_start);
if (should_pause_on_start) {
host_ptr->set_should_pause_on_start(true);
}
}
// Now that we have a devtools target, we need to give devtools the
// opportunity to attach to it before we do the actual fetch. We pass it a
// callback that will be called once we have all the handlers ready.
if (host_ptr->should_pause_on_start()) {
devtools_instrumentation::ThrottleServiceWorkerMainScriptFetch(
context_wrapper_ptr, version_id, requesting_frame_id, throttle_handle);
}
}
void ServiceWorkerDevToolsManager::WorkerMainScriptFetchingFailed(
scoped_refptr<ServiceWorkerContextWrapper> context_wrapper,
int64_t version_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
scoped_refptr<ServiceWorkerDevToolsAgentHost> host =
TakeNewInstallingHost(context_wrapper.get(), version_id);
// While not strictly required, some WPTs expect all messages to be answered
// before finishing and will loop until they get an answer. This call makes
// sure all pending messages are answered with an error when we fail the
// main script fetch.
host->WorkerMainScriptFetchingFailed();
// This observer call should trigger the destruction of the
// ServiceWorkerDevToolsAgentHost by removing the scoped_ptr references held
// by auto-attachers.
for (auto& observer : observer_list_)
observer.WorkerDestroyed(host.get());
}
void ServiceWorkerDevToolsManager::WorkerStarting(
int worker_process_id,
int worker_route_id,
scoped_refptr<ServiceWorkerContextWrapper> context_wrapper,
int64_t version_id,
const GURL& url,
const GURL& scope,
bool is_installed_version,
network::mojom::ClientSecurityStatePtr client_security_state,
mojo::PendingRemote<network::mojom::CrossOriginEmbedderPolicyReporter>
coep_reporter,
mojo::PendingRemote<network::mojom::DocumentIsolationPolicyReporter>
dip_reporter,
base::UnguessableToken* devtools_worker_token,
bool* pause_on_start) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
const WorkerId worker_id(worker_process_id, worker_route_id);
DCHECK(!base::Contains(live_hosts_, worker_id));
scoped_refptr<ServiceWorkerDevToolsAgentHost> agent_host =
TakeStoppedHost(context_wrapper.get(), version_id);
if (agent_host) {
live_hosts_[worker_id] = agent_host;
agent_host->WorkerStarted(worker_process_id, worker_route_id);
*pause_on_start =
agent_host->IsAttached() && agent_host->should_pause_on_start();
*devtools_worker_token = agent_host->devtools_worker_token();
return;
}
agent_host = TakeNewInstallingHost(context_wrapper.get(), version_id);
if (agent_host) {
live_hosts_[worker_id] = agent_host;
agent_host->WorkerStarted(worker_process_id, worker_route_id);
*pause_on_start = agent_host->should_pause_on_start();
*devtools_worker_token = agent_host->devtools_worker_token();
if (client_security_state) {
agent_host->UpdateClientSecurityState(std::move(client_security_state),
std::move(coep_reporter),
std::move(dip_reporter));
}
return;
}
*devtools_worker_token = base::UnguessableToken::Create();
auto host = base::MakeRefCounted<ServiceWorkerDevToolsAgentHost>(
worker_process_id, worker_route_id, std::move(context_wrapper),
version_id, url, scope, is_installed_version,
std::move(client_security_state), std::move(coep_reporter),
std::move(dip_reporter), *devtools_worker_token);
live_hosts_[worker_id] = host;
*pause_on_start = debug_service_worker_on_start_;
for (auto& observer : observer_list_) {
bool should_pause_on_start = false;
observer.WorkerCreated(host.get(), &should_pause_on_start);
if (should_pause_on_start)
*pause_on_start = true;
}
}
void ServiceWorkerDevToolsManager::WorkerReadyForInspection(
int worker_process_id,
int worker_route_id,
mojo::PendingRemote<blink::mojom::DevToolsAgent> agent_remote,
mojo::PendingReceiver<blink::mojom::DevToolsAgentHost> host_receiver) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
const WorkerId worker_id(worker_process_id, worker_route_id);
auto it = live_hosts_.find(worker_id);
if (it == live_hosts_.end())
return;
scoped_refptr<ServiceWorkerDevToolsAgentHost> host = it->second;
host->WorkerReadyForInspection(std::move(agent_remote),
std::move(host_receiver));
// Bring up UI for the ones not picked by other clients.
if (debug_service_worker_on_start_ && !host->IsAttached())
host->Inspect();
}
void ServiceWorkerDevToolsManager::WorkerVersionInstalled(int worker_process_id,
int worker_route_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
const WorkerId worker_id(worker_process_id, worker_route_id);
auto it = live_hosts_.find(worker_id);
if (it == live_hosts_.end())
return;
it->second->WorkerVersionInstalled();
}
void ServiceWorkerDevToolsManager::WorkerVersionDoomed(
int worker_process_id,
int worker_route_id,
scoped_refptr<ServiceWorkerContextWrapper> context_wrapper,
int64_t version_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
const WorkerId worker_id(worker_process_id, worker_route_id);
auto it = live_hosts_.find(worker_id);
if (it != live_hosts_.end()) {
it->second->WorkerVersionDoomed();
return;
}
scoped_refptr<ServiceWorkerDevToolsAgentHost> host =
TakeStoppedHost(context_wrapper.get(), version_id);
if (!host)
return;
host->WorkerVersionDoomed();
// The worker has already been stopped and since it's doomed it will never
// restart.
for (auto& observer : observer_list_)
observer.WorkerDestroyed(host.get());
}
void ServiceWorkerDevToolsManager::WorkerStopped(int worker_process_id,
int worker_route_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
const WorkerId worker_id(worker_process_id, worker_route_id);
auto it = live_hosts_.find(worker_id);
if (it == live_hosts_.end())
return;
scoped_refptr<ServiceWorkerDevToolsAgentHost> agent_host(it->second);
live_hosts_.erase(it);
agent_host->WorkerStopped();
if (agent_host->version_doomed_time().is_null()) {
stopped_hosts_.insert(agent_host.get());
} else {
// The worker version has been doomed, it will never restart.
for (auto& observer : observer_list_)
observer.WorkerDestroyed(agent_host.get());
}
}
void ServiceWorkerDevToolsManager::AgentHostDestroyed(
ServiceWorkerDevToolsAgentHost* agent_host) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// Might be missing during shutdown due to different
// destruction order of this manager, service workers
// and their agent hosts.
stopped_hosts_.erase(agent_host);
}
void ServiceWorkerDevToolsManager::AddObserver(Observer* observer) {
observer_list_.AddObserver(observer);
}
void ServiceWorkerDevToolsManager::RemoveObserver(Observer* observer) {
observer_list_.RemoveObserver(observer);
}
void ServiceWorkerDevToolsManager::set_debug_service_worker_on_start(
bool debug_on_start) {
debug_service_worker_on_start_ = debug_on_start;
}
ServiceWorkerDevToolsManager::ServiceWorkerDevToolsManager()
: debug_service_worker_on_start_(false) {
}
ServiceWorkerDevToolsManager::~ServiceWorkerDevToolsManager() = default;
void ServiceWorkerDevToolsManager::NavigationPreloadRequestSent(
int worker_process_id,
int worker_route_id,
const std::string& request_id,
const network::ResourceRequest& request) {
const WorkerId worker_id(worker_process_id, worker_route_id);
auto it = live_hosts_.find(worker_id);
if (it == live_hosts_.end())
return;
auto timestamp = base::TimeTicks::Now();
network::mojom::URLRequestDevToolsInfoPtr request_info =
network::ExtractDevToolsInfo(request);
for (auto* network :
protocol::NetworkHandler::ForAgentHost(it->second.get())) {
network->RequestSent(request_id, std::string(), request.headers,
*request_info,
protocol::Network::Initiator::TypeEnum::Preload,
/*initiator_url=*/std::nullopt,
/*initiator_devtools_request_id=*/"",
/*frame_token=*/std::nullopt, timestamp);
}
}
void ServiceWorkerDevToolsManager::NavigationPreloadResponseReceived(
int worker_process_id,
int worker_route_id,
const std::string& request_id,
const GURL& url,
const network::mojom::URLResponseHead& head) {
const WorkerId worker_id(worker_process_id, worker_route_id);
auto it = live_hosts_.find(worker_id);
if (it == live_hosts_.end())
return;
network::mojom::URLResponseHeadDevToolsInfoPtr head_info =
network::ExtractDevToolsInfo(head);
for (auto* network : protocol::NetworkHandler::ForAgentHost(it->second.get()))
network->ResponseReceived(request_id, std::string(), url,
protocol::Network::ResourceTypeEnum::Other,
*head_info, std::nullopt);
}
void ServiceWorkerDevToolsManager::NavigationPreloadCompleted(
int worker_process_id,
int worker_route_id,
const std::string& request_id,
const network::URLLoaderCompletionStatus& status) {
const WorkerId worker_id(worker_process_id, worker_route_id);
auto it = live_hosts_.find(worker_id);
if (it == live_hosts_.end())
return;
for (auto* network : protocol::NetworkHandler::ForAgentHost(it->second.get()))
network->LoadingComplete(
request_id, protocol::Network::ResourceTypeEnum::Other, status);
}
scoped_refptr<ServiceWorkerDevToolsAgentHost>
ServiceWorkerDevToolsManager::TakeStoppedHost(
const ServiceWorkerContextWrapper* context_wrapper,
int64_t version_id) {
auto it = std::ranges::find_if(
stopped_hosts_, [&context_wrapper, &version_id](
ServiceWorkerDevToolsAgentHost* agent_host) {
return agent_host->context_wrapper() == context_wrapper &&
agent_host->version_id() == version_id;
});
if (it == stopped_hosts_.end())
return nullptr;
scoped_refptr<ServiceWorkerDevToolsAgentHost> agent_host(*it);
stopped_hosts_.erase(it);
return agent_host;
}
scoped_refptr<ServiceWorkerDevToolsAgentHost>
ServiceWorkerDevToolsManager::TakeNewInstallingHost(
const ServiceWorkerContextWrapper* context_wrapper,
int64_t version_id) {
auto it = std::ranges::find_if(
new_installing_hosts_,
[&context_wrapper, &version_id](
const scoped_refptr<ServiceWorkerDevToolsAgentHost>& agent_host) {
return agent_host->context_wrapper() == context_wrapper &&
agent_host->version_id() == version_id;
});
if (it == new_installing_hosts_.end())
return nullptr;
scoped_refptr<ServiceWorkerDevToolsAgentHost> agent_host(std::move(*it));
new_installing_hosts_.erase(it);
return agent_host;
}
} // namespace content