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