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