[go: nahoru, domu]

blob: 485bf3fcf0b810c1056367d82d82a501f68e4824 [file] [log] [blame]
// Copyright 2017 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/renderer/service_worker/service_worker_subresource_loader.h"
#include <optional>
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/notreached.h"
#include "base/strings/strcat.h"
#include "base/task/sequenced_task_runner.h"
#include "base/trace_event/trace_event.h"
#include "content/common/fetch/fetch_request_type_converters.h"
#include "content/common/service_worker/race_network_request_url_loader_client.h"
#include "content/common/service_worker/service_worker_router_evaluator.h"
#include "content/public/common/content_features.h"
#include "content/renderer/service_worker/controller_service_worker_connector.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "net/base/net_errors.h"
#include "net/url_request/redirect_util.h"
#include "net/url_request/url_request.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/record_ontransfersizeupdate_utils.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/mojom/early_hints.mojom.h"
#include "services/network/public/mojom/service_worker_router_info.mojom.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "third_party/blink/public/common/blob/blob_utils.h"
#include "third_party/blink/public/common/service_worker/service_worker_loader_helpers.h"
#include "third_party/blink/public/common/service_worker/service_worker_type_converters.h"
#include "third_party/blink/public/mojom/blob/blob.mojom.h"
#include "third_party/blink/public/mojom/service_worker/dispatch_fetch_event_params.mojom.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_fetch_handler_bypass_option.mojom-shared.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_stream_handle.mojom.h"
namespace content {
namespace {
constexpr char kServiceWorkerSubresourceLoaderScope[] =
"ServiceWorkerSubresourceLoader";
template <typename T>
static std::string MojoEnumToString(T mojo_enum) {
std::ostringstream oss;
oss << mojo_enum;
return oss.str();
}
network::mojom::URLResponseHeadPtr RewriteResponseHead(
base::TimeTicks service_worker_start_time,
base::TimeTicks service_worker_ready_time,
std::optional<network::mojom::ServiceWorkerRouterInfo>
service_worker_router_info,
network::mojom::URLResponseHeadPtr response_head) {
response_head->load_timing.service_worker_start_time =
service_worker_start_time;
response_head->load_timing.service_worker_ready_time =
service_worker_ready_time;
if (service_worker_router_info) {
response_head->service_worker_router_info =
network::mojom::ServiceWorkerRouterInfo::New(
*std::move(service_worker_router_info));
}
return response_head;
}
// A wrapper URLLoaderClient that invokes the given RewriteHeaderCallback
// whenever a response or redirect is received.
class HeaderRewritingURLLoaderClient : public network::mojom::URLLoaderClient {
public:
using RewriteHeaderCallback =
base::RepeatingCallback<network::mojom::URLResponseHeadPtr(
network::mojom::URLResponseHeadPtr)>;
HeaderRewritingURLLoaderClient(
mojo::Remote<network::mojom::URLLoaderClient> url_loader_client,
RewriteHeaderCallback rewrite_header_callback)
: url_loader_client_(std::move(url_loader_client)),
rewrite_header_callback_(rewrite_header_callback) {}
~HeaderRewritingURLLoaderClient() override {}
private:
// network::mojom::URLLoaderClient implementation:
void OnReceiveEarlyHints(network::mojom::EarlyHintsPtr early_hints) override {
DCHECK(url_loader_client_.is_bound());
url_loader_client_->OnReceiveEarlyHints(std::move(early_hints));
}
void OnReceiveResponse(
network::mojom::URLResponseHeadPtr response_head,
mojo::ScopedDataPipeConsumerHandle body,
std::optional<mojo_base::BigBuffer> cached_metadata) override {
DCHECK(url_loader_client_.is_bound());
url_loader_client_->OnReceiveResponse(
rewrite_header_callback_.Run(std::move(response_head)), std::move(body),
std::move(cached_metadata));
}
void OnReceiveRedirect(
const net::RedirectInfo& redirect_info,
network::mojom::URLResponseHeadPtr response_head) override {
DCHECK(url_loader_client_.is_bound());
url_loader_client_->OnReceiveRedirect(
redirect_info, rewrite_header_callback_.Run(std::move(response_head)));
}
void OnUploadProgress(int64_t current_position,
int64_t total_size,
OnUploadProgressCallback ack_callback) override {
DCHECK(url_loader_client_.is_bound());
url_loader_client_->OnUploadProgress(current_position, total_size,
std::move(ack_callback));
}
void OnTransferSizeUpdated(int32_t transfer_size_diff) override {
DCHECK(url_loader_client_.is_bound());
network::RecordOnTransferSizeUpdatedUMA(
network::OnTransferSizeUpdatedFrom::kHeaderRewritingURLLoaderClient);
url_loader_client_->OnTransferSizeUpdated(transfer_size_diff);
}
void OnComplete(const network::URLLoaderCompletionStatus& status) override {
DCHECK(url_loader_client_.is_bound());
url_loader_client_->OnComplete(status);
}
mojo::Remote<network::mojom::URLLoaderClient> url_loader_client_;
RewriteHeaderCallback rewrite_header_callback_;
};
void RestoreRequestBody(network::ResourceRequestBody* body,
network::DataElementChunkedDataPipe original_body) {
// A non-null request body should be attached only when the request
// had a streaming body. That means `body` should be non-null, and consist
// of only one kChunkedDataPipe element.
DCHECK(body);
auto& elements = *body->elements_mutable();
DCHECK_EQ(elements.size(), 1u);
DCHECK_EQ(elements[0].type(), network::DataElement::Tag::kChunkedDataPipe);
elements[0] = network::DataElement(std::move(original_body));
}
// As a workaround for the future timestamp set by the sender, we adjust the
// time if it happens for a machine without a timer consistent across
// processes. (See crbug.com/1342408)
blink::mojom::ServiceWorkerFetchEventTimingPtr AdjustTimingIfNeededCrBug1342408(
blink::mojom::ServiceWorkerFetchEventTimingPtr timing) {
base::TimeTicks now = base::TimeTicks::Now();
const char* kMetricsName = "ServiceWorker.WorkaroundForCrBug1342408Applied";
if (base::TimeTicks::IsConsistentAcrossProcesses() ||
timing->respond_with_settled_time <= now) {
base::UmaHistogramBoolean(kMetricsName, false);
return timing;
}
auto diff = timing->respond_with_settled_time - now;
timing->dispatch_event_time -= diff;
timing->respond_with_settled_time -= diff;
base::UmaHistogramBoolean(kMetricsName, true);
return timing;
}
} // namespace
// A ServiceWorkerStreamCallback implementation which waits for completion of
// a stream response for subresource loading. It calls
// ServiceWorkerSubresourceLoader::CommitCompleted() upon completion of the
// response.
class ServiceWorkerSubresourceLoader::StreamWaiter
: public blink::mojom::ServiceWorkerStreamCallback {
public:
StreamWaiter(
ServiceWorkerSubresourceLoader* owner,
mojo::PendingReceiver<blink::mojom::ServiceWorkerStreamCallback> receiver)
: owner_(owner), receiver_(this, std::move(receiver)) {
DCHECK(owner_);
receiver_.set_disconnect_handler(
base::BindOnce(&StreamWaiter::OnAborted, base::Unretained(this)));
}
StreamWaiter(const StreamWaiter&) = delete;
StreamWaiter& operator=(const StreamWaiter&) = delete;
// mojom::ServiceWorkerStreamCallback implementations:
void OnCompleted() override { owner_->OnBodyReadingComplete(net::OK); }
void OnAborted() override { owner_->OnBodyReadingComplete(net::ERR_ABORTED); }
private:
raw_ptr<ServiceWorkerSubresourceLoader, ExperimentalRenderer> owner_;
mojo::Receiver<blink::mojom::ServiceWorkerStreamCallback> receiver_;
};
bool ServiceWorkerSubresourceLoader::MaybeStartAutoPreload() {
if (controller_connector_->fetch_handler_bypass_option() !=
blink::mojom::ServiceWorkerFetchHandlerBypassOption::kAutoPreload) {
return false;
}
return ServiceWorkerSubresourceLoader::StartRaceNetworkRequest();
}
bool ServiceWorkerSubresourceLoader::MaybeStartRaceNetworkRequest() {
if (controller_connector_->fetch_handler_bypass_option() !=
blink::mojom::ServiceWorkerFetchHandlerBypassOption::
kRaceNetworkRequest) {
return false;
}
return ServiceWorkerSubresourceLoader::StartRaceNetworkRequest();
}
bool ServiceWorkerSubresourceLoader::StartRaceNetworkRequest() {
// If the fetch event is restarted for some reason, stop dispatching
// RaceNetworkRequest to avoid making the race condition complex.
if (fetch_request_restarted_) {
return false;
}
// RaceNetworkRequest only supports GET method.
if (resource_request_.method != net::HttpRequestHeaders::kGetMethod) {
return false;
}
// RaceNetworkRequest is triggered only if the scheme is HTTP or HTTPS.
// crbug.com/1477990
if (!resource_request_.url.SchemeIsHTTPOrHTTPS()) {
return false;
}
// Create URLLoader related assets to handle the request triggered by
// RaceNetworkRequset.
mojo::PendingRemote<network::mojom::URLLoaderClient> forwarding_client;
forwarded_race_network_request_url_loader_factory_.emplace(
forwarding_client.InitWithNewPipeAndPassReceiver(),
network::SharedURLLoaderFactory::Create(fallback_factory_->Clone()));
DCHECK(!race_network_request_loader_client_);
race_network_request_loader_client_.emplace(resource_request_,
weak_factory_.GetWeakPtr(),
std::move(forwarding_client));
// If the initial state is not kWaitForBody, that means creating data pipes
// failed. Do not start RaceNetworkRequest this case.
switch (race_network_request_loader_client_->state()) {
case ServiceWorkerRaceNetworkRequestURLLoaderClient::State::kWaitForBody:
break;
default:
return false;
}
mojo::PendingRemote<network::mojom::URLLoaderFactory> remote_factory;
forwarded_race_network_request_url_loader_factory_->Clone(
remote_factory.InitWithNewPipeAndPassReceiver());
mojo::PendingRemote<network::mojom::URLLoaderClient> client_to_pass;
race_network_request_loader_client_->Bind(&client_to_pass);
scoped_refptr<network::SharedURLLoaderFactory> factory =
network::SharedURLLoaderFactory::Create(fallback_factory_->Clone());
CHECK(commit_responsibility() == FetchResponseFrom::kNoResponseYet ||
commit_responsibility() ==
FetchResponseFrom::kSubresourceLoaderIsHandlingRedirect);
factory->CreateLoaderAndStart(
forwarded_race_network_request_url_loader_factory_
->InitURLLoaderNewPipeAndPassReceiver(),
request_id_, network::mojom::kURLLoadOptionNone, resource_request_,
std::move(client_to_pass),
net::MutableNetworkTrafficAnnotationTag(
ServiceWorkerRaceNetworkRequestURLLoaderClient::
NetworkTrafficAnnotationTag()));
// Keep the URL loader related assets alive while the FetchEvent is ongoing
// in the service worker.
DCHECK(!race_network_request_url_loader_factory_);
CHECK(!remote_forwarded_race_network_request_url_loader_factory_);
race_network_request_url_loader_factory_ = std::move(factory);
remote_forwarded_race_network_request_url_loader_factory_ =
std::move(remote_factory);
return true;
}
// ServiceWorkerSubresourceLoader -------------------------------------------
ServiceWorkerSubresourceLoader::ServiceWorkerSubresourceLoader(
mojo::PendingReceiver<network::mojom::URLLoader> receiver,
int32_t request_id,
uint32_t options,
const network::ResourceRequest& resource_request,
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
scoped_refptr<ControllerServiceWorkerConnector> controller_connector,
scoped_refptr<network::SharedURLLoaderFactory> fallback_factory,
scoped_refptr<base::SequencedTaskRunner> task_runner,
base::WeakPtr<ServiceWorkerSubresourceLoaderFactory>
service_worker_subresource_loader_factory)
: response_head_(network::mojom::URLResponseHead::New()),
redirect_limit_(net::URLRequest::kMaxRedirects),
url_loader_client_(std::move(client)),
url_loader_receiver_(this, std::move(receiver)),
body_as_blob_size_(blink::BlobUtils::kUnknownSize),
controller_connector_(std::move(controller_connector)),
fetch_request_restarted_(false),
body_reading_complete_(false),
side_data_reading_complete_(false),
request_id_(request_id),
options_(options),
traffic_annotation_(traffic_annotation),
resource_request_(resource_request),
fallback_factory_(std::move(fallback_factory)),
task_runner_(std::move(task_runner)),
service_worker_subresource_loader_factory_(
std::move(service_worker_subresource_loader_factory)),
response_source_(network::mojom::FetchResponseSource::kUnspecified) {
DCHECK(controller_connector_);
response_head_->request_start = base::TimeTicks::Now();
response_head_->load_timing.request_start = base::TimeTicks::Now();
response_head_->load_timing.request_start_time = base::Time::Now();
// base::Unretained() is safe since |url_loader_receiver_| is owned by |this|.
url_loader_receiver_.set_disconnect_handler(
base::BindOnce(&ServiceWorkerSubresourceLoader::OnMojoDisconnect,
base::Unretained(this)));
StartRequest(resource_request);
}
ServiceWorkerSubresourceLoader::~ServiceWorkerSubresourceLoader() = default;
void ServiceWorkerSubresourceLoader::OnMojoDisconnect() {
delete this;
}
void ServiceWorkerSubresourceLoader::StartRequest(
const network::ResourceRequest& resource_request) {
TRACE_EVENT_WITH_FLOW1(
"ServiceWorker", "ServiceWorkerSubresourceLoader::StartRequest",
TRACE_ID_WITH_SCOPE(kServiceWorkerSubresourceLoaderScope,
TRACE_ID_LOCAL(request_id_)),
TRACE_EVENT_FLAG_FLOW_OUT, "url", resource_request.url.spec());
TransitionToStatus(Status::kStarted);
CHECK(commit_responsibility() == FetchResponseFrom::kNoResponseYet ||
commit_responsibility() ==
FetchResponseFrom::kSubresourceLoaderIsHandlingRedirect);
DCHECK(!controller_connector_observation_.IsObserving());
controller_connector_observation_.Observe(controller_connector_.get());
fetch_request_restarted_ = false;
// |service_worker_start_time| becomes web-exposed
// PerformanceResourceTiming#workerStart, which is the time before starting
// the worker or just before firing a fetch event. The idea is (fetchStart -
// workerStart) is the time taken to start service worker. In our case, we
// don't really know if the worker is started or not yet, but here is a good
// time to set workerStart, since it will either started soon or the fetch
// event will be dispatched soon.
// https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-workerstart
response_head_->load_timing.service_worker_start_time =
base::TimeTicks::Now();
DispatchFetchEvent();
}
void ServiceWorkerSubresourceLoader::DispatchFetchEvent() {
// Evaluate the registered routing info first, because this result may bypass
// ServiceWorker start process.
const auto eval_result = MaybeEvaluateRouterConditions();
enum RaceNetworkRequestMode {
kDefault,
kForced,
kSkipped
} race_network_request_mode = kDefault;
if (eval_result) { // matched the rule.
auto router_info = network::mojom::ServiceWorkerRouterInfo::New();
router_info->rule_id_matched = eval_result->id;
response_head_->service_worker_router_info = std::move(router_info);
const auto& sources = eval_result->sources;
// TODO(crbug.com/1371756): support other sources in the full form.
// https://github.com/yoshisatoyanagisawa/service-worker-static-routing-api/blob/main/final-form.md
auto source_type = sources[0].type;
set_used_router_source_type(source_type);
switch (source_type) {
case blink::ServiceWorkerRouterSource::Type::kNetwork:
// Network fallback is requested.
{
auto timing = blink::mojom::ServiceWorkerFetchEventTiming::New();
timing->dispatch_event_time = base::TimeTicks::Now();
timing->respond_with_settled_time = base::TimeTicks::Now();
OnFallback(std::nullopt, std::move(timing));
}
return;
case blink::ServiceWorkerRouterSource::Type::kRace:
race_network_request_mode = kForced;
break;
case blink::ServiceWorkerRouterSource::Type::kFetchEvent:
race_network_request_mode = kSkipped;
break;
case blink::ServiceWorkerRouterSource::Type::kCache:
controller_connector_->CallCacheStorageMatch(
sources[0].cache_source->cache_name,
blink::mojom::FetchAPIRequest::From(resource_request_),
base::BindOnce(
&ServiceWorkerSubresourceLoader::DidCacheStorageMatch,
weak_factory_.GetWeakPtr(), base::TimeTicks::Now()));
return;
}
}
// This may start the ServiceWorker if it's not started yet.
blink::mojom::ControllerServiceWorker* controller =
controller_connector_->GetControllerServiceWorker(
blink::mojom::ControllerServiceWorkerPurpose::FETCH_SUB_RESOURCE);
response_head_->load_timing.send_start = base::TimeTicks::Now();
response_head_->load_timing.send_end = base::TimeTicks::Now();
TRACE_EVENT1("ServiceWorker",
"ServiceWorkerSubresourceLoader::DispatchFetchEvent",
"controller", (controller ? "exists" : "does not exist"));
if (!controller) {
auto controller_state = controller_connector_->state();
if (controller_state ==
ControllerServiceWorkerConnector::State::kNoController) {
// The controller was lost after this loader or its loader factory was
// created.
fallback_factory_->CreateLoaderAndStart(
url_loader_receiver_.Unbind(), request_id_, options_,
resource_request_, url_loader_client_.Unbind(), traffic_annotation_);
delete this;
return;
}
// When kNoContainerHost, the network request will be aborted soon since the
// network provider has already been discarded. In that case, we don't need
// to return an error as the client must be shutting down.
DCHECK_EQ(ControllerServiceWorkerConnector::State::kNoContainerHost,
controller_state);
SettleFetchEventDispatch(std::nullopt);
return;
}
// Enable the service worker to access the files to be uploaded before
// dispatching a fetch event.
if (resource_request_.request_body) {
const auto& files = resource_request_.request_body->GetReferencedFiles();
if (!files.empty()) {
controller_connector_->EnsureFileAccess(
files,
base::BindOnce(
&ServiceWorkerSubresourceLoader::DispatchFetchEventForSubresource,
weak_factory_.GetWeakPtr()));
return;
}
}
switch (race_network_request_mode) {
case kForced:
if (StartRaceNetworkRequest()) {
SetDispatchedPreloadType(DispatchedPreloadType::kRaceNetworkRequest);
}
break;
case kDefault:
if (MaybeStartRaceNetworkRequest()) {
SetDispatchedPreloadType(DispatchedPreloadType::kRaceNetworkRequest);
} else if (MaybeStartAutoPreload()) {
SetDispatchedPreloadType(DispatchedPreloadType::kAutoPreload);
SetCommitResponsibility(FetchResponseFrom::kServiceWorker);
}
break;
case kSkipped:
// Don't start race network request.
break;
}
DispatchFetchEventForSubresource();
}
void ServiceWorkerSubresourceLoader::DispatchFetchEventForSubresource() {
mojo::PendingRemote<blink::mojom::ServiceWorkerFetchResponseCallback>
response_callback;
response_callback_receiver_.Bind(
response_callback.InitWithNewPipeAndPassReceiver());
blink::mojom::ControllerServiceWorker* controller =
controller_connector_->GetControllerServiceWorker(
blink::mojom::ControllerServiceWorkerPurpose::FETCH_SUB_RESOURCE);
if (!controller) {
SettleFetchEventDispatch(std::nullopt);
return;
}
auto params = blink::mojom::DispatchFetchEventParams::New();
params->request = blink::mojom::FetchAPIRequest::From(resource_request_);
params->client_id = controller_connector_->client_id();
if (remote_forwarded_race_network_request_url_loader_factory_) {
params->race_network_request_loader_factory =
std::move(remote_forwarded_race_network_request_url_loader_factory_);
params->request->service_worker_race_network_request_token =
base::UnguessableToken::Create();
}
// TODO(falken): Grant the controller service worker's process access to files
// in the body, like ServiceWorkerFetchDispatcher::DispatchFetchEvent() does.
controller->DispatchFetchEventForSubresource(
std::move(params), std::move(response_callback),
base::BindOnce(&ServiceWorkerSubresourceLoader::OnFetchEventFinished,
weak_factory_.GetWeakPtr()));
}
void ServiceWorkerSubresourceLoader::OnFetchEventFinished(
blink::mojom::ServiceWorkerEventStatus status) {
TRACE_EVENT_WITH_FLOW1(
"ServiceWorker", "ServiceWorkerSubresourceLoader::OnFetchEventFinished",
TRACE_ID_WITH_SCOPE(kServiceWorkerSubresourceLoaderScope,
TRACE_ID_LOCAL(request_id_)),
TRACE_EVENT_FLAG_FLOW_IN, "status", MojoEnumToString(status));
// Stop restarting logic here since OnFetchEventFinished() indicates that the
// fetch event dispatch reached the renderer.
SettleFetchEventDispatch(
mojo::ConvertTo<blink::ServiceWorkerStatusCode>(status));
switch (status) {
case blink::mojom::ServiceWorkerEventStatus::COMPLETED:
// ServiceWorkerFetchResponseCallback interface (OnResponse*() or
// OnFallback() below) is expected to be called normally and handle this
// request.
break;
case blink::mojom::ServiceWorkerEventStatus::REJECTED:
// OnResponse() is expected to called with an error about the rejected
// promise, and handle this request.
break;
case blink::mojom::ServiceWorkerEventStatus::ABORTED:
case blink::mojom::ServiceWorkerEventStatus::TIMEOUT:
// Fetch event dispatch did not complete, possibly due to timeout of
// respondWith() or waitUntil(). Return network error.
// TODO(falken): This seems racy. respondWith() may have been called
// already and we could have an outstanding stream or blob in progress,
// and we might hit CommitCompleted() twice once that settles.
CommitCompleted(net::ERR_FAILED, "Fetch event dispatch did not complete");
}
}
void ServiceWorkerSubresourceLoader::OnConnectionClosed() {
response_callback_receiver_.reset();
// If the connection to the service worker gets disconnected after dispatching
// a fetch event and before getting the response of the fetch event, restart
// the fetch event again. If it has already been restarted, that means
// starting worker failed. In that case, abort the request.
if (fetch_request_restarted_) {
SettleFetchEventDispatch(
blink::ServiceWorkerStatusCode::kErrorStartWorkerFailed);
switch (commit_responsibility()) {
case FetchResponseFrom::kNoResponseYet:
case FetchResponseFrom::kSubresourceLoaderIsHandlingRedirect:
case FetchResponseFrom::kServiceWorker:
CommitCompleted(net::ERR_FAILED, "Disconnected before completed");
return;
case FetchResponseFrom::kWithoutServiceWorker:
// If the fetch request is already handled by RaceNetworkRequest, no
// need to call CommitCompleted here.
return;
case FetchResponseFrom::kAutoPreloadHandlingFallback:
NOTREACHED_NORETURN();
}
}
fetch_request_restarted_ = true;
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&ServiceWorkerSubresourceLoader::DispatchFetchEvent,
weak_factory_.GetWeakPtr()));
}
void ServiceWorkerSubresourceLoader::SettleFetchEventDispatch(
std::optional<blink::ServiceWorkerStatusCode> status) {
if (!controller_connector_observation_.IsObserving()) {
// Already settled.
return;
}
controller_connector_observation_.Reset();
if (status) {
blink::ServiceWorkerStatusCode value = status.value();
UMA_HISTOGRAM_ENUMERATION("ServiceWorker.FetchEvent.Subresource.Status",
value);
}
}
void ServiceWorkerSubresourceLoader::OnResponse(
blink::mojom::FetchAPIResponsePtr response,
blink::mojom::ServiceWorkerFetchEventTimingPtr timing) {
TRACE_EVENT_WITH_FLOW0(
"ServiceWorker", "ServiceWorkerSubresourceLoader::OnResponse",
TRACE_ID_WITH_SCOPE(kServiceWorkerSubresourceLoaderScope,
TRACE_ID_LOCAL(request_id_)),
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
SettleFetchEventDispatch(blink::ServiceWorkerStatusCode::kOk);
UpdateResponseTiming(std::move(timing));
StartResponse(std::move(response), nullptr /* body_as_stream */);
}
void ServiceWorkerSubresourceLoader::OnResponseStream(
blink::mojom::FetchAPIResponsePtr response,
blink::mojom::ServiceWorkerStreamHandlePtr body_as_stream,
blink::mojom::ServiceWorkerFetchEventTimingPtr timing) {
// TODO(crbug.com/1342408): remove the following workaround when we can always
// expect CPUs have invariant TSC.
timing = AdjustTimingIfNeededCrBug1342408(std::move(timing));
TRACE_EVENT_WITH_FLOW0(
"ServiceWorker", "ServiceWorkerSubresourceLoader::OnResponseStream",
TRACE_ID_WITH_SCOPE(kServiceWorkerSubresourceLoaderScope,
TRACE_ID_LOCAL(request_id_)),
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
SettleFetchEventDispatch(blink::ServiceWorkerStatusCode::kOk);
UpdateResponseTiming(std::move(timing));
StartResponse(std::move(response), std::move(body_as_stream));
}
void ServiceWorkerSubresourceLoader::OnFallback(
std::optional<network::DataElementChunkedDataPipe> request_body,
blink::mojom::ServiceWorkerFetchEventTimingPtr timing) {
SettleFetchEventDispatch(blink::ServiceWorkerStatusCode::kOk);
UpdateResponseTiming(std::move(timing));
TRACE_EVENT_WITH_FLOW0(
"ServiceWorker", "ServiceWorkerSubresourceLoader::OnFallback",
TRACE_ID_WITH_SCOPE(kServiceWorkerSubresourceLoaderScope,
TRACE_ID_LOCAL(request_id_)),
TRACE_EVENT_FLAG_FLOW_IN);
bool is_race_network_request_aborted = false;
if (race_network_request_loader_client_) {
switch (race_network_request_loader_client_->state()) {
case ServiceWorkerRaceNetworkRequestURLLoaderClient::State::kAborted:
is_race_network_request_aborted = true;
break;
default:
break;
}
}
if (dispatched_preload_type() == DispatchedPreloadType::kAutoPreload &&
commit_responsibility() == FetchResponseFrom::kServiceWorker &&
!is_race_network_request_aborted) {
// When AutoPreload is dispatched, set the fetch handler end time and record
// loading metrics.
race_network_request_loader_client_
->MaybeRecordResponseReceivedToFetchHandlerEndTiming(
base::TimeTicks::Now(), /*is_fallback=*/true);
// Update the commit responsibility to the intermediate state
// |kAutoPreloadHandlingFallback| for the fallback. This is a special
// treatment for AutoPreload.
SetCommitResponsibility(FetchResponseFrom::kAutoPreloadHandlingFallback);
}
switch (commit_responsibility()) {
case FetchResponseFrom::kNoResponseYet:
case FetchResponseFrom::kSubresourceLoaderIsHandlingRedirect:
// If the RaceNetworkRequest or AutoPreload is successfully processed but
// the response is not handled yet, ask its URLLoaderClient to handle the
// response regardless of the response status not to dispatch additional
// network request for fallback.
switch (dispatched_preload_type()) {
case DispatchedPreloadType::kRaceNetworkRequest:
case DispatchedPreloadType::kAutoPreload:
if (!is_race_network_request_aborted) {
SetCommitResponsibility(FetchResponseFrom::kWithoutServiceWorker);
return;
}
break;
case DispatchedPreloadType::kNone:
SetCommitResponsibility(FetchResponseFrom::kServiceWorker);
break;
case DispatchedPreloadType::kNavigationPreload:
NOTREACHED_NORETURN();
}
break;
case FetchResponseFrom::kServiceWorker:
// RaceNetworkRequest comes first and it's a redirect.
// When redirect happens, RaceNetworkRequest defer the remaining response
// to the fetch handler (FetchResponseFrom::kServiceWorker). However, if
// the fetch handler result is a fallback, the fetch handler itself can't
// handle the response anymore because the execution is already completed.
// It costs additional request but we cancel the in-flight
// RaceNetworkRequest and start the new network equest.
if (dispatched_preload_type() ==
DispatchedPreloadType::kRaceNetworkRequest) {
race_network_request_loader_client_.reset();
}
break;
case FetchResponseFrom::kWithoutServiceWorker:
// If the fetch response is handled by RaceNetworkRequest, the new
// fallback request is not dispatched. OnFallback doesn't delete the
// instance and flip the status. Those are handled in the process of
// RaceNetworkRequest handling.
// TODO(crbug.com/1432075) Fallback response should be handled as a
// fallback. The response from RaceNetworkRequest is currently handled by
// the code path for the non-fallback case.
return;
case FetchResponseFrom::kAutoPreloadHandlingFallback:
// |kAutoPreloadHandlingFallback| is the intermediate state to transfer
// the commit responsibility from the fetch handler to the network
// request (kServiceWorker). If the fetch handler result is fallback,
// manually set the network request (kWithoutServiceWorker).
SetCommitResponsibility(FetchResponseFrom::kWithoutServiceWorker);
// If the network request is faster than the fetch handler, the response
// from the network is processed but not committed. We have to explicitly
// commit and complete the response. Otherwise
// |ServiceWorkerRaceNetworkRequestURLLoaderClient::CommitResponse()| will
// be called.
race_network_request_loader_client_
->CommitAndCompleteResponseIfDataTransferFinished();
return;
}
// Hand over to the network loader.
mojo::PendingRemote<network::mojom::URLLoaderClient> client;
std::optional<network::mojom::ServiceWorkerRouterInfo> router_info;
if (response_head_->service_worker_router_info) {
router_info = *response_head_->service_worker_router_info;
}
auto client_impl = std::make_unique<HeaderRewritingURLLoaderClient>(
std::move(url_loader_client_),
base::BindRepeating(&RewriteResponseHead,
response_head_->load_timing.service_worker_start_time,
response_head_->load_timing.service_worker_ready_time,
router_info));
mojo::MakeSelfOwnedReceiver(std::move(client_impl),
client.InitWithNewPipeAndPassReceiver());
if (request_body.has_value()) {
RestoreRequestBody(resource_request_.request_body.get(),
std::move(*request_body));
}
fallback_factory_->CreateLoaderAndStart(
url_loader_receiver_.Unbind(), request_id_, options_, resource_request_,
std::move(client), traffic_annotation_);
// Per spec, redirects after this point are not intercepted by the service
// worker again (https://crbug.com/517364). So this loader is done.
//
// It's OK to destruct this loader here. This loader may be the only one who
// has a ref to fallback_factory_ but in that case the web context that made
// the request is dead so the request is moot.
TransitionToStatus(Status::kCompleted);
RecordTimingMetricsForNetworkFallbackCase();
delete this;
}
void ServiceWorkerSubresourceLoader::UpdateResponseTiming(
blink::mojom::ServiceWorkerFetchEventTimingPtr timing) {
if (!ShouldAvoidRecordingServiceWorkerTimingInfo()) {
// |service_worker_ready_time| becomes web-exposed
// PerformanceResourceTiming#fetchStart, which is the time just before
// dispatching the fetch event, so set it to |dispatch_event_time|.
response_head_->load_timing.service_worker_ready_time =
timing->dispatch_event_time;
response_head_->load_timing.service_worker_fetch_start =
timing->dispatch_event_time;
response_head_->load_timing.service_worker_respond_with_settled =
timing->respond_with_settled_time;
}
fetch_event_timing_ = std::move(timing);
}
void ServiceWorkerSubresourceLoader::StartResponse(
blink::mojom::FetchAPIResponsePtr response,
blink::mojom::ServiceWorkerStreamHandlePtr body_as_stream) {
// When AutoPreload is dispatched, set the fetch handler end time and record
// loading metrics.
if (dispatched_preload_type() == DispatchedPreloadType::kAutoPreload) {
race_network_request_loader_client_
->MaybeRecordResponseReceivedToFetchHandlerEndTiming(
base::TimeTicks::Now(), /*is_fallback=*/false);
}
switch (commit_responsibility()) {
case FetchResponseFrom::kNoResponseYet:
case FetchResponseFrom::kSubresourceLoaderIsHandlingRedirect:
SetCommitResponsibility(FetchResponseFrom::kServiceWorker);
break;
case FetchResponseFrom::kServiceWorker:
break;
case FetchResponseFrom::kWithoutServiceWorker:
// If the response of RaceNetworkRequest is already handled, discard the
// fetch handler result but consume data pipes here not to make data for
// the fetch handler being stuck.
if (!body_as_stream.is_null() && body_as_stream->stream.is_valid() &&
race_network_request_loader_client_) {
race_network_request_loader_client_->DrainData(
std::move(body_as_stream->stream));
}
return;
case FetchResponseFrom::kAutoPreloadHandlingFallback:
NOTREACHED_NORETURN();
}
// Cancel the in-flight request processing for the fallback.
if (commit_responsibility() == FetchResponseFrom::kServiceWorker &&
race_network_request_loader_client_) {
race_network_request_loader_client_->CancelWriteData(
commit_responsibility());
}
RecordFetchResponseFrom();
// A response with status code 0 is Blink telling us to respond with network
// error.
if (response->status_code == 0) {
CommitCompleted(net::ERR_FAILED, "Zero response status");
return;
}
blink::ServiceWorkerLoaderHelpers::SaveResponseInfo(*response,
response_head_.get());
response_head_->response_start = base::TimeTicks::Now();
response_head_->load_timing.receive_headers_start = base::TimeTicks::Now();
response_head_->load_timing.receive_headers_end =
response_head_->load_timing.receive_headers_start;
response_source_ = response->response_source;
// Constructed subresource responses are always same-origin as the requesting
// client.
response_head_->timing_allow_passed = true;
// Handle a redirect response. ComputeRedirectInfo returns non-null redirect
// info if the given response is a redirect.
std::optional<net::RedirectInfo> redirect_info =
blink::ServiceWorkerLoaderHelpers::ComputeRedirectInfo(resource_request_,
*response_head_);
if (redirect_info) {
HandleRedirect(*redirect_info, response_head_);
return;
}
// We have a non-redirect response. Send the headers to the client.
CommitResponseHeaders(response_head_);
bool body_stream_is_valid =
!body_as_stream.is_null() && body_as_stream->stream.is_valid();
// Handle the case where there is no body content.
if (!body_stream_is_valid && !response->blob) {
CommitEmptyResponseAndComplete();
return;
}
mojo::ScopedDataPipeConsumerHandle data_pipe;
// Handle a stream response body.
if (body_stream_is_valid) {
DCHECK(!response->blob);
DCHECK(url_loader_client_.is_bound());
stream_waiter_ = std::make_unique<StreamWaiter>(
this, std::move(body_as_stream->callback_receiver));
data_pipe = std::move(body_as_stream->stream);
}
// Handle a blob response body.
if (response->blob) {
DCHECK(!body_as_stream);
DCHECK(response->blob->blob.is_valid());
body_as_blob_.Bind(std::move(response->blob->blob));
body_as_blob_size_ = response->blob->size;
// Start reading the body blob immediately. This will allow the body to
// start buffering in the pipe while the side data is read.
int error = StartBlobReading(&data_pipe);
if (error != net::OK) {
CommitCompleted(error, "Failed to read blob body");
return;
}
}
DCHECK(data_pipe.is_valid());
// Read side data if necessary. We only do this if both the
// |side_data_blob| is available to read and the request is destined
// for a script.
auto request_destination = resource_request_.destination;
if (response->side_data_blob &&
(request_destination == network::mojom::RequestDestination::kScript ||
response->mime_type == "application/wasm")) {
side_data_as_blob_.Bind(std::move(response->side_data_blob->blob));
side_data_as_blob_->ReadSideData(base::BindOnce(
&ServiceWorkerSubresourceLoader::OnSideDataReadingComplete,
weak_factory_.GetWeakPtr(), std::move(data_pipe)));
return;
}
// Otherwise we can immediately complete side data reading so that the
// entire resource completes when the main body is read.
OnSideDataReadingComplete(std::move(data_pipe),
std::optional<mojo_base::BigBuffer>());
}
void ServiceWorkerSubresourceLoader::CommitResponseHeaders(
const network::mojom::URLResponseHeadPtr& response_head) {
DCHECK(url_loader_client_.is_bound());
TRACE_EVENT_WITH_FLOW2(
"ServiceWorker", "ServiceWorkerSubesourceLoader::CommitResponseHeaders",
this, TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT,
"response_code", response_head->headers->response_code(), "status_text",
response_head->headers->GetStatusText());
TransitionToStatus(Status::kSentHeader);
}
void ServiceWorkerSubresourceLoader::CommitResponseBody(
const network::mojom::URLResponseHeadPtr& response_head,
mojo::ScopedDataPipeConsumerHandle response_body,
std::optional<mojo_base::BigBuffer> cached_metadata) {
TransitionToStatus(Status::kSentBody);
// TODO(kinuko): Fill the ssl_info.
url_loader_client_->OnReceiveResponse(response_head.Clone(),
std::move(response_body),
std::move(cached_metadata));
}
void ServiceWorkerSubresourceLoader::CommitEmptyResponseAndComplete() {
mojo::ScopedDataPipeProducerHandle producer_handle;
mojo::ScopedDataPipeConsumerHandle consumer_handle;
if (CreateDataPipe(nullptr, producer_handle, consumer_handle) !=
MOJO_RESULT_OK) {
CommitCompleted(net::ERR_INSUFFICIENT_RESOURCES,
"Can't create empty data pipe");
return;
}
producer_handle.reset(); // The data pipe is empty.
CommitResponseBody(response_head_, std::move(consumer_handle), std::nullopt);
CommitCompleted(net::OK, "No body exists");
}
void ServiceWorkerSubresourceLoader::CommitCompleted(int error_code,
const char* reason) {
TRACE_EVENT_WITH_FLOW2(
"ServiceWorker", "ServiceWorkerSubresourceLoader::CommitCompleted",
TRACE_ID_WITH_SCOPE(kServiceWorkerSubresourceLoaderScope,
TRACE_ID_LOCAL(request_id_)),
TRACE_EVENT_FLAG_FLOW_IN, "error_code", net::ErrorToString(error_code),
"reason", TRACE_STR_COPY(reason));
TransitionToStatus(Status::kCompleted);
if (error_code == net::OK) {
switch (commit_responsibility()) {
case FetchResponseFrom::kNoResponseYet:
case FetchResponseFrom::kSubresourceLoaderIsHandlingRedirect:
case FetchResponseFrom::kAutoPreloadHandlingFallback:
NOTREACHED();
break;
case FetchResponseFrom::kServiceWorker:
RecordTimingMetricsForFetchHandlerHandledCase();
break;
case FetchResponseFrom::kWithoutServiceWorker:
RecordTimingMetricsForRaceNetworkReqestCase();
break;
}
}
DCHECK(url_loader_client_.is_bound());
body_as_blob_.reset();
stream_waiter_.reset();
network::URLLoaderCompletionStatus status;
status.error_code = error_code;
status.completion_time = base::TimeTicks::Now();
url_loader_client_->OnComplete(status);
// Invalidate weak pointers to prevent callbacks after commit. This can
// occur if an error code is encountered which forces an early commit.
weak_factory_.InvalidateWeakPtrs();
}
void ServiceWorkerSubresourceLoader::HandleRedirect(
const net::RedirectInfo& redirect_info,
const network::mojom::URLResponseHeadPtr& response_head) {
// If the fetch response is not from the fetch handler, call
// SettleFetchEventDispatch here explicitly because the loader is going to
// handle the response with RaceNetworkRequest, and the in-flight fetch
// event by the fetch handler may not be settled yet.
if (commit_responsibility() == FetchResponseFrom::kWithoutServiceWorker) {
SettleFetchEventDispatch(std::nullopt);
}
redirect_info_ = std::move(redirect_info);
if (redirect_limit_-- == 0) {
CommitCompleted(net::ERR_TOO_MANY_REDIRECTS, "Too many redirects");
return;
}
response_head->encoded_data_length = 0;
url_loader_client_->OnReceiveRedirect(*redirect_info_, response_head.Clone());
TransitionToStatus(Status::kSentRedirect);
}
void ServiceWorkerSubresourceLoader::
RecordTimingMetricsForFetchHandlerHandledCase() {
if (!InitRecordTimingMetricsIfEligible(response_head_->load_timing)) {
return;
}
RecordForwardServiceWorkerToWorkerReadyTiming(response_head_->load_timing);
RecordWorkerReadyToFetchHandlerEndTiming(response_head_->load_timing);
RecordFetchHandlerEndToResponseReceivedTiming(response_head_->load_timing);
RecordResponseReceivedToCompletedTiming(response_head_->load_timing);
RecordStartToCompletedTiming(response_head_->load_timing);
}
void ServiceWorkerSubresourceLoader::
RecordTimingMetricsForNetworkFallbackCase() {
if (!InitRecordTimingMetricsIfEligible(response_head_->load_timing)) {
return;
}
RecordForwardServiceWorkerToWorkerReadyTiming(response_head_->load_timing);
RecordWorkerReadyToFetchHandlerEndTiming(response_head_->load_timing);
RecordFetchHandlerEndToFallbackNetworkTiming(response_head_->load_timing);
RecordStartToCompletedTiming(response_head_->load_timing);
}
void ServiceWorkerSubresourceLoader::
RecordTimingMetricsForRaceNetworkReqestCase() {
DCHECK(race_network_request_loader_client_);
if (!InitRecordTimingMetricsIfEligible(
race_network_request_loader_client_->GetLoadTimingInfo())) {
return;
}
RecordStartToCompletedTiming(
race_network_request_loader_client_->GetLoadTimingInfo());
}
bool ServiceWorkerSubresourceLoader::InitRecordTimingMetricsIfEligible(
const net::LoadTimingInfo& load_timing) {
// |devtools_request_id| is set when DevTools is attached. Don't record
// metrics when DevTools is attached to reduce noise.
if (resource_request_.devtools_request_id.has_value()) {
return false;
}
// |fetch_event_timing_| can be recorded in different process. We can get
// reasonable metrics only when TimeTicks are consistent across processes.
if (!base::TimeTicks::IsHighResolution() ||
!base::TimeTicks::IsConsistentAcrossProcesses()) {
return false;
}
TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP1(
"ServiceWorker", "ServiceWorker.LoadTiming.Subresource", this,
load_timing.request_start, "url", resource_request_.url);
TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP0(
"ServiceWorker", "ServiceWorker.LoadTiming.Subresource", this,
completion_time_);
if (ShouldAvoidRecordingServiceWorkerTimingInfo()) {
return false;
}
return true;
}
void ServiceWorkerSubresourceLoader::
RecordForwardServiceWorkerToWorkerReadyTiming(
const net::LoadTimingInfo& load_timing) {
UMA_HISTOGRAM_TIMES(
"ServiceWorker.LoadTiming.Subresource."
"ForwardServiceWorkerToWorkerReady",
load_timing.service_worker_ready_time -
load_timing.service_worker_start_time);
TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0(
"ServiceWorker", "ForwardServiceWorkerToWorkerReady", this,
load_timing.service_worker_start_time);
TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP0(
"ServiceWorker", "ForwardServiceWorkerToWorkerReady", this,
load_timing.service_worker_ready_time);
}
void ServiceWorkerSubresourceLoader::RecordWorkerReadyToFetchHandlerEndTiming(
const net::LoadTimingInfo& load_timing) {
DCHECK(fetch_event_timing_);
UMA_HISTOGRAM_TIMES(
"ServiceWorker.LoadTiming.Subresource."
"WorkerReadyToFetchHandlerEnd",
fetch_event_timing_->respond_with_settled_time -
load_timing.service_worker_ready_time);
TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0(
"ServiceWorker", "WorkerReadyToFetchHandlerEnd", this,
load_timing.service_worker_ready_time);
TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP0(
"ServiceWorker", "WorkerReadyToFetchHandlerEnd", this,
fetch_event_timing_->respond_with_settled_time);
}
void ServiceWorkerSubresourceLoader::
RecordFetchHandlerEndToResponseReceivedTiming(
const net::LoadTimingInfo& load_timing) {
DCHECK(fetch_event_timing_);
UMA_HISTOGRAM_TIMES(
"ServiceWorker.LoadTiming.Subresource."
"FetchHandlerEndToResponseReceived",
load_timing.receive_headers_end -
fetch_event_timing_->respond_with_settled_time);
TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0(
"ServiceWorker", "FetchHandlerEndToResponseReceived", this,
fetch_event_timing_->respond_with_settled_time);
TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP0(
"ServiceWorker", "FetchHandlerEndToResponseReceived", this,
load_timing.receive_headers_end);
}
void ServiceWorkerSubresourceLoader::RecordResponseReceivedToCompletedTiming(
const net::LoadTimingInfo& load_timing) {
UMA_HISTOGRAM_MEDIUM_TIMES(
"ServiceWorker.LoadTiming.Subresource."
"ResponseReceivedToCompleted2",
completion_time_ - load_timing.receive_headers_end);
TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP1(
"ServiceWorker", "ResponseReceivedToCompleted", this,
load_timing.receive_headers_end, "fetch_response_source",
blink::ServiceWorkerLoaderHelpers::FetchResponseSourceToSuffix(
response_source_));
TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP0(
"ServiceWorker", "ResponseReceivedToCompleted", this, completion_time_);
// Same as above, breakdown by response source.
base::UmaHistogramMediumTimes(
base::StrCat(
{"ServiceWorker.LoadTiming.Subresource."
"ResponseReceivedToCompleted2.",
blink::ServiceWorkerLoaderHelpers::FetchResponseSourceToSuffix(
response_source_)}),
completion_time_ - load_timing.receive_headers_end);
}
void ServiceWorkerSubresourceLoader::
RecordFetchHandlerEndToFallbackNetworkTiming(
const net::LoadTimingInfo& load_timing) {
DCHECK(fetch_event_timing_);
UMA_HISTOGRAM_TIMES(
"ServiceWorker.LoadTiming.Subresource."
"FetchHandlerEndToFallbackNetwork",
completion_time_ - fetch_event_timing_->respond_with_settled_time);
TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0(
"ServiceWorker", "FetchHandlerEndToFallbackNetwork", this,
fetch_event_timing_->respond_with_settled_time);
TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP0(
"ServiceWorker", "FetchHandlerEndToFallbackNetwork", this,
completion_time_);
}
void ServiceWorkerSubresourceLoader::RecordStartToCompletedTiming(
const net::LoadTimingInfo& load_timing) {
base::UmaHistogramMediumTimes(
"ServiceWorker.LoadTiming.Subresource.StartToCompleted",
completion_time_ - load_timing.request_start);
}
// ServiceWorkerSubresourceLoader: URLLoader implementation -----------------
void ServiceWorkerSubresourceLoader::FollowRedirect(
const std::vector<std::string>& removed_headers,
const net::HttpRequestHeaders& modified_headers,
const net::HttpRequestHeaders& modified_cors_exempt_headers,
const std::optional<GURL>& new_url) {
TRACE_EVENT_WITH_FLOW1(
"ServiceWorker", "ServiceWorkerSubresourceLoader::FollowRedirect",
TRACE_ID_WITH_SCOPE(kServiceWorkerSubresourceLoaderScope,
TRACE_ID_LOCAL(request_id_)),
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "new_url",
redirect_info_ ? redirect_info_->new_url.spec() : "(none)");
// In rare cases, the client seems to call FollowRedirect() when we aren't
// expecting it. Just complete with error if we have not already completed.
// https://crbug.com/1162035
if (!redirect_info_) {
if (status_ != Status::kCompleted)
CommitCompleted(net::ERR_INVALID_REDIRECT, "Invalid redirect");
return;
}
DCHECK_EQ(status_, Status::kSentRedirect);
// TODO(arthursonzogni, juncai): This seems to be correctly implemented, but
// not used so far. Add tests and remove this DCHECK to support this feature
// if needed. See https://crbug.com/845683.
DCHECK(modified_headers.IsEmpty() && modified_cors_exempt_headers.IsEmpty())
<< "Redirect with modified headers is not supported yet. See "
"https://crbug.com/845683";
DCHECK(!new_url.has_value()) << "Redirect with modified url was not "
"supported yet. crbug.com/845683";
bool should_clear_upload = false;
net::RedirectUtil::UpdateHttpRequest(
resource_request_.url, resource_request_.method, *redirect_info_,
removed_headers, modified_headers, &resource_request_.headers,
&should_clear_upload);
resource_request_.cors_exempt_headers.MergeFrom(modified_cors_exempt_headers);
for (const std::string& name : removed_headers)
resource_request_.cors_exempt_headers.RemoveHeader(name);
if (should_clear_upload)
resource_request_.request_body = nullptr;
resource_request_.url = redirect_info_->new_url;
resource_request_.method = redirect_info_->new_method;
resource_request_.site_for_cookies = redirect_info_->new_site_for_cookies;
resource_request_.referrer = GURL(redirect_info_->new_referrer);
resource_request_.referrer_policy = redirect_info_->new_referrer_policy;
// Restart the request.
TransitionToStatus(Status::kNotStarted);
redirect_info_.reset();
response_callback_receiver_.reset();
SetCommitResponsibility(
FetchResponseFrom::kSubresourceLoaderIsHandlingRedirect);
race_network_request_loader_client_.reset();
race_network_request_url_loader_factory_.reset();
StartRequest(resource_request_);
}
void ServiceWorkerSubresourceLoader::SetPriority(net::RequestPriority priority,
int intra_priority_value) {
// Not supported (do nothing).
}
void ServiceWorkerSubresourceLoader::PauseReadingBodyFromNet() {}
void ServiceWorkerSubresourceLoader::ResumeReadingBodyFromNet() {}
int ServiceWorkerSubresourceLoader::StartBlobReading(
mojo::ScopedDataPipeConsumerHandle* body_pipe) {
TRACE_EVENT_WITH_FLOW0(
"ServiceWorker", "ServiceWorkerSubresourceLoader::StartBlobReading",
TRACE_ID_WITH_SCOPE(kServiceWorkerSubresourceLoaderScope,
TRACE_ID_LOCAL(request_id_)),
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
DCHECK(body_pipe);
DCHECK(!body_reading_complete_);
return blink::ServiceWorkerLoaderHelpers::ReadBlobResponseBody(
&body_as_blob_, body_as_blob_size_,
base::BindOnce(&ServiceWorkerSubresourceLoader::OnBodyReadingComplete,
weak_factory_.GetWeakPtr()),
body_pipe);
}
void ServiceWorkerSubresourceLoader::OnSideDataReadingComplete(
mojo::ScopedDataPipeConsumerHandle data_pipe,
std::optional<mojo_base::BigBuffer> metadata) {
TRACE_EVENT_WITH_FLOW1(
"ServiceWorker",
"ServiceWorkerSubresourceLoader::OnSideDataReadingComplete",
TRACE_ID_WITH_SCOPE(kServiceWorkerSubresourceLoaderScope,
TRACE_ID_LOCAL(request_id_)),
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "metadata size",
(metadata ? metadata->size() : 0));
DCHECK(url_loader_client_);
DCHECK(!side_data_reading_complete_);
side_data_reading_complete_ = true;
DCHECK(data_pipe.is_valid());
CommitResponseBody(response_head_, std::move(data_pipe), std::move(metadata));
// If the blob reading completed before the side data reading, then we
// must manually finalize the blob reading now.
if (body_reading_complete_) {
OnBodyReadingComplete(net::OK);
}
// Otherwise we asyncly continue in OnBlobReadingComplete().
}
void ServiceWorkerSubresourceLoader::OnBodyReadingComplete(int net_error) {
TRACE_EVENT_WITH_FLOW0(
"ServiceWorker", "ServiceWorkerSubresourceLoader::OnBodyReadingComplete",
TRACE_ID_WITH_SCOPE(kServiceWorkerSubresourceLoaderScope,
TRACE_ID_LOCAL(request_id_)),
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
body_reading_complete_ = true;
// If the side data has not completed reading yet, then we need to delay
// calling CommitCompleted. This method will be called again from
// OnSideDataReadingComplete(). Only delay for successful reads, though.
// Abort immediately on error.
if (!side_data_reading_complete_ && net_error == net::OK)
return;
CommitCompleted(net_error, "Body reading completed");
}
bool ServiceWorkerSubresourceLoader::IsMainResourceLoader() {
return false;
}
std::optional<ServiceWorkerRouterEvaluator::Result>
ServiceWorkerSubresourceLoader::MaybeEvaluateRouterConditions() const {
auto* router_evaluator = controller_connector_->router_evaluator();
if (!router_evaluator) {
return {};
}
CHECK(router_evaluator->IsValid());
// Avoid calling GetRecentRunningStatus() if there is no rules that
// need running status.
// Getting recent running status sends IPC to the browser process,
// and affection to performance is concerned.
std::optional<ServiceWorkerRouterEvaluator::Result> result;
if (router_evaluator->need_running_status()) {
result = router_evaluator->Evaluate(
resource_request_, controller_connector_->GetRecentRunningStatus());
} else {
result = router_evaluator->EvaluateWithoutRunningStatus(resource_request_);
}
return result;
}
// ServiceWorkerSubresourceLoaderFactory ------------------------------------
// static
void ServiceWorkerSubresourceLoaderFactory::Create(
scoped_refptr<ControllerServiceWorkerConnector> controller_connector,
scoped_refptr<network::SharedURLLoaderFactory> fallback_factory,
mojo::PendingReceiver<network::mojom::URLLoaderFactory> receiver,
scoped_refptr<base::SequencedTaskRunner> task_runner) {
new ServiceWorkerSubresourceLoaderFactory(
std::move(controller_connector), std::move(fallback_factory),
std::move(receiver), std::move(task_runner));
}
ServiceWorkerSubresourceLoaderFactory::ServiceWorkerSubresourceLoaderFactory(
scoped_refptr<ControllerServiceWorkerConnector> controller_connector,
scoped_refptr<network::SharedURLLoaderFactory> fallback_factory,
mojo::PendingReceiver<network::mojom::URLLoaderFactory> receiver,
scoped_refptr<base::SequencedTaskRunner> task_runner)
: controller_connector_(std::move(controller_connector)),
fallback_factory_(std::move(fallback_factory)),
task_runner_(std::move(task_runner)) {
DCHECK(fallback_factory_);
receivers_.Add(this, std::move(receiver));
receivers_.set_disconnect_handler(base::BindRepeating(
&ServiceWorkerSubresourceLoaderFactory::OnMojoDisconnect,
base::Unretained(this)));
}
ServiceWorkerSubresourceLoaderFactory::
~ServiceWorkerSubresourceLoaderFactory() = default;
void ServiceWorkerSubresourceLoaderFactory::CreateLoaderAndStart(
mojo::PendingReceiver<network::mojom::URLLoader> receiver,
int32_t request_id,
uint32_t options,
const network::ResourceRequest& resource_request,
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) {
// This loader destructs itself, as we want to transparently switch to the
// network loader when fallback happens. When that happens the loader unbinds
// the request, passes the request to the fallback factory, and
// destructs itself (while the loader client continues to work).
new ServiceWorkerSubresourceLoader(
std::move(receiver), request_id, options, resource_request,
std::move(client), traffic_annotation, controller_connector_,
fallback_factory_, task_runner_, weak_factory_.GetWeakPtr());
}
void ServiceWorkerSubresourceLoaderFactory::Clone(
mojo::PendingReceiver<network::mojom::URLLoaderFactory> receiver) {
receivers_.Add(this, std::move(receiver));
}
void ServiceWorkerSubresourceLoaderFactory::OnMojoDisconnect() {
if (!receivers_.empty())
return;
delete this;
}
void ServiceWorkerSubresourceLoader::TransitionToStatus(Status new_status) {
#if DCHECK_IS_ON()
switch (new_status) {
case Status::kNotStarted:
DCHECK_EQ(status_, Status::kSentRedirect);
break;
case Status::kStarted:
DCHECK_EQ(status_, Status::kNotStarted);
break;
case Status::kSentRedirect:
DCHECK_EQ(status_, Status::kStarted);
break;
case Status::kSentHeader:
DCHECK_EQ(status_, Status::kStarted);
break;
case Status::kSentBody:
DCHECK_EQ(status_, Status::kSentHeader);
break;
case Status::kCompleted:
DCHECK(
// Network fallback before interception.
status_ == Status::kNotStarted ||
// Network fallback after interception.
status_ == Status::kStarted ||
// Pipe creation failure for empty response.
status_ == Status::kSentHeader ||
// Success case or error while sending the response's body.
status_ == Status::kSentBody);
break;
}
#endif // DCHECK_IS_ON()
status_ = new_status;
if (new_status == Status::kCompleted) {
completion_time_ = base::TimeTicks::Now();
}
}
void ServiceWorkerSubresourceLoader::DidCacheStorageMatch(
base::TimeTicks event_dispatch_time,
blink::mojom::MatchResultPtr result) {
auto timing = blink::mojom::ServiceWorkerFetchEventTiming::New();
timing->dispatch_event_time = event_dispatch_time;
timing->respond_with_settled_time = base::TimeTicks::Now();
switch (result->which()) {
case blink::mojom::MatchResult::Tag::kStatus: // error fallback.
base::UmaHistogramEnumeration(
"ServiceWorker.StaticRouter.Subresource.CacheStorageError",
result->get_status());
OnFallback(std::nullopt, std::move(timing));
return;
case blink::mojom::MatchResult::Tag::kResponse: // we got fetch response.
if (result->get_response()->parsed_headers) {
// We intend to reset the parsed header. Or, invalid parsed headers
// should be set.
//
// According to content/browser/cache_storage/cache_storage_cache.cc,
// the field looks not set up with the meaningful value.
// Also, the Cache Storage API code looks not using the parsed_header
// according to third_party/blink/renderer/core/fetch/response.cc.
// (It can be tracked from
// third_party/blink/renderer/modules/cache_storage/cache_storage.cc)
result->get_response()->parsed_headers.reset();
}
OnResponse(std::move(result->get_response()), std::move(timing));
return;
case blink::mojom::MatchResult::Tag::kEagerResponse:
// EagerResponse, which should be used only if `in_related_fetch_event`
// is set.
NOTREACHED_NORETURN();
}
}
} // namespace content