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 weak_ptr_factory_(this) { | 27 weak_ptr_factory_(this) { |
79 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | 28 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
80 } | 29 } |
81 | 30 |
82 OfflinePageTabHelper::~OfflinePageTabHelper() {} | 31 OfflinePageTabHelper::~OfflinePageTabHelper() {} |
83 | 32 |
84 void OfflinePageTabHelper::SetDelegateForTesting( | |
85 std::unique_ptr<OfflinePageTabHelper::Delegate> delegate) { | |
86 DCHECK(delegate); | |
87 delegate_ = std::move(delegate); | |
88 } | |
89 | |
90 void OfflinePageTabHelper::DidStartNavigation( | 33 void OfflinePageTabHelper::DidStartNavigation( |
91 content::NavigationHandle* navigation_handle) { | 34 content::NavigationHandle* navigation_handle) { |
92 // Skips non-main frame. | 35 // Skips non-main frame. |
93 if (!navigation_handle->IsInMainFrame()) | 36 if (!navigation_handle->IsInMainFrame()) |
94 return; | 37 return; |
95 | 38 |
96 // 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 |
97 // operations. | 40 // operations. |
98 weak_ptr_factory_.InvalidateWeakPtrs(); | 41 weak_ptr_factory_.InvalidateWeakPtrs(); |
99 | 42 |
100 // 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, |
101 // unless we are currently looking at an offline page. | 44 offline_page_ = nullptr; |
102 GURL navigated_url = navigation_handle->GetURL(); | |
103 if (offline_page_ && navigated_url != offline_page_->GetOfflineURL()) | |
104 offline_page_ = nullptr; | |
105 | |
106 // If an offline download file is opened, don't do redirect per network | |
107 // conditions. Also store a cached copy here such that Tab knows that | |
108 // the offline page is opened. | |
109 if (OfflinePageUtils::MightBeOfflineURL(navigated_url)) { | |
110 OfflinePageModel* offline_page_model = | |
111 OfflinePageModelFactory::GetForBrowserContext( | |
112 web_contents()->GetBrowserContext()); | |
113 if (offline_page_model) { | |
114 const OfflinePageItem* offline_page = | |
115 offline_page_model->MaybeGetPageByOfflineURL(navigated_url); | |
116 if (offline_page && | |
117 offline_page->client_id.name_space == kDownloadNamespace) { | |
118 offline_page_ = base::MakeUnique<OfflinePageItem>(*offline_page); | |
119 return; | |
120 } | |
121 } | |
122 } | |
123 | |
124 // Ignore navigations that are forward or back transitions in the nav stack | |
125 // which are not at the head of the stack. | |
126 // TODO(dimich): Not sure this is needed. Clarify and remove. Bug 624216. | |
127 const content::NavigationController& controller = | |
128 web_contents()->GetController(); | |
129 if (controller.GetEntryCount() > 0 && | |
130 controller.GetCurrentEntryIndex() != -1 && | |
131 controller.GetCurrentEntryIndex() < controller.GetEntryCount() - 1) { | |
132 return; | |
133 } | |
134 | |
135 if (net::NetworkChangeNotifier::IsOffline()) { | |
136 GetBestPageForRedirectToOffline( | |
137 RedirectResult::REDIRECTED_ON_DISCONNECTED_NETWORK, navigated_url); | |
138 return; | |
139 } | |
140 | |
141 content::BrowserContext* context = web_contents()->GetBrowserContext(); | |
142 if (ShouldUseOfflineForSlowNetwork(context)) { | |
143 GetBestPageForRedirectToOffline( | |
144 RedirectResult::REDIRECTED_ON_PROHIBITIVELY_SLOW_NETWORK, | |
145 navigated_url); | |
146 return; | |
147 } | |
148 | |
149 OfflinePageModel* offline_page_model = | |
150 OfflinePageModelFactory::GetForBrowserContext(context); | |
151 if (!offline_page_model) | |
152 return; | |
153 | |
154 offline_page_model->GetPageByOfflineURL( | |
155 navigated_url, base::Bind(&OfflinePageTabHelper::RedirectToOnline, | |
156 weak_ptr_factory_.GetWeakPtr(), navigated_url)); | |
157 } | 45 } |
158 | 46 |
159 void OfflinePageTabHelper::DidFinishNavigation( | 47 void OfflinePageTabHelper::DidFinishNavigation( |
160 content::NavigationHandle* navigation_handle) { | 48 content::NavigationHandle* navigation_handle) { |
161 // Skips non-main frame. | 49 // Skips non-main frame. |
162 if (!navigation_handle->IsInMainFrame()) | 50 if (!navigation_handle->IsInMainFrame()) |
163 return; | 51 return; |
164 | 52 |
165 GURL navigated_url = navigation_handle->GetURL(); | 53 // We might be reloading the URL in order to fetch the offline page. |
166 net::Error error_code = navigation_handle->GetNetErrorCode(); | 54 // * If successful, nothing to do. |
167 content::BrowserContext* browser_context = | 55 // * Otherwise, we're hitting error again. Bail out to avoid loop. |
168 web_contents()->GetBrowserContext(); | 56 if (reloading_url_on_net_error_) { |
169 | 57 reloading_url_on_net_error_ = false; |
Dmitry Titov
2016/08/20 01:08:14
Would it be better to reset reloading_url_on_net_e
jianli
2016/08/20 01:48:52
Done.
| |
170 // If the offline page is being loaded successfully, set the access record but | |
171 // no need to do anything else. | |
172 if (error_code == net::OK) { | |
173 OfflinePageUtils::GetOnlineURLForOfflineURL( | |
174 browser_context, navigated_url, | |
175 base::Bind(&ReportAccessedOfflinePage, browser_context, navigated_url)); | |
176 return; | 58 return; |
177 } | 59 } |
178 | 60 |
179 // When the navigation starts, we redirect immediately from online page to | 61 // When the navigation starts, the request might be intercepted to serve the |
180 // 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 |
181 // 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 |
182 // 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 |
183 // 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 |
184 // 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(); | |
185 if (error_code != net::ERR_INTERNET_DISCONNECTED && | 69 if (error_code != net::ERR_INTERNET_DISCONNECTED && |
186 error_code != net::ERR_NAME_NOT_RESOLVED && | 70 error_code != net::ERR_NAME_NOT_RESOLVED && |
187 error_code != net::ERR_ADDRESS_UNREACHABLE && | 71 error_code != net::ERR_ADDRESS_UNREACHABLE && |
188 error_code != net::ERR_PROXY_CONNECTION_FAILED) { | 72 error_code != net::ERR_PROXY_CONNECTION_FAILED) { |
189 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 OfflinePageRequestJob::ReportAggregatedRequestResult( | |
77 AggregatedRequestResult::SHOW_NET_ERROR_PAGE); | |
78 } | |
190 return; | 79 return; |
191 } | 80 } |
192 | 81 |
193 // Don't actually want to redirect on a forward/back nav. | |
194 // TODO(dimich): Clarify and possibly redirect as well. Bug 624216. | |
195 if (ui::PageTransitionTypeIncludingQualifiersIs( | |
196 navigation_handle->GetPageTransition(), | |
197 ui::PAGE_TRANSITION_FORWARD_BACK)) { | |
198 ReportRedirectResultUMA(RedirectResult::IGNORED_FLAKY_NETWORK_FORWARD_BACK); | |
199 return; | |
200 } | |
201 | |
202 GetBestPageForRedirectToOffline( | |
203 RedirectResult::REDIRECTED_ON_FLAKY_NETWORK, navigated_url); | |
204 } | |
205 | |
206 void OfflinePageTabHelper::RedirectToOnline( | |
207 const GURL& navigated_url, | |
208 const OfflinePageItem* offline_page) { | |
209 // Bails out if no redirection is needed. No UMA reporting since all regular | |
210 // navigations will be here and it'll dwarf the useful reporting. | |
211 if (!offline_page) | |
212 return; | |
213 | |
214 GURL redirect_url = offline_page->url; | |
215 if (IsInRedirectLoop(redirect_url)) { | |
216 ReportRedirectResultUMA(RedirectResult::REDIRECT_LOOP_ONLINE); | |
217 return; | |
218 } | |
219 | |
220 Redirect(navigated_url, redirect_url); | |
221 // Clear the offline page since we are redirecting to online. | |
222 offline_page_ = nullptr; | |
223 | |
224 ReportRedirectResultUMA(RedirectResult::REDIRECTED_ON_CONNECTED_NETWORK); | |
225 } | |
226 | |
227 void OfflinePageTabHelper::GetBestPageForRedirectToOffline( | |
228 RedirectResult result, const GURL& online_url) { | |
229 // 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 |
230 // page, so we can leave. | 83 // page, so we can leave. |
231 int tab_id; | 84 int tab_id; |
232 if (!delegate_->GetTabId(web_contents(), &tab_id)) { | 85 if (!OfflinePageUtils::GetTabId(web_contents(), &tab_id)) { |
233 ReportRedirectResultUMA(RedirectResult::NO_TAB_ID); | 86 OfflinePageRequestJob::ReportAggregatedRequestResult( |
87 AggregatedRequestResult::NO_TAB_ID); | |
88 // No need to report NO_TAB_ID since it should have already been detected | |
Dmitry Titov
2016/08/20 01:08:14
I don't understand that comment. It says no need t
jianli
2016/08/20 01:48:52
Forgot to remove the report code. Done.
| |
89 // and reported in offline page request handler. | |
234 return; | 90 return; |
235 } | 91 } |
236 | 92 |
237 OfflinePageUtils::SelectPageForOnlineURL( | 93 OfflinePageUtils::SelectPageForOnlineURL( |
238 web_contents()->GetBrowserContext(), | 94 web_contents()->GetBrowserContext(), |
239 online_url, | 95 navigation_handle->GetURL(), |
240 tab_id, | 96 tab_id, |
241 base::Bind(&OfflinePageTabHelper::SelectPageForOnlineURLDone, | 97 base::Bind(&OfflinePageTabHelper::SelectPageForOnlineURLDone, |
242 weak_ptr_factory_.GetWeakPtr(), result, online_url)); | 98 weak_ptr_factory_.GetWeakPtr())); |
243 } | 99 } |
244 | 100 |
245 void OfflinePageTabHelper::SelectPageForOnlineURLDone( | 101 void OfflinePageTabHelper::SelectPageForOnlineURLDone( |
246 RedirectResult result, | |
247 const GURL& online_url, | |
248 const OfflinePageItem* offline_page) { | 102 const OfflinePageItem* offline_page) { |
249 DCHECK(result == RedirectResult::REDIRECTED_ON_FLAKY_NETWORK || | 103 // Bails out if no offline page is found. |
250 result == RedirectResult::REDIRECTED_ON_DISCONNECTED_NETWORK || | 104 if (!offline_page) |
Dmitry Titov
2016/08/20 01:08:14
Do we loose some UMA counts here? This seems to be
jianli
2016/08/20 01:48:52
Done.
| |
251 result == RedirectResult::REDIRECTED_ON_PROHIBITIVELY_SLOW_NETWORK); | |
252 | |
253 if (!offline_page) { | |
254 switch (result) { | |
255 case RedirectResult::REDIRECTED_ON_FLAKY_NETWORK: | |
256 ReportRedirectResultUMA( | |
257 RedirectResult::PAGE_NOT_FOUND_ON_FLAKY_NETWORK); | |
258 return; | |
259 case RedirectResult::REDIRECTED_ON_PROHIBITIVELY_SLOW_NETWORK: | |
260 ReportRedirectResultUMA( | |
261 RedirectResult::PAGE_NOT_FOUND_ON_PROHIBITIVELY_SLOW_NETWORK); | |
262 return; | |
263 case RedirectResult::REDIRECTED_ON_DISCONNECTED_NETWORK: | |
264 ReportRedirectResultUMA( | |
265 RedirectResult::PAGE_NOT_FOUND_ON_DISCONNECTED_NETWORK); | |
266 return; | |
267 default: | |
268 NOTREACHED(); | |
269 return; | |
270 } | |
271 } | |
272 | |
273 // If the page is being loaded on a slow network, only use the offline page | |
274 // if it was created within the past 7 days. | |
275 if (result == RedirectResult::REDIRECTED_ON_PROHIBITIVELY_SLOW_NETWORK && | |
276 delegate_->Now() - offline_page->creation_time > | |
277 base::TimeDelta::FromDays(7)) { | |
278 ReportRedirectResultUMA( | |
279 RedirectResult::PAGE_NOT_FRESH_ON_PROHIBITIVELY_SLOW_NETWORK); | |
280 return; | |
281 } | |
282 | |
283 TryRedirectToOffline(result, online_url, *offline_page); | |
284 } | |
285 | |
286 void OfflinePageTabHelper::TryRedirectToOffline( | |
287 RedirectResult result, | |
288 const GURL& from_url, | |
289 const OfflinePageItem& offline_page) { | |
290 GURL redirect_url = offline_page.GetOfflineURL(); | |
291 if (!redirect_url.is_valid()) | |
292 return; | 105 return; |
293 | 106 |
294 if (IsInRedirectLoop(redirect_url)) { | 107 reloading_url_on_net_error_ = true; |
295 ReportRedirectResultUMA(RedirectResult::REDIRECT_LOOP_OFFLINE); | |
296 return; | |
297 } | |
298 | 108 |
299 Redirect(from_url, redirect_url); | 109 // Reloads the page with extra header set to force loading the offline page. |
300 offline_page_ = base::MakeUnique<OfflinePageItem>(offline_page); | 110 content::NavigationController::LoadURLParams load_params(offline_page->url); |
301 ReportRedirectResultUMA(result); | 111 load_params.transition_type = ui::PAGE_TRANSITION_RELOAD; |
302 } | 112 load_params.extra_headers = kLoadingOfflinePageHeader; |
303 | 113 load_params.extra_headers += ":"; |
304 void OfflinePageTabHelper::Redirect(const GURL& from_url, const GURL& to_url) { | 114 load_params.extra_headers += kLoadingOfflinePageReason; |
305 content::NavigationController::LoadURLParams load_params(to_url); | 115 load_params.extra_headers += kLoadingOfflinePageDueToNetError; |
306 load_params.transition_type = ui::PAGE_TRANSITION_CLIENT_REDIRECT; | |
307 load_params.redirect_chain.push_back(from_url); | |
308 web_contents()->GetController().LoadURLWithParams(load_params); | 116 web_contents()->GetController().LoadURLWithParams(load_params); |
309 } | 117 } |
310 | 118 |
311 bool OfflinePageTabHelper::IsInRedirectLoop(const GURL& to_url) const { | 119 void OfflinePageTabHelper::SetOfflinePage(const OfflinePageItem& offline_page) { |
312 // Detects looping between online and offline redirections. | 120 offline_page_ = base::MakeUnique<OfflinePageItem>(offline_page); |
313 const content::NavigationController& controller = | |
314 web_contents()->GetController(); | |
315 content::NavigationEntry* entry = controller.GetPendingEntry(); | |
316 return entry && | |
317 !entry->GetRedirectChain().empty() && | |
318 entry->GetRedirectChain().back() == to_url; | |
319 } | |
320 | |
321 void OfflinePageTabHelper::ReportRedirectResultUMA(RedirectResult result) { | |
322 UMA_HISTOGRAM_ENUMERATION("OfflinePages.RedirectResult", | |
323 static_cast<int>(result), | |
324 static_cast<int>(RedirectResult::REDIRECT_RESULT_MAX)); | |
325 } | 121 } |
326 | 122 |
327 } // namespace offline_pages | 123 } // namespace offline_pages |
OLD | NEW |