blob: 04a6ce3a26b4bd98269950b6a7ede2e2c9fc91d6 [file] [log] [blame]
// Copyright 2020 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_update_job_cache_copier.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/single_thread_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "content/browser/appcache/appcache_disk_cache_ops.h"
#include "content/browser/appcache/appcache_response_info.h"
#include "content/browser/appcache/appcache_service_impl.h"
#include "content/browser/appcache/appcache_update_metrics_recorder.h"
#include "content/browser/appcache/appcache_update_url_fetcher.h"
#include "content/browser/appcache/appcache_update_url_loader_request.h"
#include "net/http/http_response_headers.h"
namespace content {
namespace {
const int kAppCacheFetchBufferSize = 32768;
void UpdateHttpInfo(net::HttpResponseInfo* response,
net::HttpResponseInfo* new_response,
AppCacheUpdateMetricsRecorder* metrics) {
response->headers->Update(*new_response->headers.get());
response->stale_revalidate_timeout = base::Time();
bool was_corrupt = false;
if (response->response_time.is_null() || response->request_time.is_null()) {
was_corrupt = true;
// Metrics for corrupt resources are tracked in AppCacheUpdateJob's
// CanUseExistingResource(), so we don't change anything here to avoid
// double-counting.
}
response->response_time = new_response->response_time;
response->request_time = new_response->request_time;
if (was_corrupt && !response->response_time.is_null() &&
!response->request_time.is_null()) {
metrics->IncrementExistingCorruptionFixedInUpdate();
}
response->network_accessed = new_response->network_accessed;
response->unused_since_prefetch = new_response->unused_since_prefetch;
response->restricted_prefetch = new_response->restricted_prefetch;
response->ssl_info = new_response->ssl_info;
if (new_response->vary_data.is_valid()) {
response->vary_data = new_response->vary_data;
} else if (response->vary_data.is_valid()) {
// There is a vary header in the stored response but not in the current one.
// This requires access to the request's HttpRequestInfo which we don't
// have. See HttpCache::Transaction::DoUpdateCachedResponse() for more
// info.
metrics->IncrementExistingVaryDuring304();
}
}
} // namespace
AppCacheUpdateJob::CacheCopier::CacheCopier(
AppCacheUpdateJob* job,
const GURL& url,
const GURL& manifest_url,
std::unique_ptr<AppCacheUpdateJob::URLFetcher> incoming_fetcher,
std::unique_ptr<AppCacheResponseWriter> response_writer)
: job_(job),
url_(url),
manifest_url_(manifest_url),
incoming_fetcher_(std::move(incoming_fetcher)),
response_writer_(std::move(response_writer)),
state_(State::kIdle),
read_data_size_(0) {
DCHECK_EQ(job_->internal_state_, AppCacheUpdateJobState::DOWNLOADING);
}
AppCacheUpdateJob::CacheCopier::~CacheCopier() = default;
void AppCacheUpdateJob::CacheCopier::Cancel() {
state_ = State::kCanceled;
}
void AppCacheUpdateJob::CacheCopier::Run() {
if (job_->IsFinished()) {
Cancel();
return;
}
DCHECK_EQ(state_, State::kIdle);
DCHECK_EQ(job_->internal_state_, AppCacheUpdateJobState::DOWNLOADING);
DCHECK(!existing_appcache_response_info_);
DCHECK(incoming_fetcher_->existing_entry().has_response_id());
DCHECK(!read_data_response_reader_);
DCHECK(!read_data_buffer_);
DCHECK(!merged_http_info_);
DCHECK(response_writer_);
incoming_http_info_ = std::make_unique<net::HttpResponseInfo>(
incoming_fetcher_->request()->GetResponseInfo());
read_data_response_reader_ = job_->service_->storage()->CreateResponseReader(
manifest_url_, incoming_fetcher_->existing_entry().response_id());
read_data_buffer_ =
base::MakeRefCounted<net::IOBuffer>(kAppCacheFetchBufferSize);
state_ = State::kAwaitReadInfoComplete;
job_->service_->storage()->LoadResponseInfo(
manifest_url_, incoming_fetcher_->existing_entry().response_id(), this);
// Async continues in |OnResponseInfoLoaded|.
}
void AppCacheUpdateJob::CacheCopier::OnResponseInfoLoaded(
AppCacheResponseInfo* response_info,
int64_t response_id) {
if (job_->IsFinished()) {
Cancel();
return;
}
DCHECK(response_info);
DCHECK_EQ(state_, State::kAwaitReadInfoComplete);
DCHECK_EQ(job_->internal_state_, AppCacheUpdateJobState::DOWNLOADING);
DCHECK_EQ(incoming_fetcher_->existing_entry().response_id(), response_id);
DCHECK(!existing_appcache_response_info_);
DCHECK(!merged_http_info_);
existing_appcache_response_info_ = response_info;
// Update the cached HttpResponseInfo with the incoming HttpResponseInfo.
merged_http_info_ = std::make_unique<net::HttpResponseInfo>(
existing_appcache_response_info_->http_response_info());
UpdateHttpInfo(merged_http_info_.get(), incoming_http_info_.get(),
&job_->update_metrics_);
// Get ready to write the merged HttpResponseInfo to disk.
scoped_refptr<HttpResponseInfoIOBuffer> io_buffer =
base::MakeRefCounted<HttpResponseInfoIOBuffer>(
std::move(merged_http_info_));
// Prepare for next state.
state_ = State::kAwaitWriteInfoComplete;
// Trigger async WriteInfo() call.
response_writer_->WriteInfo(
io_buffer.get(),
base::BindOnce(&AppCacheUpdateJob::CacheCopier::OnWriteInfoComplete,
base::Unretained(this)));
// Async continues in |OnWriteInfoComplete| with state kOnWriteInfoComplete.
}
void AppCacheUpdateJob::CacheCopier::OnWriteInfoComplete(int result) {
if (job_->IsFinished()) {
Cancel();
return;
}
DCHECK_EQ(job_->internal_state_, AppCacheUpdateJobState::DOWNLOADING);
DCHECK_EQ(state_, State::kAwaitWriteInfoComplete);
DCHECK_GT(result, 0);
// Prepare for next state.
result = 0;
state_ = State::kReadData;
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&AppCacheUpdateJob::CacheCopier::ReadData,
base::Unretained(this)));
// Async continues in |ReadData| with state kReadData.
}
void AppCacheUpdateJob::CacheCopier::ReadData() {
if (job_->IsFinished()) {
Cancel();
return;
}
DCHECK_EQ(job_->internal_state_, AppCacheUpdateJobState::DOWNLOADING);
DCHECK_EQ(state_, State::kReadData);
DCHECK(read_data_response_reader_);
DCHECK(read_data_buffer_);
// Prepare for next state.
state_ = State::kAwaitReadDataComplete;
read_data_response_reader_->ReadData(
read_data_buffer_.get(), kAppCacheFetchBufferSize,
base::BindOnce(&AppCacheUpdateJob::CacheCopier::OnReadDataComplete,
base::Unretained(this)));
// Async continues in |OnReadDataComplete| with state kAwaitReadDataComplete.
}
void AppCacheUpdateJob::CacheCopier::OnReadDataComplete(int result) {
if (job_->IsFinished()) {
Cancel();
return;
}
DCHECK_EQ(job_->internal_state_, AppCacheUpdateJobState::DOWNLOADING);
DCHECK_EQ(state_, State::kAwaitReadDataComplete);
DCHECK_GE(result, 0);
if (result == 0) {
// If there's no more to read, then we're done copying data.
// Prepare for next state.
state_ = State::kCopyComplete;
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&AppCacheUpdateJob::CacheCopier::CopyComplete,
base::Unretained(this)));
// Async continues in |CopyComplete| with state kCopyComplete.
return;
}
read_data_size_ = result;
// Prepare for next state.
state_ = State::kAwaitWriteDataComplete;
// Trigger async WriteData() call.
response_writer_->WriteData(
read_data_buffer_.get(), read_data_size_,
base::BindOnce(&AppCacheUpdateJob::CacheCopier::OnWriteDataComplete,
base::Unretained(this)));
// Async continues in |OnWriteDataComplete| with state
// kAwaitWriteDataComplete.
}
void AppCacheUpdateJob::CacheCopier::OnWriteDataComplete(int result) {
if (job_->IsFinished()) {
Cancel();
return;
}
DCHECK_EQ(job_->internal_state_, AppCacheUpdateJobState::DOWNLOADING);
DCHECK_EQ(state_, State::kAwaitWriteDataComplete);
DCHECK_GE(result, 0);
DCHECK_EQ(read_data_size_, result);
// Prepare for next state.
read_data_size_ = 0;
state_ = State::kReadData;
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&AppCacheUpdateJob::CacheCopier::ReadData,
base::Unretained(this)));
// Async continues in |ReadData| with state kReadData.
}
void AppCacheUpdateJob::CacheCopier::CopyComplete() {
if (job_->IsFinished()) {
Cancel();
return;
}
DCHECK_EQ(job_->internal_state_, AppCacheUpdateJobState::DOWNLOADING);
DCHECK_EQ(state_, State::kCopyComplete);
// |job_| owns |this|, so calling ContinueHandleResourceFetchCompleted
// will result in |this| being deleted by |job_|. Post this as a task to
// allow control flow here to exit first.
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&AppCacheUpdateJob::ContinueHandleResourceFetchCompleted,
base::Unretained(job_), url_,
base::Owned(std::move(incoming_fetcher_))));
}
} // namespace content