blob: 272785c62f6cc94b387836d38c047408f4c1cb6b [file] [log] [blame]
// Copyright (c) 2012 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_throttle.h"
#include <vector>
#include "base/bind.h"
#include "base/logging.h"
#include "base/memory/singleton.h"
#include "base/metrics/histogram.h"
#include "base/strings/string_util.h"
#include "chrome/browser/chromeos/offline/offline_load_page.h"
#include "chrome/browser/net/chrome_url_request_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/resource_controller.h"
#include "content/public/browser/resource_request_info.h"
#include "content/public/browser/web_contents.h"
#include "net/base/net_errors.h"
#include "net/base/net_util.h"
#include "net/base/network_change_notifier.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_context.h"
#include "url/url_constants.h"
#include "webkit/browser/appcache/appcache_service.h"
using content::BrowserThread;
using content::RenderViewHost;
using content::WebContents;
namespace {
void ShowOfflinePage(
int render_process_id,
int render_view_id,
const GURL& url,
const chromeos::OfflineLoadPage::CompletionCallback& callback) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
// Check again on UI thread and proceed if it's connected.
if (!net::NetworkChangeNotifier::IsOffline()) {
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE, base::Bind(callback, true));
} else {
RenderViewHost* render_view_host =
RenderViewHost::FromID(render_process_id, render_view_id);
WebContents* web_contents = render_view_host ?
WebContents::FromRenderViewHost(render_view_host) : NULL;
// There is a chance that the tab closed after we decided to show
// the offline page on the IO thread and before we actually show the
// offline page here on the UI thread.
if (web_contents)
(new chromeos::OfflineLoadPage(web_contents, url, callback))->Show();
}
}
} // namespace
OfflineResourceThrottle::OfflineResourceThrottle(
net::URLRequest* request,
appcache::AppCacheService* appcache_service)
: request_(request),
appcache_service_(appcache_service) {
DCHECK(appcache_service);
}
OfflineResourceThrottle::~OfflineResourceThrottle() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (!appcache_completion_callback_.IsCancelled())
appcache_completion_callback_.Cancel();
}
void OfflineResourceThrottle::WillStartRequest(bool* defer) {
if (!ShouldShowOfflinePage(request_->url()))
return;
DVLOG(1) << "WillStartRequest: this=" << this << ", url=" << request_->url();
const GURL* url = &(request_->url());
const GURL* first_party = &(request_->first_party_for_cookies());
// Anticipate a client-side HSTS based redirect from HTTP to HTTPS, and
// ask the appcache about the HTTPS url instead of the HTTP url.
GURL redirect_url;
if (request_->GetHSTSRedirect(&redirect_url)) {
if (url->GetOrigin() == first_party->GetOrigin())
first_party = &redirect_url;
url = &redirect_url;
}
DCHECK(appcache_completion_callback_.IsCancelled());
appcache_completion_callback_.Reset(
base::Bind(&OfflineResourceThrottle::OnCanHandleOfflineComplete,
AsWeakPtr()));
appcache_service_->CanHandleMainResourceOffline(
*url, *first_party,
appcache_completion_callback_.callback());
*defer = true;
}
const char* OfflineResourceThrottle::GetNameForLogging() const {
return "OfflineResourceThrottle";
}
void OfflineResourceThrottle::OnBlockingPageComplete(bool proceed) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (proceed) {
controller()->Resume();
} else {
controller()->Cancel();
}
}
bool OfflineResourceThrottle::IsRemote(const GURL& url) const {
return !net::IsLocalhost(url.host()) && (url.SchemeIs(url::kFtpScheme) ||
url.SchemeIs(url::kHttpScheme) ||
url.SchemeIs(url::kHttpsScheme));
}
bool OfflineResourceThrottle::ShouldShowOfflinePage(const GURL& url) const {
// If the network is disconnected while loading other resources, we'll simply
// show broken link/images.
return IsRemote(url) && net::NetworkChangeNotifier::IsOffline();
}
void OfflineResourceThrottle::OnCanHandleOfflineComplete(int rv) {
appcache_completion_callback_.Cancel();
if (rv == net::OK) {
controller()->Resume();
} else {
const content::ResourceRequestInfo* info =
content::ResourceRequestInfo::ForRequest(request_);
BrowserThread::PostTask(
BrowserThread::UI,
FROM_HERE,
base::Bind(
&ShowOfflinePage,
info->GetChildID(),
info->GetRouteID(),
request_->url(),
base::Bind(
&OfflineResourceThrottle::OnBlockingPageComplete,
AsWeakPtr())));
}
}