910e62b5创建于 1月15日历史提交
// 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