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

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: Fix junit test 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_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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698