blob: abde435556a1d77d9d4cf1a5312777c87c388828 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/service_worker/service_worker_single_script_update_checker.h"
#include <utility>
#include "base/bind.h"
#include "base/trace_event/trace_event.h"
#include "content/browser/appcache/appcache_response.h"
#include "content/browser/loader/browser_initiated_resource_request.h"
#include "content/browser/loader/navigation_url_loader_impl.h"
#include "content/browser/service_worker/service_worker_cache_writer.h"
#include "content/browser/service_worker/service_worker_consts.h"
#include "content/browser/service_worker/service_worker_loader_helpers.h"
#include "content/common/service_worker/service_worker_utils.h"
#include "content/common/throttling_url_loader.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/common/content_client.h"
#include "content/public/common/resource_type.h"
#include "mojo/public/cpp/system/simple_watcher.h"
#include "net/base/ip_endpoint.h"
#include "net/base/load_flags.h"
#include "services/network/public/cpp/net_adapters.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "third_party/blink/public/common/loader/url_loader_throttle.h"
#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom.h"
namespace content {
namespace {
constexpr net::NetworkTrafficAnnotationTag kUpdateCheckTrafficAnnotation =
net::DefineNetworkTrafficAnnotation("service_worker_update_checker",
R"(
semantics {
sender: "ServiceWorker System"
description:
"This request is issued by an update check to fetch the content of "
"the new scripts."
trigger:
"ServiceWorker's update logic, which is triggered by a navigation to a "
"site controlled by a service worker."
data:
"No body. 'Service-Worker: script' header is attached when it's the "
"main worker script. Requests may include cookies and credentials."
destination: WEBSITE
}
policy {
cookies_allowed: YES
cookies_store: "user"
setting:
"Users can control this feature via the 'Cookies' setting under "
"'Privacy, Content settings'. If cookies are disabled for a single "
"site, serviceworkers are disabled for the site only. If they are "
"totally disabled, all serviceworker requests will be stopped."
chrome_policy {
URLBlacklist {
URLBlacklist: { entries: '*' }
}
}
chrome_policy {
URLWhitelist {
URLWhitelist { }
}
}
}
comments:
"Chrome would be unable to update service workers without this type of "
"request. Using either URLBlacklist or URLWhitelist policies (or a "
"combination of both) limits the scope of these requests."
)");
} // namespace
// This is for debugging https://crbug.com/959627.
// The purpose is to see where the IOBuffer comes from by checking |__vfptr|.
class ServiceWorkerSingleScriptUpdateChecker::WrappedIOBuffer
: public net::WrappedIOBuffer {
public:
WrappedIOBuffer(const char* data) : net::WrappedIOBuffer(data) {}
private:
~WrappedIOBuffer() override = default;
// This is to make sure that the vtable is not merged with other classes.
virtual void dummy() { NOTREACHED(); }
};
ServiceWorkerSingleScriptUpdateChecker::ServiceWorkerSingleScriptUpdateChecker(
const GURL& script_url,
bool is_main_script,
const GURL& main_script_url,
const GURL& scope,
bool force_bypass_cache,
blink::mojom::ServiceWorkerUpdateViaCache update_via_cache,
base::TimeDelta time_since_last_check,
const net::HttpRequestHeaders& default_headers,
ServiceWorkerUpdatedScriptLoader::BrowserContextGetter
browser_context_getter,
scoped_refptr<network::SharedURLLoaderFactory> loader_factory,
std::unique_ptr<ServiceWorkerResponseReader> compare_reader,
std::unique_ptr<ServiceWorkerResponseReader> copy_reader,
std::unique_ptr<ServiceWorkerResponseWriter> writer,
ResultCallback callback)
: script_url_(script_url),
is_main_script_(is_main_script),
scope_(scope),
force_bypass_cache_(force_bypass_cache),
update_via_cache_(update_via_cache),
time_since_last_check_(time_since_last_check),
network_client_binding_(this),
network_watcher_(FROM_HERE,
mojo::SimpleWatcher::ArmingPolicy::MANUAL,
base::SequencedTaskRunnerHandle::Get()),
callback_(std::move(callback)) {
TRACE_EVENT_WITH_FLOW2("ServiceWorker",
"ServiceWorkerSingleScriptUpdateChecker::"
"ServiceWorkerSingleScriptUpdateChecker",
this, TRACE_EVENT_FLAG_FLOW_OUT, "script_url",
script_url.spec(), "main_script_url",
main_script_url.spec());
uint32_t options = network::mojom::kURLLoadOptionNone;
network::ResourceRequest resource_request;
resource_request.url = script_url;
resource_request.site_for_cookies = main_script_url;
resource_request.do_not_prompt_for_login = true;
resource_request.headers = default_headers;
// ResourceRequest::request_initiator is the request's origin in the spec.
// https://fetch.spec.whatwg.org/#concept-request-origin
// It's needed to be set to the origin of the main script url.
// https://github.com/w3c/ServiceWorker/issues/1447
const url::Origin origin = url::Origin::Create(main_script_url);
resource_request.request_initiator = origin;
// This key is used to isolate requests from different contexts in accessing
// shared network resources like the http cache.
resource_request.trusted_params = network::ResourceRequest::TrustedParams();
resource_request.trusted_params->network_isolation_key =
net::NetworkIsolationKey(origin, origin);
if (is_main_script_) {
// Set the "Service-Worker" header for the main script request:
// https://w3c.github.io/ServiceWorker/#service-worker-script-request
resource_request.headers.SetHeader("Service-Worker", "script");
// The "Fetch a classic worker script" uses "same-origin" as mode and
// credentials mode.
// https://html.spec.whatwg.org/C/#fetch-a-classic-worker-script
resource_request.mode = network::mojom::RequestMode::kSameOrigin;
resource_request.credentials_mode =
network::mojom::CredentialsMode::kSameOrigin;
// |fetch_request_context_type| and |resource_type| roughly correspond to
// the request's |destination| in the Fetch spec.
// The destination is "serviceworker" for the main script.
// https://w3c.github.io/ServiceWorker/#update-algorithm
resource_request.fetch_request_context_type =
static_cast<int>(blink::mojom::RequestContextType::SERVICE_WORKER);
resource_request.resource_type =
static_cast<int>(ResourceType::kServiceWorker);
// Request SSLInfo. It will be persisted in service worker storage and
// may be used by ServiceWorkerNavigationLoader for navigations handled
// by this service worker.
options |= network::mojom::kURLLoadOptionSendSSLInfoWithResponse;
} else {
// The "fetch a classic worker-imported script" doesn't have any statement
// about mode and credentials mode. Use the default value, which is
// "no-cors".
// https://html.spec.whatwg.org/C/#fetch-a-classic-worker-imported-script
DCHECK_EQ(network::mojom::RequestMode::kNoCors, resource_request.mode);
// Explicitly set it to kOmit because the default value of
// ResourceRequest::credentials_mode (kInclude) is different from the
// default value in the spec "omit".
// https://fetch.spec.whatwg.org/#concept-request-credentials-mode
//
// TODO(https://crbug.com/799935): Remove this once we use kOmit as the
// default value.
// TODO(https://crbug.com/972458): Need the test.
resource_request.credentials_mode = network::mojom::CredentialsMode::kOmit;
// |fetch_request_context_type| and |resource_type| roughly correspond to
// the request's |destination| in the Fetch spec.
// The destination is "script" for the imported script.
// https://w3c.github.io/ServiceWorker/#update-algorithm
resource_request.fetch_request_context_type =
static_cast<int>(blink::mojom::RequestContextType::SCRIPT);
resource_request.resource_type = static_cast<int>(ResourceType::kScript);
}
// Upgrade the request to an a priori authenticated URL, if appropriate.
// https://w3c.github.io/webappsec-upgrade-insecure-requests/#upgrade-request
// TODO(https://crbug.com/987491): Set |ResourceRequest::upgrade_if_insecure_|
// appropriately.
// TODO(https://crbug.com/824647): Support ES modules. Use "cors" as a mode
// for service worker served as modules, and "omit" as a credentials mode:
// https://html.spec.whatwg.org/C/#fetch-a-single-module-script
SetFetchMetadataHeadersForBrowserInitiatedRequest(&resource_request);
if (service_worker_loader_helpers::ShouldValidateBrowserCacheForScript(
is_main_script_, force_bypass_cache_, update_via_cache_,
time_since_last_check_)) {
resource_request.load_flags |= net::LOAD_VALIDATE_CACHE;
}
cache_writer_ = ServiceWorkerCacheWriter::CreateForComparison(
std::move(compare_reader), std::move(copy_reader), std::move(writer),
/*pause_when_not_identical=*/true);
network::mojom::URLLoaderClientPtrInfo network_client;
network_client_binding_.Bind(mojo::MakeRequest(&network_client));
// Use NavigationURLLoaderImpl to get a unique request id across
// browser-initiated navigations and worker script fetch.
const int request_id =
NavigationURLLoaderImpl::MakeGlobalRequestID().request_id;
network_loader_ = ServiceWorkerUpdatedScriptLoader::
ThrottlingURLLoaderCoreWrapper::CreateLoaderAndStart(
loader_factory->Clone(), browser_context_getter, MSG_ROUTING_NONE,
request_id, options, resource_request, std::move(network_client),
kUpdateCheckTrafficAnnotation);
DCHECK_EQ(network_loader_state_,
ServiceWorkerUpdatedScriptLoader::LoaderState::kNotStarted);
network_loader_state_ =
ServiceWorkerUpdatedScriptLoader::LoaderState::kLoadingHeader;
}
ServiceWorkerSingleScriptUpdateChecker::
~ServiceWorkerSingleScriptUpdateChecker() = default;
// URLLoaderClient override ----------------------------------------------------
void ServiceWorkerSingleScriptUpdateChecker::OnReceiveResponse(
network::mojom::URLResponseHeadPtr response_head) {
TRACE_EVENT_WITH_FLOW0(
"ServiceWorker",
"ServiceWorkerSingleScriptUpdateChecker::OnReceiveResponse", this,
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
DCHECK_EQ(network_loader_state_,
ServiceWorkerUpdatedScriptLoader::LoaderState::kLoadingHeader);
blink::ServiceWorkerStatusCode service_worker_status;
network::URLLoaderCompletionStatus completion_status;
std::string error_message;
std::unique_ptr<net::HttpResponseInfo> response_info =
service_worker_loader_helpers::CreateHttpResponseInfoAndCheckHeaders(
response_head, &service_worker_status, &completion_status,
&error_message);
if (!response_info) {
DCHECK_NE(net::OK, completion_status.error_code);
Fail(service_worker_status, error_message, completion_status);
return;
}
// Check the path restriction defined in the spec:
// https://w3c.github.io/ServiceWorker/#service-worker-script-response
// Only main script needs the following check.
if (is_main_script_) {
std::string service_worker_allowed;
bool has_header = response_head->headers->EnumerateHeader(
nullptr, ServiceWorkerConsts::kServiceWorkerAllowed,
&service_worker_allowed);
if (!ServiceWorkerUtils::IsPathRestrictionSatisfied(
scope_, script_url_, has_header ? &service_worker_allowed : nullptr,
&error_message)) {
Fail(blink::ServiceWorkerStatusCode::kErrorSecurity, error_message,
network::URLLoaderCompletionStatus(net::ERR_INSECURE_RESPONSE));
return;
}
}
network_loader_state_ =
ServiceWorkerUpdatedScriptLoader::LoaderState::kWaitingForBody;
network_accessed_ = response_head->network_accessed;
WriteHeaders(
base::MakeRefCounted<HttpResponseInfoIOBuffer>(std::move(response_info)));
}
void ServiceWorkerSingleScriptUpdateChecker::OnReceiveRedirect(
const net::RedirectInfo& redirect_info,
network::mojom::URLResponseHeadPtr response_head) {
TRACE_EVENT_WITH_FLOW0(
"ServiceWorker",
"ServiceWorkerSingleScriptUpdateChecker::OnReceiveRedirect", this,
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
// Resource requests for the main service worker script should not follow
// redirects.
// Step 9.5: "Set request's redirect mode to "error"."
// https://w3c.github.io/ServiceWorker/#update-algorithm
//
// TODO(https://crbug.com/889798): Follow redirects for imported scripts.
Fail(blink::ServiceWorkerStatusCode::kErrorNetwork,
ServiceWorkerConsts::kServiceWorkerRedirectError,
network::URLLoaderCompletionStatus(net::ERR_INVALID_REDIRECT));
}
void ServiceWorkerSingleScriptUpdateChecker::OnUploadProgress(
int64_t current_position,
int64_t total_size,
OnUploadProgressCallback ack_callback) {
// The network request for update checking shouldn't have upload data.
NOTREACHED();
}
void ServiceWorkerSingleScriptUpdateChecker::OnReceiveCachedMetadata(
mojo_base::BigBuffer data) {}
void ServiceWorkerSingleScriptUpdateChecker::OnTransferSizeUpdated(
int32_t transfer_size_diff) {}
void ServiceWorkerSingleScriptUpdateChecker::OnStartLoadingResponseBody(
mojo::ScopedDataPipeConsumerHandle consumer) {
TRACE_EVENT_WITH_FLOW0(
"ServiceWorker",
"ServiceWorkerSingleScriptUpdateChecker::OnStartLoadingResponseBody",
this, TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
DCHECK_EQ(network_loader_state_,
ServiceWorkerUpdatedScriptLoader::LoaderState::kWaitingForBody);
network_consumer_ = std::move(consumer);
network_loader_state_ =
ServiceWorkerUpdatedScriptLoader::LoaderState::kLoadingBody;
MaybeStartNetworkConsumerHandleWatcher();
}
void ServiceWorkerSingleScriptUpdateChecker::OnComplete(
const network::URLLoaderCompletionStatus& status) {
TRACE_EVENT_WITH_FLOW1(
"ServiceWorker", "ServiceWorkerSingleScriptUpdateChecker::OnComplete",
this, TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "status",
net::ErrorToString(status.error_code));
ServiceWorkerUpdatedScriptLoader::LoaderState previous_loader_state =
network_loader_state_;
network_loader_state_ =
ServiceWorkerUpdatedScriptLoader::LoaderState::kCompleted;
if (status.error_code != net::OK) {
Fail(blink::ServiceWorkerStatusCode::kErrorNetwork,
ServiceWorkerConsts::kServiceWorkerFetchScriptError, status);
return;
}
DCHECK(previous_loader_state ==
ServiceWorkerUpdatedScriptLoader::LoaderState::kWaitingForBody ||
previous_loader_state ==
ServiceWorkerUpdatedScriptLoader::LoaderState::kLoadingBody);
// Response body is empty.
if (previous_loader_state ==
ServiceWorkerUpdatedScriptLoader::LoaderState::kWaitingForBody) {
DCHECK_EQ(body_writer_state_,
ServiceWorkerUpdatedScriptLoader::WriterState::kNotStarted);
body_writer_state_ =
ServiceWorkerUpdatedScriptLoader::WriterState::kCompleted;
switch (header_writer_state_) {
case ServiceWorkerUpdatedScriptLoader::WriterState::kNotStarted:
NOTREACHED()
<< "Response header should be received before OnComplete()";
break;
case ServiceWorkerUpdatedScriptLoader::WriterState::kWriting:
// Wait until it's written. OnWriteHeadersComplete() will call
// Finish().
return;
case ServiceWorkerUpdatedScriptLoader::WriterState::kCompleted:
DCHECK(!network_consumer_.is_valid());
// Compare the cached data with an empty data to notify |cache_writer_|
// of the end of the comparison.
CompareData(nullptr /* pending_buffer */, 0 /* bytes_available */);
break;
}
}
// Response body exists.
if (previous_loader_state ==
ServiceWorkerUpdatedScriptLoader::LoaderState::kLoadingBody) {
switch (body_writer_state_) {
case ServiceWorkerUpdatedScriptLoader::WriterState::kNotStarted:
DCHECK_EQ(header_writer_state_,
ServiceWorkerUpdatedScriptLoader::WriterState::kWriting);
return;
case ServiceWorkerUpdatedScriptLoader::WriterState::kWriting:
DCHECK_EQ(header_writer_state_,
ServiceWorkerUpdatedScriptLoader::WriterState::kCompleted);
// Still reading the body from the network. Update checking will
// complete when all the body is read or any difference is found.
return;
case ServiceWorkerUpdatedScriptLoader::WriterState::kCompleted:
DCHECK_EQ(header_writer_state_,
ServiceWorkerUpdatedScriptLoader::WriterState::kCompleted);
// Pass empty data to notify |cache_writer_| that comparison is
// finished.
CompareData(/*pending_buffer=*/nullptr, /*bytes_available=*/0);
return;
}
}
}
// static
const char* ServiceWorkerSingleScriptUpdateChecker::ResultToString(
Result result) {
switch (result) {
case Result::kNotCompared:
return "Not compared";
case Result::kFailed:
return "Failed";
case Result::kIdentical:
return "Identical";
case Result::kDifferent:
return "Different";
}
}
//------------------------------------------------------------------------------
void ServiceWorkerSingleScriptUpdateChecker::WriteHeaders(
scoped_refptr<HttpResponseInfoIOBuffer> info_buffer) {
TRACE_EVENT_WITH_FLOW0(
"ServiceWorker", "ServiceWorkerSingleScriptUpdateChecker::WriteHeaders",
this, TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
DCHECK_EQ(header_writer_state_,
ServiceWorkerUpdatedScriptLoader::WriterState::kNotStarted);
header_writer_state_ =
ServiceWorkerUpdatedScriptLoader::WriterState::kWriting;
// Pass the header to the cache_writer_. This is written to the storage when
// the body had changes.
net::Error error = cache_writer_->MaybeWriteHeaders(
info_buffer.get(),
base::BindOnce(
&ServiceWorkerSingleScriptUpdateChecker::OnWriteHeadersComplete,
weak_factory_.GetWeakPtr()));
if (error == net::ERR_IO_PENDING) {
// OnWriteHeadersComplete() will be called asynchronously.
return;
}
// MaybeWriteHeaders() doesn't run the callback if it finishes synchronously,
// so explicitly call it here.
OnWriteHeadersComplete(error);
}
void ServiceWorkerSingleScriptUpdateChecker::OnWriteHeadersComplete(
net::Error error) {
TRACE_EVENT_WITH_FLOW1(
"ServiceWorker",
"ServiceWorkerSingleScriptUpdateChecker::OnWriteHeadersComplete", this,
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "error", error);
DCHECK_EQ(header_writer_state_,
ServiceWorkerUpdatedScriptLoader::WriterState::kWriting);
DCHECK_NE(error, net::ERR_IO_PENDING);
header_writer_state_ =
ServiceWorkerUpdatedScriptLoader::WriterState::kCompleted;
if (error != net::OK) {
// This error means the cache writer failed to read script header from
// storage.
Fail(blink::ServiceWorkerStatusCode::kErrorDiskCache,
ServiceWorkerConsts::kDatabaseErrorMessage,
network::URLLoaderCompletionStatus(error));
return;
}
MaybeStartNetworkConsumerHandleWatcher();
}
void ServiceWorkerSingleScriptUpdateChecker::
MaybeStartNetworkConsumerHandleWatcher() {
if (network_loader_state_ ==
ServiceWorkerUpdatedScriptLoader::LoaderState::kWaitingForBody) {
TRACE_EVENT_WITH_FLOW1("ServiceWorker",
"ServiceWorkerSingleScriptUpdateChecker::"
"MaybeStartNetworkConsumerHandleWatcher",
this,
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT,
"state", "wait for the body");
// OnStartLoadingResponseBody() or OnComplete() will continue the sequence.
return;
}
if (header_writer_state_ !=
ServiceWorkerUpdatedScriptLoader::WriterState::kCompleted) {
TRACE_EVENT_WITH_FLOW1("ServiceWorker",
"ServiceWorkerSingleScriptUpdateChecker::"
"MaybeStartNetworkConsumerHandleWatcher",
this,
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT,
"state", "wait for writing header");
DCHECK_EQ(header_writer_state_,
ServiceWorkerUpdatedScriptLoader::WriterState::kWriting);
// OnWriteHeadersComplete() will continue the sequence.
return;
}
TRACE_EVENT_WITH_FLOW1("ServiceWorker",
"ServiceWorkerSingleScriptUpdateChecker::"
"MaybeStartNetworkConsumerHandleWatcher",
this,
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT,
"state", "start loading body");
DCHECK_EQ(body_writer_state_,
ServiceWorkerUpdatedScriptLoader::WriterState::kNotStarted);
body_writer_state_ = ServiceWorkerUpdatedScriptLoader::WriterState::kWriting;
network_watcher_.Watch(
network_consumer_.get(),
MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
base::BindRepeating(
&ServiceWorkerSingleScriptUpdateChecker::OnNetworkDataAvailable,
weak_factory_.GetWeakPtr()));
network_watcher_.ArmOrNotify();
}
void ServiceWorkerSingleScriptUpdateChecker::OnNetworkDataAvailable(
MojoResult,
const mojo::HandleSignalsState& state) {
DCHECK_EQ(header_writer_state_,
ServiceWorkerUpdatedScriptLoader::WriterState::kCompleted);
DCHECK(network_consumer_.is_valid());
scoped_refptr<network::MojoToNetPendingBuffer> pending_buffer;
uint32_t bytes_available = 0;
MojoResult result = network::MojoToNetPendingBuffer::BeginRead(
&network_consumer_, &pending_buffer, &bytes_available);
TRACE_EVENT_WITH_FLOW2(
"ServiceWorker",
"ServiceWorkerSingleScriptUpdateChecker::OnNetworkDataAvailable", this,
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "result", result,
"bytes_available", bytes_available);
switch (result) {
case MOJO_RESULT_OK:
CompareData(std::move(pending_buffer), bytes_available);
return;
case MOJO_RESULT_FAILED_PRECONDITION:
body_writer_state_ =
ServiceWorkerUpdatedScriptLoader::WriterState::kCompleted;
// Closed by peer. This indicates all the data from the network service
// are read or there is an error. In the error case, the reason is
// notified via OnComplete().
if (network_loader_state_ ==
ServiceWorkerUpdatedScriptLoader::LoaderState::kCompleted) {
// Compare the cached data with an empty data to notify |cache_writer_|
// the end of the comparison.
CompareData(nullptr /* pending_buffer */, 0 /* bytes_available */);
}
return;
case MOJO_RESULT_SHOULD_WAIT:
network_watcher_.ArmOrNotify();
return;
}
NOTREACHED() << static_cast<int>(result);
}
// |pending_buffer| is a buffer keeping a Mojo data pipe which is going to be
// read by a cache writer. It should be kept alive until the read is done. It's
// nullptr when there is no data to be read, and that means the body from the
// network reaches the end. In that case, |bytes_to_compare| is zero.
void ServiceWorkerSingleScriptUpdateChecker::CompareData(
scoped_refptr<network::MojoToNetPendingBuffer> pending_buffer,
uint32_t bytes_to_compare) {
TRACE_EVENT_WITH_FLOW0(
"ServiceWorker", "ServiceWorkerSingleScriptUpdateChecker::CompareData",
this, TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
DCHECK(pending_buffer || bytes_to_compare == 0);
auto buffer = base::MakeRefCounted<WrappedIOBuffer>(
pending_buffer ? pending_buffer->buffer() : nullptr);
// Compare the network data and the stored data.
net::Error error = cache_writer_->MaybeWriteData(
buffer.get(), bytes_to_compare,
base::BindOnce(
&ServiceWorkerSingleScriptUpdateChecker::OnCompareDataComplete,
weak_factory_.GetWeakPtr(), pending_buffer, bytes_to_compare));
if (error == net::ERR_IO_PENDING && !cache_writer_->is_pausing()) {
// OnCompareDataComplete() will be called asynchronously.
return;
}
// MaybeWriteData() doesn't run the callback if it finishes synchronously, so
// explicitly call it here.
OnCompareDataComplete(std::move(pending_buffer), bytes_to_compare, error);
}
// |pending_buffer| is a buffer passed from CompareData(). Please refer to the
// comment on CompareData(). |error| is the result of the comparison done by the
// cache writer (which is actually reading and not yet writing to the cache,
// since it's in the comparison phase). It's net::OK when the body from the
// network and from the disk cache are the same, net::ERR_IO_PENDING if it
// detects a change in the script, or other error code if something went wrong
// reading from the disk cache.
void ServiceWorkerSingleScriptUpdateChecker::OnCompareDataComplete(
scoped_refptr<network::MojoToNetPendingBuffer> pending_buffer,
uint32_t bytes_written,
net::Error error) {
TRACE_EVENT_WITH_FLOW2(
"ServiceWorker",
"ServiceWorkerSingleScriptUpdateChecker::OnCompareDataComplete", this,
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "error", error,
"bytes_written", bytes_written);
DCHECK(pending_buffer || bytes_written == 0);
if (pending_buffer) {
// We consumed |bytes_written| bytes of data from the network so call
// CompleteRead(), regardless of what |error| is.
pending_buffer->CompleteRead(bytes_written);
network_consumer_ = pending_buffer->ReleaseHandle();
}
if (cache_writer_->is_pausing()) {
// |cache_writer_| can be pausing only when it finds difference between
// stored body and network body.
DCHECK_EQ(error, net::ERR_IO_PENDING);
Succeed(Result::kDifferent);
return;
}
if (error != net::OK) {
// Something went wrong reading from the disk cache.
Fail(blink::ServiceWorkerStatusCode::kErrorDiskCache,
ServiceWorkerConsts::kDatabaseErrorMessage,
network::URLLoaderCompletionStatus(error));
return;
}
if (bytes_written == 0) {
// All data has been read. If we reach here without any error, the script
// from the network was identical to the one in the disk cache.
Succeed(Result::kIdentical);
return;
}
network_watcher_.ArmOrNotify();
}
void ServiceWorkerSingleScriptUpdateChecker::Fail(
blink::ServiceWorkerStatusCode status,
const std::string& error_message,
network::URLLoaderCompletionStatus network_status) {
TRACE_EVENT_WITH_FLOW2("ServiceWorker",
"ServiceWorkerSingleScriptUpdateChecker::Fail", this,
TRACE_EVENT_FLAG_FLOW_IN, "status",
blink::ServiceWorkerStatusToString(status),
"error_message", error_message);
Finish(Result::kFailed,
std::make_unique<FailureInfo>(status, error_message,
std::move(network_status)));
}
void ServiceWorkerSingleScriptUpdateChecker::Succeed(Result result) {
TRACE_EVENT_WITH_FLOW1(
"ServiceWorker", "ServiceWorkerSingleScriptUpdateChecker::Succeed", this,
TRACE_EVENT_FLAG_FLOW_IN, "result", ResultToString(result));
DCHECK_NE(result, Result::kFailed);
Finish(result, nullptr);
}
void ServiceWorkerSingleScriptUpdateChecker::Finish(
Result result,
std::unique_ptr<FailureInfo> failure_info) {
network_watcher_.Cancel();
if (Result::kDifferent == result) {
auto paused_state = std::make_unique<PausedState>(
std::move(cache_writer_), std::move(network_loader_),
network_client_binding_.Unbind(), std::move(network_consumer_),
network_loader_state_, body_writer_state_);
std::move(callback_).Run(script_url_, result, nullptr,
std::move(paused_state));
return;
}
network_loader_.reset();
network_client_binding_.Close();
network_consumer_.reset();
std::move(callback_).Run(script_url_, result, std::move(failure_info),
nullptr);
}
ServiceWorkerSingleScriptUpdateChecker::PausedState::PausedState(
std::unique_ptr<ServiceWorkerCacheWriter> cache_writer,
std::unique_ptr<
ServiceWorkerUpdatedScriptLoader::ThrottlingURLLoaderCoreWrapper>
network_loader,
network::mojom::URLLoaderClientRequest network_client_request,
mojo::ScopedDataPipeConsumerHandle network_consumer,
ServiceWorkerUpdatedScriptLoader::LoaderState network_loader_state,
ServiceWorkerUpdatedScriptLoader::WriterState body_writer_state)
: cache_writer(std::move(cache_writer)),
network_loader(std::move(network_loader)),
network_client_request(std::move(network_client_request)),
network_consumer(std::move(network_consumer)),
network_loader_state(network_loader_state),
body_writer_state(body_writer_state) {}
ServiceWorkerSingleScriptUpdateChecker::PausedState::~PausedState() = default;
ServiceWorkerSingleScriptUpdateChecker::FailureInfo::FailureInfo(
blink::ServiceWorkerStatusCode status,
const std::string& error_message,
network::URLLoaderCompletionStatus network_status)
: status(status),
error_message(error_message),
network_status(network_status) {}
ServiceWorkerSingleScriptUpdateChecker::FailureInfo::~FailureInfo() = default;
} // namespace content