blob: a3a58feb64c22d1cf4b0331fa7265a3958b26518 [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 "content/browser/appcache/appcache_response.h"
#include "content/browser/service_worker/service_worker_cache_writer.h"
#include "content/public/common/resource_type.h"
#include "mojo/public/cpp/system/simple_watcher.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/mime_util/mime_util.h"
// TODO(momohatt): Use ServiceWorkerMetrics for UMA.
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
namespace content {
ServiceWorkerSingleScriptUpdateChecker::ServiceWorkerSingleScriptUpdateChecker(
const GURL& url,
int64_t resource_id,
bool is_main_script,
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_(url),
resource_id_(resource_id),
network_client_binding_(this),
network_watcher_(FROM_HERE,
mojo::SimpleWatcher::ArmingPolicy::MANUAL,
base::SequencedTaskRunnerHandle::Get()),
callback_(std::move(callback)),
weak_factory_(this) {
network::ResourceRequest resource_request;
resource_request.url = url;
resource_request.resource_type =
is_main_script ? RESOURCE_TYPE_SERVICE_WORKER : RESOURCE_TYPE_SCRIPT;
resource_request.do_not_prompt_for_login = true;
if (is_main_script)
resource_request.headers.SetHeader("Service-Worker", "script");
// TODO(momohatt): Handle cases where force_bypass_cache is enabled.
// |compare_reader| shouldn't be a nullptr, which forces
// ServiceWorkerCacheWriter to do the comparison.
DCHECK(compare_reader);
cache_writer_ = std::make_unique<ServiceWorkerCacheWriter>(
std::move(compare_reader), std::move(copy_reader), std::move(writer),
true /* pause_when_not_identical */);
network::mojom::URLLoaderClientPtr network_client;
network_client_binding_.Bind(mojo::MakeRequest(&network_client));
loader_factory->CreateLoaderAndStart(
mojo::MakeRequest(&network_loader_), -1 /* routing_id */,
-1 /* request_id */, network::mojom::kURLLoadOptionNone, resource_request,
std::move(network_client),
net::MutableNetworkTrafficAnnotationTag(kUpdateCheckTrafficAnnotation));
DCHECK_EQ(NetworkLoaderState::kNotStarted, network_loader_state_);
network_loader_state_ = NetworkLoaderState::kLoadingHeader;
}
ServiceWorkerSingleScriptUpdateChecker::
~ServiceWorkerSingleScriptUpdateChecker() = default;
// URLLoaderClient override ----------------------------------------------------
void ServiceWorkerSingleScriptUpdateChecker::OnReceiveResponse(
const network::ResourceResponseHead& response_head) {
DCHECK_EQ(NetworkLoaderState::kLoadingHeader, network_loader_state_);
// We don't have complete info here, but fill in what we have now.
// At least we need headers and SSL info.
auto response_info = std::make_unique<net::HttpResponseInfo>();
response_info->headers = response_head.headers;
if (response_head.ssl_info.has_value())
response_info->ssl_info = *response_head.ssl_info;
response_info->was_fetched_via_spdy = response_head.was_fetched_via_spdy;
response_info->was_alpn_negotiated = response_head.was_alpn_negotiated;
response_info->alpn_negotiated_protocol =
response_head.alpn_negotiated_protocol;
response_info->connection_info = response_head.connection_info;
response_info->socket_address = response_head.socket_address;
// TODO(momohatt): Check for header errors.
network_loader_state_ = NetworkLoaderState::kWaitingForBody;
WriteHeaders(
base::MakeRefCounted<HttpResponseInfoIOBuffer>(std::move(response_info)));
}
void ServiceWorkerSingleScriptUpdateChecker::OnReceiveRedirect(
const net::RedirectInfo& redirect_info,
const network::ResourceResponseHead& response_head) {
// TODO(momohatt): Raise error and terminate the update check here, like
// ServiceWorkerNewScriptLoader does.
NOTIMPLEMENTED();
}
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(
const std::vector<uint8_t>& data) {}
void ServiceWorkerSingleScriptUpdateChecker::OnTransferSizeUpdated(
int32_t transfer_size_diff) {}
void ServiceWorkerSingleScriptUpdateChecker::OnStartLoadingResponseBody(
mojo::ScopedDataPipeConsumerHandle consumer) {
DCHECK_EQ(NetworkLoaderState::kWaitingForBody, network_loader_state_);
network_consumer_ = std::move(consumer);
network_loader_state_ = NetworkLoaderState::kLoadingBody;
MaybeStartNetworkConsumerHandleWatcher();
}
void ServiceWorkerSingleScriptUpdateChecker::OnComplete(
const network::URLLoaderCompletionStatus& status) {
NetworkLoaderState previous_loader_state = network_loader_state_;
network_loader_state_ = NetworkLoaderState::kCompleted;
if (status.error_code != net::OK) {
Finish(Result::kFailed);
return;
}
DCHECK(previous_loader_state == NetworkLoaderState::kWaitingForBody ||
previous_loader_state == NetworkLoaderState::kLoadingBody);
// Response body is empty.
if (previous_loader_state == NetworkLoaderState::kWaitingForBody) {
DCHECK_EQ(CacheWriterState::kNotStarted, body_writer_state_);
body_writer_state_ = CacheWriterState::kCompleted;
switch (header_writer_state_) {
case CacheWriterState::kNotStarted:
NOTREACHED()
<< "Response header should be received before OnComplete()";
break;
case CacheWriterState::kWriting:
// Wait until it's written. OnWriteHeadersComplete() will call
// Finish().
return;
case CacheWriterState::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 == NetworkLoaderState::kLoadingBody) {
switch (body_writer_state_) {
case CacheWriterState::kNotStarted:
DCHECK_EQ(CacheWriterState::kWriting, header_writer_state_);
return;
case CacheWriterState::kWriting:
DCHECK_EQ(CacheWriterState::kCompleted, header_writer_state_);
return;
case CacheWriterState::kCompleted:
DCHECK_EQ(CacheWriterState::kCompleted, header_writer_state_);
Finish(Result::kIdentical);
return;
}
}
}
//------------------------------------------------------------------------------
void ServiceWorkerSingleScriptUpdateChecker::WriteHeaders(
scoped_refptr<HttpResponseInfoIOBuffer> info_buffer) {
DCHECK_EQ(CacheWriterState::kNotStarted, header_writer_state_);
header_writer_state_ = CacheWriterState::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) {
DCHECK_EQ(CacheWriterState::kWriting, header_writer_state_);
DCHECK_NE(net::ERR_IO_PENDING, error);
header_writer_state_ = CacheWriterState::kCompleted;
if (error != net::OK) {
Finish(Result::kFailed);
return;
}
// Response body is empty.
if (network_loader_state_ == NetworkLoaderState::kCompleted &&
body_writer_state_ == CacheWriterState::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;
}
MaybeStartNetworkConsumerHandleWatcher();
}
void ServiceWorkerSingleScriptUpdateChecker::
MaybeStartNetworkConsumerHandleWatcher() {
if (network_loader_state_ == NetworkLoaderState::kWaitingForBody) {
// OnStartLoadingResponseBody() or OnComplete() will continue the sequence.
return;
}
if (header_writer_state_ != CacheWriterState::kCompleted) {
DCHECK_EQ(CacheWriterState::kWriting, header_writer_state_);
// OnWriteHeadersComplete() will continue the sequence.
return;
}
DCHECK_EQ(CacheWriterState::kNotStarted, body_writer_state_);
body_writer_state_ = CacheWriterState::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(CacheWriterState::kCompleted, header_writer_state_);
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);
switch (result) {
case MOJO_RESULT_OK:
CompareData(std::move(pending_buffer), bytes_available);
return;
case MOJO_RESULT_FAILED_PRECONDITION:
// 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_ == NetworkLoaderState::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);
}
void ServiceWorkerSingleScriptUpdateChecker::CompareData(
scoped_refptr<network::MojoToNetPendingBuffer> pending_buffer,
uint32_t bytes_to_compare) {
auto buffer = base::MakeRefCounted<net::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(),
base::WrapRefCounted(pending_buffer.get()), bytes_to_compare));
if (pending_buffer) {
pending_buffer->CompleteRead(bytes_to_compare);
network_consumer_ = pending_buffer->ReleaseHandle();
}
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);
}
void ServiceWorkerSingleScriptUpdateChecker::OnCompareDataComplete(
scoped_refptr<network::MojoToNetPendingBuffer> pending_buffer,
uint32_t bytes_written,
net::Error error) {
if (cache_writer_->is_pausing()) {
// |cache_writer_| can be pausing only when it finds difference between
// stored body and network body.
DCHECK_EQ(net::ERR_IO_PENDING, error);
Finish(Result::kDifferent);
return;
}
if (!pending_buffer || error != net::OK) {
Finish(Result::kIdentical);
return;
}
DCHECK(pending_buffer);
network_watcher_.ArmOrNotify();
}
void ServiceWorkerSingleScriptUpdateChecker::Finish(Result result) {
network_watcher_.Cancel();
network_loader_state_ = NetworkLoaderState::kCompleted;
header_writer_state_ = CacheWriterState::kCompleted;
body_writer_state_ = CacheWriterState::kCompleted;
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_));
std::move(callback_).Run(script_url_, resource_id_, result,
std::move(paused_state));
return;
}
network_loader_.reset();
network_client_binding_.Close();
network_consumer_.reset();
std::move(callback_).Run(script_url_, resource_id_, result, nullptr);
}
ServiceWorkerSingleScriptUpdateChecker::PausedState::PausedState(
std::unique_ptr<ServiceWorkerCacheWriter> cache_writer,
network::mojom::URLLoaderPtr network_loader,
network::mojom::URLLoaderClientRequest network_client_request,
mojo::ScopedDataPipeConsumerHandle network_consumer)
: 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)) {}
ServiceWorkerSingleScriptUpdateChecker::PausedState::~PausedState() = default;
} // namespace content