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