| OLD | NEW |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/android/offline_pages/offline_page_tab_helper.h" | 5 #include "chrome/browser/android/offline_pages/offline_page_tab_helper.h" |
| 6 | 6 |
| 7 #include "base/bind.h" | 7 #include "base/bind.h" |
| 8 #include "base/logging.h" | 8 #include "base/logging.h" |
| 9 #include "base/memory/ptr_util.h" | 9 #include "base/memory/ptr_util.h" |
| 10 #include "base/metrics/histogram.h" | 10 #include "chrome/browser/android/offline_pages/offline_page_request_job.h" |
| 11 #include "base/threading/thread_task_runner_handle.h" | |
| 12 #include "base/time/time.h" | |
| 13 #include "chrome/browser/android/offline_pages/offline_page_model_factory.h" | |
| 14 #include "chrome/browser/android/offline_pages/offline_page_utils.h" | 11 #include "chrome/browser/android/offline_pages/offline_page_utils.h" |
| 15 #include "chrome/browser/net/nqe/ui_network_quality_estimator_service.h" | |
| 16 #include "chrome/browser/net/nqe/ui_network_quality_estimator_service_factory.h" | |
| 17 #include "chrome/browser/profiles/profile.h" | |
| 18 #include "components/offline_pages/client_namespace_constants.h" | |
| 19 #include "components/offline_pages/offline_page_item.h" | 12 #include "components/offline_pages/offline_page_item.h" |
| 20 #include "components/offline_pages/offline_page_model.h" | |
| 21 #include "components/previews/previews_experiments.h" | |
| 22 #include "content/public/browser/browser_thread.h" | 13 #include "content/public/browser/browser_thread.h" |
| 23 #include "content/public/browser/navigation_controller.h" | 14 #include "content/public/browser/navigation_controller.h" |
| 24 #include "content/public/browser/navigation_entry.h" | 15 #include "content/public/browser/navigation_entry.h" |
| 25 #include "content/public/browser/navigation_handle.h" | 16 #include "content/public/browser/navigation_handle.h" |
| 26 #include "content/public/browser/render_frame_host.h" | 17 #include "content/public/browser/render_frame_host.h" |
| 27 #include "content/public/browser/web_contents.h" | 18 #include "content/public/browser/web_contents.h" |
| 28 #include "net/base/net_errors.h" | |
| 29 #include "net/base/network_change_notifier.h" | |
| 30 #include "net/nqe/network_quality_estimator.h" | |
| 31 #include "ui/base/page_transition_types.h" | 19 #include "ui/base/page_transition_types.h" |
| 32 | 20 |
| 33 DEFINE_WEB_CONTENTS_USER_DATA_KEY(offline_pages::OfflinePageTabHelper); | 21 DEFINE_WEB_CONTENTS_USER_DATA_KEY(offline_pages::OfflinePageTabHelper); |
| 34 | 22 |
| 35 namespace offline_pages { | 23 namespace offline_pages { |
| 36 namespace { | |
| 37 | |
| 38 void ReportAccessedOfflinePage(content::BrowserContext* browser_context, | |
| 39 const GURL& navigated_url, | |
| 40 const GURL& online_url) { | |
| 41 // If there is a valid online URL for this navigated URL, then we are looking | |
| 42 // at an offline page. | |
| 43 if (online_url.is_valid()) | |
| 44 OfflinePageUtils::MarkPageAccessed(browser_context, navigated_url); | |
| 45 } | |
| 46 | |
| 47 // Whether using offline pages for slow networks is allowed and the network is | |
| 48 // currently estimated to be prohibitively slow. | |
| 49 bool ShouldUseOfflineForSlowNetwork(content::BrowserContext* context) { | |
| 50 if (!previews::IsOfflinePreviewsEnabled()) | |
| 51 return false; | |
| 52 Profile* profile = Profile::FromBrowserContext(context); | |
| 53 UINetworkQualityEstimatorService* nqe_service = | |
| 54 UINetworkQualityEstimatorServiceFactory::GetForProfile(profile); | |
| 55 if (!nqe_service) | |
| 56 return false; | |
| 57 net::EffectiveConnectionType effective_connection_type = | |
| 58 nqe_service->GetEffectiveConnectionType(); | |
| 59 return effective_connection_type >= net::EFFECTIVE_CONNECTION_TYPE_OFFLINE && | |
| 60 effective_connection_type <= net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G; | |
| 61 } | |
| 62 | |
| 63 class DefaultDelegate : public OfflinePageTabHelper::Delegate { | |
| 64 public: | |
| 65 DefaultDelegate() {} | |
| 66 // offline_pages::OfflinePageTabHelper::Delegate implementation: | |
| 67 bool GetTabId(content::WebContents* web_contents, | |
| 68 int* tab_id) const override { | |
| 69 return OfflinePageUtils::GetTabId(web_contents, tab_id); | |
| 70 } | |
| 71 base::Time Now() const override { return base::Time::Now(); } | |
| 72 }; | |
| 73 } // namespace | |
| 74 | 24 |
| 75 OfflinePageTabHelper::OfflinePageTabHelper(content::WebContents* web_contents) | 25 OfflinePageTabHelper::OfflinePageTabHelper(content::WebContents* web_contents) |
| 76 : content::WebContentsObserver(web_contents), | 26 : content::WebContentsObserver(web_contents), |
| 77 delegate_(new DefaultDelegate()), | |
| 78 is_offline_preview_(false), | 27 is_offline_preview_(false), |
| 79 weak_ptr_factory_(this) { | 28 weak_ptr_factory_(this) { |
| 80 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | 29 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| 81 } | 30 } |
| 82 | 31 |
| 83 OfflinePageTabHelper::~OfflinePageTabHelper() {} | 32 OfflinePageTabHelper::~OfflinePageTabHelper() {} |
| 84 | 33 |
| 85 void OfflinePageTabHelper::SetDelegateForTesting( | |
| 86 std::unique_ptr<OfflinePageTabHelper::Delegate> delegate) { | |
| 87 DCHECK(delegate); | |
| 88 delegate_ = std::move(delegate); | |
| 89 } | |
| 90 | |
| 91 void OfflinePageTabHelper::DidStartNavigation( | 34 void OfflinePageTabHelper::DidStartNavigation( |
| 92 content::NavigationHandle* navigation_handle) { | 35 content::NavigationHandle* navigation_handle) { |
| 93 // Skips non-main frame. | 36 // Skips non-main frame. |
| 94 if (!navigation_handle->IsInMainFrame()) | 37 if (!navigation_handle->IsInMainFrame()) |
| 95 return; | 38 return; |
| 96 | 39 |
| 97 // This is a new navigation so we can invalidate any previously scheduled | 40 // This is a new navigation so we can invalidate any previously scheduled |
| 98 // operations. | 41 // operations. |
| 99 weak_ptr_factory_.InvalidateWeakPtrs(); | 42 weak_ptr_factory_.InvalidateWeakPtrs(); |
| 100 | 43 |
| 101 // Since this is a new navigation, we will reset the cached offline page, | 44 // Since this is a new navigation, we will reset the cached offline page, |
| 102 // unless we are currently looking at an offline page. | 45 offline_page_ = nullptr; |
| 103 GURL navigated_url = navigation_handle->GetURL(); | 46 is_offline_preview_ = false; |
| 104 if (offline_page_ && navigated_url != offline_page_->GetOfflineURL()) { | |
| 105 offline_page_ = nullptr; | |
| 106 is_offline_preview_ = false; | |
| 107 } | |
| 108 | 47 |
| 109 // If an offline download file is opened, don't do redirect per network | 48 reloading_url_on_net_error_ = false; |
| 110 // conditions. Also store a cached copy here such that Tab knows that | |
| 111 // the offline page is opened. | |
| 112 if (OfflinePageUtils::MightBeOfflineURL(navigated_url)) { | |
| 113 OfflinePageModel* offline_page_model = | |
| 114 OfflinePageModelFactory::GetForBrowserContext( | |
| 115 web_contents()->GetBrowserContext()); | |
| 116 if (offline_page_model) { | |
| 117 const OfflinePageItem* offline_page = | |
| 118 offline_page_model->MaybeGetPageByOfflineURL(navigated_url); | |
| 119 if (offline_page && | |
| 120 offline_page->client_id.name_space == kDownloadNamespace) { | |
| 121 offline_page_ = base::MakeUnique<OfflinePageItem>(*offline_page); | |
| 122 return; | |
| 123 } | |
| 124 } | |
| 125 } | |
| 126 | |
| 127 // Ignore navigations that are forward or back transitions in the nav stack | |
| 128 // which are not at the head of the stack. | |
| 129 // TODO(dimich): Not sure this is needed. Clarify and remove. Bug 624216. | |
| 130 const content::NavigationController& controller = | |
| 131 web_contents()->GetController(); | |
| 132 if (controller.GetEntryCount() > 0 && | |
| 133 controller.GetCurrentEntryIndex() != -1 && | |
| 134 controller.GetCurrentEntryIndex() < controller.GetEntryCount() - 1) { | |
| 135 return; | |
| 136 } | |
| 137 | |
| 138 if (net::NetworkChangeNotifier::IsOffline()) { | |
| 139 GetBestPageForRedirectToOffline( | |
| 140 RedirectResult::REDIRECTED_ON_DISCONNECTED_NETWORK, navigated_url); | |
| 141 return; | |
| 142 } | |
| 143 | |
| 144 content::BrowserContext* context = web_contents()->GetBrowserContext(); | |
| 145 if (ShouldUseOfflineForSlowNetwork(context)) { | |
| 146 GetBestPageForRedirectToOffline( | |
| 147 RedirectResult::REDIRECTED_ON_PROHIBITIVELY_SLOW_NETWORK, | |
| 148 navigated_url); | |
| 149 return; | |
| 150 } | |
| 151 | |
| 152 OfflinePageModel* offline_page_model = | |
| 153 OfflinePageModelFactory::GetForBrowserContext(context); | |
| 154 if (!offline_page_model) | |
| 155 return; | |
| 156 | |
| 157 offline_page_model->GetPageByOfflineURL( | |
| 158 navigated_url, base::Bind(&OfflinePageTabHelper::RedirectToOnline, | |
| 159 weak_ptr_factory_.GetWeakPtr(), navigated_url)); | |
| 160 } | 49 } |
| 161 | 50 |
| 162 void OfflinePageTabHelper::DidFinishNavigation( | 51 void OfflinePageTabHelper::DidFinishNavigation( |
| 163 content::NavigationHandle* navigation_handle) { | 52 content::NavigationHandle* navigation_handle) { |
| 164 // Skips non-main frame. | 53 // Skips non-main frame. |
| 165 if (!navigation_handle->IsInMainFrame()) | 54 if (!navigation_handle->IsInMainFrame()) |
| 166 return; | 55 return; |
| 167 | 56 |
| 168 GURL navigated_url = navigation_handle->GetURL(); | 57 // We might be reloading the URL in order to fetch the offline page. |
| 58 // * If successful, nothing to do. |
| 59 // * Otherwise, we're hitting error again. Bail out to avoid loop. |
| 60 if (reloading_url_on_net_error_) |
| 61 return; |
| 62 |
| 63 // When the navigation starts, the request might be intercepted to serve the |
| 64 // offline content if the network is detected to be in disconnected or poor |
| 65 // conditions. This detection might not work for some cases, i.e., connected |
| 66 // to a hotspot or proxy that does not have network, and the navigation will |
| 67 // eventually fail. To handle this, we will reload the page to force the |
| 68 // offline interception if the error code matches the following list. |
| 69 // Otherwise, the error page will be shown. |
| 169 net::Error error_code = navigation_handle->GetNetErrorCode(); | 70 net::Error error_code = navigation_handle->GetNetErrorCode(); |
| 170 content::BrowserContext* browser_context = | |
| 171 web_contents()->GetBrowserContext(); | |
| 172 | |
| 173 // If the offline page is being loaded successfully, set the access record but | |
| 174 // no need to do anything else. | |
| 175 if (error_code == net::OK) { | |
| 176 OfflinePageUtils::GetOnlineURLForOfflineURL( | |
| 177 browser_context, navigated_url, | |
| 178 base::Bind(&ReportAccessedOfflinePage, browser_context, navigated_url)); | |
| 179 return; | |
| 180 } | |
| 181 | |
| 182 // When the navigation starts, we redirect immediately from online page to | |
| 183 // offline version on the case that there is no network connection. If there | |
| 184 // is still network connection but with no or poor network connectivity, the | |
| 185 // navigation will eventually fail and we want to redirect to offline copy | |
| 186 // in this case. If error code doesn't match this list, then we still show | |
| 187 // the error page and not an offline page, so do nothing. | |
| 188 if (error_code != net::ERR_INTERNET_DISCONNECTED && | 71 if (error_code != net::ERR_INTERNET_DISCONNECTED && |
| 189 error_code != net::ERR_NAME_NOT_RESOLVED && | 72 error_code != net::ERR_NAME_NOT_RESOLVED && |
| 190 error_code != net::ERR_ADDRESS_UNREACHABLE && | 73 error_code != net::ERR_ADDRESS_UNREACHABLE && |
| 191 error_code != net::ERR_PROXY_CONNECTION_FAILED) { | 74 error_code != net::ERR_PROXY_CONNECTION_FAILED) { |
| 192 ReportRedirectResultUMA(RedirectResult::SHOW_NET_ERROR_PAGE); | 75 // Do not report aborted error since the error page is not shown on this |
| 76 // error. |
| 77 if (error_code != net::ERR_ABORTED) { |
| 78 OfflinePageRequestJob::ReportAggregatedRequestResult( |
| 79 OfflinePageRequestJob::AggregatedRequestResult::SHOW_NET_ERROR_PAGE); |
| 80 } |
| 193 return; | 81 return; |
| 194 } | 82 } |
| 195 | 83 |
| 196 // Don't actually want to redirect on a forward/back nav. | |
| 197 // TODO(dimich): Clarify and possibly redirect as well. Bug 624216. | |
| 198 if (ui::PageTransitionTypeIncludingQualifiersIs( | |
| 199 navigation_handle->GetPageTransition(), | |
| 200 ui::PAGE_TRANSITION_FORWARD_BACK)) { | |
| 201 ReportRedirectResultUMA(RedirectResult::IGNORED_FLAKY_NETWORK_FORWARD_BACK); | |
| 202 return; | |
| 203 } | |
| 204 | |
| 205 GetBestPageForRedirectToOffline( | |
| 206 RedirectResult::REDIRECTED_ON_FLAKY_NETWORK, navigated_url); | |
| 207 } | |
| 208 | |
| 209 void OfflinePageTabHelper::RedirectToOnline( | |
| 210 const GURL& navigated_url, | |
| 211 const OfflinePageItem* offline_page) { | |
| 212 // Bails out if no redirection is needed. No UMA reporting since all regular | |
| 213 // navigations will be here and it'll dwarf the useful reporting. | |
| 214 if (!offline_page) | |
| 215 return; | |
| 216 | |
| 217 GURL redirect_url = offline_page->url; | |
| 218 if (IsInRedirectLoop(redirect_url)) { | |
| 219 ReportRedirectResultUMA(RedirectResult::REDIRECT_LOOP_ONLINE); | |
| 220 return; | |
| 221 } | |
| 222 | |
| 223 Redirect(navigated_url, redirect_url); | |
| 224 // Clear the offline page since we are redirecting to online. | |
| 225 offline_page_ = nullptr; | |
| 226 is_offline_preview_ = false; | |
| 227 | |
| 228 ReportRedirectResultUMA(RedirectResult::REDIRECTED_ON_CONNECTED_NETWORK); | |
| 229 } | |
| 230 | |
| 231 void OfflinePageTabHelper::GetBestPageForRedirectToOffline( | |
| 232 RedirectResult result, const GURL& online_url) { | |
| 233 // When there is no valid tab android there is nowhere to show the offline | 84 // When there is no valid tab android there is nowhere to show the offline |
| 234 // page, so we can leave. | 85 // page, so we can leave. |
| 235 int tab_id; | 86 int tab_id; |
| 236 if (!delegate_->GetTabId(web_contents(), &tab_id)) { | 87 if (!OfflinePageUtils::GetTabId(web_contents(), &tab_id)) { |
| 237 ReportRedirectResultUMA(RedirectResult::NO_TAB_ID); | 88 // No need to report NO_TAB_ID since it should have already been detected |
| 89 // and reported in offline page request handler. |
| 238 return; | 90 return; |
| 239 } | 91 } |
| 240 | 92 |
| 241 OfflinePageUtils::SelectPageForOnlineURL( | 93 OfflinePageUtils::SelectPageForOnlineURL( |
| 242 web_contents()->GetBrowserContext(), | 94 web_contents()->GetBrowserContext(), |
| 243 online_url, | 95 navigation_handle->GetURL(), |
| 244 tab_id, | 96 tab_id, |
| 245 base::Bind(&OfflinePageTabHelper::SelectPageForOnlineURLDone, | 97 base::Bind(&OfflinePageTabHelper::SelectPageForOnlineURLDone, |
| 246 weak_ptr_factory_.GetWeakPtr(), result, online_url)); | 98 weak_ptr_factory_.GetWeakPtr())); |
| 247 } | 99 } |
| 248 | 100 |
| 249 void OfflinePageTabHelper::SelectPageForOnlineURLDone( | 101 void OfflinePageTabHelper::SelectPageForOnlineURLDone( |
| 250 RedirectResult result, | |
| 251 const GURL& online_url, | |
| 252 const OfflinePageItem* offline_page) { | 102 const OfflinePageItem* offline_page) { |
| 253 DCHECK(result == RedirectResult::REDIRECTED_ON_FLAKY_NETWORK || | 103 // Bails out if no offline page is found. |
| 254 result == RedirectResult::REDIRECTED_ON_DISCONNECTED_NETWORK || | |
| 255 result == RedirectResult::REDIRECTED_ON_PROHIBITIVELY_SLOW_NETWORK); | |
| 256 | |
| 257 if (!offline_page) { | 104 if (!offline_page) { |
| 258 switch (result) { | 105 OfflinePageRequestJob::ReportAggregatedRequestResult( |
| 259 case RedirectResult::REDIRECTED_ON_FLAKY_NETWORK: | 106 OfflinePageRequestJob::AggregatedRequestResult:: |
| 260 ReportRedirectResultUMA( | 107 PAGE_NOT_FOUND_ON_FLAKY_NETWORK); |
| 261 RedirectResult::PAGE_NOT_FOUND_ON_FLAKY_NETWORK); | |
| 262 return; | |
| 263 case RedirectResult::REDIRECTED_ON_PROHIBITIVELY_SLOW_NETWORK: | |
| 264 ReportRedirectResultUMA( | |
| 265 RedirectResult::PAGE_NOT_FOUND_ON_PROHIBITIVELY_SLOW_NETWORK); | |
| 266 return; | |
| 267 case RedirectResult::REDIRECTED_ON_DISCONNECTED_NETWORK: | |
| 268 ReportRedirectResultUMA( | |
| 269 RedirectResult::PAGE_NOT_FOUND_ON_DISCONNECTED_NETWORK); | |
| 270 return; | |
| 271 default: | |
| 272 NOTREACHED(); | |
| 273 return; | |
| 274 } | |
| 275 } | |
| 276 | |
| 277 // If the page is being loaded on a slow network, only use the offline page | |
| 278 // if it was created within the past 7 days. | |
| 279 if (result == RedirectResult::REDIRECTED_ON_PROHIBITIVELY_SLOW_NETWORK && | |
| 280 delegate_->Now() - offline_page->creation_time > | |
| 281 base::TimeDelta::FromDays(7)) { | |
| 282 ReportRedirectResultUMA( | |
| 283 RedirectResult::PAGE_NOT_FRESH_ON_PROHIBITIVELY_SLOW_NETWORK); | |
| 284 return; | 108 return; |
| 285 } | 109 } |
| 286 | 110 |
| 287 TryRedirectToOffline(result, online_url, *offline_page); | 111 reloading_url_on_net_error_ = true; |
| 288 } | |
| 289 | 112 |
| 290 void OfflinePageTabHelper::TryRedirectToOffline( | 113 // Reloads the page with extra header set to force loading the offline page. |
| 291 RedirectResult result, | 114 content::NavigationController::LoadURLParams load_params(offline_page->url); |
| 292 const GURL& from_url, | 115 load_params.transition_type = ui::PAGE_TRANSITION_RELOAD; |
| 293 const OfflinePageItem& offline_page) { | 116 load_params.extra_headers = kLoadingOfflinePageHeader; |
| 294 GURL redirect_url = offline_page.GetOfflineURL(); | 117 load_params.extra_headers += ":"; |
| 295 if (!redirect_url.is_valid()) | 118 load_params.extra_headers += kLoadingOfflinePageReason; |
| 296 return; | 119 load_params.extra_headers += kLoadingOfflinePageDueToNetError; |
| 297 | |
| 298 if (IsInRedirectLoop(redirect_url)) { | |
| 299 ReportRedirectResultUMA(RedirectResult::REDIRECT_LOOP_OFFLINE); | |
| 300 return; | |
| 301 } | |
| 302 | |
| 303 Redirect(from_url, redirect_url); | |
| 304 offline_page_ = base::MakeUnique<OfflinePageItem>(offline_page); | |
| 305 is_offline_preview_ = | |
| 306 (result == RedirectResult::REDIRECTED_ON_PROHIBITIVELY_SLOW_NETWORK); | |
| 307 ReportRedirectResultUMA(result); | |
| 308 } | |
| 309 | |
| 310 void OfflinePageTabHelper::Redirect(const GURL& from_url, const GURL& to_url) { | |
| 311 content::NavigationController::LoadURLParams load_params(to_url); | |
| 312 load_params.transition_type = ui::PAGE_TRANSITION_CLIENT_REDIRECT; | |
| 313 load_params.redirect_chain.push_back(from_url); | |
| 314 web_contents()->GetController().LoadURLWithParams(load_params); | 120 web_contents()->GetController().LoadURLWithParams(load_params); |
| 315 } | 121 } |
| 316 | 122 |
| 317 bool OfflinePageTabHelper::IsInRedirectLoop(const GURL& to_url) const { | 123 void OfflinePageTabHelper::SetOfflinePage(const OfflinePageItem& offline_page, |
| 318 // Detects looping between online and offline redirections. | 124 bool is_offline_preview) { |
| 319 const content::NavigationController& controller = | 125 offline_page_ = base::MakeUnique<OfflinePageItem>(offline_page); |
| 320 web_contents()->GetController(); | 126 is_offline_preview_ = is_offline_preview; |
| 321 content::NavigationEntry* entry = controller.GetPendingEntry(); | |
| 322 return entry && | |
| 323 !entry->GetRedirectChain().empty() && | |
| 324 entry->GetRedirectChain().back() == to_url; | |
| 325 } | |
| 326 | |
| 327 void OfflinePageTabHelper::ReportRedirectResultUMA(RedirectResult result) { | |
| 328 UMA_HISTOGRAM_ENUMERATION("OfflinePages.RedirectResult", | |
| 329 static_cast<int>(result), | |
| 330 static_cast<int>(RedirectResult::REDIRECT_RESULT_MAX)); | |
| 331 } | 127 } |
| 332 | 128 |
| 333 } // namespace offline_pages | 129 } // namespace offline_pages |
| OLD | NEW |