blob: cd40152f1fbe0d9636341fecf617511a51fa09a9 [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/net/net_error_tab_helper.h"
#include "base/bind.h"
#include "base/metrics/field_trial.h"
#include "base/prefs/pref_service.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/io_thread.h"
#include "chrome/browser/net/dns_probe_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/net/net_error_info.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/render_messages.h"
#include "content/public/browser/browser_thread.h"
#include "net/base/net_errors.h"
using base::FieldTrialList;
using chrome_common_net::DnsProbeStatus;
using chrome_common_net::DnsProbeStatusToString;
using content::BrowserContext;
using content::BrowserThread;
using content::PageTransition;
using content::RenderViewHost;
using content::WebContents;
using content::WebContentsObserver;
DEFINE_WEB_CONTENTS_USER_DATA_KEY(chrome_browser_net::NetErrorTabHelper);
namespace chrome_browser_net {
namespace {
static NetErrorTabHelper::TestingState testing_state_ =
NetErrorTabHelper::TESTING_DEFAULT;
// Returns whether |net_error| is a DNS-related error (and therefore whether
// the tab helper should start a DNS probe after receiving it.)
bool IsDnsError(int net_error) {
return net_error == net::ERR_NAME_NOT_RESOLVED ||
net_error == net::ERR_NAME_RESOLUTION_FAILED;
}
void OnDnsProbeFinishedOnIOThread(
const base::Callback<void(DnsProbeStatus)>& callback,
DnsProbeStatus result) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
BrowserThread::PostTask(
BrowserThread::UI,
FROM_HERE,
base::Bind(callback, result));
}
// Can only access g_browser_process->io_thread() from the browser thread,
// so have to pass it in to the callback instead of dereferencing it here.
void StartDnsProbeOnIOThread(
const base::Callback<void(DnsProbeStatus)>& callback,
IOThread* io_thread) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
DnsProbeService* probe_service =
io_thread->globals()->dns_probe_service.get();
probe_service->ProbeDns(base::Bind(&OnDnsProbeFinishedOnIOThread, callback));
}
} // namespace
NetErrorTabHelper::~NetErrorTabHelper() {
}
// static
void NetErrorTabHelper::set_state_for_testing(TestingState state) {
testing_state_ = state;
}
void NetErrorTabHelper::DidStartProvisionalLoadForFrame(
int64 frame_id,
int64 parent_frame_id,
bool is_main_frame,
const GURL& validated_url,
bool is_error_page,
bool is_iframe_srcdoc,
RenderViewHost* render_view_host) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (!is_main_frame)
return;
is_error_page_ = is_error_page;
}
void NetErrorTabHelper::DidCommitProvisionalLoadForFrame(
int64 frame_id,
const string16& frame_unique_name,
bool is_main_frame,
const GURL& url,
PageTransition transition_type,
RenderViewHost* render_view_host) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (!is_main_frame)
return;
// Resend status every time an error page commits; this is somewhat spammy,
// but ensures that the status will make it to the real error page, even if
// the link doctor loads a blank intermediate page or the tab switches
// renderer processes.
if (is_error_page_ && dns_error_active_) {
dns_error_page_committed_ = true;
DVLOG(1) << "Committed error page; resending status.";
SendInfo();
} else {
dns_error_active_ = false;
dns_error_page_committed_ = false;
}
}
void NetErrorTabHelper::DidFailProvisionalLoad(
int64 frame_id,
const string16& frame_unique_name,
bool is_main_frame,
const GURL& validated_url,
int error_code,
const string16& error_description,
RenderViewHost* render_view_host) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (!is_main_frame)
return;
if (IsDnsError(error_code)) {
dns_error_active_ = true;
OnMainFrameDnsError();
}
}
NetErrorTabHelper::NetErrorTabHelper(WebContents* contents)
: WebContentsObserver(contents),
weak_factory_(this),
is_error_page_(false),
dns_error_active_(false),
dns_error_page_committed_(false),
dns_probe_status_(chrome_common_net::DNS_PROBE_POSSIBLE),
probes_enabled_(chrome_common_net::DnsProbesEnabled()) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
// If this helper is under test, it won't have a WebContents.
if (contents)
InitializePref(contents);
}
void NetErrorTabHelper::OnMainFrameDnsError() {
if (ProbesAllowed()) {
// Don't start more than one probe at a time.
if (dns_probe_status_ != chrome_common_net::DNS_PROBE_STARTED) {
StartDnsProbe();
dns_probe_status_ = chrome_common_net::DNS_PROBE_STARTED;
}
} else {
dns_probe_status_ = chrome_common_net::DNS_PROBE_NOT_RUN;
}
}
void NetErrorTabHelper::StartDnsProbe() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(dns_error_active_);
DCHECK_NE(chrome_common_net::DNS_PROBE_STARTED, dns_probe_status_);
DVLOG(1) << "Starting DNS probe.";
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
base::Bind(&StartDnsProbeOnIOThread,
base::Bind(&NetErrorTabHelper::OnDnsProbeFinished,
weak_factory_.GetWeakPtr()),
g_browser_process->io_thread()));
}
void NetErrorTabHelper::OnDnsProbeFinished(DnsProbeStatus result) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK_EQ(chrome_common_net::DNS_PROBE_STARTED, dns_probe_status_);
DCHECK(chrome_common_net::DnsProbeStatusIsFinished(result));
DVLOG(1) << "Finished DNS probe with result "
<< DnsProbeStatusToString(result) << ".";
dns_probe_status_ = result;
if (dns_error_page_committed_)
SendInfo();
}
void NetErrorTabHelper::InitializePref(WebContents* contents) {
DCHECK(contents);
BrowserContext* browser_context = contents->GetBrowserContext();
Profile* profile = Profile::FromBrowserContext(browser_context);
resolve_errors_with_web_service_.Init(
prefs::kAlternateErrorPagesEnabled,
profile->GetPrefs());
}
bool NetErrorTabHelper::ProbesAllowed() const {
if (testing_state_ != TESTING_DEFAULT)
return testing_state_ == TESTING_FORCE_ENABLED;
// TODO(ttuttle): Disable on mobile?
return probes_enabled_ && *resolve_errors_with_web_service_;
}
void NetErrorTabHelper::SendInfo() {
DCHECK_NE(chrome_common_net::DNS_PROBE_POSSIBLE, dns_probe_status_);
DCHECK(dns_error_page_committed_);
DVLOG(1) << "Sending status " << DnsProbeStatusToString(dns_probe_status_);
Send(new ChromeViewMsg_NetErrorInfo(routing_id(), dns_probe_status_));
if (!dns_probe_status_snoop_callback_.is_null())
dns_probe_status_snoop_callback_.Run(dns_probe_status_);
}
} // namespace chrome_browser_net