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
3 // found in the LICENSE file.
4
5 #include "chrome/browser/offline_pages/background_loader_offliner.h"
6
7 #include <string>
8 #include <utility>
9 #include <vector>
10
11 #include "base/bind.h"
12 #include "base/json/json_writer.h"
13 #include "base/metrics/histogram_functions.h"
14 #include "base/metrics/histogram_macros.h"
15 #include "base/strings/stringprintf.h"
16 #include "base/system/sys_info.h"
17 #include "base/threading/thread_task_runner_handle.h"
18 #include "base/time/time.h"
19 #include "chrome/browser/offline_pages/offline_page_mhtml_archiver.h"
20 #include "chrome/browser/offline_pages/offliner_helper.h"
21 #include "chrome/browser/offline_pages/offliner_user_data.h"
22 #include "chrome/browser/previews/previews_ui_tab_helper.h"
23 #include "chrome/browser/profiles/profile.h"
24 #include "chrome/browser/ssl/security_state_tab_helper.h"
25 #include "chrome/common/chrome_isolated_world_ids.h"
26 #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_data.h"
27 #include "components/offline_pages/content/renovations/render_frame_script_injector.h"
28 #include "components/offline_pages/core/background/offliner_policy.h"
29 #include "components/offline_pages/core/background/save_page_request.h"
30 #include "components/offline_pages/core/client_namespace_constants.h"
31 #include "components/offline_pages/core/offline_page_client_policy.h"
32 #include "components/offline_pages/core/offline_page_feature.h"
33 #include "components/offline_pages/core/offline_page_model.h"
34 #include "components/offline_pages/core/renovations/page_renovation_loader.h"
35 #include "components/offline_pages/core/renovations/page_renovator.h"
36 #include "components/previews/content/previews_user_data.h"
37 #include "components/security_state/core/security_state.h"
38 #include "content/public/browser/browser_context.h"
39 #include "content/public/browser/mhtml_extra_parts.h"
40 #include "content/public/browser/navigation_entry.h"
41 #include "content/public/browser/navigation_handle.h"
42 #include "content/public/browser/render_frame_host.h"
43 #include "content/public/browser/web_contents.h"
44 #include "content/public/browser/web_contents_user_data.h"
45 #include "net/cert/cert_status_flags.h"
46 #include "net/http/http_response_headers.h"
47 #include "third_party/blink/public/common/loader/previews_state.h"
48
49 namespace offline_pages {
50
51 namespace {
52 const char kContentType[] = "text/plain";
53 const char kContentTransferEncodingBinary[] =
54 "Content-Transfer-Encoding: binary";
55 const char kXHeaderForSignals[] = "X-Chrome-Loading-Metrics-Data: 1";
56
AddHistogramSuffix(const ClientId & client_id,const char * histogram_name)57 std::string AddHistogramSuffix(const ClientId& client_id,
58 const char* histogram_name) {
59 if (client_id.name_space.empty()) {
60 NOTREACHED();
61 return histogram_name;
62 }
63 std::string adjusted_histogram_name(histogram_name);
64 adjusted_histogram_name += "." + client_id.name_space;
65 return adjusted_histogram_name;
66 }
67
RecordErrorCauseUMA(const ClientId & client_id,int error_code)68 void RecordErrorCauseUMA(const ClientId& client_id, int error_code) {
69 base::UmaHistogramSparse(
70 AddHistogramSuffix(client_id,
71 "OfflinePages.Background.LoadingErrorStatusCode"),
72 error_code);
73 }
74
RecordOffliningPreviewsUMA(const ClientId & client_id,blink::PreviewsState previews_state)75 void RecordOffliningPreviewsUMA(const ClientId& client_id,
76 blink::PreviewsState previews_state) {
77 bool is_previews_enabled =
78 (previews_state != blink::PreviewsTypes::PREVIEWS_OFF &&
79 previews_state != blink::PreviewsTypes::PREVIEWS_NO_TRANSFORM);
80
81 base::UmaHistogramBoolean(
82 AddHistogramSuffix(client_id,
83 "OfflinePages.Background.OffliningPreviewStatus"),
84 is_previews_enabled);
85 }
86
HandleLoadTerminationCancel(Offliner::CompletionCallback completion_callback,const SavePageRequest & canceled_request)87 void HandleLoadTerminationCancel(
88 Offliner::CompletionCallback completion_callback,
89 const SavePageRequest& canceled_request) {
90 std::move(completion_callback)
91 .Run(canceled_request, Offliner::RequestStatus::FOREGROUND_CANCELED);
92 }
93
94 } // namespace
95
BackgroundLoaderOffliner(content::BrowserContext * browser_context,const OfflinerPolicy * policy,OfflinePageModel * offline_page_model,std::unique_ptr<LoadTerminationListener> load_termination_listener)96 BackgroundLoaderOffliner::BackgroundLoaderOffliner(
97 content::BrowserContext* browser_context,
98 const OfflinerPolicy* policy,
99 OfflinePageModel* offline_page_model,
100 std::unique_ptr<LoadTerminationListener> load_termination_listener)
101 : browser_context_(browser_context),
102 offline_page_model_(offline_page_model),
103 policy_(policy),
104 load_termination_listener_(std::move(load_termination_listener)),
105 save_state_(NONE),
106 page_load_state_(SUCCESS),
107 network_bytes_(0LL),
108 is_low_bar_met_(false),
109 did_snapshot_on_last_retry_(false) {
110 DCHECK(offline_page_model_);
111 DCHECK(browser_context_);
112 // When the offliner is created for test harness runs, the
113 // |load_termination_listener_| will be set to nullptr, in order to prevent
114 // crashing, adding a check here.
115 if (load_termination_listener_)
116 load_termination_listener_->set_offliner(this);
117
118 for (int i = 0; i < ResourceDataType::RESOURCE_DATA_TYPE_COUNT; ++i) {
119 stats_[i].requested = 0;
120 stats_[i].completed = 0;
121 }
122 }
123
~BackgroundLoaderOffliner()124 BackgroundLoaderOffliner::~BackgroundLoaderOffliner() {}
125
126 // static
FromWebContents(content::WebContents * contents)127 BackgroundLoaderOffliner* BackgroundLoaderOffliner::FromWebContents(
128 content::WebContents* contents) {
129 Offliner* offliner = OfflinerUserData::OfflinerFromWebContents(contents);
130 // Today we only have one kind of offliner that uses OfflinerUserData. If we
131 // add other types, revisit this cast.
132 if (offliner)
133 return static_cast<BackgroundLoaderOffliner*>(offliner);
134 return nullptr;
135 }
136
LoadAndSave(const SavePageRequest & request,CompletionCallback completion_callback,const ProgressCallback & progress_callback)137 bool BackgroundLoaderOffliner::LoadAndSave(
138 const SavePageRequest& request,
139 CompletionCallback completion_callback,
140 const ProgressCallback& progress_callback) {
141 DCHECK(completion_callback);
142 DCHECK(progress_callback);
143 DCHECK(offline_page_model_);
144
145 if (pending_request_) {
146 DVLOG(1) << "Already have pending request";
147 return false;
148 }
149
150 if (GetPolicy(request.client_id().name_space)
151 .requires_specific_user_settings &&
152 (AreThirdPartyCookiesBlocked(browser_context_) ||
153 IsNetworkPredictionDisabled(browser_context_))) {
154 DVLOG(1) << "WARNING: Unable to load when 3rd party cookies blocked or "
155 << "prediction disabled";
156 return false;
157 }
158
159 if (!OfflinePageModel::CanSaveURL(request.url())) {
160 DVLOG(1) << "Not able to save page for requested url: " << request.url();
161 return false;
162 }
163
164 ResetLoader();
165 AttachObservers();
166
167 MarkLoadStartTime();
168
169 // Track copy of pending request.
170 pending_request_.reset(new SavePageRequest(request));
171 completion_callback_ = std::move(completion_callback);
172 progress_callback_ = progress_callback;
173
174 if (IsOfflinePagesRenovationsEnabled()) {
175 // Lazily create PageRenovationLoader
176 if (!page_renovation_loader_)
177 page_renovation_loader_ = std::make_unique<PageRenovationLoader>();
178
179 // Set up PageRenovator for this offlining instance.
180 auto script_injector = std::make_unique<RenderFrameScriptInjector>(
181 loader_->web_contents()->GetMainFrame(),
182 ISOLATED_WORLD_ID_CHROME_INTERNAL);
183 page_renovator_ = std::make_unique<PageRenovator>(
184 page_renovation_loader_.get(), std::move(script_injector),
185 request.url());
186 }
187
188 // Load page attempt.
189 loader_.get()->LoadPage(request.url());
190
191 snapshot_controller_ = std::make_unique<BackgroundSnapshotController>(
192 base::ThreadTaskRunnerHandle::Get(), this,
193 static_cast<bool>(page_renovator_));
194
195 return true;
196 }
197
Cancel(CancelCallback callback)198 bool BackgroundLoaderOffliner::Cancel(CancelCallback callback) {
199 DCHECK(pending_request_);
200 // We ignore the case where pending_request_ is not set, but given the checks
201 // in RequestCoordinator this should not happen.
202 if (!pending_request_)
203 return false;
204
205 // TODO(chili): We are not able to cancel a pending
206 // OfflinePageModel::SaveSnapshot() operation. We will notify caller that
207 // cancel completed once the SavePage operation returns.
208 if (save_state_ != NONE) {
209 save_state_ = DELETE_AFTER_SAVE;
210 cancel_callback_ = std::move(callback);
211 return true;
212 }
213
214 // Post the cancel callback right after this call concludes.
215 base::ThreadTaskRunnerHandle::Get()->PostTask(
216 FROM_HERE, base::BindOnce(std::move(callback), *pending_request_.get()));
217 ResetState();
218 return true;
219 }
220
TerminateLoadIfInProgress()221 void BackgroundLoaderOffliner::TerminateLoadIfInProgress() {
222 if (!pending_request_)
223 return;
224
225 Cancel(base::BindOnce(HandleLoadTerminationCancel,
226 std::move(completion_callback_)));
227 }
228
HandleTimeout(int64_t request_id)229 bool BackgroundLoaderOffliner::HandleTimeout(int64_t request_id) {
230 if (pending_request_) {
231 DCHECK(request_id == pending_request_->request_id());
232 if (is_low_bar_met_ && (pending_request_->started_attempt_count() + 1 >=
233 policy_->GetMaxStartedTries() ||
234 pending_request_->completed_attempt_count() + 1 >=
235 policy_->GetMaxCompletedTries())) {
236 // If we are already in the middle of a save operation, let it finish
237 // but do not return SAVED_ON_LAST_RETRY
238 if (save_state_ == NONE) {
239 did_snapshot_on_last_retry_ = true;
240 StartSnapshot();
241 }
242 return true;
243 }
244 }
245 return false;
246 }
247
CanDownload(base::OnceCallback<void (bool)> callback)248 void BackgroundLoaderOffliner::CanDownload(
249 base::OnceCallback<void(bool)> callback) {
250 if (!pending_request_.get()) {
251 std::move(callback).Run(false); // Shouldn't happen though...
252 return;
253 }
254
255 bool should_allow_downloads = false;
256 Offliner::RequestStatus final_status =
257 Offliner::RequestStatus::LOADING_FAILED_DOWNLOAD;
258 // Check whether we should allow file downloads for this save page request.
259 // If we want to proceed with the file download, fail with
260 // DOWNLOAD_THROTTLED. If we don't want to proceed with the file download,
261 // fail with LOADING_FAILED_DOWNLOAD.
262 if (GetPolicy(pending_request_.get()->client_id().name_space)
263 .allows_conversion_to_background_file_download) {
264 should_allow_downloads = true;
265 final_status = Offliner::RequestStatus::DOWNLOAD_THROTTLED;
266 }
267
268 std::move(callback).Run(should_allow_downloads);
269 SavePageRequest request(*pending_request_.get());
270 std::move(completion_callback_).Run(request, final_status);
271 base::ThreadTaskRunnerHandle::Get()->PostTask(
272 FROM_HERE, base::BindOnce(&BackgroundLoaderOffliner::ResetState,
273 weak_ptr_factory_.GetWeakPtr()));
274 }
275
MarkLoadStartTime()276 void BackgroundLoaderOffliner::MarkLoadStartTime() {
277 load_start_time_ = base::TimeTicks::Now();
278 }
279
DocumentAvailableInMainFrame()280 void BackgroundLoaderOffliner::DocumentAvailableInMainFrame() {
281 is_low_bar_met_ = true;
282
283 // Add this signal to signal_data_.
284 AddLoadingSignal("DocumentAvailableInMainFrame");
285 }
286
DocumentOnLoadCompletedInMainFrame()287 void BackgroundLoaderOffliner::DocumentOnLoadCompletedInMainFrame() {
288 if (!pending_request_.get()) {
289 DVLOG(1) << "DidStopLoading called even though no pending request.";
290 return;
291 }
292
293 // Add this signal to signal_data_.
294 AddLoadingSignal("DocumentOnLoadCompletedInMainFrame");
295
296 snapshot_controller_->DocumentOnLoadCompletedInMainFrame();
297 }
298
RenderProcessGone(base::TerminationStatus status)299 void BackgroundLoaderOffliner::RenderProcessGone(
300 base::TerminationStatus status) {
301 if (pending_request_) {
302 SavePageRequest request(*pending_request_.get());
303 switch (status) {
304 case base::TERMINATION_STATUS_OOM:
305 case base::TERMINATION_STATUS_PROCESS_CRASHED:
306 case base::TERMINATION_STATUS_STILL_RUNNING:
307 std::move(completion_callback_)
308 .Run(request, Offliner::RequestStatus::LOADING_FAILED_NO_NEXT);
309 break;
310 case base::TERMINATION_STATUS_PROCESS_WAS_KILLED:
311 default:
312 std::move(completion_callback_)
313 .Run(request, Offliner::RequestStatus::LOADING_FAILED);
314 }
315 ResetState();
316 }
317 }
318
WebContentsDestroyed()319 void BackgroundLoaderOffliner::WebContentsDestroyed() {
320 if (pending_request_) {
321 SavePageRequest request(*pending_request_.get());
322 std::move(completion_callback_)
323 .Run(request, Offliner::RequestStatus::LOADING_FAILED);
324 ResetState();
325 }
326 }
327
DidFinishNavigation(content::NavigationHandle * navigation_handle)328 void BackgroundLoaderOffliner::DidFinishNavigation(
329 content::NavigationHandle* navigation_handle) {
330 if (!navigation_handle->IsInMainFrame())
331 return;
332 // If there was an error of any kind (certificate, client, DNS, etc),
333 // Mark as error page. Resetting here causes RecordNavigationMetrics to crash.
334 if (navigation_handle->IsErrorPage()) {
335 RecordErrorCauseUMA(pending_request_->client_id(),
336 static_cast<int>(navigation_handle->GetNetErrorCode()));
337 page_load_state_ = RETRIABLE_NET_ERROR;
338 } else {
339 int status_code = 200; // Default to OK.
340 // No response header can imply intermediate navigation state.
341 if (navigation_handle->GetResponseHeaders())
342 status_code = navigation_handle->GetResponseHeaders()->response_code();
343 // 2XX and 3XX are ok because they indicate success or redirection.
344 // We track 301 because it's MOVED_PERMANENTLY and usually accompanies an
345 // error page with new address.
346 // 400+ codes are client and server errors.
347 // We skip 418 because it's a teapot.
348 if (status_code == 301 || (status_code >= 400 && status_code != 418)) {
349 RecordErrorCauseUMA(pending_request_->client_id(), status_code);
350 page_load_state_ = RETRIABLE_HTTP_ERROR;
351 }
352 }
353
354 PreviewsUITabHelper* previews_tab_helper =
355 PreviewsUITabHelper::FromWebContents(navigation_handle->GetWebContents());
356 blink::PreviewsState previews_state = blink::PreviewsTypes::PREVIEWS_OFF;
357 if (previews_tab_helper) {
358 previews::PreviewsUserData* previews_user_data =
359 previews_tab_helper->GetPreviewsUserData(navigation_handle);
360 if (previews_user_data)
361 previews_state = previews_user_data->CommittedPreviewsState();
362 }
363
364 RecordOffliningPreviewsUMA(pending_request_->client_id(), previews_state);
365 }
366
SetBackgroundSnapshotControllerForTest(std::unique_ptr<BackgroundSnapshotController> controller)367 void BackgroundLoaderOffliner::SetBackgroundSnapshotControllerForTest(
368 std::unique_ptr<BackgroundSnapshotController> controller) {
369 snapshot_controller_ = std::move(controller);
370 }
371
ObserveResourceLoading(ResourceLoadingObserver::ResourceDataType type,bool started)372 void BackgroundLoaderOffliner::ObserveResourceLoading(
373 ResourceLoadingObserver::ResourceDataType type,
374 bool started) {
375 // Add the signal to extra data, and use for tracking.
376
377 RequestStats& found_stats = stats_[type];
378 if (started)
379 ++found_stats.requested;
380 else
381 ++found_stats.completed;
382 }
383
OnNetworkBytesChanged(int64_t bytes)384 void BackgroundLoaderOffliner::OnNetworkBytesChanged(int64_t bytes) {
385 if (pending_request_ && save_state_ != SAVING) {
386 network_bytes_ += bytes;
387 progress_callback_.Run(*pending_request_, network_bytes_);
388 }
389 }
390
StartSnapshot()391 void BackgroundLoaderOffliner::StartSnapshot() {
392 if (!pending_request_.get()) {
393 DVLOG(1) << "Pending request was cleared during delay.";
394 return;
395 }
396 DCHECK(is_low_bar_met_)
397 << "Minimum quality must have been reached before saving a snapshot";
398
399 // Add this signal to signal_data_.
400 AddLoadingSignal("Snapshotting");
401
402 SavePageRequest request(*pending_request_.get());
403 // If there was an error navigating to page, return loading failed.
404 if (page_load_state_ != SUCCESS) {
405 Offliner::RequestStatus status;
406 switch (page_load_state_) {
407 case RETRIABLE_NET_ERROR:
408 status = Offliner::RequestStatus::LOADING_FAILED_NET_ERROR;
409 break;
410 case RETRIABLE_HTTP_ERROR:
411 status = Offliner::RequestStatus::LOADING_FAILED_HTTP_ERROR;
412 break;
413 case NONRETRIABLE:
414 status = Offliner::RequestStatus::LOADING_FAILED_NO_RETRY;
415 break;
416 default:
417 // We should've already checked for Success before entering here.
418 NOTREACHED();
419 status = Offliner::RequestStatus::LOADING_FAILED;
420 }
421
422 std::move(completion_callback_).Run(request, status);
423 ResetState();
424 return;
425 }
426
427 content::WebContents* web_contents(
428 content::WebContentsObserver::web_contents());
429
430 Offliner::RequestStatus loaded_page_error =
431 CanSavePageInBackground(web_contents);
432 if (loaded_page_error != Offliner::RequestStatus::UNKNOWN) {
433 std::move(completion_callback_).Run(request, loaded_page_error);
434 ResetState();
435 return;
436 }
437
438 save_state_ = SAVING;
439
440 // Capture loading signals to UMA.
441 RequestStats& image_stats = stats_[ResourceDataType::IMAGE];
442 RequestStats& css_stats = stats_[ResourceDataType::TEXT_CSS];
443 RequestStats& xhr_stats = stats_[ResourceDataType::XHR];
444
445 // Add loading signal into the MHTML that will be generated if the command
446 // line flag is set for it.
447 if (IsOfflinePagesLoadSignalCollectingEnabled()) {
448 // Write resource percentage signal data into extra data before emitting it
449 // to the MHTML.
450 signal_data_.SetDouble("StartedImages", image_stats.requested);
451 signal_data_.SetDouble("CompletedImages", image_stats.completed);
452 signal_data_.SetDouble("StartedCSS", css_stats.requested);
453 signal_data_.SetDouble("CompletedCSS", css_stats.completed);
454 signal_data_.SetDouble("StartedXHR", xhr_stats.requested);
455 signal_data_.SetDouble("CompletedXHR", xhr_stats.completed);
456
457 // Stash loading signals for writing when we write out the MHTML.
458 std::string headers = base::StringPrintf(
459 "%s\r\n%s\r\n\r\n", kContentTransferEncodingBinary, kXHeaderForSignals);
460 std::string body;
461 base::JSONWriter::Write(signal_data_, &body);
462 std::string content_type = kContentType;
463 std::string content_location = base::StringPrintf(
464 "cid:signal-data-%" PRId64 "@mhtml.blink", request.request_id());
465
466 content::MHTMLExtraParts* extra_parts =
467 content::MHTMLExtraParts::FromWebContents(web_contents);
468 DCHECK(extra_parts);
469 if (extra_parts != nullptr) {
470 extra_parts->AddExtraMHTMLPart(content_type, content_location, headers,
471 body);
472 }
473 }
474
475 std::unique_ptr<OfflinePageArchiver> archiver(new OfflinePageMHTMLArchiver());
476
477 OfflinePageModel::SavePageParams params;
478 params.url = web_contents->GetLastCommittedURL();
479 params.client_id = request.client_id();
480 params.proposed_offline_id = request.request_id();
481 params.is_background = true;
482 params.use_page_problem_detectors = true;
483 params.request_origin = request.request_origin();
484
485 // Pass in the original URL if it's different from last committed
486 // when redirects occur.
487 if (!request.original_url().is_empty())
488 params.original_url = request.original_url();
489 else if (params.url != request.url())
490 params.original_url = request.url();
491
492 offline_page_model_->SavePage(
493 params, std::move(archiver), web_contents,
494 base::BindOnce(&BackgroundLoaderOffliner::OnPageSaved,
495 weak_ptr_factory_.GetWeakPtr()));
496 }
497
RunRenovations()498 void BackgroundLoaderOffliner::RunRenovations() {
499 if (page_renovator_) {
500 page_renovator_->RunRenovations(
501 base::BindOnce(&BackgroundLoaderOffliner::RenovationsCompleted,
502 weak_ptr_factory_.GetWeakPtr()));
503 }
504 }
505
OnPageSaved(SavePageResult save_result,int64_t offline_id)506 void BackgroundLoaderOffliner::OnPageSaved(SavePageResult save_result,
507 int64_t offline_id) {
508 if (!pending_request_)
509 return;
510
511 SavePageRequest request(*pending_request_.get());
512 bool did_snapshot_on_last_retry = did_snapshot_on_last_retry_;
513 ResetState();
514
515 if (save_state_ == DELETE_AFTER_SAVE) {
516 // Delete the saved page off disk and from the OPM.
517 PageCriteria criteria;
518 criteria.offline_ids = std::vector<int64_t>{offline_id};
519 offline_page_model_->DeletePagesWithCriteria(
520 criteria,
521 base::BindOnce(&BackgroundLoaderOffliner::DeleteOfflinePageCallback,
522 weak_ptr_factory_.GetWeakPtr(), request));
523 save_state_ = NONE;
524 return;
525 }
526
527 save_state_ = NONE;
528
529 Offliner::RequestStatus save_status;
530 if (save_result == SavePageResult::ALREADY_EXISTS) {
531 save_status = RequestStatus::SAVED;
532 } else if (save_result == SavePageResult::SUCCESS) {
533 if (did_snapshot_on_last_retry)
534 save_status = RequestStatus::SAVED_ON_LAST_RETRY;
535 else
536 save_status = RequestStatus::SAVED;
537 } else {
538 save_status = RequestStatus::SAVE_FAILED;
539 }
540
541 std::move(completion_callback_).Run(request, save_status);
542 }
543
DeleteOfflinePageCallback(const SavePageRequest & request,DeletePageResult result)544 void BackgroundLoaderOffliner::DeleteOfflinePageCallback(
545 const SavePageRequest& request,
546 DeletePageResult result) {
547 std::move(cancel_callback_).Run(request);
548 }
549
ResetState()550 void BackgroundLoaderOffliner::ResetState() {
551 pending_request_.reset();
552 // Stop snapshot controller from triggering any more events.
553 snapshot_controller_->Stop();
554 // Delete the snapshot controller after stack unwinds, so we don't
555 // corrupt stack in some edge cases. Deleting it soon should be safe because
556 // we check against pending_request_ with every action, and snapshot
557 // controller is configured to only call StartSnapshot once for BGL.
558 base::ThreadTaskRunnerHandle::Get()->DeleteSoon(
559 FROM_HERE, snapshot_controller_.release());
560 page_load_state_ = SUCCESS;
561 network_bytes_ = 0LL;
562 is_low_bar_met_ = false;
563 did_snapshot_on_last_retry_ = false;
564 content::WebContentsObserver::Observe(nullptr);
565 loader_.reset();
566
567 for (int i = 0; i < ResourceDataType::RESOURCE_DATA_TYPE_COUNT; ++i) {
568 stats_[i].requested = 0;
569 stats_[i].completed = 0;
570 }
571 }
572
ResetLoader()573 void BackgroundLoaderOffliner::ResetLoader() {
574 loader_.reset(
575 new background_loader::BackgroundLoaderContents(browser_context_));
576 loader_->SetDelegate(this);
577 }
578
AttachObservers()579 void BackgroundLoaderOffliner::AttachObservers() {
580 content::WebContents* contents = loader_->web_contents();
581 content::WebContentsObserver::Observe(contents);
582 OfflinerUserData::AddToWebContents(contents, this);
583 }
584
AddLoadingSignal(const char * signal_name)585 void BackgroundLoaderOffliner::AddLoadingSignal(const char* signal_name) {
586 base::TimeTicks current_time = base::TimeTicks::Now();
587 base::TimeDelta delay_so_far = current_time - load_start_time_;
588 // We would prefer to use int64_t here, but JSON does not support that type.
589 // Given the choice between int and double, we choose to implicitly convert to
590 // a double since it maintains more precision (we can get a longer time in
591 // milliseconds than we can with a 2 bit int, 53 bits vs 32).
592 signal_data_.SetDouble(signal_name, delay_so_far.InMillisecondsF());
593 }
594
RenovationsCompleted()595 void BackgroundLoaderOffliner::RenovationsCompleted() {
596 snapshot_controller_->RenovationsCompleted();
597 }
598
CanSavePageInBackground(content::WebContents * web_contents)599 Offliner::RequestStatus BackgroundLoaderOffliner::CanSavePageInBackground(
600 content::WebContents* web_contents) {
601 DCHECK(is_low_bar_met_)
602 << "Minimum quality must have been reached before checking loaded page";
603 std::unique_ptr<security_state::VisibleSecurityState> visible_security_state =
604 GetVisibleSecurityState(web_contents);
605 // Checks for HTTPS certificate errors (HTTP connections are not affected).
606 if (security_state::HasMajorCertificateError(*visible_security_state))
607 return Offliner::RequestStatus::LOADED_PAGE_HAS_CERTIFICATE_ERROR;
608
609 // Checks if the page is blocked by SafeBrowsing.
610 if (visible_security_state->malicious_content_status !=
611 security_state::MaliciousContentStatus::MALICIOUS_CONTENT_STATUS_NONE) {
612 return Offliner::RequestStatus::LOADED_PAGE_IS_BLOCKED;
613 }
614
615 // Don't save Chrome error or interstitial pages.
616 if (GetPageType(web_contents) != content::PageType::PAGE_TYPE_NORMAL)
617 return Offliner::RequestStatus::LOADED_PAGE_IS_CHROME_INTERNAL;
618
619 return Offliner::RequestStatus::UNKNOWN;
620 }
621
622 std::unique_ptr<security_state::VisibleSecurityState>
GetVisibleSecurityState(content::WebContents * web_contents)623 BackgroundLoaderOffliner::GetVisibleSecurityState(
624 content::WebContents* web_contents) {
625 // Note: this tab helper needs to be created here as in the background it is
626 // not created by default.
627 SecurityStateTabHelper::CreateForWebContents(web_contents);
628 SecurityStateTabHelper* helper =
629 SecurityStateTabHelper::FromWebContents(web_contents);
630 DCHECK(helper);
631 return helper->GetVisibleSecurityState();
632 }
633
GetPageType(content::WebContents * web_contents)634 content::PageType BackgroundLoaderOffliner::GetPageType(
635 content::WebContents* web_contents) {
636 DCHECK(web_contents->GetController().GetVisibleEntry())
637 << "An entry must have committed at this WebContents";
638 return web_contents->GetController().GetVisibleEntry()->GetPageType();
639 }
640
641 } // namespace offline_pages
642