// Copyright (c) 2011 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 "chrome/browser/renderer_host/offline_resource_handler.h"
#include <vector>
#include "base/logging.h"
#include "base/memory/singleton.h"
#include "base/metrics/histogram.h"
#include "base/string_util.h"
#include "chrome/browser/chromeos/network_state_notifier.h"
#include "chrome/browser/chromeos/offline/offline_load_page.h"
#include "chrome/browser/net/chrome_url_request_context.h"
#include "chrome/common/url_constants.h"
#include "content/browser/browser_thread.h"
#include "content/browser/renderer_host/resource_dispatcher_host.h"
#include "content/browser/renderer_host/resource_dispatcher_host_request_info.h"
#include "net/base/net_errors.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_context.h"
OfflineResourceHandler::OfflineResourceHandler(
ResourceHandler* handler,
int host_id,
int route_id,
ResourceDispatcherHost* rdh,
net::URLRequest* request)
: next_handler_(handler),
process_host_id_(host_id),
render_view_id_(route_id),
rdh_(rdh),
request_(request),
deferred_request_id_(-1) {
}
OfflineResourceHandler::~OfflineResourceHandler() {
DCHECK(!appcache_completion_callback_.get());
}
bool OfflineResourceHandler::OnUploadProgress(int request_id,
uint64 position,
uint64 size) {
return next_handler_->OnUploadProgress(request_id, position, size);
}
bool OfflineResourceHandler::OnRequestRedirected(int request_id,
const GURL& new_url,
ResourceResponse* response,
bool* defer) {
return next_handler_->OnRequestRedirected(
request_id, new_url, response, defer);
}
bool OfflineResourceHandler::OnResponseStarted(int request_id,
ResourceResponse* response) {
return next_handler_->OnResponseStarted(request_id, response);
}
bool OfflineResourceHandler::OnResponseCompleted(
int request_id,
const net::URLRequestStatus& status,
const std::string& security_info) {
return next_handler_->OnResponseCompleted(request_id, status, security_info);
}
void OfflineResourceHandler::OnRequestClosed() {
if (appcache_completion_callback_) {
appcache_completion_callback_->Cancel();
appcache_completion_callback_.release();
Release(); // Balanced with OnWillStart
}
next_handler_->OnRequestClosed();
}
void OfflineResourceHandler::OnCanHandleOfflineComplete(int rv) {
CHECK(appcache_completion_callback_);
appcache_completion_callback_ = NULL;
if (deferred_request_id_ == -1) {
LOG(WARNING) << "OnCanHandleOfflineComplete called after completion: "
<< " this=" << this;
NOTREACHED();
return;
}
if (rv == net::OK) {
Resume();
Release(); // Balanced with OnWillStart
} else {
// Skipping AddRef/Release because they're redundant.
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
NewRunnableMethod(this, &OfflineResourceHandler::ShowOfflinePage));
}
}
bool OfflineResourceHandler::OnWillStart(int request_id,
const GURL& url,
bool* defer) {
if (ShouldShowOfflinePage(url)) {
deferred_request_id_ = request_id;
deferred_url_ = url;
DVLOG(1) << "OnWillStart: this=" << this << ", request id=" << request_id
<< ", url=" << url;
AddRef(); // Balanced with OnCanHandleOfflineComplete
DCHECK(!appcache_completion_callback_);
appcache_completion_callback_ =
new net::CancelableCompletionCallback<OfflineResourceHandler>(
this, &OfflineResourceHandler::OnCanHandleOfflineComplete);
ChromeURLRequestContext* url_request_context =
static_cast<ChromeURLRequestContext*>(request_->context());
url_request_context->appcache_service()->CanHandleMainResourceOffline(
url, appcache_completion_callback_);
*defer = true;
return true;
}
return next_handler_->OnWillStart(request_id, url, defer);
}
// We'll let the original event handler provide a buffer, and reuse it for
// subsequent reads until we're done buffering.
bool OfflineResourceHandler::OnWillRead(int request_id, net::IOBuffer** buf,
int* buf_size, int min_size) {
return next_handler_->OnWillRead(request_id, buf, buf_size, min_size);
}
bool OfflineResourceHandler::OnReadCompleted(int request_id, int* bytes_read) {
return next_handler_->OnReadCompleted(request_id, bytes_read);
}
void OfflineResourceHandler::OnBlockingPageComplete(bool proceed) {
if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
NewRunnableMethod(this,
&OfflineResourceHandler::OnBlockingPageComplete,
proceed));
return;
}
if (deferred_request_id_ == -1) {
LOG(WARNING) << "OnBlockingPageComplete called after completion: "
<< " this=" << this;
NOTREACHED();
return;
}
if (proceed) {
Resume();
} else {
int request_id = deferred_request_id_;
ClearRequestInfo();
rdh_->CancelRequest(process_host_id_, request_id, false);
}
Release(); // Balanced with OnWillStart
}
void OfflineResourceHandler::ClearRequestInfo() {
deferred_url_ = GURL();
deferred_request_id_ = -1;
}
bool OfflineResourceHandler::IsRemote(const GURL& url) const {
return url.SchemeIs(chrome::kFtpScheme) ||
url.SchemeIs(chrome::kHttpScheme) ||
url.SchemeIs(chrome::kHttpsScheme);
}
bool OfflineResourceHandler::ShouldShowOfflinePage(const GURL& url) const {
// Only check main frame. If the network is disconnected while
// loading other resources, we'll simply show broken link/images.
return IsRemote(url) &&
!chromeos::NetworkStateNotifier::is_connected() &&
ResourceDispatcherHost::InfoForRequest(request_)->resource_type()
== ResourceType::MAIN_FRAME;
}
void OfflineResourceHandler::Resume() {
const GURL url = deferred_url_;
int request_id = deferred_request_id_;
ClearRequestInfo();
bool defer = false;
DVLOG(1) << "Resume load: this=" << this << ", request id=" << request_id;
next_handler_->OnWillStart(request_id, url, &defer);
if (!defer)
rdh_->StartDeferredRequest(process_host_id_, request_id);
}
void OfflineResourceHandler::ShowOfflinePage() {
chromeos::OfflineLoadPage::Show(
process_host_id_, render_view_id_, deferred_url_, this);
}