Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(671)

Side by Side Diff: chrome/browser/android/offline_pages/offline_page_tab_helper.cc

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

Powered by Google App Engine
This is Rietveld 408576698