blob: 82d9d153754c562fac3e5705cb42f33e03412233 [file] [log] [blame]
// Copyright (c) 2017 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/appcache/appcache_url_loader_job.h"
#include "base/strings/string_number_conversions.h"
#include "content/browser/appcache/appcache_histograms.h"
#include "content/browser/appcache/appcache_request_handler.h"
#include "content/browser/appcache/appcache_subresource_url_factory.h"
#include "content/browser/appcache/appcache_url_loader_request.h"
#include "content/browser/url_loader_factory_getter.h"
#include "content/public/common/resource_type.h"
#include "net/http/http_status_code.h"
#include "services/network/public/cpp/net_adapters.h"
namespace content {
AppCacheURLLoaderJob::~AppCacheURLLoaderJob() {
if (storage_.get())
storage_->CancelDelegateCallbacks(this);
}
bool AppCacheURLLoaderJob::IsStarted() const {
return delivery_type_ != AWAITING_DELIVERY_ORDERS &&
delivery_type_ != NETWORK_DELIVERY;
}
void AppCacheURLLoaderJob::DeliverAppCachedResponse(const GURL& manifest_url,
int64_t cache_id,
const AppCacheEntry& entry,
bool is_fallback) {
if (!storage_.get() || !appcache_request_) {
DeliverErrorResponse();
return;
}
delivery_type_ = APPCACHED_DELIVERY;
// In tests we only care about the delivery_type_ state.
if (AppCacheRequestHandler::IsRunningInTests())
return;
load_timing_info_.request_start_time = base::Time::Now();
load_timing_info_.request_start = base::TimeTicks::Now();
AppCacheHistograms::AddAppCacheJobStartDelaySample(base::TimeTicks::Now() -
start_time_tick_);
manifest_url_ = manifest_url;
cache_id_ = cache_id;
entry_ = entry;
is_fallback_ = is_fallback;
if (is_fallback_ && loader_callback_)
CallLoaderCallback();
InitializeRangeRequestInfo(appcache_request_->GetResourceRequest()->headers);
storage_->LoadResponseInfo(manifest_url_, entry_.response_id(), this);
}
void AppCacheURLLoaderJob::DeliverNetworkResponse() {
delivery_type_ = NETWORK_DELIVERY;
// In tests we only care about the delivery_type_ state.
if (AppCacheRequestHandler::IsRunningInTests())
return;
AppCacheHistograms::AddNetworkJobStartDelaySample(base::TimeTicks::Now() -
start_time_tick_);
// We signal our caller with an empy callback that it needs to perform
// the network load.
DCHECK(loader_callback_ && !binding_.is_bound());
std::move(loader_callback_).Run(StartLoaderCallback());
weak_factory_.InvalidateWeakPtrs();
is_deleting_soon_ = true;
base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, this);
}
void AppCacheURLLoaderJob::DeliverErrorResponse() {
delivery_type_ = ERROR_DELIVERY;
// In tests we only care about the delivery_type_ state.
if (AppCacheRequestHandler::IsRunningInTests())
return;
if (loader_callback_)
CallLoaderCallback();
NotifyCompleted(net::ERR_FAILED);
AppCacheHistograms::AddErrorJobStartDelaySample(base::TimeTicks::Now() -
start_time_tick_);
}
AppCacheURLLoaderJob* AppCacheURLLoaderJob::AsURLLoaderJob() {
return this;
}
base::WeakPtr<AppCacheJob> AppCacheURLLoaderJob::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
base::WeakPtr<AppCacheURLLoaderJob> AppCacheURLLoaderJob::GetDerivedWeakPtr() {
return weak_factory_.GetWeakPtr();
}
void AppCacheURLLoaderJob::FollowRedirect() {
NOTREACHED() << "appcache never produces redirects";
}
void AppCacheURLLoaderJob::ProceedWithResponse() {
// TODO(arthursonzogni): Implement this if AppCache starts using the
// AppCacheURLLoader before the Network Service has shipped.
NOTREACHED();
}
void AppCacheURLLoaderJob::SetPriority(net::RequestPriority priority,
int32_t intra_priority_value) {}
void AppCacheURLLoaderJob::PauseReadingBodyFromNet() {}
void AppCacheURLLoaderJob::ResumeReadingBodyFromNet() {}
void AppCacheURLLoaderJob::DeleteIfNeeded() {
if (binding_.is_bound() || is_deleting_soon_)
return;
delete this;
}
void AppCacheURLLoaderJob::Start(mojom::URLLoaderRequest request,
mojom::URLLoaderClientPtr client) {
DCHECK(!binding_.is_bound());
binding_.Bind(std::move(request));
client_ = std::move(client);
binding_.set_connection_error_handler(base::BindOnce(
&AppCacheURLLoaderJob::OnConnectionError, GetDerivedWeakPtr()));
}
AppCacheURLLoaderJob::AppCacheURLLoaderJob(
AppCacheURLLoaderRequest* appcache_request,
AppCacheStorage* storage,
LoaderCallback loader_callback)
: storage_(storage->GetWeakPtr()),
start_time_tick_(base::TimeTicks::Now()),
cache_id_(kAppCacheNoCacheId),
is_fallback_(false),
binding_(this),
writable_handle_watcher_(FROM_HERE,
mojo::SimpleWatcher::ArmingPolicy::MANUAL),
loader_callback_(std::move(loader_callback)),
appcache_request_(appcache_request->GetWeakPtr()),
is_main_resource_load_(IsResourceTypeFrame(static_cast<ResourceType>(
appcache_request->GetResourceRequest()->resource_type))),
weak_factory_(this) {}
void AppCacheURLLoaderJob::CallLoaderCallback() {
DCHECK(loader_callback_);
DCHECK(!binding_.is_bound());
std::move(loader_callback_)
.Run(base::BindOnce(&AppCacheURLLoaderJob::Start, GetDerivedWeakPtr()));
DCHECK(binding_.is_bound());
}
void AppCacheURLLoaderJob::OnResponseInfoLoaded(
AppCacheResponseInfo* response_info,
int64_t response_id) {
DCHECK(IsDeliveringAppCacheResponse());
if (!storage_.get()) {
DeliverErrorResponse();
return;
}
if (response_info) {
if (loader_callback_)
CallLoaderCallback();
info_ = response_info;
reader_.reset(
storage_->CreateResponseReader(manifest_url_, entry_.response_id()));
if (is_range_request())
SetupRangeResponse();
response_body_stream_ = std::move(data_pipe_.producer_handle);
// TODO(ananta)
// Move the asynchronous reading and mojo pipe handling code to a helper
// class. That would also need a change to BlobURLLoader.
// Wait for the data pipe to be ready to accept data.
writable_handle_watcher_.Watch(
response_body_stream_.get(), MOJO_HANDLE_SIGNAL_WRITABLE,
base::Bind(&AppCacheURLLoaderJob::OnResponseBodyStreamReady,
GetDerivedWeakPtr()));
SendResponseInfo();
ReadMore();
return;
}
// We failed to load the response headers from the disk cache.
if (storage_->service()->storage() == storage_.get()) {
// A resource that is expected to be in the appcache is missing.
// If the 'check' fails, the corrupt appcache will be deleted.
// See http://code.google.com/p/chromium/issues/detail?id=50657
storage_->service()->CheckAppCacheResponse(manifest_url_, cache_id_,
entry_.response_id());
AppCacheHistograms::CountResponseRetrieval(false, is_main_resource_load_,
manifest_url_.GetOrigin());
}
cache_entry_not_found_ = true;
// We fallback to the network unless this job was falling back to the
// appcache from the network which had already failed in some way.
if (!is_fallback_)
DeliverNetworkResponse();
else
DeliverErrorResponse();
}
void AppCacheURLLoaderJob::OnReadComplete(int result) {
if (result > 0) {
uint32_t bytes_written = static_cast<uint32_t>(result);
response_body_stream_ = pending_write_->Complete(bytes_written);
pending_write_ = nullptr;
ReadMore();
return;
}
writable_handle_watcher_.Cancel();
pending_write_->Complete(0);
pending_write_ = nullptr;
NotifyCompleted(result);
}
void AppCacheURLLoaderJob::OnResponseBodyStreamReady(MojoResult result) {
// TODO(ananta)
// Add proper error handling here.
if (result != MOJO_RESULT_OK) {
DCHECK(false);
NotifyCompleted(net::ERR_FAILED);
return;
}
ReadMore();
}
void AppCacheURLLoaderJob::OnConnectionError() {
if (storage_.get())
storage_->CancelDelegateCallbacks(this);
weak_factory_.InvalidateWeakPtrs();
is_deleting_soon_ = true;
base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, this);
}
void AppCacheURLLoaderJob::SendResponseInfo() {
DCHECK(client_);
// If this is null it means the response information was sent to the client.
if (!data_pipe_.consumer_handle.is_valid())
return;
const net::HttpResponseInfo* http_info = is_range_request()
? range_response_info_.get()
: info_->http_response_info();
ResourceResponseHead response_head;
response_head.headers = http_info->headers;
response_head.appcache_id = cache_id_;
response_head.appcache_manifest_url = manifest_url_;
http_info->headers->GetMimeType(&response_head.mime_type);
http_info->headers->GetCharset(&response_head.charset);
// TODO(ananta)
// Verify if the times sent here are correct.
response_head.request_time = http_info->request_time;
response_head.response_time = http_info->response_time;
response_head.content_length =
is_range_request() ? range_response_info_->headers->GetContentLength()
: info_->response_data_size();
response_head.connection_info = http_info->connection_info;
response_head.socket_address = http_info->socket_address;
response_head.was_fetched_via_spdy = http_info->was_fetched_via_spdy;
response_head.was_alpn_negotiated = http_info->was_alpn_negotiated;
response_head.alpn_negotiated_protocol = http_info->alpn_negotiated_protocol;
response_head.load_timing = load_timing_info_;
client_->OnReceiveResponse(response_head, http_info->ssl_info,
mojom::DownloadedTempFilePtr());
client_->OnStartLoadingResponseBody(std::move(data_pipe_.consumer_handle));
}
void AppCacheURLLoaderJob::ReadMore() {
DCHECK(!pending_write_.get());
uint32_t num_bytes;
// TODO: we should use the abstractions in MojoAsyncResourceHandler.
MojoResult result = network::NetToMojoPendingBuffer::BeginWrite(
&response_body_stream_, &pending_write_, &num_bytes);
if (result == MOJO_RESULT_SHOULD_WAIT) {
// The pipe is full. We need to wait for it to have more space.
writable_handle_watcher_.ArmOrNotify();
return;
} else if (result != MOJO_RESULT_OK) {
NotifyCompleted(net::ERR_FAILED);
writable_handle_watcher_.Cancel();
response_body_stream_.reset();
return;
}
CHECK_GT(static_cast<uint32_t>(std::numeric_limits<int>::max()), num_bytes);
auto buffer =
base::MakeRefCounted<network::NetToMojoIOBuffer>(pending_write_.get());
uint32_t bytes_to_read =
std::min<uint32_t>(num_bytes, info_->response_data_size());
reader_->ReadData(
buffer.get(), bytes_to_read,
base::Bind(&AppCacheURLLoaderJob::OnReadComplete, GetDerivedWeakPtr()));
}
void AppCacheURLLoaderJob::NotifyCompleted(int error_code) {
if (storage_.get())
storage_->CancelDelegateCallbacks(this);
if (AppCacheRequestHandler::IsRunningInTests())
return;
network::URLLoaderCompletionStatus status(error_code);
if (!error_code) {
const net::HttpResponseInfo* http_info =
is_range_request() ? range_response_info_.get()
: (info_ ? info_->http_response_info() : nullptr);
status.exists_in_cache = http_info->was_cached;
status.completion_time = base::TimeTicks::Now();
status.encoded_body_length =
is_range_request() ? range_response_info_->headers->GetContentLength()
: (info_ ? info_->response_data_size() : 0);
status.decoded_body_length = status.encoded_body_length;
}
client_->OnComplete(status);
if (delivery_type_ == APPCACHED_DELIVERY) {
AppCacheHistograms::CountResponseRetrieval(
error_code == 0, is_main_resource_load_, manifest_url_.GetOrigin());
}
}
} // namespace content