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 #include "components/history/core/browser/history_backend.h"
6
7 #include <algorithm>
8 #include <functional>
9 #include <limits>
10 #include <map>
11 #include <memory>
12 #include <set>
13 #include <utility>
14 #include <vector>
15
16 #include "base/bind.h"
17 #include "base/callback_helpers.h"
18 #include "base/compiler_specific.h"
19 #include "base/containers/flat_set.h"
20 #include "base/files/file_enumerator.h"
21 #include "base/files/file_path.h"
22 #include "base/files/file_util.h"
23 #include "base/memory/memory_pressure_listener.h"
24 #include "base/metrics/histogram_macros.h"
25 #include "base/no_destructor.h"
26 #include "base/rand_util.h"
27 #include "base/sequenced_task_runner.h"
28 #include "base/single_thread_task_runner.h"
29 #include "base/strings/string_util.h"
30 #include "base/strings/utf_string_conversions.h"
31 #include "base/time/time.h"
32 #include "base/timer/elapsed_timer.h"
33 #include "base/trace_event/trace_event.h"
34 #include "build/build_config.h"
35 #include "components/favicon/core/favicon_backend.h"
36 #include "components/history/core/browser/download_constants.h"
37 #include "components/history/core/browser/download_row.h"
38 #include "components/history/core/browser/history_backend_client.h"
39 #include "components/history/core/browser/history_backend_observer.h"
40 #include "components/history/core/browser/history_constants.h"
41 #include "components/history/core/browser/history_database.h"
42 #include "components/history/core/browser/history_database_params.h"
43 #include "components/history/core/browser/history_db_task.h"
44 #include "components/history/core/browser/in_memory_history_backend.h"
45 #include "components/history/core/browser/keyword_search_term.h"
46 #include "components/history/core/browser/page_usage_data.h"
47 #include "components/history/core/browser/sync/typed_url_sync_bridge.h"
48 #include "components/history/core/browser/url_utils.h"
49 #include "components/sync/model_impl/client_tag_based_model_type_processor.h"
50 #include "components/url_formatter/url_formatter.h"
51 #include "net/base/escape.h"
52 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
53 #include "sql/error_delegate_util.h"
54 #include "third_party/skia/include/core/SkBitmap.h"
55 #include "ui/gfx/codec/png_codec.h"
56 #include "url/gurl.h"
57 #include "url/url_constants.h"
58
59 #if defined(OS_IOS)
60 #include "base/ios/scoped_critical_action.h"
61 #endif
62
63 using base::Time;
64 using base::TimeDelta;
65 using base::TimeTicks;
66 using favicon::FaviconBitmap;
67 using favicon::FaviconBitmapID;
68 using favicon::FaviconBitmapIDSize;
69 using favicon::FaviconBitmapType;
70 using favicon::IconMapping;
71 using syncer::ClientTagBasedModelTypeProcessor;
72
73 /* The HistoryBackend consists of two components:
74
75 HistoryDatabase (stores past 3 months of history)
76 URLDatabase (stores a list of URLs)
77 DownloadDatabase (stores a list of downloads)
78 VisitDatabase (stores a list of visits for the URLs)
79 VisitSegmentDatabase (stores groups of URLs for the most visited view).
80
81 ExpireHistoryBackend (manages deleting things older than 3 months)
82 */
83
84 namespace history {
85
86 namespace {
87
88 #if DCHECK_IS_ON()
89 // Use to keep track of paths used to host HistoryBackends. This class
90 // is thread-safe. No two backends should ever run at the same time using the
91 // same directory since they will contend on the files created there.
92 class HistoryPathsTracker {
93 public:
94 HistoryPathsTracker(const HistoryPathsTracker&) = delete;
95 HistoryPathsTracker& operator=(const HistoryPathsTracker&) = delete;
96
GetInstance()97 static HistoryPathsTracker* GetInstance() {
98 static base::NoDestructor<HistoryPathsTracker> instance;
99 return instance.get();
100 }
101
AddPath(const base::FilePath & file_path)102 void AddPath(const base::FilePath& file_path) {
103 base::AutoLock auto_lock(lock_);
104 paths_.insert(file_path);
105 }
106
RemovePath(const base::FilePath & file_path)107 void RemovePath(const base::FilePath& file_path) {
108 base::AutoLock auto_lock(lock_);
109 auto it = paths_.find(file_path);
110
111 // If the backend was created without a db we are not tracking it.
112 if (it != paths_.end())
113 paths_.erase(it);
114 }
115
HasPath(const base::FilePath & file_path)116 bool HasPath(const base::FilePath& file_path) {
117 base::AutoLock auto_lock(lock_);
118 return paths_.find(file_path) != paths_.end();
119 }
120
121 private:
122 friend class base::NoDestructor<HistoryPathsTracker>;
123
124 HistoryPathsTracker() = default;
125 ~HistoryPathsTracker() = default;
126
127 base::Lock lock_;
128 base::flat_set<base::FilePath> paths_ GUARDED_BY(lock_);
129 };
130 #endif
131
HasApiTransition2or3(ui::PageTransition transition)132 bool HasApiTransition2or3(ui::PageTransition transition) {
133 return (ui::PageTransitionGetQualifier(transition) &
134 (ui::PageTransitionGetQualifier(ui::PAGE_TRANSITION_FROM_API_2) |
135 ui::PageTransitionGetQualifier(ui::PAGE_TRANSITION_FROM_API_3))) !=
136 0;
137 }
138
RunUnlessCanceled(base::OnceClosure closure,const base::CancelableTaskTracker::IsCanceledCallback & is_canceled)139 void RunUnlessCanceled(
140 base::OnceClosure closure,
141 const base::CancelableTaskTracker::IsCanceledCallback& is_canceled) {
142 if (!is_canceled.Run())
143 std::move(closure).Run();
144 }
145
146 // How long we'll wait to do a commit, so that things are batched together.
147 const int kCommitIntervalSeconds = 10;
148
149 // The maximum number of items we'll allow in the redirect list before
150 // deleting some.
151 const int kMaxRedirectCount = 32;
152
153 // The number of days old a history entry can be before it is considered "old"
154 // and is deleted.
155 const int kExpireDaysThreshold = 90;
156
157 // The maximum number of days for which domain visit metrics are computed
158 // each time HistoryBackend::GetDomainDiversity() is called.
159 constexpr int kDomainDiversityMaxBacktrackedDays = 7;
160
161 // An offset that corrects possible error in date/time arithmetic caused by
162 // fluctuation of day length due to Daylight Saving Time (DST). For example,
163 // given midnight M, its next midnight can be computed as (M + 24 hour
164 // + offset).LocalMidnight(). In most modern DST systems, the DST shift is
165 // typically 1 hour. However, a larger value of 4 is chosen here to
166 // accommodate larger DST shifts that have been used historically and to
167 // avoid other potential issues.
168 constexpr int kDSTRoundingOffsetHours = 4;
169
170 } // namespace
171
FormatUrlForRedirectComparison(const GURL & url)172 base::string16 FormatUrlForRedirectComparison(const GURL& url) {
173 url::Replacements<char> remove_port;
174 remove_port.ClearPort();
175 return url_formatter::FormatUrl(
176 url.ReplaceComponents(remove_port),
177 url_formatter::kFormatUrlOmitHTTP | url_formatter::kFormatUrlOmitHTTPS |
178 url_formatter::kFormatUrlOmitUsernamePassword |
179 url_formatter::kFormatUrlOmitTrivialSubdomains,
180 net::UnescapeRule::NONE, nullptr, nullptr, nullptr);
181 }
182
MidnightNDaysLater(base::Time time,int days)183 base::Time MidnightNDaysLater(base::Time time, int days) {
184 return (time.LocalMidnight() + base::TimeDelta::FromDays(days) +
185 base::TimeDelta::FromHours(kDSTRoundingOffsetHours))
186 .LocalMidnight();
187 }
188
QueuedHistoryDBTask(std::unique_ptr<HistoryDBTask> task,scoped_refptr<base::SingleThreadTaskRunner> origin_loop,const base::CancelableTaskTracker::IsCanceledCallback & is_canceled)189 QueuedHistoryDBTask::QueuedHistoryDBTask(
190 std::unique_ptr<HistoryDBTask> task,
191 scoped_refptr<base::SingleThreadTaskRunner> origin_loop,
192 const base::CancelableTaskTracker::IsCanceledCallback& is_canceled)
193 : task_(std::move(task)),
194 origin_loop_(origin_loop),
195 is_canceled_(is_canceled) {
196 DCHECK(task_);
197 DCHECK(origin_loop_);
198 DCHECK(!is_canceled_.is_null());
199 }
200
~QueuedHistoryDBTask()201 QueuedHistoryDBTask::~QueuedHistoryDBTask() {
202 // Ensure that |task_| is destroyed on its origin thread.
203 origin_loop_->PostTask(FROM_HERE,
204 base::BindOnce(&base::DeletePointer<HistoryDBTask>,
205 base::Unretained(task_.release())));
206 }
207
is_canceled()208 bool QueuedHistoryDBTask::is_canceled() {
209 return is_canceled_.Run();
210 }
211
Run(HistoryBackend * backend,HistoryDatabase * db)212 bool QueuedHistoryDBTask::Run(HistoryBackend* backend, HistoryDatabase* db) {
213 return task_->RunOnDBThread(backend, db);
214 }
215
DoneRun()216 void QueuedHistoryDBTask::DoneRun() {
217 origin_loop_->PostTask(
218 FROM_HERE,
219 base::BindOnce(&RunUnlessCanceled,
220 base::BindOnce(&HistoryDBTask::DoneRunOnMainThread,
221 base::Unretained(task_.get())),
222 is_canceled_));
223 }
224
225 // HistoryBackendHelper --------------------------------------------------------
226
227 // Wrapper around base::SupportsUserData with a public destructor.
228 class HistoryBackendHelper : public base::SupportsUserData {
229 public:
230 HistoryBackendHelper();
231 ~HistoryBackendHelper() override;
232 };
233
234 HistoryBackendHelper::HistoryBackendHelper() = default;
235
236 HistoryBackendHelper::~HistoryBackendHelper() = default;
237
238 // HistoryBackend --------------------------------------------------------------
239
240 // static
IsTypedIncrement(ui::PageTransition transition)241 bool HistoryBackend::IsTypedIncrement(ui::PageTransition transition) {
242 if (ui::PageTransitionIsNewNavigation(transition) &&
243 ((ui::PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_TYPED) &&
244 !ui::PageTransitionIsRedirect(transition)) ||
245 ui::PageTransitionCoreTypeIs(transition,
246 ui::PAGE_TRANSITION_KEYWORD_GENERATED))) {
247 return true;
248 }
249 return false;
250 }
251
HistoryBackend(std::unique_ptr<Delegate> delegate,std::unique_ptr<HistoryBackendClient> backend_client,scoped_refptr<base::SequencedTaskRunner> task_runner)252 HistoryBackend::HistoryBackend(
253 std::unique_ptr<Delegate> delegate,
254 std::unique_ptr<HistoryBackendClient> backend_client,
255 scoped_refptr<base::SequencedTaskRunner> task_runner)
256 : delegate_(std::move(delegate)),
257 scheduled_kill_db_(false),
258 expirer_(this, backend_client.get(), task_runner),
259 recent_redirects_(kMaxRedirectCount),
260 segment_queried_(false),
261 backend_client_(std::move(backend_client)),
262 task_runner_(task_runner) {
263 DCHECK(delegate_);
264 }
265
~HistoryBackend()266 HistoryBackend::~HistoryBackend() {
267 DCHECK(scheduled_commit_.IsCancelled()) << "Deleting without cleanup";
268 queued_history_db_tasks_.clear();
269
270 // Release stashed embedder object before cleaning up the databases.
271 supports_user_data_helper_.reset();
272
273 // First close the databases before optionally running the "destroy" task.
274 CloseAllDatabases();
275
276 if (!backend_destroy_task_.is_null()) {
277 // Notify an interested party (typically a unit test) that we're done.
278 DCHECK(backend_destroy_task_runner_);
279 backend_destroy_task_runner_->PostTask(FROM_HERE,
280 std::move(backend_destroy_task_));
281 }
282
283 #if DCHECK_IS_ON()
284 HistoryPathsTracker::GetInstance()->RemovePath(history_dir_);
285 #endif
286
287 #if defined(OS_ANDROID)
288 if (backend_client_ && !history_dir_.empty())
289 backend_client_->OnHistoryBackendDestroyed(this, history_dir_);
290 #endif
291 }
292
Init(bool force_fail,const HistoryDatabaseParams & history_database_params)293 void HistoryBackend::Init(
294 bool force_fail,
295 const HistoryDatabaseParams& history_database_params) {
296 TRACE_EVENT0("browser", "HistoryBackend::Init");
297
298 DCHECK(base::PathExists(history_database_params.history_dir))
299 << "History directory does not exist. If you are in a test make sure "
300 "that ~TestingProfile() has not been called or that the "
301 "ScopedTempDirectory used outlives this task.";
302
303 // HistoryBackend is created on the UI thread by HistoryService, then the
304 // HistoryBackend::Init() method is called on the DB thread. Create the
305 // base::SupportsUserData on the DB thread since it is not thread-safe.
306 supports_user_data_helper_.reset(new HistoryBackendHelper);
307
308 if (!force_fail)
309 InitImpl(history_database_params);
310 delegate_->DBLoaded();
311
312 typed_url_sync_bridge_ = std::make_unique<TypedURLSyncBridge>(
313 this, db_.get(),
314 std::make_unique<ClientTagBasedModelTypeProcessor>(
315 syncer::TYPED_URLS, /*dump_stack=*/base::RepeatingClosure()));
316 typed_url_sync_bridge_->Init();
317
318 memory_pressure_listener_ = std::make_unique<base::MemoryPressureListener>(
319 FROM_HERE, base::BindRepeating(&HistoryBackend::OnMemoryPressure,
320 base::Unretained(this)));
321 }
322
SetOnBackendDestroyTask(scoped_refptr<base::SingleThreadTaskRunner> task_runner,base::OnceClosure task)323 void HistoryBackend::SetOnBackendDestroyTask(
324 scoped_refptr<base::SingleThreadTaskRunner> task_runner,
325 base::OnceClosure task) {
326 TRACE_EVENT0("browser", "HistoryBackend::SetOnBackendDestroyTask");
327 if (!backend_destroy_task_.is_null())
328 DLOG(WARNING) << "Setting more than one destroy task, overriding";
329 backend_destroy_task_runner_ = std::move(task_runner);
330 backend_destroy_task_ = std::move(task);
331 }
332
Closing()333 void HistoryBackend::Closing() {
334 TRACE_EVENT0("browser", "HistoryBackend::Closing");
335 // Any scheduled commit will have a reference to us, we must make it
336 // release that reference before we can be destroyed.
337 CancelScheduledCommit();
338 }
339
340 #if defined(OS_IOS)
PersistState()341 void HistoryBackend::PersistState() {
342 TRACE_EVENT0("browser", "HistoryBackend::PersistState");
343 Commit();
344 }
345 #endif
346
ClearCachedDataForContextID(ContextID context_id)347 void HistoryBackend::ClearCachedDataForContextID(ContextID context_id) {
348 TRACE_EVENT0("browser", "HistoryBackend::ClearCachedDataForContextID");
349 tracker_.ClearCachedDataForContextID(context_id);
350 }
351
GetFaviconsFileName() const352 base::FilePath HistoryBackend::GetFaviconsFileName() const {
353 return history_dir_.Append(kFaviconsFilename);
354 }
355
GetLastSegmentID(VisitID from_visit)356 SegmentID HistoryBackend::GetLastSegmentID(VisitID from_visit) {
357 // Set is used to detect referrer loops. Should not happen, but can
358 // if the database is corrupt.
359 std::set<VisitID> visit_set;
360 VisitID visit_id = from_visit;
361 while (visit_id) {
362 VisitRow row;
363 if (!db_->GetRowForVisit(visit_id, &row))
364 return 0;
365 if (row.segment_id)
366 return row.segment_id; // Found a visit in this change with a segment.
367
368 // Check the referrer of this visit, if any.
369 visit_id = row.referring_visit;
370
371 if (visit_set.find(visit_id) != visit_set.end()) {
372 NOTREACHED() << "Loop in referer chain, giving up";
373 break;
374 }
375 visit_set.insert(visit_id);
376 }
377 return 0;
378 }
379
UpdateSegments(const GURL & url,VisitID from_visit,VisitID visit_id,ui::PageTransition transition_type,const Time ts)380 SegmentID HistoryBackend::UpdateSegments(const GURL& url,
381 VisitID from_visit,
382 VisitID visit_id,
383 ui::PageTransition transition_type,
384 const Time ts) {
385 if (!db_)
386 return 0;
387
388 // We only consider main frames.
389 if (!ui::PageTransitionIsMainFrame(transition_type))
390 return 0;
391
392 SegmentID segment_id = 0;
393
394 // Are we at the beginning of a new segment?
395 // Note that navigating to an existing entry (with back/forward) reuses the
396 // same transition type. We are not adding it as a new segment in that case
397 // because if this was the target of a redirect, we might end up with
398 // 2 entries for the same final URL. Ex: User types google.net, gets
399 // redirected to google.com. A segment is created for google.net. On
400 // google.com users navigates through a link, then press back. That last
401 // navigation is for the entry google.com transition typed. We end up adding
402 // a segment for that one as well. So we end up with google.net and google.com
403 // in the segment table, showing as 2 entries in the NTP.
404 // Note also that we should still be updating the visit count for that segment
405 // which we are not doing now. It should be addressed when
406 // http://crbug.com/96860 is fixed.
407 if ((ui::PageTransitionCoreTypeIs(transition_type,
408 ui::PAGE_TRANSITION_TYPED) ||
409 ui::PageTransitionCoreTypeIs(transition_type,
410 ui::PAGE_TRANSITION_AUTO_BOOKMARK)) &&
411 (transition_type & ui::PAGE_TRANSITION_FORWARD_BACK) == 0) {
412 // If so, create or get the segment.
413 std::string segment_name = db_->ComputeSegmentName(url);
414 URLID url_id = db_->GetRowForURL(url, nullptr);
415 if (!url_id)
416 return 0;
417
418 segment_id = db_->GetSegmentNamed(segment_name);
419 if (!segment_id) {
420 segment_id = db_->CreateSegment(url_id, segment_name);
421 if (!segment_id) {
422 NOTREACHED();
423 return 0;
424 }
425 } else {
426 // Note: if we update an existing segment, we update the url used to
427 // represent that segment in order to minimize stale most visited
428 // images.
429 db_->UpdateSegmentRepresentationURL(segment_id, url_id);
430 }
431 } else {
432 // Note: it is possible there is no segment ID set for this visit chain.
433 // This can happen if the initial navigation wasn't AUTO_BOOKMARK or
434 // TYPED. (For example GENERATED). In this case this visit doesn't count
435 // toward any segment.
436 segment_id = GetLastSegmentID(from_visit);
437 if (!segment_id)
438 return 0;
439 }
440
441 // Set the segment in the visit.
442 if (!db_->SetSegmentID(visit_id, segment_id)) {
443 NOTREACHED();
444 return 0;
445 }
446
447 // Finally, increase the counter for that segment / day.
448 if (!db_->IncreaseSegmentVisitCount(segment_id, ts, 1)) {
449 NOTREACHED();
450 return 0;
451 }
452 return segment_id;
453 }
454
UpdateWithPageEndTime(ContextID context_id,int nav_entry_id,const GURL & url,Time end_ts)455 void HistoryBackend::UpdateWithPageEndTime(ContextID context_id,
456 int nav_entry_id,
457 const GURL& url,
458 Time end_ts) {
459 TRACE_EVENT0("browser", "HistoryBackend::UpdateWithPageEndTime");
460 // Will be filled with the URL ID and the visit ID of the last addition.
461 VisitID visit_id = tracker_.GetLastVisit(context_id, nav_entry_id, url);
462 UpdateVisitDuration(visit_id, end_ts);
463 }
464
UpdateVisitDuration(VisitID visit_id,const Time end_ts)465 void HistoryBackend::UpdateVisitDuration(VisitID visit_id, const Time end_ts) {
466 if (!db_)
467 return;
468
469 // Get the starting visit_time for visit_id.
470 VisitRow visit_row;
471 if (db_->GetRowForVisit(visit_id, &visit_row)) {
472 // We should never have a negative duration time even when time is skewed.
473 visit_row.visit_duration = end_ts > visit_row.visit_time
474 ? end_ts - visit_row.visit_time
475 : TimeDelta::FromMicroseconds(0);
476 db_->UpdateVisitRow(visit_row);
477 }
478 }
479
IsUntypedIntranetHost(const GURL & url)480 bool HistoryBackend::IsUntypedIntranetHost(const GURL& url) {
481 if (!url.SchemeIs(url::kHttpScheme) && !url.SchemeIs(url::kHttpsScheme) &&
482 !url.SchemeIs(url::kFtpScheme))
483 return false;
484
485 const std::string host = url.host();
486 const size_t registry_length =
487 net::registry_controlled_domains::GetCanonicalHostRegistryLength(
488 host, net::registry_controlled_domains::EXCLUDE_UNKNOWN_REGISTRIES,
489 net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES);
490 return (registry_length == 0) && !db_->IsTypedHost(host, /*scheme=*/nullptr);
491 }
492
GetCountsAndLastVisitForOrigins(const std::set<GURL> & origins) const493 OriginCountAndLastVisitMap HistoryBackend::GetCountsAndLastVisitForOrigins(
494 const std::set<GURL>& origins) const {
495 if (!db_)
496 return OriginCountAndLastVisitMap();
497 if (origins.empty())
498 return OriginCountAndLastVisitMap();
499
500 URLDatabase::URLEnumerator it;
501 if (!db_->InitURLEnumeratorForEverything(&it))
502 return OriginCountAndLastVisitMap();
503
504 OriginCountAndLastVisitMap origin_count_map;
505 for (const GURL& origin : origins)
506 origin_count_map[origin] = std::make_pair(0, base::Time());
507
508 URLRow row;
509 while (it.GetNextURL(&row)) {
510 GURL origin = row.url().GetOrigin();
511 auto iter = origin_count_map.find(origin);
512 if (iter != origin_count_map.end()) {
513 std::pair<int, base::Time>& value = iter->second;
514 ++(value.first);
515 if (value.second.is_null() || value.second < row.last_visit())
516 value.second = row.last_visit();
517 }
518 }
519
520 return origin_count_map;
521 }
522
AddPage(const HistoryAddPageArgs & request)523 void HistoryBackend::AddPage(const HistoryAddPageArgs& request) {
524 TRACE_EVENT0("browser", "HistoryBackend::AddPage");
525
526 if (!db_)
527 return;
528
529 // Will be filled with the URL ID and the visit ID of the last addition.
530 std::pair<URLID, VisitID> last_ids(
531 0, tracker_.GetLastVisit(request.context_id, request.nav_entry_id,
532 request.referrer));
533
534 VisitID from_visit_id = last_ids.second;
535
536 // If a redirect chain is given, we expect the last item in that chain to be
537 // the final URL.
538 DCHECK(request.redirects.empty() || request.redirects.back() == request.url);
539
540 // If the user is adding older history, we need to make sure our times
541 // are correct.
542 if (request.time < first_recorded_time_)
543 first_recorded_time_ = request.time;
544
545 ui::PageTransition request_transition = request.transition;
546 bool is_keyword_generated = ui::PageTransitionCoreTypeIs(
547 request_transition, ui::PAGE_TRANSITION_KEYWORD_GENERATED);
548
549 // If the user is navigating to a not-previously-typed intranet hostname,
550 // change the transition to TYPED so that the omnibox will learn that this is
551 // a known host. This logic is disabled if API_2/API_3 is present as such
552 // visits are not intended to influence the omnibox, and shouldn't be
553 // changed to TYPED. (API_2/API_3 are not used with TYPED transitions).
554 bool has_redirects = request.redirects.size() > 1;
555 if (ui::PageTransitionIsMainFrame(request_transition) &&
556 !ui::PageTransitionCoreTypeIs(request_transition,
557 ui::PAGE_TRANSITION_TYPED) &&
558 !is_keyword_generated && !HasApiTransition2or3(request_transition)) {
559 // Check both the start and end of a redirect chain, since the user will
560 // consider both to have been "navigated to".
561 if (IsUntypedIntranetHost(request.url) ||
562 (has_redirects && IsUntypedIntranetHost(request.redirects[0]))) {
563 request_transition = ui::PageTransitionFromInt(
564 ui::PAGE_TRANSITION_TYPED |
565 ui::PageTransitionGetQualifier(request_transition));
566 }
567 }
568
569 // FROM_API_2/FROM_API_3 should never be used with a transition type that
570 // increments the typed-count as that defeats the purpose.
571 DCHECK(!IsTypedIncrement(request_transition) ||
572 !HasApiTransition2or3(request_transition));
573
574 if (!has_redirects) {
575 // The single entry is both a chain start and end.
576 ui::PageTransition t = ui::PageTransitionFromInt(
577 request_transition | ui::PAGE_TRANSITION_CHAIN_START |
578 ui::PAGE_TRANSITION_CHAIN_END);
579
580 // No redirect case (one element means just the page itself).
581 last_ids =
582 AddPageVisit(request.url, request.time, last_ids.second, t,
583 request.hidden, request.visit_source, IsTypedIncrement(t),
584 request.publicly_routable, request.title);
585
586 // Update the segment for this visit. KEYWORD_GENERATED visits should not
587 // result in changing most visited, so we don't update segments (most
588 // visited db).
589 if (!is_keyword_generated && request.consider_for_ntp_most_visited) {
590 UpdateSegments(request.url, from_visit_id, last_ids.second, t,
591 request.time);
592
593 // Update the referrer's duration.
594 UpdateVisitDuration(from_visit_id, request.time);
595 }
596 } else {
597 // Redirect case. Add the redirect chain.
598
599 ui::PageTransition redirect_info = ui::PAGE_TRANSITION_CHAIN_START;
600
601 RedirectList redirects = request.redirects;
602 // In the presence of client redirects, |request.redirects| can be a partial
603 // chain because previous calls to this function may have reported a
604 // redirect chain already. This is fine for the visits database where we'll
605 // just append data but insufficient for |recent_redirects_|
606 // (backpropagation of favicons and titles), where we'd like the full
607 // (extended) redirect chain. We use |extended_redirect_chain| to represent
608 // this.
609 RedirectList extended_redirect_chain;
610
611 if (redirects[0].SchemeIs(url::kAboutScheme)) {
612 // When the redirect source + referrer is "about" we skip it. This
613 // happens when a page opens a new frame/window to about:blank and then
614 // script sets the URL to somewhere else (used to hide the referrer). It
615 // would be nice to keep all these redirects properly but we don't ever
616 // see the initial about:blank load, so we don't know where the
617 // subsequent client redirect came from.
618 //
619 // In this case, we just don't bother hooking up the source of the
620 // redirects, so we remove it.
621 redirects.erase(redirects.begin());
622 } else if (request_transition & ui::PAGE_TRANSITION_CLIENT_REDIRECT) {
623 redirect_info = ui::PAGE_TRANSITION_CLIENT_REDIRECT;
624 // The first entry in the redirect chain initiated a client redirect.
625 // We don't add this to the database since the referrer is already
626 // there, so we skip over it but change the transition type of the first
627 // transition to client redirect.
628 //
629 // The referrer is invalid when restoring a session that features an
630 // https tab that redirects to a different host or to http. In this
631 // case we don't need to reconnect the new redirect with the existing
632 // chain.
633 if (request.referrer.is_valid()) {
634 DCHECK_EQ(request.referrer, redirects[0]);
635 redirects.erase(redirects.begin());
636
637 // If the navigation entry for this visit has replaced that for the
638 // first visit, remove the CHAIN_END marker from the first visit. This
639 // can be called a lot, for example, the page cycler, and most of the
640 // time we won't have changed anything.
641 VisitRow visit_row;
642 if (request.did_replace_entry) {
643 if (db_->GetRowForVisit(last_ids.second, &visit_row) &&
644 visit_row.transition & ui::PAGE_TRANSITION_CHAIN_END) {
645 visit_row.transition = ui::PageTransitionFromInt(
646 visit_row.transition & ~ui::PAGE_TRANSITION_CHAIN_END);
647 db_->UpdateVisitRow(visit_row);
648 }
649
650 extended_redirect_chain = GetCachedRecentRedirects(request.referrer);
651 }
652 }
653 }
654
655 bool transfer_typed_credit_from_first_to_second_url = false;
656 if (redirects.size() > 1) {
657 // Check if the first redirect is the same as the original URL but
658 // upgraded to HTTPS. This ignores the port numbers (in case of
659 // non-standard HTTP or HTTPS ports) and trivial subdomains (e.g., "www."
660 // or "m.").
661 if (IsTypedIncrement(request_transition) &&
662 redirects[0].SchemeIs(url::kHttpScheme) &&
663 redirects[1].SchemeIs(url::kHttpsScheme) &&
664 FormatUrlForRedirectComparison(redirects[0]) ==
665 FormatUrlForRedirectComparison(redirects[1])) {
666 transfer_typed_credit_from_first_to_second_url = true;
667 }
668 }
669
670 for (size_t redirect_index = 0; redirect_index < redirects.size();
671 redirect_index++) {
672 ui::PageTransition t = ui::PageTransitionFromInt(
673 ui::PageTransitionStripQualifier(request_transition) | redirect_info);
674
675 bool publicly_routable = false;
676
677 // If this is the last transition, add a CHAIN_END marker
678 if (redirect_index == (redirects.size() - 1)) {
679 t = ui::PageTransitionFromInt(t | ui::PAGE_TRANSITION_CHAIN_END);
680 // In order for a visit to be visible, it must have CHAIN_END. If the
681 // requested transition contained PAGE_TRANSITION_FROM_API_3, then
682 // add it to the CHAIN_END visit so that the visit is not visible.
683 if ((ui::PageTransitionGetQualifier(request_transition) &
684 ui::PAGE_TRANSITION_FROM_API_3) != 0) {
685 t = ui::PageTransitionFromInt(t | ui::PAGE_TRANSITION_FROM_API_3);
686 }
687
688 // Since request.publicly_routable is a property of the visit to
689 // request.url, it only applies to the final redirect.
690 publicly_routable = request.publicly_routable;
691 }
692
693 bool should_increment_typed_count = IsTypedIncrement(t);
694 if (transfer_typed_credit_from_first_to_second_url) {
695 if (redirect_index == 0)
696 should_increment_typed_count = false;
697 else if (redirect_index == 1)
698 should_increment_typed_count = true;
699 }
700
701 // Record all redirect visits with the same timestamp. We don't display
702 // them anyway, and if we ever decide to, we can reconstruct their order
703 // from the redirect chain.
704 last_ids = AddPageVisit(
705 redirects[redirect_index], request.time, last_ids.second, t,
706 request.hidden, request.visit_source, should_increment_typed_count,
707 publicly_routable, request.title);
708
709 if (t & ui::PAGE_TRANSITION_CHAIN_START) {
710 if (request.consider_for_ntp_most_visited) {
711 UpdateSegments(redirects[redirect_index], from_visit_id,
712 last_ids.second, t, request.time);
713 }
714
715 // Update the visit_details for this visit.
716 UpdateVisitDuration(from_visit_id, request.time);
717 }
718
719 // Subsequent transitions in the redirect list must all be server
720 // redirects.
721 redirect_info = ui::PAGE_TRANSITION_SERVER_REDIRECT;
722 }
723
724 // Last, save this redirect chain for later so we can set titles & favicons
725 // on the redirected pages properly. For this we use the extended redirect
726 // chain, which includes URLs from chained redirects.
727 extended_redirect_chain.insert(extended_redirect_chain.end(),
728 std::make_move_iterator(redirects.begin()),
729 std::make_move_iterator(redirects.end()));
730 recent_redirects_.Put(request.url, extended_redirect_chain);
731 }
732
733 // TODO(brettw) bug 1140015: Add an "add page" notification so the history
734 // views can keep in sync.
735
736 // Add the last visit to the tracker so we can get outgoing transitions.
737 // TODO(evanm): Due to http://b/1194536 we lose the referrers of a subframe
738 // navigation anyway, so last_visit_id is always zero for them. But adding
739 // them here confuses main frame history, so we skip them for now.
740 if (!ui::PageTransitionCoreTypeIs(request_transition,
741 ui::PAGE_TRANSITION_AUTO_SUBFRAME) &&
742 !ui::PageTransitionCoreTypeIs(request_transition,
743 ui::PAGE_TRANSITION_MANUAL_SUBFRAME) &&
744 !is_keyword_generated) {
745 tracker_.AddVisit(request.context_id, request.nav_entry_id, request.url,
746 last_ids.second);
747 }
748
749 ScheduleCommit();
750 }
751
InitImpl(const HistoryDatabaseParams & history_database_params)752 void HistoryBackend::InitImpl(
753 const HistoryDatabaseParams& history_database_params) {
754 DCHECK(!db_) << "Initializing HistoryBackend twice";
755 // In the rare case where the db fails to initialize a dialog may get shown
756 // the blocks the caller, yet allows other messages through. For this reason
757 // we only set db_ to the created database if creation is successful. That
758 // way other methods won't do anything as db_ is still null.
759
760 TimeTicks beginning_time = TimeTicks::Now();
761
762 // Compute the file names.
763 history_dir_ = history_database_params.history_dir;
764
765 #if DCHECK_IS_ON()
766 DCHECK(!HistoryPathsTracker::GetInstance()->HasPath(history_dir_))
767 << "There already is a HistoryBackend running using the file at: "
768 << history_database_params.history_dir
769 << ". Tests have to make sure that HistoryBackend destruction is "
770 "complete using SetOnBackendDestroyTask() or other flush mechanisms "
771 "before creating a new HistoryBackend that uses the same directory.";
772
773 HistoryPathsTracker::GetInstance()->AddPath(history_dir_);
774 #endif
775
776 base::FilePath history_name = history_dir_.Append(kHistoryFilename);
777 base::FilePath favicon_name = GetFaviconsFileName();
778
779 // Delete the old index database files which are no longer used.
780 DeleteFTSIndexDatabases();
781
782 // History database.
783 db_.reset(new HistoryDatabase(
784 history_database_params.download_interrupt_reason_none,
785 history_database_params.download_interrupt_reason_crash));
786
787 // Unretained to avoid a ref loop with db_.
788 db_->set_error_callback(base::BindRepeating(
789 &HistoryBackend::DatabaseErrorCallback, base::Unretained(this)));
790
791 db_diagnostics_.clear();
792 sql::InitStatus status = db_->Init(history_name);
793 switch (status) {
794 case sql::INIT_OK:
795 break;
796 case sql::INIT_FAILURE: {
797 // A null db_ will cause all calls on this object to notice this error
798 // and to not continue. If the error callback scheduled killing the
799 // database, the task it posted has not executed yet. Try killing the
800 // database now before we close it.
801 bool kill_db = scheduled_kill_db_;
802 if (kill_db)
803 KillHistoryDatabase();
804
805 // The frequency of this UMA will indicate how often history
806 // initialization fails.
807 UMA_HISTOGRAM_BOOLEAN("History.AttemptedToFixProfileError", kill_db);
808 FALLTHROUGH;
809 }
810 case sql::INIT_TOO_NEW: {
811 db_diagnostics_ += sql::GetCorruptFileDiagnosticsInfo(history_name);
812 delegate_->NotifyProfileError(status, db_diagnostics_);
813 db_.reset();
814 return;
815 }
816 default:
817 NOTREACHED();
818 }
819
820 // Fill the in-memory database and send it back to the history service on the
821 // main thread.
822 {
823 std::unique_ptr<InMemoryHistoryBackend> mem_backend(
824 new InMemoryHistoryBackend);
825 if (mem_backend->Init(history_name))
826 delegate_->SetInMemoryBackend(std::move(mem_backend));
827 }
828 db_->BeginExclusiveMode(); // Must be after the mem backend read the data.
829
830 // Favicon database.
831 favicon_backend_ = favicon::FaviconBackend::Create(favicon_name, this);
832 // Unlike the main database, we don't error out if the favicon database can't
833 // be created. Generally, this shouldn't happen since the favicon and main
834 // database versions should be in sync. We'll just continue without favicons
835 // in this case or any other error.
836
837 // Generate the history and favicon database metrics only after performing
838 // any migration work.
839 if (base::RandInt(1, 100) == 50) {
840 // Only do this computation sometimes since it can be expensive.
841 db_->ComputeDatabaseMetrics(history_name);
842 }
843
844 favicon::FaviconDatabase* favicon_db_ptr =
845 favicon_backend_ ? favicon_backend_->db() : nullptr;
846
847 expirer_.SetDatabases(db_.get(), favicon_db_ptr);
848
849 // Open the long-running transaction.
850 db_->BeginTransaction();
851
852 // Get the first item in our database.
853 db_->GetStartDate(&first_recorded_time_);
854
855 // Start expiring old stuff.
856 expirer_.StartExpiringOldStuff(TimeDelta::FromDays(kExpireDaysThreshold));
857
858 #if defined(OS_ANDROID)
859 if (backend_client_) {
860 backend_client_->OnHistoryBackendInitialized(this, db_.get(),
861 favicon_db_ptr, history_dir_);
862 }
863 #endif
864
865 LOCAL_HISTOGRAM_TIMES("History.InitTime", TimeTicks::Now() - beginning_time);
866 }
867
OnMemoryPressure(base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level)868 void HistoryBackend::OnMemoryPressure(
869 base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level) {
870 // TODO(sebmarchand): Check if MEMORY_PRESSURE_LEVEL_MODERATE should also be
871 // ignored.
872 if (memory_pressure_level ==
873 base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE) {
874 return;
875 }
876 if (db_)
877 db_->TrimMemory();
878 if (favicon_backend_)
879 favicon_backend_->TrimMemory();
880 }
881
CloseAllDatabases()882 void HistoryBackend::CloseAllDatabases() {
883 if (db_) {
884 // Commit the long-running transaction.
885 db_->CommitTransaction();
886 db_.reset();
887 // Forget the first recorded time since the database is closed.
888 first_recorded_time_ = base::Time();
889 }
890 favicon_backend_.reset();
891 }
892
AddPageVisit(const GURL & url,Time time,VisitID referring_visit,ui::PageTransition transition,bool hidden,VisitSource visit_source,bool should_increment_typed_count,bool publicly_routable,base::Optional<base::string16> title)893 std::pair<URLID, VisitID> HistoryBackend::AddPageVisit(
894 const GURL& url,
895 Time time,
896 VisitID referring_visit,
897 ui::PageTransition transition,
898 bool hidden,
899 VisitSource visit_source,
900 bool should_increment_typed_count,
901 bool publicly_routable,
902 base::Optional<base::string16> title) {
903 // See if this URL is already in the DB.
904 URLRow url_info(url);
905 URLID url_id = db_->GetRowForURL(url, &url_info);
906 if (url_id) {
907 // Update of an existing row.
908 if (!ui::PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_RELOAD))
909 url_info.set_visit_count(url_info.visit_count() + 1);
910 if (should_increment_typed_count)
911 url_info.set_typed_count(url_info.typed_count() + 1);
912 if (url_info.last_visit() < time)
913 url_info.set_last_visit(time);
914 if (title)
915 url_info.set_title(title.value());
916
917 // Only allow un-hiding of pages, never hiding.
918 if (!hidden)
919 url_info.set_hidden(false);
920
921 db_->UpdateURLRow(url_id, url_info);
922 } else {
923 // Addition of a new row.
924 url_info.set_visit_count(1);
925 url_info.set_typed_count(should_increment_typed_count ? 1 : 0);
926 url_info.set_last_visit(time);
927 if (title)
928 url_info.set_title(title.value());
929 url_info.set_hidden(hidden);
930
931 url_id = db_->AddURL(url_info);
932 if (!url_id) {
933 NOTREACHED() << "Adding URL failed.";
934 return std::make_pair(0, 0);
935 }
936 url_info.set_id(url_id);
937 }
938
939 // Add the visit with the time to the database.
940 VisitRow visit_info(url_id, time, referring_visit, transition, 0,
941 should_increment_typed_count, publicly_routable);
942 VisitID visit_id = db_->AddVisit(&visit_info, visit_source);
943
944 if (visit_info.visit_time < first_recorded_time_)
945 first_recorded_time_ = visit_info.visit_time;
946
947 // Broadcast a notification of the visit.
948 if (visit_id) {
949 RedirectList redirects;
950 // TODO(meelapshah) Disabled due to potential PageCycler regression.
951 // Re-enable this.
952 // QueryRedirectsTo(url, &redirects);
953 NotifyURLVisited(transition, url_info, redirects, time);
954 } else {
955 DVLOG(0) << "Failed to build visit insert statement: "
956 << "url_id = " << url_id;
957 }
958
959 return std::make_pair(url_id, visit_id);
960 }
961
AddPagesWithDetails(const URLRows & urls,VisitSource visit_source)962 void HistoryBackend::AddPagesWithDetails(const URLRows& urls,
963 VisitSource visit_source) {
964 TRACE_EVENT0("browser", "HistoryBackend::AddPagesWithDetails");
965
966 if (!db_)
967 return;
968
969 URLRows changed_urls;
970 for (auto i = urls.begin(); i != urls.end(); ++i) {
971 DCHECK(!i->last_visit().is_null());
972
973 // As of M37, we no longer maintain an archived database, ignore old visits.
974 if (IsExpiredVisitTime(i->last_visit()))
975 continue;
976
977 URLRow existing_url;
978 URLID url_id = db_->GetRowForURL(i->url(), &existing_url);
979 if (!url_id) {
980 // Add the page if it doesn't exist.
981 url_id = db_->AddURL(*i);
982 if (!url_id) {
983 NOTREACHED() << "Could not add row to DB";
984 return;
985 }
986
987 changed_urls.push_back(*i);
988 changed_urls.back().set_id(url_id); // i->id_ is likely 0.
989 }
990
991 // Sync code manages the visits itself.
992 if (visit_source != SOURCE_SYNCED) {
993 // Make up a visit to correspond to the last visit to the page.
994 VisitRow visit_info(
995 url_id, i->last_visit(), /*referring_visit=*/0,
996 ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
997 ui::PAGE_TRANSITION_CHAIN_START |
998 ui::PAGE_TRANSITION_CHAIN_END),
999 /*segment_id=*/0, /*incremented_omnibox_typed_score=*/false,
1000 /*publicly_routable=*/false);
1001 if (!db_->AddVisit(&visit_info, visit_source)) {
1002 NOTREACHED() << "Adding visit failed.";
1003 return;
1004 }
1005
1006 if (visit_info.visit_time < first_recorded_time_)
1007 first_recorded_time_ = visit_info.visit_time;
1008 }
1009 }
1010
1011 // Broadcast a notification for typed URLs that have been modified. This
1012 // will be picked up by the in-memory URL database on the main thread.
1013 //
1014 // TODO(brettw) bug 1140015: Add an "add page" notification so the history
1015 // views can keep in sync.
1016 // HistoryService::AddPagesWithDetails() is only called from sync.
1017 NotifyURLsModified(changed_urls, UrlsModifiedReason::kSync);
1018 ScheduleCommit();
1019 }
1020
SetTypedURLSyncBridgeForTest(std::unique_ptr<TypedURLSyncBridge> bridge)1021 void HistoryBackend::SetTypedURLSyncBridgeForTest(
1022 std::unique_ptr<TypedURLSyncBridge> bridge) {
1023 typed_url_sync_bridge_ = std::move(bridge);
1024 }
1025
IsExpiredVisitTime(const base::Time & time)1026 bool HistoryBackend::IsExpiredVisitTime(const base::Time& time) {
1027 return time < expirer_.GetCurrentExpirationTime();
1028 }
1029
SetPageTitle(const GURL & url,const base::string16 & title)1030 void HistoryBackend::SetPageTitle(const GURL& url,
1031 const base::string16& title) {
1032 TRACE_EVENT0("browser", "HistoryBackend::SetPageTitle");
1033
1034 if (!db_)
1035 return;
1036
1037 // Search for recent redirects which should get the same title. We make a
1038 // dummy list containing the exact URL visited if there are no redirects so
1039 // the processing below can be the same.
1040 RedirectList dummy_list;
1041 RedirectList* redirects;
1042 auto iter = recent_redirects_.Get(url);
1043 if (iter != recent_redirects_.end()) {
1044 redirects = &iter->second;
1045
1046 // This redirect chain should have the destination URL as the last item.
1047 DCHECK(!redirects->empty());
1048 DCHECK_EQ(redirects->back(), url);
1049 } else {
1050 // No redirect chain stored, make up one containing the URL we want so we
1051 // can use the same logic below.
1052 dummy_list.push_back(url);
1053 redirects = &dummy_list;
1054 }
1055
1056 URLRows changed_urls;
1057 for (size_t i = 0; i < redirects->size(); i++) {
1058 URLRow row;
1059 URLID row_id = db_->GetRowForURL(redirects->at(i), &row);
1060 if (row_id && row.title() != title) {
1061 row.set_title(title);
1062 db_->UpdateURLRow(row_id, row);
1063 changed_urls.push_back(row);
1064 }
1065 }
1066
1067 // Broadcast notifications for any URLs that have changed. This will
1068 // update the in-memory database and the InMemoryURLIndex.
1069 if (!changed_urls.empty()) {
1070 NotifyURLsModified(changed_urls, UrlsModifiedReason::kTitleChanged);
1071 ScheduleCommit();
1072 }
1073 }
1074
AddPageNoVisitForBookmark(const GURL & url,const base::string16 & title)1075 void HistoryBackend::AddPageNoVisitForBookmark(const GURL& url,
1076 const base::string16& title) {
1077 TRACE_EVENT0("browser", "HistoryBackend::AddPageNoVisitForBookmark");
1078
1079 if (!db_)
1080 return;
1081
1082 URLRow url_info(url);
1083 URLID url_id = db_->GetRowForURL(url, &url_info);
1084 if (url_id) {
1085 // URL is already known, nothing to do.
1086 return;
1087 }
1088
1089 if (!title.empty()) {
1090 url_info.set_title(title);
1091 } else {
1092 url_info.set_title(base::UTF8ToUTF16(url.spec()));
1093 }
1094
1095 url_info.set_last_visit(Time::Now());
1096 // Mark the page hidden. If the user types it in, it'll unhide.
1097 url_info.set_hidden(true);
1098
1099 db_->AddURL(url_info);
1100 }
1101
GetAllTypedURLs(URLRows * urls)1102 bool HistoryBackend::GetAllTypedURLs(URLRows* urls) {
1103 DCHECK(urls);
1104 if (!db_)
1105 return false;
1106 std::vector<URLID> url_ids;
1107 if (!db_->GetAllURLIDsForTransition(ui::PAGE_TRANSITION_TYPED, &url_ids))
1108 return false;
1109 urls->reserve(url_ids.size());
1110 for (const auto& url_id : url_ids) {
1111 URLRow url;
1112 if (!db_->GetURLRow(url_id, &url))
1113 return false;
1114 urls->push_back(url);
1115 }
1116 return true;
1117 }
1118
GetVisitsForURL(URLID id,VisitVector * visits)1119 bool HistoryBackend::GetVisitsForURL(URLID id, VisitVector* visits) {
1120 if (db_)
1121 return db_->GetVisitsForURL(id, visits);
1122 return false;
1123 }
1124
GetMostRecentVisitsForURL(URLID id,int max_visits,VisitVector * visits)1125 bool HistoryBackend::GetMostRecentVisitsForURL(URLID id,
1126 int max_visits,
1127 VisitVector* visits) {
1128 if (db_)
1129 return db_->GetMostRecentVisitsForURL(id, max_visits, visits);
1130 return false;
1131 }
1132
UpdateURLs(const URLRows & urls)1133 size_t HistoryBackend::UpdateURLs(const URLRows& urls) {
1134 if (!db_)
1135 return 0;
1136
1137 URLRows changed_urls;
1138 for (auto it = urls.begin(); it != urls.end(); ++it) {
1139 DCHECK(it->id());
1140 if (db_->UpdateURLRow(it->id(), *it))
1141 changed_urls.push_back(*it);
1142 }
1143
1144 // Broadcast notifications for any URLs that have actually been changed. This
1145 // will update the in-memory database and the InMemoryURLIndex.
1146 size_t num_updated_records = changed_urls.size();
1147 if (num_updated_records) {
1148 // HistoryService::UpdateURLs() is only called from sync.
1149 NotifyURLsModified(changed_urls, UrlsModifiedReason::kSync);
1150 ScheduleCommit();
1151 }
1152 return num_updated_records;
1153 }
1154
AddVisits(const GURL & url,const std::vector<VisitInfo> & visits,VisitSource visit_source)1155 bool HistoryBackend::AddVisits(const GURL& url,
1156 const std::vector<VisitInfo>& visits,
1157 VisitSource visit_source) {
1158 if (db_) {
1159 for (auto visit = visits.begin(); visit != visits.end(); ++visit) {
1160 if (!AddPageVisit(url, visit->first, 0, visit->second,
1161 !ui::PageTransitionIsMainFrame(visit->second),
1162 visit_source, IsTypedIncrement(visit->second),
1163 /*publicly_routable=*/false)
1164 .first) {
1165 return false;
1166 }
1167 }
1168 ScheduleCommit();
1169 return true;
1170 }
1171 return false;
1172 }
1173
RemoveVisits(const VisitVector & visits)1174 bool HistoryBackend::RemoveVisits(const VisitVector& visits) {
1175 if (!db_)
1176 return false;
1177
1178 expirer_.ExpireVisits(visits);
1179 ScheduleCommit();
1180 return true;
1181 }
1182
GetVisitsSource(const VisitVector & visits,VisitSourceMap * sources)1183 bool HistoryBackend::GetVisitsSource(const VisitVector& visits,
1184 VisitSourceMap* sources) {
1185 if (!db_)
1186 return false;
1187
1188 db_->GetVisitsSource(visits, sources);
1189 return true;
1190 }
1191
GetURL(const GURL & url,URLRow * url_row)1192 bool HistoryBackend::GetURL(const GURL& url, URLRow* url_row) {
1193 if (db_)
1194 return db_->GetRowForURL(url, url_row) != 0;
1195 return false;
1196 }
1197
GetURLByID(URLID url_id,URLRow * url_row)1198 bool HistoryBackend::GetURLByID(URLID url_id, URLRow* url_row) {
1199 if (db_)
1200 return db_->GetURLRow(url_id, url_row);
1201 return false;
1202 }
1203
QueryURL(const GURL & url,bool want_visits)1204 QueryURLResult HistoryBackend::QueryURL(const GURL& url, bool want_visits) {
1205 QueryURLResult result;
1206 result.success = db_ && db_->GetRowForURL(url, &result.row);
1207 // Optionally query the visits.
1208 if (result.success && want_visits)
1209 db_->GetVisitsForURL(result.row.id(), &result.visits);
1210 return result;
1211 }
1212
1213 base::WeakPtr<syncer::ModelTypeControllerDelegate>
GetTypedURLSyncControllerDelegate()1214 HistoryBackend::GetTypedURLSyncControllerDelegate() {
1215 DCHECK(typed_url_sync_bridge_);
1216 return typed_url_sync_bridge_->change_processor()->GetControllerDelegate();
1217 }
1218
1219 // Statistics ------------------------------------------------------------------
1220
GetHistoryCount(const Time & begin_time,const Time & end_time)1221 HistoryCountResult HistoryBackend::GetHistoryCount(const Time& begin_time,
1222 const Time& end_time) {
1223 int count = 0;
1224 return {db_ && db_->GetHistoryCount(begin_time, end_time, &count), count};
1225 }
1226
CountUniqueHostsVisitedLastMonth()1227 HistoryCountResult HistoryBackend::CountUniqueHostsVisitedLastMonth() {
1228 return {!!db_, db_ ? db_->CountUniqueHostsVisitedLastMonth() : 0};
1229 }
1230
GetDomainDiversity(base::Time report_time,int number_of_days_to_report,DomainMetricBitmaskType metric_type_bitmask)1231 DomainDiversityResults HistoryBackend::GetDomainDiversity(
1232 base::Time report_time,
1233 int number_of_days_to_report,
1234 DomainMetricBitmaskType metric_type_bitmask) {
1235 DCHECK_GE(number_of_days_to_report, 0);
1236 DomainDiversityResults result;
1237
1238 if (!db_)
1239 return result;
1240
1241 number_of_days_to_report =
1242 std::min(number_of_days_to_report, kDomainDiversityMaxBacktrackedDays);
1243
1244 base::Time current_midnight = report_time.LocalMidnight();
1245 SCOPED_UMA_HISTOGRAM_TIMER("History.DomainCountQueryTime");
1246
1247 for (int days_back = 0; days_back < number_of_days_to_report; ++days_back) {
1248 DomainMetricSet single_metric_set;
1249 single_metric_set.end_time = current_midnight;
1250
1251 if (metric_type_bitmask & kEnableLast1DayMetric) {
1252 base::Time last_midnight = MidnightNDaysLater(current_midnight, -1);
1253 single_metric_set.one_day_metric = DomainMetricCountType(
1254 db_->CountUniqueDomainsVisited(last_midnight, current_midnight),
1255 last_midnight);
1256 }
1257
1258 if (metric_type_bitmask & kEnableLast7DayMetric) {
1259 base::Time seven_midnights_ago = MidnightNDaysLater(current_midnight, -7);
1260 single_metric_set.seven_day_metric = DomainMetricCountType(
1261 db_->CountUniqueDomainsVisited(seven_midnights_ago, current_midnight),
1262 seven_midnights_ago);
1263 }
1264
1265 if (metric_type_bitmask & kEnableLast28DayMetric) {
1266 base::Time twenty_eight_midnights_ago =
1267 MidnightNDaysLater(current_midnight, -28);
1268 single_metric_set.twenty_eight_day_metric = DomainMetricCountType(
1269 db_->CountUniqueDomainsVisited(twenty_eight_midnights_ago,
1270 current_midnight),
1271 twenty_eight_midnights_ago);
1272 }
1273 result.push_back(single_metric_set);
1274
1275 current_midnight = MidnightNDaysLater(current_midnight, -1);
1276 }
1277
1278 return result;
1279 }
1280
GetLastVisitToHost(const GURL & host,base::Time begin_time,base::Time end_time)1281 HistoryLastVisitToHostResult HistoryBackend::GetLastVisitToHost(
1282 const GURL& host,
1283 base::Time begin_time,
1284 base::Time end_time) {
1285 base::Time last_visit;
1286 return {
1287 db_ && db_->GetLastVisitToHost(host, begin_time, end_time, &last_visit),
1288 last_visit};
1289 }
1290
1291 // Keyword visits --------------------------------------------------------------
1292
SetKeywordSearchTermsForURL(const GURL & url,KeywordID keyword_id,const base::string16 & term)1293 void HistoryBackend::SetKeywordSearchTermsForURL(const GURL& url,
1294 KeywordID keyword_id,
1295 const base::string16& term) {
1296 TRACE_EVENT0("browser", "HistoryBackend::SetKeywordSearchTermsForURL");
1297
1298 if (!db_)
1299 return;
1300
1301 // Get the ID for this URL.
1302 URLRow row;
1303 if (!db_->GetRowForURL(url, &row)) {
1304 // There is a small possibility the url was deleted before the keyword
1305 // was added. Ignore the request.
1306 return;
1307 }
1308
1309 db_->SetKeywordSearchTermsForURL(row.id(), keyword_id, term);
1310 delegate_->NotifyKeywordSearchTermUpdated(row, keyword_id, term);
1311
1312 ScheduleCommit();
1313 }
1314
DeleteAllSearchTermsForKeyword(KeywordID keyword_id)1315 void HistoryBackend::DeleteAllSearchTermsForKeyword(KeywordID keyword_id) {
1316 TRACE_EVENT0("browser", "HistoryBackend::DeleteAllSearchTermsForKeyword");
1317
1318 if (!db_)
1319 return;
1320
1321 db_->DeleteAllSearchTermsForKeyword(keyword_id);
1322 ScheduleCommit();
1323 }
1324
DeleteKeywordSearchTermForURL(const GURL & url)1325 void HistoryBackend::DeleteKeywordSearchTermForURL(const GURL& url) {
1326 TRACE_EVENT0("browser", "HistoryBackend::DeleteKeywordSearchTermForURL");
1327
1328 if (!db_)
1329 return;
1330
1331 URLID url_id = db_->GetRowForURL(url, nullptr);
1332 if (!url_id)
1333 return;
1334 db_->DeleteKeywordSearchTermForURL(url_id);
1335 delegate_->NotifyKeywordSearchTermDeleted(url_id);
1336
1337 ScheduleCommit();
1338 }
1339
DeleteMatchingURLsForKeyword(KeywordID keyword_id,const base::string16 & term)1340 void HistoryBackend::DeleteMatchingURLsForKeyword(KeywordID keyword_id,
1341 const base::string16& term) {
1342 TRACE_EVENT0("browser", "HistoryBackend::DeleteMatchingURLsForKeyword");
1343
1344 if (!db_)
1345 return;
1346
1347 std::vector<KeywordSearchTermRow> rows;
1348 if (db_->GetKeywordSearchTermRows(term, &rows)) {
1349 std::vector<GURL> items_to_delete;
1350 URLRow row;
1351 for (auto it = rows.begin(); it != rows.end(); ++it) {
1352 if ((it->keyword_id == keyword_id) && db_->GetURLRow(it->url_id, &row))
1353 items_to_delete.push_back(row.url());
1354 }
1355 DeleteURLs(items_to_delete);
1356 }
1357 }
1358
1359 // Observers -------------------------------------------------------------------
1360
AddObserver(HistoryBackendObserver * observer)1361 void HistoryBackend::AddObserver(HistoryBackendObserver* observer) {
1362 observers_.AddObserver(observer);
1363 }
1364
RemoveObserver(HistoryBackendObserver * observer)1365 void HistoryBackend::RemoveObserver(HistoryBackendObserver* observer) {
1366 observers_.RemoveObserver(observer);
1367 }
1368
1369 // Downloads -------------------------------------------------------------------
1370
GetNextDownloadId()1371 uint32_t HistoryBackend::GetNextDownloadId() {
1372 return db_ ? db_->GetNextDownloadId() : kInvalidDownloadId;
1373 }
1374
1375 // Get all the download entries from the database.
QueryDownloads()1376 std::vector<DownloadRow> HistoryBackend::QueryDownloads() {
1377 std::vector<DownloadRow> rows;
1378 if (db_)
1379 db_->QueryDownloads(&rows);
1380 return rows;
1381 }
1382
1383 // Update a particular download entry.
UpdateDownload(const DownloadRow & data,bool should_commit_immediately)1384 void HistoryBackend::UpdateDownload(const DownloadRow& data,
1385 bool should_commit_immediately) {
1386 TRACE_EVENT0("browser", "HistoryBackend::UpdateDownload");
1387 if (!db_)
1388 return;
1389 db_->UpdateDownload(data);
1390 if (should_commit_immediately)
1391 Commit();
1392 else
1393 ScheduleCommit();
1394 }
1395
CreateDownload(const DownloadRow & history_info)1396 bool HistoryBackend::CreateDownload(const DownloadRow& history_info) {
1397 TRACE_EVENT0("browser", "HistoryBackend::CreateDownload");
1398 if (!db_)
1399 return false;
1400 bool success = db_->CreateDownload(history_info);
1401 #if defined(OS_ANDROID)
1402 // On android, browser process can get easily killed. Download will no longer
1403 // be able to resume and the temporary file will linger forever if the
1404 // download is not committed before that. Do the commit right away to avoid
1405 // uncommitted download entry if browser is killed.
1406 Commit();
1407 #else
1408 ScheduleCommit();
1409 #endif
1410 return success;
1411 }
1412
RemoveDownloads(const std::set<uint32_t> & ids)1413 void HistoryBackend::RemoveDownloads(const std::set<uint32_t>& ids) {
1414 TRACE_EVENT0("browser", "HistoryBackend::RemoveDownloads");
1415 if (!db_)
1416 return;
1417 size_t downloads_count_before = db_->CountDownloads();
1418 // HistoryBackend uses a long-running Transaction that is committed
1419 // periodically, so this loop doesn't actually hit the disk too hard.
1420 for (auto it = ids.begin(); it != ids.end(); ++it) {
1421 db_->RemoveDownload(*it);
1422 }
1423 ScheduleCommit();
1424 size_t downloads_count_after = db_->CountDownloads();
1425
1426 DCHECK_LE(downloads_count_after, downloads_count_before);
1427 if (downloads_count_after > downloads_count_before)
1428 return;
1429 size_t num_downloads_deleted = downloads_count_before - downloads_count_after;
1430 DCHECK_GE(ids.size(), num_downloads_deleted);
1431 }
1432
QueryHistory(const base::string16 & text_query,const QueryOptions & options)1433 QueryResults HistoryBackend::QueryHistory(const base::string16& text_query,
1434 const QueryOptions& options) {
1435 QueryResults query_results;
1436 base::TimeTicks beginning_time = base::TimeTicks::Now();
1437 if (db_) {
1438 if (text_query.empty()) {
1439 // Basic history query for the main database.
1440 QueryHistoryBasic(options, &query_results);
1441 } else {
1442 // Text history query.
1443 QueryHistoryText(text_query, options, &query_results);
1444 }
1445 }
1446 UMA_HISTOGRAM_TIMES("History.QueryHistory",
1447 TimeTicks::Now() - beginning_time);
1448 return query_results;
1449 }
1450
1451 // Basic time-based querying of history.
QueryHistoryBasic(const QueryOptions & options,QueryResults * result)1452 void HistoryBackend::QueryHistoryBasic(const QueryOptions& options,
1453 QueryResults* result) {
1454 // First get all visits.
1455 VisitVector visits;
1456 bool has_more_results = db_->GetVisibleVisitsInRange(options, &visits);
1457 DCHECK_LE(static_cast<int>(visits.size()), options.EffectiveMaxCount());
1458
1459 // Now add them and the URL rows to the results.
1460 std::vector<URLResult> matching_results;
1461 URLResult url_result;
1462 for (size_t i = 0; i < visits.size(); i++) {
1463 const VisitRow visit = visits[i];
1464
1465 // Add a result row for this visit, get the URL info from the DB.
1466 if (!db_->GetURLRow(visit.url_id, &url_result)) {
1467 DVLOG(0) << "Failed to get id " << visit.url_id << " from history.urls.";
1468 continue; // DB out of sync and URL doesn't exist, try to recover.
1469 }
1470
1471 if (!url_result.url().is_valid()) {
1472 DVLOG(0) << "Got invalid URL from history.urls with id " << visit.url_id
1473 << ": " << url_result.url().possibly_invalid_spec();
1474 continue; // Don't report invalid URLs in case of corruption.
1475 }
1476
1477 url_result.set_visit_time(visit.visit_time);
1478 url_result.set_publicly_routable(visit.publicly_routable);
1479
1480 // Set whether the visit was blocked for a managed user by looking at the
1481 // transition type.
1482 url_result.set_blocked_visit(
1483 (visit.transition & ui::PAGE_TRANSITION_BLOCKED) != 0);
1484
1485 // We don't set any of the query-specific parts of the URLResult, since
1486 // snippets and stuff don't apply to basic querying.
1487 matching_results.push_back(std::move(url_result));
1488 }
1489 result->SetURLResults(std::move(matching_results));
1490
1491 if (!has_more_results && options.begin_time <= first_recorded_time_)
1492 result->set_reached_beginning(true);
1493 }
1494
1495 // Text-based querying of history.
QueryHistoryText(const base::string16 & text_query,const QueryOptions & options,QueryResults * result)1496 void HistoryBackend::QueryHistoryText(const base::string16& text_query,
1497 const QueryOptions& options,
1498 QueryResults* result) {
1499 URLRows text_matches;
1500 db_->GetTextMatchesWithAlgorithm(text_query, options.matching_algorithm,
1501 &text_matches);
1502
1503 std::vector<URLResult> matching_visits;
1504 VisitVector visits; // Declare outside loop to prevent re-construction.
1505 for (size_t i = 0; i < text_matches.size(); i++) {
1506 const URLRow& text_match = text_matches[i];
1507 // Get all visits for given URL match.
1508 db_->GetVisibleVisitsForURL(text_match.id(), options, &visits);
1509 for (size_t j = 0; j < visits.size(); j++) {
1510 URLResult url_result(text_match);
1511 url_result.set_visit_time(visits[j].visit_time);
1512 url_result.set_publicly_routable(visits[j].publicly_routable);
1513 matching_visits.push_back(url_result);
1514 }
1515 }
1516
1517 std::sort(matching_visits.begin(), matching_visits.end(),
1518 URLResult::CompareVisitTime);
1519
1520 size_t max_results = options.max_count == 0
1521 ? std::numeric_limits<size_t>::max()
1522 : static_cast<int>(options.max_count);
1523 bool has_more_results = false;
1524 if (matching_visits.size() > max_results) {
1525 has_more_results = true;
1526 matching_visits.resize(max_results);
1527 }
1528 result->SetURLResults(std::move(matching_visits));
1529
1530 if (!has_more_results && options.begin_time <= first_recorded_time_)
1531 result->set_reached_beginning(true);
1532 }
1533
QueryRedirectsFrom(const GURL & from_url)1534 RedirectList HistoryBackend::QueryRedirectsFrom(const GURL& from_url) {
1535 if (!db_)
1536 return {};
1537
1538 URLID from_url_id = db_->GetRowForURL(from_url, nullptr);
1539 VisitID cur_visit = db_->GetMostRecentVisitForURL(from_url_id, nullptr);
1540 if (!cur_visit)
1541 return {}; // No visits for URL.
1542
1543 RedirectList redirects;
1544 GetRedirectsFromSpecificVisit(cur_visit, &redirects);
1545 return redirects;
1546 }
1547
QueryRedirectsTo(const GURL & to_url)1548 RedirectList HistoryBackend::QueryRedirectsTo(const GURL& to_url) {
1549 if (!db_)
1550 return {};
1551
1552 URLID to_url_id = db_->GetRowForURL(to_url, nullptr);
1553 VisitID cur_visit = db_->GetMostRecentVisitForURL(to_url_id, nullptr);
1554 if (!cur_visit)
1555 return {}; // No visits for URL.
1556
1557 RedirectList redirects;
1558 GetRedirectsToSpecificVisit(cur_visit, &redirects);
1559 return redirects;
1560 }
1561
GetVisibleVisitCountToHost(const GURL & url)1562 VisibleVisitCountToHostResult HistoryBackend::GetVisibleVisitCountToHost(
1563 const GURL& url) {
1564 VisibleVisitCountToHostResult result;
1565 result.success = db_ && db_->GetVisibleVisitCountToHost(url, &result.count,
1566 &result.first_visit);
1567 return result;
1568 }
1569
QueryMostVisitedURLs(int result_count,int days_back)1570 MostVisitedURLList HistoryBackend::QueryMostVisitedURLs(int result_count,
1571 int days_back) {
1572 if (!db_)
1573 return {};
1574
1575 base::TimeTicks begin_time = base::TimeTicks::Now();
1576
1577 auto url_filter =
1578 backend_client_
1579 ? base::BindRepeating(&HistoryBackendClient::IsWebSafe,
1580 base::Unretained(backend_client_.get()))
1581 : base::NullCallback();
1582 std::vector<std::unique_ptr<PageUsageData>> data = db_->QuerySegmentUsage(
1583 base::Time::Now() - base::TimeDelta::FromDays(days_back), result_count,
1584 url_filter);
1585
1586 MostVisitedURLList result;
1587 for (const std::unique_ptr<PageUsageData>& current_data : data)
1588 result.emplace_back(current_data->GetURL(), current_data->GetTitle());
1589
1590 UMA_HISTOGRAM_TIMES("History.QueryMostVisitedURLsTime",
1591 base::TimeTicks::Now() - begin_time);
1592
1593 return result;
1594 }
1595
GetRedirectsFromSpecificVisit(VisitID cur_visit,RedirectList * redirects)1596 void HistoryBackend::GetRedirectsFromSpecificVisit(VisitID cur_visit,
1597 RedirectList* redirects) {
1598 // Follow any redirects from the given visit and add them to the list.
1599 // It *should* be impossible to get a circular chain here, but we check
1600 // just in case to avoid infinite loops.
1601 GURL cur_url;
1602 std::set<VisitID> visit_set;
1603 visit_set.insert(cur_visit);
1604 while (db_->GetRedirectFromVisit(cur_visit, &cur_visit, &cur_url)) {
1605 if (visit_set.find(cur_visit) != visit_set.end()) {
1606 NOTREACHED() << "Loop in visit chain, giving up";
1607 return;
1608 }
1609 visit_set.insert(cur_visit);
1610 redirects->push_back(cur_url);
1611 }
1612 }
1613
GetRedirectsToSpecificVisit(VisitID cur_visit,RedirectList * redirects)1614 void HistoryBackend::GetRedirectsToSpecificVisit(VisitID cur_visit,
1615 RedirectList* redirects) {
1616 // Follow redirects going to cur_visit. These are added to |redirects| in
1617 // the order they are found. If a redirect chain looks like A -> B -> C and
1618 // |cur_visit| = C, redirects will be {B, A} in that order.
1619 if (!db_)
1620 return;
1621
1622 GURL cur_url;
1623 std::set<VisitID> visit_set;
1624 visit_set.insert(cur_visit);
1625 while (db_->GetRedirectToVisit(cur_visit, &cur_visit, &cur_url)) {
1626 if (visit_set.find(cur_visit) != visit_set.end()) {
1627 NOTREACHED() << "Loop in visit chain, giving up";
1628 return;
1629 }
1630 visit_set.insert(cur_visit);
1631 redirects->push_back(cur_url);
1632 }
1633 }
1634
ScheduleAutocomplete(base::OnceCallback<void (HistoryBackend *,URLDatabase *)> callback)1635 void HistoryBackend::ScheduleAutocomplete(
1636 base::OnceCallback<void(HistoryBackend*, URLDatabase*)> callback) {
1637 std::move(callback).Run(this, db_.get());
1638 }
1639
DeleteFTSIndexDatabases()1640 void HistoryBackend::DeleteFTSIndexDatabases() {
1641 // Find files on disk matching the text databases file pattern so we can
1642 // quickly test for and delete them.
1643 base::FilePath::StringType filepattern = FILE_PATH_LITERAL("History Index *");
1644 base::FileEnumerator enumerator(history_dir_, false,
1645 base::FileEnumerator::FILES, filepattern);
1646 int num_databases_deleted = 0;
1647 base::FilePath current_file;
1648 while (!(current_file = enumerator.Next()).empty()) {
1649 if (sql::Database::Delete(current_file))
1650 num_databases_deleted++;
1651 }
1652 UMA_HISTOGRAM_COUNTS_1M("History.DeleteFTSIndexDatabases",
1653 num_databases_deleted);
1654 }
1655
GetFavicon(const GURL & icon_url,favicon_base::IconType icon_type,const std::vector<int> & desired_sizes)1656 std::vector<favicon_base::FaviconRawBitmapResult> HistoryBackend::GetFavicon(
1657 const GURL& icon_url,
1658 favicon_base::IconType icon_type,
1659 const std::vector<int>& desired_sizes) {
1660 return UpdateFaviconMappingsAndFetch({}, icon_url, icon_type, desired_sizes);
1661 }
1662
GetLargestFaviconForURL(const GURL & page_url,const std::vector<favicon_base::IconTypeSet> & icon_types_list,int minimum_size_in_pixels)1663 favicon_base::FaviconRawBitmapResult HistoryBackend::GetLargestFaviconForURL(
1664 const GURL& page_url,
1665 const std::vector<favicon_base::IconTypeSet>& icon_types_list,
1666 int minimum_size_in_pixels) {
1667 if (!db_ || !favicon_backend_)
1668 return {};
1669
1670 return favicon_backend_->GetLargestFaviconForUrl(page_url, icon_types_list,
1671 minimum_size_in_pixels);
1672 }
1673
1674 std::vector<favicon_base::FaviconRawBitmapResult>
GetFaviconsForURL(const GURL & page_url,const favicon_base::IconTypeSet & icon_types,const std::vector<int> & desired_sizes,bool fallback_to_host)1675 HistoryBackend::GetFaviconsForURL(const GURL& page_url,
1676 const favicon_base::IconTypeSet& icon_types,
1677 const std::vector<int>& desired_sizes,
1678 bool fallback_to_host) {
1679 if (!favicon_backend_)
1680 return {};
1681 return favicon_backend_->GetFaviconsForUrl(page_url, icon_types,
1682 desired_sizes, fallback_to_host);
1683 }
1684
1685 std::vector<favicon_base::FaviconRawBitmapResult>
GetFaviconForID(favicon_base::FaviconID favicon_id,int desired_size)1686 HistoryBackend::GetFaviconForID(favicon_base::FaviconID favicon_id,
1687 int desired_size) {
1688 if (!favicon_backend_)
1689 return {};
1690 return favicon_backend_->GetFaviconForId(favicon_id, desired_size);
1691 }
1692
1693 std::vector<favicon_base::FaviconRawBitmapResult>
UpdateFaviconMappingsAndFetch(const base::flat_set<GURL> & page_urls,const GURL & icon_url,favicon_base::IconType icon_type,const std::vector<int> & desired_sizes)1694 HistoryBackend::UpdateFaviconMappingsAndFetch(
1695 const base::flat_set<GURL>& page_urls,
1696 const GURL& icon_url,
1697 favicon_base::IconType icon_type,
1698 const std::vector<int>& desired_sizes) {
1699 if (!favicon_backend_)
1700 return {};
1701 auto result = favicon_backend_->UpdateFaviconMappingsAndFetch(
1702 page_urls, icon_url, icon_type, desired_sizes);
1703 if (!result.updated_page_urls.empty()) {
1704 for (auto& page_url : result.updated_page_urls)
1705 SendFaviconChangedNotificationForPageAndRedirects(page_url);
1706 ScheduleCommit();
1707 }
1708 return result.bitmap_results;
1709 }
1710
DeleteFaviconMappings(const base::flat_set<GURL> & page_urls,favicon_base::IconType icon_type)1711 void HistoryBackend::DeleteFaviconMappings(
1712 const base::flat_set<GURL>& page_urls,
1713 favicon_base::IconType icon_type) {
1714 if (!favicon_backend_ || !db_)
1715 return;
1716
1717 auto deleted_page_urls =
1718 favicon_backend_->DeleteFaviconMappings(page_urls, icon_type);
1719 for (auto& deleted_page_url : deleted_page_urls)
1720 SendFaviconChangedNotificationForPageAndRedirects(deleted_page_url);
1721 if (!deleted_page_urls.empty())
1722 ScheduleCommit();
1723 }
1724
MergeFavicon(const GURL & page_url,const GURL & icon_url,favicon_base::IconType icon_type,scoped_refptr<base::RefCountedMemory> bitmap_data,const gfx::Size & pixel_size)1725 void HistoryBackend::MergeFavicon(
1726 const GURL& page_url,
1727 const GURL& icon_url,
1728 favicon_base::IconType icon_type,
1729 scoped_refptr<base::RefCountedMemory> bitmap_data,
1730 const gfx::Size& pixel_size) {
1731 if (!favicon_backend_ || !db_)
1732 return;
1733
1734 favicon::MergeFaviconResult result = favicon_backend_->MergeFavicon(
1735 page_url, icon_url, icon_type, bitmap_data, pixel_size);
1736 if (result.did_page_to_icon_mapping_change)
1737 SendFaviconChangedNotificationForPageAndRedirects(page_url);
1738 if (result.did_icon_change)
1739 SendFaviconChangedNotificationForIconURL(icon_url);
1740 ScheduleCommit();
1741 }
1742
SetFavicons(const base::flat_set<GURL> & page_urls,favicon_base::IconType icon_type,const GURL & icon_url,const std::vector<SkBitmap> & bitmaps)1743 void HistoryBackend::SetFavicons(const base::flat_set<GURL>& page_urls,
1744 favicon_base::IconType icon_type,
1745 const GURL& icon_url,
1746 const std::vector<SkBitmap>& bitmaps) {
1747 if (!favicon_backend_)
1748 return;
1749
1750 ProcessSetFaviconsResult(
1751 favicon_backend_->SetFavicons(page_urls, icon_type, icon_url, bitmaps,
1752 FaviconBitmapType::ON_VISIT),
1753 icon_url);
1754 }
1755
CloneFaviconMappingsForPages(const GURL & page_url_to_read,const favicon_base::IconTypeSet & icon_types,const base::flat_set<GURL> & page_urls_to_write)1756 void HistoryBackend::CloneFaviconMappingsForPages(
1757 const GURL& page_url_to_read,
1758 const favicon_base::IconTypeSet& icon_types,
1759 const base::flat_set<GURL>& page_urls_to_write) {
1760 TRACE_EVENT0("browser", "HistoryBackend::CloneFaviconMappingsForPages");
1761
1762 if (!db_ || !favicon_backend_)
1763 return;
1764
1765 std::set<GURL> changed_urls = favicon_backend_->CloneFaviconMappingsForPages(
1766 page_url_to_read, icon_types, page_urls_to_write);
1767 if (changed_urls.empty())
1768 return;
1769
1770 ScheduleCommit();
1771 NotifyFaviconsChanged(changed_urls, GURL());
1772 }
1773
CanSetOnDemandFavicons(const GURL & page_url,favicon_base::IconType icon_type)1774 bool HistoryBackend::CanSetOnDemandFavicons(const GURL& page_url,
1775 favicon_base::IconType icon_type) {
1776 return favicon_backend_ && db_ &&
1777 favicon_backend_->CanSetOnDemandFavicons(page_url, icon_type);
1778 }
1779
SetOnDemandFavicons(const GURL & page_url,favicon_base::IconType icon_type,const GURL & icon_url,const std::vector<SkBitmap> & bitmaps)1780 bool HistoryBackend::SetOnDemandFavicons(const GURL& page_url,
1781 favicon_base::IconType icon_type,
1782 const GURL& icon_url,
1783 const std::vector<SkBitmap>& bitmaps) {
1784 if (!favicon_backend_ || !db_)
1785 return false;
1786
1787 return ProcessSetFaviconsResult(favicon_backend_->SetOnDemandFavicons(
1788 page_url, icon_type, icon_url, bitmaps),
1789 icon_url);
1790 }
1791
SetFaviconsOutOfDateForPage(const GURL & page_url)1792 void HistoryBackend::SetFaviconsOutOfDateForPage(const GURL& page_url) {
1793 if (favicon_backend_ &&
1794 favicon_backend_->SetFaviconsOutOfDateForPage(page_url)) {
1795 ScheduleCommit();
1796 }
1797 }
1798
TouchOnDemandFavicon(const GURL & icon_url)1799 void HistoryBackend::TouchOnDemandFavicon(const GURL& icon_url) {
1800 TRACE_EVENT0("browser", "HistoryBackend::TouchOnDemandFavicon");
1801
1802 if (!favicon_backend_)
1803 return;
1804 favicon_backend_->TouchOnDemandFavicon(icon_url);
1805 ScheduleCommit();
1806 }
1807
SetImportedFavicons(const favicon_base::FaviconUsageDataList & favicon_usage)1808 void HistoryBackend::SetImportedFavicons(
1809 const favicon_base::FaviconUsageDataList& favicon_usage) {
1810 TRACE_EVENT0("browser", "HistoryBackend::SetImportedFavicons");
1811
1812 if (!db_ || !favicon_backend_)
1813 return;
1814
1815 Time now = Time::Now();
1816
1817 // Track all URLs that had their favicons set or updated.
1818 std::set<GURL> favicons_changed;
1819
1820 favicon::FaviconDatabase* favicon_db = favicon_backend_->db();
1821 for (size_t i = 0; i < favicon_usage.size(); i++) {
1822 favicon_base::FaviconID favicon_id = favicon_db->GetFaviconIDForFaviconURL(
1823 favicon_usage[i].favicon_url, favicon_base::IconType::kFavicon);
1824 if (!favicon_id) {
1825 // This favicon doesn't exist yet, so we create it using the given data.
1826 // TODO(pkotwicz): Pass in real pixel size.
1827 favicon_id = favicon_db->AddFavicon(
1828 favicon_usage[i].favicon_url, favicon_base::IconType::kFavicon,
1829 new base::RefCountedBytes(favicon_usage[i].png_data),
1830 FaviconBitmapType::ON_VISIT, now, gfx::Size());
1831 }
1832
1833 // Save the mapping from all the URLs to the favicon.
1834 for (auto url = favicon_usage[i].urls.begin();
1835 url != favicon_usage[i].urls.end(); ++url) {
1836 URLRow url_row;
1837 if (!db_->GetRowForURL(*url, &url_row)) {
1838 // If the URL is present as a bookmark, add the url in history to
1839 // save the favicon mapping. This will match with what history db does
1840 // for regular bookmarked URLs with favicons - when history db is
1841 // cleaned, we keep an entry in the db with 0 visits as long as that
1842 // url is bookmarked. The same is applicable to the saved credential's
1843 // URLs.
1844 if (backend_client_ && backend_client_->IsPinnedURL(*url)) {
1845 URLRow url_info(*url);
1846 url_info.set_visit_count(0);
1847 url_info.set_typed_count(0);
1848 url_info.set_last_visit(base::Time());
1849 url_info.set_hidden(false);
1850 db_->AddURL(url_info);
1851 favicon_db->AddIconMapping(*url, favicon_id);
1852 favicons_changed.insert(*url);
1853 }
1854 } else {
1855 if (!favicon_db->GetIconMappingsForPageURL(
1856 *url, {favicon_base::IconType::kFavicon},
1857 /*mapping_data=*/nullptr)) {
1858 // URL is present in history, update the favicon *only* if it is not
1859 // set already.
1860 favicon_db->AddIconMapping(*url, favicon_id);
1861 favicons_changed.insert(*url);
1862 }
1863 }
1864 }
1865 }
1866
1867 if (!favicons_changed.empty()) {
1868 // Send the notification about the changed favicon URLs.
1869 NotifyFaviconsChanged(favicons_changed, GURL());
1870 }
1871 }
1872
GetCachedRecentRedirects(const GURL & page_url)1873 RedirectList HistoryBackend::GetCachedRecentRedirects(const GURL& page_url) {
1874 auto iter = recent_redirects_.Get(page_url);
1875 if (iter != recent_redirects_.end()) {
1876 // The redirect chain should have the destination URL as the last item.
1877 DCHECK(!iter->second.empty());
1878 DCHECK_EQ(iter->second.back(), page_url);
1879 return iter->second;
1880 }
1881 // No known redirects, construct mock redirect chain containing |page_url|.
1882 return RedirectList{page_url};
1883 }
1884
SendFaviconChangedNotificationForPageAndRedirects(const GURL & page_url)1885 void HistoryBackend::SendFaviconChangedNotificationForPageAndRedirects(
1886 const GURL& page_url) {
1887 RedirectList redirect_list = GetCachedRecentRedirects(page_url);
1888 if (!redirect_list.empty()) {
1889 std::set<GURL> favicons_changed(redirect_list.begin(), redirect_list.end());
1890 NotifyFaviconsChanged(favicons_changed, GURL());
1891 }
1892 }
1893
SendFaviconChangedNotificationForIconURL(const GURL & icon_url)1894 void HistoryBackend::SendFaviconChangedNotificationForIconURL(
1895 const GURL& icon_url) {
1896 NotifyFaviconsChanged(std::set<GURL>(), icon_url);
1897 }
1898
Commit()1899 void HistoryBackend::Commit() {
1900 if (!db_)
1901 return;
1902
1903 #if defined(OS_IOS)
1904 // Attempts to get the application running long enough to commit the database
1905 // transaction if it is currently being backgrounded.
1906 base::ios::ScopedCriticalAction scoped_critical_action(
1907 "HistoryBackend::Commit");
1908 #endif
1909
1910 // Note that a commit may not actually have been scheduled if a caller
1911 // explicitly calls this instead of using ScheduleCommit. Likewise, we
1912 // may reset the flag written by a pending commit. But this is OK! It
1913 // will merely cause extra commits (which is kind of the idea). We
1914 // could optimize more for this case (we may get two extra commits in
1915 // some cases) but it hasn't been important yet.
1916 CancelScheduledCommit();
1917
1918 db_->CommitTransaction();
1919 DCHECK_EQ(db_->transaction_nesting(), 0)
1920 << "Somebody left a transaction open";
1921 db_->BeginTransaction();
1922
1923 if (favicon_backend_)
1924 favicon_backend_->Commit();
1925 }
1926
ScheduleCommit()1927 void HistoryBackend::ScheduleCommit() {
1928 // Non-cancelled means there's an already scheduled commit. Note that
1929 // CancelableClosure starts cancelled with the default constructor.
1930 if (!scheduled_commit_.IsCancelled())
1931 return;
1932
1933 scheduled_commit_.Reset(
1934 base::BindOnce(&HistoryBackend::Commit, base::Unretained(this)));
1935
1936 task_runner_->PostDelayedTask(
1937 FROM_HERE, scheduled_commit_.callback(),
1938 base::TimeDelta::FromSeconds(kCommitIntervalSeconds));
1939 }
1940
CancelScheduledCommit()1941 void HistoryBackend::CancelScheduledCommit() {
1942 scheduled_commit_.Cancel();
1943 }
1944
ProcessDBTaskImpl()1945 void HistoryBackend::ProcessDBTaskImpl() {
1946 if (!db_) {
1947 // db went away, release all the refs.
1948 queued_history_db_tasks_.clear();
1949 return;
1950 }
1951
1952 // Remove any canceled tasks.
1953 while (!queued_history_db_tasks_.empty()) {
1954 QueuedHistoryDBTask* task = queued_history_db_tasks_.front().get();
1955 if (!task->is_canceled())
1956 break;
1957
1958 queued_history_db_tasks_.pop_front();
1959 }
1960 if (queued_history_db_tasks_.empty())
1961 return;
1962
1963 // Run the first task.
1964 std::unique_ptr<QueuedHistoryDBTask> task =
1965 std::move(queued_history_db_tasks_.front());
1966 queued_history_db_tasks_.pop_front();
1967 if (task->Run(this, db_.get())) {
1968 // The task is done, notify the callback.
1969 task->DoneRun();
1970 } else {
1971 // The task wants to run some more. Schedule it at the end of the current
1972 // tasks, and process it after an invoke later.
1973 queued_history_db_tasks_.push_back(std::move(task));
1974 task_runner_->PostTask(
1975 FROM_HERE, base::BindOnce(&HistoryBackend::ProcessDBTaskImpl, this));
1976 }
1977 }
1978
1979 ////////////////////////////////////////////////////////////////////////////////
1980 //
1981 // Generic operations
1982 //
1983 ////////////////////////////////////////////////////////////////////////////////
1984
DeleteURLs(const std::vector<GURL> & urls)1985 void HistoryBackend::DeleteURLs(const std::vector<GURL>& urls) {
1986 TRACE_EVENT0("browser", "HistoryBackend::DeleteURLs");
1987
1988 expirer_.DeleteURLs(urls, base::Time::Max());
1989
1990 db_->GetStartDate(&first_recorded_time_);
1991 // Force a commit, if the user is deleting something for privacy reasons, we
1992 // want to get it on disk ASAP.
1993 Commit();
1994 }
1995
DeleteURL(const GURL & url)1996 void HistoryBackend::DeleteURL(const GURL& url) {
1997 TRACE_EVENT0("browser", "HistoryBackend::DeleteURL");
1998
1999 expirer_.DeleteURL(url, base::Time::Max());
2000
2001 db_->GetStartDate(&first_recorded_time_);
2002 // Force a commit, if the user is deleting something for privacy reasons, we
2003 // want to get it on disk ASAP.
2004 Commit();
2005 }
2006
DeleteURLsUntil(const std::vector<std::pair<GURL,base::Time>> & urls_and_timestamps)2007 void HistoryBackend::DeleteURLsUntil(
2008 const std::vector<std::pair<GURL, base::Time>>& urls_and_timestamps) {
2009 TRACE_EVENT0("browser", "HistoryBackend::DeleteURLsUntil");
2010
2011 for (const auto& pair : urls_and_timestamps) {
2012 expirer_.DeleteURL(pair.first, pair.second);
2013 }
2014 db_->GetStartDate(&first_recorded_time_);
2015 // Force a commit, if the user is deleting something for privacy reasons, we
2016 // want to get it on disk ASAP.
2017 Commit();
2018 }
2019
ExpireHistoryBetween(const std::set<GURL> & restrict_urls,Time begin_time,Time end_time,bool user_initiated)2020 void HistoryBackend::ExpireHistoryBetween(const std::set<GURL>& restrict_urls,
2021 Time begin_time,
2022 Time end_time,
2023 bool user_initiated) {
2024 if (!db_)
2025 return;
2026
2027 if (begin_time.is_null() && (end_time.is_null() || end_time.is_max()) &&
2028 restrict_urls.empty()) {
2029 // Special case deleting all history so it can be faster and to reduce the
2030 // possibility of an information leak.
2031 DeleteAllHistory();
2032 } else {
2033 // Clearing parts of history, have the expirer do the depend
2034 expirer_.ExpireHistoryBetween(restrict_urls, begin_time, end_time,
2035 user_initiated);
2036
2037 // Force a commit, if the user is deleting something for privacy reasons,
2038 // we want to get it on disk ASAP.
2039 Commit();
2040 }
2041
2042 if (begin_time <= first_recorded_time_)
2043 db_->GetStartDate(&first_recorded_time_);
2044 }
2045
ExpireHistoryForTimes(const std::set<base::Time> & times,base::Time begin_time,base::Time end_time)2046 void HistoryBackend::ExpireHistoryForTimes(const std::set<base::Time>& times,
2047 base::Time begin_time,
2048 base::Time end_time) {
2049 if (times.empty() || !db_)
2050 return;
2051
2052 QueryOptions options;
2053 options.begin_time = begin_time;
2054 options.end_time = end_time;
2055 options.duplicate_policy = QueryOptions::KEEP_ALL_DUPLICATES;
2056 QueryResults results;
2057 QueryHistoryBasic(options, &results);
2058
2059 // 1st pass: find URLs that are visited at one of |times|.
2060 std::set<GURL> urls;
2061 for (size_t i = 0; i < results.size(); ++i) {
2062 if (times.count(results[i].visit_time()) > 0)
2063 urls.insert(results[i].url());
2064 }
2065 if (urls.empty())
2066 return;
2067
2068 // 2nd pass: collect all visit times of those URLs.
2069 std::vector<base::Time> times_to_expire;
2070 for (size_t i = 0; i < results.size(); ++i) {
2071 if (urls.count(results[i].url()))
2072 times_to_expire.push_back(results[i].visit_time());
2073 }
2074
2075 // Put the times in reverse chronological order and remove
2076 // duplicates (for expirer_.ExpireHistoryForTimes()).
2077 std::sort(times_to_expire.begin(), times_to_expire.end(),
2078 std::greater<base::Time>());
2079 times_to_expire.erase(
2080 std::unique(times_to_expire.begin(), times_to_expire.end()),
2081 times_to_expire.end());
2082
2083 // Expires by times and commit.
2084 DCHECK(!times_to_expire.empty());
2085 expirer_.ExpireHistoryForTimes(times_to_expire);
2086 Commit();
2087
2088 DCHECK_GE(times_to_expire.back(), first_recorded_time_);
2089 // Update |first_recorded_time_| if we expired it.
2090 if (times_to_expire.back() == first_recorded_time_)
2091 db_->GetStartDate(&first_recorded_time_);
2092 }
2093
ExpireHistory(const std::vector<ExpireHistoryArgs> & expire_list)2094 void HistoryBackend::ExpireHistory(
2095 const std::vector<ExpireHistoryArgs>& expire_list) {
2096 if (db_) {
2097 bool update_first_recorded_time = false;
2098
2099 for (auto it = expire_list.begin(); it != expire_list.end(); ++it) {
2100 expirer_.ExpireHistoryBetween(it->urls, it->begin_time, it->end_time,
2101 true);
2102
2103 if (it->begin_time < first_recorded_time_)
2104 update_first_recorded_time = true;
2105 }
2106 Commit();
2107
2108 // Update |first_recorded_time_| if any deletion might have affected it.
2109 if (update_first_recorded_time)
2110 db_->GetStartDate(&first_recorded_time_);
2111 }
2112 }
2113
ExpireHistoryBeforeForTesting(base::Time end_time)2114 void HistoryBackend::ExpireHistoryBeforeForTesting(base::Time end_time) {
2115 if (!db_)
2116 return;
2117
2118 expirer_.ExpireHistoryBeforeForTesting(end_time);
2119 }
2120
URLsNoLongerBookmarked(const std::set<GURL> & urls)2121 void HistoryBackend::URLsNoLongerBookmarked(const std::set<GURL>& urls) {
2122 TRACE_EVENT0("browser", "HistoryBackend::URLsNoLongerBookmarked");
2123
2124 if (!db_)
2125 return;
2126
2127 for (auto i = urls.begin(); i != urls.end(); ++i) {
2128 VisitVector visits;
2129 URLRow url_row;
2130 if (db_->GetRowForURL(*i, &url_row))
2131 db_->GetVisitsForURL(url_row.id(), &visits);
2132 // We need to call DeleteURL() even if the DB didn't contain this URL, so
2133 // that we can delete all associated icons in the case of deleting an
2134 // unvisited bookmarked URL.
2135 if (visits.empty())
2136 expirer_.DeleteURL(*i, base::Time::Max());
2137 }
2138 }
2139
DatabaseErrorCallback(int error,sql::Statement * stmt)2140 void HistoryBackend::DatabaseErrorCallback(int error, sql::Statement* stmt) {
2141 if (!scheduled_kill_db_ && sql::IsErrorCatastrophic(error)) {
2142 scheduled_kill_db_ = true;
2143
2144 db_diagnostics_ = db_->GetDiagnosticInfo(error, stmt);
2145
2146 // Don't just do the close/delete here, as we are being called by |db| and
2147 // that seems dangerous.
2148 // TODO(https://crbug.com/854258): It is also dangerous to kill the database
2149 // by a posted task: tasks that run before KillHistoryDatabase still can try
2150 // to use the broken database. Consider protecting against other tasks using
2151 // the DB or consider changing KillHistoryDatabase() to use RazeAndClose()
2152 // (then it can be cleared immediately).
2153 task_runner_->PostTask(
2154 FROM_HERE, base::BindOnce(&HistoryBackend::KillHistoryDatabase, this));
2155 }
2156 }
2157
KillHistoryDatabase()2158 void HistoryBackend::KillHistoryDatabase() {
2159 scheduled_kill_db_ = false;
2160 if (!db_)
2161 return;
2162
2163 // Notify SyncBridge about storage error. It will report failure to sync
2164 // engine and stop accepting remote updates.
2165 if (typed_url_sync_bridge_)
2166 typed_url_sync_bridge_->OnDatabaseError();
2167
2168 // Rollback transaction because Raze() cannot be called from within a
2169 // transaction.
2170 db_->RollbackTransaction();
2171 bool success = db_->Raze();
2172 UMA_HISTOGRAM_BOOLEAN("History.KillHistoryDatabaseResult", success);
2173
2174 // Release stashed embedder object before cleaning up the databases.
2175 supports_user_data_helper_.reset();
2176
2177 // The expirer keeps tabs on the active databases. Tell it about the
2178 // databases which will be closed.
2179 expirer_.SetDatabases(nullptr, nullptr);
2180
2181 // Reopen a new transaction for |db_| for the sake of CloseAllDatabases().
2182 db_->BeginTransaction();
2183 CloseAllDatabases();
2184 }
2185
GetUserData(const void * key) const2186 base::SupportsUserData::Data* HistoryBackend::GetUserData(
2187 const void* key) const {
2188 DCHECK(supports_user_data_helper_);
2189 return supports_user_data_helper_->GetUserData(key);
2190 }
2191
SetUserData(const void * key,std::unique_ptr<base::SupportsUserData::Data> data)2192 void HistoryBackend::SetUserData(
2193 const void* key,
2194 std::unique_ptr<base::SupportsUserData::Data> data) {
2195 DCHECK(supports_user_data_helper_);
2196 supports_user_data_helper_->SetUserData(key, std::move(data));
2197 }
2198
ProcessDBTask(std::unique_ptr<HistoryDBTask> task,scoped_refptr<base::SingleThreadTaskRunner> origin_loop,const base::CancelableTaskTracker::IsCanceledCallback & is_canceled)2199 void HistoryBackend::ProcessDBTask(
2200 std::unique_ptr<HistoryDBTask> task,
2201 scoped_refptr<base::SingleThreadTaskRunner> origin_loop,
2202 const base::CancelableTaskTracker::IsCanceledCallback& is_canceled) {
2203 TRACE_EVENT0("browser", "HistoryBackend::ProcessDBTask");
2204 bool scheduled = !queued_history_db_tasks_.empty();
2205 queued_history_db_tasks_.push_back(std::make_unique<QueuedHistoryDBTask>(
2206 std::move(task), origin_loop, is_canceled));
2207 if (!scheduled)
2208 ProcessDBTaskImpl();
2209 }
2210
NotifyFaviconsChanged(const std::set<GURL> & page_urls,const GURL & icon_url)2211 void HistoryBackend::NotifyFaviconsChanged(const std::set<GURL>& page_urls,
2212 const GURL& icon_url) {
2213 delegate_->NotifyFaviconsChanged(page_urls, icon_url);
2214 }
2215
NotifyURLVisited(ui::PageTransition transition,const URLRow & row,const RedirectList & redirects,base::Time visit_time)2216 void HistoryBackend::NotifyURLVisited(ui::PageTransition transition,
2217 const URLRow& row,
2218 const RedirectList& redirects,
2219 base::Time visit_time) {
2220 for (HistoryBackendObserver& observer : observers_)
2221 observer.OnURLVisited(this, transition, row, redirects, visit_time);
2222
2223 delegate_->NotifyURLVisited(transition, row, redirects, visit_time);
2224 }
2225
NotifyURLsModified(const URLRows & changed_urls,UrlsModifiedReason reason)2226 void HistoryBackend::NotifyURLsModified(const URLRows& changed_urls,
2227 UrlsModifiedReason reason) {
2228 for (HistoryBackendObserver& observer : observers_)
2229 observer.OnURLsModified(this, changed_urls,
2230 reason == UrlsModifiedReason::kExpired);
2231
2232 delegate_->NotifyURLsModified(changed_urls, reason);
2233 }
2234
NotifyURLsDeleted(DeletionInfo deletion_info)2235 void HistoryBackend::NotifyURLsDeleted(DeletionInfo deletion_info) {
2236 std::set<GURL> origins;
2237 for (const history::URLRow& row : deletion_info.deleted_rows())
2238 origins.insert(row.url().GetOrigin());
2239
2240 deletion_info.set_deleted_urls_origin_map(
2241 GetCountsAndLastVisitForOrigins(origins));
2242
2243 for (HistoryBackendObserver& observer : observers_) {
2244 observer.OnURLsDeleted(
2245 this, deletion_info.IsAllHistory(), deletion_info.is_from_expiration(),
2246 deletion_info.deleted_rows(), deletion_info.favicon_urls());
2247 }
2248
2249 delegate_->NotifyURLsDeleted(std::move(deletion_info));
2250 }
2251
2252 // Deleting --------------------------------------------------------------------
2253
DeleteAllHistory()2254 void HistoryBackend::DeleteAllHistory() {
2255 // Our approach to deleting all history is:
2256 // 1. Copy the pinned URLs and their dependencies to new tables with
2257 // temporary names.
2258 // 2. Delete the original tables. Since tables can not share pages, we know
2259 // that any data we don't want to keep is now in an unused page.
2260 // 3. Renaming the temporary tables to match the original.
2261 // 4. Vacuuming the database to delete the unused pages.
2262 //
2263 // Since we are likely to have very few pinned URLs and their dependencies
2264 // compared to all history, this is also much faster than just deleting from
2265 // the original tables directly.
2266
2267 // Get the pinned URLs.
2268 std::vector<URLAndTitle> pinned_url;
2269 if (backend_client_)
2270 pinned_url = backend_client_->GetPinnedURLs();
2271
2272 URLRows kept_url_rows;
2273 std::vector<GURL> starred_urls;
2274 for (URLAndTitle& url_and_title : pinned_url) {
2275 URLRow row;
2276 if (db_->GetRowForURL(url_and_title.url, &row)) {
2277 // Clear the last visit time so when we write these rows they are "clean."
2278 row.set_last_visit(Time());
2279 row.set_visit_count(0);
2280 row.set_typed_count(0);
2281 kept_url_rows.push_back(row);
2282 }
2283
2284 starred_urls.push_back(std::move(url_and_title.url));
2285 }
2286
2287 // Delete all cached favicons which are not used by the UI.
2288 if (!ClearAllFaviconHistory(starred_urls)) {
2289 LOG(ERROR) << "Favicon history could not be cleared";
2290 // We continue in this error case. If the user wants to delete their
2291 // history, we should delete as much as we can.
2292 }
2293
2294 // ClearAllMainHistory will change the IDs of the URLs in kept_urls.
2295 // Therefore, we clear the list afterwards to make sure nobody uses this
2296 // invalid data.
2297 if (!ClearAllMainHistory(kept_url_rows))
2298 LOG(ERROR) << "Main history could not be cleared";
2299 kept_url_rows.clear();
2300
2301 db_->GetStartDate(&first_recorded_time_);
2302
2303 // Send out the notification that history is cleared. The in-memory database
2304 // will pick this up and clear itself.
2305 NotifyURLsDeleted(DeletionInfo::ForAllHistory());
2306 }
2307
ClearAllFaviconHistory(const std::vector<GURL> & kept_urls)2308 bool HistoryBackend::ClearAllFaviconHistory(
2309 const std::vector<GURL>& kept_urls) {
2310 if (!favicon_backend_) {
2311 // When we have no reference to the favicon database, maybe there was an
2312 // error opening it. In this case, we just try to blow it away to try to
2313 // fix the error if it exists. This may fail, in which case either the
2314 // file doesn't exist or there's no more we can do.
2315 sql::Database::Delete(GetFaviconsFileName());
2316 return true;
2317 }
2318 if (!favicon_backend_->ClearAllExcept(kept_urls))
2319 return false;
2320
2321 #if defined(OS_ANDROID)
2322 // TODO(michaelbai): Add the unit test once AndroidProviderBackend is
2323 // available in HistoryBackend.
2324 db_->ClearAndroidURLRows();
2325 #endif
2326 return true;
2327 }
2328
ClearAllOnDemandFavicons()2329 void HistoryBackend::ClearAllOnDemandFavicons() {
2330 expirer_.ClearOldOnDemandFaviconsIfPossible(base::Time::Now());
2331 }
2332
ClearAllMainHistory(const URLRows & kept_urls)2333 bool HistoryBackend::ClearAllMainHistory(const URLRows& kept_urls) {
2334 // Create the duplicate URL table. We will copy the kept URLs into this.
2335 if (!db_->CreateTemporaryURLTable())
2336 return false;
2337
2338 // Insert the URLs into the temporary table.
2339 for (auto i = kept_urls.begin(); i != kept_urls.end(); ++i) {
2340 db_->AddTemporaryURL(*i);
2341 }
2342
2343 // Replace the original URL table with the temporary one.
2344 if (!db_->CommitTemporaryURLTable())
2345 return false;
2346
2347 // Delete the old tables and recreate them empty.
2348 db_->RecreateAllTablesButURL();
2349
2350 // Vacuum to reclaim the space from the dropped tables. This must be done
2351 // when there is no transaction open, and we assume that our long-running
2352 // transaction is currently open.
2353 db_->CommitTransaction();
2354 db_->Vacuum();
2355 db_->BeginTransaction();
2356 db_->GetStartDate(&first_recorded_time_);
2357
2358 return true;
2359 }
2360
GetCachedRecentRedirectsForPage(const GURL & page_url)2361 std::vector<GURL> HistoryBackend::GetCachedRecentRedirectsForPage(
2362 const GURL& page_url) {
2363 return GetCachedRecentRedirects(page_url);
2364 }
2365
ProcessSetFaviconsResult(const favicon::SetFaviconsResult & result,const GURL & icon_url)2366 bool HistoryBackend::ProcessSetFaviconsResult(
2367 const favicon::SetFaviconsResult& result,
2368 const GURL& icon_url) {
2369 if (!result.did_change_database())
2370 return false;
2371
2372 ScheduleCommit();
2373 if (result.did_update_bitmap)
2374 SendFaviconChangedNotificationForIconURL(icon_url);
2375 for (const GURL& page_url : result.updated_page_urls)
2376 SendFaviconChangedNotificationForPageAndRedirects(page_url);
2377 return true;
2378 }
2379
NotifyURLsModified(const URLRows & changed_urls,UrlsModifiedReason reason)2380 void HistoryBackend::Delegate::NotifyURLsModified(const URLRows& changed_urls,
2381 UrlsModifiedReason reason) {
2382 NotifyURLsModified(changed_urls);
2383 }
2384
2385 } // namespace history
2386