1 // Copyright (c) 2012 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 // DownloadHistory manages persisting DownloadItems to the history service by
6 // observing a single DownloadManager and all its DownloadItems using an
7 // AllDownloadItemNotifier.
8 //
9 // DownloadHistory decides whether and when to add items to, remove items from,
10 // and update items in the database. DownloadHistory uses DownloadHistoryData to
11 // store per-DownloadItem data such as whether the item is persisted or being
12 // persisted, and the last history::DownloadRow that was passed to the database.
13 // When the DownloadManager and its delegate (ChromeDownloadManagerDelegate) are
14 // initialized, DownloadHistory is created and queries the HistoryService. When
15 // the HistoryService calls back from QueryDownloads() to QueryCallback(),
16 // DownloadHistory will then wait for DownloadManager to call
17 // LoadHistoryDownloads(), and uses DownloadManager::CreateDownloadItem() to
18 // inform DownloadManager of these persisted DownloadItems. CreateDownloadItem()
19 // internally calls OnDownloadCreated(), which normally adds items to the
20 // database, so LoadHistoryDownloads() uses |loading_id_| to disable adding
21 // these items to the database.  If a download is removed via
22 // OnDownloadRemoved() while the item is still being added to the database,
23 // DownloadHistory uses |removed_while_adding_| to remember to remove the item
24 // when its ItemAdded() callback is called.  All callbacks are bound with a weak
25 // pointer to DownloadHistory to prevent use-after-free bugs.
26 // ChromeDownloadManagerDelegate owns DownloadHistory, and deletes it in
27 // Shutdown(), which is called by DownloadManagerImpl::Shutdown() after all
28 // DownloadItems are destroyed.
29 
30 #include "chrome/browser/download/download_history.h"
31 
32 #include <utility>
33 
34 #include "base/bind.h"
35 #include "base/macros.h"
36 #include "base/memory/ptr_util.h"
37 #include "base/metrics/histogram_macros.h"
38 #include "base/optional.h"
39 #include "build/build_config.h"
40 #include "chrome/browser/download/download_crx_util.h"
41 #include "chrome/browser/profiles/profile.h"
42 #include "components/download/public/common/download_features.h"
43 #include "components/download/public/common/download_item.h"
44 #include "components/download/public/common/download_utils.h"
45 #include "components/history/content/browser/download_conversions.h"
46 #include "components/history/core/browser/download_database.h"
47 #include "components/history/core/browser/download_row.h"
48 #include "components/history/core/browser/history_service.h"
49 #include "content/public/browser/browser_task_traits.h"
50 #include "content/public/browser/browser_thread.h"
51 #include "content/public/browser/download_manager.h"
52 #include "extensions/buildflags/buildflags.h"
53 
54 #if BUILDFLAG(ENABLE_EXTENSIONS)
55 #include "chrome/browser/extensions/api/downloads/downloads_api.h"
56 #endif
57 
58 namespace {
59 
60 // Max data url size to be stored in history DB.
61 const size_t kMaxDataURLSize = 1024u;
62 
63 // If there is a data URL at the end of the url chain, truncate it if it is too
64 // long.
TruncatedDataUrlAtTheEndIfNeeded(std::vector<GURL> * url_chain)65 void TruncatedDataUrlAtTheEndIfNeeded(std::vector<GURL>* url_chain) {
66   if (url_chain->empty())
67     return;
68   GURL* url = &url_chain->back();
69   if (url->SchemeIs(url::kDataScheme)) {
70     const std::string& data_url = url->spec();
71     if (data_url.size() > kMaxDataURLSize) {
72       GURL truncated_url(data_url.substr(0, kMaxDataURLSize));
73       url->Swap(&truncated_url);
74     }
75   }
76 }
77 
78 // Per-DownloadItem data. This information does not belong inside DownloadItem,
79 // and keeping maps in DownloadHistory from DownloadItem to this information is
80 // error-prone and complicated. Unfortunately, DownloadHistory::removing_*_ and
81 // removed_while_adding_ cannot be moved into this class partly because
82 // DownloadHistoryData is destroyed when DownloadItems are destroyed, and we
83 // have no control over when DownloadItems are destroyed.
84 class DownloadHistoryData : public base::SupportsUserData::Data {
85  public:
86   enum PersistenceState {
87     NOT_PERSISTED,
88     PERSISTING,
89     PERSISTED,
90   };
91 
Get(download::DownloadItem * item)92   static DownloadHistoryData* Get(download::DownloadItem* item) {
93     base::SupportsUserData::Data* data = item->GetUserData(kKey);
94     return static_cast<DownloadHistoryData*>(data);
95   }
96 
Get(const download::DownloadItem * item)97   static const DownloadHistoryData* Get(const download::DownloadItem* item) {
98     const base::SupportsUserData::Data* data = item->GetUserData(kKey);
99     return static_cast<const DownloadHistoryData*>(data);
100   }
101 
DownloadHistoryData(download::DownloadItem * item)102   explicit DownloadHistoryData(download::DownloadItem* item) {
103     item->SetUserData(kKey, base::WrapUnique(this));
104   }
105 
~DownloadHistoryData()106   ~DownloadHistoryData() override {}
107 
state() const108   PersistenceState state() const { return state_; }
SetState(PersistenceState s)109   void SetState(PersistenceState s) { state_ = s; }
110 
111   // This allows DownloadHistory::OnDownloadUpdated() to see what changed in a
112   // DownloadItem if anything, in order to prevent writing to the database
113   // unnecessarily.  It is nullified when the item is no longer in progress in
114   // order to save memory.
info()115   history::DownloadRow* info() { return info_.get(); }
set_info(const history::DownloadRow & i)116   void set_info(const history::DownloadRow& i) {
117     // TODO(qinmin): avoid creating a new copy each time.
118     info_.reset(new history::DownloadRow(i));
119   }
clear_info()120   void clear_info() {
121     info_.reset();
122   }
123 
124  private:
125   static const char kKey[];
126 
127   PersistenceState state_ = NOT_PERSISTED;
128   std::unique_ptr<history::DownloadRow> info_;
129 
130   DISALLOW_COPY_AND_ASSIGN(DownloadHistoryData);
131 };
132 
133 const char DownloadHistoryData::kKey[] =
134   "DownloadItem DownloadHistoryData";
135 
GetDownloadRow(download::DownloadItem * item)136 history::DownloadRow GetDownloadRow(download::DownloadItem* item) {
137   std::string by_ext_id, by_ext_name;
138 #if BUILDFLAG(ENABLE_EXTENSIONS)
139   extensions::DownloadedByExtension* by_ext =
140       extensions::DownloadedByExtension::Get(item);
141   if (by_ext) {
142     by_ext_id = by_ext->id();
143     by_ext_name = by_ext->name();
144   }
145 #endif
146 
147   history::DownloadRow download;
148   download.current_path = item->GetFullPath();
149   download.target_path = item->GetTargetFilePath();
150   download.url_chain = item->GetUrlChain();
151   download.referrer_url = item->GetReferrerUrl();
152   download.site_url = item->GetSiteUrl();
153   download.tab_url = item->GetTabUrl();
154   download.tab_referrer_url = item->GetTabReferrerUrl();
155   download.http_method = std::string();  // HTTP method not available yet.
156   download.mime_type = item->GetMimeType();
157   download.original_mime_type = item->GetOriginalMimeType();
158   download.start_time = item->GetStartTime();
159   download.end_time = item->GetEndTime();
160   download.etag = item->GetETag();
161   download.last_modified = item->GetLastModifiedTime();
162   download.received_bytes = item->GetReceivedBytes();
163   download.total_bytes = item->GetTotalBytes();
164   download.state = history::ToHistoryDownloadState(item->GetState());
165   download.danger_type =
166       history::ToHistoryDownloadDangerType(item->GetDangerType());
167   download.interrupt_reason =
168       history::ToHistoryDownloadInterruptReason(item->GetLastReason());
169   download.hash = std::string();  // Hash value not available yet.
170   download.id = history::ToHistoryDownloadId(item->GetId());
171   download.guid = item->GetGuid();
172   download.opened = item->GetOpened();
173   download.last_access_time = item->GetLastAccessTime();
174   download.transient = item->IsTransient();
175   download.by_ext_id = by_ext_id;
176   download.by_ext_name = by_ext_name;
177   download.download_slice_info = history::GetHistoryDownloadSliceInfos(*item);
178   TruncatedDataUrlAtTheEndIfNeeded(&download.url_chain);
179   return download;
180 }
181 
182 enum class ShouldUpdateHistoryResult {
183   NO_UPDATE,
184   UPDATE,
185   UPDATE_IMMEDIATELY,
186 };
187 
ShouldUpdateHistory(const history::DownloadRow * previous,const history::DownloadRow & current)188 ShouldUpdateHistoryResult ShouldUpdateHistory(
189     const history::DownloadRow* previous,
190     const history::DownloadRow& current) {
191   // When download path is determined, Chrome should commit the history
192   // immediately. Otherwise the file will be left permanently on the external
193   // storage if Chrome crashes right away.
194   // TODO(qinmin): this doesn't solve all the issues. When download starts,
195   // Chrome will write the http response data to a temporary file, and later
196   // rename it. If Chrome is killed before committing the history here,
197   // that temporary file will still get permanently left.
198   // See http://crbug.com/664677.
199   if (previous == nullptr || previous->current_path != current.current_path)
200     return ShouldUpdateHistoryResult::UPDATE_IMMEDIATELY;
201 
202   // Ignore url_chain, referrer, site_url, http_method, mime_type,
203   // original_mime_type, start_time, id, and guid. These fields don't change.
204   if ((previous->target_path != current.target_path) ||
205       (previous->end_time != current.end_time) ||
206       (previous->received_bytes != current.received_bytes) ||
207       (previous->total_bytes != current.total_bytes) ||
208       (previous->etag != current.etag) ||
209       (previous->last_modified != current.last_modified) ||
210       (previous->state != current.state) ||
211       (previous->danger_type != current.danger_type) ||
212       (previous->interrupt_reason != current.interrupt_reason) ||
213       (previous->hash != current.hash) ||
214       (previous->opened != current.opened) ||
215       (previous->last_access_time != current.last_access_time) ||
216       (previous->transient != current.transient) ||
217       (previous->by_ext_id != current.by_ext_id) ||
218       (previous->by_ext_name != current.by_ext_name) ||
219       (previous->download_slice_info != current.download_slice_info)) {
220     return ShouldUpdateHistoryResult::UPDATE;
221   }
222 
223   return ShouldUpdateHistoryResult::NO_UPDATE;
224 }
225 
226 }  // anonymous namespace
227 
HistoryAdapter(history::HistoryService * history)228 DownloadHistory::HistoryAdapter::HistoryAdapter(
229     history::HistoryService* history)
230     : history_(history) {
231 }
~HistoryAdapter()232 DownloadHistory::HistoryAdapter::~HistoryAdapter() {}
233 
QueryDownloads(history::HistoryService::DownloadQueryCallback callback)234 void DownloadHistory::HistoryAdapter::QueryDownloads(
235     history::HistoryService::DownloadQueryCallback callback) {
236   history_->QueryDownloads(std::move(callback));
237 }
238 
CreateDownload(const history::DownloadRow & info,history::HistoryService::DownloadCreateCallback callback)239 void DownloadHistory::HistoryAdapter::CreateDownload(
240     const history::DownloadRow& info,
241     history::HistoryService::DownloadCreateCallback callback) {
242   history_->CreateDownload(info, std::move(callback));
243 }
244 
UpdateDownload(const history::DownloadRow & data,bool should_commit_immediately)245 void DownloadHistory::HistoryAdapter::UpdateDownload(
246     const history::DownloadRow& data, bool should_commit_immediately) {
247   history_->UpdateDownload(data, should_commit_immediately);
248 }
249 
RemoveDownloads(const std::set<uint32_t> & ids)250 void DownloadHistory::HistoryAdapter::RemoveDownloads(
251     const std::set<uint32_t>& ids) {
252   history_->RemoveDownloads(ids);
253 }
254 
Observer()255 DownloadHistory::Observer::Observer() {}
~Observer()256 DownloadHistory::Observer::~Observer() {}
257 
258 // static
IsPersisted(const download::DownloadItem * item)259 bool DownloadHistory::IsPersisted(const download::DownloadItem* item) {
260   const DownloadHistoryData* data = DownloadHistoryData::Get(item);
261   return data && (data->state() == DownloadHistoryData::PERSISTED);
262 }
263 
DownloadHistory(content::DownloadManager * manager,std::unique_ptr<HistoryAdapter> history)264 DownloadHistory::DownloadHistory(content::DownloadManager* manager,
265                                  std::unique_ptr<HistoryAdapter> history)
266     : notifier_(manager, this),
267       history_(std::move(history)),
268       loading_id_(download::DownloadItem::kInvalidId),
269       initial_history_query_complete_(false) {
270   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
271   download::SimpleDownloadManager::DownloadVector items;
272   notifier_.GetManager()->GetAllDownloads(&items);
273   for (auto* item : items)
274     OnDownloadCreated(notifier_.GetManager(), item);
275   history_->QueryDownloads(base::BindOnce(&DownloadHistory::QueryCallback,
276                                           weak_ptr_factory_.GetWeakPtr()));
277 }
278 
~DownloadHistory()279 DownloadHistory::~DownloadHistory() {
280   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
281   for (Observer& observer : observers_)
282     observer.OnDownloadHistoryDestroyed();
283   observers_.Clear();
284 }
285 
AddObserver(DownloadHistory::Observer * observer)286 void DownloadHistory::AddObserver(DownloadHistory::Observer* observer) {
287   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
288   observers_.AddObserver(observer);
289 
290   if (initial_history_query_complete_)
291     observer->OnHistoryQueryComplete();
292 }
293 
RemoveObserver(DownloadHistory::Observer * observer)294 void DownloadHistory::RemoveObserver(DownloadHistory::Observer* observer) {
295   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
296   observers_.RemoveObserver(observer);
297 }
298 
QueryCallback(std::vector<history::DownloadRow> rows)299 void DownloadHistory::QueryCallback(std::vector<history::DownloadRow> rows) {
300   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
301   // ManagerGoingDown() may have happened before the history loaded.
302   if (!notifier_.GetManager())
303     return;
304 
305   notifier_.GetManager()->OnHistoryQueryComplete(
306       base::BindOnce(&DownloadHistory::LoadHistoryDownloads,
307                      weak_ptr_factory_.GetWeakPtr(), std::move(rows)));
308 }
309 
LoadHistoryDownloads(std::vector<history::DownloadRow> rows)310 void DownloadHistory::LoadHistoryDownloads(
311     std::vector<history::DownloadRow> rows) {
312   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
313   DCHECK(notifier_.GetManager());
314 
315   for (const history::DownloadRow& row : rows) {
316     loading_id_ = history::ToContentDownloadId(row.id);
317     download::DownloadItem::DownloadState history_download_state =
318         history::ToContentDownloadState(row.state);
319     download::DownloadInterruptReason history_reason =
320         history::ToContentDownloadInterruptReason(row.interrupt_reason);
321     std::vector<GURL> url_chain = row.url_chain;
322     TruncatedDataUrlAtTheEndIfNeeded(&url_chain);
323     download::DownloadItem* item = notifier_.GetManager()->CreateDownloadItem(
324         row.guid, loading_id_, row.current_path, row.target_path, url_chain,
325         row.referrer_url, row.site_url, row.tab_url, row.tab_referrer_url,
326         base::nullopt, row.mime_type, row.original_mime_type, row.start_time,
327         row.end_time, row.etag, row.last_modified, row.received_bytes,
328         row.total_bytes,
329         std::string(),  // TODO(asanka): Need to persist and restore hash of
330                         // partial file for an interrupted download. No need to
331                         // store hash for a completed file.
332         history_download_state,
333         history::ToContentDownloadDangerType(row.danger_type), history_reason,
334         row.opened, row.last_access_time, row.transient,
335         history::ToContentReceivedSlices(row.download_slice_info));
336     // DownloadManager returns a nullptr if it decides to remove the download
337     // permanently.
338     if (item == nullptr) {
339       ScheduleRemoveDownload(row.id);
340       continue;
341     }
342     DCHECK_EQ(download::DownloadItem::kInvalidId, loading_id_);
343 
344     // The download might have been in the terminal state without informing
345     // history DB. If this is the case, populate the new state back to history
346     // DB.
347     if (item->IsDone() &&
348         !download::IsDownloadDone(item->GetURL(), history_download_state,
349                                   history_reason)) {
350       OnDownloadUpdated(notifier_.GetManager(), item);
351     }
352 #if BUILDFLAG(ENABLE_EXTENSIONS)
353     if (!row.by_ext_id.empty() && !row.by_ext_name.empty()) {
354       new extensions::DownloadedByExtension(item, row.by_ext_id,
355                                             row.by_ext_name);
356       item->UpdateObservers();
357     }
358 #endif
359     DCHECK_EQ(DownloadHistoryData::PERSISTED,
360               DownloadHistoryData::Get(item)->state());
361   }
362 
363   // Indicate that the history db is initialized.
364   notifier_.GetManager()->PostInitialization(
365       content::DownloadManager::DOWNLOAD_INITIALIZATION_DEPENDENCY_HISTORY_DB);
366 
367   initial_history_query_complete_ = true;
368   for (Observer& observer : observers_)
369     observer.OnHistoryQueryComplete();
370 }
371 
MaybeAddToHistory(download::DownloadItem * item)372 void DownloadHistory::MaybeAddToHistory(download::DownloadItem* item) {
373   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
374 
375   if (!NeedToUpdateDownloadHistory(item))
376     return;
377 
378   uint32_t download_id = item->GetId();
379   DownloadHistoryData* data = DownloadHistoryData::Get(item);
380   bool removing = removing_ids_.find(download_id) != removing_ids_.end();
381 
382   // TODO(benjhayden): Remove IsTemporary().
383   if ((notifier_.GetManager() &&
384        download_crx_util::IsTrustedExtensionDownload(
385            Profile::FromBrowserContext(
386                notifier_.GetManager()->GetBrowserContext()),
387            *item)) ||
388       item->IsTemporary() ||
389       (data->state() != DownloadHistoryData::NOT_PERSISTED) || removing) {
390     return;
391   }
392 
393   data->SetState(DownloadHistoryData::PERSISTING);
394   // Keep the info for in-progress download, so we can check whether history DB
395   // update is needed when DownloadUpdated() is called.
396   history::DownloadRow download_row = GetDownloadRow(item);
397   if (item->GetState() == download::DownloadItem::IN_PROGRESS)
398     data->set_info(download_row);
399   else
400     data->clear_info();
401   history_->CreateDownload(
402       download_row, base::BindOnce(&DownloadHistory::ItemAdded,
403                                    weak_ptr_factory_.GetWeakPtr(), download_id,
404                                    download_row));
405 }
406 
ItemAdded(uint32_t download_id,const history::DownloadRow & download_row,bool success)407 void DownloadHistory::ItemAdded(uint32_t download_id,
408                                 const history::DownloadRow& download_row,
409                                 bool success) {
410   if (removed_while_adding_.find(download_id) !=
411       removed_while_adding_.end()) {
412     removed_while_adding_.erase(download_id);
413     if (success)
414       ScheduleRemoveDownload(download_id);
415     return;
416   }
417 
418   if (!notifier_.GetManager())
419     return;
420 
421   download::DownloadItem* item =
422       notifier_.GetManager()->GetDownload(download_id);
423   if (!item) {
424     // This item will have called OnDownloadDestroyed().  If the item should
425     // have been removed from history, then it would have also called
426     // OnDownloadRemoved(), which would have put |download_id| in
427     // removed_while_adding_, handled above.
428     return;
429   }
430 
431   DownloadHistoryData* data = DownloadHistoryData::Get(item);
432   bool was_persisted = IsPersisted(item);
433 
434   // The sql INSERT statement failed. Avoid an infinite loop: don't
435   // automatically retry. Retry adding the next time the item is updated by
436   // resetting the state to NOT_PERSISTED.
437   if (!success) {
438     DVLOG(20) << __func__ << " INSERT failed id=" << download_id;
439     data->SetState(DownloadHistoryData::NOT_PERSISTED);
440     return;
441   }
442   data->SetState(DownloadHistoryData::PERSISTED);
443 
444   // Notify the observer about the change in the persistence state.
445   if (was_persisted != IsPersisted(item)) {
446     for (Observer& observer : observers_)
447       observer.OnDownloadStored(item, download_row);
448   }
449 }
450 
OnDownloadCreated(content::DownloadManager * manager,download::DownloadItem * item)451 void DownloadHistory::OnDownloadCreated(content::DownloadManager* manager,
452                                         download::DownloadItem* item) {
453   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
454 
455   // All downloads should pass through OnDownloadCreated exactly once.
456   CHECK(!DownloadHistoryData::Get(item));
457   DownloadHistoryData* data = new DownloadHistoryData(item);
458   if (item->GetId() == loading_id_)
459     OnDownloadRestoredFromHistory(item);
460   if (item->GetState() == download::DownloadItem::IN_PROGRESS &&
461       NeedToUpdateDownloadHistory(item)) {
462     data->set_info(GetDownloadRow(item));
463   }
464   MaybeAddToHistory(item);
465 }
466 
OnDownloadUpdated(content::DownloadManager * manager,download::DownloadItem * item)467 void DownloadHistory::OnDownloadUpdated(content::DownloadManager* manager,
468                                         download::DownloadItem* item) {
469   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
470   DownloadHistoryData* data = DownloadHistoryData::Get(item);
471   if (data->state() == DownloadHistoryData::NOT_PERSISTED) {
472     MaybeAddToHistory(item);
473     return;
474   }
475   if (item->IsTemporary()) {
476     OnDownloadRemoved(notifier_.GetManager(), item);
477     return;
478   }
479   if (!NeedToUpdateDownloadHistory(item))
480     return;
481 
482   history::DownloadRow current_info(GetDownloadRow(item));
483   ShouldUpdateHistoryResult should_update_result =
484       ShouldUpdateHistory(data->info(), current_info);
485   bool should_update =
486       (should_update_result != ShouldUpdateHistoryResult::NO_UPDATE);
487   UMA_HISTOGRAM_ENUMERATION("Download.HistoryPropagatedUpdate",
488                             should_update, 2);
489   if (should_update) {
490     history_->UpdateDownload(
491         current_info,
492         should_update_result == ShouldUpdateHistoryResult::UPDATE_IMMEDIATELY);
493     for (Observer& observer : observers_)
494       observer.OnDownloadStored(item, current_info);
495   }
496   if (item->GetState() == download::DownloadItem::IN_PROGRESS) {
497     data->set_info(current_info);
498   } else {
499     data->clear_info();
500   }
501 }
502 
OnDownloadOpened(content::DownloadManager * manager,download::DownloadItem * item)503 void DownloadHistory::OnDownloadOpened(content::DownloadManager* manager,
504                                        download::DownloadItem* item) {
505   OnDownloadUpdated(manager, item);
506 }
507 
OnDownloadRemoved(content::DownloadManager * manager,download::DownloadItem * item)508 void DownloadHistory::OnDownloadRemoved(content::DownloadManager* manager,
509                                         download::DownloadItem* item) {
510   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
511 
512   DownloadHistoryData* data = DownloadHistoryData::Get(item);
513   if (data->state() != DownloadHistoryData::PERSISTED) {
514     if (data->state() == DownloadHistoryData::PERSISTING) {
515       // ScheduleRemoveDownload will be called when history_ calls ItemAdded().
516       removed_while_adding_.insert(item->GetId());
517     }
518     return;
519   }
520   ScheduleRemoveDownload(item->GetId());
521   // This is important: another OnDownloadRemoved() handler could do something
522   // that synchronously fires an OnDownloadUpdated().
523   data->SetState(DownloadHistoryData::NOT_PERSISTED);
524 }
525 
ScheduleRemoveDownload(uint32_t download_id)526 void DownloadHistory::ScheduleRemoveDownload(uint32_t download_id) {
527   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
528 
529   // For database efficiency, batch removals together if they happen all at
530   // once.
531   if (removing_ids_.empty()) {
532     content::GetUIThreadTaskRunner({})->PostTask(
533         FROM_HERE, base::BindOnce(&DownloadHistory::RemoveDownloadsBatch,
534                                   weak_ptr_factory_.GetWeakPtr()));
535   }
536   removing_ids_.insert(download_id);
537 }
538 
RemoveDownloadsBatch()539 void DownloadHistory::RemoveDownloadsBatch() {
540   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
541   IdSet remove_ids;
542   removing_ids_.swap(remove_ids);
543   history_->RemoveDownloads(remove_ids);
544   for (Observer& observer : observers_)
545     observer.OnDownloadsRemoved(remove_ids);
546 }
547 
OnDownloadRestoredFromHistory(download::DownloadItem * item)548 void DownloadHistory::OnDownloadRestoredFromHistory(
549     download::DownloadItem* item) {
550   DownloadHistoryData* data = DownloadHistoryData::Get(item);
551   data->SetState(DownloadHistoryData::PERSISTED);
552   loading_id_ = download::DownloadItem::kInvalidId;
553 }
554 
NeedToUpdateDownloadHistory(download::DownloadItem * item)555 bool DownloadHistory::NeedToUpdateDownloadHistory(
556     download::DownloadItem* item) {
557 #if BUILDFLAG(ENABLE_EXTENSIONS)
558   // Always populate new extension downloads to history.
559   DownloadHistoryData* data = DownloadHistoryData::Get(item);
560   extensions::DownloadedByExtension* by_ext =
561       extensions::DownloadedByExtension::Get(item);
562   if (by_ext && !by_ext->id().empty() && !by_ext->name().empty() &&
563       data->state() != DownloadHistoryData::NOT_PERSISTED) {
564     return true;
565   }
566 #endif
567 
568   // When download DB is enabled, only downloads that are in terminal state
569   // are added to or updated in history DB. Non-transient in-progress and
570   // interrupted download will be stored in the in-progress DB.
571   return !item->IsTransient() &&
572          (item->IsSavePackageDownload() || item->IsDone());
573 }
574