1 // Copyright 2017 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 "components/offline_pages/core/prefetch/prefetch_dispatcher_impl.h"
6 
7 #include <algorithm>
8 #include <utility>
9 
10 #include "base/bind.h"
11 #include "base/callback_helpers.h"
12 #include "base/guid.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "base/task/post_task.h"
15 #include "base/task_runner.h"
16 #include "base/threading/thread_task_runner_handle.h"
17 #include "components/offline_pages/core/client_namespace_constants.h"
18 #include "components/offline_pages/core/offline_event_logger.h"
19 #include "components/offline_pages/core/offline_page_feature.h"
20 #include "components/offline_pages/core/offline_page_model.h"
21 #include "components/offline_pages/core/prefetch/offline_metrics_collector.h"
22 #include "components/offline_pages/core/prefetch/prefetch_background_task.h"
23 #include "components/offline_pages/core/prefetch/prefetch_background_task_handler.h"
24 #include "components/offline_pages/core/prefetch/prefetch_downloader.h"
25 #include "components/offline_pages/core/prefetch/prefetch_gcm_handler.h"
26 #include "components/offline_pages/core/prefetch/prefetch_importer.h"
27 #include "components/offline_pages/core/prefetch/prefetch_network_request_factory.h"
28 #include "components/offline_pages/core/prefetch/prefetch_prefs.h"
29 #include "components/offline_pages/core/prefetch/prefetch_service.h"
30 #include "components/offline_pages/core/prefetch/prefetch_types.h"
31 #include "components/offline_pages/core/prefetch/server_forbidden_check_request.h"
32 #include "components/offline_pages/core/prefetch/suggestions_provider.h"
33 #include "components/offline_pages/core/prefetch/tasks/add_unique_urls_task.h"
34 #include "components/offline_pages/core/prefetch/tasks/download_archives_task.h"
35 #include "components/offline_pages/core/prefetch/tasks/download_cleanup_task.h"
36 #include "components/offline_pages/core/prefetch/tasks/download_completed_task.h"
37 #include "components/offline_pages/core/prefetch/tasks/finalize_dismissed_url_suggestion_task.h"
38 #include "components/offline_pages/core/prefetch/tasks/generate_page_bundle_reconcile_task.h"
39 #include "components/offline_pages/core/prefetch/tasks/generate_page_bundle_task.h"
40 #include "components/offline_pages/core/prefetch/tasks/get_operation_task.h"
41 #include "components/offline_pages/core/prefetch/tasks/import_archives_task.h"
42 #include "components/offline_pages/core/prefetch/tasks/import_cleanup_task.h"
43 #include "components/offline_pages/core/prefetch/tasks/import_completed_task.h"
44 #include "components/offline_pages/core/prefetch/tasks/mark_operation_done_task.h"
45 #include "components/offline_pages/core/prefetch/tasks/metrics_finalization_task.h"
46 #include "components/offline_pages/core/prefetch/tasks/page_bundle_update_task.h"
47 #include "components/offline_pages/core/prefetch/tasks/remove_url_task.h"
48 #include "components/offline_pages/core/prefetch/tasks/sent_get_operation_cleanup_task.h"
49 #include "components/offline_pages/core/prefetch/tasks/stale_entry_finalizer_task.h"
50 #include "components/offline_pages/core/prefetch/visuals_fetch_by_url.h"
51 #include "components/prefs/pref_service.h"
52 #include "url/gurl.h"
53 
54 namespace offline_pages {
55 
56 namespace {
57 
DeleteBackgroundTaskHelper(std::unique_ptr<PrefetchBackgroundTask> task)58 void DeleteBackgroundTaskHelper(std::unique_ptr<PrefetchBackgroundTask> task) {
59   task.reset();
60 }
61 
SuggestionToPrefetchURL(PrefetchSuggestion suggestion)62 PrefetchURL SuggestionToPrefetchURL(PrefetchSuggestion suggestion) {
63   return PrefetchURL(suggestion.article_url.spec(), suggestion.article_url,
64                      base::UTF8ToUTF16(suggestion.article_title),
65                      suggestion.thumbnail_url, suggestion.favicon_url,
66                      suggestion.article_snippet,
67                      suggestion.article_attribution);
68 }
69 
70 }  // namespace
71 
PrefetchDispatcherImpl(PrefService * pref_service)72 PrefetchDispatcherImpl::PrefetchDispatcherImpl(PrefService* pref_service)
73     : pref_service_(pref_service), task_queue_(this) {}
74 
75 PrefetchDispatcherImpl::~PrefetchDispatcherImpl() = default;
76 
SetService(PrefetchService * service)77 void PrefetchDispatcherImpl::SetService(PrefetchService* service) {
78   CHECK(service);
79   service_ = service;
80 }
81 
SchedulePipelineProcessing()82 void PrefetchDispatcherImpl::SchedulePipelineProcessing() {
83   needs_pipeline_processing_ = true;
84   service_->GetLogger()->RecordActivity(
85       "Dispatcher: Scheduled more pipeline processing.");
86 }
87 
EnsureTaskScheduled()88 void PrefetchDispatcherImpl::EnsureTaskScheduled() {
89   if (background_task_) {
90     background_task_->SetReschedule(
91         PrefetchBackgroundTaskRescheduleType::RESCHEDULE_WITHOUT_BACKOFF);
92   } else {
93     service_->GetPrefetchBackgroundTaskHandler()->EnsureTaskScheduled();
94   }
95 }
96 
AddCandidatePrefetchURLs(const std::string & name_space,const std::vector<PrefetchURL> & prefetch_urls)97 void PrefetchDispatcherImpl::AddCandidatePrefetchURLs(
98     const std::string& name_space,
99     const std::vector<PrefetchURL>& prefetch_urls) {
100   if (!prefetch_prefs::IsEnabled(pref_service_)) {
101     if (prefetch_prefs::IsForbiddenCheckDue(pref_service_))
102       CheckIfEnabledByServer(pref_service_, service_);
103     return;
104   }
105 
106   service_->GetLogger()->RecordActivity("Dispatcher: Received " +
107                                         std::to_string(prefetch_urls.size()) +
108                                         " suggested URLs.");
109 
110   PrefetchStore* prefetch_store = service_->GetPrefetchStore();
111 
112   // Run 2 pipeline expiration tasks first to ensure there is no buildup of URLs
113   // in the pipeline if the new ones are coming but NOW can't be entered (for
114   // example, if the user is never on WiFi with enough battery charge).
115   // First, detect stale entries and move them to FINISHED.
116   task_queue_.AddTask(
117       std::make_unique<StaleEntryFinalizerTask>(this, prefetch_store));
118 
119   // Second, move FINISHED to ZOMBIE.
120   task_queue_.AddTask(
121       std::make_unique<MetricsFinalizationTask>(prefetch_store));
122 
123   // Third, add new unique URLs and remove unneeded ZOMBIEs.
124   std::unique_ptr<Task> add_task = std::make_unique<AddUniqueUrlsTask>(
125       this, prefetch_store, name_space, prefetch_urls);
126   task_queue_.AddTask(std::move(add_task));
127 
128   // Report the 'enabled' day if we receive URLs and Prefetch is enabled.
129   service_->GetOfflineMetricsCollector()->OnPrefetchEnabled();
130 }
131 
NewSuggestionsAvailable(SuggestionsProvider * suggestions_provider)132 void PrefetchDispatcherImpl::NewSuggestionsAvailable(
133     SuggestionsProvider* suggestions_provider) {
134   // No need to GetKnownContent if prefetching is disabled (however, we don't
135   // want to prevent the server-forbidden check).
136   if (!prefetch_prefs::IsEnabled(pref_service_)) {
137     if (prefetch_prefs::IsForbiddenCheckDue(pref_service_))
138       CheckIfEnabledByServer(pref_service_, service_);
139     return;
140   }
141 
142   suggestions_provider->GetCurrentArticleSuggestions(
143       base::BindOnce(&PrefetchDispatcherImpl::AddSuggestions, GetWeakPtr()));
144 }
145 
RemoveSuggestion(const GURL & url)146 void PrefetchDispatcherImpl::RemoveSuggestion(const GURL& url) {
147   if (!prefetch_prefs::IsEnabled(pref_service_))
148     return;
149 
150   // Remove the URL from the prefetch database.
151   task_queue_.AddTask(MakeRemoveUrlTask(service_->GetPrefetchStore(), url));
152 
153   // Remove the URL from the offline model.
154   PageCriteria criteria;
155   criteria.url = url;
156   criteria.client_namespaces =
157       std::vector<std::string>{kSuggestedArticlesNamespace};
158   service_->GetOfflinePageModel()->DeletePagesWithCriteria(criteria,
159                                                            base::DoNothing());
160 }
161 
RemoveAllUnprocessedPrefetchURLs(const std::string & name_space)162 void PrefetchDispatcherImpl::RemoveAllUnprocessedPrefetchURLs(
163     const std::string& name_space) {
164   if (!prefetch_prefs::IsEnabled(pref_service_))
165     return;
166 
167   NOTIMPLEMENTED();
168 }
169 
RemovePrefetchURLsByClientId(const ClientId & client_id)170 void PrefetchDispatcherImpl::RemovePrefetchURLsByClientId(
171     const ClientId& client_id) {
172   if (!prefetch_prefs::IsEnabled(pref_service_))
173     return;
174   PrefetchStore* prefetch_store = service_->GetPrefetchStore();
175   task_queue_.AddTask(std::make_unique<FinalizeDismissedUrlSuggestionTask>(
176       prefetch_store, client_id));
177 }
178 
BeginBackgroundTask(std::unique_ptr<PrefetchBackgroundTask> background_task)179 void PrefetchDispatcherImpl::BeginBackgroundTask(
180     std::unique_ptr<PrefetchBackgroundTask> background_task) {
181   if (!prefetch_prefs::IsEnabled(pref_service_))
182     return;
183   service_->GetLogger()->RecordActivity(
184       "Dispatcher: Beginning background task.");
185 
186   background_task_ = std::move(background_task);
187   service_->GetPrefetchBackgroundTaskHandler()->RemoveSuspension();
188 
189   // Reset suspended state in case that it was set last time and Chrome is still
190   // running till new background task starts after the suspension period.
191   suspended_ = false;
192 
193   QueueReconcileTasks();
194   QueueActionTasks();
195 }
196 
QueueReconcileTasks()197 void PrefetchDispatcherImpl::QueueReconcileTasks() {
198   if (suspended_)
199     return;
200 
201   service_->GetLogger()->RecordActivity("Dispatcher: Adding reconcile tasks.");
202   // Note: For optimal results StaleEntryFinalizerTask should be executed before
203   // other reconciler tasks that deal with external systems so that entries
204   // finalized by it will promptly effect any external processing they relate
205   // to.
206   task_queue_.AddTask(std::make_unique<StaleEntryFinalizerTask>(
207       this, service_->GetPrefetchStore()));
208 
209   task_queue_.AddTask(std::make_unique<GeneratePageBundleReconcileTask>(
210       service_->GetPrefetchStore(),
211       service_->GetPrefetchNetworkRequestFactory()));
212 
213   task_queue_.AddTask(std::make_unique<SentGetOperationCleanupTask>(
214       service_->GetPrefetchStore(),
215       service_->GetPrefetchNetworkRequestFactory()));
216 
217   // Notifies the downloader that the download cleanup can proceed when the
218   // download service is up. The prefetch service and download service are two
219   // separate services which can start up on their own. The download cleanup
220   // should only kick in when both services are ready.
221   service_->GetPrefetchDownloader()->CleanupDownloadsWhenReady();
222 
223   task_queue_.AddTask(std::make_unique<ImportCleanupTask>(
224       service_->GetPrefetchStore(), service_->GetPrefetchImporter()));
225 
226   // This task should be last, because it is least important for correct
227   // operation of the system, and because any reconciliation tasks might
228   // generate more entries in the FINISHED state that the finalization task
229   // could pick up.
230   task_queue_.AddTask(
231       std::make_unique<MetricsFinalizationTask>(service_->GetPrefetchStore()));
232 }
233 
QueueActionTasks()234 void PrefetchDispatcherImpl::QueueActionTasks() {
235   service_->GetLogger()->RecordActivity("Dispatcher: Adding action tasks.");
236 
237   // Import should be run first to minimize time to import after download
238   // finishes, during the download background task.
239   std::unique_ptr<Task> import_archives_task =
240       std::make_unique<ImportArchivesTask>(service_->GetPrefetchStore(),
241                                            service_->GetPrefetchImporter());
242   task_queue_.AddTask(std::move(import_archives_task));
243 
244   std::unique_ptr<Task> download_archives_task =
245       std::make_unique<DownloadArchivesTask>(service_->GetPrefetchStore(),
246                                              service_->GetPrefetchDownloader(),
247                                              pref_service_);
248   task_queue_.AddTask(std::move(download_archives_task));
249 
250   // The following tasks should not be run unless we are in the background task,
251   // as we need to ensure WiFi access at that time.
252   if (!background_task_)
253     return;
254   DCHECK(!service_->GetCachedGCMToken().empty());
255 
256   std::unique_ptr<Task> get_operation_task = std::make_unique<GetOperationTask>(
257       service_->GetPrefetchStore(),
258       service_->GetPrefetchNetworkRequestFactory(),
259       base::BindRepeating(
260           &PrefetchDispatcherImpl::DidGenerateBundleOrGetOperationRequest,
261           GetWeakPtr(), "GetOperationRequest"));
262   task_queue_.AddTask(std::move(get_operation_task));
263 
264   std::unique_ptr<Task> generate_page_bundle_task =
265       std::make_unique<GeneratePageBundleTask>(
266           this, service_->GetPrefetchStore(), service_->GetCachedGCMToken(),
267           service_->GetPrefetchNetworkRequestFactory(),
268           base::BindRepeating(
269               &PrefetchDispatcherImpl::DidGenerateBundleOrGetOperationRequest,
270               GetWeakPtr(), "GeneratePageBundleRequest"));
271   task_queue_.AddTask(std::move(generate_page_bundle_task));
272 }
273 
AddSuggestions(std::vector<PrefetchSuggestion> suggestions)274 void PrefetchDispatcherImpl::AddSuggestions(
275     std::vector<PrefetchSuggestion> suggestions) {
276   std::vector<PrefetchURL> urls;
277   urls.reserve(suggestions.size());
278 
279   for (auto& suggestion : suggestions) {
280     urls.push_back(SuggestionToPrefetchURL(std::move(suggestion)));
281   }
282   AddCandidatePrefetchURLs(kSuggestedArticlesNamespace, urls);
283 }
284 
StopBackgroundTask()285 void PrefetchDispatcherImpl::StopBackgroundTask() {
286   if (!prefetch_prefs::IsEnabled(pref_service_))
287     return;
288 
289   service_->GetLogger()->RecordActivity(
290       "Dispatcher: Stopping background task.");
291 
292   DisposeTask();
293 }
294 
OnTaskQueueIsIdle()295 void PrefetchDispatcherImpl::OnTaskQueueIsIdle() {
296   if (!suspended_ && needs_pipeline_processing_) {
297     needs_pipeline_processing_ = false;
298     QueueActionTasks();
299   } else {
300     PrefetchNetworkRequestFactory* request_factory =
301         service_->GetPrefetchNetworkRequestFactory();
302     if (!request_factory->HasOutstandingRequests())
303       DisposeTask();
304   }
305 }
306 
DisposeTask()307 void PrefetchDispatcherImpl::DisposeTask() {
308   if (!background_task_)
309     return;
310 
311   // Delay the deletion till the caller finishes.
312   base::ThreadTaskRunnerHandle::Get()->PostTask(
313       FROM_HERE,
314       base::BindOnce(&DeleteBackgroundTaskHelper, std::move(background_task_)));
315 }
316 
GCMOperationCompletedMessageReceived(const std::string & operation_name)317 void PrefetchDispatcherImpl::GCMOperationCompletedMessageReceived(
318     const std::string& operation_name) {
319   if (!prefetch_prefs::IsEnabled(pref_service_))
320     return;
321 
322   service_->GetLogger()->RecordActivity("Dispatcher: Received GCM message.");
323 
324   PrefetchStore* prefetch_store = service_->GetPrefetchStore();
325   task_queue_.AddTask(std::make_unique<MarkOperationDoneTask>(
326       this, prefetch_store, operation_name));
327 }
328 
DidGenerateBundleOrGetOperationRequest(const std::string & request_name_for_logging,PrefetchRequestStatus status,const std::string & operation_name,const std::vector<RenderPageInfo> & pages)329 void PrefetchDispatcherImpl::DidGenerateBundleOrGetOperationRequest(
330     const std::string& request_name_for_logging,
331     PrefetchRequestStatus status,
332     const std::string& operation_name,
333     const std::vector<RenderPageInfo>& pages) {
334   LogRequestResult(request_name_for_logging, status, operation_name, pages);
335 
336   // Note that we still want to trigger PageBundleUpdateTask even if the request
337   // fails and no page is returned. This is because currently we only check for
338   // the empty task queue and no outstanding request in order to decide whether
339   // to dispose th background task upon the completion of a task.
340   PrefetchStore* prefetch_store = service_->GetPrefetchStore();
341   task_queue_.AddTask(std::make_unique<PageBundleUpdateTask>(
342       prefetch_store, this, operation_name, pages));
343 
344   if (background_task_ && status != PrefetchRequestStatus::kSuccess &&
345       status != PrefetchRequestStatus::kEmptyRequestSuccess) {
346     PrefetchBackgroundTaskRescheduleType reschedule_type =
347         PrefetchBackgroundTaskRescheduleType::NO_RESCHEDULE;
348     switch (status) {
349       case PrefetchRequestStatus::kShouldRetryWithBackoff:
350         reschedule_type =
351             PrefetchBackgroundTaskRescheduleType::RESCHEDULE_WITH_BACKOFF;
352         break;
353       case PrefetchRequestStatus::kShouldRetryWithoutBackoff:
354         reschedule_type =
355             PrefetchBackgroundTaskRescheduleType::RESCHEDULE_WITHOUT_BACKOFF;
356         break;
357       case PrefetchRequestStatus::kShouldSuspendForbiddenByOPS:
358       case PrefetchRequestStatus::kShouldSuspendNewlyForbiddenByOPS:
359       case PrefetchRequestStatus::kShouldSuspendForbidden:
360       case PrefetchRequestStatus::kShouldSuspendNotImplemented:
361       case PrefetchRequestStatus::kShouldSuspendBlockedByAdministrator:
362         reschedule_type = PrefetchBackgroundTaskRescheduleType::SUSPEND;
363         break;
364       case PrefetchRequestStatus::kSuccess:
365       case PrefetchRequestStatus::kEmptyRequestSuccess:
366         NOTREACHED();
367         break;
368     }
369     background_task_->SetReschedule(reschedule_type);
370 
371     if (reschedule_type == PrefetchBackgroundTaskRescheduleType::SUSPEND)
372       suspended_ = true;
373   }
374 }
375 
CleanupDownloads(const std::set<std::string> & outstanding_download_ids,const std::map<std::string,std::pair<base::FilePath,int64_t>> & success_downloads)376 void PrefetchDispatcherImpl::CleanupDownloads(
377     const std::set<std::string>& outstanding_download_ids,
378     const std::map<std::string, std::pair<base::FilePath, int64_t>>&
379         success_downloads) {
380   task_queue_.AddTask(std::make_unique<DownloadCleanupTask>(
381       this, service_->GetPrefetchStore(), outstanding_download_ids,
382       success_downloads));
383 }
384 
GeneratePageBundleRequested(std::unique_ptr<PrefetchDispatcher::IdsVector> ids)385 void PrefetchDispatcherImpl::GeneratePageBundleRequested(
386     std::unique_ptr<PrefetchDispatcher::IdsVector> ids) {
387   // Reverse the order so that the fresher items are last. This is done because
388   // the ids are popped from the end of the vector.
389   std::reverse(ids->begin(), ids->end());
390   FetchVisuals(std::move(ids), /* is_first_attempt= */ true);
391 }
392 
DownloadCompleted(const PrefetchDownloadResult & download_result)393 void PrefetchDispatcherImpl::DownloadCompleted(
394     const PrefetchDownloadResult& download_result) {
395   if (!prefetch_prefs::IsEnabled(pref_service_))
396     return;
397 
398   service_->GetLogger()->RecordActivity(
399       "Download " + download_result.download_id +
400       (download_result.success ? "succeeded" : "failed"));
401   if (download_result.success) {
402     service_->GetLogger()->RecordActivity(
403         "Download size: " + std::to_string(download_result.file_size));
404   }
405 
406   task_queue_.AddTask(std::make_unique<DownloadCompletedTask>(
407       this, service_->GetPrefetchStore(), download_result));
408   task_queue_.AddTask(std::make_unique<ImportArchivesTask>(
409       service_->GetPrefetchStore(), service_->GetPrefetchImporter()));
410 }
411 
ItemDownloaded(int64_t offline_id,const ClientId & client_id)412 void PrefetchDispatcherImpl::ItemDownloaded(int64_t offline_id,
413                                             const ClientId& client_id) {
414   auto ids = std::make_unique<IdsVector>();
415   ids->emplace_back(offline_id, client_id);
416   FetchVisuals(std::move(ids), /* is_first_attempt= */ false);
417 }
418 
ArchiveImported(int64_t offline_id,bool success)419 void PrefetchDispatcherImpl::ArchiveImported(int64_t offline_id, bool success) {
420   DCHECK_NE(OfflinePageModel::kInvalidOfflineId, offline_id);
421 
422   if (!prefetch_prefs::IsEnabled(pref_service_))
423     return;
424 
425   service_->GetLogger()->RecordActivity("Importing archive " +
426                                         std::to_string(offline_id) +
427                                         (success ? "succeeded" : "failed"));
428 
429   if (success)
430     service_->GetOfflineMetricsCollector()->OnSuccessfulPagePrefetch();
431 
432   task_queue_.AddTask(std::make_unique<ImportCompletedTask>(
433       this, service_->GetPrefetchStore(), service_->GetPrefetchImporter(),
434       offline_id, success));
435 }
436 
LogRequestResult(const std::string & request_name_for_logging,PrefetchRequestStatus status,const std::string & operation_name,const std::vector<RenderPageInfo> & pages)437 void PrefetchDispatcherImpl::LogRequestResult(
438     const std::string& request_name_for_logging,
439     PrefetchRequestStatus status,
440     const std::string& operation_name,
441     const std::vector<RenderPageInfo>& pages) {
442   service_->GetLogger()->RecordActivity(
443       "Finished " + request_name_for_logging +
444       " for operation: " + operation_name +
445       " with status: " + std::to_string(static_cast<int>(status)) +
446       "; included " + std::to_string(pages.size()) + " pages in result.");
447   for (const RenderPageInfo& page : pages) {
448     service_->GetLogger()->RecordActivity(
449         "Response for page: " + page.url +
450         "; status=" + std::to_string(static_cast<int>(page.status)));
451   }
452 }
453 
FetchVisuals(std::unique_ptr<PrefetchDispatcher::IdsVector> remaining_ids,bool is_first_attempt)454 void PrefetchDispatcherImpl::FetchVisuals(
455     std::unique_ptr<PrefetchDispatcher::IdsVector> remaining_ids,
456     bool is_first_attempt) {
457   if (remaining_ids->empty())
458     return;
459 
460   int64_t offline_id = remaining_ids->back().first;
461   ClientId client_id = std::move(remaining_ids->back().second);
462   DCHECK(client_id.name_space == kSuggestedArticlesNamespace);
463   remaining_ids->pop_back();
464 
465   service_->GetOfflinePageModel()->GetVisualsAvailability(
466       offline_id,
467       base::BindOnce(&PrefetchDispatcherImpl::VisualsAvailabilityChecked,
468                      GetWeakPtr(), offline_id, std::move(client_id),
469                      std::move(remaining_ids), is_first_attempt));
470 }
471 
VisualsAvailabilityChecked(int64_t offline_id,ClientId client_id,std::unique_ptr<PrefetchDispatcher::IdsVector> remaining_ids,bool is_first_attempt,VisualsAvailability availability)472 void PrefetchDispatcherImpl::VisualsAvailabilityChecked(
473     int64_t offline_id,
474     ClientId client_id,
475     std::unique_ptr<PrefetchDispatcher::IdsVector> remaining_ids,
476     bool is_first_attempt,
477     VisualsAvailability availability) {
478   if (availability.has_thumbnail && availability.has_favicon) {
479     FetchVisuals(std::move(remaining_ids), is_first_attempt);
480   } else {
481     task_queue_.AddTask(std::make_unique<GetVisualsInfoTask>(
482         service_->GetPrefetchStore(), offline_id,
483         base::BindOnce(&PrefetchDispatcherImpl::VisualsInfoReceived,
484                        GetWeakPtr(), offline_id, std::move(remaining_ids),
485                        is_first_attempt, availability)));
486   }
487 }
488 
VisualsInfoReceived(int64_t offline_id,std::unique_ptr<IdsVector> remaining_ids,bool is_first_attempt,VisualsAvailability availability,GetVisualsInfoTask::Result result)489 void PrefetchDispatcherImpl::VisualsInfoReceived(
490     int64_t offline_id,
491     std::unique_ptr<IdsVector> remaining_ids,
492     bool is_first_attempt,
493     VisualsAvailability availability,
494     GetVisualsInfoTask::Result result) {
495   GURL favicon_url = availability.has_favicon || result.favicon_url.is_empty()
496                          ? GURL()
497                          : result.favicon_url;
498 
499   if (!availability.has_thumbnail && !result.thumbnail_url.is_empty()) {
500     FetchThumbnailByURL(
501         base::BindOnce(&PrefetchDispatcherImpl::ThumbnailFetchComplete,
502                        GetWeakPtr(), offline_id, std::move(remaining_ids),
503                        is_first_attempt, favicon_url),
504         service_->GetImageFetcher(), result.thumbnail_url);
505   } else if (!favicon_url.is_empty()) {
506     FetchFavicon(offline_id, std::move(remaining_ids), is_first_attempt,
507                  favicon_url);
508   } else {
509     FetchVisuals(std::move(remaining_ids), is_first_attempt);
510   }
511 }
512 
ThumbnailFetchComplete(int64_t offline_id,std::unique_ptr<IdsVector> remaining_ids,bool is_first_attempt,const GURL & favicon_url,const std::string & thumbnail)513 void PrefetchDispatcherImpl::ThumbnailFetchComplete(
514     int64_t offline_id,
515     std::unique_ptr<IdsVector> remaining_ids,
516     bool is_first_attempt,
517     const GURL& favicon_url,
518     const std::string& thumbnail) {
519   if (!thumbnail.empty())
520     service_->GetOfflinePageModel()->StoreThumbnail(offline_id, thumbnail);
521 
522   if (favicon_url.is_empty()) {
523     FetchVisuals(std::move(remaining_ids), is_first_attempt);
524   } else {
525     FetchFavicon(offline_id, std::move(remaining_ids), is_first_attempt,
526                  favicon_url);
527   }
528 }
529 
FetchFavicon(int64_t offline_id,std::unique_ptr<IdsVector> remaining_ids,bool is_first_attempt,const GURL & favicon_url)530 void PrefetchDispatcherImpl::FetchFavicon(
531     int64_t offline_id,
532     std::unique_ptr<IdsVector> remaining_ids,
533     bool is_first_attempt,
534     const GURL& favicon_url) {
535   FetchFaviconByURL(
536       base::BindOnce(&PrefetchDispatcherImpl::FaviconFetchComplete,
537                      GetWeakPtr(), offline_id, std::move(remaining_ids),
538                      is_first_attempt),
539       service_->GetImageFetcher(), favicon_url);
540 }
541 
FaviconFetchComplete(int64_t offline_id,std::unique_ptr<IdsVector> remaining_ids,bool is_first_attempt,const std::string & favicon_data)542 void PrefetchDispatcherImpl::FaviconFetchComplete(
543     int64_t offline_id,
544     std::unique_ptr<IdsVector> remaining_ids,
545     bool is_first_attempt,
546     const std::string& favicon_data) {
547   if (!favicon_data.empty()) {
548     service_->GetOfflinePageModel()->StoreFavicon(offline_id, favicon_data);
549   }
550 
551   FetchVisuals(std::move(remaining_ids), is_first_attempt);
552 }
553 
554 }  // namespace offline_pages
555