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/renderer_host/offline_resource_throttle.h" 6 7 #include <vector> 8 9 #include "base/bind.h" 10 #include "base/logging.h" 11 #include "base/memory/singleton.h" 12 #include "base/metrics/histogram.h" 13 #include "base/strings/string_util.h" 14 #include "chrome/browser/chromeos/offline/offline_load_page.h" 15 #include "content/public/browser/appcache_service.h" 16 #include "content/public/browser/browser_thread.h" 17 #include "content/public/browser/render_view_host.h" 18 #include "content/public/browser/resource_controller.h" 19 #include "content/public/browser/resource_request_info.h" 20 #include "content/public/browser/web_contents.h" 21 #include "net/base/net_errors.h" 22 #include "net/base/net_util.h" 23 #include "net/base/network_change_notifier.h" 24 #include "net/url_request/url_request.h" 25 #include "net/url_request/url_request_context.h" 26 #include "url/url_constants.h" 27 28 using content::BrowserThread; 29 using content::RenderViewHost; 30 using content::WebContents; 31 32 namespace { 33 34 void ShowOfflinePage( 35 int render_process_id, 36 int render_view_id, 37 const GURL& url, 38 const chromeos::OfflineLoadPage::CompletionCallback& callback) { 39 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 40 41 // Check again on UI thread and proceed if it's connected. 42 if (!net::NetworkChangeNotifier::IsOffline()) { 43 BrowserThread::PostTask( 44 BrowserThread::IO, FROM_HERE, base::Bind(callback, true)); 45 } else { 46 RenderViewHost* render_view_host = 47 RenderViewHost::FromID(render_process_id, render_view_id); 48 WebContents* web_contents = render_view_host ? 49 WebContents::FromRenderViewHost(render_view_host) : NULL; 50 // There is a chance that the tab closed after we decided to show 51 // the offline page on the IO thread and before we actually show the 52 // offline page here on the UI thread. 53 if (web_contents) 54 (new chromeos::OfflineLoadPage(web_contents, url, callback))->Show(); 55 } 56 } 57 58 } // namespace 59 60 OfflineResourceThrottle::OfflineResourceThrottle( 61 net::URLRequest* request, 62 content::AppCacheService* appcache_service) 63 : request_(request), 64 appcache_service_(appcache_service) { 65 DCHECK(appcache_service); 66 } 67 68 OfflineResourceThrottle::~OfflineResourceThrottle() { 69 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 70 71 if (!appcache_completion_callback_.IsCancelled()) 72 appcache_completion_callback_.Cancel(); 73 } 74 75 void OfflineResourceThrottle::WillStartRequest(bool* defer) { 76 if (!ShouldShowOfflinePage(request_->url())) 77 return; 78 79 DVLOG(1) << "WillStartRequest: this=" << this << ", url=" << request_->url(); 80 81 const GURL* url = &(request_->url()); 82 const GURL* first_party = &(request_->first_party_for_cookies()); 83 84 // Anticipate a client-side HSTS based redirect from HTTP to HTTPS, and 85 // ask the appcache about the HTTPS url instead of the HTTP url. 86 GURL redirect_url; 87 if (request_->GetHSTSRedirect(&redirect_url)) { 88 if (url->GetOrigin() == first_party->GetOrigin()) 89 first_party = &redirect_url; 90 url = &redirect_url; 91 } 92 93 DCHECK(appcache_completion_callback_.IsCancelled()); 94 95 appcache_completion_callback_.Reset( 96 base::Bind(&OfflineResourceThrottle::OnCanHandleOfflineComplete, 97 AsWeakPtr())); 98 appcache_service_->CanHandleMainResourceOffline( 99 *url, *first_party, 100 appcache_completion_callback_.callback()); 101 102 *defer = true; 103 } 104 105 const char* OfflineResourceThrottle::GetNameForLogging() const { 106 return "OfflineResourceThrottle"; 107 } 108 109 void OfflineResourceThrottle::OnBlockingPageComplete(bool proceed) { 110 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 111 112 if (proceed) { 113 controller()->Resume(); 114 } else { 115 controller()->Cancel(); 116 } 117 } 118 119 bool OfflineResourceThrottle::IsRemote(const GURL& url) const { 120 return !net::IsLocalhost(url.host()) && (url.SchemeIs(url::kFtpScheme) || 121 url.SchemeIs(url::kHttpScheme) || 122 url.SchemeIs(url::kHttpsScheme)); 123 } 124 125 bool OfflineResourceThrottle::ShouldShowOfflinePage(const GURL& url) const { 126 // If the network is disconnected while loading other resources, we'll simply 127 // show broken link/images. 128 return IsRemote(url) && net::NetworkChangeNotifier::IsOffline(); 129 } 130 131 void OfflineResourceThrottle::OnCanHandleOfflineComplete(int rv) { 132 appcache_completion_callback_.Cancel(); 133 134 if (rv == net::OK) { 135 controller()->Resume(); 136 } else { 137 const content::ResourceRequestInfo* info = 138 content::ResourceRequestInfo::ForRequest(request_); 139 BrowserThread::PostTask( 140 BrowserThread::UI, 141 FROM_HERE, 142 base::Bind( 143 &ShowOfflinePage, 144 info->GetChildID(), 145 info->GetRouteID(), 146 request_->url(), 147 base::Bind( 148 &OfflineResourceThrottle::OnBlockingPageComplete, 149 AsWeakPtr()))); 150 } 151 } 152