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

Side by Side Diff: chrome/browser/android/offline_pages/offline_page_request_handler.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: Update 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_request_handler.h" 5 #include "chrome/browser/android/offline_pages/offline_page_request_handler.h"
6 6
7 #include "base/metrics/histogram_macros.h"
8 #include "base/strings/string_tokenizer.h"
9 #include "base/strings/string_util.h"
10 #include "chrome/browser/android/offline_pages/offline_page_model_factory.h"
11 #include "chrome/browser/android/offline_pages/offline_page_tab_helper.h"
12 #include "chrome/browser/android/offline_pages/offline_page_utils.h"
13 #include "chrome/browser/browser_process.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "chrome/browser/profiles/profile_manager.h"
7 #include "components/offline_pages/offline_page_feature.h" 16 #include "components/offline_pages/offline_page_feature.h"
17 #include "components/offline_pages/offline_page_model.h"
18 #include "components/previews/previews_experiments.h"
8 #include "content/public/browser/browser_thread.h" 19 #include "content/public/browser/browser_thread.h"
20 #include "content/public/browser/web_contents.h"
21 #include "net/base/filename_util.h"
22 #include "net/base/network_change_notifier.h"
23 #include "net/nqe/network_quality_estimator.h"
9 #include "net/url_request/url_request.h" 24 #include "net/url_request/url_request.h"
25 #include "net/url_request/url_request_context.h"
10 #include "net/url_request/url_request_interceptor.h" 26 #include "net/url_request/url_request_interceptor.h"
27 #include "net/url_request/url_request_job.h"
11 28
12 namespace offline_pages { 29 namespace offline_pages {
13 30
31 const char kLoadingOfflinePageHeader[] = "X-chromium-offline";
32 const char kLoadingOfflinePageReason[] = "reason=";
33 const char kLoadingOfflinePageDueToNetError[] = "error";
34
14 namespace { 35 namespace {
15 36
37 // This enum is used to tell all possible outcomes of handling network requests
38 // that might serve offline contents.
39 enum class RequestResult {
40 OFFLINE_PAGE_SERVED,
41 NO_TAB_ID,
42 NO_WEB_CONTENTS,
43 PAGE_NOT_FRESH,
44 OFFLINE_PAGE_NOT_FOUND
45 };
46
16 int kUserDataKey; // Only address is used. 47 int kUserDataKey; // Only address is used.
17 48
18 // An interceptor to hijack requests and potentially service them based on 49 // An interceptor to hijack requests and potentially service them based on
19 // their offline information. 50 // their offline information.
20 class OfflinePageRequestInterceptor : public net::URLRequestInterceptor { 51 class OfflinePageRequestInterceptor : public net::URLRequestInterceptor {
21 public: 52 public:
22 explicit OfflinePageRequestInterceptor(void* profile_id) 53 explicit OfflinePageRequestInterceptor(void* profile_id)
23 : profile_id_(profile_id) { 54 : profile_id_(profile_id) {
24 DCHECK(profile_id); 55 DCHECK(profile_id);
25 } 56 }
(...skipping 10 matching lines...) Expand all
36 return nullptr; 67 return nullptr;
37 return handler->MaybeCreateJob(request, network_delegate, profile_id_); 68 return handler->MaybeCreateJob(request, network_delegate, profile_id_);
38 } 69 }
39 70
40 // The profile for processing offline pages. 71 // The profile for processing offline pages.
41 void* const profile_id_; 72 void* const profile_id_;
42 73
43 DISALLOW_COPY_AND_ASSIGN(OfflinePageRequestInterceptor); 74 DISALLOW_COPY_AND_ASSIGN(OfflinePageRequestInterceptor);
44 }; 75 };
45 76
77 NetworkState GetNetworkState(net::URLRequest* request) {
78 DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
79
80 // If extra header kLoadingOfflinePageHeader is present, offline page should
81 // be loaded if available.
82 std::string value;
83 if (request->extra_request_headers().GetHeader(kLoadingOfflinePageHeader,
84 &value)) {
85 std::string reason;
86 base::StringTokenizer tokenizer(value, ", ");
87 while (tokenizer.GetNext()) {
88 if (base::StartsWith(tokenizer.token(),
89 kLoadingOfflinePageReason,
90 base::CompareCase::INSENSITIVE_ASCII)) {
91 reason = tokenizer.token().substr(
92 arraysize(kLoadingOfflinePageReason) - 1);
93 break;
94 }
95 }
96 return base::EqualsCaseInsensitiveASCII(
97 reason, kLoadingOfflinePageDueToNetError) ?
98 NetworkState::FLAKY_NETWORK : NetworkState::SKIPPED_NETWORK_CHECK;
fgorski 2016/08/15 21:38:32 would it make sense to continue network checks if
jianli 2016/08/15 23:15:43 Done.
99 }
100
101 if (net::NetworkChangeNotifier::IsOffline())
102 return NetworkState::DISCONNECTED_NETWORK;
103
104 // NetworkQualityEstimator only works when it is enabled.
105 if (!previews::IsOfflinePreviewsEnabled())
106 return NetworkState::CONNECTED_NETWORK;
107
108 net::NetworkQualityEstimator* network_quality_estimator =
109 request->context() ? request->context()->network_quality_estimator()
110 : nullptr;
111 if (!network_quality_estimator)
112 return NetworkState::CONNECTED_NETWORK;
113
114 net::EffectiveConnectionType effective_connection_type =
115 network_quality_estimator->GetEffectiveConnectionType();
116 return effective_connection_type >= net::EFFECTIVE_CONNECTION_TYPE_OFFLINE &&
fgorski 2016/08/15 21:38:32 Is the effective connection type enum guaranteed t
jianli 2016/08/15 23:15:43 I think so.
117 effective_connection_type <= net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G ?
118 NetworkState::PROHIBITIVELY_SLOW_NETWORK :
119 NetworkState::CONNECTED_NETWORK;
120 }
121
122 void ReportRequestResult(RequestResult request_result,
fgorski 2016/08/15 21:38:32 Consider splitting it in 2 methods, please. (But u
jianli 2016/08/15 23:15:43 Done.
123 NetworkState network_state) {
124 AggregatedRequestResult result;
125 if (request_result == RequestResult::NO_TAB_ID) {
126 result = AggregatedRequestResult::NO_TAB_ID;
127 } else if (request_result == RequestResult::NO_WEB_CONTENTS) {
128 result = AggregatedRequestResult::NO_WEB_CONTENTS;
129 } else if (request_result == RequestResult::PAGE_NOT_FRESH) {
130 DCHECK_EQ(NetworkState::PROHIBITIVELY_SLOW_NETWORK, network_state);
131 result =
132 AggregatedRequestResult::PAGE_NOT_FRESH_ON_PROHIBITIVELY_SLOW_NETWORK;
133 } else if (request_result == RequestResult::OFFLINE_PAGE_NOT_FOUND) {
134 switch (network_state) {
135 case NetworkState::DISCONNECTED_NETWORK:
136 result =
137 AggregatedRequestResult::PAGE_NOT_FOUND_ON_DISCONNECTED_NETWORK;
138 break;
139 case NetworkState::PROHIBITIVELY_SLOW_NETWORK:
140 result =
141 AggregatedRequestResult::
142 PAGE_NOT_FOUND_ON_PROHIBITIVELY_SLOW_NETWORK;
143 break;
144 case NetworkState::FLAKY_NETWORK:
145 result =
146 AggregatedRequestResult::PAGE_NOT_FOUND_ON_DISCONNECTED_NETWORK;
fgorski 2016/08/15 21:38:32 ON_FLAKY_NETWORK
jianli 2016/08/15 23:15:43 Done.
147 break;
148 case NetworkState::SKIPPED_NETWORK_CHECK:
149 result = net::NetworkChangeNotifier::IsOffline() ?
150 AggregatedRequestResult::PAGE_NOT_FOUND_ON_DISCONNECTED_NETWORK :
151 AggregatedRequestResult::PAGE_NOT_FOUND_ON_CONNECTED_NETWORK;
152 default:
153 NOTREACHED();
154 }
155 } else {
156 DCHECK_EQ(RequestResult::OFFLINE_PAGE_SERVED, request_result);
157 switch (network_state) {
158 case NetworkState::DISCONNECTED_NETWORK:
159 result = AggregatedRequestResult::SHOW_OFFLINE_ON_DISCONNECTED_NETWORK;
160 break;
161 case NetworkState::PROHIBITIVELY_SLOW_NETWORK:
162 result =
163 AggregatedRequestResult::SHOW_OFFLINE_ON_PROHIBITIVELY_SLOW_NETWORK;
164 break;
165 case NetworkState::FLAKY_NETWORK:
166 result = AggregatedRequestResult::SHOW_OFFLINE_ON_FLAKY_NETWORK;
167 break;
168 case NetworkState::CONNECTED_NETWORK:
169 result = AggregatedRequestResult::SHOW_OFFLINE_ON_CONNECTED_NETWORK;
170 break;
171 case NetworkState::SKIPPED_NETWORK_CHECK:
172 result = net::NetworkChangeNotifier::IsOffline() ?
173 AggregatedRequestResult::SHOW_OFFLINE_ON_DISCONNECTED_NETWORK :
174 AggregatedRequestResult::SHOW_OFFLINE_ON_CONNECTED_NETWORK;
175 default:
176 NOTREACHED();
177 }
178 }
179
180 OfflinePageRequestHandler::ReportAggregatedRequestResult(result);
181 }
182
183 // Called on IO thread after an offline file path is obtained.
fgorski 2016/08/15 21:38:32 nit: tell the reader what it does, reather than wh
jianli 2016/08/15 23:15:43 Done.
184 void DidGetOfflineFilePathOnIO(base::WeakPtr<OfflinePageRequestJob> job,
185 const base::FilePath& offline_file_path) {
186 DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
187
188 if (!job)
189 return;
190
191 if (offline_file_path.empty())
192 job->FallbackToDefault();
193 else
194 job->SetOfflineFilePath(offline_file_path);
195 }
196
197 // Called on UI thread after an offline file path is obtained.
198 void DidGetOfflineFilePathOnUI(base::WeakPtr<OfflinePageRequestJob> job,
199 const base::FilePath& offline_file_path) {
200 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
201
202 content::BrowserThread::PostTask(
203 content::BrowserThread::IO,
204 FROM_HERE,
205 base::Bind(&DidGetOfflineFilePathOnIO, job, offline_file_path));
206 }
207
208 // Called on UI thread after an offline page is found.
fgorski 2016/08/15 21:38:32 ditto
jianli 2016/08/15 23:15:43 Done.
209 void SelectPageForOnlineURLDone(
210 NetworkState network_state,
211 base::WeakPtr<OfflinePageRequestJob> job,
212 content::ResourceRequestInfo::WebContentsGetter web_contents_getter,
213 const OfflinePageItem* offline_page){
214 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
215
216 RequestResult request_result;
217 base::FilePath offline_file_path;
218
219 // If offline page is not found, fall through with empty file path such that
220 // OfflinePageRequestJob will be notified on failure.
221 if (offline_page) {
222 // If the page is being loaded on a slow network, only use the offline page
223 // if it was created within the past day.
224 if (network_state == NetworkState::PROHIBITIVELY_SLOW_NETWORK &&
225 base::Time::Now() - offline_page->creation_time >
fgorski 2016/08/15 21:38:32 Add TODO: this should be policy driven I think: 1.
jianli 2016/08/15 23:15:43 This is ported from existing logic in OfflinePageT
226 base::TimeDelta::FromDays(1)) {
227 request_result = RequestResult::PAGE_NOT_FRESH;
228 } else {
229 net::FileURLToFilePath(offline_page->GetOfflineURL(), &offline_file_path);
fgorski 2016/08/15 21:38:32 move to if web_contents
jianli 2016/08/15 23:15:43 Done.
230
231 content::WebContents* web_contents = web_contents_getter.Run();
232 if (web_contents) {
233 request_result = RequestResult::OFFLINE_PAGE_SERVED;
234
235 OfflinePageModel* offline_page_model =
236 OfflinePageModelFactory::GetForBrowserContext(
237 web_contents->GetBrowserContext());
238 DCHECK(offline_page_model);
239 offline_page_model->MarkPageAccessed(offline_page->offline_id);
240
241 OfflinePageTabHelper* tab_helper =
242 OfflinePageTabHelper::FromWebContents(web_contents);
243 DCHECK(tab_helper);
244 tab_helper->SetOfflinePage(*offline_page);
245 } else {
246 request_result = RequestResult::NO_WEB_CONTENTS;
247 }
248 }
249 } else {
250 request_result = RequestResult::OFFLINE_PAGE_NOT_FOUND;
251 }
252
253 ReportRequestResult(request_result, network_state);
254 DidGetOfflineFilePathOnUI(job, offline_file_path);
255 }
256
46 } // namespace 257 } // namespace
47 258
48 // static 259 // static
49 void OfflinePageRequestHandler::InitializeHandler( 260 void OfflinePageRequestHandler::InitializeHandler(
50 net::URLRequest* request, 261 net::URLRequest* request,
51 content::ResourceType resource_type) { 262 content::ResourceType resource_type) {
52 if (!IsOfflinePagesEnabled()) 263 if (!IsOfflinePagesEnabled())
53 return; 264 return;
54 265
55 // Ignore the requests not for the main resource. 266 // Ignore the requests not for the main resource.
56 if (resource_type != content::RESOURCE_TYPE_MAIN_FRAME) 267 if (resource_type != content::RESOURCE_TYPE_MAIN_FRAME)
57 return; 268 return;
58 269
59 // Ignore non-http/https requests. 270 // Ignore non-http/https requests.
60 if (!request->url().SchemeIsHTTPOrHTTPS()) 271 if (!request->url().SchemeIsHTTPOrHTTPS())
61 return; 272 return;
62 273
63 // Ignore requests other than GET. 274 // Ignore requests other than GET.
64 if (request->method() != "GET") 275 if (request->method() != "GET")
65 return; 276 return;
66 277
278 // Bail out if no need to load offline page.
279 NetworkState network_state = GetNetworkState(request);
280 if (network_state == NetworkState::CONNECTED_NETWORK)
281 return;
282
67 // Any more precise checks to see if the request should be intercepted are 283 // Any more precise checks to see if the request should be intercepted are
68 // asynchronous, so just create our handler in all cases. 284 // asynchronous, so just create our handler in all cases.
69 request->SetUserData(&kUserDataKey, new OfflinePageRequestHandler()); 285 request->SetUserData(&kUserDataKey,
286 new OfflinePageRequestHandler(network_state));
70 } 287 }
71 288
72 // static 289 // static
73 OfflinePageRequestHandler* OfflinePageRequestHandler::GetHandler( 290 OfflinePageRequestHandler* OfflinePageRequestHandler::GetHandler(
74 net::URLRequest* request) { 291 net::URLRequest* request) {
75 return static_cast<OfflinePageRequestHandler*>( 292 return static_cast<OfflinePageRequestHandler*>(
76 request->GetUserData(&kUserDataKey)); 293 request->GetUserData(&kUserDataKey));
77 } 294 }
78 295
79 // static 296 // static
80 std::unique_ptr<net::URLRequestInterceptor> 297 std::unique_ptr<net::URLRequestInterceptor>
81 OfflinePageRequestHandler::CreateInterceptor(void* profile_id) { 298 OfflinePageRequestHandler::CreateInterceptor(void* profile_id) {
82 if (!IsOfflinePagesEnabled()) 299 if (!IsOfflinePagesEnabled())
83 return nullptr; 300 return nullptr;
84 301
85 return std::unique_ptr<net::URLRequestInterceptor>( 302 return std::unique_ptr<net::URLRequestInterceptor>(
86 new OfflinePageRequestInterceptor(profile_id)); 303 new OfflinePageRequestInterceptor(profile_id));
87 } 304 }
88 305
89 OfflinePageRequestHandler::OfflinePageRequestHandler() { 306 // static
307 void OfflinePageRequestHandler::ReportAggregatedRequestResult(
308 AggregatedRequestResult result) {
309 UMA_HISTOGRAM_ENUMERATION("OfflinePages.AggregatedRequestResult",
310 static_cast<int>(result),
311 static_cast<int>(AggregatedRequestResult::AGGREGATED_REQUEST_RESULT_MAX));
312 }
313
314 OfflinePageRequestHandler::OfflinePageRequestHandler(NetworkState network_state)
315 : network_state_(network_state),
316 weak_ptr_factory_(this) {
317 DCHECK_NE(NetworkState::CONNECTED_NETWORK, network_state);
90 } 318 }
91 319
92 OfflinePageRequestHandler::~OfflinePageRequestHandler() { 320 OfflinePageRequestHandler::~OfflinePageRequestHandler() {
93 } 321 }
94 322
323 void OfflinePageRequestHandler::OnPrepareToRestart() {
324 use_default_ = true;
325 offline_job_.reset();
326 }
327
95 net::URLRequestJob* OfflinePageRequestHandler::MaybeCreateJob( 328 net::URLRequestJob* OfflinePageRequestHandler::MaybeCreateJob(
96 net::URLRequest* request, 329 net::URLRequest* request,
97 net::NetworkDelegate* network_delegate, 330 net::NetworkDelegate* network_delegate,
98 void* profile_id) { 331 void* profile_id) {
99 DCHECK_CURRENTLY_ON(content::BrowserThread::IO); 332 DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
100 333
101 // TODO(jianli): to be implemented. 334 // Fall back to default which is set when offline page is not found.
102 return nullptr; 335 if (use_default_)
336 return nullptr;
fgorski 2016/08/15 21:38:32 is default selected if this returns nullptr?
jianli 2016/08/15 23:15:43 Yes.
337
338 OfflinePageRequestJob* offline_job =
339 new OfflinePageRequestJob(request, network_delegate, this);
340 offline_job_ = offline_job->GetWeakPtr();
341
342 content::BrowserThread::PostTask(
343 content::BrowserThread::UI,
344 FROM_HERE,
345 base::Bind(&OfflinePageRequestHandler::GetOfflineFileURL,
346 weak_ptr_factory_.GetWeakPtr(),
347 request->url(),
348 profile_id,
349 content::ResourceRequestInfo::ForRequest(request)->
350 GetWebContentsGetterForRequest()));
351
352 return offline_job_.get();
353 }
354
355 void OfflinePageRequestHandler::GetOfflineFileURL(
356 const GURL& online_url,
357 void* profile_id,
358 content::ResourceRequestInfo::WebContentsGetter web_contents_getter){
359 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
360
361 // |profile_id| needs to be checked with ProfileManager::IsValidProfile
362 // before using it.
363 if (!g_browser_process->profile_manager()->IsValidProfile(profile_id)) {
364 FailedToGetOfflineFileURL();
365 return;
366 }
367 Profile* profile = reinterpret_cast<Profile*>(profile_id);
368
369 content::WebContents* web_contents = web_contents_getter.Run();
370 int tab_id;
371 if (!OfflinePageUtils::GetTabId(web_contents, &tab_id)) {
372 ReportRequestResult(RequestResult::NO_TAB_ID, network_state_);
373 FailedToGetOfflineFileURL();
374 return;
375 }
376
377 OfflinePageUtils::SelectPageForOnlineURL(
378 profile,
379 online_url,
380 tab_id,
381 base::Bind(&SelectPageForOnlineURLDone,
382 network_state_,
383 offline_job_,
384 web_contents_getter));
385 }
386
387 void OfflinePageRequestHandler::FailedToGetOfflineFileURL() {
388 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
389
390 // Proceed with empty file path in order to notify the OfflinePageRequestJob
391 // about the failure.
392 base::FilePath empty_file_path;
393 DidGetOfflineFilePathOnUI(offline_job_, empty_file_path);
103 } 394 }
104 395
105 } // namespace offline_pages 396 } // namespace offline_pages
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698