blob: 23a462cc05aec9f57914e21a5b77f5a24a387367 [file] [log] [blame]
// Copyright 2024 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/service_worker/service_worker_client.h"
#include <set>
#include <variant>
#include "base/check_is_test.h"
#include "base/containers/adapters.h"
#include "base/containers/contains.h"
#include "base/debug/alias.h"
#include "base/debug/crash_logging.h"
#include "base/notreached.h"
#include "base/task/single_thread_task_runner.h"
#include "base/types/optional_util.h"
#include "base/uuid.h"
#include "content/browser/child_process_security_policy_impl.h"
#include "content/browser/loader/navigation_url_loader_impl.h"
#include "content/browser/preloading/prefetch/prefetch_features.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/service_worker/service_worker_container_host.h"
#include "content/browser/service_worker/service_worker_context_core.h"
#include "content/browser/service_worker/service_worker_security_utils.h"
#include "content/browser/worker_host/dedicated_worker_host.h"
#include "content/browser/worker_host/dedicated_worker_service_impl.h"
#include "content/browser/worker_host/shared_worker_host.h"
#include "content/browser/worker_host/shared_worker_service_impl.h"
#include "content/common/content_navigation_policy.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/common/content_client.h"
#include "content/public/common/origin_util.h"
#include "mojo/public/cpp/bindings/remote_set.h"
#include "net/base/url_util.h"
#include "services/network/public/cpp/single_request_url_loader_factory.h"
#include "services/network/public/cpp/url_loader_factory_builder.h"
#include "services/network/public/cpp/wrapper_shared_url_loader_factory.h"
#include "third_party/abseil-cpp/absl/functional/overload.h"
#include "third_party/blink/public/common/service_worker/service_worker_scope_match.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_running_status_callback.mojom.h"
namespace content {
namespace {
void RunCallbacks(
std::vector<ServiceWorkerClient::ExecutionReadyCallback> callbacks) {
for (auto& callback : callbacks) {
std::move(callback).Run();
}
}
} // namespace
// RAII helper class for keeping track of versions waiting for an update hint
// from the renderer.
//
// This class is move-only.
class ServiceWorkerClient::PendingUpdateVersion {
public:
explicit PendingUpdateVersion(scoped_refptr<ServiceWorkerVersion> version)
: version_(std::move(version)) {
version_->IncrementPendingUpdateHintCount();
}
PendingUpdateVersion(PendingUpdateVersion&& other) {
version_ = std::move(other.version_);
}
PendingUpdateVersion(const PendingUpdateVersion&) = delete;
PendingUpdateVersion& operator=(const PendingUpdateVersion&) = delete;
~PendingUpdateVersion() {
if (version_) {
version_->DecrementPendingUpdateHintCount();
}
}
PendingUpdateVersion& operator=(PendingUpdateVersion&& other) {
version_ = std::move(other.version_);
return *this;
}
// Needed for base::flat_set.
bool operator<(const PendingUpdateVersion& other) const {
return version_ < other.version_;
}
private:
scoped_refptr<ServiceWorkerVersion> version_;
};
// To realize the running status condition in the ServiceWorker static routing
// API, the latest ServiceWorker running status is notified to all renderers
// that execute ServiceWorker subresource loaders and controlled by the
// ServiceWorker.
//
// This is an observer to receive the ServiceWorker running status change,
// and make the update notified to all the renderers via mojo IPC.
//
// See:
// https://w3c.github.io/ServiceWorker/#dom-routercondition-runningstatus
class ServiceWorkerClient::ServiceWorkerRunningStatusObserver final
: public ServiceWorkerVersion::Observer {
public:
void OnRunningStateChanged(ServiceWorkerVersion* version) override {
Notify(version->running_status());
}
void Notify(blink::EmbeddedWorkerStatus status) {
for (const auto& callback : callbacks_) {
callback->OnStatusChanged(status);
}
}
void AddCallback(
mojo::PendingRemote<blink::mojom::ServiceWorkerRunningStatusCallback>
callback) {
callbacks_.Add(std::move(callback));
}
private:
mojo::RemoteSet<blink::mojom::ServiceWorkerRunningStatusCallback> callbacks_;
};
ServiceWorkerClient::ServiceWorkerClient(
base::WeakPtr<ServiceWorkerContextCore> context,
bool is_parent_frame_secure,
FrameTreeNodeId ongoing_navigation_frame_tree_node_id)
: context_(std::move(context)),
owner_(context_->service_worker_client_owner()),
create_time_(base::TimeTicks::Now()),
client_uuid_(base::Uuid::GenerateRandomV4().AsLowercaseString()),
is_parent_frame_secure_(is_parent_frame_secure),
is_initiated_by_prefetch_(false),
client_info_(ServiceWorkerClientInfo()),
process_id_for_worker_client_(ChildProcessHost::kInvalidUniqueID),
ongoing_navigation_frame_tree_node_id_(
ongoing_navigation_frame_tree_node_id) {
DCHECK(context_);
}
ServiceWorkerClient::ServiceWorkerClient(
base::WeakPtr<ServiceWorkerContextCore> context,
bool is_parent_frame_secure,
scoped_refptr<network::SharedURLLoaderFactory>
network_url_loader_factory_for_prefetch)
: context_(std::move(context)),
owner_(context_->service_worker_client_owner()),
create_time_(base::TimeTicks::Now()),
client_uuid_(base::Uuid::GenerateRandomV4().AsLowercaseString()),
is_parent_frame_secure_(is_parent_frame_secure),
is_initiated_by_prefetch_(true),
client_info_(ServiceWorkerClientInfo()),
process_id_for_worker_client_(ChildProcessHost::kInvalidUniqueID),
network_url_loader_factory_for_prefetch_(
std::move(network_url_loader_factory_for_prefetch)) {
DCHECK(context_);
}
ServiceWorkerClient::ServiceWorkerClient(
base::WeakPtr<ServiceWorkerContextCore> context,
int process_id,
ServiceWorkerClientInfo client_info)
: context_(std::move(context)),
owner_(context_->service_worker_client_owner()),
create_time_(base::TimeTicks::Now()),
client_uuid_(base::Uuid::GenerateRandomV4().AsLowercaseString()),
is_parent_frame_secure_(true),
is_initiated_by_prefetch_(false),
client_info_(client_info),
process_id_for_worker_client_(process_id) {
DCHECK(context_);
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_NE(process_id_for_worker_client_, ChildProcessHost::kInvalidUniqueID);
}
ServiceWorkerClient::~ServiceWorkerClient() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (IsContainerForWindowClient()) {
auto* rfh = RenderFrameHostImpl::FromID(GetRenderFrameHostId());
if (rfh) {
rfh->RemoveServiceWorkerClient(client_uuid());
}
}
if (controller_) {
controller_->Uncontrol(client_uuid());
if (running_status_observer_) {
controller_->RemoveObserver(running_status_observer_.get());
running_status_observer_.reset();
}
}
// Remove |this| as an observer of ServiceWorkerRegistrations.
// TODO(falken): Use base::ScopedObservation instead of this explicit call.
controller_.reset();
controller_registration_.reset();
// Ensure callbacks awaiting execution ready are notified.
RunExecutionReadyCallbacks();
RemoveAllMatchingRegistrations();
}
void ServiceWorkerClient::HintToUpdateServiceWorker() {
// The destructors notify the ServiceWorkerVersions to update.
versions_to_update_.clear();
}
void ServiceWorkerClient::EnsureFileAccess(
const std::vector<base::FilePath>& file_paths,
blink::mojom::ServiceWorkerContainerHost::EnsureFileAccessCallback
callback) {
ServiceWorkerVersion* version =
controller_registration_ ? controller_registration_->active_version()
: nullptr;
// The controller might have legitimately been lost due to
// NotifyControllerLost(), so don't ReportBadMessage() here.
if (version) {
int controller_process_id = version->embedded_worker()->process_id();
ChildProcessSecurityPolicyImpl* policy =
ChildProcessSecurityPolicyImpl::GetInstance();
for (const auto& file : file_paths) {
if (!policy->CanReadFile(GetProcessId(), file)) {
mojo::ReportBadMessage(
"The renderer doesn't have access to the file "
"but it tried to grant access to the controller.");
return;
}
if (!policy->CanReadFile(controller_process_id, file)) {
policy->GrantReadFile(controller_process_id, file);
}
}
}
std::move(callback).Run();
}
void ServiceWorkerClient::OnExecutionReady() {
// Since `OnExecutionReady()` is a part of `ServiceWorkerContainerHost`,
// this method is called only if `is_container_ready()` is true.
CHECK(is_container_ready());
if (is_execution_ready()) {
mojo::ReportBadMessage("SWPH_OER_ALREADY_READY");
return;
}
// The controller was sent on navigation commit but we must send it again here
// because 1) the controller might have changed since navigation commit due to
// skipWaiting(), and 2) the UseCounter might have changed since navigation
// commit, in such cases the updated information was prevented being sent due
// to false is_execution_ready().
// TODO(leonhsl): Create some layout tests covering the above case 1), in
// which case we may also need to set |notify_controllerchange| correctly.
container_host()->SendSetController(false /* notify_controllerchange */);
SetExecutionReady();
}
void ServiceWorkerClient::OnVersionAttributesChanged(
ServiceWorkerRegistration* registration,
blink::mojom::ChangedServiceWorkerObjectsMaskPtr changed_mask) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (container_host()) {
container_host()->OnVersionAttributesChanged(registration,
std::move(changed_mask));
}
}
void ServiceWorkerClient::OnRegistrationFailed(
ServiceWorkerRegistration* registration) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
RemoveMatchingRegistration(registration);
}
void ServiceWorkerClient::OnRegistrationFinishedUninstalling(
ServiceWorkerRegistration* registration) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
RemoveMatchingRegistration(registration);
}
void ServiceWorkerClient::OnSkippedWaiting(
ServiceWorkerRegistration* registration) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (controller_registration_ != registration) {
return;
}
#if DCHECK_IS_ON()
DCHECK(controller_);
ServiceWorkerVersion* active = controller_registration_->active_version();
DCHECK(active);
DCHECK_NE(active, controller_.get());
DCHECK_EQ(active->status(), ServiceWorkerVersion::ACTIVATING);
#endif // DCHECK_IS_ON()
if (IsBackForwardCacheEnabled() && IsInBackForwardCache()) {
// This ServiceWorkerContainerHost is evicted from BackForwardCache in
// |ActivateWaitingVersion|, but not deleted yet. This can happen because
// asynchronous eviction and |OnSkippedWaiting| are in the same task.
// The controller does not have to be updated because |this| will be evicted
// from BackForwardCache.
// TODO(yuzus): Wire registration with ServiceWorkerContainerHost so that we
// can check on the caller side.
return;
}
UpdateController(true /* notify_controllerchange */);
}
void ServiceWorkerClient::AddMatchingRegistration(
ServiceWorkerRegistration* registration) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(blink::ServiceWorkerScopeMatches(registration->scope(),
GetUrlForScopeMatch()));
DCHECK(registration->key() == key());
if (!IsEligibleForServiceWorkerController()) {
return;
}
size_t key = registration->scope().spec().size();
if (base::Contains(matching_registrations_, key)) {
return;
}
registration->AddListener(this);
matching_registrations_[key] = registration;
if (container_host()) {
container_host()->ReturnRegistrationForReadyIfNeeded();
}
}
void ServiceWorkerClient::RemoveMatchingRegistration(
ServiceWorkerRegistration* registration) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_NE(controller_registration_, registration);
#if DCHECK_IS_ON()
DCHECK(IsMatchingRegistration(registration));
#endif // DCHECK_IS_ON()
registration->RemoveListener(this);
size_t key = registration->scope().spec().size();
matching_registrations_.erase(key);
}
ServiceWorkerRegistration* ServiceWorkerClient::MatchRegistration() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (const auto& registration : base::Reversed(matching_registrations_)) {
if (registration.second->is_uninstalled()) {
continue;
}
if (registration.second->is_uninstalling()) {
return nullptr;
}
return registration.second.get();
}
return nullptr;
}
void ServiceWorkerClient::AddServiceWorkerToUpdate(
scoped_refptr<ServiceWorkerVersion> version) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// This is only called for windows now, but it should be called for all
// clients someday.
DCHECK(IsContainerForWindowClient());
versions_to_update_.emplace(std::move(version));
}
void ServiceWorkerClient::CountFeature(blink::mojom::WebFeature feature) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// `container_` can be used only if ServiceWorkerContainerInfoForClient has
// been passed to the renderer process. Otherwise, the method call will crash
// inside the mojo library (See crbug.com/40918057).
if (!is_container_ready()) {
buffered_used_features_.insert(feature);
return;
}
// And only when loading finished so the controller is really settled.
if (!is_execution_ready()) {
buffered_used_features_.insert(feature);
return;
}
container_host()->CountFeature(feature);
}
void ServiceWorkerClient::NotifyControllerLost() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (IsBackForwardCacheEnabled() && IsInBackForwardCache()) {
// The controller was unregistered, which usually does not happen while it
// has controllees. Since the document is in the back/forward cache, it does
// not count as a controllee. However, this means if it were to be restored,
// the page would be in an unexpected state, so evict the bfcache entry.
EvictFromBackForwardCache(BackForwardCacheMetrics::NotRestoredReason::
kServiceWorkerUnregistration);
}
SetControllerRegistration(nullptr, true /* notify_controllerchange */);
}
void ServiceWorkerClient::ClaimedByRegistration(
scoped_refptr<ServiceWorkerRegistration> registration) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(registration->active_version());
DCHECK(is_execution_ready());
// TODO(falken): This should just early return, or DCHECK. claim() should have
// no effect on a page that's already using the registration.
if (registration == controller_registration_) {
UpdateController(true /* notify_controllerchange */);
return;
}
SetControllerRegistration(registration, true /* notify_controllerchange */);
}
blink::mojom::ServiceWorkerClientType ServiceWorkerClient::GetClientType()
const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return std::visit(
absl::Overload(
[](GlobalRenderFrameHostId render_frame_host_id) {
return blink::mojom::ServiceWorkerClientType::kWindow;
},
[](blink::DedicatedWorkerToken dedicated_worker_token) {
return blink::mojom::ServiceWorkerClientType::kDedicatedWorker;
},
[](blink::SharedWorkerToken shared_worker_token) {
return blink::mojom::ServiceWorkerClientType::kSharedWorker;
}),
client_info_);
}
bool ServiceWorkerClient::IsContainerForWindowClient() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return std::holds_alternative<GlobalRenderFrameHostId>(client_info_);
}
bool ServiceWorkerClient::IsContainerForWorkerClient() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return std::holds_alternative<blink::DedicatedWorkerToken>(client_info_) ||
std::holds_alternative<blink::SharedWorkerToken>(client_info_);
}
ServiceWorkerClientInfo ServiceWorkerClient::GetServiceWorkerClientInfo()
const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return client_info_;
}
blink::mojom::ServiceWorkerContainerInfoForClientPtr
ServiceWorkerClient::CommitResponse(
base::PassKey<ScopedServiceWorkerClient>,
std::optional<GlobalRenderFrameHostId> rfh_id,
const PolicyContainerPolicies& policy_container_policies,
mojo::PendingRemote<network::mojom::CrossOriginEmbedderPolicyReporter>
coep_reporter,
mojo::PendingRemote<network::mojom::DocumentIsolationPolicyReporter>
dip_reporter,
ukm::SourceId ukm_source_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK_EQ(client_phase_, ClientPhase::kInitial);
if (IsContainerForWindowClient()) {
CHECK(coep_reporter);
CHECK(rfh_id);
ongoing_navigation_frame_tree_node_id_ = FrameTreeNodeId();
client_info_ = *rfh_id;
if (controller_) {
controller_->UpdateForegroundPriority();
}
auto* rfh = RenderFrameHostImpl::FromID(*rfh_id);
// `rfh` may be null in tests (but it should not happen in production).
if (rfh) {
rfh->AddServiceWorkerClient(client_uuid(),
weak_ptr_factory_.GetWeakPtr());
}
}
CHECK(!container_host_);
auto container_info =
blink::mojom::ServiceWorkerContainerInfoForClient::New();
container_host_ = std::make_unique<ServiceWorkerContainerHostForClient>(
base::PassKey<ServiceWorkerClient>(), AsWeakPtr(), container_info,
policy_container_policies, std::move(coep_reporter),
std::move(dip_reporter), std::move(ukm_source_id));
// `network_url_loader_factory_for_prefetch_` is no longer used after commit.
network_url_loader_factory_for_prefetch_.reset();
TransitionToClientPhase(ClientPhase::kResponseCommitted);
return container_info;
}
void ServiceWorkerClient::OnEndNavigationCommit() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsContainerForWindowClient());
DCHECK(!navigation_commit_ended_);
navigation_commit_ended_ = true;
if (controller_) {
controller_->OnControlleeNavigationCommitted(client_uuid_,
GetRenderFrameHostId());
}
}
void ServiceWorkerClient::UpdateUrlsInternal(
const GURL& creation_url,
const std::optional<url::Origin>& top_frame_origin,
const blink::StorageKey& storage_key) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const GURL url = creation_url.is_valid()
? net::SimplifyUrlForRequest(creation_url)
: creation_url;
GURL previous_url = url_;
creation_url_ = creation_url;
// The url_ needs the URL fragment removed, but the creation URL needs to be
// the original URL including the fragment.
url_ = url;
top_frame_origin_ = top_frame_origin;
key_ = storage_key;
service_worker_security_utils::CheckOnUpdateUrls(GetUrlForScopeMatch(), key_);
if (previous_url != url) {
// Revoke the token on URL change since any service worker holding the token
// may no longer be the potential controller of this frame and shouldn't
// have the power to display SSL dialogs for it.
if (IsContainerForWindowClient()) {
fetch_request_window_id_ = base::UnguessableToken::Create();
}
}
// Update client id on cross origin redirects. This corresponds to the HTML
// standard's "process a navigation fetch" algorithm's step for discarding
// |reservedEnvironment|.
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#process-a-navigate-fetch
// "If |reservedEnvironment| is not null and |currentURL|'s origin is not the
// same as |reservedEnvironment|'s creation URL's origin, then:
// 1. Run the environment discarding steps for |reservedEnvironment|.
// 2. Set |reservedEnvironment| to null."
if (previous_url.is_valid() && !url::IsSameOriginWith(previous_url, url)) {
// Remove old controller since we know the controller is definitely
// changed. We need to remove |this| from |controller_|'s controllee before
// updating UUID since ServiceWorkerVersion has a map from uuid to provider
// hosts.
SetControllerRegistration(nullptr, false /* notify_controllerchange */);
// Set UUID to the new one.
std::string previous_client_uuid = client_uuid_;
client_uuid_ = base::Uuid::GenerateRandomV4().AsLowercaseString();
owner_->UpdateServiceWorkerClientClientID(previous_client_uuid,
client_uuid_);
}
SyncMatchingRegistrations();
}
namespace {
// Attempt to get the storage key from |RenderFrameHostImpl|. This correctly
// accounts for extension URLs. The absence of this logic was a potential cause
// for https://crbug.com/1346450.
std::optional<blink::StorageKey> GetStorageKeyFromRenderFrameHost(
FrameTreeNodeId frame_tree_node_id,
const url::Origin& origin,
const base::UnguessableToken* nonce) {
FrameTreeNode* frame_tree_node =
FrameTreeNode::GloballyFindByID(frame_tree_node_id);
if (!frame_tree_node) {
return std::nullopt;
}
RenderFrameHostImpl* frame_host = frame_tree_node->current_frame_host();
if (!frame_host) {
return std::nullopt;
}
return frame_host->CalculateStorageKey(origin, nonce);
}
// For dedicated/shared worker cases, if a storage key is returned, it will have
// its origin replaced by |origin|. This would mean that the origin of the
// WorkerHost and the origin as used by the service worker code don't match,
// however in cases where these wouldn't match the load will be aborted later
// anyway.
std::optional<blink::StorageKey> GetStorageKeyFromDedicatedWorkerHost(
content::StoragePartition* storage_partition,
blink::DedicatedWorkerToken dedicated_worker_token,
const url::Origin& origin) {
auto* worker_service = static_cast<DedicatedWorkerServiceImpl*>(
storage_partition->GetDedicatedWorkerService());
auto* worker_host =
worker_service->GetDedicatedWorkerHostFromToken(dedicated_worker_token);
if (worker_host) {
return worker_host->GetStorageKey().WithOrigin(origin);
}
return std::nullopt;
}
std::optional<blink::StorageKey> GetStorageKeyFromSharedWorkerHost(
content::StoragePartition* storage_partition,
blink::SharedWorkerToken shared_worker_token,
const url::Origin& origin) {
auto* worker_service = static_cast<SharedWorkerServiceImpl*>(
storage_partition->GetSharedWorkerService());
auto* worker_host =
worker_service->GetSharedWorkerHostFromToken(shared_worker_token);
if (worker_host) {
return worker_host->GetStorageKey().WithOrigin(origin);
}
return std::nullopt;
}
} // namespace
blink::StorageKey ServiceWorkerClient::CalculateStorageKeyForUpdateUrls(
const GURL& url,
const net::IsolationInfo& isolation_info_from_handle) const {
CHECK(!is_response_committed());
const url::Origin origin = url::Origin::Create(url);
const std::optional<blink::StorageKey> storage_key = std::visit(
absl::Overload(
[&](GlobalRenderFrameHostId render_frame_host_id) {
if (is_initiated_by_prefetch_) {
// Falls back to the `CreateFromOriginAndIsolationInfo()` case
// below.
// Navigation isn't served by prefetch if the key for prefetch
// calculated here is wrong/mismatching, checked at
// `PrefetchURLLoaderInterceptor::OnGetPrefetchComplete()`.
// https://crbug.com/413207408.
return std::optional<blink::StorageKey>(std::nullopt);
}
// We use `ongoing_navigation_frame_tree_node_id_` instead of
// `render_frame_host_id` because this method is called before
// response commit.
return GetStorageKeyFromRenderFrameHost(
ongoing_navigation_frame_tree_node_id_, origin,
base::OptionalToPtr(isolation_info_from_handle.nonce()));
},
[&](blink::DedicatedWorkerToken dedicated_worker_token) {
auto* process = RenderProcessHost::FromID(GetProcessId());
return process ? GetStorageKeyFromDedicatedWorkerHost(
process->GetStoragePartition(),
dedicated_worker_token, origin)
: std::nullopt;
},
[&](blink::SharedWorkerToken shared_worker_token) {
auto* process = RenderProcessHost::FromID(GetProcessId());
return process ? GetStorageKeyFromSharedWorkerHost(
process->GetStoragePartition(),
shared_worker_token, origin)
: std::nullopt;
}),
client_info_);
if (storage_key) {
return *storage_key;
}
// If we're in this case then we couldn't get the StorageKey from the RFH,
// which means we also can't get the storage partitioning status from
// RuntimeFeatureState(Read)Context. Using
// CreateFromOriginAndIsolationInfo() will create a key based on
// net::features::kThirdPartyStoragePartitioning state.
return blink::StorageKey::CreateFromOriginAndIsolationInfo(
origin, isolation_info_from_handle);
}
void ServiceWorkerClient::UpdateUrls(
const GURL& creation_url,
const std::optional<url::Origin>& top_frame_origin,
const blink::StorageKey& storage_key) {
CHECK(!is_response_committed());
UpdateUrlsInternal(creation_url, top_frame_origin, storage_key);
}
void ServiceWorkerClient::UpdateUrlsAfterCommitResponseForTesting(
const GURL& url,
const std::optional<url::Origin>& top_frame_origin,
const blink::StorageKey& storage_key) {
CHECK(is_response_committed());
UpdateUrlsInternal(url, top_frame_origin, storage_key);
}
void ServiceWorkerClient::SetControllerRegistration(
scoped_refptr<ServiceWorkerRegistration> controller_registration,
bool notify_controllerchange) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (controller_registration) {
CHECK(IsEligibleForServiceWorkerController());
DCHECK(controller_registration->active_version());
#if DCHECK_IS_ON()
DCHECK(IsMatchingRegistration(controller_registration.get()));
#endif // DCHECK_IS_ON()
}
controller_registration_ = controller_registration;
UpdateController(notify_controllerchange);
}
bool ServiceWorkerClient::IsEligibleForServiceWorkerController() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!url_.is_valid()) {
return false;
}
// Pass GetUrlForScopeMatch() instead of `url_` because we cannot take the
// origin of `url_` when it's a blob URL (see https://crbug.com/1144717). It's
// guaranteed that the URL returned by GetURLForScopeMatch() has the same
// logical origin as `url_`.
// TODO(asamidoi): Add url::Origin member for ServiceWorkerContainerHost and
// use it as the argument of OriginCanAccessServiceWorkers().
if (!OriginCanAccessServiceWorkers(GetUrlForScopeMatch())) {
return false;
}
if (is_parent_frame_secure_) {
return true;
}
std::set<std::string> schemes;
GetContentClient()->browser()->GetSchemesBypassingSecureContextCheckAllowlist(
&schemes);
return schemes.find(url_.scheme()) != schemes.end();
}
bool ServiceWorkerClient::is_response_committed() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
switch (client_phase_) {
case ClientPhase::kInitial:
case ClientPhase::kResponseNotCommitted:
return false;
case ClientPhase::kResponseCommitted:
case ClientPhase::kContainerReady:
case ClientPhase::kExecutionReady:
return true;
}
}
bool ServiceWorkerClient::is_container_ready() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
switch (client_phase_) {
case ClientPhase::kInitial:
case ClientPhase::kResponseCommitted:
case ClientPhase::kResponseNotCommitted:
return false;
case ClientPhase::kContainerReady:
case ClientPhase::kExecutionReady:
return true;
}
}
void ServiceWorkerClient::AddExecutionReadyCallback(
ExecutionReadyCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!is_execution_ready());
execution_ready_callbacks_.push_back(std::move(callback));
}
bool ServiceWorkerClient::is_execution_ready() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
switch (client_phase_) {
case ClientPhase::kInitial:
case ClientPhase::kResponseCommitted:
case ClientPhase::kResponseNotCommitted:
case ClientPhase::kContainerReady:
return false;
case ClientPhase::kExecutionReady:
return true;
}
}
GlobalRenderFrameHostId ServiceWorkerClient::GetRenderFrameHostId() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsContainerForWindowClient());
return std::get<GlobalRenderFrameHostId>(client_info_);
}
int ServiceWorkerClient::GetProcessId() const {
if (IsContainerForWindowClient()) {
return GetRenderFrameHostId().child_id;
}
DCHECK(IsContainerForWorkerClient());
return process_id_for_worker_client_;
}
NavigationRequest* ServiceWorkerClient::GetOngoingNavigationRequestBeforeCommit(
base::PassKey<StoragePartitionImpl>) const {
DCHECK(IsContainerForWindowClient());
DCHECK(!GetRenderFrameHostId());
// For Window clients for prefetch,
// `GetOngoingNavigationRequestBeforeCommit()` isn't called at all, because
// prefetching requests don't set `URLLoaderNetworkServiceObserver`.
CHECK(!is_initiated_by_prefetch_);
// It is safe to use `ongoing_navigation_frame_tree_node_id_` to obtain the
// corresponding navigation request without being concerned about the case
// that a new navigation had started and the old navigation had been deleted,
// because the owner of this instance will reset the key that can be used to
// retrieve this instance, which makes the old key stale and cannot locate
// this instance. This mechanism guarantees that this instance would always be
// associated with the latest navigation.
// However, this design requires callers to carefully get the
// `ServiceWorkerContainerHost` instance from scratch instead of using a
// stored one, and it would be better to optimize the design when possible.
FrameTreeNode* frame_tree_node =
FrameTreeNode::GloballyFindByID(ongoing_navigation_frame_tree_node_id_);
return frame_tree_node ? frame_tree_node->navigation_request() : nullptr;
}
std::string ServiceWorkerClient::GetFrameTreeNodeTypeStringBeforeCommit()
const {
CHECK(!is_response_committed());
// TODO(https://crbug.com/40947546): If needed, assign a proper metrics name
// for clients for prefetch where `ongoing_navigation_frame_tree_node_id` is
// null.
if (FrameTreeNode* frame_tree_node = FrameTreeNode::GloballyFindByID(
ongoing_navigation_frame_tree_node_id_)) {
CHECK(IsContainerForWindowClient());
return frame_tree_node->IsOutermostMainFrame() ? "OutermostMainFrame"
: "NotOutermostMainFrame";
}
return "Unknown";
}
const std::string& ServiceWorkerClient::client_uuid() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return client_uuid_;
}
std::string ServiceWorkerClient::client_uuid_for_resulting_client_id() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (is_initiated_by_prefetch_) {
return "";
}
return client_uuid_;
}
ServiceWorkerVersion* ServiceWorkerClient::controller() const {
#if DCHECK_IS_ON()
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CheckControllerConsistency(false);
#endif // DCHECK_IS_ON()
return controller_.get();
}
ServiceWorkerRegistration* ServiceWorkerClient::controller_registration()
const {
#if DCHECK_IS_ON()
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CheckControllerConsistency(false);
#endif // DCHECK_IS_ON()
return controller_registration_.get();
}
bool ServiceWorkerClient::IsInBackForwardCache() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return is_in_back_forward_cache_;
}
void ServiceWorkerClient::EvictFromBackForwardCache(
BackForwardCacheMetrics::NotRestoredReason reason) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsBackForwardCacheEnabled());
is_in_back_forward_cache_ = false;
if (!IsContainerForWindowClient()) {
return;
}
auto* rfh = RenderFrameHostImpl::FromID(GetRenderFrameHostId());
// |rfh| could be evicted before this function is called.
if (rfh && rfh->IsInBackForwardCache()) {
rfh->EvictFromBackForwardCacheWithReason(reason);
}
}
void ServiceWorkerClient::OnEnterBackForwardCache() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsBackForwardCacheEnabled());
if (controller_) {
// TODO(crbug.com/330928087): remove check when this issue resolved.
SCOPED_CRASH_KEY_NUMBER("SWC_OnEBFC", "client_type",
static_cast<int32_t>(GetClientType()));
SCOPED_CRASH_KEY_BOOL("SWC_OnEBFC", "is_execution_ready",
is_execution_ready());
SCOPED_CRASH_KEY_BOOL("SWC_OnEBFC", "is_blob_or_about_url",
url() != GetUrlForScopeMatch());
SCOPED_CRASH_KEY_BOOL("SWC_OnEBFC", "is_inherited", is_inherited());
CHECK(!controller_->BFCacheContainsControllee(client_uuid()));
controller_->MoveControlleeToBackForwardCacheMap(client_uuid());
}
is_in_back_forward_cache_ = true;
}
void ServiceWorkerClient::OnRestoreFromBackForwardCache() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsBackForwardCacheEnabled());
// TODO(crbug.com/330928087): remove check when this issue resolved.
SCOPED_CRASH_KEY_BOOL("SWC_OnRFBFC", "is_in_bfcache",
is_in_back_forward_cache_);
SCOPED_CRASH_KEY_BOOL("SWC_OnRFBFC", "is_blob_or_about_url",
url() != GetUrlForScopeMatch());
SCOPED_CRASH_KEY_BOOL("SWC_OnRFBFC", "is_inherited", is_inherited());
if (controller_) {
controller_->RestoreControlleeFromBackForwardCacheMap(client_uuid());
}
is_in_back_forward_cache_ = false;
}
void ServiceWorkerClient::SyncMatchingRegistrations() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!controller_registration_);
RemoveAllMatchingRegistrations();
if (!context_) {
return;
}
const auto& registrations = context_->GetLiveRegistrations();
for (const auto& key_registration : registrations) {
ServiceWorkerRegistration* registration = key_registration.second;
if (!registration->is_uninstalled() && registration->key() == key() &&
blink::ServiceWorkerScopeMatches(registration->scope(),
GetUrlForScopeMatch())) {
AddMatchingRegistration(registration);
}
}
}
#if DCHECK_IS_ON()
bool ServiceWorkerClient::IsMatchingRegistration(
ServiceWorkerRegistration* registration) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::string spec = registration->scope().spec();
size_t key = spec.size();
auto iter = matching_registrations_.find(key);
if (iter == matching_registrations_.end()) {
return false;
}
if (iter->second.get() != registration) {
return false;
}
return true;
}
#endif // DCHECK_IS_ON()
void ServiceWorkerClient::RemoveAllMatchingRegistrations() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!controller_registration_);
for (const auto& it : matching_registrations_) {
ServiceWorkerRegistration* registration = it.second.get();
registration->RemoveListener(this);
}
matching_registrations_.clear();
}
void ServiceWorkerClient::SetExecutionReady() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
switch (client_phase_) {
case ClientPhase::kInitial:
// When `CommitResponse()` is not yet called, it should be skipped because
// the service worker client initialization failed somewhere, and thus
// ignore the `SetExecutionReady()` call here and transition to
// `kResponseNotCommitted` to confirm that no further transitions are
// attempted. See also the comment at `kResponseNotCommitted` in the
// header file.
TransitionToClientPhase(ClientPhase::kResponseNotCommitted);
break;
case ClientPhase::kContainerReady:
// Successful case.
TransitionToClientPhase(ClientPhase::kExecutionReady);
RunExecutionReadyCallbacks();
if (context_) {
context_->NotifyClientIsExecutionReady(*this);
}
FlushFeatures();
break;
case ClientPhase::kResponseCommitted:
case ClientPhase::kExecutionReady:
case ClientPhase::kResponseNotCommitted:
// Invalid state transition.
NOTREACHED()
<< "ServiceWorkerClient::SetExecutionReady() called on ClientPhase "
<< static_cast<int>(client_phase_);
}
}
void ServiceWorkerClient::RunExecutionReadyCallbacks() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::vector<ExecutionReadyCallback> callbacks;
execution_ready_callbacks_.swap(callbacks);
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&RunCallbacks, std::move(callbacks)));
}
void ServiceWorkerClient::TransitionToClientPhase(ClientPhase new_phase) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (client_phase_ == new_phase) {
return;
}
switch (client_phase_) {
case ClientPhase::kInitial:
CHECK(new_phase == ClientPhase::kResponseCommitted ||
new_phase == ClientPhase::kResponseNotCommitted);
break;
case ClientPhase::kResponseCommitted:
CHECK_EQ(new_phase, ClientPhase::kContainerReady);
break;
case ClientPhase::kContainerReady:
CHECK_EQ(new_phase, ClientPhase::kExecutionReady);
break;
case ClientPhase::kExecutionReady:
case ClientPhase::kResponseNotCommitted:
NOTREACHED() << "Invalid transition from "
<< static_cast<int>(client_phase_);
}
client_phase_ = new_phase;
}
void ServiceWorkerClient::UpdateController(bool notify_controllerchange) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
ServiceWorkerVersion* version =
controller_registration_ ? controller_registration_->active_version()
: nullptr;
CHECK(!version || IsEligibleForServiceWorkerController());
if (version == controller_.get()) {
return;
}
scoped_refptr<ServiceWorkerVersion> previous_version = controller_;
controller_ = version;
if (version) {
// TODO(crbug.com/330928087): remove check when this issue resolved.
SCOPED_CRASH_KEY_NUMBER("SWV_RCFBCM", "client_type",
static_cast<int32_t>(GetClientType()));
SCOPED_CRASH_KEY_BOOL("SWV_RCFBCM", "is_execution_ready",
is_execution_ready());
SCOPED_CRASH_KEY_BOOL("SWV_RCFBCM", "is_blob_or_about_url",
url() != GetUrlForScopeMatch());
SCOPED_CRASH_KEY_BOOL("SWV_RCFBCM", "is_inherited", is_inherited());
CHECK(!version->BFCacheContainsControllee(client_uuid()));
version->AddControllee(this);
if (IsBackForwardCacheEnabled() && IsInBackForwardCache()) {
// |this| was not |version|'s controllee when |OnEnterBackForwardCache|
// was called.
version->MoveControlleeToBackForwardCacheMap(client_uuid());
}
if (running_status_observer_) {
version->AddObserver(running_status_observer_.get());
running_status_observer_->Notify(version->running_status());
}
}
if (previous_version) {
previous_version->Uncontrol(client_uuid());
if (running_status_observer_) {
previous_version->RemoveObserver(running_status_observer_.get());
}
}
// No need to `SetController` if the container is not ready because
// when the container gets ready, `ControllerServiceWorkerInfoPtr` is also
// sent in the same IPC call. Moreover, it is harmful to resend the past
// SetController to the renderer because it moves the controller in the
// renderer to the past one.
if (!is_container_ready()) {
return;
}
if (!is_execution_ready()) {
return;
}
container_host()->SendSetController(notify_controllerchange);
}
#if DCHECK_IS_ON()
void ServiceWorkerClient::CheckControllerConsistency(bool should_crash) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!controller_) {
DCHECK(!controller_registration_);
return;
}
DCHECK(controller_registration_);
DCHECK_EQ(controller_->registration_id(), controller_registration_->id());
switch (controller_->status()) {
case ServiceWorkerVersion::NEW:
case ServiceWorkerVersion::INSTALLING:
case ServiceWorkerVersion::INSTALLED:
if (should_crash) {
ServiceWorkerVersion::Status status = controller_->status();
base::debug::Alias(&status);
NOTREACHED() << "Controller service worker has a bad status: "
<< ServiceWorkerVersion::VersionStatusToString(status);
}
break;
case ServiceWorkerVersion::REDUNDANT: {
if (should_crash) {
NOTREACHED();
}
break;
}
case ServiceWorkerVersion::ACTIVATING:
case ServiceWorkerVersion::ACTIVATED:
// Valid status, controller is being activated.
break;
}
}
#endif // DCHECK_IS_ON()
const GURL& ServiceWorkerClient::GetUrlForScopeMatch() const {
if (!scope_match_url_for_client_.is_empty()) {
return scope_match_url_for_client_;
}
return url_;
}
void ServiceWorkerClient::InheritControllerFrom(
ServiceWorkerClient& creator_host,
const GURL& client_url) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(GetClientType() ==
blink::mojom::ServiceWorkerClientType::kDedicatedWorker ||
(base::FeatureList::IsEnabled(kSharedWorkerBlobURLFix) &&
GetClientType() ==
blink::mojom::ServiceWorkerClientType::kSharedWorker) ||
(base::FeatureList::IsEnabled(features::kServiceWorkerSrcdocSupport) &&
GetClientType() == blink::mojom::ServiceWorkerClientType::kWindow &&
client_url.IsAboutSrcdoc()));
// Only expect srcdoc url or blob url.
DCHECK(client_url.SchemeIsBlob() ||
(base::FeatureList::IsEnabled(features::kServiceWorkerSrcdocSupport) &&
client_url.IsAboutSrcdoc()));
// origins of blob client_url and creator host should be the same or both
// opaque.
if (client_url.SchemeIsBlob()) {
service_worker_security_utils::CheckOnUpdateUrls(client_url,
creator_host.key());
}
// Let `scope_match_url_for_client_` be the creator's url for scope match
// because a client should be handled by the service worker of its creator.
// Update it before UpdateUrls so that CheckOnUpdateUrls inside UpdateUrls
// checks with the updated GetUrlForScopeMatch().
scope_match_url_for_client_ = creator_host.GetUrlForScopeMatch();
UpdateUrls(client_url, creator_host.top_frame_origin(), creator_host.key());
// Inherit the controller of the creator.
if (creator_host.controller_registration()) {
AddMatchingRegistration(creator_host.controller_registration());
// If the creator is in back forward cache, the client should also
// be in back forward cache. Otherwise, CHECK fail during restoring from
// back forward cache.
is_in_back_forward_cache_ = creator_host.is_in_back_forward_cache();
// TODO(crbug.com/341322515): remove this CHECK.
// This CHECK is to ensure this path does not cause the crash at
// ServiceWorkerVersion::RemoveControlleeFromBackForwardCacheMap().
CHECK(creator_host.controller_registration()->active_version());
SetControllerRegistration(creator_host.controller_registration(),
false /* notify_controllerchange */);
}
creator_host.SetInherited();
}
mojo::PendingReceiver<blink::mojom::ServiceWorkerRunningStatusCallback>
ServiceWorkerClient::GetRunningStatusCallbackReceiver() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(controller_);
if (!running_status_observer_) {
running_status_observer_ =
absl::make_unique<ServiceWorkerRunningStatusObserver>();
controller_->AddObserver(running_status_observer_.get());
}
mojo::PendingRemote<blink::mojom::ServiceWorkerRunningStatusCallback>
remote_callback;
auto receiver = remote_callback.InitWithNewPipeAndPassReceiver();
running_status_observer_->AddCallback(std::move(remote_callback));
return receiver;
}
void ServiceWorkerClient::SetContainerReady() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
TransitionToClientPhase(ClientPhase::kContainerReady);
CHECK(container_host()->IsContainerRemoteConnected());
FlushFeatures();
}
void ServiceWorkerClient::FlushFeatures() {
std::set<blink::mojom::WebFeature> features;
features.swap(buffered_used_features_);
for (const auto& feature : features) {
CountFeature(feature);
}
}
void ServiceWorkerClient::SetNetworkURLLoaderFactoryForTesting(
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory) {
CHECK_IS_TEST();
network_url_loader_factory_override_for_testing_ = url_loader_factory;
}
scoped_refptr<network::SharedURLLoaderFactory>
ServiceWorkerClient::CreateNetworkURLLoaderFactory(
CreateNetworkURLLoaderFactoryType type,
StoragePartitionImpl* storage_partition,
const network::ResourceRequest& resource_request) {
CHECK(!is_response_committed());
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (network_url_loader_factory_override_for_testing_) {
CHECK_IS_TEST();
return network_url_loader_factory_override_for_testing_;
}
if (is_initiated_by_prefetch_) {
// We skip `WillCreateURLLoaderFactory` below, because it is already
// included in `network_url_loader_factory_for_prefetch_` (see
// `PrefetchNetworkContext::CreateNewURLLoaderFactory()`).
// We also skip `CreateURLLoaderHandlerForServiceWorkerNavigationPreload`,
// because this is a prefetch request and don't have to consult with search
// prefetch cache via
// `CreateURLLoaderHandlerForServiceWorkerNavigationPreload`.
return network_url_loader_factory_for_prefetch_;
}
switch (type) {
case CreateNetworkURLLoaderFactoryType::kNavigationPreload:
// Allow the embedder to intercept the URLLoader request if necessary.
// This must be a synchronous decision by the embedder. In the future, we
// may wish to support asynchronous decisions using
// |URLLoaderRequestInterceptor| in the same fashion that they are used
// for navigation requests.
if (ContentBrowserClient::URLLoaderRequestHandler
embedder_url_loader_handler =
GetContentClient()
->browser()
->CreateURLLoaderHandlerForServiceWorkerNavigationPreload(
ongoing_navigation_frame_tree_node_id_,
resource_request)) {
return base::MakeRefCounted<network::SingleRequestURLLoaderFactory>(
std::move(embedder_url_loader_handler));
}
break;
case CreateNetworkURLLoaderFactoryType::kRaceNetworkRequest:
case CreateNetworkURLLoaderFactoryType::kSyntheticNetworkRequest:
break;
}
// For worker clients, `ongoing_navigation_frame_tree_node_id_` is null.
// TODO(falken): Can `navigation_request` check be a DCHECK now that the
// caller does not post a task to this function?
auto* frame_tree_node =
FrameTreeNode::GloballyFindByID(ongoing_navigation_frame_tree_node_id_);
if (!frame_tree_node || !storage_partition ||
!frame_tree_node->navigation_request()) {
// The navigation was cancelled. Just drop the request. Otherwise, we might
// go to network without consulting the embedder first, which would break
// guarantees.
//
// TODO(https://crbug.com/40947546): Clients for prefetch (where
// `ongoing_navigation_frame_tree_node_id` is null) also fall into this case
// and thus don't support navigationPreload and race network requests. Fix
// this.
mojo::PendingRemote<network::mojom::URLLoaderFactory> network_factory;
return base::MakeRefCounted<network::WrapperSharedURLLoaderFactory>(
std::move(network_factory));
}
// We ignore the value of |bypass_redirect_checks_unused| since a redirect is
// just relayed to the service worker where preloadResponse is resolved as
// redirect.
bool bypass_redirect_checks_unused;
// Consult the embedder.
mojo::PendingRemote<network::mojom::TrustedURLLoaderHeaderClient>
header_client;
network::URLLoaderFactoryBuilder factory_builder;
// Here we give nullptr for |factory_override|, because CORS is no-op
// for navigations.
GetContentClient()->browser()->WillCreateURLLoaderFactory(
storage_partition->browser_context(),
frame_tree_node->current_frame_host(),
frame_tree_node->current_frame_host()->GetProcess()->GetDeprecatedID(),
ContentBrowserClient::URLLoaderFactoryType::kNavigation, url::Origin(),
net::IsolationInfo(),
frame_tree_node->navigation_request()->GetNavigationId(),
ukm::SourceIdObj::FromInt64(
frame_tree_node->navigation_request()->GetNextPageUkmSourceId()),
factory_builder, &header_client, &bypass_redirect_checks_unused,
/*disable_secure_dns=*/nullptr, /*factory_override=*/nullptr,
GetUIThreadTaskRunner({BrowserTaskType::kNavigationNetworkResponse}));
// Make the network factory.
return base::MakeRefCounted<network::WrapperSharedURLLoaderFactory>(
NavigationURLLoaderImpl::CreateURLLoaderFactoryWithHeaderClient(
std::move(header_client), std::move(factory_builder),
storage_partition,
// TODO(crbug.com/390003764): Consider whether/how to apply devtools
// cookies setting overrides for a service worker.
/*devtools_cookie_overrides=*/std::nullopt,
/*cookie_overrides=*/std::nullopt));
}
// If a blob URL is used for a SharedWorker script's URL, a controller will be
// inherited.
BASE_FEATURE(SharedWorkerBlobURLFix,
base::FEATURE_ENABLED_BY_DEFAULT);
} // namespace content