Home | History | Annotate | Download | only in net
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/net/net_error_tab_helper.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/prefs/pref_service.h"
      9 #include "chrome/browser/browser_process.h"
     10 #include "chrome/browser/io_thread.h"
     11 #include "chrome/browser/net/dns_probe_service.h"
     12 #include "chrome/browser/profiles/profile.h"
     13 #include "chrome/common/net/net_error_info.h"
     14 #include "chrome/common/pref_names.h"
     15 #include "chrome/common/render_messages.h"
     16 #include "content/public/browser/browser_thread.h"
     17 #include "net/base/net_errors.h"
     18 
     19 using chrome_common_net::DnsProbeStatus;
     20 using chrome_common_net::DnsProbeStatusToString;
     21 using content::BrowserContext;
     22 using content::BrowserThread;
     23 using content::PageTransition;
     24 using content::RenderViewHost;
     25 using content::WebContents;
     26 using content::WebContentsObserver;
     27 
     28 DEFINE_WEB_CONTENTS_USER_DATA_KEY(chrome_browser_net::NetErrorTabHelper);
     29 
     30 namespace chrome_browser_net {
     31 
     32 namespace {
     33 
     34 static NetErrorTabHelper::TestingState testing_state_ =
     35     NetErrorTabHelper::TESTING_DEFAULT;
     36 
     37 // Returns whether |net_error| is a DNS-related error (and therefore whether
     38 // the tab helper should start a DNS probe after receiving it.)
     39 bool IsDnsError(int net_error) {
     40   return net_error == net::ERR_NAME_NOT_RESOLVED ||
     41          net_error == net::ERR_NAME_RESOLUTION_FAILED;
     42 }
     43 
     44 void OnDnsProbeFinishedOnIOThread(
     45     const base::Callback<void(DnsProbeStatus)>& callback,
     46     DnsProbeStatus result) {
     47   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
     48 
     49   BrowserThread::PostTask(
     50       BrowserThread::UI,
     51       FROM_HERE,
     52       base::Bind(callback, result));
     53 }
     54 
     55 // Can only access g_browser_process->io_thread() from the browser thread,
     56 // so have to pass it in to the callback instead of dereferencing it here.
     57 void StartDnsProbeOnIOThread(
     58     const base::Callback<void(DnsProbeStatus)>& callback,
     59     IOThread* io_thread) {
     60   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
     61 
     62   DnsProbeService* probe_service =
     63       io_thread->globals()->dns_probe_service.get();
     64 
     65   probe_service->ProbeDns(base::Bind(&OnDnsProbeFinishedOnIOThread, callback));
     66 }
     67 
     68 }  // namespace
     69 
     70 NetErrorTabHelper::~NetErrorTabHelper() {
     71 }
     72 
     73 // static
     74 void NetErrorTabHelper::set_state_for_testing(TestingState state) {
     75   testing_state_ = state;
     76 }
     77 
     78 void NetErrorTabHelper::DidStartProvisionalLoadForFrame(
     79     int64 frame_id,
     80     int64 parent_frame_id,
     81     bool is_main_frame,
     82     const GURL& validated_url,
     83     bool is_error_page,
     84     bool is_iframe_srcdoc,
     85     RenderViewHost* render_view_host) {
     86   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     87 
     88   if (!is_main_frame)
     89     return;
     90 
     91   is_error_page_ = is_error_page;
     92 }
     93 
     94 void NetErrorTabHelper::DidCommitProvisionalLoadForFrame(
     95     int64 frame_id,
     96     const base::string16& frame_unique_name,
     97     bool is_main_frame,
     98     const GURL& url,
     99     PageTransition transition_type,
    100     RenderViewHost* render_view_host) {
    101   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    102 
    103   if (!is_main_frame)
    104     return;
    105 
    106   // Resend status every time an error page commits; this is somewhat spammy,
    107   // but ensures that the status will make it to the real error page, even if
    108   // the link doctor loads a blank intermediate page or the tab switches
    109   // renderer processes.
    110   if (is_error_page_ && dns_error_active_) {
    111     dns_error_page_committed_ = true;
    112     DVLOG(1) << "Committed error page; resending status.";
    113     SendInfo();
    114   } else {
    115     dns_error_active_ = false;
    116     dns_error_page_committed_ = false;
    117   }
    118 }
    119 
    120 void NetErrorTabHelper::DidFailProvisionalLoad(
    121     int64 frame_id,
    122     const base::string16& frame_unique_name,
    123     bool is_main_frame,
    124     const GURL& validated_url,
    125     int error_code,
    126     const base::string16& error_description,
    127     RenderViewHost* render_view_host) {
    128   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    129 
    130   if (!is_main_frame)
    131     return;
    132 
    133   if (IsDnsError(error_code)) {
    134     dns_error_active_ = true;
    135     OnMainFrameDnsError();
    136   }
    137 }
    138 
    139 NetErrorTabHelper::NetErrorTabHelper(WebContents* contents)
    140     : WebContentsObserver(contents),
    141       weak_factory_(this),
    142       is_error_page_(false),
    143       dns_error_active_(false),
    144       dns_error_page_committed_(false),
    145       dns_probe_status_(chrome_common_net::DNS_PROBE_POSSIBLE) {
    146   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    147 
    148   // If this helper is under test, it won't have a WebContents.
    149   if (contents)
    150     InitializePref(contents);
    151 }
    152 
    153 void NetErrorTabHelper::OnMainFrameDnsError() {
    154   if (ProbesAllowed()) {
    155     // Don't start more than one probe at a time.
    156     if (dns_probe_status_ != chrome_common_net::DNS_PROBE_STARTED) {
    157       StartDnsProbe();
    158       dns_probe_status_ = chrome_common_net::DNS_PROBE_STARTED;
    159     }
    160   } else {
    161     dns_probe_status_ = chrome_common_net::DNS_PROBE_NOT_RUN;
    162   }
    163 }
    164 
    165 void NetErrorTabHelper::StartDnsProbe() {
    166   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    167   DCHECK(dns_error_active_);
    168   DCHECK_NE(chrome_common_net::DNS_PROBE_STARTED, dns_probe_status_);
    169 
    170   DVLOG(1) << "Starting DNS probe.";
    171 
    172   BrowserThread::PostTask(
    173       BrowserThread::IO,
    174       FROM_HERE,
    175       base::Bind(&StartDnsProbeOnIOThread,
    176                  base::Bind(&NetErrorTabHelper::OnDnsProbeFinished,
    177                             weak_factory_.GetWeakPtr()),
    178                  g_browser_process->io_thread()));
    179 }
    180 
    181 void NetErrorTabHelper::OnDnsProbeFinished(DnsProbeStatus result) {
    182   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    183   DCHECK_EQ(chrome_common_net::DNS_PROBE_STARTED, dns_probe_status_);
    184   DCHECK(chrome_common_net::DnsProbeStatusIsFinished(result));
    185 
    186   DVLOG(1) << "Finished DNS probe with result "
    187            << DnsProbeStatusToString(result) << ".";
    188 
    189   dns_probe_status_ = result;
    190 
    191   if (dns_error_page_committed_)
    192     SendInfo();
    193 }
    194 
    195 void NetErrorTabHelper::InitializePref(WebContents* contents) {
    196   DCHECK(contents);
    197 
    198   BrowserContext* browser_context = contents->GetBrowserContext();
    199   Profile* profile = Profile::FromBrowserContext(browser_context);
    200   resolve_errors_with_web_service_.Init(
    201       prefs::kAlternateErrorPagesEnabled,
    202       profile->GetPrefs());
    203 }
    204 
    205 bool NetErrorTabHelper::ProbesAllowed() const {
    206   if (testing_state_ != TESTING_DEFAULT)
    207     return testing_state_ == TESTING_FORCE_ENABLED;
    208 
    209   // TODO(ttuttle): Disable on mobile?
    210   return *resolve_errors_with_web_service_;
    211 }
    212 
    213 void NetErrorTabHelper::SendInfo() {
    214   DCHECK_NE(chrome_common_net::DNS_PROBE_POSSIBLE, dns_probe_status_);
    215   DCHECK(dns_error_page_committed_);
    216 
    217   DVLOG(1) << "Sending status " << DnsProbeStatusToString(dns_probe_status_);
    218   Send(new ChromeViewMsg_NetErrorInfo(routing_id(), dns_probe_status_));
    219 
    220   if (!dns_probe_status_snoop_callback_.is_null())
    221     dns_probe_status_snoop_callback_.Run(dns_probe_status_);
    222 }
    223 
    224 }  // namespace chrome_browser_net
    225