history_service.cc revision cedac228d2dd51db4b79ea1e72c7f249408ee061
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// The history system runs on a background thread so that potentially slow 6// database operations don't delay the browser. This backend processing is 7// represented by HistoryBackend. The HistoryService's job is to dispatch to 8// that thread. 9// 10// Main thread History thread 11// ----------- -------------- 12// HistoryService <----------------> HistoryBackend 13// -> HistoryDatabase 14// -> SQLite connection to History 15// -> ArchivedDatabase 16// -> SQLite connection to Archived History 17// -> ThumbnailDatabase 18// -> SQLite connection to Thumbnails 19// (and favicons) 20 21#include "chrome/browser/history/history_service.h" 22 23#include "base/bind_helpers.h" 24#include "base/callback.h" 25#include "base/command_line.h" 26#include "base/compiler_specific.h" 27#include "base/location.h" 28#include "base/memory/ref_counted.h" 29#include "base/message_loop/message_loop.h" 30#include "base/path_service.h" 31#include "base/prefs/pref_service.h" 32#include "base/thread_task_runner_handle.h" 33#include "base/threading/thread.h" 34#include "base/time/time.h" 35#include "chrome/browser/autocomplete/history_url_provider.h" 36#include "chrome/browser/bookmarks/bookmark_model_factory.h" 37#include "chrome/browser/browser_process.h" 38#include "chrome/browser/chrome_notification_types.h" 39#include "chrome/browser/history/download_row.h" 40#include "chrome/browser/history/history_backend.h" 41#include "chrome/browser/history/history_notifications.h" 42#include "chrome/browser/history/history_types.h" 43#include "chrome/browser/history/in_memory_database.h" 44#include "chrome/browser/history/in_memory_history_backend.h" 45#include "chrome/browser/history/in_memory_url_index.h" 46#include "chrome/browser/history/top_sites.h" 47#include "chrome/browser/history/visit_database.h" 48#include "chrome/browser/history/visit_filter.h" 49#include "chrome/browser/history/web_history_service.h" 50#include "chrome/browser/history/web_history_service_factory.h" 51#include "chrome/browser/profiles/profile.h" 52#include "chrome/browser/ui/profile_error_dialog.h" 53#include "chrome/common/chrome_constants.h" 54#include "chrome/common/chrome_switches.h" 55#include "chrome/common/importer/imported_favicon_usage.h" 56#include "chrome/common/pref_names.h" 57#include "chrome/common/thumbnail_score.h" 58#include "chrome/common/url_constants.h" 59#include "components/bookmarks/browser/bookmark_model.h" 60#include "components/history/core/browser/history_client.h" 61#include "components/visitedlink/browser/visitedlink_master.h" 62#include "content/public/browser/browser_thread.h" 63#include "content/public/browser/download_item.h" 64#include "content/public/browser/notification_service.h" 65#include "grit/chromium_strings.h" 66#include "grit/generated_resources.h" 67#include "sync/api/sync_error_factory.h" 68#include "third_party/skia/include/core/SkBitmap.h" 69 70using base::Time; 71using history::HistoryBackend; 72 73namespace { 74 75static const char* kHistoryThreadName = "Chrome_HistoryThread"; 76 77void RunWithFaviconResults( 78 const favicon_base::FaviconResultsCallback& callback, 79 std::vector<favicon_base::FaviconBitmapResult>* bitmap_results) { 80 callback.Run(*bitmap_results); 81} 82 83void RunWithFaviconResult(const favicon_base::FaviconRawCallback& callback, 84 favicon_base::FaviconBitmapResult* bitmap_result) { 85 callback.Run(*bitmap_result); 86} 87 88// Extract history::URLRows into GURLs for VisitedLinkMaster. 89class URLIteratorFromURLRows 90 : public visitedlink::VisitedLinkMaster::URLIterator { 91 public: 92 explicit URLIteratorFromURLRows(const history::URLRows& url_rows) 93 : itr_(url_rows.begin()), 94 end_(url_rows.end()) { 95 } 96 97 virtual const GURL& NextURL() OVERRIDE { 98 return (itr_++)->url(); 99 } 100 101 virtual bool HasNextURL() const OVERRIDE { 102 return itr_ != end_; 103 } 104 105 private: 106 history::URLRows::const_iterator itr_; 107 history::URLRows::const_iterator end_; 108 109 DISALLOW_COPY_AND_ASSIGN(URLIteratorFromURLRows); 110}; 111 112// Callback from WebHistoryService::ExpireWebHistory(). 113void ExpireWebHistoryComplete(bool success) { 114 // Ignore the result. 115 // 116 // TODO(davidben): ExpireLocalAndRemoteHistoryBetween callback should not fire 117 // until this completes. 118} 119 120} // namespace 121 122// Sends messages from the backend to us on the main thread. This must be a 123// separate class from the history service so that it can hold a reference to 124// the history service (otherwise we would have to manually AddRef and 125// Release when the Backend has a reference to us). 126class HistoryService::BackendDelegate : public HistoryBackend::Delegate { 127 public: 128 BackendDelegate( 129 const base::WeakPtr<HistoryService>& history_service, 130 const scoped_refptr<base::SequencedTaskRunner>& service_task_runner, 131 Profile* profile) 132 : history_service_(history_service), 133 service_task_runner_(service_task_runner), 134 profile_(profile) { 135 } 136 137 virtual void NotifyProfileError(sql::InitStatus init_status) OVERRIDE { 138 // Send to the history service on the main thread. 139 service_task_runner_->PostTask( 140 FROM_HERE, 141 base::Bind(&HistoryService::NotifyProfileError, history_service_, 142 init_status)); 143 } 144 145 virtual void SetInMemoryBackend( 146 scoped_ptr<history::InMemoryHistoryBackend> backend) OVERRIDE { 147 // Send the backend to the history service on the main thread. 148 service_task_runner_->PostTask( 149 FROM_HERE, 150 base::Bind(&HistoryService::SetInMemoryBackend, history_service_, 151 base::Passed(&backend))); 152 } 153 154 virtual void BroadcastNotifications( 155 int type, 156 scoped_ptr<history::HistoryDetails> details) OVERRIDE { 157 // Send the notification on the history thread. 158 if (content::NotificationService::current()) { 159 content::Details<history::HistoryDetails> det(details.get()); 160 content::NotificationService::current()->Notify( 161 type, content::Source<Profile>(profile_), det); 162 } 163 // Send the notification to the history service on the main thread. 164 service_task_runner_->PostTask( 165 FROM_HERE, 166 base::Bind(&HistoryService::BroadcastNotificationsHelper, 167 history_service_, type, base::Passed(&details))); 168 } 169 170 virtual void DBLoaded() OVERRIDE { 171 service_task_runner_->PostTask( 172 FROM_HERE, 173 base::Bind(&HistoryService::OnDBLoaded, history_service_)); 174 } 175 176 virtual void NotifyVisitDBObserversOnAddVisit( 177 const history::BriefVisitInfo& info) OVERRIDE { 178 service_task_runner_->PostTask( 179 FROM_HERE, 180 base::Bind(&HistoryService::NotifyVisitDBObserversOnAddVisit, 181 history_service_, info)); 182 } 183 184 private: 185 const base::WeakPtr<HistoryService> history_service_; 186 const scoped_refptr<base::SequencedTaskRunner> service_task_runner_; 187 Profile* const profile_; 188}; 189 190// The history thread is intentionally not a BrowserThread because the 191// sync integration unit tests depend on being able to create more than one 192// history thread. 193HistoryService::HistoryService() 194 : weak_ptr_factory_(this), 195 thread_(new base::Thread(kHistoryThreadName)), 196 history_client_(NULL), 197 profile_(NULL), 198 backend_loaded_(false), 199 bookmark_service_(NULL), 200 no_db_(false) { 201} 202 203HistoryService::HistoryService(history::HistoryClient* client, Profile* profile) 204 : weak_ptr_factory_(this), 205 thread_(new base::Thread(kHistoryThreadName)), 206 history_client_(client), 207 profile_(profile), 208 visitedlink_master_(new visitedlink::VisitedLinkMaster( 209 profile, this, true)), 210 backend_loaded_(false), 211 bookmark_service_(NULL), 212 no_db_(false) { 213 DCHECK(profile_); 214 registrar_.Add(this, chrome::NOTIFICATION_HISTORY_URLS_DELETED, 215 content::Source<Profile>(profile_)); 216 registrar_.Add(this, chrome::NOTIFICATION_TEMPLATE_URL_REMOVED, 217 content::Source<Profile>(profile_)); 218} 219 220HistoryService::~HistoryService() { 221 DCHECK(thread_checker_.CalledOnValidThread()); 222 // Shutdown the backend. This does nothing if Cleanup was already invoked. 223 Cleanup(); 224} 225 226bool HistoryService::BackendLoaded() { 227 DCHECK(thread_checker_.CalledOnValidThread()); 228 return backend_loaded_; 229} 230 231void HistoryService::Cleanup() { 232 DCHECK(thread_checker_.CalledOnValidThread()); 233 if (!thread_) { 234 // We've already cleaned up. 235 return; 236 } 237 238 weak_ptr_factory_.InvalidateWeakPtrs(); 239 240 // Unload the backend. 241 if (history_backend_) { 242 // Get rid of the in-memory backend. 243 in_memory_backend_.reset(); 244 245 // Give the InMemoryURLIndex a chance to shutdown. 246 // NOTE: In tests, there may be no index. 247 if (in_memory_url_index_) 248 in_memory_url_index_->ShutDown(); 249 250 // The backend's destructor must run on the history thread since it is not 251 // threadsafe. So this thread must not be the last thread holding a 252 // reference to the backend, or a crash could happen. 253 // 254 // We have a reference to the history backend. There is also an extra 255 // reference held by our delegate installed in the backend, which 256 // HistoryBackend::Closing will release. This means if we scheduled a call 257 // to HistoryBackend::Closing and *then* released our backend reference, 258 // there will be a race between us and the backend's Closing function to see 259 // who is the last holder of a reference. If the backend thread's Closing 260 // manages to run before we release our backend refptr, the last reference 261 // will be held by this thread and the destructor will be called from here. 262 // 263 // Therefore, we create a closure to run the Closing operation first. This 264 // holds a reference to the backend. Then we release our reference, then we 265 // schedule the task to run. After the task runs, it will delete its 266 // reference from the history thread, ensuring everything works properly. 267 // 268 // TODO(ajwong): Cleanup HistoryBackend lifetime issues. 269 // See http://crbug.com/99767. 270 history_backend_->AddRef(); 271 base::Closure closing_task = 272 base::Bind(&HistoryBackend::Closing, history_backend_.get()); 273 ScheduleTask(PRIORITY_NORMAL, closing_task); 274 closing_task.Reset(); 275 HistoryBackend* raw_ptr = history_backend_.get(); 276 history_backend_ = NULL; 277 thread_->message_loop()->ReleaseSoon(FROM_HERE, raw_ptr); 278 } 279 280 // Delete the thread, which joins with the background thread. We defensively 281 // NULL the pointer before deleting it in case somebody tries to use it 282 // during shutdown, but this shouldn't happen. 283 base::Thread* thread = thread_; 284 thread_ = NULL; 285 delete thread; 286} 287 288void HistoryService::NotifyRenderProcessHostDestruction(const void* host) { 289 DCHECK(thread_checker_.CalledOnValidThread()); 290 ScheduleAndForget(PRIORITY_NORMAL, 291 &HistoryBackend::NotifyRenderProcessHostDestruction, host); 292} 293 294history::URLDatabase* HistoryService::InMemoryDatabase() { 295 DCHECK(thread_checker_.CalledOnValidThread()); 296 return in_memory_backend_ ? in_memory_backend_->db() : NULL; 297} 298 299bool HistoryService::GetTypedCountForURL(const GURL& url, int* typed_count) { 300 DCHECK(thread_checker_.CalledOnValidThread()); 301 history::URLRow url_row; 302 if (!GetRowForURL(url, &url_row)) 303 return false; 304 *typed_count = url_row.typed_count(); 305 return true; 306} 307 308bool HistoryService::GetLastVisitTimeForURL(const GURL& url, 309 base::Time* last_visit) { 310 DCHECK(thread_checker_.CalledOnValidThread()); 311 history::URLRow url_row; 312 if (!GetRowForURL(url, &url_row)) 313 return false; 314 *last_visit = url_row.last_visit(); 315 return true; 316} 317 318bool HistoryService::GetVisitCountForURL(const GURL& url, int* visit_count) { 319 DCHECK(thread_checker_.CalledOnValidThread()); 320 history::URLRow url_row; 321 if (!GetRowForURL(url, &url_row)) 322 return false; 323 *visit_count = url_row.visit_count(); 324 return true; 325} 326 327history::TypedUrlSyncableService* HistoryService::GetTypedUrlSyncableService() 328 const { 329 return history_backend_->GetTypedUrlSyncableService(); 330} 331 332void HistoryService::Shutdown() { 333 DCHECK(thread_checker_.CalledOnValidThread()); 334 // It's possible that bookmarks haven't loaded and history is waiting for 335 // bookmarks to complete loading. In such a situation history can't shutdown 336 // (meaning if we invoked history_service_->Cleanup now, we would 337 // deadlock). To break the deadlock we tell BookmarkModel it's about to be 338 // deleted so that it can release the signal history is waiting on, allowing 339 // history to shutdown (history_service_->Cleanup to complete). In such a 340 // scenario history sees an incorrect view of bookmarks, but it's better 341 // than a deadlock. 342 BookmarkModel* bookmark_model = static_cast<BookmarkModel*>( 343 BookmarkModelFactory::GetForProfileIfExists(profile_)); 344 if (bookmark_model) 345 bookmark_model->Shutdown(); 346 347 Cleanup(); 348} 349 350void HistoryService::SetKeywordSearchTermsForURL(const GURL& url, 351 TemplateURLID keyword_id, 352 const base::string16& term) { 353 DCHECK(thread_checker_.CalledOnValidThread()); 354 ScheduleAndForget(PRIORITY_UI, 355 &HistoryBackend::SetKeywordSearchTermsForURL, 356 url, keyword_id, term); 357} 358 359void HistoryService::DeleteAllSearchTermsForKeyword( 360 TemplateURLID keyword_id) { 361 DCHECK(thread_checker_.CalledOnValidThread()); 362 ScheduleAndForget(PRIORITY_UI, 363 &HistoryBackend::DeleteAllSearchTermsForKeyword, 364 keyword_id); 365} 366 367HistoryService::Handle HistoryService::GetMostRecentKeywordSearchTerms( 368 TemplateURLID keyword_id, 369 const base::string16& prefix, 370 int max_count, 371 CancelableRequestConsumerBase* consumer, 372 const GetMostRecentKeywordSearchTermsCallback& callback) { 373 DCHECK(thread_checker_.CalledOnValidThread()); 374 return Schedule(PRIORITY_UI, &HistoryBackend::GetMostRecentKeywordSearchTerms, 375 consumer, 376 new history::GetMostRecentKeywordSearchTermsRequest(callback), 377 keyword_id, prefix, max_count); 378} 379 380void HistoryService::DeleteKeywordSearchTermForURL(const GURL& url) { 381 DCHECK(thread_checker_.CalledOnValidThread()); 382 ScheduleAndForget(PRIORITY_UI, &HistoryBackend::DeleteKeywordSearchTermForURL, 383 url); 384} 385 386void HistoryService::DeleteMatchingURLsForKeyword(TemplateURLID keyword_id, 387 const base::string16& term) { 388 DCHECK(thread_checker_.CalledOnValidThread()); 389 ScheduleAndForget(PRIORITY_UI, &HistoryBackend::DeleteMatchingURLsForKeyword, 390 keyword_id, term); 391} 392 393void HistoryService::URLsNoLongerBookmarked(const std::set<GURL>& urls) { 394 DCHECK(thread_checker_.CalledOnValidThread()); 395 ScheduleAndForget(PRIORITY_NORMAL, &HistoryBackend::URLsNoLongerBookmarked, 396 urls); 397} 398 399void HistoryService::ScheduleDBTask(history::HistoryDBTask* task, 400 CancelableRequestConsumerBase* consumer) { 401 DCHECK(thread_checker_.CalledOnValidThread()); 402 history::HistoryDBTaskRequest* request = new history::HistoryDBTaskRequest( 403 base::Bind(&history::HistoryDBTask::DoneRunOnMainThread, task)); 404 request->value = task; // The value is the task to execute. 405 Schedule(PRIORITY_UI, &HistoryBackend::ProcessDBTask, consumer, request); 406} 407 408HistoryService::Handle HistoryService::QuerySegmentUsageSince( 409 CancelableRequestConsumerBase* consumer, 410 const Time from_time, 411 int max_result_count, 412 const SegmentQueryCallback& callback) { 413 DCHECK(thread_checker_.CalledOnValidThread()); 414 return Schedule(PRIORITY_UI, &HistoryBackend::QuerySegmentUsage, 415 consumer, new history::QuerySegmentUsageRequest(callback), 416 from_time, max_result_count); 417} 418 419void HistoryService::FlushForTest(const base::Closure& flushed) { 420 thread_->message_loop_proxy()->PostTaskAndReply( 421 FROM_HERE, base::Bind(&base::DoNothing), flushed); 422} 423 424void HistoryService::SetOnBackendDestroyTask(const base::Closure& task) { 425 DCHECK(thread_checker_.CalledOnValidThread()); 426 ScheduleAndForget(PRIORITY_NORMAL, &HistoryBackend::SetOnBackendDestroyTask, 427 base::MessageLoop::current(), task); 428} 429 430void HistoryService::AddPage(const GURL& url, 431 Time time, 432 const void* id_scope, 433 int32 page_id, 434 const GURL& referrer, 435 const history::RedirectList& redirects, 436 content::PageTransition transition, 437 history::VisitSource visit_source, 438 bool did_replace_entry) { 439 DCHECK(thread_checker_.CalledOnValidThread()); 440 AddPage( 441 history::HistoryAddPageArgs(url, time, id_scope, page_id, referrer, 442 redirects, transition, visit_source, 443 did_replace_entry)); 444} 445 446void HistoryService::AddPage(const GURL& url, 447 base::Time time, 448 history::VisitSource visit_source) { 449 DCHECK(thread_checker_.CalledOnValidThread()); 450 AddPage( 451 history::HistoryAddPageArgs(url, time, NULL, 0, GURL(), 452 history::RedirectList(), 453 content::PAGE_TRANSITION_LINK, 454 visit_source, false)); 455} 456 457void HistoryService::AddPage(const history::HistoryAddPageArgs& add_page_args) { 458 DCHECK(thread_checker_.CalledOnValidThread()); 459 DCHECK(thread_) << "History service being called after cleanup"; 460 461 // Filter out unwanted URLs. We don't add auto-subframe URLs. They are a 462 // large part of history (think iframes for ads) and we never display them in 463 // history UI. We will still add manual subframes, which are ones the user 464 // has clicked on to get. 465 if (!CanAddURL(add_page_args.url)) 466 return; 467 468 // Add link & all redirects to visited link list. 469 if (visitedlink_master_) { 470 visitedlink_master_->AddURL(add_page_args.url); 471 472 if (!add_page_args.redirects.empty()) { 473 // We should not be asked to add a page in the middle of a redirect chain. 474 DCHECK_EQ(add_page_args.url, 475 add_page_args.redirects[add_page_args.redirects.size() - 1]); 476 477 // We need the !redirects.empty() condition above since size_t is unsigned 478 // and will wrap around when we subtract one from a 0 size. 479 for (size_t i = 0; i < add_page_args.redirects.size() - 1; i++) 480 visitedlink_master_->AddURL(add_page_args.redirects[i]); 481 } 482 } 483 484 ScheduleAndForget(PRIORITY_NORMAL, &HistoryBackend::AddPage, add_page_args); 485} 486 487void HistoryService::AddPageNoVisitForBookmark(const GURL& url, 488 const base::string16& title) { 489 DCHECK(thread_checker_.CalledOnValidThread()); 490 if (!CanAddURL(url)) 491 return; 492 493 ScheduleAndForget(PRIORITY_NORMAL, 494 &HistoryBackend::AddPageNoVisitForBookmark, url, title); 495} 496 497void HistoryService::SetPageTitle(const GURL& url, 498 const base::string16& title) { 499 DCHECK(thread_checker_.CalledOnValidThread()); 500 ScheduleAndForget(PRIORITY_NORMAL, &HistoryBackend::SetPageTitle, url, title); 501} 502 503void HistoryService::UpdateWithPageEndTime(const void* host, 504 int32 page_id, 505 const GURL& url, 506 Time end_ts) { 507 DCHECK(thread_checker_.CalledOnValidThread()); 508 ScheduleAndForget(PRIORITY_NORMAL, &HistoryBackend::UpdateWithPageEndTime, 509 host, page_id, url, end_ts); 510} 511 512void HistoryService::AddPageWithDetails(const GURL& url, 513 const base::string16& title, 514 int visit_count, 515 int typed_count, 516 Time last_visit, 517 bool hidden, 518 history::VisitSource visit_source) { 519 DCHECK(thread_checker_.CalledOnValidThread()); 520 // Filter out unwanted URLs. 521 if (!CanAddURL(url)) 522 return; 523 524 // Add to the visited links system. 525 if (visitedlink_master_) 526 visitedlink_master_->AddURL(url); 527 528 history::URLRow row(url); 529 row.set_title(title); 530 row.set_visit_count(visit_count); 531 row.set_typed_count(typed_count); 532 row.set_last_visit(last_visit); 533 row.set_hidden(hidden); 534 535 history::URLRows rows; 536 rows.push_back(row); 537 538 ScheduleAndForget(PRIORITY_NORMAL, 539 &HistoryBackend::AddPagesWithDetails, rows, visit_source); 540} 541 542void HistoryService::AddPagesWithDetails(const history::URLRows& info, 543 history::VisitSource visit_source) { 544 DCHECK(thread_checker_.CalledOnValidThread()); 545 // Add to the visited links system. 546 if (visitedlink_master_) { 547 std::vector<GURL> urls; 548 urls.reserve(info.size()); 549 for (history::URLRows::const_iterator i = info.begin(); i != info.end(); 550 ++i) 551 urls.push_back(i->url()); 552 553 visitedlink_master_->AddURLs(urls); 554 } 555 556 ScheduleAndForget(PRIORITY_NORMAL, 557 &HistoryBackend::AddPagesWithDetails, info, visit_source); 558} 559 560base::CancelableTaskTracker::TaskId HistoryService::GetFavicons( 561 const std::vector<GURL>& icon_urls, 562 int icon_types, 563 int desired_size_in_dip, 564 const std::vector<ui::ScaleFactor>& desired_scale_factors, 565 const favicon_base::FaviconResultsCallback& callback, 566 base::CancelableTaskTracker* tracker) { 567 DCHECK(thread_checker_.CalledOnValidThread()); 568 569 std::vector<favicon_base::FaviconBitmapResult>* results = 570 new std::vector<favicon_base::FaviconBitmapResult>(); 571 return tracker->PostTaskAndReply( 572 thread_->message_loop_proxy().get(), 573 FROM_HERE, 574 base::Bind(&HistoryBackend::GetFavicons, 575 history_backend_.get(), 576 icon_urls, 577 icon_types, 578 desired_size_in_dip, 579 desired_scale_factors, 580 results), 581 base::Bind(&RunWithFaviconResults, callback, base::Owned(results))); 582} 583 584base::CancelableTaskTracker::TaskId HistoryService::GetFaviconsForURL( 585 const GURL& page_url, 586 int icon_types, 587 int desired_size_in_dip, 588 const std::vector<ui::ScaleFactor>& desired_scale_factors, 589 const favicon_base::FaviconResultsCallback& callback, 590 base::CancelableTaskTracker* tracker) { 591 DCHECK(thread_checker_.CalledOnValidThread()); 592 593 std::vector<favicon_base::FaviconBitmapResult>* results = 594 new std::vector<favicon_base::FaviconBitmapResult>(); 595 return tracker->PostTaskAndReply( 596 thread_->message_loop_proxy().get(), 597 FROM_HERE, 598 base::Bind(&HistoryBackend::GetFaviconsForURL, 599 history_backend_.get(), 600 page_url, 601 icon_types, 602 desired_size_in_dip, 603 desired_scale_factors, 604 results), 605 base::Bind(&RunWithFaviconResults, callback, base::Owned(results))); 606} 607 608base::CancelableTaskTracker::TaskId HistoryService::GetLargestFaviconForURL( 609 const GURL& page_url, 610 const std::vector<int>& icon_types, 611 int minimum_size_in_pixels, 612 const favicon_base::FaviconRawCallback& callback, 613 base::CancelableTaskTracker* tracker) { 614 DCHECK(thread_checker_.CalledOnValidThread()); 615 616 favicon_base::FaviconBitmapResult* result = 617 new favicon_base::FaviconBitmapResult(); 618 return tracker->PostTaskAndReply( 619 thread_->message_loop_proxy().get(), 620 FROM_HERE, 621 base::Bind(&HistoryBackend::GetLargestFaviconForURL, 622 history_backend_.get(), 623 page_url, 624 icon_types, 625 minimum_size_in_pixels, 626 result), 627 base::Bind(&RunWithFaviconResult, callback, base::Owned(result))); 628} 629 630base::CancelableTaskTracker::TaskId HistoryService::GetFaviconForID( 631 favicon_base::FaviconID favicon_id, 632 int desired_size_in_dip, 633 ui::ScaleFactor desired_scale_factor, 634 const favicon_base::FaviconResultsCallback& callback, 635 base::CancelableTaskTracker* tracker) { 636 DCHECK(thread_checker_.CalledOnValidThread()); 637 638 std::vector<favicon_base::FaviconBitmapResult>* results = 639 new std::vector<favicon_base::FaviconBitmapResult>(); 640 return tracker->PostTaskAndReply( 641 thread_->message_loop_proxy().get(), 642 FROM_HERE, 643 base::Bind(&HistoryBackend::GetFaviconForID, 644 history_backend_.get(), 645 favicon_id, 646 desired_size_in_dip, 647 desired_scale_factor, 648 results), 649 base::Bind(&RunWithFaviconResults, callback, base::Owned(results))); 650} 651 652base::CancelableTaskTracker::TaskId 653HistoryService::UpdateFaviconMappingsAndFetch( 654 const GURL& page_url, 655 const std::vector<GURL>& icon_urls, 656 int icon_types, 657 int desired_size_in_dip, 658 const std::vector<ui::ScaleFactor>& desired_scale_factors, 659 const favicon_base::FaviconResultsCallback& callback, 660 base::CancelableTaskTracker* tracker) { 661 DCHECK(thread_checker_.CalledOnValidThread()); 662 663 std::vector<favicon_base::FaviconBitmapResult>* results = 664 new std::vector<favicon_base::FaviconBitmapResult>(); 665 return tracker->PostTaskAndReply( 666 thread_->message_loop_proxy().get(), 667 FROM_HERE, 668 base::Bind(&HistoryBackend::UpdateFaviconMappingsAndFetch, 669 history_backend_.get(), 670 page_url, 671 icon_urls, 672 icon_types, 673 desired_size_in_dip, 674 desired_scale_factors, 675 results), 676 base::Bind(&RunWithFaviconResults, callback, base::Owned(results))); 677} 678 679void HistoryService::MergeFavicon( 680 const GURL& page_url, 681 const GURL& icon_url, 682 favicon_base::IconType icon_type, 683 scoped_refptr<base::RefCountedMemory> bitmap_data, 684 const gfx::Size& pixel_size) { 685 DCHECK(thread_checker_.CalledOnValidThread()); 686 if (!CanAddURL(page_url)) 687 return; 688 689 ScheduleAndForget(PRIORITY_NORMAL, &HistoryBackend::MergeFavicon, page_url, 690 icon_url, icon_type, bitmap_data, pixel_size); 691} 692 693void HistoryService::SetFavicons( 694 const GURL& page_url, 695 favicon_base::IconType icon_type, 696 const std::vector<favicon_base::FaviconBitmapData>& favicon_bitmap_data) { 697 DCHECK(thread_checker_.CalledOnValidThread()); 698 if (!CanAddURL(page_url)) 699 return; 700 701 ScheduleAndForget(PRIORITY_NORMAL, &HistoryBackend::SetFavicons, page_url, 702 icon_type, favicon_bitmap_data); 703} 704 705void HistoryService::SetFaviconsOutOfDateForPage(const GURL& page_url) { 706 DCHECK(thread_checker_.CalledOnValidThread()); 707 ScheduleAndForget(PRIORITY_NORMAL, 708 &HistoryBackend::SetFaviconsOutOfDateForPage, page_url); 709} 710 711void HistoryService::CloneFavicons(const GURL& old_page_url, 712 const GURL& new_page_url) { 713 DCHECK(thread_checker_.CalledOnValidThread()); 714 ScheduleAndForget(PRIORITY_NORMAL, &HistoryBackend::CloneFavicons, 715 old_page_url, new_page_url); 716} 717 718void HistoryService::SetImportedFavicons( 719 const std::vector<ImportedFaviconUsage>& favicon_usage) { 720 DCHECK(thread_checker_.CalledOnValidThread()); 721 ScheduleAndForget(PRIORITY_NORMAL, 722 &HistoryBackend::SetImportedFavicons, favicon_usage); 723} 724 725HistoryService::Handle HistoryService::QueryURL( 726 const GURL& url, 727 bool want_visits, 728 CancelableRequestConsumerBase* consumer, 729 const QueryURLCallback& callback) { 730 DCHECK(thread_checker_.CalledOnValidThread()); 731 return Schedule(PRIORITY_UI, &HistoryBackend::QueryURL, consumer, 732 new history::QueryURLRequest(callback), url, want_visits); 733} 734 735// Downloads ------------------------------------------------------------------- 736 737// Handle creation of a download by creating an entry in the history service's 738// 'downloads' table. 739void HistoryService::CreateDownload( 740 const history::DownloadRow& create_info, 741 const HistoryService::DownloadCreateCallback& callback) { 742 DCHECK(thread_) << "History service being called after cleanup"; 743 DCHECK(thread_checker_.CalledOnValidThread()); 744 PostTaskAndReplyWithResult( 745 thread_->message_loop_proxy(), FROM_HERE, 746 base::Bind(&HistoryBackend::CreateDownload, history_backend_.get(), 747 create_info), 748 callback); 749} 750 751void HistoryService::GetNextDownloadId( 752 const content::DownloadIdCallback& callback) { 753 DCHECK(thread_) << "History service being called after cleanup"; 754 DCHECK(thread_checker_.CalledOnValidThread()); 755 PostTaskAndReplyWithResult( 756 thread_->message_loop_proxy(), FROM_HERE, 757 base::Bind(&HistoryBackend::GetNextDownloadId, history_backend_.get()), 758 callback); 759} 760 761// Handle queries for a list of all downloads in the history database's 762// 'downloads' table. 763void HistoryService::QueryDownloads( 764 const DownloadQueryCallback& callback) { 765 DCHECK(thread_) << "History service being called after cleanup"; 766 DCHECK(thread_checker_.CalledOnValidThread()); 767 std::vector<history::DownloadRow>* rows = 768 new std::vector<history::DownloadRow>(); 769 scoped_ptr<std::vector<history::DownloadRow> > scoped_rows(rows); 770 // Beware! The first Bind() does not simply |scoped_rows.get()| because 771 // base::Passed(&scoped_rows) nullifies |scoped_rows|, and compilers do not 772 // guarantee that the first Bind's arguments are evaluated before the second 773 // Bind's arguments. 774 thread_->message_loop_proxy()->PostTaskAndReply( 775 FROM_HERE, 776 base::Bind(&HistoryBackend::QueryDownloads, history_backend_.get(), rows), 777 base::Bind(callback, base::Passed(&scoped_rows))); 778} 779 780// Handle updates for a particular download. This is a 'fire and forget' 781// operation, so we don't need to be called back. 782void HistoryService::UpdateDownload(const history::DownloadRow& data) { 783 DCHECK(thread_checker_.CalledOnValidThread()); 784 ScheduleAndForget(PRIORITY_NORMAL, &HistoryBackend::UpdateDownload, data); 785} 786 787void HistoryService::RemoveDownloads(const std::set<uint32>& ids) { 788 DCHECK(thread_checker_.CalledOnValidThread()); 789 ScheduleAndForget(PRIORITY_NORMAL, 790 &HistoryBackend::RemoveDownloads, ids); 791} 792 793HistoryService::Handle HistoryService::QueryHistory( 794 const base::string16& text_query, 795 const history::QueryOptions& options, 796 CancelableRequestConsumerBase* consumer, 797 const QueryHistoryCallback& callback) { 798 DCHECK(thread_checker_.CalledOnValidThread()); 799 return Schedule(PRIORITY_UI, &HistoryBackend::QueryHistory, consumer, 800 new history::QueryHistoryRequest(callback), 801 text_query, options); 802} 803 804HistoryService::Handle HistoryService::QueryRedirectsFrom( 805 const GURL& from_url, 806 CancelableRequestConsumerBase* consumer, 807 const QueryRedirectsCallback& callback) { 808 DCHECK(thread_checker_.CalledOnValidThread()); 809 return Schedule(PRIORITY_UI, &HistoryBackend::QueryRedirectsFrom, consumer, 810 new history::QueryRedirectsRequest(callback), from_url); 811} 812 813HistoryService::Handle HistoryService::QueryRedirectsTo( 814 const GURL& to_url, 815 CancelableRequestConsumerBase* consumer, 816 const QueryRedirectsCallback& callback) { 817 DCHECK(thread_checker_.CalledOnValidThread()); 818 return Schedule(PRIORITY_NORMAL, &HistoryBackend::QueryRedirectsTo, consumer, 819 new history::QueryRedirectsRequest(callback), to_url); 820} 821 822HistoryService::Handle HistoryService::GetVisibleVisitCountToHost( 823 const GURL& url, 824 CancelableRequestConsumerBase* consumer, 825 const GetVisibleVisitCountToHostCallback& callback) { 826 DCHECK(thread_checker_.CalledOnValidThread()); 827 return Schedule(PRIORITY_UI, &HistoryBackend::GetVisibleVisitCountToHost, 828 consumer, new history::GetVisibleVisitCountToHostRequest(callback), url); 829} 830 831HistoryService::Handle HistoryService::QueryTopURLsAndRedirects( 832 int result_count, 833 CancelableRequestConsumerBase* consumer, 834 const QueryTopURLsAndRedirectsCallback& callback) { 835 DCHECK(thread_checker_.CalledOnValidThread()); 836 return Schedule(PRIORITY_NORMAL, &HistoryBackend::QueryTopURLsAndRedirects, 837 consumer, new history::QueryTopURLsAndRedirectsRequest(callback), 838 result_count); 839} 840 841HistoryService::Handle HistoryService::QueryMostVisitedURLs( 842 int result_count, 843 int days_back, 844 CancelableRequestConsumerBase* consumer, 845 const QueryMostVisitedURLsCallback& callback) { 846 DCHECK(thread_checker_.CalledOnValidThread()); 847 return Schedule(PRIORITY_NORMAL, &HistoryBackend::QueryMostVisitedURLs, 848 consumer, 849 new history::QueryMostVisitedURLsRequest(callback), 850 result_count, days_back); 851} 852 853HistoryService::Handle HistoryService::QueryFilteredURLs( 854 int result_count, 855 const history::VisitFilter& filter, 856 bool extended_info, 857 CancelableRequestConsumerBase* consumer, 858 const QueryFilteredURLsCallback& callback) { 859 DCHECK(thread_checker_.CalledOnValidThread()); 860 return Schedule(PRIORITY_NORMAL, 861 &HistoryBackend::QueryFilteredURLs, 862 consumer, 863 new history::QueryFilteredURLsRequest(callback), 864 result_count, filter, extended_info); 865} 866 867void HistoryService::Observe(int type, 868 const content::NotificationSource& source, 869 const content::NotificationDetails& details) { 870 DCHECK(thread_checker_.CalledOnValidThread()); 871 if (!thread_) 872 return; 873 874 switch (type) { 875 case chrome::NOTIFICATION_HISTORY_URLS_DELETED: { 876 // Update the visited link system for deleted URLs. We will update the 877 // visited link system for added URLs as soon as we get the add 878 // notification (we don't have to wait for the backend, which allows us to 879 // be faster to update the state). 880 // 881 // For deleted URLs, we don't typically know what will be deleted since 882 // delete notifications are by time. We would also like to be more 883 // respectful of privacy and never tell the user something is gone when it 884 // isn't. Therefore, we update the delete URLs after the fact. 885 if (visitedlink_master_) { 886 content::Details<history::URLsDeletedDetails> deleted_details(details); 887 888 if (deleted_details->all_history) { 889 visitedlink_master_->DeleteAllURLs(); 890 } else { 891 URLIteratorFromURLRows iterator(deleted_details->rows); 892 visitedlink_master_->DeleteURLs(&iterator); 893 } 894 } 895 break; 896 } 897 898 case chrome::NOTIFICATION_TEMPLATE_URL_REMOVED: 899 DeleteAllSearchTermsForKeyword( 900 *(content::Details<TemplateURLID>(details).ptr())); 901 break; 902 903 default: 904 NOTREACHED(); 905 } 906} 907 908void HistoryService::RebuildTable( 909 const scoped_refptr<URLEnumerator>& enumerator) { 910 DCHECK(thread_checker_.CalledOnValidThread()); 911 ScheduleAndForget(PRIORITY_NORMAL, &HistoryBackend::IterateURLs, enumerator); 912} 913 914bool HistoryService::Init(const base::FilePath& history_dir, 915 BookmarkService* bookmark_service, 916 bool no_db) { 917 DCHECK(thread_checker_.CalledOnValidThread()); 918 if (!thread_->Start()) { 919 Cleanup(); 920 return false; 921 } 922 923 history_dir_ = history_dir; 924 bookmark_service_ = bookmark_service; 925 no_db_ = no_db; 926 927 if (profile_) { 928 std::string languages = 929 profile_->GetPrefs()->GetString(prefs::kAcceptLanguages); 930 in_memory_url_index_.reset( 931 new history::InMemoryURLIndex(profile_, history_dir_, languages)); 932 in_memory_url_index_->Init(); 933 } 934 935 // Create the history backend. 936 scoped_refptr<HistoryBackend> backend( 937 new HistoryBackend(history_dir_, 938 new BackendDelegate( 939 weak_ptr_factory_.GetWeakPtr(), 940 base::ThreadTaskRunnerHandle::Get(), 941 profile_), 942 bookmark_service_)); 943 history_backend_.swap(backend); 944 945 // There may not be a profile when unit testing. 946 std::string languages; 947 if (profile_) { 948 PrefService* prefs = profile_->GetPrefs(); 949 languages = prefs->GetString(prefs::kAcceptLanguages); 950 } 951 ScheduleAndForget(PRIORITY_UI, &HistoryBackend::Init, languages, no_db_); 952 953 if (visitedlink_master_) { 954 bool result = visitedlink_master_->Init(); 955 DCHECK(result); 956 } 957 958 return true; 959} 960 961void HistoryService::ScheduleAutocomplete(HistoryURLProvider* provider, 962 HistoryURLProviderParams* params) { 963 DCHECK(thread_checker_.CalledOnValidThread()); 964 ScheduleAndForget(PRIORITY_UI, &HistoryBackend::ScheduleAutocomplete, 965 scoped_refptr<HistoryURLProvider>(provider), params); 966} 967 968void HistoryService::ScheduleTask(SchedulePriority priority, 969 const base::Closure& task) { 970 DCHECK(thread_checker_.CalledOnValidThread()); 971 CHECK(thread_); 972 CHECK(thread_->message_loop()); 973 // TODO(brettw): Do prioritization. 974 thread_->message_loop()->PostTask(FROM_HERE, task); 975} 976 977// static 978bool HistoryService::CanAddURL(const GURL& url) { 979 if (!url.is_valid()) 980 return false; 981 982 // TODO: We should allow kChromeUIScheme URLs if they have been explicitly 983 // typed. Right now, however, these are marked as typed even when triggered 984 // by a shortcut or menu action. 985 if (url.SchemeIs(url::kJavaScriptScheme) || 986 url.SchemeIs(content::kChromeDevToolsScheme) || 987 url.SchemeIs(content::kChromeUIScheme) || 988 url.SchemeIs(content::kViewSourceScheme) || 989 url.SchemeIs(chrome::kChromeNativeScheme) || 990 url.SchemeIs(chrome::kChromeSearchScheme) || 991 url.SchemeIs(chrome::kDomDistillerScheme)) 992 return false; 993 994 // Allow all about: and chrome: URLs except about:blank, since the user may 995 // like to see "chrome://memory/", etc. in their history and autocomplete. 996 if (url == GURL(content::kAboutBlankURL)) 997 return false; 998 999 return true; 1000} 1001 1002base::WeakPtr<HistoryService> HistoryService::AsWeakPtr() { 1003 DCHECK(thread_checker_.CalledOnValidThread()); 1004 return weak_ptr_factory_.GetWeakPtr(); 1005} 1006 1007syncer::SyncMergeResult HistoryService::MergeDataAndStartSyncing( 1008 syncer::ModelType type, 1009 const syncer::SyncDataList& initial_sync_data, 1010 scoped_ptr<syncer::SyncChangeProcessor> sync_processor, 1011 scoped_ptr<syncer::SyncErrorFactory> error_handler) { 1012 DCHECK(thread_checker_.CalledOnValidThread()); 1013 DCHECK_EQ(type, syncer::HISTORY_DELETE_DIRECTIVES); 1014 delete_directive_handler_.Start(this, initial_sync_data, 1015 sync_processor.Pass()); 1016 return syncer::SyncMergeResult(type); 1017} 1018 1019void HistoryService::StopSyncing(syncer::ModelType type) { 1020 DCHECK(thread_checker_.CalledOnValidThread()); 1021 DCHECK_EQ(type, syncer::HISTORY_DELETE_DIRECTIVES); 1022 delete_directive_handler_.Stop(); 1023} 1024 1025syncer::SyncDataList HistoryService::GetAllSyncData( 1026 syncer::ModelType type) const { 1027 DCHECK(thread_checker_.CalledOnValidThread()); 1028 DCHECK_EQ(type, syncer::HISTORY_DELETE_DIRECTIVES); 1029 // TODO(akalin): Keep track of existing delete directives. 1030 return syncer::SyncDataList(); 1031} 1032 1033syncer::SyncError HistoryService::ProcessSyncChanges( 1034 const tracked_objects::Location& from_here, 1035 const syncer::SyncChangeList& change_list) { 1036 delete_directive_handler_.ProcessSyncChanges(this, change_list); 1037 return syncer::SyncError(); 1038} 1039 1040syncer::SyncError HistoryService::ProcessLocalDeleteDirective( 1041 const sync_pb::HistoryDeleteDirectiveSpecifics& delete_directive) { 1042 DCHECK(thread_checker_.CalledOnValidThread()); 1043 return delete_directive_handler_.ProcessLocalDeleteDirective( 1044 delete_directive); 1045} 1046 1047void HistoryService::SetInMemoryBackend( 1048 scoped_ptr<history::InMemoryHistoryBackend> mem_backend) { 1049 DCHECK(thread_checker_.CalledOnValidThread()); 1050 DCHECK(!in_memory_backend_) << "Setting mem DB twice"; 1051 in_memory_backend_.reset(mem_backend.release()); 1052 1053 // The database requires additional initialization once we own it. 1054 in_memory_backend_->AttachToHistoryService(profile_); 1055} 1056 1057void HistoryService::NotifyProfileError(sql::InitStatus init_status) { 1058 DCHECK(thread_checker_.CalledOnValidThread()); 1059 ShowProfileErrorDialog( 1060 PROFILE_ERROR_HISTORY, 1061 (init_status == sql::INIT_FAILURE) ? 1062 IDS_COULDNT_OPEN_PROFILE_ERROR : IDS_PROFILE_TOO_NEW_ERROR); 1063} 1064 1065void HistoryService::DeleteURL(const GURL& url) { 1066 DCHECK(thread_checker_.CalledOnValidThread()); 1067 // We will update the visited links when we observe the delete notifications. 1068 ScheduleAndForget(PRIORITY_NORMAL, &HistoryBackend::DeleteURL, url); 1069} 1070 1071void HistoryService::DeleteURLsForTest(const std::vector<GURL>& urls) { 1072 DCHECK(thread_checker_.CalledOnValidThread()); 1073 // We will update the visited links when we observe the delete 1074 // notifications. 1075 ScheduleAndForget(PRIORITY_NORMAL, &HistoryBackend::DeleteURLs, urls); 1076} 1077 1078void HistoryService::ExpireHistoryBetween( 1079 const std::set<GURL>& restrict_urls, 1080 Time begin_time, 1081 Time end_time, 1082 const base::Closure& callback, 1083 base::CancelableTaskTracker* tracker) { 1084 DCHECK(thread_); 1085 DCHECK(thread_checker_.CalledOnValidThread()); 1086 DCHECK(history_backend_.get()); 1087 tracker->PostTaskAndReply(thread_->message_loop_proxy().get(), 1088 FROM_HERE, 1089 base::Bind(&HistoryBackend::ExpireHistoryBetween, 1090 history_backend_, 1091 restrict_urls, 1092 begin_time, 1093 end_time), 1094 callback); 1095} 1096 1097void HistoryService::ExpireHistory( 1098 const std::vector<history::ExpireHistoryArgs>& expire_list, 1099 const base::Closure& callback, 1100 base::CancelableTaskTracker* tracker) { 1101 DCHECK(thread_); 1102 DCHECK(thread_checker_.CalledOnValidThread()); 1103 DCHECK(history_backend_.get()); 1104 tracker->PostTaskAndReply( 1105 thread_->message_loop_proxy().get(), 1106 FROM_HERE, 1107 base::Bind(&HistoryBackend::ExpireHistory, history_backend_, expire_list), 1108 callback); 1109} 1110 1111void HistoryService::ExpireLocalAndRemoteHistoryBetween( 1112 const std::set<GURL>& restrict_urls, 1113 Time begin_time, 1114 Time end_time, 1115 const base::Closure& callback, 1116 base::CancelableTaskTracker* tracker) { 1117 // TODO(dubroy): This should be factored out into a separate class that 1118 // dispatches deletions to the proper places. 1119 1120 history::WebHistoryService* web_history = 1121 WebHistoryServiceFactory::GetForProfile(profile_); 1122 if (web_history) { 1123 // TODO(dubroy): This API does not yet support deletion of specific URLs. 1124 DCHECK(restrict_urls.empty()); 1125 1126 delete_directive_handler_.CreateDeleteDirectives( 1127 std::set<int64>(), begin_time, end_time); 1128 1129 // Attempt online deletion from the history server, but ignore the result. 1130 // Deletion directives ensure that the results will eventually be deleted. 1131 // 1132 // TODO(davidben): |callback| should not run until this operation completes 1133 // too. 1134 web_history->ExpireHistoryBetween( 1135 restrict_urls, begin_time, end_time, 1136 base::Bind(&ExpireWebHistoryComplete)); 1137 } 1138 ExpireHistoryBetween(restrict_urls, begin_time, end_time, callback, tracker); 1139} 1140 1141void HistoryService::BroadcastNotificationsHelper( 1142 int type, 1143 scoped_ptr<history::HistoryDetails> details) { 1144 DCHECK(thread_checker_.CalledOnValidThread()); 1145 // TODO(evanm): this is currently necessitated by generate_profile, which 1146 // runs without a browser process. generate_profile should really create 1147 // a browser process, at which point this check can then be nuked. 1148 if (!g_browser_process) 1149 return; 1150 1151 if (!thread_) 1152 return; 1153 1154 // The source of all of our notifications is the profile. Note that this 1155 // pointer is NULL in unit tests. 1156 content::Source<Profile> source(profile_); 1157 1158 // The details object just contains the pointer to the object that the 1159 // backend has allocated for us. The receiver of the notification will cast 1160 // this to the proper type. 1161 content::Details<history::HistoryDetails> det(details.get()); 1162 1163 content::NotificationService::current()->Notify(type, source, det); 1164} 1165 1166void HistoryService::OnDBLoaded() { 1167 DCHECK(thread_checker_.CalledOnValidThread()); 1168 backend_loaded_ = true; 1169 content::NotificationService::current()->Notify( 1170 chrome::NOTIFICATION_HISTORY_LOADED, 1171 content::Source<Profile>(profile_), 1172 content::Details<HistoryService>(this)); 1173} 1174 1175bool HistoryService::GetRowForURL(const GURL& url, history::URLRow* url_row) { 1176 DCHECK(thread_checker_.CalledOnValidThread()); 1177 history::URLDatabase* db = InMemoryDatabase(); 1178 return db && (db->GetRowForURL(url, url_row) != 0); 1179} 1180 1181void HistoryService::AddVisitDatabaseObserver( 1182 history::VisitDatabaseObserver* observer) { 1183 DCHECK(thread_checker_.CalledOnValidThread()); 1184 visit_database_observers_.AddObserver(observer); 1185} 1186 1187void HistoryService::RemoveVisitDatabaseObserver( 1188 history::VisitDatabaseObserver* observer) { 1189 DCHECK(thread_checker_.CalledOnValidThread()); 1190 visit_database_observers_.RemoveObserver(observer); 1191} 1192 1193void HistoryService::NotifyVisitDBObserversOnAddVisit( 1194 const history::BriefVisitInfo& info) { 1195 DCHECK(thread_checker_.CalledOnValidThread()); 1196 FOR_EACH_OBSERVER(history::VisitDatabaseObserver, visit_database_observers_, 1197 OnAddVisit(info)); 1198} 1199