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

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: 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_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/network_change_notifier.h"
22 #include "net/nqe/network_quality_estimator.h"
9 #include "net/url_request/url_request.h" 23 #include "net/url_request/url_request.h"
24 #include "net/url_request/url_request_context.h"
10 #include "net/url_request/url_request_interceptor.h" 25 #include "net/url_request/url_request_interceptor.h"
26 #include "net/url_request/url_request_job.h"
11 27
12 namespace offline_pages { 28 namespace offline_pages {
13 29
30 const char kLoadingOfflinePageHeader[] = "X-chromium-offline";
31 const char kLoadingOfflinePageReason[] = "reason=";
32 const char kLoadingOfflinePageDueToNetError[] = "error";
33
14 namespace { 34 namespace {
15 35
36 // This enum is used to tell all possible outcomes of handling network requests
37 // that might serve offline contents.
38 enum class RequestResult {
39 OFFLINE_PAGE_SERVED,
40 NO_TAB_ID,
41 NO_WEB_CONTENTS,
42 PAGE_NOT_FRESH,
43 OFFLINE_PAGE_NOT_FOUND
44 };
45
16 int kUserDataKey; // Only address is used. 46 int kUserDataKey; // Only address is used.
17 47
18 // An interceptor to hijack requests and potentially service them based on 48 // An interceptor to hijack requests and potentially service them based on
19 // their offline information. 49 // their offline information.
20 class OfflinePageRequestInterceptor : public net::URLRequestInterceptor { 50 class OfflinePageRequestInterceptor : public net::URLRequestInterceptor {
21 public: 51 public:
22 explicit OfflinePageRequestInterceptor(void* profile_id) 52 explicit OfflinePageRequestInterceptor(void* profile_id)
23 : profile_id_(profile_id) { 53 : profile_id_(profile_id) {
24 DCHECK(profile_id); 54 DCHECK(profile_id);
25 } 55 }
(...skipping 10 matching lines...) Expand all
36 return nullptr; 66 return nullptr;
37 return handler->MaybeCreateJob(request, network_delegate, profile_id_); 67 return handler->MaybeCreateJob(request, network_delegate, profile_id_);
38 } 68 }
39 69
40 // The profile for processing offline pages. 70 // The profile for processing offline pages.
41 void* const profile_id_; 71 void* const profile_id_;
42 72
43 DISALLOW_COPY_AND_ASSIGN(OfflinePageRequestInterceptor); 73 DISALLOW_COPY_AND_ASSIGN(OfflinePageRequestInterceptor);
44 }; 74 };
45 75
76 NetworkState GetNetworkState(net::URLRequest* request) {
Dmitry Titov 2016/08/16 20:02:55 This can be split into several functions and then
jianli 2016/08/18 22:46:35 Done.
77 DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
78
79 // If extra header kLoadingOfflinePageHeader is present, offline page should
80 // be loaded if available.
81 bool has_offline_header = false;
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 if (base::EqualsCaseInsensitiveASCII(reason,
97 kLoadingOfflinePageDueToNetError)) {
98 return NetworkState::FLAKY_NETWORK;
99 }
100 has_offline_header = true;
101 }
102
103 if (net::NetworkChangeNotifier::IsOffline())
104 return NetworkState::DISCONNECTED_NETWORK;
105
106 // NetworkQualityEstimator only works when it is enabled.
107 if (previews::IsOfflinePreviewsEnabled()) {
108 net::NetworkQualityEstimator* network_quality_estimator =
109 request->context() ? request->context()->network_quality_estimator()
110 : nullptr;
111 if (network_quality_estimator) {
112 net::EffectiveConnectionType effective_connection_type =
113 network_quality_estimator->GetEffectiveConnectionType();
114 if (effective_connection_type >= net::EFFECTIVE_CONNECTION_TYPE_OFFLINE &&
115 effective_connection_type <= net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G) {
116 return NetworkState::PROHIBITIVELY_SLOW_NETWORK;
117 }
118 }
119 }
120
121 return has_offline_header ? NetworkState::FORCE_OFFLINE_ON_CONNECTED_NETWORK
122 : NetworkState::CONNECTED_NETWORK;
123 }
124
125 AggregatedRequestResult RequestResultToAggregatedRequestResult(
126 RequestResult request_result, NetworkState network_state) {
127 if (request_result == RequestResult::NO_TAB_ID)
128 return AggregatedRequestResult::NO_TAB_ID;
129
130 if (request_result == RequestResult::NO_WEB_CONTENTS)
131 return AggregatedRequestResult::NO_WEB_CONTENTS;
132
133 if (request_result == RequestResult::PAGE_NOT_FRESH) {
134 DCHECK_EQ(NetworkState::PROHIBITIVELY_SLOW_NETWORK, network_state);
135 return
136 AggregatedRequestResult::PAGE_NOT_FRESH_ON_PROHIBITIVELY_SLOW_NETWORK;
137 }
138
139 if (request_result == RequestResult::OFFLINE_PAGE_NOT_FOUND) {
140 switch (network_state) {
141 case NetworkState::DISCONNECTED_NETWORK:
142 return AggregatedRequestResult::PAGE_NOT_FOUND_ON_DISCONNECTED_NETWORK;
143 case NetworkState::PROHIBITIVELY_SLOW_NETWORK:
144 return AggregatedRequestResult::
145 PAGE_NOT_FOUND_ON_PROHIBITIVELY_SLOW_NETWORK;
146 case NetworkState::FLAKY_NETWORK:
147 return AggregatedRequestResult::PAGE_NOT_FOUND_ON_FLAKY_NETWORK;
148 case NetworkState::FORCE_OFFLINE_ON_CONNECTED_NETWORK:
149 return AggregatedRequestResult::PAGE_NOT_FOUND_ON_CONNECTED_NETWORK;
150 default:
151 NOTREACHED();
152 }
153 }
154
155 DCHECK_EQ(RequestResult::OFFLINE_PAGE_SERVED, request_result);
156 switch (network_state) {
157 case NetworkState::DISCONNECTED_NETWORK:
158 return AggregatedRequestResult::SHOW_OFFLINE_ON_DISCONNECTED_NETWORK;
159 case NetworkState::PROHIBITIVELY_SLOW_NETWORK:
160 return AggregatedRequestResult::
161 SHOW_OFFLINE_ON_PROHIBITIVELY_SLOW_NETWORK;
162 case NetworkState::FLAKY_NETWORK:
163 return AggregatedRequestResult::SHOW_OFFLINE_ON_FLAKY_NETWORK;
164 case NetworkState::CONNECTED_NETWORK:
165 return AggregatedRequestResult::SHOW_OFFLINE_ON_CONNECTED_NETWORK;
166 case NetworkState::FORCE_OFFLINE_ON_CONNECTED_NETWORK:
167 return AggregatedRequestResult::SHOW_OFFLINE_ON_CONNECTED_NETWORK;
168 default:
169 NOTREACHED();
170 }
171
172 return AggregatedRequestResult::AGGREGATED_REQUEST_RESULT_MAX;
173 }
174
175 void ReportRequestResult(RequestResult request_result,
176 NetworkState network_state) {
177 OfflinePageRequestHandler::ReportAggregatedRequestResult(
178 RequestResultToAggregatedRequestResult(request_result, network_state));
179 }
180
181 void NotifyOfflineFilePathOnIO(base::WeakPtr<OfflinePageRequestJob> job,
182 const base::FilePath& offline_file_path) {
183 DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
184
185 if (!job)
186 return;
187
188 if (offline_file_path.empty())
189 job->FallbackToDefault();
190 else
191 job->SetOfflineFilePath(offline_file_path);
192 }
193
194 // Notifies OfflinePageRequestJob about the offline file path. Note that the
195 // file path may be empty if not found or on error.
196 void NotifyOfflineFilePathOnUI(base::WeakPtr<OfflinePageRequestJob> job,
197 const base::FilePath& offline_file_path) {
198 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
199
200 // Delegates to IO thread since OfflinePageRequestJob should only be accessed
201 // from IO thread.
202 content::BrowserThread::PostTask(
203 content::BrowserThread::IO,
204 FROM_HERE,
205 base::Bind(&NotifyOfflineFilePathOnIO, job, offline_file_path));
206 }
207
208 // Handles the result of finding an offline page.
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) {
Dmitry Titov 2016/08/16 20:02:55 early return would reduce the nesting level here.
jianli 2016/08/18 22:46:35 Done.
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 // TODO(romax): Make the constant be policy driven.
225 if (network_state == NetworkState::PROHIBITIVELY_SLOW_NETWORK &&
226 base::Time::Now() - offline_page->creation_time >
227 base::TimeDelta::FromDays(1)) {
228 request_result = RequestResult::PAGE_NOT_FRESH;
Dmitry Titov 2016/08/16 20:02:55 It would be probbaly more readable if this code ha
jianli 2016/08/18 22:46:35 Done.
229 } else {
230 // |web_contents_getter| is passed from IO thread. We neeed to check if
231 // web contents is still valid.
232 content::WebContents* web_contents = web_contents_getter.Run();
233 if (web_contents) {
234 request_result = RequestResult::OFFLINE_PAGE_SERVED;
235
236 offline_file_path = offline_page->file_path;
237
238 // Since offline page will be loaded, it should be marked as accessed.
239 OfflinePageModel* offline_page_model =
240 OfflinePageModelFactory::GetForBrowserContext(
241 web_contents->GetBrowserContext());
242 DCHECK(offline_page_model);
243 offline_page_model->MarkPageAccessed(offline_page->offline_id);
244
245 // Save an cached copy of OfflinePageItem such that Tab code can get
246 // the loaded offline page immediately.
247 OfflinePageTabHelper* tab_helper =
248 OfflinePageTabHelper::FromWebContents(web_contents);
249 DCHECK(tab_helper);
250 tab_helper->SetOfflinePage(*offline_page);
251 } else {
252 request_result = RequestResult::NO_WEB_CONTENTS;
253 }
254 }
255 } else {
256 request_result = RequestResult::OFFLINE_PAGE_NOT_FOUND;
257 }
258
259 ReportRequestResult(request_result, network_state);
260 NotifyOfflineFilePathOnUI(job, offline_file_path);
261 }
262
46 } // namespace 263 } // namespace
47 264
48 // static 265 // static
49 void OfflinePageRequestHandler::InitializeHandler( 266 void OfflinePageRequestHandler::InitializeHandler(
50 net::URLRequest* request, 267 net::URLRequest* request,
51 content::ResourceType resource_type) { 268 content::ResourceType resource_type) {
52 if (!IsOfflinePagesEnabled()) 269 if (!IsOfflinePagesEnabled())
53 return; 270 return;
54 271
55 // Ignore the requests not for the main resource. 272 // Ignore the requests not for the main resource.
56 if (resource_type != content::RESOURCE_TYPE_MAIN_FRAME) 273 if (resource_type != content::RESOURCE_TYPE_MAIN_FRAME)
57 return; 274 return;
58 275
59 // Ignore non-http/https requests. 276 // Ignore non-http/https requests.
60 if (!request->url().SchemeIsHTTPOrHTTPS()) 277 if (!request->url().SchemeIsHTTPOrHTTPS())
61 return; 278 return;
62 279
63 // Ignore requests other than GET. 280 // Ignore requests other than GET.
64 if (request->method() != "GET") 281 if (request->method() != "GET")
65 return; 282 return;
66 283
284 // Bail out if no need to load offline page.
285 NetworkState network_state = GetNetworkState(request);
286 if (network_state == NetworkState::CONNECTED_NETWORK)
287 return;
288
67 // Any more precise checks to see if the request should be intercepted are 289 // Any more precise checks to see if the request should be intercepted are
68 // asynchronous, so just create our handler in all cases. 290 // asynchronous, so just create our handler in all cases.
69 request->SetUserData(&kUserDataKey, new OfflinePageRequestHandler()); 291 request->SetUserData(&kUserDataKey,
292 new OfflinePageRequestHandler(network_state));
mmenke 2016/08/17 17:09:28 Rather than attach an object that does stuff to th
jianli 2016/08/18 22:46:35 Done.
70 } 293 }
71 294
72 // static 295 // static
73 OfflinePageRequestHandler* OfflinePageRequestHandler::GetHandler( 296 OfflinePageRequestHandler* OfflinePageRequestHandler::GetHandler(
74 net::URLRequest* request) { 297 net::URLRequest* request) {
75 return static_cast<OfflinePageRequestHandler*>( 298 return static_cast<OfflinePageRequestHandler*>(
76 request->GetUserData(&kUserDataKey)); 299 request->GetUserData(&kUserDataKey));
77 } 300 }
78 301
79 // static 302 // static
80 std::unique_ptr<net::URLRequestInterceptor> 303 std::unique_ptr<net::URLRequestInterceptor>
81 OfflinePageRequestHandler::CreateInterceptor(void* profile_id) { 304 OfflinePageRequestHandler::CreateInterceptor(void* profile_id) {
82 if (!IsOfflinePagesEnabled()) 305 if (!IsOfflinePagesEnabled())
83 return nullptr; 306 return nullptr;
84 307
85 return std::unique_ptr<net::URLRequestInterceptor>( 308 return std::unique_ptr<net::URLRequestInterceptor>(
86 new OfflinePageRequestInterceptor(profile_id)); 309 new OfflinePageRequestInterceptor(profile_id));
87 } 310 }
88 311
89 OfflinePageRequestHandler::OfflinePageRequestHandler() { 312 // static
313 void OfflinePageRequestHandler::ReportAggregatedRequestResult(
314 AggregatedRequestResult result) {
315 UMA_HISTOGRAM_ENUMERATION("OfflinePages.AggregatedRequestResult",
316 static_cast<int>(result),
317 static_cast<int>(AggregatedRequestResult::AGGREGATED_REQUEST_RESULT_MAX));
318 }
319
320 OfflinePageRequestHandler::OfflinePageRequestHandler(NetworkState network_state)
321 : network_state_(network_state),
322 weak_ptr_factory_(this) {
323 DCHECK_NE(NetworkState::CONNECTED_NETWORK, network_state);
90 } 324 }
91 325
92 OfflinePageRequestHandler::~OfflinePageRequestHandler() { 326 OfflinePageRequestHandler::~OfflinePageRequestHandler() {
93 } 327 }
94 328
329 void OfflinePageRequestHandler::OnPrepareToRestart() {
330 use_default_ = true;
331 offline_job_.reset();
332 }
333
95 net::URLRequestJob* OfflinePageRequestHandler::MaybeCreateJob( 334 net::URLRequestJob* OfflinePageRequestHandler::MaybeCreateJob(
96 net::URLRequest* request, 335 net::URLRequest* request,
97 net::NetworkDelegate* network_delegate, 336 net::NetworkDelegate* network_delegate,
98 void* profile_id) { 337 void* profile_id) {
99 DCHECK_CURRENTLY_ON(content::BrowserThread::IO); 338 DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
100 339
101 // TODO(jianli): to be implemented. 340 // Fall back to default which is set when offline page is not found.
102 return nullptr; 341 if (use_default_)
342 return nullptr;
343
344 OfflinePageRequestJob* offline_job =
345 new OfflinePageRequestJob(request, network_delegate, this);
346 offline_job_ = offline_job->GetWeakPtr();
347
348 content::BrowserThread::PostTask(
349 content::BrowserThread::UI,
350 FROM_HERE,
351 base::Bind(&OfflinePageRequestHandler::GetOfflineFileURL,
mmenke 2016/08/17 17:09:28 This do-two-things-at-once behavior seems weird to
jianli 2016/08/18 22:46:35 Done.
352 weak_ptr_factory_.GetWeakPtr(),
353 request->url(),
354 profile_id,
355 content::ResourceRequestInfo::ForRequest(request)->
356 GetWebContentsGetterForRequest()));
357
358 return offline_job_.get();
359 }
360
361 void OfflinePageRequestHandler::GetOfflineFileURL(
362 const GURL& online_url,
363 void* profile_id,
364 content::ResourceRequestInfo::WebContentsGetter web_contents_getter){
365 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
366
367 // |profile_id| needs to be checked with ProfileManager::IsValidProfile
368 // before using it.
369 if (!g_browser_process->profile_manager()->IsValidProfile(profile_id)) {
370 FailedToGetOfflineFileURL();
371 return;
372 }
373 Profile* profile = reinterpret_cast<Profile*>(profile_id);
374
375 content::WebContents* web_contents = web_contents_getter.Run();
mmenke 2016/08/17 17:09:28 web_contents_getter can return nullptr - the tab c
jianli 2016/08/18 22:46:35 Done.
376 int tab_id;
377 if (!OfflinePageUtils::GetTabId(web_contents, &tab_id)) {
378 ReportRequestResult(RequestResult::NO_TAB_ID, network_state_);
379 FailedToGetOfflineFileURL();
380 return;
381 }
382
383 OfflinePageUtils::SelectPageForOnlineURL(
384 profile,
385 online_url,
386 tab_id,
387 base::Bind(&SelectPageForOnlineURLDone,
388 network_state_,
389 offline_job_,
390 web_contents_getter));
391 }
392
393 void OfflinePageRequestHandler::FailedToGetOfflineFileURL() {
394 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
395
396 // Proceed with empty file path in order to notify the OfflinePageRequestJob
397 // about the failure.
398 base::FilePath empty_file_path;
399 NotifyOfflineFilePathOnUI(offline_job_, empty_file_path);
mmenke 2016/08/17 17:09:28 BUG: we're on the UI thread, this class is owned
jianli 2016/08/18 22:46:35 Done.
103 } 400 }
104 401
105 } // namespace offline_pages 402 } // namespace offline_pages
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698