summaryrefslogtreecommitdiffstats
path: root/components
diff options
context:
space:
mode:
authorsdefresne <sdefresne@chromium.org>2015-03-12 11:49:12 -0700
committerCommit bot <commit-bot@chromium.org>2015-03-12 18:50:31 +0000
commit506dd521e1f6934a997e051481da3fda2098a354 (patch)
tree5741833876ae8f92957ad0e9717f2d5fba991049 /components
parentc4622464ba3fdb5d16cf99125ea71bc6fb83d30e (diff)
downloadchromium_src-506dd521e1f6934a997e051481da3fda2098a354.zip
chromium_src-506dd521e1f6934a997e051481da3fda2098a354.tar.gz
chromium_src-506dd521e1f6934a997e051481da3fda2098a354.tar.bz2
Componentize HistoryService, HistoryBackend and related classes
Move a bunch of files from chrome/browser/history to components/history as they have no problematic dependency anymore (move them in a single pass as they have circular #include). Update DEPS, dependencies in gyp/gn files and #include. Files were moved with tools/git/move_source_file.py and #include fixes were automated. BUG=453790 Review URL: https://codereview.chromium.org/961873003 Cr-Commit-Position: refs/heads/master@{#320332}
Diffstat (limited to 'components')
-rw-r--r--components/components.gyp2
-rw-r--r--components/history.gypi14
-rw-r--r--components/history/DEPS1
-rw-r--r--components/history/content/DEPS3
-rw-r--r--components/history/content/browser/BUILD.gn3
-rw-r--r--components/history/content/browser/DEPS4
-rw-r--r--components/history/content/browser/content_visit_delegate.cc121
-rw-r--r--components/history/content/browser/content_visit_delegate.h51
-rw-r--r--components/history/core/browser/BUILD.gn11
-rw-r--r--components/history/core/browser/delete_directive_handler.cc433
-rw-r--r--components/history/core/browser/delete_directive_handler.h80
-rw-r--r--components/history/core/browser/history_backend.cc2658
-rw-r--r--components/history/core/browser/history_backend.h827
-rw-r--r--components/history/core/browser/history_service.cc1134
-rw-r--r--components/history/core/browser/history_service.h812
-rw-r--r--components/history/core/browser/in_memory_history_backend.cc111
-rw-r--r--components/history/core/browser/in_memory_history_backend.h105
-rw-r--r--components/history/core/browser/typed_url_syncable_service.cc439
-rw-r--r--components/history/core/browser/typed_url_syncable_service.h161
-rw-r--r--components/search_engines/template_url_service_unittest.cc2
20 files changed, 6967 insertions, 5 deletions
diff --git a/components/components.gyp b/components/components.gyp
index 6fc7aec..849b45f 100644
--- a/components/components.gyp
+++ b/components/components.gyp
@@ -31,7 +31,6 @@
'favicon_base.gypi',
'google.gypi',
'handoff.gypi',
- 'history.gypi',
'infobars.gypi',
'json_schema.gypi',
'keyed_service.gypi',
@@ -135,6 +134,7 @@
# introduced.
'includes': [
'gcm_driver.gypi',
+ 'history.gypi',
'omnibox.gypi',
'renderer_context_menu.gypi',
'search_engines.gypi',
diff --git a/components/history.gypi b/components/history.gypi
index 75be781..7bf0dd0 100644
--- a/components/history.gypi
+++ b/components/history.gypi
@@ -17,6 +17,7 @@
'../net/net.gyp:net',
'../skia/skia.gyp:skia',
'../sql/sql.gyp:sql',
+ '../sync/sync.gyp:sync',
'../third_party/sqlite/sqlite.gyp:sqlite',
'../ui/base/ui_base.gyp:ui_base',
'../ui/gfx/gfx.gyp:gfx',
@@ -28,6 +29,8 @@
],
'sources': [
# Note: sources list duplicated in GN build.
+ 'history/core/browser/delete_directive_handler.cc',
+ 'history/core/browser/delete_directive_handler.h',
'history/core/browser/download_constants.h',
'history/core/browser/download_database.cc',
'history/core/browser/download_database.h',
@@ -37,6 +40,8 @@
'history/core/browser/download_types.h',
'history/core/browser/expire_history_backend.cc',
'history/core/browser/expire_history_backend.h',
+ 'history/core/browser/history_backend.cc',
+ 'history/core/browser/history_backend.h',
'history/core/browser/history_backend_notifier.h',
'history/core/browser/history_backend_observer.h',
'history/core/browser/history_client.cc',
@@ -51,11 +56,15 @@
'history/core/browser/history_db_task.h',
'history/core/browser/history_match.cc',
'history/core/browser/history_match.h',
+ 'history/core/browser/history_service.cc',
+ 'history/core/browser/history_service.h',
'history/core/browser/history_service_observer.h',
'history/core/browser/history_types.cc',
'history/core/browser/history_types.h',
'history/core/browser/in_memory_database.cc',
'history/core/browser/in_memory_database.h',
+ 'history/core/browser/in_memory_history_backend.cc',
+ 'history/core/browser/in_memory_history_backend.h',
'history/core/browser/keyword_id.h',
'history/core/browser/keyword_search_term.cc',
'history/core/browser/keyword_search_term.h',
@@ -72,6 +81,8 @@
'history/core/browser/top_sites_database.cc',
'history/core/browser/top_sites_database.h',
'history/core/browser/top_sites_observer.h',
+ 'history/core/browser/typed_url_syncable_service.cc',
+ 'history/core/browser/typed_url_syncable_service.h',
'history/core/browser/url_database.cc',
'history/core/browser/url_database.h',
'history/core/browser/url_row.cc',
@@ -172,8 +183,11 @@
'../base/base.gyp:base',
'../content/content.gyp:content_browser',
'history_core_browser',
+ 'visitedlink_browser',
],
'sources': [
+ 'history/content/browser/content_visit_delegate.cc',
+ 'history/content/browser/content_visit_delegate.h',
'history/content/browser/download_constants_utils.cc',
'history/content/browser/download_constants_utils.h',
'history/content/browser/history_context_helper.cc',
diff --git a/components/history/DEPS b/components/history/DEPS
index e6b1835..58b0b17 100644
--- a/components/history/DEPS
+++ b/components/history/DEPS
@@ -6,6 +6,7 @@ include_rules = [
"+google_apis/gaia",
"+net",
"+sql",
+ "+sync",
"+third_party/skia",
"+third_party/sqlite",
"+ui/base",
diff --git a/components/history/content/DEPS b/components/history/content/DEPS
deleted file mode 100644
index 60dbcf4..0000000
--- a/components/history/content/DEPS
+++ /dev/null
@@ -1,3 +0,0 @@
-include_rules = [
- "+content",
-]
diff --git a/components/history/content/browser/BUILD.gn b/components/history/content/browser/BUILD.gn
index cd4c98c..22f91b5 100644
--- a/components/history/content/browser/BUILD.gn
+++ b/components/history/content/browser/BUILD.gn
@@ -4,6 +4,8 @@
static_library("browser") {
sources = [
+ "content_visit_delegate.cc",
+ "content_visit_delegate.h",
"download_constants_utils.cc",
"download_constants_utils.h",
"history_context_helper.cc",
@@ -15,6 +17,7 @@ static_library("browser") {
deps = [
"//base",
"//components/history/core/browser",
+ "//components/visitedlink/browser",
"//content/public/browser",
]
}
diff --git a/components/history/content/browser/DEPS b/components/history/content/browser/DEPS
new file mode 100644
index 0000000..4ef4ece
--- /dev/null
+++ b/components/history/content/browser/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+ "+components/visitedlink/browser",
+ "+content/public/browser",
+]
diff --git a/components/history/content/browser/content_visit_delegate.cc b/components/history/content/browser/content_visit_delegate.cc
new file mode 100644
index 0000000..9b6a393
--- /dev/null
+++ b/components/history/content/browser/content_visit_delegate.cc
@@ -0,0 +1,121 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/history/content/browser/content_visit_delegate.h"
+
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "components/history/core/browser/history_backend.h"
+#include "components/history/core/browser/history_database.h"
+#include "components/history/core/browser/history_db_task.h"
+#include "components/history/core/browser/history_service.h"
+#include "components/visitedlink/browser/visitedlink_master.h"
+#include "url/gurl.h"
+
+namespace {
+
+// URLIterator from std::vector<GURL>
+class URLIteratorFromURLs : public visitedlink::VisitedLinkMaster::URLIterator {
+ public:
+ explicit URLIteratorFromURLs(const std::vector<GURL>& urls)
+ : itr_(urls.begin()), end_(urls.end()) {}
+
+ // visitedlink::VisitedLinkMaster::URLIterator implementation.
+ const GURL& NextURL() override { return *(itr_++); }
+ bool HasNextURL() const override { return itr_ != end_; }
+
+ private:
+ std::vector<GURL>::const_iterator itr_;
+ std::vector<GURL>::const_iterator end_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLIteratorFromURLs);
+};
+
+// IterateUrlsDBTask bridge HistoryBackend::URLEnumerator to
+// visitedlink::VisitedLinkDelegate::URLEnumerator.
+class IterateUrlsDBTask : public history::HistoryDBTask {
+ public:
+ explicit IterateUrlsDBTask(const scoped_refptr<
+ visitedlink::VisitedLinkDelegate::URLEnumerator>& enumerator);
+ ~IterateUrlsDBTask() override;
+
+ private:
+ // Implementation of history::HistoryDBTask.
+ bool RunOnDBThread(history::HistoryBackend* backend,
+ history::HistoryDatabase* db) override;
+ void DoneRunOnMainThread() override;
+
+ scoped_refptr<visitedlink::VisitedLinkDelegate::URLEnumerator> enumerator_;
+
+ DISALLOW_COPY_AND_ASSIGN(IterateUrlsDBTask);
+};
+
+IterateUrlsDBTask::IterateUrlsDBTask(const scoped_refptr<
+ visitedlink::VisitedLinkDelegate::URLEnumerator>& enumerator)
+ : enumerator_(enumerator) {
+}
+
+IterateUrlsDBTask::~IterateUrlsDBTask() {
+}
+
+bool IterateUrlsDBTask::RunOnDBThread(history::HistoryBackend* backend,
+ history::HistoryDatabase* db) {
+ bool success = false;
+ if (db) {
+ history::HistoryDatabase::URLEnumerator iter;
+ if (db->InitURLEnumeratorForEverything(&iter)) {
+ history::URLRow row;
+ while (iter.GetNextURL(&row))
+ enumerator_->OnURL(row.url());
+ success = true;
+ }
+ }
+ enumerator_->OnComplete(success);
+ return true;
+}
+
+void IterateUrlsDBTask::DoneRunOnMainThread() {
+}
+
+} // namespace
+
+ContentVisitDelegate::ContentVisitDelegate(
+ content::BrowserContext* browser_context)
+ : history_service_(nullptr),
+ visitedlink_master_(
+ new visitedlink::VisitedLinkMaster(browser_context, this, true)) {
+}
+
+ContentVisitDelegate::~ContentVisitDelegate() {
+}
+
+bool ContentVisitDelegate::Init(HistoryService* history_service) {
+ DCHECK(history_service);
+ history_service_ = history_service;
+ return visitedlink_master_->Init();
+}
+
+void ContentVisitDelegate::AddURL(const GURL& url) {
+ visitedlink_master_->AddURL(url);
+}
+
+void ContentVisitDelegate::AddURLs(const std::vector<GURL>& urls) {
+ visitedlink_master_->AddURLs(urls);
+}
+
+void ContentVisitDelegate::DeleteURLs(const std::vector<GURL>& urls) {
+ URLIteratorFromURLs iterator(urls);
+ visitedlink_master_->DeleteURLs(&iterator);
+}
+
+void ContentVisitDelegate::DeleteAllURLs() {
+ visitedlink_master_->DeleteAllURLs();
+}
+
+void ContentVisitDelegate::RebuildTable(
+ const scoped_refptr<URLEnumerator>& enumerator) {
+ DCHECK(history_service_);
+ scoped_ptr<history::HistoryDBTask> task(new IterateUrlsDBTask(enumerator));
+ history_service_->ScheduleDBTask(task.Pass(), &task_tracker_);
+}
diff --git a/components/history/content/browser/content_visit_delegate.h b/components/history/content/browser/content_visit_delegate.h
new file mode 100644
index 0000000..611fe27
--- /dev/null
+++ b/components/history/content/browser/content_visit_delegate.h
@@ -0,0 +1,51 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_HISTORY_CONTENT_BROWSER_CONTENT_VISIT_DELEGATE_H_
+#define COMPONENTS_HISTORY_CONTENT_BROWSER_CONTENT_VISIT_DELEGATE_H_
+
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/task/cancelable_task_tracker.h"
+#include "components/history/core/browser/visit_delegate.h"
+#include "components/visitedlink/browser/visitedlink_delegate.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace visitedlink {
+class VisitedLinkMaster;
+}
+
+// ContentVisitDelegate bridge history::VisitDelegate events to
+// visitedlink::VisitedLinkMaster.
+class ContentVisitDelegate : public history::VisitDelegate,
+ public visitedlink::VisitedLinkDelegate {
+ public:
+ explicit ContentVisitDelegate(content::BrowserContext* browser_context);
+ ~ContentVisitDelegate() override;
+
+ private:
+ // Implementation of history::VisitDelegate.
+ bool Init(HistoryService* history_service) override;
+ void AddURL(const GURL& url) override;
+ void AddURLs(const std::vector<GURL>& urls) override;
+ void DeleteURLs(const std::vector<GURL>& urls) override;
+ void DeleteAllURLs() override;
+
+ // Implementation of visitedlink::VisitedLinkDelegate.
+ void RebuildTable(const scoped_refptr<
+ visitedlink::VisitedLinkDelegate::URLEnumerator>& enumerator) override;
+
+ HistoryService* history_service_; // Weak.
+ scoped_ptr<visitedlink::VisitedLinkMaster> visitedlink_master_;
+ base::CancelableTaskTracker task_tracker_;
+
+ DISALLOW_COPY_AND_ASSIGN(ContentVisitDelegate);
+};
+
+#endif // COMPONENTS_HISTORY_CONTENT_BROWSER_CONTENT_VISIT_DELEGATE_H_
diff --git a/components/history/core/browser/BUILD.gn b/components/history/core/browser/BUILD.gn
index 3a4571d..b2ed448 100644
--- a/components/history/core/browser/BUILD.gn
+++ b/components/history/core/browser/BUILD.gn
@@ -4,6 +4,8 @@
static_library("browser") {
sources = [
+ "delete_directive_handler.cc",
+ "delete_directive_handler.h",
"download_constants.h",
"download_database.cc",
"download_database.h",
@@ -13,6 +15,8 @@ static_library("browser") {
"download_types.h",
"expire_history_backend.cc",
"expire_history_backend.h",
+ "history_backend.cc",
+ "history_backend.h",
"history_backend_notifier.h",
"history_backend_observer.h",
"history_client.cc",
@@ -27,11 +31,15 @@ static_library("browser") {
"history_db_task.h",
"history_match.cc",
"history_match.h",
+ "history_service.cc",
+ "history_service.h",
"history_service_observer.h",
"history_types.cc",
"history_types.h",
"in_memory_database.cc",
"in_memory_database.h",
+ "in_memory_history_backend.cc",
+ "in_memory_history_backend.h",
"keyword_id.h",
"keyword_search_term.cc",
"keyword_search_term.h",
@@ -48,6 +56,8 @@ static_library("browser") {
"top_sites_database.cc",
"top_sites_database.h",
"top_sites_observer.h",
+ "typed_url_syncable_service.cc",
+ "typed_url_syncable_service.h",
"url_database.cc",
"url_database.h",
"url_row.cc",
@@ -78,6 +88,7 @@ static_library("browser") {
"//net",
"//skia",
"//sql",
+ "//sync",
"//third_party/sqlite",
"//ui/base",
"//ui/gfx",
diff --git a/components/history/core/browser/delete_directive_handler.cc b/components/history/core/browser/delete_directive_handler.cc
new file mode 100644
index 0000000..5404ce8
--- /dev/null
+++ b/components/history/core/browser/delete_directive_handler.cc
@@ -0,0 +1,433 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/history/core/browser/delete_directive_handler.h"
+
+#include "base/json/json_writer.h"
+#include "base/rand_util.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "components/history/core/browser/history_backend.h"
+#include "components/history/core/browser/history_db_task.h"
+#include "components/history/core/browser/history_service.h"
+#include "sync/api/sync_change.h"
+#include "sync/protocol/history_delete_directive_specifics.pb.h"
+#include "sync/protocol/proto_value_conversions.h"
+#include "sync/protocol/sync.pb.h"
+
+namespace {
+
+std::string RandASCIIString(size_t length) {
+ std::string result;
+ const int kMin = static_cast<int>(' ');
+ const int kMax = static_cast<int>('~');
+ for (size_t i = 0; i < length; ++i)
+ result.push_back(static_cast<char>(base::RandInt(kMin, kMax)));
+ return result;
+}
+
+std::string DeleteDirectiveToString(
+ const sync_pb::HistoryDeleteDirectiveSpecifics& delete_directive) {
+ scoped_ptr<base::DictionaryValue> value(
+ syncer::HistoryDeleteDirectiveSpecificsToValue(delete_directive));
+ std::string str;
+ base::JSONWriter::Write(value.get(), &str);
+ return str;
+}
+
+// Compare time range directives first by start time, then by end time.
+bool TimeRangeLessThan(const syncer::SyncData& data1,
+ const syncer::SyncData& data2) {
+ const sync_pb::TimeRangeDirective& range1 =
+ data1.GetSpecifics().history_delete_directive().time_range_directive();
+ const sync_pb::TimeRangeDirective& range2 =
+ data2.GetSpecifics().history_delete_directive().time_range_directive();
+ if (range1.start_time_usec() < range2.start_time_usec())
+ return true;
+ if (range1.start_time_usec() > range2.start_time_usec())
+ return false;
+ return range1.end_time_usec() < range2.end_time_usec();
+}
+
+// Converts a Unix timestamp in microseconds to a base::Time value.
+base::Time UnixUsecToTime(int64 usec) {
+ return base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(usec);
+}
+
+// Converts a base::Time value to a Unix timestamp in microseconds.
+int64 TimeToUnixUsec(base::Time time) {
+ DCHECK(!time.is_null());
+ return (time - base::Time::UnixEpoch()).InMicroseconds();
+}
+
+// Converts global IDs in |global_id_directive| to times.
+void GetTimesFromGlobalIds(
+ const sync_pb::GlobalIdDirective& global_id_directive,
+ std::set<base::Time>* times) {
+ for (int i = 0; i < global_id_directive.global_id_size(); ++i) {
+ times->insert(
+ base::Time::FromInternalValue(global_id_directive.global_id(i)));
+ }
+}
+
+#if !defined(NDEBUG)
+// Checks that the given delete directive is properly formed.
+void CheckDeleteDirectiveValid(
+ const sync_pb::HistoryDeleteDirectiveSpecifics& delete_directive) {
+ if (delete_directive.has_global_id_directive()) {
+ const sync_pb::GlobalIdDirective& global_id_directive =
+ delete_directive.global_id_directive();
+
+ DCHECK(!delete_directive.has_time_range_directive());
+ DCHECK_NE(global_id_directive.global_id_size(), 0);
+ if (global_id_directive.has_start_time_usec())
+ DCHECK_GE(global_id_directive.start_time_usec(), 0);
+ if (global_id_directive.has_end_time_usec()) {
+ DCHECK_GT(global_id_directive.end_time_usec(), 0);
+
+ if (global_id_directive.has_start_time_usec()) {
+ DCHECK_LE(global_id_directive.start_time_usec(),
+ global_id_directive.end_time_usec());
+ }
+ }
+
+ } else if (delete_directive.has_time_range_directive()) {
+ const sync_pb::TimeRangeDirective& time_range_directive =
+ delete_directive.time_range_directive();
+
+ DCHECK(!delete_directive.has_global_id_directive());
+ DCHECK(time_range_directive.has_start_time_usec());
+ DCHECK(time_range_directive.has_end_time_usec());
+ DCHECK_GE(time_range_directive.start_time_usec(), 0);
+ DCHECK_GT(time_range_directive.end_time_usec(), 0);
+ DCHECK_GT(time_range_directive.end_time_usec(),
+ time_range_directive.start_time_usec());
+ } else {
+ NOTREACHED() << "Delete directive has no time range or global ID directive";
+ }
+}
+#endif // !defined(NDEBUG)
+
+} // anonymous namespace
+
+namespace history {
+
+class DeleteDirectiveHandler::DeleteDirectiveTask : public HistoryDBTask {
+ public:
+ DeleteDirectiveTask(
+ base::WeakPtr<DeleteDirectiveHandler> delete_directive_handler,
+ const syncer::SyncDataList& delete_directive,
+ DeleteDirectiveHandler::PostProcessingAction post_processing_action)
+ : delete_directive_handler_(delete_directive_handler),
+ delete_directives_(delete_directive),
+ post_processing_action_(post_processing_action) {}
+
+ // Implements HistoryDBTask.
+ bool RunOnDBThread(history::HistoryBackend* backend,
+ history::HistoryDatabase* db) override;
+ void DoneRunOnMainThread() override;
+
+ private:
+ ~DeleteDirectiveTask() override {}
+
+ // Process a list of global Id directives. Delete all visits to a URL in
+ // time ranges of directives if the timestamp of one visit matches with one
+ // global id.
+ void ProcessGlobalIdDeleteDirectives(
+ history::HistoryBackend* history_backend,
+ const syncer::SyncDataList& global_id_directives);
+
+ // Process a list of time range directives, all history entries within the
+ // time ranges are deleted. |time_range_directives| should be sorted by
+ // |start_time_usec| and |end_time_usec| already.
+ void ProcessTimeRangeDeleteDirectives(
+ history::HistoryBackend* history_backend,
+ const syncer::SyncDataList& time_range_directives);
+
+ base::WeakPtr<DeleteDirectiveHandler> delete_directive_handler_;
+ syncer::SyncDataList delete_directives_;
+ DeleteDirectiveHandler::PostProcessingAction post_processing_action_;
+};
+
+bool DeleteDirectiveHandler::DeleteDirectiveTask::RunOnDBThread(
+ history::HistoryBackend* backend,
+ history::HistoryDatabase* db) {
+ syncer::SyncDataList global_id_directives;
+ syncer::SyncDataList time_range_directives;
+ for (syncer::SyncDataList::const_iterator it = delete_directives_.begin();
+ it != delete_directives_.end(); ++it) {
+ DCHECK_EQ(it->GetDataType(), syncer::HISTORY_DELETE_DIRECTIVES);
+ const sync_pb::HistoryDeleteDirectiveSpecifics& delete_directive =
+ it->GetSpecifics().history_delete_directive();
+ if (delete_directive.has_global_id_directive()) {
+ global_id_directives.push_back(*it);
+ } else {
+ time_range_directives.push_back(*it);
+ }
+ }
+
+ ProcessGlobalIdDeleteDirectives(backend, global_id_directives);
+ std::sort(time_range_directives.begin(), time_range_directives.end(),
+ TimeRangeLessThan);
+ ProcessTimeRangeDeleteDirectives(backend, time_range_directives);
+ return true;
+}
+
+void DeleteDirectiveHandler::DeleteDirectiveTask::DoneRunOnMainThread() {
+ if (delete_directive_handler_.get()) {
+ delete_directive_handler_->FinishProcessing(post_processing_action_,
+ delete_directives_);
+ }
+}
+
+void DeleteDirectiveHandler::DeleteDirectiveTask::
+ ProcessGlobalIdDeleteDirectives(
+ history::HistoryBackend* history_backend,
+ const syncer::SyncDataList& global_id_directives) {
+ if (global_id_directives.empty())
+ return;
+
+ // Group times represented by global IDs by time ranges of delete directives.
+ // It's more efficient for backend to process all directives with same time
+ // range at once.
+ typedef std::map<std::pair<base::Time, base::Time>, std::set<base::Time>>
+ GlobalIdTimesGroup;
+ GlobalIdTimesGroup id_times_group;
+ for (size_t i = 0; i < global_id_directives.size(); ++i) {
+ DVLOG(1) << "Processing delete directive: "
+ << DeleteDirectiveToString(global_id_directives[i]
+ .GetSpecifics()
+ .history_delete_directive());
+
+ const sync_pb::GlobalIdDirective& id_directive =
+ global_id_directives[i]
+ .GetSpecifics()
+ .history_delete_directive()
+ .global_id_directive();
+ if (id_directive.global_id_size() == 0 ||
+ !id_directive.has_start_time_usec() ||
+ !id_directive.has_end_time_usec()) {
+ DLOG(ERROR) << "Invalid global id directive.";
+ continue;
+ }
+ GetTimesFromGlobalIds(id_directive,
+ &id_times_group[std::make_pair(
+ UnixUsecToTime(id_directive.start_time_usec()),
+ UnixUsecToTime(id_directive.end_time_usec()))]);
+ }
+
+ if (id_times_group.empty())
+ return;
+
+ // Call backend to expire history of directives in each group.
+ for (GlobalIdTimesGroup::const_iterator group_it = id_times_group.begin();
+ group_it != id_times_group.end(); ++group_it) {
+ // Add 1us to cover history entries visited at the end time because time
+ // range in directive is inclusive.
+ history_backend->ExpireHistoryForTimes(
+ group_it->second, group_it->first.first,
+ group_it->first.second + base::TimeDelta::FromMicroseconds(1));
+ }
+}
+
+void DeleteDirectiveHandler::DeleteDirectiveTask::
+ ProcessTimeRangeDeleteDirectives(
+ history::HistoryBackend* history_backend,
+ const syncer::SyncDataList& time_range_directives) {
+ if (time_range_directives.empty())
+ return;
+
+ // Iterate through time range directives. Expire history in combined
+ // time range for multiple directives whose time ranges overlap.
+ base::Time current_start_time;
+ base::Time current_end_time;
+ for (size_t i = 0; i < time_range_directives.size(); ++i) {
+ const sync_pb::HistoryDeleteDirectiveSpecifics& delete_directive =
+ time_range_directives[i].GetSpecifics().history_delete_directive();
+ DVLOG(1) << "Processing time range directive: "
+ << DeleteDirectiveToString(delete_directive);
+
+ const sync_pb::TimeRangeDirective& time_range_directive =
+ delete_directive.time_range_directive();
+ if (!time_range_directive.has_start_time_usec() ||
+ !time_range_directive.has_end_time_usec() ||
+ time_range_directive.start_time_usec() >=
+ time_range_directive.end_time_usec()) {
+ DLOG(ERROR) << "Invalid time range directive.";
+ continue;
+ }
+
+ base::Time directive_start_time =
+ UnixUsecToTime(time_range_directive.start_time_usec());
+ base::Time directive_end_time =
+ UnixUsecToTime(time_range_directive.end_time_usec());
+ if (directive_start_time > current_end_time) {
+ if (!current_start_time.is_null()) {
+ // Add 1us to cover history entries visited at the end time because
+ // time range in directive is inclusive.
+ history_backend->ExpireHistoryBetween(
+ std::set<GURL>(), current_start_time,
+ current_end_time + base::TimeDelta::FromMicroseconds(1));
+ }
+ current_start_time = directive_start_time;
+ }
+ if (directive_end_time > current_end_time)
+ current_end_time = directive_end_time;
+ }
+
+ if (!current_start_time.is_null()) {
+ history_backend->ExpireHistoryBetween(
+ std::set<GURL>(), current_start_time,
+ current_end_time + base::TimeDelta::FromMicroseconds(1));
+ }
+}
+
+DeleteDirectiveHandler::DeleteDirectiveHandler() : weak_ptr_factory_(this) {
+}
+
+DeleteDirectiveHandler::~DeleteDirectiveHandler() {
+ weak_ptr_factory_.InvalidateWeakPtrs();
+}
+
+void DeleteDirectiveHandler::Start(
+ HistoryService* history_service,
+ const syncer::SyncDataList& initial_sync_data,
+ scoped_ptr<syncer::SyncChangeProcessor> sync_processor) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ sync_processor_ = sync_processor.Pass();
+ if (!initial_sync_data.empty()) {
+ // Drop processed delete directives during startup.
+ history_service->ScheduleDBTask(
+ scoped_ptr<history::HistoryDBTask>(
+ new DeleteDirectiveTask(weak_ptr_factory_.GetWeakPtr(),
+ initial_sync_data, DROP_AFTER_PROCESSING)),
+ &internal_tracker_);
+ }
+}
+
+void DeleteDirectiveHandler::Stop() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ sync_processor_.reset();
+}
+
+bool DeleteDirectiveHandler::CreateDeleteDirectives(
+ const std::set<int64>& global_ids,
+ base::Time begin_time,
+ base::Time end_time) {
+ base::Time now = base::Time::Now();
+ sync_pb::HistoryDeleteDirectiveSpecifics delete_directive;
+
+ // Delete directives require a non-null begin time, so use 1 if it's null.
+ int64 begin_time_usecs =
+ begin_time.is_null() ? 0 : TimeToUnixUsec(begin_time);
+
+ // Determine the actual end time -- it should not be null or in the future.
+ // TODO(dubroy): Use sane time (crbug.com/146090) here when it's available.
+ base::Time end = (end_time.is_null() || end_time > now) ? now : end_time;
+ // -1 because end time in delete directives is inclusive.
+ int64 end_time_usecs = TimeToUnixUsec(end) - 1;
+
+ if (global_ids.empty()) {
+ sync_pb::TimeRangeDirective* time_range_directive =
+ delete_directive.mutable_time_range_directive();
+ time_range_directive->set_start_time_usec(begin_time_usecs);
+ time_range_directive->set_end_time_usec(end_time_usecs);
+ } else {
+ for (std::set<int64>::const_iterator it = global_ids.begin();
+ it != global_ids.end(); ++it) {
+ sync_pb::GlobalIdDirective* global_id_directive =
+ delete_directive.mutable_global_id_directive();
+ global_id_directive->add_global_id(*it);
+ global_id_directive->set_start_time_usec(begin_time_usecs);
+ global_id_directive->set_end_time_usec(end_time_usecs);
+ }
+ }
+ syncer::SyncError error = ProcessLocalDeleteDirective(delete_directive);
+ return !error.IsSet();
+}
+
+syncer::SyncError DeleteDirectiveHandler::ProcessLocalDeleteDirective(
+ const sync_pb::HistoryDeleteDirectiveSpecifics& delete_directive) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (!sync_processor_) {
+ return syncer::SyncError(FROM_HERE, syncer::SyncError::DATATYPE_ERROR,
+ "Cannot send local delete directive to sync",
+ syncer::HISTORY_DELETE_DIRECTIVES);
+ }
+#if !defined(NDEBUG)
+ CheckDeleteDirectiveValid(delete_directive);
+#endif
+
+ // Generate a random sync tag since history delete directives don't
+ // have a 'built-in' ID. 8 bytes should suffice.
+ std::string sync_tag = RandASCIIString(8);
+ sync_pb::EntitySpecifics entity_specifics;
+ entity_specifics.mutable_history_delete_directive()->CopyFrom(
+ delete_directive);
+ syncer::SyncData sync_data =
+ syncer::SyncData::CreateLocalData(sync_tag, sync_tag, entity_specifics);
+ syncer::SyncChange change(FROM_HERE, syncer::SyncChange::ACTION_ADD,
+ sync_data);
+ syncer::SyncChangeList changes(1, change);
+ return sync_processor_->ProcessSyncChanges(FROM_HERE, changes);
+}
+
+syncer::SyncError DeleteDirectiveHandler::ProcessSyncChanges(
+ HistoryService* history_service,
+ const syncer::SyncChangeList& change_list) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (!sync_processor_) {
+ return syncer::SyncError(FROM_HERE, syncer::SyncError::DATATYPE_ERROR,
+ "Sync is disabled.",
+ syncer::HISTORY_DELETE_DIRECTIVES);
+ }
+
+ syncer::SyncDataList delete_directives;
+ for (syncer::SyncChangeList::const_iterator it = change_list.begin();
+ it != change_list.end(); ++it) {
+ switch (it->change_type()) {
+ case syncer::SyncChange::ACTION_ADD:
+ delete_directives.push_back(it->sync_data());
+ break;
+ case syncer::SyncChange::ACTION_DELETE:
+ // TODO(akalin): Keep track of existing delete directives.
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ }
+
+ if (!delete_directives.empty()) {
+ // Don't drop real-time delete directive so that sync engine can detect
+ // redelivered delete directives to avoid processing them again and again
+ // in one chrome session.
+ history_service->ScheduleDBTask(
+ scoped_ptr<history::HistoryDBTask>(
+ new DeleteDirectiveTask(weak_ptr_factory_.GetWeakPtr(),
+ delete_directives, KEEP_AFTER_PROCESSING)),
+ &internal_tracker_);
+ }
+ return syncer::SyncError();
+}
+
+void DeleteDirectiveHandler::FinishProcessing(
+ PostProcessingAction post_processing_action,
+ const syncer::SyncDataList& delete_directives) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // If specified, drop processed delete directive in sync model because they
+ // only need to be applied once.
+ if (sync_processor_.get() &&
+ post_processing_action == DROP_AFTER_PROCESSING) {
+ syncer::SyncChangeList change_list;
+ for (size_t i = 0; i < delete_directives.size(); ++i) {
+ change_list.push_back(syncer::SyncChange(
+ FROM_HERE, syncer::SyncChange::ACTION_DELETE, delete_directives[i]));
+ }
+ sync_processor_->ProcessSyncChanges(FROM_HERE, change_list);
+ }
+}
+
+} // namespace history
diff --git a/components/history/core/browser/delete_directive_handler.h b/components/history/core/browser/delete_directive_handler.h
new file mode 100644
index 0000000..24a5cc8
--- /dev/null
+++ b/components/history/core/browser/delete_directive_handler.h
@@ -0,0 +1,80 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_HISTORY_CORE_BROWSER_DELETE_DIRECTIVE_HANDLER_H_
+#define COMPONENTS_HISTORY_CORE_BROWSER_DELETE_DIRECTIVE_HANDLER_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/task/cancelable_task_tracker.h"
+#include "base/threading/thread_checker.h"
+#include "sync/api/sync_change_processor.h"
+#include "sync/api/sync_data.h"
+
+namespace sync_pb {
+class HistoryDeleteDirectiveSpecifics;
+}
+
+class HistoryService;
+
+namespace history {
+
+// DeleteDirectiveHandler sends delete directives created locally to sync
+// engine to propagate to other clients. It also expires local history entries
+// according to given delete directives from server.
+class DeleteDirectiveHandler {
+ public:
+ DeleteDirectiveHandler();
+ ~DeleteDirectiveHandler();
+
+ // Start/stop processing delete directives when sync is enabled/disabled.
+ void Start(HistoryService* history_service,
+ const syncer::SyncDataList& initial_sync_data,
+ scoped_ptr<syncer::SyncChangeProcessor> sync_processor);
+ void Stop();
+
+ // Create delete directives for the deletion of visits identified by
+ // |global_ids| (which may be empty), in the time range specified by
+ // |begin_time| and |end_time|.
+ bool CreateDeleteDirectives(const std::set<int64>& global_ids,
+ base::Time begin_time,
+ base::Time end_time);
+
+ // Sends the given |delete_directive| to SyncChangeProcessor (if it exists).
+ // Returns any error resulting from sending the delete directive to sync.
+ // NOTE: the given |delete_directive| is not processed to remove local
+ // history entries that match. Caller still needs to call other
+ // interfaces, e.g. HistoryService::ExpireHistoryBetween(), to delete
+ // local history entries.
+ syncer::SyncError ProcessLocalDeleteDirective(
+ const sync_pb::HistoryDeleteDirectiveSpecifics& delete_directive);
+
+ // Expires local history entries according to delete directives from server.
+ syncer::SyncError ProcessSyncChanges(
+ HistoryService* history_service,
+ const syncer::SyncChangeList& change_list);
+
+ private:
+ class DeleteDirectiveTask;
+ friend class DeleteDirectiveTask;
+
+ // Action to take on processed delete directives.
+ enum PostProcessingAction { KEEP_AFTER_PROCESSING, DROP_AFTER_PROCESSING };
+
+ // Callback when history backend finishes deleting visits according to
+ // |delete_directives|.
+ void FinishProcessing(PostProcessingAction post_processing_action,
+ const syncer::SyncDataList& delete_directives);
+
+ base::CancelableTaskTracker internal_tracker_;
+ scoped_ptr<syncer::SyncChangeProcessor> sync_processor_;
+ base::ThreadChecker thread_checker_;
+ base::WeakPtrFactory<DeleteDirectiveHandler> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeleteDirectiveHandler);
+};
+
+} // namespace history
+
+#endif // COMPONENTS_HISTORY_CORE_BROWSER_DELETE_DIRECTIVE_HANDLER_H_
diff --git a/components/history/core/browser/history_backend.cc b/components/history/core/browser/history_backend.cc
new file mode 100644
index 0000000..14b74d1
--- /dev/null
+++ b/components/history/core/browser/history_backend.cc
@@ -0,0 +1,2658 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/history/core/browser/history_backend.h"
+
+#include <algorithm>
+#include <functional>
+#include <list>
+#include <map>
+#include <set>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/files/file_enumerator.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram.h"
+#include "base/rand_util.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "components/favicon_base/select_favicon_frames.h"
+#include "components/history/core/browser/download_constants.h"
+#include "components/history/core/browser/download_row.h"
+#include "components/history/core/browser/history_backend_observer.h"
+#include "components/history/core/browser/history_client.h"
+#include "components/history/core/browser/history_constants.h"
+#include "components/history/core/browser/history_database.h"
+#include "components/history/core/browser/history_database_params.h"
+#include "components/history/core/browser/history_db_task.h"
+#include "components/history/core/browser/in_memory_history_backend.h"
+#include "components/history/core/browser/keyword_search_term.h"
+#include "components/history/core/browser/page_usage_data.h"
+#include "components/history/core/browser/typed_url_syncable_service.h"
+#include "components/history/core/browser/visit_filter.h"
+#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
+#include "sql/error_delegate_util.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/gfx/codec/png_codec.h"
+#include "url/gurl.h"
+#include "url/url_constants.h"
+
+using base::Time;
+using base::TimeDelta;
+using base::TimeTicks;
+
+/* The HistoryBackend consists of two components:
+
+ HistoryDatabase (stores past 3 months of history)
+ URLDatabase (stores a list of URLs)
+ DownloadDatabase (stores a list of downloads)
+ VisitDatabase (stores a list of visits for the URLs)
+ VisitSegmentDatabase (stores groups of URLs for the most visited view).
+
+ ExpireHistoryBackend (manages deleting things older than 3 months)
+*/
+
+namespace history {
+
+namespace {
+void RunUnlessCanceled(
+ const base::Closure& closure,
+ const base::CancelableTaskTracker::IsCanceledCallback& is_canceled) {
+ if (!is_canceled.Run())
+ closure.Run();
+}
+} // namespace
+
+// How long we'll wait to do a commit, so that things are batched together.
+const int kCommitIntervalSeconds = 10;
+
+// The amount of time before we re-fetch the favicon.
+const int kFaviconRefetchDays = 7;
+
+// The maximum number of items we'll allow in the redirect list before
+// deleting some.
+const int kMaxRedirectCount = 32;
+
+// The number of days old a history entry can be before it is considered "old"
+// and is deleted.
+const int kExpireDaysThreshold = 90;
+
+// Converts from PageUsageData to MostVisitedURL. |redirects| is a
+// list of redirects for this URL. Empty list means no redirects.
+MostVisitedURL MakeMostVisitedURL(const PageUsageData& page_data,
+ const RedirectList& redirects) {
+ MostVisitedURL mv;
+ mv.url = page_data.GetURL();
+ mv.title = page_data.GetTitle();
+ if (redirects.empty()) {
+ // Redirects must contain at least the target url.
+ mv.redirects.push_back(mv.url);
+ } else {
+ mv.redirects = redirects;
+ if (mv.redirects[mv.redirects.size() - 1] != mv.url) {
+ // The last url must be the target url.
+ mv.redirects.push_back(mv.url);
+ }
+ }
+ return mv;
+}
+
+// This task is run on a timer so that commits happen at regular intervals
+// so they are batched together. The important thing about this class is that
+// it supports canceling of the task so the reference to the backend will be
+// freed. The problem is that when history is shutting down, there is likely
+// to be one of these commits still pending and holding a reference.
+//
+// The backend can call Cancel to have this task release the reference. The
+// task will still run (if we ever get to processing the event before
+// shutdown), but it will not do anything.
+//
+// Note that this is a refcounted object and is not a task in itself. It should
+// be assigned to a RunnableMethod.
+//
+// TODO(brettw): bug 1165182: This should be replaced with a
+// base::WeakPtrFactory which will handle everything automatically (like we do
+// in ExpireHistoryBackend).
+class CommitLaterTask : public base::RefCounted<CommitLaterTask> {
+ public:
+ explicit CommitLaterTask(HistoryBackend* history_backend)
+ : history_backend_(history_backend) {}
+
+ // The backend will call this function if it is being destroyed so that we
+ // release our reference.
+ void Cancel() { history_backend_ = nullptr; }
+
+ void RunCommit() {
+ if (history_backend_.get())
+ history_backend_->Commit();
+ }
+
+ private:
+ friend class base::RefCounted<CommitLaterTask>;
+
+ ~CommitLaterTask() {}
+
+ scoped_refptr<HistoryBackend> history_backend_;
+};
+
+QueuedHistoryDBTask::QueuedHistoryDBTask(
+ scoped_ptr<HistoryDBTask> task,
+ scoped_refptr<base::SingleThreadTaskRunner> origin_loop,
+ const base::CancelableTaskTracker::IsCanceledCallback& is_canceled)
+ : task_(task.Pass()), origin_loop_(origin_loop), is_canceled_(is_canceled) {
+ DCHECK(task_);
+ DCHECK(origin_loop_.get());
+ DCHECK(!is_canceled_.is_null());
+}
+
+QueuedHistoryDBTask::~QueuedHistoryDBTask() {
+ // Ensure that |task_| is destroyed on its origin thread.
+ origin_loop_->PostTask(FROM_HERE,
+ base::Bind(&base::DeletePointer<HistoryDBTask>,
+ base::Unretained(task_.release())));
+}
+
+bool QueuedHistoryDBTask::is_canceled() {
+ return is_canceled_.Run();
+}
+
+bool QueuedHistoryDBTask::Run(HistoryBackend* backend, HistoryDatabase* db) {
+ return task_->RunOnDBThread(backend, db);
+}
+
+void QueuedHistoryDBTask::DoneRun() {
+ origin_loop_->PostTask(
+ FROM_HERE, base::Bind(&RunUnlessCanceled,
+ base::Bind(&HistoryDBTask::DoneRunOnMainThread,
+ base::Unretained(task_.get())),
+ is_canceled_));
+}
+
+// HistoryBackendHelper --------------------------------------------------------
+
+// Wrapper around base::SupportsUserData with a public destructor.
+class HistoryBackendHelper : public base::SupportsUserData {
+ public:
+ HistoryBackendHelper();
+ ~HistoryBackendHelper() override;
+};
+
+HistoryBackendHelper::HistoryBackendHelper() {
+}
+
+HistoryBackendHelper::~HistoryBackendHelper() {
+}
+
+// HistoryBackend --------------------------------------------------------------
+
+HistoryBackend::HistoryBackend(Delegate* delegate,
+ HistoryClient* history_client)
+ : delegate_(delegate),
+ scheduled_kill_db_(false),
+ expirer_(this, history_client),
+ recent_redirects_(kMaxRedirectCount),
+ backend_destroy_message_loop_(nullptr),
+ segment_queried_(false),
+ history_client_(history_client) {
+}
+
+HistoryBackend::~HistoryBackend() {
+ DCHECK(!scheduled_commit_.get()) << "Deleting without cleanup";
+ STLDeleteContainerPointers(queued_history_db_tasks_.begin(),
+ queued_history_db_tasks_.end());
+ queued_history_db_tasks_.clear();
+
+ // Release stashed embedder object before cleaning up the databases.
+ supports_user_data_helper_.reset();
+
+ // First close the databases before optionally running the "destroy" task.
+ CloseAllDatabases();
+
+ if (!backend_destroy_task_.is_null()) {
+ // Notify an interested party (typically a unit test) that we're done.
+ DCHECK(backend_destroy_message_loop_);
+ backend_destroy_message_loop_->PostTask(FROM_HERE, backend_destroy_task_);
+ }
+
+ if (history_client_ && !history_dir_.empty())
+ history_client_->OnHistoryBackendDestroyed(this, history_dir_);
+}
+
+void HistoryBackend::Init(
+ const std::string& languages,
+ bool force_fail,
+ const HistoryDatabaseParams& history_database_params) {
+ // HistoryBackend is created on the UI thread by HistoryService, then the
+ // HistoryBackend::Init() method is called on the DB thread. Create the
+ // base::SupportsUserData on the DB thread since it is not thread-safe.
+ supports_user_data_helper_.reset(new HistoryBackendHelper);
+
+ if (!force_fail)
+ InitImpl(languages, history_database_params);
+ delegate_->DBLoaded();
+ typed_url_syncable_service_.reset(new TypedUrlSyncableService(this));
+ memory_pressure_listener_.reset(new base::MemoryPressureListener(
+ base::Bind(&HistoryBackend::OnMemoryPressure, base::Unretained(this))));
+}
+
+void HistoryBackend::SetOnBackendDestroyTask(base::MessageLoop* message_loop,
+ const base::Closure& task) {
+ if (!backend_destroy_task_.is_null())
+ DLOG(WARNING) << "Setting more than one destroy task, overriding";
+ backend_destroy_message_loop_ = message_loop;
+ backend_destroy_task_ = task;
+}
+
+void HistoryBackend::Closing() {
+ // Any scheduled commit will have a reference to us, we must make it
+ // release that reference before we can be destroyed.
+ CancelScheduledCommit();
+
+ // Release our reference to the delegate, this reference will be keeping the
+ // history service alive.
+ delegate_.reset();
+}
+
+void HistoryBackend::ClearCachedDataForContextID(ContextID context_id) {
+ tracker_.ClearCachedDataForContextID(context_id);
+}
+
+base::FilePath HistoryBackend::GetThumbnailFileName() const {
+ return history_dir_.Append(history::kThumbnailsFilename);
+}
+
+base::FilePath HistoryBackend::GetFaviconsFileName() const {
+ return history_dir_.Append(history::kFaviconsFilename);
+}
+
+base::FilePath HistoryBackend::GetArchivedFileName() const {
+ return history_dir_.Append(history::kArchivedHistoryFilename);
+}
+
+SegmentID HistoryBackend::GetLastSegmentID(VisitID from_visit) {
+ // Set is used to detect referrer loops. Should not happen, but can
+ // if the database is corrupt.
+ std::set<VisitID> visit_set;
+ VisitID visit_id = from_visit;
+ while (visit_id) {
+ VisitRow row;
+ if (!db_->GetRowForVisit(visit_id, &row))
+ return 0;
+ if (row.segment_id)
+ return row.segment_id; // Found a visit in this change with a segment.
+
+ // Check the referrer of this visit, if any.
+ visit_id = row.referring_visit;
+
+ if (visit_set.find(visit_id) != visit_set.end()) {
+ NOTREACHED() << "Loop in referer chain, giving up";
+ break;
+ }
+ visit_set.insert(visit_id);
+ }
+ return 0;
+}
+
+SegmentID HistoryBackend::UpdateSegments(const GURL& url,
+ VisitID from_visit,
+ VisitID visit_id,
+ ui::PageTransition transition_type,
+ const Time ts) {
+ if (!db_)
+ return 0;
+
+ // We only consider main frames.
+ if (!ui::PageTransitionIsMainFrame(transition_type))
+ return 0;
+
+ SegmentID segment_id = 0;
+ ui::PageTransition t = ui::PageTransitionStripQualifier(transition_type);
+
+ // Are we at the beginning of a new segment?
+ // Note that navigating to an existing entry (with back/forward) reuses the
+ // same transition type. We are not adding it as a new segment in that case
+ // because if this was the target of a redirect, we might end up with
+ // 2 entries for the same final URL. Ex: User types google.net, gets
+ // redirected to google.com. A segment is created for google.net. On
+ // google.com users navigates through a link, then press back. That last
+ // navigation is for the entry google.com transition typed. We end up adding
+ // a segment for that one as well. So we end up with google.net and google.com
+ // in the segment table, showing as 2 entries in the NTP.
+ // Note also that we should still be updating the visit count for that segment
+ // which we are not doing now. It should be addressed when
+ // http://crbug.com/96860 is fixed.
+ if ((t == ui::PAGE_TRANSITION_TYPED ||
+ t == ui::PAGE_TRANSITION_AUTO_BOOKMARK) &&
+ (transition_type & ui::PAGE_TRANSITION_FORWARD_BACK) == 0) {
+ // If so, create or get the segment.
+ std::string segment_name = db_->ComputeSegmentName(url);
+ URLID url_id = db_->GetRowForURL(url, nullptr);
+ if (!url_id)
+ return 0;
+
+ segment_id = db_->GetSegmentNamed(segment_name);
+ if (!segment_id) {
+ segment_id = db_->CreateSegment(url_id, segment_name);
+ if (!segment_id) {
+ NOTREACHED();
+ return 0;
+ }
+ } else {
+ // Note: if we update an existing segment, we update the url used to
+ // represent that segment in order to minimize stale most visited
+ // images.
+ db_->UpdateSegmentRepresentationURL(segment_id, url_id);
+ }
+ } else {
+ // Note: it is possible there is no segment ID set for this visit chain.
+ // This can happen if the initial navigation wasn't AUTO_BOOKMARK or
+ // TYPED. (For example GENERATED). In this case this visit doesn't count
+ // toward any segment.
+ segment_id = GetLastSegmentID(from_visit);
+ if (!segment_id)
+ return 0;
+ }
+
+ // Set the segment in the visit.
+ if (!db_->SetSegmentID(visit_id, segment_id)) {
+ NOTREACHED();
+ return 0;
+ }
+
+ // Finally, increase the counter for that segment / day.
+ if (!db_->IncreaseSegmentVisitCount(segment_id, ts, 1)) {
+ NOTREACHED();
+ return 0;
+ }
+ return segment_id;
+}
+
+void HistoryBackend::UpdateWithPageEndTime(ContextID context_id,
+ int nav_entry_id,
+ const GURL& url,
+ Time end_ts) {
+ // Will be filled with the URL ID and the visit ID of the last addition.
+ VisitID visit_id = tracker_.GetLastVisit(context_id, nav_entry_id, url);
+ UpdateVisitDuration(visit_id, end_ts);
+}
+
+void HistoryBackend::UpdateVisitDuration(VisitID visit_id, const Time end_ts) {
+ if (!db_)
+ return;
+
+ // Get the starting visit_time for visit_id.
+ VisitRow visit_row;
+ if (db_->GetRowForVisit(visit_id, &visit_row)) {
+ // We should never have a negative duration time even when time is skewed.
+ visit_row.visit_duration = end_ts > visit_row.visit_time
+ ? end_ts - visit_row.visit_time
+ : TimeDelta::FromMicroseconds(0);
+ db_->UpdateVisitRow(visit_row);
+ }
+}
+
+void HistoryBackend::AddPage(const HistoryAddPageArgs& request) {
+ if (!db_)
+ return;
+
+ // Will be filled with the URL ID and the visit ID of the last addition.
+ std::pair<URLID, VisitID> last_ids(
+ 0, tracker_.GetLastVisit(request.context_id, request.nav_entry_id,
+ request.referrer));
+
+ VisitID from_visit_id = last_ids.second;
+
+ // If a redirect chain is given, we expect the last item in that chain to be
+ // the final URL.
+ DCHECK(request.redirects.empty() || request.redirects.back() == request.url);
+
+ // If the user is adding older history, we need to make sure our times
+ // are correct.
+ if (request.time < first_recorded_time_)
+ first_recorded_time_ = request.time;
+
+ ui::PageTransition request_transition = request.transition;
+ ui::PageTransition stripped_transition =
+ ui::PageTransitionStripQualifier(request_transition);
+ bool is_keyword_generated =
+ (stripped_transition == ui::PAGE_TRANSITION_KEYWORD_GENERATED);
+
+ // If the user is navigating to a not-previously-typed intranet hostname,
+ // change the transition to TYPED so that the omnibox will learn that this is
+ // a known host.
+ bool has_redirects = request.redirects.size() > 1;
+ if (ui::PageTransitionIsMainFrame(request_transition) &&
+ (stripped_transition != ui::PAGE_TRANSITION_TYPED) &&
+ !is_keyword_generated) {
+ const GURL& origin_url(has_redirects ? request.redirects[0] : request.url);
+ if (origin_url.SchemeIs(url::kHttpScheme) ||
+ origin_url.SchemeIs(url::kHttpsScheme) ||
+ origin_url.SchemeIs(url::kFtpScheme)) {
+ std::string host(origin_url.host());
+ size_t registry_length =
+ net::registry_controlled_domains::GetRegistryLength(
+ host,
+ net::registry_controlled_domains::EXCLUDE_UNKNOWN_REGISTRIES,
+ net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES);
+ if (registry_length == 0 && !db_->IsTypedHost(host)) {
+ stripped_transition = ui::PAGE_TRANSITION_TYPED;
+ request_transition = ui::PageTransitionFromInt(
+ stripped_transition |
+ ui::PageTransitionGetQualifier(request_transition));
+ }
+ }
+ }
+
+ if (!has_redirects) {
+ // The single entry is both a chain start and end.
+ ui::PageTransition t = ui::PageTransitionFromInt(
+ request_transition | ui::PAGE_TRANSITION_CHAIN_START |
+ ui::PAGE_TRANSITION_CHAIN_END);
+
+ // No redirect case (one element means just the page itself).
+ last_ids = AddPageVisit(request.url, request.time, last_ids.second, t,
+ request.visit_source);
+
+ // Update the segment for this visit. KEYWORD_GENERATED visits should not
+ // result in changing most visited, so we don't update segments (most
+ // visited db).
+ if (!is_keyword_generated) {
+ UpdateSegments(request.url, from_visit_id, last_ids.second, t,
+ request.time);
+
+ // Update the referrer's duration.
+ UpdateVisitDuration(from_visit_id, request.time);
+ }
+ } else {
+ // Redirect case. Add the redirect chain.
+
+ ui::PageTransition redirect_info = ui::PAGE_TRANSITION_CHAIN_START;
+
+ RedirectList redirects = request.redirects;
+ if (redirects[0].SchemeIs(url::kAboutScheme)) {
+ // When the redirect source + referrer is "about" we skip it. This
+ // happens when a page opens a new frame/window to about:blank and then
+ // script sets the URL to somewhere else (used to hide the referrer). It
+ // would be nice to keep all these redirects properly but we don't ever
+ // see the initial about:blank load, so we don't know where the
+ // subsequent client redirect came from.
+ //
+ // In this case, we just don't bother hooking up the source of the
+ // redirects, so we remove it.
+ redirects.erase(redirects.begin());
+ } else if (request_transition & ui::PAGE_TRANSITION_CLIENT_REDIRECT) {
+ redirect_info = ui::PAGE_TRANSITION_CLIENT_REDIRECT;
+ // The first entry in the redirect chain initiated a client redirect.
+ // We don't add this to the database since the referrer is already
+ // there, so we skip over it but change the transition type of the first
+ // transition to client redirect.
+ //
+ // The referrer is invalid when restoring a session that features an
+ // https tab that redirects to a different host or to http. In this
+ // case we don't need to reconnect the new redirect with the existing
+ // chain.
+ if (request.referrer.is_valid()) {
+ DCHECK(request.referrer == redirects[0]);
+ redirects.erase(redirects.begin());
+
+ // If the navigation entry for this visit has replaced that for the
+ // first visit, remove the CHAIN_END marker from the first visit. This
+ // can be called a lot, for example, the page cycler, and most of the
+ // time we won't have changed anything.
+ VisitRow visit_row;
+ if (request.did_replace_entry &&
+ db_->GetRowForVisit(last_ids.second, &visit_row) &&
+ visit_row.transition & ui::PAGE_TRANSITION_CHAIN_END) {
+ visit_row.transition = ui::PageTransitionFromInt(
+ visit_row.transition & ~ui::PAGE_TRANSITION_CHAIN_END);
+ db_->UpdateVisitRow(visit_row);
+ }
+ }
+ }
+
+ for (size_t redirect_index = 0; redirect_index < redirects.size();
+ redirect_index++) {
+ ui::PageTransition t =
+ ui::PageTransitionFromInt(stripped_transition | redirect_info);
+
+ // If this is the last transition, add a CHAIN_END marker
+ if (redirect_index == (redirects.size() - 1)) {
+ t = ui::PageTransitionFromInt(t | ui::PAGE_TRANSITION_CHAIN_END);
+ }
+
+ // Record all redirect visits with the same timestamp. We don't display
+ // them anyway, and if we ever decide to, we can reconstruct their order
+ // from the redirect chain.
+ last_ids = AddPageVisit(redirects[redirect_index], request.time,
+ last_ids.second, t, request.visit_source);
+ if (t & ui::PAGE_TRANSITION_CHAIN_START) {
+ // Update the segment for this visit.
+ UpdateSegments(redirects[redirect_index], from_visit_id,
+ last_ids.second, t, request.time);
+
+ // Update the visit_details for this visit.
+ UpdateVisitDuration(from_visit_id, request.time);
+ }
+
+ // Subsequent transitions in the redirect list must all be server
+ // redirects.
+ redirect_info = ui::PAGE_TRANSITION_SERVER_REDIRECT;
+ }
+
+ // Last, save this redirect chain for later so we can set titles & favicons
+ // on the redirected pages properly.
+ recent_redirects_.Put(request.url, redirects);
+ }
+
+ // TODO(brettw) bug 1140015: Add an "add page" notification so the history
+ // views can keep in sync.
+
+ // Add the last visit to the tracker so we can get outgoing transitions.
+ // TODO(evanm): Due to http://b/1194536 we lose the referrers of a subframe
+ // navigation anyway, so last_visit_id is always zero for them. But adding
+ // them here confuses main frame history, so we skip them for now.
+ if (stripped_transition != ui::PAGE_TRANSITION_AUTO_SUBFRAME &&
+ stripped_transition != ui::PAGE_TRANSITION_MANUAL_SUBFRAME &&
+ !is_keyword_generated) {
+ tracker_.AddVisit(request.context_id, request.nav_entry_id, request.url,
+ last_ids.second);
+ }
+
+ ScheduleCommit();
+}
+
+void HistoryBackend::InitImpl(
+ const std::string& languages,
+ const HistoryDatabaseParams& history_database_params) {
+ DCHECK(!db_) << "Initializing HistoryBackend twice";
+ // In the rare case where the db fails to initialize a dialog may get shown
+ // the blocks the caller, yet allows other messages through. For this reason
+ // we only set db_ to the created database if creation is successful. That
+ // way other methods won't do anything as db_ is still null.
+
+ TimeTicks beginning_time = TimeTicks::Now();
+
+ // Compute the file names.
+ history_dir_ = history_database_params.history_dir;
+ base::FilePath history_name = history_dir_.Append(history::kHistoryFilename);
+ base::FilePath thumbnail_name = GetFaviconsFileName();
+ base::FilePath archived_name = GetArchivedFileName();
+
+ // Delete the old index database files which are no longer used.
+ DeleteFTSIndexDatabases();
+
+ // History database.
+ db_.reset(new HistoryDatabase(
+ history_database_params.download_interrupt_reason_none,
+ history_database_params.download_interrupt_reason_crash));
+
+ // Unretained to avoid a ref loop with db_.
+ db_->set_error_callback(base::Bind(&HistoryBackend::DatabaseErrorCallback,
+ base::Unretained(this)));
+
+ sql::InitStatus status = db_->Init(history_name);
+ switch (status) {
+ case sql::INIT_OK:
+ break;
+ case sql::INIT_FAILURE: {
+ // A null db_ will cause all calls on this object to notice this error
+ // and to not continue. If the error callback scheduled killing the
+ // database, the task it posted has not executed yet. Try killing the
+ // database now before we close it.
+ bool kill_db = scheduled_kill_db_;
+ if (kill_db)
+ KillHistoryDatabase();
+ UMA_HISTOGRAM_BOOLEAN("History.AttemptedToFixProfileError", kill_db);
+ delegate_->NotifyProfileError(status);
+ db_.reset();
+ return;
+ }
+ default:
+ NOTREACHED();
+ }
+
+ // Fill the in-memory database and send it back to the history service on the
+ // main thread.
+ {
+ scoped_ptr<InMemoryHistoryBackend> mem_backend(new InMemoryHistoryBackend);
+ if (mem_backend->Init(history_name))
+ delegate_->SetInMemoryBackend(mem_backend.Pass());
+ }
+ db_->BeginExclusiveMode(); // Must be after the mem backend read the data.
+
+ // Thumbnail database.
+ // TODO(shess): "thumbnail database" these days only stores
+ // favicons. Thumbnails are stored in "top sites". Consider
+ // renaming "thumbnail" references to "favicons" or something of the
+ // sort.
+ thumbnail_db_.reset(new ThumbnailDatabase(history_client_));
+ if (thumbnail_db_->Init(thumbnail_name) != sql::INIT_OK) {
+ // Unlike the main database, we don't error out when the database is too
+ // new because this error is much less severe. Generally, this shouldn't
+ // happen since the thumbnail and main database versions should be in sync.
+ // We'll just continue without thumbnails & favicons in this case or any
+ // other error.
+ LOG(WARNING) << "Could not initialize the thumbnail database.";
+ thumbnail_db_.reset();
+ }
+
+ // Nuke any files corresponding to the legacy Archived History Database, which
+ // previously retained expired (> 3 months old) history entries, but, in the
+ // end, was not used for much, and consequently has been removed as of M37.
+ // TODO(engedy): Remove this code after the end of 2014.
+ sql::Connection::Delete(archived_name);
+
+ // Generate the history and thumbnail database metrics only after performing
+ // any migration work.
+ if (base::RandInt(1, 100) == 50) {
+ // Only do this computation sometimes since it can be expensive.
+ db_->ComputeDatabaseMetrics(history_name);
+ if (thumbnail_db_)
+ thumbnail_db_->ComputeDatabaseMetrics();
+ }
+
+ expirer_.SetDatabases(db_.get(), thumbnail_db_.get());
+
+ // Open the long-running transaction.
+ db_->BeginTransaction();
+ if (thumbnail_db_)
+ thumbnail_db_->BeginTransaction();
+
+ // Get the first item in our database.
+ db_->GetStartDate(&first_recorded_time_);
+
+ // Start expiring old stuff.
+ expirer_.StartExpiringOldStuff(TimeDelta::FromDays(kExpireDaysThreshold));
+
+ if (history_client_) {
+ history_client_->OnHistoryBackendInitialized(
+ this, db_.get(), thumbnail_db_.get(), history_dir_);
+ }
+
+ LOCAL_HISTOGRAM_TIMES("History.InitTime", TimeTicks::Now() - beginning_time);
+}
+
+void HistoryBackend::OnMemoryPressure(
+ base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level) {
+ bool trim_aggressively =
+ memory_pressure_level ==
+ base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL;
+ if (db_)
+ db_->TrimMemory(trim_aggressively);
+ if (thumbnail_db_)
+ thumbnail_db_->TrimMemory(trim_aggressively);
+}
+
+void HistoryBackend::CloseAllDatabases() {
+ if (db_) {
+ // Commit the long-running transaction.
+ db_->CommitTransaction();
+ db_.reset();
+ // Forget the first recorded time since the database is closed.
+ first_recorded_time_ = base::Time();
+ }
+ if (thumbnail_db_) {
+ thumbnail_db_->CommitTransaction();
+ thumbnail_db_.reset();
+ }
+}
+
+std::pair<URLID, VisitID> HistoryBackend::AddPageVisit(
+ const GURL& url,
+ Time time,
+ VisitID referring_visit,
+ ui::PageTransition transition,
+ VisitSource visit_source) {
+ // Top-level frame navigations are visible, everything else is hidden
+ bool new_hidden = !ui::PageTransitionIsMainFrame(transition);
+
+ // NOTE: This code must stay in sync with
+ // ExpireHistoryBackend::ExpireURLsForVisits().
+ // TODO(pkasting): http://b/1148304 We shouldn't be marking so many URLs as
+ // typed, which would eliminate the need for this code.
+ int typed_increment = 0;
+ ui::PageTransition transition_type =
+ ui::PageTransitionStripQualifier(transition);
+ if ((transition_type == ui::PAGE_TRANSITION_TYPED &&
+ !ui::PageTransitionIsRedirect(transition)) ||
+ transition_type == ui::PAGE_TRANSITION_KEYWORD_GENERATED)
+ typed_increment = 1;
+
+ // See if this URL is already in the DB.
+ URLRow url_info(url);
+ URLID url_id = db_->GetRowForURL(url, &url_info);
+ if (url_id) {
+ // Update of an existing row.
+ if (ui::PageTransitionStripQualifier(transition) !=
+ ui::PAGE_TRANSITION_RELOAD)
+ url_info.set_visit_count(url_info.visit_count() + 1);
+ if (typed_increment)
+ url_info.set_typed_count(url_info.typed_count() + typed_increment);
+ if (url_info.last_visit() < time)
+ url_info.set_last_visit(time);
+
+ // Only allow un-hiding of pages, never hiding.
+ if (!new_hidden)
+ url_info.set_hidden(false);
+
+ db_->UpdateURLRow(url_id, url_info);
+ } else {
+ // Addition of a new row.
+ url_info.set_visit_count(1);
+ url_info.set_typed_count(typed_increment);
+ url_info.set_last_visit(time);
+ url_info.set_hidden(new_hidden);
+
+ url_id = db_->AddURL(url_info);
+ if (!url_id) {
+ NOTREACHED() << "Adding URL failed.";
+ return std::make_pair(0, 0);
+ }
+ url_info.id_ = url_id;
+ }
+
+ // Add the visit with the time to the database.
+ VisitRow visit_info(url_id, time, referring_visit, transition, 0);
+ VisitID visit_id = db_->AddVisit(&visit_info, visit_source);
+ NotifyVisitObservers(visit_info);
+
+ if (visit_info.visit_time < first_recorded_time_)
+ first_recorded_time_ = visit_info.visit_time;
+
+ // Broadcast a notification of the visit.
+ if (visit_id) {
+ RedirectList redirects;
+ // TODO(meelapshah) Disabled due to potential PageCycler regression.
+ // Re-enable this.
+ // QueryRedirectsTo(url, &redirects);
+ NotifyURLVisited(transition, url_info, redirects, time);
+ } else {
+ DVLOG(0) << "Failed to build visit insert statement: "
+ << "url_id = " << url_id;
+ }
+
+ return std::make_pair(url_id, visit_id);
+}
+
+void HistoryBackend::AddPagesWithDetails(const URLRows& urls,
+ VisitSource visit_source) {
+ if (!db_)
+ return;
+
+ URLRows changed_urls;
+ for (URLRows::const_iterator i = urls.begin(); i != urls.end(); ++i) {
+ DCHECK(!i->last_visit().is_null());
+
+ // As of M37, we no longer maintain an archived database, ignore old visits.
+ if (IsExpiredVisitTime(i->last_visit()))
+ continue;
+
+ URLRow existing_url;
+ URLID url_id = db_->GetRowForURL(i->url(), &existing_url);
+ if (!url_id) {
+ // Add the page if it doesn't exist.
+ url_id = db_->AddURL(*i);
+ if (!url_id) {
+ NOTREACHED() << "Could not add row to DB";
+ return;
+ }
+
+ changed_urls.push_back(*i);
+ changed_urls.back().set_id(url_id); // i->id_ is likely 0.
+ }
+
+ // Sync code manages the visits itself.
+ if (visit_source != SOURCE_SYNCED) {
+ // Make up a visit to correspond to the last visit to the page.
+ VisitRow visit_info(
+ url_id, i->last_visit(), 0,
+ ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
+ ui::PAGE_TRANSITION_CHAIN_START |
+ ui::PAGE_TRANSITION_CHAIN_END),
+ 0);
+ if (!db_->AddVisit(&visit_info, visit_source)) {
+ NOTREACHED() << "Adding visit failed.";
+ return;
+ }
+ NotifyVisitObservers(visit_info);
+
+ if (visit_info.visit_time < first_recorded_time_)
+ first_recorded_time_ = visit_info.visit_time;
+ }
+ }
+
+ // Broadcast a notification for typed URLs that have been modified. This
+ // will be picked up by the in-memory URL database on the main thread.
+ //
+ // TODO(brettw) bug 1140015: Add an "add page" notification so the history
+ // views can keep in sync.
+ NotifyURLsModified(changed_urls);
+ ScheduleCommit();
+}
+
+bool HistoryBackend::IsExpiredVisitTime(const base::Time& time) {
+ return time < expirer_.GetCurrentExpirationTime();
+}
+
+void HistoryBackend::SetPageTitle(const GURL& url,
+ const base::string16& title) {
+ if (!db_)
+ return;
+
+ // Search for recent redirects which should get the same title. We make a
+ // dummy list containing the exact URL visited if there are no redirects so
+ // the processing below can be the same.
+ history::RedirectList dummy_list;
+ history::RedirectList* redirects;
+ RedirectCache::iterator iter = recent_redirects_.Get(url);
+ if (iter != recent_redirects_.end()) {
+ redirects = &iter->second;
+
+ // This redirect chain should have the destination URL as the last item.
+ DCHECK(!redirects->empty());
+ DCHECK(redirects->back() == url);
+ } else {
+ // No redirect chain stored, make up one containing the URL we want so we
+ // can use the same logic below.
+ dummy_list.push_back(url);
+ redirects = &dummy_list;
+ }
+
+ URLRows changed_urls;
+ for (size_t i = 0; i < redirects->size(); i++) {
+ URLRow row;
+ URLID row_id = db_->GetRowForURL(redirects->at(i), &row);
+ if (row_id && row.title() != title) {
+ row.set_title(title);
+ db_->UpdateURLRow(row_id, row);
+ changed_urls.push_back(row);
+ }
+ }
+
+ // Broadcast notifications for any URLs that have changed. This will
+ // update the in-memory database and the InMemoryURLIndex.
+ if (!changed_urls.empty()) {
+ NotifyURLsModified(changed_urls);
+ ScheduleCommit();
+ }
+}
+
+void HistoryBackend::AddPageNoVisitForBookmark(const GURL& url,
+ const base::string16& title) {
+ if (!db_)
+ return;
+
+ URLRow url_info(url);
+ URLID url_id = db_->GetRowForURL(url, &url_info);
+ if (url_id) {
+ // URL is already known, nothing to do.
+ return;
+ }
+
+ if (!title.empty()) {
+ url_info.set_title(title);
+ } else {
+ url_info.set_title(base::UTF8ToUTF16(url.spec()));
+ }
+
+ url_info.set_last_visit(Time::Now());
+ // Mark the page hidden. If the user types it in, it'll unhide.
+ url_info.set_hidden(true);
+
+ db_->AddURL(url_info);
+}
+
+bool HistoryBackend::GetAllTypedURLs(URLRows* urls) {
+ if (db_)
+ return db_->GetAllTypedUrls(urls);
+ return false;
+}
+
+bool HistoryBackend::GetVisitsForURL(URLID id, VisitVector* visits) {
+ if (db_)
+ return db_->GetVisitsForURL(id, visits);
+ return false;
+}
+
+bool HistoryBackend::GetMostRecentVisitsForURL(URLID id,
+ int max_visits,
+ VisitVector* visits) {
+ if (db_)
+ return db_->GetMostRecentVisitsForURL(id, max_visits, visits);
+ return false;
+}
+
+size_t HistoryBackend::UpdateURLs(const history::URLRows& urls) {
+ if (!db_)
+ return 0;
+
+ URLRows changed_urls;
+ for (history::URLRows::const_iterator it = urls.begin(); it != urls.end();
+ ++it) {
+ DCHECK(it->id());
+ if (db_->UpdateURLRow(it->id(), *it))
+ changed_urls.push_back(*it);
+ }
+
+ // Broadcast notifications for any URLs that have actually been changed. This
+ // will update the in-memory database and the InMemoryURLIndex.
+ size_t num_updated_records = changed_urls.size();
+ if (num_updated_records) {
+ NotifyURLsModified(changed_urls);
+ ScheduleCommit();
+ }
+ return num_updated_records;
+}
+
+bool HistoryBackend::AddVisits(const GURL& url,
+ const std::vector<VisitInfo>& visits,
+ VisitSource visit_source) {
+ if (db_) {
+ for (std::vector<VisitInfo>::const_iterator visit = visits.begin();
+ visit != visits.end(); ++visit) {
+ if (!AddPageVisit(url, visit->first, 0, visit->second, visit_source)
+ .first) {
+ return false;
+ }
+ }
+ ScheduleCommit();
+ return true;
+ }
+ return false;
+}
+
+bool HistoryBackend::RemoveVisits(const VisitVector& visits) {
+ if (!db_)
+ return false;
+
+ expirer_.ExpireVisits(visits);
+ ScheduleCommit();
+ return true;
+}
+
+bool HistoryBackend::GetVisitsSource(const VisitVector& visits,
+ VisitSourceMap* sources) {
+ if (!db_)
+ return false;
+
+ db_->GetVisitsSource(visits, sources);
+ return true;
+}
+
+bool HistoryBackend::GetURL(const GURL& url, history::URLRow* url_row) {
+ if (db_)
+ return db_->GetRowForURL(url, url_row) != 0;
+ return false;
+}
+
+void HistoryBackend::QueryURL(const GURL& url,
+ bool want_visits,
+ QueryURLResult* result) {
+ DCHECK(result);
+ result->success = db_ && db_->GetRowForURL(url, &result->row);
+ // Optionally query the visits.
+ if (result->success && want_visits)
+ db_->GetVisitsForURL(result->row.id(), &result->visits);
+}
+
+TypedUrlSyncableService* HistoryBackend::GetTypedUrlSyncableService() const {
+ return typed_url_syncable_service_.get();
+}
+
+// Keyword visits --------------------------------------------------------------
+
+void HistoryBackend::SetKeywordSearchTermsForURL(const GURL& url,
+ KeywordID keyword_id,
+ const base::string16& term) {
+ if (!db_)
+ return;
+
+ // Get the ID for this URL.
+ URLRow row;
+ if (!db_->GetRowForURL(url, &row)) {
+ // There is a small possibility the url was deleted before the keyword
+ // was added. Ignore the request.
+ return;
+ }
+
+ db_->SetKeywordSearchTermsForURL(row.id(), keyword_id, term);
+
+ if (delegate_)
+ delegate_->NotifyKeywordSearchTermUpdated(row, keyword_id, term);
+
+ ScheduleCommit();
+}
+
+void HistoryBackend::DeleteAllSearchTermsForKeyword(KeywordID keyword_id) {
+ if (!db_)
+ return;
+
+ db_->DeleteAllSearchTermsForKeyword(keyword_id);
+ ScheduleCommit();
+}
+
+void HistoryBackend::DeleteKeywordSearchTermForURL(const GURL& url) {
+ if (!db_)
+ return;
+
+ URLID url_id = db_->GetRowForURL(url, nullptr);
+ if (!url_id)
+ return;
+ db_->DeleteKeywordSearchTermForURL(url_id);
+
+ if (delegate_)
+ delegate_->NotifyKeywordSearchTermDeleted(url_id);
+
+ ScheduleCommit();
+}
+
+void HistoryBackend::DeleteMatchingURLsForKeyword(KeywordID keyword_id,
+ const base::string16& term) {
+ if (!db_)
+ return;
+
+ std::vector<KeywordSearchTermRow> rows;
+ if (db_->GetKeywordSearchTermRows(term, &rows)) {
+ std::vector<GURL> items_to_delete;
+ URLRow row;
+ for (std::vector<KeywordSearchTermRow>::iterator it = rows.begin();
+ it != rows.end(); ++it) {
+ if ((it->keyword_id == keyword_id) && db_->GetURLRow(it->url_id, &row))
+ items_to_delete.push_back(row.url());
+ }
+ DeleteURLs(items_to_delete);
+ }
+}
+
+// Observers -------------------------------------------------------------------
+
+void HistoryBackend::AddObserver(HistoryBackendObserver* observer) {
+ observers_.AddObserver(observer);
+}
+
+void HistoryBackend::RemoveObserver(HistoryBackendObserver* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+// Downloads -------------------------------------------------------------------
+
+uint32 HistoryBackend::GetNextDownloadId() {
+ return db_ ? db_->GetNextDownloadId() : kInvalidDownloadId;
+}
+
+// Get all the download entries from the database.
+void HistoryBackend::QueryDownloads(std::vector<DownloadRow>* rows) {
+ if (db_)
+ db_->QueryDownloads(rows);
+}
+
+// Update a particular download entry.
+void HistoryBackend::UpdateDownload(const history::DownloadRow& data) {
+ if (!db_)
+ return;
+ db_->UpdateDownload(data);
+ ScheduleCommit();
+}
+
+bool HistoryBackend::CreateDownload(const history::DownloadRow& history_info) {
+ if (!db_)
+ return false;
+ bool success = db_->CreateDownload(history_info);
+ ScheduleCommit();
+ return success;
+}
+
+void HistoryBackend::RemoveDownloads(const std::set<uint32>& ids) {
+ if (!db_)
+ return;
+ size_t downloads_count_before = db_->CountDownloads();
+ base::TimeTicks started_removing = base::TimeTicks::Now();
+ // HistoryBackend uses a long-running Transaction that is committed
+ // periodically, so this loop doesn't actually hit the disk too hard.
+ for (std::set<uint32>::const_iterator it = ids.begin(); it != ids.end();
+ ++it) {
+ db_->RemoveDownload(*it);
+ }
+ ScheduleCommit();
+ base::TimeTicks finished_removing = base::TimeTicks::Now();
+ size_t downloads_count_after = db_->CountDownloads();
+
+ DCHECK_LE(downloads_count_after, downloads_count_before);
+ if (downloads_count_after > downloads_count_before)
+ return;
+ size_t num_downloads_deleted = downloads_count_before - downloads_count_after;
+ UMA_HISTOGRAM_COUNTS("Download.DatabaseRemoveDownloadsCount",
+ num_downloads_deleted);
+ base::TimeDelta micros = (1000 * (finished_removing - started_removing));
+ UMA_HISTOGRAM_TIMES("Download.DatabaseRemoveDownloadsTime", micros);
+ if (num_downloads_deleted > 0) {
+ UMA_HISTOGRAM_TIMES("Download.DatabaseRemoveDownloadsTimePerRecord",
+ (1000 * micros) / num_downloads_deleted);
+ }
+ DCHECK_GE(ids.size(), num_downloads_deleted);
+ if (ids.size() < num_downloads_deleted)
+ return;
+ UMA_HISTOGRAM_COUNTS("Download.DatabaseRemoveDownloadsCountNotRemoved",
+ ids.size() - num_downloads_deleted);
+}
+
+void HistoryBackend::QueryHistory(const base::string16& text_query,
+ const QueryOptions& options,
+ QueryResults* query_results) {
+ DCHECK(query_results);
+ base::TimeTicks beginning_time = base::TimeTicks::Now();
+ if (db_) {
+ if (text_query.empty()) {
+ // Basic history query for the main database.
+ QueryHistoryBasic(options, query_results);
+ } else {
+ // Text history query.
+ QueryHistoryText(text_query, options, query_results);
+ }
+ }
+ UMA_HISTOGRAM_TIMES("History.QueryHistory",
+ TimeTicks::Now() - beginning_time);
+}
+
+// Basic time-based querying of history.
+void HistoryBackend::QueryHistoryBasic(const QueryOptions& options,
+ QueryResults* result) {
+ // First get all visits.
+ VisitVector visits;
+ bool has_more_results = db_->GetVisibleVisitsInRange(options, &visits);
+ DCHECK(static_cast<int>(visits.size()) <= options.EffectiveMaxCount());
+
+ // Now add them and the URL rows to the results.
+ URLResult url_result;
+ for (size_t i = 0; i < visits.size(); i++) {
+ const VisitRow visit = visits[i];
+
+ // Add a result row for this visit, get the URL info from the DB.
+ if (!db_->GetURLRow(visit.url_id, &url_result)) {
+ DVLOG(0) << "Failed to get id " << visit.url_id << " from history.urls.";
+ continue; // DB out of sync and URL doesn't exist, try to recover.
+ }
+
+ if (!url_result.url().is_valid()) {
+ DVLOG(0) << "Got invalid URL from history.urls with id " << visit.url_id
+ << ": " << url_result.url().possibly_invalid_spec();
+ continue; // Don't report invalid URLs in case of corruption.
+ }
+
+ url_result.set_visit_time(visit.visit_time);
+
+ // Set whether the visit was blocked for a managed user by looking at the
+ // transition type.
+ url_result.set_blocked_visit(
+ (visit.transition & ui::PAGE_TRANSITION_BLOCKED) != 0);
+
+ // We don't set any of the query-specific parts of the URLResult, since
+ // snippets and stuff don't apply to basic querying.
+ result->AppendURLBySwapping(&url_result);
+ }
+
+ if (!has_more_results && options.begin_time <= first_recorded_time_)
+ result->set_reached_beginning(true);
+}
+
+// Text-based querying of history.
+void HistoryBackend::QueryHistoryText(const base::string16& text_query,
+ const QueryOptions& options,
+ QueryResults* result) {
+ URLRows text_matches;
+ db_->GetTextMatches(text_query, &text_matches);
+
+ std::vector<URLResult> matching_visits;
+ VisitVector visits; // Declare outside loop to prevent re-construction.
+ for (size_t i = 0; i < text_matches.size(); i++) {
+ const URLRow& text_match = text_matches[i];
+ // Get all visits for given URL match.
+ db_->GetVisibleVisitsForURL(text_match.id(), options, &visits);
+ for (size_t j = 0; j < visits.size(); j++) {
+ URLResult url_result(text_match);
+ url_result.set_visit_time(visits[j].visit_time);
+ matching_visits.push_back(url_result);
+ }
+ }
+
+ std::sort(matching_visits.begin(), matching_visits.end(),
+ URLResult::CompareVisitTime);
+
+ size_t max_results = options.max_count == 0
+ ? std::numeric_limits<size_t>::max()
+ : static_cast<int>(options.max_count);
+ for (std::vector<URLResult>::iterator it = matching_visits.begin();
+ it != matching_visits.end() && result->size() < max_results; ++it) {
+ result->AppendURLBySwapping(&(*it));
+ }
+
+ if (matching_visits.size() == result->size() &&
+ options.begin_time <= first_recorded_time_)
+ result->set_reached_beginning(true);
+}
+
+void HistoryBackend::QueryRedirectsFrom(const GURL& from_url,
+ RedirectList* redirects) {
+ redirects->clear();
+ if (!db_)
+ return;
+
+ URLID from_url_id = db_->GetRowForURL(from_url, nullptr);
+ VisitID cur_visit = db_->GetMostRecentVisitForURL(from_url_id, nullptr);
+ if (!cur_visit)
+ return; // No visits for URL.
+
+ GetRedirectsFromSpecificVisit(cur_visit, redirects);
+}
+
+void HistoryBackend::QueryRedirectsTo(const GURL& to_url,
+ RedirectList* redirects) {
+ redirects->clear();
+ if (!db_)
+ return;
+
+ URLID to_url_id = db_->GetRowForURL(to_url, nullptr);
+ VisitID cur_visit = db_->GetMostRecentVisitForURL(to_url_id, nullptr);
+ if (!cur_visit)
+ return; // No visits for URL.
+
+ GetRedirectsToSpecificVisit(cur_visit, redirects);
+}
+
+void HistoryBackend::GetVisibleVisitCountToHost(
+ const GURL& url,
+ VisibleVisitCountToHostResult* result) {
+ result->count = 0;
+ result->success = db_.get() &&
+ db_->GetVisibleVisitCountToHost(url, &result->count,
+ &result->first_visit);
+}
+
+void HistoryBackend::QueryMostVisitedURLs(int result_count,
+ int days_back,
+ MostVisitedURLList* result) {
+ if (!db_)
+ return;
+
+ ScopedVector<PageUsageData> data;
+ db_->QuerySegmentUsage(
+ base::Time::Now() - base::TimeDelta::FromDays(days_back), result_count,
+ &data.get());
+
+ for (size_t i = 0; i < data.size(); ++i) {
+ PageUsageData* current_data = data[i];
+ RedirectList redirects;
+ QueryRedirectsFrom(current_data->GetURL(), &redirects);
+ MostVisitedURL url = MakeMostVisitedURL(*current_data, redirects);
+ result->push_back(url);
+ }
+}
+
+void HistoryBackend::QueryFilteredURLs(int result_count,
+ const history::VisitFilter& filter,
+ bool extended_info,
+ history::FilteredURLList* result) {
+ DCHECK(result);
+ base::Time request_start = base::Time::Now();
+
+ result->clear();
+ if (!db_) {
+ // No History Database - return an empty list.
+ return;
+ }
+
+ VisitVector visits;
+ db_->GetDirectVisitsDuringTimes(filter, 0, &visits);
+
+ std::map<URLID, double> score_map;
+ for (size_t i = 0; i < visits.size(); ++i) {
+ score_map[visits[i].url_id] += filter.GetVisitScore(visits[i]);
+ }
+
+ // TODO(georgey): experiment with visit_segment database granularity (it is
+ // currently 24 hours) to use it directly instead of using visits database,
+ // which is considerably slower.
+ ScopedVector<PageUsageData> data;
+ data.reserve(score_map.size());
+ for (std::map<URLID, double>::iterator it = score_map.begin();
+ it != score_map.end(); ++it) {
+ PageUsageData* pud = new PageUsageData(it->first);
+ pud->SetScore(it->second);
+ data.push_back(pud);
+ }
+
+ // Limit to the top |result_count| results.
+ std::sort(data.begin(), data.end(), PageUsageData::Predicate);
+ if (result_count && implicit_cast<int>(data.size()) > result_count)
+ data.resize(result_count);
+
+ for (size_t i = 0; i < data.size(); ++i) {
+ URLRow info;
+ if (db_->GetURLRow(data[i]->GetID(), &info)) {
+ data[i]->SetURL(info.url());
+ data[i]->SetTitle(info.title());
+ }
+ }
+
+ for (size_t i = 0; i < data.size(); ++i) {
+ PageUsageData* current_data = data[i];
+ FilteredURL url(*current_data);
+
+ if (extended_info) {
+ VisitVector visits;
+ db_->GetVisitsForURL(current_data->GetID(), &visits);
+ if (visits.size() > 0) {
+ url.extended_info.total_visits = visits.size();
+ for (size_t i = 0; i < visits.size(); ++i) {
+ url.extended_info.duration_opened +=
+ visits[i].visit_duration.InSeconds();
+ if (visits[i].visit_time > url.extended_info.last_visit_time) {
+ url.extended_info.last_visit_time = visits[i].visit_time;
+ }
+ }
+ // TODO(macourteau): implement the url.extended_info.visits stat.
+ }
+ }
+ result->push_back(url);
+ }
+
+ int delta_time = std::max(
+ 1, std::min(999, static_cast<int>((base::Time::Now() - request_start)
+ .InMilliseconds())));
+ STATIC_HISTOGRAM_POINTER_BLOCK(
+ "NewTabPage.SuggestedSitesLoadTime", Add(delta_time),
+ base::LinearHistogram::FactoryGet(
+ "NewTabPage.SuggestedSitesLoadTime", 1, 1000, 100,
+ base::Histogram::kUmaTargetedHistogramFlag));
+}
+
+void HistoryBackend::GetRedirectsFromSpecificVisit(
+ VisitID cur_visit,
+ history::RedirectList* redirects) {
+ // Follow any redirects from the given visit and add them to the list.
+ // It *should* be impossible to get a circular chain here, but we check
+ // just in case to avoid infinite loops.
+ GURL cur_url;
+ std::set<VisitID> visit_set;
+ visit_set.insert(cur_visit);
+ while (db_->GetRedirectFromVisit(cur_visit, &cur_visit, &cur_url)) {
+ if (visit_set.find(cur_visit) != visit_set.end()) {
+ NOTREACHED() << "Loop in visit chain, giving up";
+ return;
+ }
+ visit_set.insert(cur_visit);
+ redirects->push_back(cur_url);
+ }
+}
+
+void HistoryBackend::GetRedirectsToSpecificVisit(
+ VisitID cur_visit,
+ history::RedirectList* redirects) {
+ // Follow redirects going to cur_visit. These are added to |redirects| in
+ // the order they are found. If a redirect chain looks like A -> B -> C and
+ // |cur_visit| = C, redirects will be {B, A} in that order.
+ if (!db_)
+ return;
+
+ GURL cur_url;
+ std::set<VisitID> visit_set;
+ visit_set.insert(cur_visit);
+ while (db_->GetRedirectToVisit(cur_visit, &cur_visit, &cur_url)) {
+ if (visit_set.find(cur_visit) != visit_set.end()) {
+ NOTREACHED() << "Loop in visit chain, giving up";
+ return;
+ }
+ visit_set.insert(cur_visit);
+ redirects->push_back(cur_url);
+ }
+}
+
+void HistoryBackend::ScheduleAutocomplete(const base::Callback<
+ void(history::HistoryBackend*, history::URLDatabase*)>& callback) {
+ callback.Run(this, db_.get());
+}
+
+void HistoryBackend::DeleteFTSIndexDatabases() {
+ // Find files on disk matching the text databases file pattern so we can
+ // quickly test for and delete them.
+ base::FilePath::StringType filepattern = FILE_PATH_LITERAL("History Index *");
+ base::FileEnumerator enumerator(history_dir_, false,
+ base::FileEnumerator::FILES, filepattern);
+ int num_databases_deleted = 0;
+ base::FilePath current_file;
+ while (!(current_file = enumerator.Next()).empty()) {
+ if (sql::Connection::Delete(current_file))
+ num_databases_deleted++;
+ }
+ UMA_HISTOGRAM_COUNTS("History.DeleteFTSIndexDatabases",
+ num_databases_deleted);
+}
+
+void HistoryBackend::GetFavicons(
+ const std::vector<GURL>& icon_urls,
+ int icon_types,
+ const std::vector<int>& desired_sizes,
+ std::vector<favicon_base::FaviconRawBitmapResult>* bitmap_results) {
+ UpdateFaviconMappingsAndFetchImpl(nullptr, icon_urls, icon_types,
+ desired_sizes, bitmap_results);
+}
+
+void HistoryBackend::GetLargestFaviconForURL(
+ const GURL& page_url,
+ const std::vector<int>& icon_types,
+ int minimum_size_in_pixels,
+ favicon_base::FaviconRawBitmapResult* favicon_bitmap_result) {
+ DCHECK(favicon_bitmap_result);
+
+ if (!db_ || !thumbnail_db_)
+ return;
+
+ TimeTicks beginning_time = TimeTicks::Now();
+
+ std::vector<IconMapping> icon_mappings;
+ if (!thumbnail_db_->GetIconMappingsForPageURL(page_url, &icon_mappings) ||
+ icon_mappings.empty())
+ return;
+
+ int required_icon_types = 0;
+ for (std::vector<int>::const_iterator i = icon_types.begin();
+ i != icon_types.end(); ++i) {
+ required_icon_types |= *i;
+ }
+
+ // Find the largest bitmap for each IconType placing in
+ // |largest_favicon_bitmaps|.
+ std::map<favicon_base::IconType, FaviconBitmap> largest_favicon_bitmaps;
+ for (std::vector<IconMapping>::const_iterator i = icon_mappings.begin();
+ i != icon_mappings.end(); ++i) {
+ if (!(i->icon_type & required_icon_types))
+ continue;
+ std::vector<FaviconBitmapIDSize> bitmap_id_sizes;
+ thumbnail_db_->GetFaviconBitmapIDSizes(i->icon_id, &bitmap_id_sizes);
+ FaviconBitmap& largest = largest_favicon_bitmaps[i->icon_type];
+ for (std::vector<FaviconBitmapIDSize>::const_iterator j =
+ bitmap_id_sizes.begin();
+ j != bitmap_id_sizes.end(); ++j) {
+ if (largest.bitmap_id == 0 ||
+ (largest.pixel_size.width() < j->pixel_size.width() &&
+ largest.pixel_size.height() < j->pixel_size.height())) {
+ largest.icon_id = i->icon_id;
+ largest.bitmap_id = j->bitmap_id;
+ largest.pixel_size = j->pixel_size;
+ }
+ }
+ }
+ if (largest_favicon_bitmaps.empty())
+ return;
+
+ // Find an icon which is larger than minimum_size_in_pixels in the order of
+ // icon_types.
+ FaviconBitmap largest_icon;
+ for (std::vector<int>::const_iterator t = icon_types.begin();
+ t != icon_types.end(); ++t) {
+ for (std::map<favicon_base::IconType, FaviconBitmap>::const_iterator f =
+ largest_favicon_bitmaps.begin();
+ f != largest_favicon_bitmaps.end(); ++f) {
+ if (f->first & *t &&
+ (largest_icon.bitmap_id == 0 ||
+ (largest_icon.pixel_size.height() < f->second.pixel_size.height() &&
+ largest_icon.pixel_size.width() < f->second.pixel_size.width()))) {
+ largest_icon = f->second;
+ }
+ }
+ if (largest_icon.pixel_size.width() > minimum_size_in_pixels &&
+ largest_icon.pixel_size.height() > minimum_size_in_pixels)
+ break;
+ }
+
+ GURL icon_url;
+ favicon_base::IconType icon_type;
+ if (!thumbnail_db_->GetFaviconHeader(largest_icon.icon_id, &icon_url,
+ &icon_type)) {
+ return;
+ }
+
+ base::Time last_updated;
+ favicon_base::FaviconRawBitmapResult bitmap_result;
+ bitmap_result.icon_url = icon_url;
+ bitmap_result.icon_type = icon_type;
+ if (!thumbnail_db_->GetFaviconBitmap(largest_icon.bitmap_id, &last_updated,
+ &bitmap_result.bitmap_data,
+ &bitmap_result.pixel_size)) {
+ return;
+ }
+
+ bitmap_result.expired =
+ (Time::Now() - last_updated) > TimeDelta::FromDays(kFaviconRefetchDays);
+ if (bitmap_result.is_valid())
+ *favicon_bitmap_result = bitmap_result;
+
+ LOCAL_HISTOGRAM_TIMES("History.GetLargestFaviconForURL",
+ TimeTicks::Now() - beginning_time);
+}
+
+void HistoryBackend::GetFaviconsForURL(
+ const GURL& page_url,
+ int icon_types,
+ const std::vector<int>& desired_sizes,
+ std::vector<favicon_base::FaviconRawBitmapResult>* bitmap_results) {
+ DCHECK(bitmap_results);
+ GetFaviconsFromDB(page_url, icon_types, desired_sizes, bitmap_results);
+}
+
+void HistoryBackend::GetFaviconForID(
+ favicon_base::FaviconID favicon_id,
+ int desired_size,
+ std::vector<favicon_base::FaviconRawBitmapResult>* bitmap_results) {
+ std::vector<favicon_base::FaviconID> favicon_ids;
+ favicon_ids.push_back(favicon_id);
+ std::vector<int> desired_sizes;
+ desired_sizes.push_back(desired_size);
+
+ // Get results from DB.
+ GetFaviconBitmapResultsForBestMatch(favicon_ids, desired_sizes,
+ bitmap_results);
+}
+
+void HistoryBackend::UpdateFaviconMappingsAndFetch(
+ const GURL& page_url,
+ const std::vector<GURL>& icon_urls,
+ int icon_types,
+ const std::vector<int>& desired_sizes,
+ std::vector<favicon_base::FaviconRawBitmapResult>* bitmap_results) {
+ UpdateFaviconMappingsAndFetchImpl(&page_url, icon_urls, icon_types,
+ desired_sizes, bitmap_results);
+}
+
+void HistoryBackend::MergeFavicon(
+ const GURL& page_url,
+ const GURL& icon_url,
+ favicon_base::IconType icon_type,
+ scoped_refptr<base::RefCountedMemory> bitmap_data,
+ const gfx::Size& pixel_size) {
+ if (!thumbnail_db_ || !db_)
+ return;
+
+ favicon_base::FaviconID favicon_id =
+ thumbnail_db_->GetFaviconIDForFaviconURL(icon_url, icon_type, nullptr);
+
+ if (!favicon_id) {
+ // There is no favicon at |icon_url|, create it.
+ favicon_id = thumbnail_db_->AddFavicon(icon_url, icon_type);
+ }
+
+ std::vector<FaviconBitmapIDSize> bitmap_id_sizes;
+ thumbnail_db_->GetFaviconBitmapIDSizes(favicon_id, &bitmap_id_sizes);
+
+ // If there is already a favicon bitmap of |pixel_size| at |icon_url|,
+ // replace it.
+ bool bitmap_identical = false;
+ bool replaced_bitmap = false;
+ for (size_t i = 0; i < bitmap_id_sizes.size(); ++i) {
+ if (bitmap_id_sizes[i].pixel_size == pixel_size) {
+ if (IsFaviconBitmapDataEqual(bitmap_id_sizes[i].bitmap_id, bitmap_data)) {
+ thumbnail_db_->SetFaviconBitmapLastUpdateTime(
+ bitmap_id_sizes[i].bitmap_id, base::Time::Now());
+ bitmap_identical = true;
+ } else {
+ thumbnail_db_->SetFaviconBitmap(bitmap_id_sizes[i].bitmap_id,
+ bitmap_data, base::Time::Now());
+ replaced_bitmap = true;
+ }
+ break;
+ }
+ }
+
+ // Create a vector of the pixel sizes of the favicon bitmaps currently at
+ // |icon_url|.
+ std::vector<gfx::Size> favicon_sizes;
+ for (size_t i = 0; i < bitmap_id_sizes.size(); ++i)
+ favicon_sizes.push_back(bitmap_id_sizes[i].pixel_size);
+
+ if (!replaced_bitmap && !bitmap_identical) {
+ // Set the preexisting favicon bitmaps as expired as the preexisting favicon
+ // bitmaps are not consistent with the merged in data.
+ thumbnail_db_->SetFaviconOutOfDate(favicon_id);
+
+ // Delete an arbitrary favicon bitmap to avoid going over the limit of
+ // |kMaxFaviconBitmapsPerIconURL|.
+ if (bitmap_id_sizes.size() >= kMaxFaviconBitmapsPerIconURL) {
+ thumbnail_db_->DeleteFaviconBitmap(bitmap_id_sizes[0].bitmap_id);
+ favicon_sizes.erase(favicon_sizes.begin());
+ }
+ thumbnail_db_->AddFaviconBitmap(favicon_id, bitmap_data, base::Time::Now(),
+ pixel_size);
+ favicon_sizes.push_back(pixel_size);
+ }
+
+ // A site may have changed the favicons that it uses for |page_url|.
+ // Example Scenario:
+ // page_url = news.google.com
+ // Initial State: www.google.com/favicon.ico 16x16, 32x32
+ // MergeFavicon(news.google.com, news.google.com/news_specific.ico, ...,
+ // ..., 16x16)
+ //
+ // Difficulties:
+ // 1. Sync requires that a call to GetFaviconsForURL() returns the
+ // |bitmap_data| passed into MergeFavicon().
+ // - It is invalid for the 16x16 bitmap for www.google.com/favicon.ico to
+ // stay mapped to news.google.com because it would be unclear which 16x16
+ // bitmap should be returned via GetFaviconsForURL().
+ //
+ // 2. www.google.com/favicon.ico may be mapped to more than just
+ // news.google.com (eg www.google.com).
+ // - The 16x16 bitmap cannot be deleted from www.google.com/favicon.ico
+ //
+ // To resolve these problems, we copy all of the favicon bitmaps previously
+ // mapped to news.google.com (|page_url|) and add them to the favicon at
+ // news.google.com/news_specific.ico (|icon_url|). The favicon sizes for
+ // |icon_url| are set to default to indicate that |icon_url| has incomplete
+ // / incorrect data.
+ // Difficulty 1: All but news.google.com/news_specific.ico are unmapped from
+ // news.google.com
+ // Difficulty 2: The favicon bitmaps for www.google.com/favicon.ico are not
+ // modified.
+
+ std::vector<IconMapping> icon_mappings;
+ thumbnail_db_->GetIconMappingsForPageURL(page_url, icon_type, &icon_mappings);
+
+ // Copy the favicon bitmaps mapped to |page_url| to the favicon at |icon_url|
+ // till the limit of |kMaxFaviconBitmapsPerIconURL| is reached.
+ for (size_t i = 0; i < icon_mappings.size(); ++i) {
+ if (favicon_sizes.size() >= kMaxFaviconBitmapsPerIconURL)
+ break;
+
+ if (icon_mappings[i].icon_url == icon_url)
+ continue;
+
+ std::vector<FaviconBitmap> bitmaps_to_copy;
+ thumbnail_db_->GetFaviconBitmaps(icon_mappings[i].icon_id,
+ &bitmaps_to_copy);
+ for (size_t j = 0; j < bitmaps_to_copy.size(); ++j) {
+ // Do not add a favicon bitmap at a pixel size for which there is already
+ // a favicon bitmap mapped to |icon_url|. The one there is more correct
+ // and having multiple equally sized favicon bitmaps for |page_url| is
+ // ambiguous in terms of GetFaviconsForURL().
+ std::vector<gfx::Size>::iterator it =
+ std::find(favicon_sizes.begin(), favicon_sizes.end(),
+ bitmaps_to_copy[j].pixel_size);
+ if (it != favicon_sizes.end())
+ continue;
+
+ // Add the favicon bitmap as expired as it is not consistent with the
+ // merged in data.
+ thumbnail_db_->AddFaviconBitmap(
+ favicon_id, bitmaps_to_copy[j].bitmap_data, base::Time(),
+ bitmaps_to_copy[j].pixel_size);
+ favicon_sizes.push_back(bitmaps_to_copy[j].pixel_size);
+
+ if (favicon_sizes.size() >= kMaxFaviconBitmapsPerIconURL)
+ break;
+ }
+ }
+
+ // Update the favicon mappings such that only |icon_url| is mapped to
+ // |page_url|.
+ bool mapping_changed = false;
+ if (icon_mappings.size() != 1 || icon_mappings[0].icon_url != icon_url) {
+ std::vector<favicon_base::FaviconID> favicon_ids;
+ favicon_ids.push_back(favicon_id);
+ SetFaviconMappingsForPageAndRedirects(page_url, icon_type, favicon_ids);
+ mapping_changed = true;
+ }
+
+ if (mapping_changed || !bitmap_identical)
+ SendFaviconChangedNotificationForPageAndRedirects(page_url);
+ ScheduleCommit();
+}
+
+void HistoryBackend::SetFavicons(const GURL& page_url,
+ favicon_base::IconType icon_type,
+ const GURL& icon_url,
+ const std::vector<SkBitmap>& bitmaps) {
+ if (!thumbnail_db_ || !db_)
+ return;
+
+ DCHECK_GE(kMaxFaviconBitmapsPerIconURL, bitmaps.size());
+
+ // Track whether the method modifies or creates any favicon bitmaps, favicons
+ // or icon mappings.
+ bool data_modified = false;
+
+ favicon_base::FaviconID icon_id =
+ thumbnail_db_->GetFaviconIDForFaviconURL(icon_url, icon_type, nullptr);
+
+ if (!icon_id) {
+ icon_id = thumbnail_db_->AddFavicon(icon_url, icon_type);
+ data_modified = true;
+ }
+
+ data_modified |= SetFaviconBitmaps(icon_id, bitmaps);
+
+ std::vector<favicon_base::FaviconID> icon_ids(1u, icon_id);
+ data_modified |=
+ SetFaviconMappingsForPageAndRedirects(page_url, icon_type, icon_ids);
+
+ if (data_modified) {
+ // Send notification to the UI as an icon mapping, favicon, or favicon
+ // bitmap was changed by this function.
+ SendFaviconChangedNotificationForPageAndRedirects(page_url);
+ }
+ ScheduleCommit();
+}
+
+void HistoryBackend::SetFaviconsOutOfDateForPage(const GURL& page_url) {
+ std::vector<IconMapping> icon_mappings;
+
+ if (!thumbnail_db_ ||
+ !thumbnail_db_->GetIconMappingsForPageURL(page_url, &icon_mappings))
+ return;
+
+ for (std::vector<IconMapping>::iterator m = icon_mappings.begin();
+ m != icon_mappings.end(); ++m) {
+ thumbnail_db_->SetFaviconOutOfDate(m->icon_id);
+ }
+ ScheduleCommit();
+}
+
+void HistoryBackend::CloneFavicons(const GURL& old_page_url,
+ const GURL& new_page_url) {
+ if (!thumbnail_db_)
+ return;
+
+ // Prevent cross-domain cloning.
+ if (old_page_url.GetOrigin() != new_page_url.GetOrigin())
+ return;
+
+ thumbnail_db_->CloneIconMappings(old_page_url, new_page_url);
+ ScheduleCommit();
+}
+
+void HistoryBackend::SetImportedFavicons(
+ const favicon_base::FaviconUsageDataList& favicon_usage) {
+ if (!db_ || !thumbnail_db_)
+ return;
+
+ Time now = Time::Now();
+
+ // Track all URLs that had their favicons set or updated.
+ std::set<GURL> favicons_changed;
+
+ for (size_t i = 0; i < favicon_usage.size(); i++) {
+ favicon_base::FaviconID favicon_id =
+ thumbnail_db_->GetFaviconIDForFaviconURL(
+ favicon_usage[i].favicon_url, favicon_base::FAVICON, nullptr);
+ if (!favicon_id) {
+ // This favicon doesn't exist yet, so we create it using the given data.
+ // TODO(pkotwicz): Pass in real pixel size.
+ favicon_id = thumbnail_db_->AddFavicon(
+ favicon_usage[i].favicon_url, favicon_base::FAVICON,
+ new base::RefCountedBytes(favicon_usage[i].png_data), now,
+ gfx::Size());
+ }
+
+ // Save the mapping from all the URLs to the favicon.
+ HistoryClient* history_client = GetHistoryClient();
+ for (std::set<GURL>::const_iterator url = favicon_usage[i].urls.begin();
+ url != favicon_usage[i].urls.end(); ++url) {
+ URLRow url_row;
+ if (!db_->GetRowForURL(*url, &url_row)) {
+ // If the URL is present as a bookmark, add the url in history to
+ // save the favicon mapping. This will match with what history db does
+ // for regular bookmarked URLs with favicons - when history db is
+ // cleaned, we keep an entry in the db with 0 visits as long as that
+ // url is bookmarked.
+ if (history_client && history_client->IsBookmarked(*url)) {
+ URLRow url_info(*url);
+ url_info.set_visit_count(0);
+ url_info.set_typed_count(0);
+ url_info.set_last_visit(base::Time());
+ url_info.set_hidden(false);
+ db_->AddURL(url_info);
+ thumbnail_db_->AddIconMapping(*url, favicon_id);
+ favicons_changed.insert(*url);
+ }
+ } else {
+ if (!thumbnail_db_->GetIconMappingsForPageURL(
+ *url, favicon_base::FAVICON, nullptr)) {
+ // URL is present in history, update the favicon *only* if it is not
+ // set already.
+ thumbnail_db_->AddIconMapping(*url, favicon_id);
+ favicons_changed.insert(*url);
+ }
+ }
+ }
+ }
+
+ if (!favicons_changed.empty()) {
+ // Send the notification about the changed favicon URLs.
+ NotifyFaviconChanged(favicons_changed);
+ }
+}
+
+void HistoryBackend::UpdateFaviconMappingsAndFetchImpl(
+ const GURL* page_url,
+ const std::vector<GURL>& icon_urls,
+ int icon_types,
+ const std::vector<int>& desired_sizes,
+ std::vector<favicon_base::FaviconRawBitmapResult>* bitmap_results) {
+ // If |page_url| is specified, |icon_types| must be either a single icon
+ // type or icon types which are equivalent.
+ DCHECK(!page_url || icon_types == favicon_base::FAVICON ||
+ icon_types == favicon_base::TOUCH_ICON ||
+ icon_types == favicon_base::TOUCH_PRECOMPOSED_ICON ||
+ icon_types ==
+ (favicon_base::TOUCH_ICON | favicon_base::TOUCH_PRECOMPOSED_ICON));
+ bitmap_results->clear();
+
+ if (!thumbnail_db_) {
+ return;
+ }
+
+ std::vector<favicon_base::FaviconID> favicon_ids;
+
+ // The icon type for which the mappings will the updated and data will be
+ // returned.
+ favicon_base::IconType selected_icon_type = favicon_base::INVALID_ICON;
+
+ for (size_t i = 0; i < icon_urls.size(); ++i) {
+ const GURL& icon_url = icon_urls[i];
+ favicon_base::IconType icon_type_out;
+ const favicon_base::FaviconID favicon_id =
+ thumbnail_db_->GetFaviconIDForFaviconURL(icon_url, icon_types,
+ &icon_type_out);
+
+ if (favicon_id) {
+ // Return and update icon mappings only for the largest icon type. As
+ // |icon_urls| is not sorted in terms of icon type, clear |favicon_ids|
+ // if an |icon_url| with a larger icon type is found.
+ if (icon_type_out > selected_icon_type) {
+ selected_icon_type = icon_type_out;
+ favicon_ids.clear();
+ }
+ if (icon_type_out == selected_icon_type)
+ favicon_ids.push_back(favicon_id);
+ }
+ }
+
+ if (page_url && !favicon_ids.empty()) {
+ bool mappings_updated = SetFaviconMappingsForPageAndRedirects(
+ *page_url, selected_icon_type, favicon_ids);
+ if (mappings_updated) {
+ SendFaviconChangedNotificationForPageAndRedirects(*page_url);
+ ScheduleCommit();
+ }
+ }
+
+ GetFaviconBitmapResultsForBestMatch(favicon_ids, desired_sizes,
+ bitmap_results);
+}
+
+bool HistoryBackend::SetFaviconBitmaps(favicon_base::FaviconID icon_id,
+ const std::vector<SkBitmap>& bitmaps) {
+ std::vector<FaviconBitmapIDSize> bitmap_id_sizes;
+ thumbnail_db_->GetFaviconBitmapIDSizes(icon_id, &bitmap_id_sizes);
+
+ typedef std::pair<scoped_refptr<base::RefCountedBytes>, gfx::Size>
+ PNGEncodedBitmap;
+ std::vector<PNGEncodedBitmap> to_add;
+ for (size_t i = 0; i < bitmaps.size(); ++i) {
+ scoped_refptr<base::RefCountedBytes> bitmap_data(new base::RefCountedBytes);
+ if (!gfx::PNGCodec::EncodeBGRASkBitmap(bitmaps[i], false,
+ &bitmap_data->data())) {
+ continue;
+ }
+ to_add.push_back(std::make_pair(
+ bitmap_data, gfx::Size(bitmaps[i].width(), bitmaps[i].height())));
+ }
+
+ bool favicon_bitmaps_changed = false;
+ for (size_t i = 0; i < bitmap_id_sizes.size(); ++i) {
+ const gfx::Size& pixel_size = bitmap_id_sizes[i].pixel_size;
+ std::vector<PNGEncodedBitmap>::iterator match_it = to_add.end();
+ for (std::vector<PNGEncodedBitmap>::iterator it = to_add.begin();
+ it != to_add.end(); ++it) {
+ if (it->second == pixel_size) {
+ match_it = it;
+ break;
+ }
+ }
+
+ FaviconBitmapID bitmap_id = bitmap_id_sizes[i].bitmap_id;
+ if (match_it == to_add.end()) {
+ thumbnail_db_->DeleteFaviconBitmap(bitmap_id);
+
+ favicon_bitmaps_changed = true;
+ } else {
+ if (!favicon_bitmaps_changed &&
+ IsFaviconBitmapDataEqual(bitmap_id, match_it->first)) {
+ thumbnail_db_->SetFaviconBitmapLastUpdateTime(bitmap_id,
+ base::Time::Now());
+ } else {
+ thumbnail_db_->SetFaviconBitmap(bitmap_id, match_it->first,
+ base::Time::Now());
+ favicon_bitmaps_changed = true;
+ }
+ to_add.erase(match_it);
+ }
+ }
+
+ for (size_t i = 0; i < to_add.size(); ++i) {
+ thumbnail_db_->AddFaviconBitmap(icon_id, to_add[i].first, base::Time::Now(),
+ to_add[i].second);
+
+ favicon_bitmaps_changed = true;
+ }
+ return favicon_bitmaps_changed;
+}
+
+bool HistoryBackend::IsFaviconBitmapDataEqual(
+ FaviconBitmapID bitmap_id,
+ const scoped_refptr<base::RefCountedMemory>& new_bitmap_data) {
+ if (!new_bitmap_data.get())
+ return false;
+
+ scoped_refptr<base::RefCountedMemory> original_bitmap_data;
+ thumbnail_db_->GetFaviconBitmap(bitmap_id, nullptr, &original_bitmap_data,
+ nullptr);
+ return new_bitmap_data->Equals(original_bitmap_data);
+}
+
+bool HistoryBackend::GetFaviconsFromDB(
+ const GURL& page_url,
+ int icon_types,
+ const std::vector<int>& desired_sizes,
+ std::vector<favicon_base::FaviconRawBitmapResult>* favicon_bitmap_results) {
+ DCHECK(favicon_bitmap_results);
+ favicon_bitmap_results->clear();
+
+ if (!db_ || !thumbnail_db_)
+ return false;
+
+ // Time the query.
+ TimeTicks beginning_time = TimeTicks::Now();
+
+ // Get FaviconIDs for |page_url| and one of |icon_types|.
+ std::vector<IconMapping> icon_mappings;
+ thumbnail_db_->GetIconMappingsForPageURL(page_url, icon_types,
+ &icon_mappings);
+ std::vector<favicon_base::FaviconID> favicon_ids;
+ for (size_t i = 0; i < icon_mappings.size(); ++i)
+ favicon_ids.push_back(icon_mappings[i].icon_id);
+
+ // Populate |favicon_bitmap_results| and |icon_url_sizes|.
+ bool success = GetFaviconBitmapResultsForBestMatch(favicon_ids, desired_sizes,
+ favicon_bitmap_results);
+ UMA_HISTOGRAM_TIMES("History.GetFavIconFromDB", // historical name
+ TimeTicks::Now() - beginning_time);
+ return success && !favicon_bitmap_results->empty();
+}
+
+bool HistoryBackend::GetFaviconBitmapResultsForBestMatch(
+ const std::vector<favicon_base::FaviconID>& candidate_favicon_ids,
+ const std::vector<int>& desired_sizes,
+ std::vector<favicon_base::FaviconRawBitmapResult>* favicon_bitmap_results) {
+ favicon_bitmap_results->clear();
+
+ if (candidate_favicon_ids.empty())
+ return true;
+
+ // Find the FaviconID and the FaviconBitmapIDs which best match
+ // |desired_size_in_dip| and |desired_scale_factors|.
+ // TODO(pkotwicz): Select bitmap results from multiple favicons once
+ // content::FaviconStatus supports multiple icon URLs.
+ favicon_base::FaviconID best_favicon_id = 0;
+ std::vector<FaviconBitmapID> best_bitmap_ids;
+ float highest_score = kSelectFaviconFramesInvalidScore;
+ for (size_t i = 0; i < candidate_favicon_ids.size(); ++i) {
+ std::vector<FaviconBitmapIDSize> bitmap_id_sizes;
+ thumbnail_db_->GetFaviconBitmapIDSizes(candidate_favicon_ids[i],
+ &bitmap_id_sizes);
+
+ // Build vector of gfx::Size from |bitmap_id_sizes|.
+ std::vector<gfx::Size> sizes;
+ for (size_t j = 0; j < bitmap_id_sizes.size(); ++j)
+ sizes.push_back(bitmap_id_sizes[j].pixel_size);
+
+ std::vector<size_t> candidate_bitmap_indices;
+ float score = 0;
+ SelectFaviconFrameIndices(sizes, desired_sizes, &candidate_bitmap_indices,
+ &score);
+ if (score > highest_score) {
+ highest_score = score;
+ best_favicon_id = candidate_favicon_ids[i], best_bitmap_ids.clear();
+ for (size_t j = 0; j < candidate_bitmap_indices.size(); ++j) {
+ size_t candidate_index = candidate_bitmap_indices[j];
+ best_bitmap_ids.push_back(bitmap_id_sizes[candidate_index].bitmap_id);
+ }
+ }
+ }
+
+ // Construct FaviconRawBitmapResults from |best_favicon_id| and
+ // |best_bitmap_ids|.
+ GURL icon_url;
+ favicon_base::IconType icon_type;
+ if (!thumbnail_db_->GetFaviconHeader(best_favicon_id, &icon_url,
+ &icon_type)) {
+ return false;
+ }
+
+ for (size_t i = 0; i < best_bitmap_ids.size(); ++i) {
+ base::Time last_updated;
+ favicon_base::FaviconRawBitmapResult bitmap_result;
+ bitmap_result.icon_url = icon_url;
+ bitmap_result.icon_type = icon_type;
+ if (!thumbnail_db_->GetFaviconBitmap(best_bitmap_ids[i], &last_updated,
+ &bitmap_result.bitmap_data,
+ &bitmap_result.pixel_size)) {
+ return false;
+ }
+
+ bitmap_result.expired =
+ (Time::Now() - last_updated) > TimeDelta::FromDays(kFaviconRefetchDays);
+ if (bitmap_result.is_valid())
+ favicon_bitmap_results->push_back(bitmap_result);
+ }
+ return true;
+}
+
+bool HistoryBackend::SetFaviconMappingsForPageAndRedirects(
+ const GURL& page_url,
+ favicon_base::IconType icon_type,
+ const std::vector<favicon_base::FaviconID>& icon_ids) {
+ if (!thumbnail_db_)
+ return false;
+
+ // Find all the pages whose favicons we should set, we want to set it for
+ // all the pages in the redirect chain if it redirected.
+ history::RedirectList redirects;
+ GetCachedRecentRedirects(page_url, &redirects);
+
+ bool mappings_changed = false;
+
+ // Save page <-> favicon associations.
+ for (history::RedirectList::const_iterator i(redirects.begin());
+ i != redirects.end(); ++i) {
+ mappings_changed |= SetFaviconMappingsForPage(*i, icon_type, icon_ids);
+ }
+ return mappings_changed;
+}
+
+bool HistoryBackend::SetFaviconMappingsForPage(
+ const GURL& page_url,
+ favicon_base::IconType icon_type,
+ const std::vector<favicon_base::FaviconID>& icon_ids) {
+ DCHECK_LE(icon_ids.size(), kMaxFaviconsPerPage);
+ bool mappings_changed = false;
+
+ // Two icon types are considered 'equivalent' if one of the icon types is
+ // TOUCH_ICON and the other is TOUCH_PRECOMPOSED_ICON.
+ //
+ // Sets the icon mappings from |page_url| for |icon_type| to the favicons
+ // with |icon_ids|. Mappings for |page_url| to favicons of type |icon_type|
+ // whose FaviconID is not in |icon_ids| are removed. All icon mappings for
+ // |page_url| to favicons of a type equivalent to |icon_type| are removed.
+ // Remove any favicons which are orphaned as a result of the removal of the
+ // icon mappings.
+
+ std::vector<favicon_base::FaviconID> unmapped_icon_ids = icon_ids;
+
+ std::vector<IconMapping> icon_mappings;
+ thumbnail_db_->GetIconMappingsForPageURL(page_url, &icon_mappings);
+
+ for (std::vector<IconMapping>::iterator m = icon_mappings.begin();
+ m != icon_mappings.end(); ++m) {
+ std::vector<favicon_base::FaviconID>::iterator icon_id_it = std::find(
+ unmapped_icon_ids.begin(), unmapped_icon_ids.end(), m->icon_id);
+
+ // If the icon mapping already exists, avoid removing it and adding it back.
+ if (icon_id_it != unmapped_icon_ids.end()) {
+ unmapped_icon_ids.erase(icon_id_it);
+ continue;
+ }
+
+ if ((icon_type == favicon_base::TOUCH_ICON &&
+ m->icon_type == favicon_base::TOUCH_PRECOMPOSED_ICON) ||
+ (icon_type == favicon_base::TOUCH_PRECOMPOSED_ICON &&
+ m->icon_type == favicon_base::TOUCH_ICON) ||
+ (icon_type == m->icon_type)) {
+ thumbnail_db_->DeleteIconMapping(m->mapping_id);
+
+ // Removing the icon mapping may have orphaned the associated favicon so
+ // we must recheck it. This is not super fast, but this case will get
+ // triggered rarely, since normally a page will always map to the same
+ // favicon IDs. It will mostly happen for favicons we import.
+ if (!thumbnail_db_->HasMappingFor(m->icon_id))
+ thumbnail_db_->DeleteFavicon(m->icon_id);
+ mappings_changed = true;
+ }
+ }
+
+ for (size_t i = 0; i < unmapped_icon_ids.size(); ++i) {
+ thumbnail_db_->AddIconMapping(page_url, unmapped_icon_ids[i]);
+ mappings_changed = true;
+ }
+ return mappings_changed;
+}
+
+void HistoryBackend::GetCachedRecentRedirects(
+ const GURL& page_url,
+ history::RedirectList* redirect_list) {
+ RedirectCache::iterator iter = recent_redirects_.Get(page_url);
+ if (iter != recent_redirects_.end()) {
+ *redirect_list = iter->second;
+
+ // The redirect chain should have the destination URL as the last item.
+ DCHECK(!redirect_list->empty());
+ DCHECK(redirect_list->back() == page_url);
+ } else {
+ // No known redirects, construct mock redirect chain containing |page_url|.
+ redirect_list->push_back(page_url);
+ }
+}
+
+void HistoryBackend::SendFaviconChangedNotificationForPageAndRedirects(
+ const GURL& page_url) {
+ history::RedirectList redirect_list;
+ GetCachedRecentRedirects(page_url, &redirect_list);
+ if (!redirect_list.empty()) {
+ std::set<GURL> favicons_changed(redirect_list.begin(), redirect_list.end());
+ NotifyFaviconChanged(favicons_changed);
+ }
+}
+
+void HistoryBackend::Commit() {
+ if (!db_)
+ return;
+
+ // Note that a commit may not actually have been scheduled if a caller
+ // explicitly calls this instead of using ScheduleCommit. Likewise, we
+ // may reset the flag written by a pending commit. But this is OK! It
+ // will merely cause extra commits (which is kind of the idea). We
+ // could optimize more for this case (we may get two extra commits in
+ // some cases) but it hasn't been important yet.
+ CancelScheduledCommit();
+
+ db_->CommitTransaction();
+ DCHECK(db_->transaction_nesting() == 0) << "Somebody left a transaction open";
+ db_->BeginTransaction();
+
+ if (thumbnail_db_) {
+ thumbnail_db_->CommitTransaction();
+ DCHECK(thumbnail_db_->transaction_nesting() == 0)
+ << "Somebody left a transaction open";
+ thumbnail_db_->BeginTransaction();
+ }
+}
+
+void HistoryBackend::ScheduleCommit() {
+ if (scheduled_commit_.get())
+ return;
+ scheduled_commit_ = new CommitLaterTask(this);
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&CommitLaterTask::RunCommit, scheduled_commit_.get()),
+ base::TimeDelta::FromSeconds(kCommitIntervalSeconds));
+}
+
+void HistoryBackend::CancelScheduledCommit() {
+ if (scheduled_commit_.get()) {
+ scheduled_commit_->Cancel();
+ scheduled_commit_ = nullptr;
+ }
+}
+
+void HistoryBackend::ProcessDBTaskImpl() {
+ if (!db_) {
+ // db went away, release all the refs.
+ STLDeleteContainerPointers(queued_history_db_tasks_.begin(),
+ queued_history_db_tasks_.end());
+ queued_history_db_tasks_.clear();
+ return;
+ }
+
+ // Remove any canceled tasks.
+ while (!queued_history_db_tasks_.empty()) {
+ QueuedHistoryDBTask* task = queued_history_db_tasks_.front();
+ if (!task->is_canceled())
+ break;
+
+ delete task;
+ queued_history_db_tasks_.pop_front();
+ }
+ if (queued_history_db_tasks_.empty())
+ return;
+
+ // Run the first task.
+ scoped_ptr<QueuedHistoryDBTask> task(queued_history_db_tasks_.front());
+ queued_history_db_tasks_.pop_front();
+ if (task->Run(this, db_.get())) {
+ // The task is done, notify the callback.
+ task->DoneRun();
+ } else {
+ // The task wants to run some more. Schedule it at the end of the current
+ // tasks, and process it after an invoke later.
+ queued_history_db_tasks_.push_back(task.release());
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(&HistoryBackend::ProcessDBTaskImpl, this));
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Generic operations
+//
+////////////////////////////////////////////////////////////////////////////////
+
+void HistoryBackend::DeleteURLs(const std::vector<GURL>& urls) {
+ expirer_.DeleteURLs(urls);
+
+ db_->GetStartDate(&first_recorded_time_);
+ // Force a commit, if the user is deleting something for privacy reasons, we
+ // want to get it on disk ASAP.
+ Commit();
+}
+
+void HistoryBackend::DeleteURL(const GURL& url) {
+ expirer_.DeleteURL(url);
+
+ db_->GetStartDate(&first_recorded_time_);
+ // Force a commit, if the user is deleting something for privacy reasons, we
+ // want to get it on disk ASAP.
+ Commit();
+}
+
+void HistoryBackend::ExpireHistoryBetween(const std::set<GURL>& restrict_urls,
+ Time begin_time,
+ Time end_time) {
+ if (!db_)
+ return;
+
+ if (begin_time.is_null() && (end_time.is_null() || end_time.is_max()) &&
+ restrict_urls.empty()) {
+ // Special case deleting all history so it can be faster and to reduce the
+ // possibility of an information leak.
+ DeleteAllHistory();
+ } else {
+ // Clearing parts of history, have the expirer do the depend
+ expirer_.ExpireHistoryBetween(restrict_urls, begin_time, end_time);
+
+ // Force a commit, if the user is deleting something for privacy reasons,
+ // we want to get it on disk ASAP.
+ Commit();
+ }
+
+ if (begin_time <= first_recorded_time_)
+ db_->GetStartDate(&first_recorded_time_);
+}
+
+void HistoryBackend::ExpireHistoryForTimes(const std::set<base::Time>& times,
+ base::Time begin_time,
+ base::Time end_time) {
+ if (times.empty() || !db_)
+ return;
+
+ DCHECK(*times.begin() >= begin_time)
+ << "Min time is before begin time: " << times.begin()->ToJsTime()
+ << " v.s. " << begin_time.ToJsTime();
+ DCHECK(*times.rbegin() < end_time)
+ << "Max time is after end time: " << times.rbegin()->ToJsTime()
+ << " v.s. " << end_time.ToJsTime();
+
+ history::QueryOptions options;
+ options.begin_time = begin_time;
+ options.end_time = end_time;
+ options.duplicate_policy = QueryOptions::KEEP_ALL_DUPLICATES;
+ QueryResults results;
+ QueryHistoryBasic(options, &results);
+
+ // 1st pass: find URLs that are visited at one of |times|.
+ std::set<GURL> urls;
+ for (size_t i = 0; i < results.size(); ++i) {
+ if (times.count(results[i].visit_time()) > 0)
+ urls.insert(results[i].url());
+ }
+ if (urls.empty())
+ return;
+
+ // 2nd pass: collect all visit times of those URLs.
+ std::vector<base::Time> times_to_expire;
+ for (size_t i = 0; i < results.size(); ++i) {
+ if (urls.count(results[i].url()))
+ times_to_expire.push_back(results[i].visit_time());
+ }
+
+ // Put the times in reverse chronological order and remove
+ // duplicates (for expirer_.ExpireHistoryForTimes()).
+ std::sort(times_to_expire.begin(), times_to_expire.end(),
+ std::greater<base::Time>());
+ times_to_expire.erase(
+ std::unique(times_to_expire.begin(), times_to_expire.end()),
+ times_to_expire.end());
+
+ // Expires by times and commit.
+ DCHECK(!times_to_expire.empty());
+ expirer_.ExpireHistoryForTimes(times_to_expire);
+ Commit();
+
+ DCHECK(times_to_expire.back() >= first_recorded_time_);
+ // Update |first_recorded_time_| if we expired it.
+ if (times_to_expire.back() == first_recorded_time_)
+ db_->GetStartDate(&first_recorded_time_);
+}
+
+void HistoryBackend::ExpireHistory(
+ const std::vector<history::ExpireHistoryArgs>& expire_list) {
+ if (db_) {
+ bool update_first_recorded_time = false;
+
+ for (std::vector<history::ExpireHistoryArgs>::const_iterator it =
+ expire_list.begin();
+ it != expire_list.end(); ++it) {
+ expirer_.ExpireHistoryBetween(it->urls, it->begin_time, it->end_time);
+
+ if (it->begin_time < first_recorded_time_)
+ update_first_recorded_time = true;
+ }
+ Commit();
+
+ // Update |first_recorded_time_| if any deletion might have affected it.
+ if (update_first_recorded_time)
+ db_->GetStartDate(&first_recorded_time_);
+ }
+}
+
+void HistoryBackend::URLsNoLongerBookmarked(const std::set<GURL>& urls) {
+ if (!db_)
+ return;
+
+ for (std::set<GURL>::const_iterator i = urls.begin(); i != urls.end(); ++i) {
+ URLRow url_row;
+ if (!db_->GetRowForURL(*i, &url_row))
+ continue; // The URL isn't in the db; nothing to do.
+
+ VisitVector visits;
+ db_->GetVisitsForURL(url_row.id(), &visits);
+
+ if (visits.empty())
+ expirer_.DeleteURL(*i); // There are no more visits; nuke the URL.
+ }
+}
+
+void HistoryBackend::DatabaseErrorCallback(int error, sql::Statement* stmt) {
+ if (!scheduled_kill_db_ && sql::IsErrorCatastrophic(error)) {
+ scheduled_kill_db_ = true;
+ // Don't just do the close/delete here, as we are being called by |db| and
+ // that seems dangerous.
+ // TODO(shess): Consider changing KillHistoryDatabase() to use
+ // RazeAndClose(). Then it can be cleared immediately.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(&HistoryBackend::KillHistoryDatabase, this));
+ }
+}
+
+void HistoryBackend::KillHistoryDatabase() {
+ scheduled_kill_db_ = false;
+ if (!db_)
+ return;
+
+ // Rollback transaction because Raze() cannot be called from within a
+ // transaction.
+ db_->RollbackTransaction();
+ bool success = db_->Raze();
+ UMA_HISTOGRAM_BOOLEAN("History.KillHistoryDatabaseResult", success);
+
+ // Release stashed embedder object before cleaning up the databases.
+ supports_user_data_helper_.reset();
+
+ // The expirer keeps tabs on the active databases. Tell it about the
+ // databases which will be closed.
+ expirer_.SetDatabases(nullptr, nullptr);
+
+ // Reopen a new transaction for |db_| for the sake of CloseAllDatabases().
+ db_->BeginTransaction();
+ CloseAllDatabases();
+}
+
+base::SupportsUserData::Data* HistoryBackend::GetUserData(
+ const void* key) const {
+ DCHECK(supports_user_data_helper_);
+ return supports_user_data_helper_->GetUserData(key);
+}
+
+void HistoryBackend::SetUserData(const void* key,
+ base::SupportsUserData::Data* data) {
+ DCHECK(supports_user_data_helper_);
+ supports_user_data_helper_->SetUserData(key, data);
+}
+
+void HistoryBackend::ProcessDBTask(
+ scoped_ptr<HistoryDBTask> task,
+ scoped_refptr<base::SingleThreadTaskRunner> origin_loop,
+ const base::CancelableTaskTracker::IsCanceledCallback& is_canceled) {
+ bool scheduled = !queued_history_db_tasks_.empty();
+ queued_history_db_tasks_.push_back(
+ new QueuedHistoryDBTask(task.Pass(), origin_loop, is_canceled));
+ if (!scheduled)
+ ProcessDBTaskImpl();
+}
+
+void HistoryBackend::NotifyFaviconChanged(const std::set<GURL>& urls) {
+ if (delegate_)
+ delegate_->NotifyFaviconChanged(urls);
+}
+
+void HistoryBackend::NotifyURLVisited(ui::PageTransition transition,
+ const URLRow& row,
+ const RedirectList& redirects,
+ base::Time visit_time) {
+ URLRow url_info(row);
+ if (typed_url_syncable_service_.get())
+ typed_url_syncable_service_->OnUrlVisited(transition, &url_info);
+
+ FOR_EACH_OBSERVER(
+ HistoryBackendObserver, observers_,
+ OnURLVisited(this, transition, url_info, redirects, visit_time));
+
+ // TODO(sdefresne): turn HistoryBackend::Delegate from HistoryService into
+ // an HistoryBackendObserver and register it so that we can remove this
+ // method.
+ if (delegate_)
+ delegate_->NotifyURLVisited(transition, url_info, redirects, visit_time);
+}
+
+void HistoryBackend::NotifyURLsModified(const URLRows& rows) {
+ URLRows changed_urls(rows);
+ if (typed_url_syncable_service_.get())
+ typed_url_syncable_service_->OnUrlsModified(&changed_urls);
+
+ FOR_EACH_OBSERVER(HistoryBackendObserver, observers_,
+ OnURLsModified(this, changed_urls));
+
+ // TODO(sdefresne): turn HistoryBackend::Delegate from HistoryService into
+ // an HistoryBackendObserver and register it so that we can remove this
+ // method.
+ if (delegate_)
+ delegate_->NotifyURLsModified(changed_urls);
+}
+
+void HistoryBackend::NotifyURLsDeleted(bool all_history,
+ bool expired,
+ const URLRows& rows,
+ const std::set<GURL>& favicon_urls) {
+ URLRows copied_rows(rows);
+ if (typed_url_syncable_service_.get()) {
+ typed_url_syncable_service_->OnUrlsDeleted(all_history, expired,
+ &copied_rows);
+ }
+
+ FOR_EACH_OBSERVER(
+ HistoryBackendObserver, observers_,
+ OnURLsDeleted(this, all_history, expired, copied_rows, favicon_urls));
+
+ // TODO(sdefresne): turn HistoryBackend::Delegate from HistoryService into
+ // an HistoryBackendObserver and register it so that we can remove this
+ // method.
+ if (delegate_)
+ delegate_->NotifyURLsDeleted(all_history, expired, copied_rows,
+ favicon_urls);
+}
+
+// Deleting --------------------------------------------------------------------
+
+void HistoryBackend::DeleteAllHistory() {
+ // Our approach to deleting all history is:
+ // 1. Copy the bookmarks and their dependencies to new tables with temporary
+ // names.
+ // 2. Delete the original tables. Since tables can not share pages, we know
+ // that any data we don't want to keep is now in an unused page.
+ // 3. Renaming the temporary tables to match the original.
+ // 4. Vacuuming the database to delete the unused pages.
+ //
+ // Since we are likely to have very few bookmarks and their dependencies
+ // compared to all history, this is also much faster than just deleting from
+ // the original tables directly.
+
+ // Get the bookmarked URLs.
+ std::vector<URLAndTitle> starred_urls;
+ HistoryClient* history_client = GetHistoryClient();
+ if (history_client)
+ history_client->GetBookmarks(&starred_urls);
+
+ URLRows kept_urls;
+ for (size_t i = 0; i < starred_urls.size(); i++) {
+ URLRow row;
+ if (!db_->GetRowForURL(starred_urls[i].url, &row))
+ continue;
+
+ // Clear the last visit time so when we write these rows they are "clean."
+ row.set_last_visit(Time());
+ row.set_visit_count(0);
+ row.set_typed_count(0);
+ kept_urls.push_back(row);
+ }
+
+ // Clear thumbnail and favicon history. The favicons for the given URLs will
+ // be kept.
+ if (!ClearAllThumbnailHistory(kept_urls)) {
+ LOG(ERROR) << "Thumbnail history could not be cleared";
+ // We continue in this error case. If the user wants to delete their
+ // history, we should delete as much as we can.
+ }
+
+ // ClearAllMainHistory will change the IDs of the URLs in kept_urls.
+ // Therefore, we clear the list afterwards to make sure nobody uses this
+ // invalid data.
+ if (!ClearAllMainHistory(kept_urls))
+ LOG(ERROR) << "Main history could not be cleared";
+ kept_urls.clear();
+
+ db_->GetStartDate(&first_recorded_time_);
+
+ // Send out the notification that history is cleared. The in-memory database
+ // will pick this up and clear itself.
+ NotifyURLsDeleted(true, false, URLRows(), std::set<GURL>());
+}
+
+bool HistoryBackend::ClearAllThumbnailHistory(const URLRows& kept_urls) {
+ if (!thumbnail_db_) {
+ // When we have no reference to the thumbnail database, maybe there was an
+ // error opening it. In this case, we just try to blow it away to try to
+ // fix the error if it exists. This may fail, in which case either the
+ // file doesn't exist or there's no more we can do.
+ sql::Connection::Delete(GetFaviconsFileName());
+
+ // Older version of the database.
+ sql::Connection::Delete(GetThumbnailFileName());
+ return true;
+ }
+
+ // Urls to retain mappings for.
+ std::vector<GURL> urls_to_keep;
+ for (URLRows::const_iterator i = kept_urls.begin(); i != kept_urls.end();
+ ++i) {
+ urls_to_keep.push_back(i->url());
+ }
+
+ // Isolate from any long-running transaction.
+ thumbnail_db_->CommitTransaction();
+ thumbnail_db_->BeginTransaction();
+
+ // TODO(shess): If this fails, perhaps the database should be razed
+ // or deleted.
+ if (!thumbnail_db_->RetainDataForPageUrls(urls_to_keep)) {
+ thumbnail_db_->RollbackTransaction();
+ thumbnail_db_->BeginTransaction();
+ return false;
+ }
+
+#if defined(OS_ANDROID)
+ // TODO (michaelbai): Add the unit test once AndroidProviderBackend is
+ // available in HistoryBackend.
+ db_->ClearAndroidURLRows();
+#endif
+
+ // Vacuum to remove all the pages associated with the dropped tables. There
+ // must be no transaction open on the table when we do this. We assume that
+ // our long-running transaction is open, so we complete it and start it again.
+ DCHECK(thumbnail_db_->transaction_nesting() == 1);
+ thumbnail_db_->CommitTransaction();
+ thumbnail_db_->Vacuum();
+ thumbnail_db_->BeginTransaction();
+ return true;
+}
+
+bool HistoryBackend::ClearAllMainHistory(const URLRows& kept_urls) {
+ // Create the duplicate URL table. We will copy the kept URLs into this.
+ if (!db_->CreateTemporaryURLTable())
+ return false;
+
+ // Insert the URLs into the temporary table.
+ for (URLRows::const_iterator i = kept_urls.begin(); i != kept_urls.end();
+ ++i) {
+ db_->AddTemporaryURL(*i);
+ }
+
+ // Replace the original URL table with the temporary one.
+ if (!db_->CommitTemporaryURLTable())
+ return false;
+
+ // Delete the old tables and recreate them empty.
+ db_->RecreateAllTablesButURL();
+
+ // Vacuum to reclaim the space from the dropped tables. This must be done
+ // when there is no transaction open, and we assume that our long-running
+ // transaction is currently open.
+ db_->CommitTransaction();
+ db_->Vacuum();
+ db_->BeginTransaction();
+ db_->GetStartDate(&first_recorded_time_);
+
+ return true;
+}
+
+HistoryClient* HistoryBackend::GetHistoryClient() {
+ if (history_client_)
+ history_client_->BlockUntilBookmarksLoaded();
+ return history_client_;
+}
+
+void HistoryBackend::NotifyVisitObservers(const VisitRow& visit) {
+ BriefVisitInfo info;
+ info.url_id = visit.url_id;
+ info.time = visit.visit_time;
+ info.transition = visit.transition;
+ // If we don't have a delegate yet during setup or shutdown, we will drop
+ // these notifications.
+ if (delegate_)
+ delegate_->NotifyAddVisit(info);
+}
+
+} // namespace history
diff --git a/components/history/core/browser/history_backend.h b/components/history/core/browser/history_backend.h
new file mode 100644
index 0000000..ecef76f
--- /dev/null
+++ b/components/history/core/browser/history_backend.h
@@ -0,0 +1,827 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_HISTORY_CORE_BROWSER_HISTORY_BACKEND_H_
+#define COMPONENTS_HISTORY_CORE_BROWSER_HISTORY_BACKEND_H_
+
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/containers/mru_cache.h"
+#include "base/files/file_path.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/memory_pressure_listener.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/observer_list.h"
+#include "base/single_thread_task_runner.h"
+#include "base/supports_user_data.h"
+#include "base/task/cancelable_task_tracker.h"
+#include "components/favicon_base/favicon_usage_data.h"
+#include "components/history/core/browser/expire_history_backend.h"
+#include "components/history/core/browser/history_backend_notifier.h"
+#include "components/history/core/browser/history_types.h"
+#include "components/history/core/browser/keyword_id.h"
+#include "components/history/core/browser/thumbnail_database.h"
+#include "components/history/core/browser/visit_tracker.h"
+#include "sql/init_status.h"
+
+class HistoryURLProvider;
+struct HistoryURLProviderParams;
+class SkBitmap;
+class TestingProfile;
+struct ThumbnailScore;
+
+namespace base {
+class MessageLoop;
+class SingleThreadTaskRunner;
+}
+
+namespace history {
+class CommitLaterTask;
+struct DownloadRow;
+class HistoryBackendObserver;
+class HistoryClient;
+class HistoryDatabase;
+struct HistoryDatabaseParams;
+struct HistoryDetails;
+class HistoryDBTask;
+class InMemoryHistoryBackend;
+class TypedUrlSyncableService;
+class VisitFilter;
+class HistoryBackendHelper;
+
+// The maximum number of icons URLs per page which can be stored in the
+// thumbnail database.
+static const size_t kMaxFaviconsPerPage = 8;
+
+// The maximum number of bitmaps for a single icon URL which can be stored in
+// the thumbnail database.
+static const size_t kMaxFaviconBitmapsPerIconURL = 8;
+
+// Keeps track of a queued HistoryDBTask. This class lives solely on the
+// DB thread.
+class QueuedHistoryDBTask {
+ public:
+ QueuedHistoryDBTask(
+ scoped_ptr<HistoryDBTask> task,
+ scoped_refptr<base::SingleThreadTaskRunner> origin_loop,
+ const base::CancelableTaskTracker::IsCanceledCallback& is_canceled);
+ ~QueuedHistoryDBTask();
+
+ bool is_canceled();
+ bool Run(HistoryBackend* backend, HistoryDatabase* db);
+ void DoneRun();
+
+ private:
+ scoped_ptr<HistoryDBTask> task_;
+ scoped_refptr<base::SingleThreadTaskRunner> origin_loop_;
+ base::CancelableTaskTracker::IsCanceledCallback is_canceled_;
+
+ DISALLOW_COPY_AND_ASSIGN(QueuedHistoryDBTask);
+};
+
+// *See the .cc file for more information on the design.*
+//
+// Internal history implementation which does most of the work of the history
+// system. This runs on a background thread (to not block the browser when we
+// do expensive operations) and is NOT threadsafe, so it must only be called
+// from message handlers on the background thread. Invoking on another thread
+// requires threadsafe refcounting.
+//
+// Most functions here are just the implementations of the corresponding
+// functions in the history service. These functions are not documented
+// here, see the history service for behavior.
+class HistoryBackend : public base::RefCountedThreadSafe<HistoryBackend>,
+ public HistoryBackendNotifier {
+ public:
+ // Interface implemented by the owner of the HistoryBackend object. Normally,
+ // the history service implements this to send stuff back to the main thread.
+ // The unit tests can provide a different implementation if they don't have
+ // a history service object.
+ class Delegate {
+ public:
+ virtual ~Delegate() {}
+
+ // Called when the database cannot be read correctly for some reason.
+ virtual void NotifyProfileError(sql::InitStatus init_status) = 0;
+
+ // Sets the in-memory history backend. The in-memory backend is created by
+ // the main backend. For non-unit tests, this happens on the background
+ // thread. It is to be used on the main thread, so this would transfer
+ // it to the history service. Unit tests can override this behavior.
+ //
+ // This function is NOT guaranteed to be called. If there is an error,
+ // there may be no in-memory database.
+ virtual void SetInMemoryBackend(
+ scoped_ptr<InMemoryHistoryBackend> backend) = 0;
+
+ // Notify HistoryService that VisitDatabase was changed. The event will be
+ // forwarded to the history::HistoryServiceObservers in the UI thread.
+ virtual void NotifyAddVisit(const BriefVisitInfo& info) = 0;
+
+ // Notify HistoryService that some URLs favicon changed that will forward
+ // the events to the FaviconChangedObservers in the correct thread.
+ virtual void NotifyFaviconChanged(const std::set<GURL>& urls) = 0;
+
+ // Notify HistoryService that the user is visiting an URL. The event will
+ // be forwarded to the HistoryServiceObservers in the correct thread.
+ virtual void NotifyURLVisited(ui::PageTransition transition,
+ const URLRow& row,
+ const RedirectList& redirects,
+ base::Time visit_time) = 0;
+
+ // Notify HistoryService that some URLs have been modified. The event will
+ // be forwarded to the HistoryServiceObservers in the correct thread.
+ virtual void NotifyURLsModified(const URLRows& changed_urls) = 0;
+
+ // Notify HistoryService that some or all of the URLs have been deleted.
+ // The event will be forwarded to the HistoryServiceObservers in the correct
+ // thread.
+ virtual void NotifyURLsDeleted(bool all_history,
+ bool expired,
+ const URLRows& deleted_rows,
+ const std::set<GURL>& favicon_urls) = 0;
+
+ // Notify HistoryService that some keyword has been searched using omnibox.
+ // The event will be forwarded to the HistoryServiceObservers in the correct
+ // thread.
+ virtual void NotifyKeywordSearchTermUpdated(const URLRow& row,
+ KeywordID keyword_id,
+ const base::string16& term) = 0;
+
+ // Notify HistoryService that keyword search term has been deleted.
+ // The event will be forwarded to the HistoryServiceObservers in the correct
+ // thread.
+ virtual void NotifyKeywordSearchTermDeleted(URLID url_id) = 0;
+
+ // Invoked when the backend has finished loading the db.
+ virtual void DBLoaded() = 0;
+ };
+
+ // Init must be called to complete object creation. This object can be
+ // constructed on any thread, but all other functions including Init() must
+ // be called on the history thread.
+ //
+ // |history_dir| is the directory where the history files will be placed.
+ // See the definition of BroadcastNotificationsCallback above. This function
+ // takes ownership of the callback pointer.
+ //
+ // |history_client| is used to determine bookmarked URLs when deleting and
+ // may be null.
+ //
+ // This constructor is fast and does no I/O, so can be called at any time.
+ HistoryBackend(Delegate* delegate, HistoryClient* history_client);
+
+ // Must be called after creation but before any objects are created. If this
+ // fails, all other functions will fail as well. (Since this runs on another
+ // thread, we don't bother returning failure.)
+ //
+ // |languages| gives a list of language encodings with which the history
+ // URLs and omnibox searches are interpreted.
+ // |force_fail| can be set during unittests to unconditionally fail to init.
+ void Init(const std::string& languages,
+ bool force_fail,
+ const HistoryDatabaseParams& history_database_params);
+
+ // Notification that the history system is shutting down. This will break
+ // the refs owned by the delegate and any pending transaction so it will
+ // actually be deleted.
+ void Closing();
+
+ void ClearCachedDataForContextID(ContextID context_id);
+
+ // Navigation ----------------------------------------------------------------
+
+ // |request.time| must be unique with high probability.
+ void AddPage(const HistoryAddPageArgs& request);
+ virtual void SetPageTitle(const GURL& url, const base::string16& title);
+ void AddPageNoVisitForBookmark(const GURL& url, const base::string16& title);
+ void UpdateWithPageEndTime(ContextID context_id,
+ int nav_entry_id,
+ const GURL& url,
+ base::Time end_ts);
+
+ // Querying ------------------------------------------------------------------
+
+ // Run the |callback| on the History thread.
+ // |callback| should handle the null database case.
+ void ScheduleAutocomplete(const base::Callback<
+ void(history::HistoryBackend*, history::URLDatabase*)>& callback);
+
+ void QueryURL(const GURL& url,
+ bool want_visits,
+ QueryURLResult* query_url_result);
+ void QueryHistory(const base::string16& text_query,
+ const QueryOptions& options,
+ QueryResults* query_results);
+
+ // Computes the most recent URL(s) that the given canonical URL has
+ // redirected to. There may be more than one redirect in a row, so this
+ // function will fill the given array with the entire chain. If there are
+ // no redirects for the most recent visit of the URL, or the URL is not
+ // in history, the array will be empty.
+ void QueryRedirectsFrom(const GURL& url, RedirectList* redirects);
+
+ // Similar to above function except computes a chain of redirects to the
+ // given URL. Stores the most recent list of redirects ending at |url| in the
+ // given RedirectList. For example, if we have the redirect list A -> B -> C,
+ // then calling this function with url=C would fill redirects with {B, A}.
+ void QueryRedirectsTo(const GURL& url, RedirectList* redirects);
+
+ void GetVisibleVisitCountToHost(const GURL& url,
+ VisibleVisitCountToHostResult* result);
+
+ // Request the |result_count| most visited URLs and the chain of
+ // redirects leading to each of these URLs. |days_back| is the
+ // number of days of history to use. Used by TopSites.
+ void QueryMostVisitedURLs(int result_count,
+ int days_back,
+ MostVisitedURLList* result);
+
+ // Request the |result_count| URLs and the chain of redirects
+ // leading to each of these URLs, filterd and sorted based on the |filter|.
+ // If |debug| is enabled, additional data will be computed and provided.
+ void QueryFilteredURLs(int result_count,
+ const history::VisitFilter& filter,
+ bool debug,
+ history::FilteredURLList* result);
+
+ // Favicon -------------------------------------------------------------------
+
+ void GetFavicons(
+ const std::vector<GURL>& icon_urls,
+ int icon_types,
+ const std::vector<int>& desired_sizes,
+ std::vector<favicon_base::FaviconRawBitmapResult>* bitmap_results);
+
+ void GetLargestFaviconForURL(
+ const GURL& page_url,
+ const std::vector<int>& icon_types,
+ int minimum_size_in_pixels,
+ favicon_base::FaviconRawBitmapResult* bitmap_result);
+
+ void GetFaviconsForURL(
+ const GURL& page_url,
+ int icon_types,
+ const std::vector<int>& desired_sizes,
+ std::vector<favicon_base::FaviconRawBitmapResult>* bitmap_results);
+
+ void GetFaviconForID(
+ favicon_base::FaviconID favicon_id,
+ int desired_size,
+ std::vector<favicon_base::FaviconRawBitmapResult>* bitmap_results);
+
+ void UpdateFaviconMappingsAndFetch(
+ const GURL& page_url,
+ const std::vector<GURL>& icon_urls,
+ int icon_types,
+ const std::vector<int>& desired_sizes,
+ std::vector<favicon_base::FaviconRawBitmapResult>* bitmap_results);
+
+ void MergeFavicon(const GURL& page_url,
+ const GURL& icon_url,
+ favicon_base::IconType icon_type,
+ scoped_refptr<base::RefCountedMemory> bitmap_data,
+ const gfx::Size& pixel_size);
+
+ void SetFavicons(const GURL& page_url,
+ favicon_base::IconType icon_type,
+ const GURL& icon_url,
+ const std::vector<SkBitmap>& bitmaps);
+
+ void SetFaviconsOutOfDateForPage(const GURL& page_url);
+
+ void CloneFavicons(const GURL& old_page_url, const GURL& new_page_url);
+
+ void SetImportedFavicons(
+ const favicon_base::FaviconUsageDataList& favicon_usage);
+
+ // Downloads -----------------------------------------------------------------
+
+ uint32 GetNextDownloadId();
+ void QueryDownloads(std::vector<DownloadRow>* rows);
+ void UpdateDownload(const DownloadRow& data);
+ bool CreateDownload(const history::DownloadRow& history_info);
+ void RemoveDownloads(const std::set<uint32>& ids);
+
+ // Keyword search terms ------------------------------------------------------
+
+ void SetKeywordSearchTermsForURL(const GURL& url,
+ KeywordID keyword_id,
+ const base::string16& term);
+
+ void DeleteAllSearchTermsForKeyword(KeywordID keyword_id);
+
+ void DeleteKeywordSearchTermForURL(const GURL& url);
+
+ void DeleteMatchingURLsForKeyword(KeywordID keyword_id,
+ const base::string16& term);
+
+ // Observers -----------------------------------------------------------------
+
+ void AddObserver(HistoryBackendObserver* observer);
+ void RemoveObserver(HistoryBackendObserver* observer);
+
+ // Generic operations --------------------------------------------------------
+
+ void ProcessDBTask(
+ scoped_ptr<HistoryDBTask> task,
+ scoped_refptr<base::SingleThreadTaskRunner> origin_loop,
+ const base::CancelableTaskTracker::IsCanceledCallback& is_canceled);
+
+ virtual bool GetAllTypedURLs(URLRows* urls);
+
+ virtual bool GetVisitsForURL(URLID id, VisitVector* visits);
+
+ // Fetches up to |max_visits| most recent visits for the passed URL.
+ virtual bool GetMostRecentVisitsForURL(URLID id,
+ int max_visits,
+ VisitVector* visits);
+
+ // For each element in |urls|, updates the pre-existing URLRow in the database
+ // with the same ID; or ignores the element if no such row exists. Returns the
+ // number of records successfully updated.
+ virtual size_t UpdateURLs(const history::URLRows& urls);
+
+ // While adding visits in batch, the source needs to be provided.
+ virtual bool AddVisits(const GURL& url,
+ const std::vector<history::VisitInfo>& visits,
+ VisitSource visit_source);
+
+ virtual bool RemoveVisits(const VisitVector& visits);
+
+ // Returns the VisitSource associated with each one of the passed visits.
+ // If there is no entry in the map for a given visit, that means the visit
+ // was SOURCE_BROWSED. Returns false if there is no HistoryDatabase..
+ bool GetVisitsSource(const VisitVector& visits, VisitSourceMap* sources);
+
+ virtual bool GetURL(const GURL& url, history::URLRow* url_row);
+
+ // Returns the syncable service for syncing typed urls. The returned service
+ // is owned by |this| object.
+ virtual TypedUrlSyncableService* GetTypedUrlSyncableService() const;
+
+ // Deleting ------------------------------------------------------------------
+
+ virtual void DeleteURLs(const std::vector<GURL>& urls);
+
+ virtual void DeleteURL(const GURL& url);
+
+ // Calls ExpireHistoryBackend::ExpireHistoryBetween and commits the change.
+ void ExpireHistoryBetween(const std::set<GURL>& restrict_urls,
+ base::Time begin_time,
+ base::Time end_time);
+
+ // Finds the URLs visited at |times| and expires all their visits within
+ // [|begin_time|, |end_time|). All times in |times| should be in
+ // [|begin_time|, |end_time|). This is used when expiration request is from
+ // server side, i.e. web history deletes, where only visit times (possibly
+ // incomplete) are transmitted to protect user's privacy.
+ void ExpireHistoryForTimes(const std::set<base::Time>& times,
+ base::Time begin_time,
+ base::Time end_time);
+
+ // Calls ExpireHistoryBetween() once for each element in the vector.
+ // The fields of |ExpireHistoryArgs| map directly to the arguments of
+ // of ExpireHistoryBetween().
+ void ExpireHistory(const std::vector<ExpireHistoryArgs>& expire_list);
+
+ // Bookmarks -----------------------------------------------------------------
+
+ // Notification that a URL is no longer bookmarked. If there are no visits
+ // for the specified url, it is deleted.
+ void URLsNoLongerBookmarked(const std::set<GURL>& urls);
+
+ // Callbacks To Kill Database When It Gets Corrupted -------------------------
+
+ // Called by the database to report errors. Schedules one call to
+ // KillHistoryDatabase() in case of corruption.
+ void DatabaseErrorCallback(int error, sql::Statement* stmt);
+
+ // Raze the history database. It will be recreated in a future run. Hopefully
+ // things go better then. Continue running but without reading or storing any
+ // state into the HistoryBackend databases. Close all of the databases managed
+ // HistoryBackend as there are no provisions for accessing the other databases
+ // managed by HistoryBackend when the history database cannot be accessed.
+ void KillHistoryDatabase();
+
+ // SupportsUserData ----------------------------------------------------------
+
+ // The user data allows the clients to associate data with this object.
+ // Multiple user data values can be stored under different keys.
+ // This object will TAKE OWNERSHIP of the given data pointer, and will
+ // delete the object if it is changed or the object is destroyed.
+ base::SupportsUserData::Data* GetUserData(const void* key) const;
+ void SetUserData(const void* key, base::SupportsUserData::Data* data);
+
+ // Testing -------------------------------------------------------------------
+
+ // Sets the task to run and the message loop to run it on when this object
+ // is destroyed. See HistoryService::SetOnBackendDestroyTask for a more
+ // complete description.
+ void SetOnBackendDestroyTask(base::MessageLoop* message_loop,
+ const base::Closure& task);
+
+ // Adds the given rows to the database if it doesn't exist. A visit will be
+ // added for each given URL at the last visit time in the URLRow if the
+ // passed visit type != SOURCE_SYNCED (the sync code manages visits itself).
+ // Each visit will have the visit_source type set.
+ void AddPagesWithDetails(const URLRows& info, VisitSource visit_source);
+
+#if defined(UNIT_TEST)
+ HistoryDatabase* db() const { return db_.get(); }
+
+ ExpireHistoryBackend* expire_backend() { return &expirer_; }
+#endif
+
+ // Returns true if the passed visit time is already expired (used by the sync
+ // code to avoid syncing visits that would immediately be expired).
+ virtual bool IsExpiredVisitTime(const base::Time& time);
+
+ base::Time GetFirstRecordedTimeForTest() { return first_recorded_time_; }
+
+ protected:
+ ~HistoryBackend() override;
+
+ private:
+ friend class base::RefCountedThreadSafe<HistoryBackend>;
+ friend class CommitLaterTask; // The commit task needs to call Commit().
+ friend class HistoryBackendTest;
+ friend class HistoryBackendDBTest; // So the unit tests can poke our innards.
+ FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest, DeleteAll);
+ FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest, DeleteAllThenAddData);
+ FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest, AddPagesWithDetails);
+ FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest, UpdateURLs);
+ FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest, ImportedFaviconsTest);
+ FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest, URLsNoLongerBookmarked);
+ FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest, StripUsernamePasswordTest);
+ FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest, DeleteThumbnailsDatabaseTest);
+ FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest, AddPageVisitSource);
+ FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest, AddPageVisitNotLastVisit);
+ FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest,
+ AddPageVisitFiresNotificationWithCorrectDetails);
+ FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest, AddPageArgsSource);
+ FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest, AddVisitsSource);
+ FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest, GetMostRecentVisits);
+ FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest, RemoveVisitsSource);
+ FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest, RemoveVisitsTransitions);
+ FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest, MigrationVisitSource);
+ FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest,
+ SetFaviconMappingsForPageAndRedirects);
+ FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest,
+ SetFaviconMappingsForPageDuplicates);
+ FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest, SetFaviconsDeleteBitmaps);
+ FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest, SetFaviconsReplaceBitmapData);
+ FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest,
+ SetFaviconsSameFaviconURLForTwoPages);
+ FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest,
+ UpdateFaviconMappingsAndFetchNoChange);
+ FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest, MergeFaviconPageURLNotInDB);
+ FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest, MergeFaviconPageURLInDB);
+ FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest, MergeFaviconMaxFaviconsPerPage);
+ FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest,
+ MergeFaviconIconURLMappedToDifferentPageURL);
+ FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest,
+ MergeFaviconMaxFaviconBitmapsPerIconURL);
+ FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest,
+ UpdateFaviconMappingsAndFetchMultipleIconTypes);
+ FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest, GetFaviconsFromDBEmpty);
+ FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest,
+ GetFaviconsFromDBNoFaviconBitmaps);
+ FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest,
+ GetFaviconsFromDBSelectClosestMatch);
+ FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest, GetFaviconsFromDBIconType);
+ FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest, GetFaviconsFromDBExpired);
+ FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest,
+ UpdateFaviconMappingsAndFetchNoDB);
+ FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest,
+ CloneFaviconIsRestrictedToSameDomain);
+ FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest, QueryFilteredURLs);
+ FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest, UpdateVisitDuration);
+ FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest, ExpireHistoryForTimes);
+ FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest, DeleteFTSIndexDatabases);
+ FRIEND_TEST_ALL_PREFIXES(ProfileSyncServiceTypedUrlTest,
+ ProcessUserChangeRemove);
+ friend class ::TestingProfile;
+
+ // Computes the name of the specified database on disk.
+ base::FilePath GetArchivedFileName() const;
+ base::FilePath GetThumbnailFileName() const;
+
+ // Returns the name of the Favicons database. This is the new name
+ // of the Thumbnails database.
+ base::FilePath GetFaviconsFileName() const;
+
+ class URLQuerier;
+ friend class URLQuerier;
+
+ // Does the work of Init.
+ void InitImpl(const std::string& languages,
+ const HistoryDatabaseParams& history_database_params);
+
+ // Called when the system is under memory pressure.
+ void OnMemoryPressure(
+ base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level);
+
+ // Closes all databases managed by HistoryBackend. Commits any pending
+ // transactions.
+ void CloseAllDatabases();
+
+ // Adds a single visit to the database, updating the URL information such
+ // as visit and typed count. The visit ID of the added visit and the URL ID
+ // of the associated URL (whether added or not) is returned. Both values will
+ // be 0 on failure.
+ //
+ // This does not schedule database commits, it is intended to be used as a
+ // subroutine for AddPage only. It also assumes the database is valid.
+ std::pair<URLID, VisitID> AddPageVisit(const GURL& url,
+ base::Time time,
+ VisitID referring_visit,
+ ui::PageTransition transition,
+ VisitSource visit_source);
+
+ // Returns a redirect chain in |redirects| for the VisitID
+ // |cur_visit|. |cur_visit| is assumed to be valid. Assumes that
+ // this HistoryBackend object has been Init()ed successfully.
+ void GetRedirectsFromSpecificVisit(VisitID cur_visit,
+ history::RedirectList* redirects);
+
+ // Similar to the above function except returns a redirect list ending
+ // at |cur_visit|.
+ void GetRedirectsToSpecificVisit(VisitID cur_visit,
+ history::RedirectList* redirects);
+
+ // Update the visit_duration information in visits table.
+ void UpdateVisitDuration(VisitID visit_id, const base::Time end_ts);
+
+ // Querying ------------------------------------------------------------------
+
+ // Backends for QueryHistory. *Basic() handles queries that are not
+ // text search queries and can just be given directly to the history DB.
+ // The *Text() version performs a brute force query of the history DB to
+ // search for results which match the given text query.
+ // Both functions assume QueryHistory already checked the DB for validity.
+ void QueryHistoryBasic(const QueryOptions& options, QueryResults* result);
+ void QueryHistoryText(const base::string16& text_query,
+ const QueryOptions& options,
+ QueryResults* result);
+
+ // Committing ----------------------------------------------------------------
+
+ // We always keep a transaction open on the history database so that multiple
+ // transactions can be batched. Periodically, these are flushed (use
+ // ScheduleCommit). This function does the commit to write any new changes to
+ // disk and opens a new transaction. This will be called automatically by
+ // ScheduleCommit, or it can be called explicitly if a caller really wants
+ // to write something to disk.
+ void Commit();
+
+ // Schedules a commit to happen in the future. We do this so that many
+ // operations over a period of time will be batched together. If there is
+ // already a commit scheduled for the future, this will do nothing.
+ void ScheduleCommit();
+
+ // Cancels the scheduled commit, if any. If there is no scheduled commit,
+ // does nothing.
+ void CancelScheduledCommit();
+
+ // Segments ------------------------------------------------------------------
+
+ // Walks back a segment chain to find the last visit with a non null segment
+ // id and returns it. If there is none found, returns 0.
+ SegmentID GetLastSegmentID(VisitID from_visit);
+
+ // Update the segment information. This is called internally when a page is
+ // added. Return the segment id of the segment that has been updated.
+ SegmentID UpdateSegments(const GURL& url,
+ VisitID from_visit,
+ VisitID visit_id,
+ ui::PageTransition transition_type,
+ const base::Time ts);
+
+ // Favicons ------------------------------------------------------------------
+
+ // Used by both UpdateFaviconMappingsAndFetch and GetFavicons.
+ // If |page_url| is non-null, the icon urls for |page_url| (and all
+ // redirects) are set to the subset of |icon_urls| for which icons are
+ // already stored in the database.
+ // If |page_url| is non-null, |icon_types| can be multiple icon types
+ // only if |icon_types| == TOUCH_ICON | TOUCH_PRECOMPOSED_ICON.
+ // If multiple icon types are specified, |page_url| will be mapped to the
+ // icon URLs of the largest type available in the database.
+ void UpdateFaviconMappingsAndFetchImpl(
+ const GURL* page_url,
+ const std::vector<GURL>& icon_urls,
+ int icon_types,
+ const std::vector<int>& desired_sizes,
+ std::vector<favicon_base::FaviconRawBitmapResult>* results);
+
+ // Set the favicon bitmaps for |icon_id|.
+ // For each entry in |bitmaps|, if a favicon bitmap already exists at the
+ // entry's pixel size, replace the favicon bitmap's data with the entry's
+ // bitmap data. Otherwise add a new favicon bitmap.
+ // Any favicon bitmaps already mapped to |icon_id| whose pixel size does not
+ // match the pixel size of one of |bitmaps| is deleted.
+ // Returns true if any of the bitmap data at |icon_id| is changed as a result
+ // of calling this method.
+ bool SetFaviconBitmaps(favicon_base::FaviconID icon_id,
+ const std::vector<SkBitmap>& bitmaps);
+
+ // Returns true if the bitmap data at |bitmap_id| equals |new_bitmap_data|.
+ bool IsFaviconBitmapDataEqual(
+ FaviconBitmapID bitmap_id,
+ const scoped_refptr<base::RefCountedMemory>& new_bitmap_data);
+
+ // Returns true if there are favicons for |page_url| and one of the types in
+ // |icon_types|.
+ // |favicon_bitmap_results| is set to the favicon bitmaps whose edge sizes
+ // most closely match |desired_sizes|. If |desired_sizes| has a '0' entry, the
+ // largest favicon bitmap with one of the icon types in |icon_types| is
+ // returned. If |icon_types| contains multiple icon types and there are
+ // several matched icon types in the database, results will only be returned
+ // for a single icon type in the priority of TOUCH_PRECOMPOSED_ICON,
+ // TOUCH_ICON, and FAVICON. See the comment for
+ // GetFaviconResultsForBestMatch() for more details on how
+ // |favicon_bitmap_results| is constructed.
+ bool GetFaviconsFromDB(const GURL& page_url,
+ int icon_types,
+ const std::vector<int>& desired_sizes,
+ std::vector<favicon_base::FaviconRawBitmapResult>*
+ favicon_bitmap_results);
+
+ // Returns the favicon bitmaps whose edge sizes most closely match
+ // |desired_sizes| in |favicon_bitmap_results|. If |desired_sizes| has a '0'
+ // entry, only the largest favicon bitmap is returned. Goodness is computed
+ // via SelectFaviconFrameIndices(). It is computed on a per FaviconID basis,
+ // thus all |favicon_bitmap_results| are guaranteed to be for the same
+ // FaviconID. |favicon_bitmap_results| will have at most one entry for each
+ // desired edge size. There will be fewer entries if the same favicon bitmap
+ // is the best result for multiple edge sizes.
+ // Returns true if there were no errors.
+ bool GetFaviconBitmapResultsForBestMatch(
+ const std::vector<favicon_base::FaviconID>& candidate_favicon_ids,
+ const std::vector<int>& desired_sizes,
+ std::vector<favicon_base::FaviconRawBitmapResult>*
+ favicon_bitmap_results);
+
+ // Maps the favicon ids in |icon_ids| to |page_url| (and all redirects)
+ // for |icon_type|.
+ // Returns true if the mappings for the page or any of its redirects were
+ // changed.
+ bool SetFaviconMappingsForPageAndRedirects(
+ const GURL& page_url,
+ favicon_base::IconType icon_type,
+ const std::vector<favicon_base::FaviconID>& icon_ids);
+
+ // Maps the favicon ids in |icon_ids| to |page_url| for |icon_type|.
+ // Returns true if the function changed some of |page_url|'s mappings.
+ bool SetFaviconMappingsForPage(
+ const GURL& page_url,
+ favicon_base::IconType icon_type,
+ const std::vector<favicon_base::FaviconID>& icon_ids);
+
+ // Returns all the page URLs in the redirect chain for |page_url|. If there
+ // are no known redirects for |page_url|, returns a vector with |page_url|.
+ void GetCachedRecentRedirects(const GURL& page_url,
+ history::RedirectList* redirect_list);
+
+ // Send notification that the favicon has changed for |page_url| and all its
+ // redirects.
+ void SendFaviconChangedNotificationForPageAndRedirects(const GURL& page_url);
+
+ // Generic stuff -------------------------------------------------------------
+
+ // Processes the next scheduled HistoryDBTask, scheduling this method
+ // to be invoked again if there are more tasks that need to run.
+ void ProcessDBTaskImpl();
+
+ // HistoryBackendNotifier:
+ void NotifyFaviconChanged(const std::set<GURL>& urls) override;
+ void NotifyURLVisited(ui::PageTransition transition,
+ const URLRow& row,
+ const RedirectList& redirects,
+ base::Time visit_time) override;
+ void NotifyURLsModified(const URLRows& rows) override;
+ void NotifyURLsDeleted(bool all_history,
+ bool expired,
+ const URLRows& rows,
+ const std::set<GURL>& favicon_urls) override;
+
+ // Deleting all history ------------------------------------------------------
+
+ // Deletes all history. This is a special case of deleting that is separated
+ // from our normal dependency-following method for performance reasons. The
+ // logic lives here instead of ExpireHistoryBackend since it will cause
+ // re-initialization of some databases (e.g. Thumbnails) that could fail.
+ // When these databases are not valid, our pointers must be null, so we need
+ // to handle this type of operation to keep the pointers in sync.
+ void DeleteAllHistory();
+
+ // Given a vector of all URLs that we will keep, removes all thumbnails
+ // referenced by any URL, and also all favicons that aren't used by those
+ // URLs.
+ bool ClearAllThumbnailHistory(const URLRows& kept_urls);
+
+ // Deletes all information in the history database, except for the supplied
+ // set of URLs in the URL table (these should correspond to the bookmarked
+ // URLs).
+ //
+ // The IDs of the URLs may change.
+ bool ClearAllMainHistory(const URLRows& kept_urls);
+
+ // Deletes the FTS index database files, which are no longer used.
+ void DeleteFTSIndexDatabases();
+
+ // Returns the HistoryClient, blocking until the bookmarks are loaded. This
+ // may return null during testing.
+ HistoryClient* GetHistoryClient();
+
+ // Notify any observers of an addition to the visit database.
+ void NotifyVisitObservers(const VisitRow& visit);
+
+ // Data ----------------------------------------------------------------------
+
+ // Delegate. See the class definition above for more information. This will
+ // be null before Init is called and after Cleanup, but is guaranteed
+ // non-null in between.
+ scoped_ptr<Delegate> delegate_;
+
+ // Directory where database files will be stored, empty until Init is called.
+ base::FilePath history_dir_;
+
+ // The history/thumbnail databases. Either may be null if the database could
+ // not be opened, all users must first check for null and return immediately
+ // if it is. The thumbnail DB may be null when the history one isn't, but not
+ // vice-versa.
+ scoped_ptr<HistoryDatabase> db_;
+ bool scheduled_kill_db_; // Database is being killed due to error.
+ scoped_ptr<ThumbnailDatabase> thumbnail_db_;
+
+ // Manages expiration between the various databases.
+ ExpireHistoryBackend expirer_;
+
+ // A commit has been scheduled to occur sometime in the future. We can check
+ // non-null-ness to see if there is a commit scheduled in the future, and we
+ // can use the pointer to cancel the scheduled commit. There can be only one
+ // scheduled commit at a time (see ScheduleCommit).
+ scoped_refptr<CommitLaterTask> scheduled_commit_;
+
+ // Maps recent redirect destination pages to the chain of redirects that
+ // brought us to there. Pages that did not have redirects or were not the
+ // final redirect in a chain will not be in this list, as well as pages that
+ // redirected "too long" ago (as determined by ExpireOldRedirects above).
+ // It is used to set titles & favicons for redirects to that of the
+ // destination.
+ //
+ // As with AddPage, the last item in the redirect chain will be the
+ // destination of the redirect (i.e., the key into recent_redirects_);
+ typedef base::MRUCache<GURL, history::RedirectList> RedirectCache;
+ RedirectCache recent_redirects_;
+
+ // Timestamp of the first entry in our database.
+ base::Time first_recorded_time_;
+
+ // When set, this is the task that should be invoked on destruction.
+ base::MessageLoop* backend_destroy_message_loop_;
+ base::Closure backend_destroy_task_;
+
+ // Tracks page transition types.
+ VisitTracker tracker_;
+
+ // A boolean variable to track whether we have already purged obsolete segment
+ // data.
+ bool segment_queried_;
+
+ // List of QueuedHistoryDBTasks to run;
+ std::list<QueuedHistoryDBTask*> queued_history_db_tasks_;
+
+ // Used to determine if a URL is bookmarked; may be null.
+ //
+ // Use GetHistoryClient to access this, which makes sure the bookmarks are
+ // loaded before returning.
+ HistoryClient* history_client_;
+
+ // Used to allow embedder code to stash random data by key. Those object will
+ // be deleted before closing the databases (hence the member variable instead
+ // of inheritance from base::SupportsUserData).
+ scoped_ptr<HistoryBackendHelper> supports_user_data_helper_;
+
+ // Used to manage syncing of the typed urls datatype. This will be null before
+ // Init is called.
+ scoped_ptr<TypedUrlSyncableService> typed_url_syncable_service_;
+
+ // Listens for the system being under memory pressure.
+ scoped_ptr<base::MemoryPressureListener> memory_pressure_listener_;
+
+ // List of observers
+ ObserverList<HistoryBackendObserver> observers_;
+
+ DISALLOW_COPY_AND_ASSIGN(HistoryBackend);
+};
+
+} // namespace history
+
+#endif // COMPONENTS_HISTORY_CORE_BROWSER_HISTORY_BACKEND_H_
diff --git a/components/history/core/browser/history_service.cc b/components/history/core/browser/history_service.cc
new file mode 100644
index 0000000..41cfbc6
--- /dev/null
+++ b/components/history/core/browser/history_service.cc
@@ -0,0 +1,1134 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// The history system runs on a background thread so that potentially slow
+// database operations don't delay the browser. This backend processing is
+// represented by HistoryBackend. The HistoryService's job is to dispatch to
+// that thread.
+//
+// Main thread History thread
+// ----------- --------------
+// HistoryService <----------------> HistoryBackend
+// -> HistoryDatabase
+// -> SQLite connection to History
+// -> ThumbnailDatabase
+// -> SQLite connection to Thumbnails
+// (and favicons)
+
+#include "components/history/core/browser/history_service.h"
+
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/command_line.h"
+#include "base/compiler_specific.h"
+#include "base/location.h"
+#include "base/memory/ref_counted.h"
+#include "base/message_loop/message_loop.h"
+#include "base/thread_task_runner_handle.h"
+#include "base/threading/thread.h"
+#include "base/time/time.h"
+#include "components/history/core/browser/download_row.h"
+#include "components/history/core/browser/history_backend.h"
+#include "components/history/core/browser/history_client.h"
+#include "components/history/core/browser/history_database_params.h"
+#include "components/history/core/browser/history_db_task.h"
+#include "components/history/core/browser/history_service_observer.h"
+#include "components/history/core/browser/history_types.h"
+#include "components/history/core/browser/in_memory_database.h"
+#include "components/history/core/browser/in_memory_history_backend.h"
+#include "components/history/core/browser/keyword_search_term.h"
+#include "components/history/core/browser/visit_database.h"
+#include "components/history/core/browser/visit_delegate.h"
+#include "components/history/core/browser/visit_filter.h"
+#include "components/history/core/browser/web_history_service.h"
+#include "components/history/core/common/thumbnail_score.h"
+#include "sync/api/sync_error_factory.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+
+using base::Time;
+using history::HistoryBackend;
+using history::KeywordID;
+
+namespace {
+
+static const char* kHistoryThreadName = "Chrome_HistoryThread";
+
+void RunWithFaviconResults(
+ const favicon_base::FaviconResultsCallback& callback,
+ std::vector<favicon_base::FaviconRawBitmapResult>* bitmap_results) {
+ callback.Run(*bitmap_results);
+}
+
+void RunWithFaviconResult(
+ const favicon_base::FaviconRawBitmapCallback& callback,
+ favicon_base::FaviconRawBitmapResult* bitmap_result) {
+ callback.Run(*bitmap_result);
+}
+
+void RunWithQueryURLResult(const HistoryService::QueryURLCallback& callback,
+ const history::QueryURLResult* result) {
+ callback.Run(result->success, result->row, result->visits);
+}
+
+void RunWithVisibleVisitCountToHostResult(
+ const HistoryService::GetVisibleVisitCountToHostCallback& callback,
+ const history::VisibleVisitCountToHostResult* result) {
+ callback.Run(result->success, result->count, result->first_visit);
+}
+
+// Callback from WebHistoryService::ExpireWebHistory().
+void ExpireWebHistoryComplete(bool success) {
+ // Ignore the result.
+ //
+ // TODO(davidben): ExpireLocalAndRemoteHistoryBetween callback should not fire
+ // until this completes.
+}
+
+} // namespace
+
+// Sends messages from the backend to us on the main thread. This must be a
+// separate class from the history service so that it can hold a reference to
+// the history service (otherwise we would have to manually AddRef and
+// Release when the Backend has a reference to us).
+class HistoryService::BackendDelegate : public HistoryBackend::Delegate {
+ public:
+ BackendDelegate(
+ const base::WeakPtr<HistoryService>& history_service,
+ const scoped_refptr<base::SequencedTaskRunner>& service_task_runner)
+ : history_service_(history_service),
+ service_task_runner_(service_task_runner) {}
+
+ void NotifyProfileError(sql::InitStatus init_status) override {
+ // Send to the history service on the main thread.
+ service_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&HistoryService::NotifyProfileError,
+ history_service_, init_status));
+ }
+
+ void SetInMemoryBackend(
+ scoped_ptr<history::InMemoryHistoryBackend> backend) override {
+ // Send the backend to the history service on the main thread.
+ service_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&HistoryService::SetInMemoryBackend,
+ history_service_, base::Passed(&backend)));
+ }
+
+ void NotifyAddVisit(const history::BriefVisitInfo& info) override {
+ service_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&HistoryService::NotifyAddVisit, history_service_, info));
+ }
+
+ void NotifyFaviconChanged(const std::set<GURL>& urls) override {
+ // Send the notification to the history service on the main thread.
+ service_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&HistoryService::NotifyFaviconChanged,
+ history_service_, urls));
+ }
+
+ void NotifyURLVisited(ui::PageTransition transition,
+ const history::URLRow& row,
+ const history::RedirectList& redirects,
+ base::Time visit_time) override {
+ service_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&HistoryService::NotifyURLVisited, history_service_,
+ transition, row, redirects, visit_time));
+ }
+
+ void NotifyURLsModified(const history::URLRows& changed_urls) override {
+ service_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&HistoryService::NotifyURLsModified,
+ history_service_, changed_urls));
+ }
+
+ void NotifyURLsDeleted(bool all_history,
+ bool expired,
+ const history::URLRows& deleted_rows,
+ const std::set<GURL>& favicon_urls) override {
+ service_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&HistoryService::NotifyURLsDeleted, history_service_,
+ all_history, expired, deleted_rows, favicon_urls));
+ }
+
+ void NotifyKeywordSearchTermUpdated(const history::URLRow& row,
+ KeywordID keyword_id,
+ const base::string16& term) override {
+ service_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&HistoryService::NotifyKeywordSearchTermUpdated,
+ history_service_, row, keyword_id, term));
+ }
+
+ void NotifyKeywordSearchTermDeleted(history::URLID url_id) override {
+ service_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&HistoryService::NotifyKeywordSearchTermDeleted,
+ history_service_, url_id));
+ }
+
+ void DBLoaded() override {
+ service_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&HistoryService::OnDBLoaded, history_service_));
+ }
+
+ private:
+ const base::WeakPtr<HistoryService> history_service_;
+ const scoped_refptr<base::SequencedTaskRunner> service_task_runner_;
+};
+
+// The history thread is intentionally not a BrowserThread because the
+// sync integration unit tests depend on being able to create more than one
+// history thread.
+HistoryService::HistoryService()
+ : thread_(new base::Thread(kHistoryThreadName)),
+ history_client_(nullptr),
+ backend_loaded_(false),
+ weak_ptr_factory_(this) {
+}
+
+HistoryService::HistoryService(
+ history::HistoryClient* history_client,
+ scoped_ptr<history::VisitDelegate> visit_delegate)
+ : thread_(new base::Thread(kHistoryThreadName)),
+ visit_delegate_(visit_delegate.Pass()),
+ history_client_(history_client),
+ backend_loaded_(false),
+ weak_ptr_factory_(this) {
+}
+
+HistoryService::~HistoryService() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ // Shutdown the backend. This does nothing if Cleanup was already invoked.
+ Cleanup();
+}
+
+bool HistoryService::BackendLoaded() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return backend_loaded_;
+}
+
+void HistoryService::ClearCachedDataForContextID(
+ history::ContextID context_id) {
+ DCHECK(thread_) << "History service being called after cleanup";
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ScheduleTask(PRIORITY_NORMAL,
+ base::Bind(&HistoryBackend::ClearCachedDataForContextID,
+ history_backend_.get(), context_id));
+}
+
+history::URLDatabase* HistoryService::InMemoryDatabase() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return in_memory_backend_ ? in_memory_backend_->db() : nullptr;
+}
+
+bool HistoryService::GetTypedCountForURL(const GURL& url, int* typed_count) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ history::URLRow url_row;
+ if (!GetRowForURL(url, &url_row))
+ return false;
+ *typed_count = url_row.typed_count();
+ return true;
+}
+
+bool HistoryService::GetLastVisitTimeForURL(const GURL& url,
+ base::Time* last_visit) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ history::URLRow url_row;
+ if (!GetRowForURL(url, &url_row))
+ return false;
+ *last_visit = url_row.last_visit();
+ return true;
+}
+
+bool HistoryService::GetVisitCountForURL(const GURL& url, int* visit_count) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ history::URLRow url_row;
+ if (!GetRowForURL(url, &url_row))
+ return false;
+ *visit_count = url_row.visit_count();
+ return true;
+}
+
+history::TypedUrlSyncableService* HistoryService::GetTypedUrlSyncableService()
+ const {
+ return history_backend_->GetTypedUrlSyncableService();
+}
+
+void HistoryService::Shutdown() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ Cleanup();
+}
+
+void HistoryService::SetKeywordSearchTermsForURL(const GURL& url,
+ KeywordID keyword_id,
+ const base::string16& term) {
+ DCHECK(thread_) << "History service being called after cleanup";
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ScheduleTask(PRIORITY_UI,
+ base::Bind(&HistoryBackend::SetKeywordSearchTermsForURL,
+ history_backend_.get(), url, keyword_id, term));
+}
+
+void HistoryService::DeleteAllSearchTermsForKeyword(KeywordID keyword_id) {
+ DCHECK(thread_) << "History service being called after cleanup";
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (in_memory_backend_)
+ in_memory_backend_->DeleteAllSearchTermsForKeyword(keyword_id);
+
+ ScheduleTask(PRIORITY_UI,
+ base::Bind(&HistoryBackend::DeleteAllSearchTermsForKeyword,
+ history_backend_.get(), keyword_id));
+}
+
+void HistoryService::DeleteKeywordSearchTermForURL(const GURL& url) {
+ DCHECK(thread_) << "History service being called after cleanup";
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ScheduleTask(PRIORITY_UI,
+ base::Bind(&HistoryBackend::DeleteKeywordSearchTermForURL,
+ history_backend_.get(), url));
+}
+
+void HistoryService::DeleteMatchingURLsForKeyword(KeywordID keyword_id,
+ const base::string16& term) {
+ DCHECK(thread_) << "History service being called after cleanup";
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ScheduleTask(PRIORITY_UI,
+ base::Bind(&HistoryBackend::DeleteMatchingURLsForKeyword,
+ history_backend_.get(), keyword_id, term));
+}
+
+void HistoryService::URLsNoLongerBookmarked(const std::set<GURL>& urls) {
+ DCHECK(thread_) << "History service being called after cleanup";
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ScheduleTask(PRIORITY_NORMAL,
+ base::Bind(&HistoryBackend::URLsNoLongerBookmarked,
+ history_backend_.get(), urls));
+}
+
+void HistoryService::AddObserver(history::HistoryServiceObserver* observer) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ observers_.AddObserver(observer);
+}
+
+void HistoryService::RemoveObserver(history::HistoryServiceObserver* observer) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ observers_.RemoveObserver(observer);
+}
+
+base::CancelableTaskTracker::TaskId HistoryService::ScheduleDBTask(
+ scoped_ptr<history::HistoryDBTask> task,
+ base::CancelableTaskTracker* tracker) {
+ DCHECK(thread_) << "History service being called after cleanup";
+ DCHECK(thread_checker_.CalledOnValidThread());
+ base::CancelableTaskTracker::IsCanceledCallback is_canceled;
+ base::CancelableTaskTracker::TaskId task_id =
+ tracker->NewTrackedTaskId(&is_canceled);
+ // Use base::ThreadTaskRunnerHandler::Get() to get a message loop proxy to
+ // the current message loop so that we can forward the call to the method
+ // HistoryDBTask::DoneRunOnMainThread() in the correct thread.
+ thread_->message_loop_proxy()->PostTask(
+ FROM_HERE, base::Bind(&HistoryBackend::ProcessDBTask,
+ history_backend_.get(), base::Passed(&task),
+ base::ThreadTaskRunnerHandle::Get(), is_canceled));
+ return task_id;
+}
+
+void HistoryService::FlushForTest(const base::Closure& flushed) {
+ thread_->message_loop_proxy()->PostTaskAndReply(
+ FROM_HERE, base::Bind(&base::DoNothing), flushed);
+}
+
+void HistoryService::SetOnBackendDestroyTask(const base::Closure& task) {
+ DCHECK(thread_) << "History service being called after cleanup";
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ScheduleTask(
+ PRIORITY_NORMAL,
+ base::Bind(&HistoryBackend::SetOnBackendDestroyTask,
+ history_backend_.get(), base::MessageLoop::current(), task));
+}
+
+void HistoryService::AddPage(const GURL& url,
+ Time time,
+ history::ContextID context_id,
+ int nav_entry_id,
+ const GURL& referrer,
+ const history::RedirectList& redirects,
+ ui::PageTransition transition,
+ history::VisitSource visit_source,
+ bool did_replace_entry) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ AddPage(history::HistoryAddPageArgs(url, time, context_id, nav_entry_id,
+ referrer, redirects, transition,
+ visit_source, did_replace_entry));
+}
+
+void HistoryService::AddPage(const GURL& url,
+ base::Time time,
+ history::VisitSource visit_source) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ AddPage(history::HistoryAddPageArgs(
+ url, time, nullptr, 0, GURL(), history::RedirectList(),
+ ui::PAGE_TRANSITION_LINK, visit_source, false));
+}
+
+void HistoryService::AddPage(const history::HistoryAddPageArgs& add_page_args) {
+ DCHECK(thread_) << "History service being called after cleanup";
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // Filter out unwanted URLs. We don't add auto-subframe URLs. They are a
+ // large part of history (think iframes for ads) and we never display them in
+ // history UI. We will still add manual subframes, which are ones the user
+ // has clicked on to get.
+ if (history_client_ && !history_client_->CanAddURL(add_page_args.url))
+ return;
+
+ // Inform VisitedDelegate of all links and redirects.
+ if (visit_delegate_) {
+ if (!add_page_args.redirects.empty()) {
+ // We should not be asked to add a page in the middle of a redirect chain,
+ // and thus add_page_args.url should be the last element in the array
+ // add_page_args.redirects which mean we can use VisitDelegate::AddURLs()
+ // with the whole array.
+ DCHECK_EQ(add_page_args.url, add_page_args.redirects.back());
+ visit_delegate_->AddURLs(add_page_args.redirects);
+ } else {
+ visit_delegate_->AddURL(add_page_args.url);
+ }
+ }
+
+ ScheduleTask(PRIORITY_NORMAL,
+ base::Bind(&HistoryBackend::AddPage, history_backend_.get(),
+ add_page_args));
+}
+
+void HistoryService::AddPageNoVisitForBookmark(const GURL& url,
+ const base::string16& title) {
+ DCHECK(thread_) << "History service being called after cleanup";
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (history_client_ && !history_client_->CanAddURL(url))
+ return;
+
+ ScheduleTask(PRIORITY_NORMAL,
+ base::Bind(&HistoryBackend::AddPageNoVisitForBookmark,
+ history_backend_.get(), url, title));
+}
+
+void HistoryService::SetPageTitle(const GURL& url,
+ const base::string16& title) {
+ DCHECK(thread_) << "History service being called after cleanup";
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ScheduleTask(PRIORITY_NORMAL, base::Bind(&HistoryBackend::SetPageTitle,
+ history_backend_.get(), url, title));
+}
+
+void HistoryService::UpdateWithPageEndTime(history::ContextID context_id,
+ int nav_entry_id,
+ const GURL& url,
+ Time end_ts) {
+ DCHECK(thread_) << "History service being called after cleanup";
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ScheduleTask(
+ PRIORITY_NORMAL,
+ base::Bind(&HistoryBackend::UpdateWithPageEndTime, history_backend_.get(),
+ context_id, nav_entry_id, url, end_ts));
+}
+
+void HistoryService::AddPageWithDetails(const GURL& url,
+ const base::string16& title,
+ int visit_count,
+ int typed_count,
+ Time last_visit,
+ bool hidden,
+ history::VisitSource visit_source) {
+ DCHECK(thread_) << "History service being called after cleanup";
+ DCHECK(thread_checker_.CalledOnValidThread());
+ // Filter out unwanted URLs.
+ if (history_client_ && !history_client_->CanAddURL(url))
+ return;
+
+ // Inform VisitDelegate of the URL.
+ if (visit_delegate_)
+ visit_delegate_->AddURL(url);
+
+ history::URLRow row(url);
+ row.set_title(title);
+ row.set_visit_count(visit_count);
+ row.set_typed_count(typed_count);
+ row.set_last_visit(last_visit);
+ row.set_hidden(hidden);
+
+ history::URLRows rows;
+ rows.push_back(row);
+
+ ScheduleTask(PRIORITY_NORMAL,
+ base::Bind(&HistoryBackend::AddPagesWithDetails,
+ history_backend_.get(), rows, visit_source));
+}
+
+void HistoryService::AddPagesWithDetails(const history::URLRows& info,
+ history::VisitSource visit_source) {
+ DCHECK(thread_) << "History service being called after cleanup";
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // Inform the VisitDelegate of the URLs
+ if (!info.empty() && visit_delegate_) {
+ std::vector<GURL> urls;
+ urls.reserve(info.size());
+ for (const auto& row : info)
+ urls.push_back(row.url());
+ visit_delegate_->AddURLs(urls);
+ }
+
+ ScheduleTask(PRIORITY_NORMAL,
+ base::Bind(&HistoryBackend::AddPagesWithDetails,
+ history_backend_.get(), info, visit_source));
+}
+
+base::CancelableTaskTracker::TaskId HistoryService::GetFavicons(
+ const std::vector<GURL>& icon_urls,
+ int icon_types,
+ const std::vector<int>& desired_sizes,
+ const favicon_base::FaviconResultsCallback& callback,
+ base::CancelableTaskTracker* tracker) {
+ DCHECK(thread_) << "History service being called after cleanup";
+ DCHECK(thread_checker_.CalledOnValidThread());
+ std::vector<favicon_base::FaviconRawBitmapResult>* results =
+ new std::vector<favicon_base::FaviconRawBitmapResult>();
+ return tracker->PostTaskAndReply(
+ thread_->message_loop_proxy().get(), FROM_HERE,
+ base::Bind(&HistoryBackend::GetFavicons, history_backend_.get(),
+ icon_urls, icon_types, desired_sizes, results),
+ base::Bind(&RunWithFaviconResults, callback, base::Owned(results)));
+}
+
+base::CancelableTaskTracker::TaskId HistoryService::GetFaviconsForURL(
+ const GURL& page_url,
+ int icon_types,
+ const std::vector<int>& desired_sizes,
+ const favicon_base::FaviconResultsCallback& callback,
+ base::CancelableTaskTracker* tracker) {
+ DCHECK(thread_) << "History service being called after cleanup";
+ DCHECK(thread_checker_.CalledOnValidThread());
+ std::vector<favicon_base::FaviconRawBitmapResult>* results =
+ new std::vector<favicon_base::FaviconRawBitmapResult>();
+ return tracker->PostTaskAndReply(
+ thread_->message_loop_proxy().get(), FROM_HERE,
+ base::Bind(&HistoryBackend::GetFaviconsForURL, history_backend_.get(),
+ page_url, icon_types, desired_sizes, results),
+ base::Bind(&RunWithFaviconResults, callback, base::Owned(results)));
+}
+
+base::CancelableTaskTracker::TaskId HistoryService::GetLargestFaviconForURL(
+ const GURL& page_url,
+ const std::vector<int>& icon_types,
+ int minimum_size_in_pixels,
+ const favicon_base::FaviconRawBitmapCallback& callback,
+ base::CancelableTaskTracker* tracker) {
+ DCHECK(thread_) << "History service being called after cleanup";
+ DCHECK(thread_checker_.CalledOnValidThread());
+ favicon_base::FaviconRawBitmapResult* result =
+ new favicon_base::FaviconRawBitmapResult();
+ return tracker->PostTaskAndReply(
+ thread_->message_loop_proxy().get(), FROM_HERE,
+ base::Bind(&HistoryBackend::GetLargestFaviconForURL,
+ history_backend_.get(), page_url, icon_types,
+ minimum_size_in_pixels, result),
+ base::Bind(&RunWithFaviconResult, callback, base::Owned(result)));
+}
+
+base::CancelableTaskTracker::TaskId HistoryService::GetFaviconForID(
+ favicon_base::FaviconID favicon_id,
+ int desired_size,
+ const favicon_base::FaviconResultsCallback& callback,
+ base::CancelableTaskTracker* tracker) {
+ DCHECK(thread_) << "History service being called after cleanup";
+ DCHECK(thread_checker_.CalledOnValidThread());
+ std::vector<favicon_base::FaviconRawBitmapResult>* results =
+ new std::vector<favicon_base::FaviconRawBitmapResult>();
+ return tracker->PostTaskAndReply(
+ thread_->message_loop_proxy().get(), FROM_HERE,
+ base::Bind(&HistoryBackend::GetFaviconForID, history_backend_.get(),
+ favicon_id, desired_size, results),
+ base::Bind(&RunWithFaviconResults, callback, base::Owned(results)));
+}
+
+base::CancelableTaskTracker::TaskId
+HistoryService::UpdateFaviconMappingsAndFetch(
+ const GURL& page_url,
+ const std::vector<GURL>& icon_urls,
+ int icon_types,
+ const std::vector<int>& desired_sizes,
+ const favicon_base::FaviconResultsCallback& callback,
+ base::CancelableTaskTracker* tracker) {
+ DCHECK(thread_) << "History service being called after cleanup";
+ DCHECK(thread_checker_.CalledOnValidThread());
+ std::vector<favicon_base::FaviconRawBitmapResult>* results =
+ new std::vector<favicon_base::FaviconRawBitmapResult>();
+ return tracker->PostTaskAndReply(
+ thread_->message_loop_proxy().get(), FROM_HERE,
+ base::Bind(&HistoryBackend::UpdateFaviconMappingsAndFetch,
+ history_backend_.get(), page_url, icon_urls, icon_types,
+ desired_sizes, results),
+ base::Bind(&RunWithFaviconResults, callback, base::Owned(results)));
+}
+
+void HistoryService::MergeFavicon(
+ const GURL& page_url,
+ const GURL& icon_url,
+ favicon_base::IconType icon_type,
+ scoped_refptr<base::RefCountedMemory> bitmap_data,
+ const gfx::Size& pixel_size) {
+ DCHECK(thread_) << "History service being called after cleanup";
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (history_client_ && !history_client_->CanAddURL(page_url))
+ return;
+
+ ScheduleTask(
+ PRIORITY_NORMAL,
+ base::Bind(&HistoryBackend::MergeFavicon, history_backend_.get(),
+ page_url, icon_url, icon_type, bitmap_data, pixel_size));
+}
+
+void HistoryService::SetFavicons(const GURL& page_url,
+ favicon_base::IconType icon_type,
+ const GURL& icon_url,
+ const std::vector<SkBitmap>& bitmaps) {
+ DCHECK(thread_) << "History service being called after cleanup";
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (history_client_ && !history_client_->CanAddURL(page_url))
+ return;
+
+ ScheduleTask(PRIORITY_NORMAL,
+ base::Bind(&HistoryBackend::SetFavicons, history_backend_.get(),
+ page_url, icon_type, icon_url, bitmaps));
+}
+
+void HistoryService::SetFaviconsOutOfDateForPage(const GURL& page_url) {
+ DCHECK(thread_) << "History service being called after cleanup";
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ScheduleTask(PRIORITY_NORMAL,
+ base::Bind(&HistoryBackend::SetFaviconsOutOfDateForPage,
+ history_backend_.get(), page_url));
+}
+
+void HistoryService::CloneFavicons(const GURL& old_page_url,
+ const GURL& new_page_url) {
+ DCHECK(thread_) << "History service being called after cleanup";
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ScheduleTask(PRIORITY_NORMAL,
+ base::Bind(&HistoryBackend::CloneFavicons,
+ history_backend_.get(), old_page_url, new_page_url));
+}
+
+void HistoryService::SetImportedFavicons(
+ const favicon_base::FaviconUsageDataList& favicon_usage) {
+ DCHECK(thread_) << "History service being called after cleanup";
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ScheduleTask(PRIORITY_NORMAL,
+ base::Bind(&HistoryBackend::SetImportedFavicons,
+ history_backend_.get(), favicon_usage));
+}
+
+base::CancelableTaskTracker::TaskId HistoryService::QueryURL(
+ const GURL& url,
+ bool want_visits,
+ const QueryURLCallback& callback,
+ base::CancelableTaskTracker* tracker) {
+ DCHECK(thread_) << "History service being called after cleanup";
+ DCHECK(thread_checker_.CalledOnValidThread());
+ history::QueryURLResult* query_url_result = new history::QueryURLResult();
+ return tracker->PostTaskAndReply(
+ thread_->message_loop_proxy().get(), FROM_HERE,
+ base::Bind(&HistoryBackend::QueryURL, history_backend_.get(), url,
+ want_visits, base::Unretained(query_url_result)),
+ base::Bind(&RunWithQueryURLResult, callback,
+ base::Owned(query_url_result)));
+}
+
+// Downloads -------------------------------------------------------------------
+
+// Handle creation of a download by creating an entry in the history service's
+// 'downloads' table.
+void HistoryService::CreateDownload(
+ const history::DownloadRow& create_info,
+ const HistoryService::DownloadCreateCallback& callback) {
+ DCHECK(thread_) << "History service being called after cleanup";
+ DCHECK(thread_checker_.CalledOnValidThread());
+ PostTaskAndReplyWithResult(thread_->message_loop_proxy().get(), FROM_HERE,
+ base::Bind(&HistoryBackend::CreateDownload,
+ history_backend_.get(), create_info),
+ callback);
+}
+
+void HistoryService::GetNextDownloadId(const DownloadIdCallback& callback) {
+ DCHECK(thread_) << "History service being called after cleanup";
+ DCHECK(thread_checker_.CalledOnValidThread());
+ PostTaskAndReplyWithResult(
+ thread_->message_loop_proxy().get(), FROM_HERE,
+ base::Bind(&HistoryBackend::GetNextDownloadId, history_backend_.get()),
+ callback);
+}
+
+// Handle queries for a list of all downloads in the history database's
+// 'downloads' table.
+void HistoryService::QueryDownloads(const DownloadQueryCallback& callback) {
+ DCHECK(thread_) << "History service being called after cleanup";
+ DCHECK(thread_checker_.CalledOnValidThread());
+ std::vector<history::DownloadRow>* rows =
+ new std::vector<history::DownloadRow>();
+ scoped_ptr<std::vector<history::DownloadRow>> scoped_rows(rows);
+ // Beware! The first Bind() does not simply |scoped_rows.get()| because
+ // base::Passed(&scoped_rows) nullifies |scoped_rows|, and compilers do not
+ // guarantee that the first Bind's arguments are evaluated before the second
+ // Bind's arguments.
+ thread_->message_loop_proxy()->PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&HistoryBackend::QueryDownloads, history_backend_.get(), rows),
+ base::Bind(callback, base::Passed(&scoped_rows)));
+}
+
+// Handle updates for a particular download. This is a 'fire and forget'
+// operation, so we don't need to be called back.
+void HistoryService::UpdateDownload(const history::DownloadRow& data) {
+ DCHECK(thread_) << "History service being called after cleanup";
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ScheduleTask(PRIORITY_NORMAL, base::Bind(&HistoryBackend::UpdateDownload,
+ history_backend_.get(), data));
+}
+
+void HistoryService::RemoveDownloads(const std::set<uint32>& ids) {
+ DCHECK(thread_) << "History service being called after cleanup";
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ScheduleTask(PRIORITY_NORMAL, base::Bind(&HistoryBackend::RemoveDownloads,
+ history_backend_.get(), ids));
+}
+
+base::CancelableTaskTracker::TaskId HistoryService::QueryHistory(
+ const base::string16& text_query,
+ const history::QueryOptions& options,
+ const QueryHistoryCallback& callback,
+ base::CancelableTaskTracker* tracker) {
+ DCHECK(thread_) << "History service being called after cleanup";
+ DCHECK(thread_checker_.CalledOnValidThread());
+ history::QueryResults* query_results = new history::QueryResults();
+ return tracker->PostTaskAndReply(
+ thread_->message_loop_proxy().get(), FROM_HERE,
+ base::Bind(&HistoryBackend::QueryHistory, history_backend_.get(),
+ text_query, options, base::Unretained(query_results)),
+ base::Bind(callback, base::Owned(query_results)));
+}
+
+base::CancelableTaskTracker::TaskId HistoryService::QueryRedirectsFrom(
+ const GURL& from_url,
+ const QueryRedirectsCallback& callback,
+ base::CancelableTaskTracker* tracker) {
+ DCHECK(thread_) << "History service being called after cleanup";
+ DCHECK(thread_checker_.CalledOnValidThread());
+ history::RedirectList* result = new history::RedirectList();
+ return tracker->PostTaskAndReply(
+ thread_->message_loop_proxy().get(), FROM_HERE,
+ base::Bind(&HistoryBackend::QueryRedirectsFrom, history_backend_.get(),
+ from_url, base::Unretained(result)),
+ base::Bind(callback, base::Owned(result)));
+}
+
+base::CancelableTaskTracker::TaskId HistoryService::QueryRedirectsTo(
+ const GURL& to_url,
+ const QueryRedirectsCallback& callback,
+ base::CancelableTaskTracker* tracker) {
+ DCHECK(thread_) << "History service being called after cleanup";
+ DCHECK(thread_checker_.CalledOnValidThread());
+ history::RedirectList* result = new history::RedirectList();
+ return tracker->PostTaskAndReply(
+ thread_->message_loop_proxy().get(), FROM_HERE,
+ base::Bind(&HistoryBackend::QueryRedirectsTo, history_backend_.get(),
+ to_url, base::Unretained(result)),
+ base::Bind(callback, base::Owned(result)));
+}
+
+base::CancelableTaskTracker::TaskId HistoryService::GetVisibleVisitCountToHost(
+ const GURL& url,
+ const GetVisibleVisitCountToHostCallback& callback,
+ base::CancelableTaskTracker* tracker) {
+ DCHECK(thread_) << "History service being called after cleanup";
+ DCHECK(thread_checker_.CalledOnValidThread());
+ history::VisibleVisitCountToHostResult* result =
+ new history::VisibleVisitCountToHostResult();
+ return tracker->PostTaskAndReply(
+ thread_->message_loop_proxy().get(), FROM_HERE,
+ base::Bind(&HistoryBackend::GetVisibleVisitCountToHost,
+ history_backend_.get(), url, base::Unretained(result)),
+ base::Bind(&RunWithVisibleVisitCountToHostResult, callback,
+ base::Owned(result)));
+}
+
+base::CancelableTaskTracker::TaskId HistoryService::QueryMostVisitedURLs(
+ int result_count,
+ int days_back,
+ const QueryMostVisitedURLsCallback& callback,
+ base::CancelableTaskTracker* tracker) {
+ DCHECK(thread_) << "History service being called after cleanup";
+ DCHECK(thread_checker_.CalledOnValidThread());
+ history::MostVisitedURLList* result = new history::MostVisitedURLList();
+ return tracker->PostTaskAndReply(
+ thread_->message_loop_proxy().get(), FROM_HERE,
+ base::Bind(&HistoryBackend::QueryMostVisitedURLs, history_backend_.get(),
+ result_count, days_back, base::Unretained(result)),
+ base::Bind(callback, base::Owned(result)));
+}
+
+base::CancelableTaskTracker::TaskId HistoryService::QueryFilteredURLs(
+ int result_count,
+ const history::VisitFilter& filter,
+ bool extended_info,
+ const QueryFilteredURLsCallback& callback,
+ base::CancelableTaskTracker* tracker) {
+ DCHECK(thread_) << "History service being called after cleanup";
+ DCHECK(thread_checker_.CalledOnValidThread());
+ history::FilteredURLList* result = new history::FilteredURLList();
+ return tracker->PostTaskAndReply(
+ thread_->message_loop_proxy().get(), FROM_HERE,
+ base::Bind(&HistoryBackend::QueryFilteredURLs, history_backend_.get(),
+ result_count, filter, extended_info, base::Unretained(result)),
+ base::Bind(callback, base::Owned(result)));
+}
+
+void HistoryService::Cleanup() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (!thread_) {
+ // We've already cleaned up.
+ return;
+ }
+
+ NotifyHistoryServiceBeingDeleted();
+
+ weak_ptr_factory_.InvalidateWeakPtrs();
+
+ // Unload the backend.
+ if (history_backend_.get()) {
+ // Get rid of the in-memory backend.
+ in_memory_backend_.reset();
+
+ // The backend's destructor must run on the history thread since it is not
+ // threadsafe. So this thread must not be the last thread holding a
+ // reference to the backend, or a crash could happen.
+ //
+ // We have a reference to the history backend. There is also an extra
+ // reference held by our delegate installed in the backend, which
+ // HistoryBackend::Closing will release. This means if we scheduled a call
+ // to HistoryBackend::Closing and *then* released our backend reference,
+ // there will be a race between us and the backend's Closing function to see
+ // who is the last holder of a reference. If the backend thread's Closing
+ // manages to run before we release our backend refptr, the last reference
+ // will be held by this thread and the destructor will be called from here.
+ //
+ // Therefore, we create a closure to run the Closing operation first. This
+ // holds a reference to the backend. Then we release our reference, then we
+ // schedule the task to run. After the task runs, it will delete its
+ // reference from the history thread, ensuring everything works properly.
+ //
+ // TODO(ajwong): Cleanup HistoryBackend lifetime issues.
+ // See http://crbug.com/99767.
+ history_backend_->AddRef();
+ base::Closure closing_task =
+ base::Bind(&HistoryBackend::Closing, history_backend_.get());
+ ScheduleTask(PRIORITY_NORMAL, closing_task);
+ closing_task.Reset();
+ HistoryBackend* raw_ptr = history_backend_.get();
+ history_backend_ = nullptr;
+ thread_->message_loop()->ReleaseSoon(FROM_HERE, raw_ptr);
+ }
+
+ // Delete the thread, which joins with the background thread. We defensively
+ // nullptr the pointer before deleting it in case somebody tries to use it
+ // during shutdown, but this shouldn't happen.
+ base::Thread* thread = thread_;
+ thread_ = nullptr;
+ delete thread;
+}
+
+bool HistoryService::Init(
+ bool no_db,
+ const std::string& languages,
+ const history::HistoryDatabaseParams& history_database_params) {
+ DCHECK(thread_) << "History service being called after cleanup";
+ DCHECK(thread_checker_.CalledOnValidThread());
+ base::Thread::Options options;
+ options.timer_slack = base::TIMER_SLACK_MAXIMUM;
+ if (!thread_->StartWithOptions(options)) {
+ Cleanup();
+ return false;
+ }
+
+ // Create the history backend.
+ scoped_refptr<HistoryBackend> backend(new HistoryBackend(
+ new BackendDelegate(weak_ptr_factory_.GetWeakPtr(),
+ base::ThreadTaskRunnerHandle::Get()),
+ history_client_));
+ history_backend_.swap(backend);
+
+ ScheduleTask(PRIORITY_UI,
+ base::Bind(&HistoryBackend::Init, history_backend_.get(),
+ languages, no_db, history_database_params));
+
+ if (visit_delegate_ && !visit_delegate_->Init(this))
+ return false;
+
+ return true;
+}
+
+void HistoryService::ScheduleAutocomplete(const base::Callback<
+ void(history::HistoryBackend*, history::URLDatabase*)>& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ScheduleTask(PRIORITY_UI, base::Bind(&HistoryBackend::ScheduleAutocomplete,
+ history_backend_.get(), callback));
+}
+
+void HistoryService::ScheduleTask(SchedulePriority priority,
+ const base::Closure& task) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ CHECK(thread_);
+ CHECK(thread_->message_loop());
+ // TODO(brettw): Do prioritization.
+ thread_->message_loop()->PostTask(FROM_HERE, task);
+}
+
+base::WeakPtr<HistoryService> HistoryService::AsWeakPtr() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return weak_ptr_factory_.GetWeakPtr();
+}
+
+syncer::SyncMergeResult HistoryService::MergeDataAndStartSyncing(
+ syncer::ModelType type,
+ const syncer::SyncDataList& initial_sync_data,
+ scoped_ptr<syncer::SyncChangeProcessor> sync_processor,
+ scoped_ptr<syncer::SyncErrorFactory> error_handler) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_EQ(type, syncer::HISTORY_DELETE_DIRECTIVES);
+ delete_directive_handler_.Start(this, initial_sync_data,
+ sync_processor.Pass());
+ return syncer::SyncMergeResult(type);
+}
+
+void HistoryService::StopSyncing(syncer::ModelType type) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_EQ(type, syncer::HISTORY_DELETE_DIRECTIVES);
+ delete_directive_handler_.Stop();
+}
+
+syncer::SyncDataList HistoryService::GetAllSyncData(
+ syncer::ModelType type) const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_EQ(type, syncer::HISTORY_DELETE_DIRECTIVES);
+ // TODO(akalin): Keep track of existing delete directives.
+ return syncer::SyncDataList();
+}
+
+syncer::SyncError HistoryService::ProcessSyncChanges(
+ const tracked_objects::Location& from_here,
+ const syncer::SyncChangeList& change_list) {
+ delete_directive_handler_.ProcessSyncChanges(this, change_list);
+ return syncer::SyncError();
+}
+
+syncer::SyncError HistoryService::ProcessLocalDeleteDirective(
+ const sync_pb::HistoryDeleteDirectiveSpecifics& delete_directive) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return delete_directive_handler_.ProcessLocalDeleteDirective(
+ delete_directive);
+}
+
+void HistoryService::SetInMemoryBackend(
+ scoped_ptr<history::InMemoryHistoryBackend> mem_backend) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!in_memory_backend_) << "Setting mem DB twice";
+ in_memory_backend_.reset(mem_backend.release());
+
+ // The database requires additional initialization once we own it.
+ in_memory_backend_->AttachToHistoryService(this);
+}
+
+void HistoryService::NotifyProfileError(sql::InitStatus init_status) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (history_client_)
+ history_client_->NotifyProfileError(init_status);
+}
+
+void HistoryService::DeleteURL(const GURL& url) {
+ DCHECK(thread_) << "History service being called after cleanup";
+ DCHECK(thread_checker_.CalledOnValidThread());
+ // We will update the visited links when we observe the delete notifications.
+ ScheduleTask(PRIORITY_NORMAL, base::Bind(&HistoryBackend::DeleteURL,
+ history_backend_.get(), url));
+}
+
+void HistoryService::DeleteURLsForTest(const std::vector<GURL>& urls) {
+ DCHECK(thread_) << "History service being called after cleanup";
+ DCHECK(thread_checker_.CalledOnValidThread());
+ // We will update the visited links when we observe the delete
+ // notifications.
+ ScheduleTask(PRIORITY_NORMAL, base::Bind(&HistoryBackend::DeleteURLs,
+ history_backend_.get(), urls));
+}
+
+void HistoryService::ExpireHistoryBetween(
+ const std::set<GURL>& restrict_urls,
+ Time begin_time,
+ Time end_time,
+ const base::Closure& callback,
+ base::CancelableTaskTracker* tracker) {
+ DCHECK(thread_) << "History service being called after cleanup";
+ DCHECK(thread_checker_.CalledOnValidThread());
+ tracker->PostTaskAndReply(
+ thread_->message_loop_proxy().get(), FROM_HERE,
+ base::Bind(&HistoryBackend::ExpireHistoryBetween, history_backend_,
+ restrict_urls, begin_time, end_time),
+ callback);
+}
+
+void HistoryService::ExpireHistory(
+ const std::vector<history::ExpireHistoryArgs>& expire_list,
+ const base::Closure& callback,
+ base::CancelableTaskTracker* tracker) {
+ DCHECK(thread_) << "History service being called after cleanup";
+ DCHECK(thread_checker_.CalledOnValidThread());
+ tracker->PostTaskAndReply(
+ thread_->message_loop_proxy().get(), FROM_HERE,
+ base::Bind(&HistoryBackend::ExpireHistory, history_backend_, expire_list),
+ callback);
+}
+
+void HistoryService::ExpireLocalAndRemoteHistoryBetween(
+ history::WebHistoryService* web_history,
+ const std::set<GURL>& restrict_urls,
+ Time begin_time,
+ Time end_time,
+ const base::Closure& callback,
+ base::CancelableTaskTracker* tracker) {
+ // TODO(dubroy): This should be factored out into a separate class that
+ // dispatches deletions to the proper places.
+ if (web_history) {
+ // TODO(dubroy): This API does not yet support deletion of specific URLs.
+ DCHECK(restrict_urls.empty());
+
+ delete_directive_handler_.CreateDeleteDirectives(std::set<int64>(),
+ begin_time, end_time);
+
+ // Attempt online deletion from the history server, but ignore the result.
+ // Deletion directives ensure that the results will eventually be deleted.
+ //
+ // TODO(davidben): |callback| should not run until this operation completes
+ // too.
+ web_history->ExpireHistoryBetween(restrict_urls, begin_time, end_time,
+ base::Bind(&ExpireWebHistoryComplete));
+ }
+ ExpireHistoryBetween(restrict_urls, begin_time, end_time, callback, tracker);
+}
+
+void HistoryService::OnDBLoaded() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ backend_loaded_ = true;
+ NotifyHistoryServiceLoaded();
+}
+
+bool HistoryService::GetRowForURL(const GURL& url, history::URLRow* url_row) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ history::URLDatabase* db = InMemoryDatabase();
+ return db && (db->GetRowForURL(url, url_row) != 0);
+}
+
+void HistoryService::NotifyAddVisit(const history::BriefVisitInfo& info) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ FOR_EACH_OBSERVER(history::HistoryServiceObserver, observers_,
+ OnAddVisit(this, info));
+}
+
+void HistoryService::NotifyURLVisited(ui::PageTransition transition,
+ const history::URLRow& row,
+ const history::RedirectList& redirects,
+ base::Time visit_time) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ FOR_EACH_OBSERVER(history::HistoryServiceObserver, observers_,
+ OnURLVisited(this, transition, row, redirects, visit_time));
+}
+
+void HistoryService::NotifyURLsModified(const history::URLRows& changed_urls) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ FOR_EACH_OBSERVER(history::HistoryServiceObserver, observers_,
+ OnURLsModified(this, changed_urls));
+}
+
+void HistoryService::NotifyURLsDeleted(bool all_history,
+ bool expired,
+ const history::URLRows& deleted_rows,
+ const std::set<GURL>& favicon_urls) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (!thread_)
+ return;
+
+ // Inform the VisitDelegate of the deleted URLs. We will inform the delegate
+ // of added URLs as soon as we get the add notification (we don't have to wait
+ // for the backend, which allows us to be faster to update the state).
+ //
+ // For deleted URLs, we don't typically know what will be deleted since
+ // delete notifications are by time. We would also like to be more
+ // respectful of privacy and never tell the user something is gone when it
+ // isn't. Therefore, we update the delete URLs after the fact.
+ if (visit_delegate_) {
+ if (all_history) {
+ visit_delegate_->DeleteAllURLs();
+ } else {
+ std::vector<GURL> urls;
+ urls.reserve(deleted_rows.size());
+ for (const auto& row : deleted_rows)
+ urls.push_back(row.url());
+ visit_delegate_->DeleteURLs(urls);
+ }
+ }
+
+ FOR_EACH_OBSERVER(
+ history::HistoryServiceObserver, observers_,
+ OnURLsDeleted(this, all_history, expired, deleted_rows, favicon_urls));
+}
+
+void HistoryService::NotifyHistoryServiceLoaded() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ FOR_EACH_OBSERVER(history::HistoryServiceObserver, observers_,
+ OnHistoryServiceLoaded(this));
+}
+
+void HistoryService::NotifyHistoryServiceBeingDeleted() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ FOR_EACH_OBSERVER(history::HistoryServiceObserver, observers_,
+ HistoryServiceBeingDeleted(this));
+}
+
+void HistoryService::NotifyKeywordSearchTermUpdated(
+ const history::URLRow& row,
+ history::KeywordID keyword_id,
+ const base::string16& term) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ FOR_EACH_OBSERVER(history::HistoryServiceObserver, observers_,
+ OnKeywordSearchTermUpdated(this, row, keyword_id, term));
+}
+
+void HistoryService::NotifyKeywordSearchTermDeleted(history::URLID url_id) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ FOR_EACH_OBSERVER(history::HistoryServiceObserver, observers_,
+ OnKeywordSearchTermDeleted(this, url_id));
+}
+
+scoped_ptr<base::CallbackList<void(const std::set<GURL>&)>::Subscription>
+HistoryService::AddFaviconChangedCallback(
+ const HistoryService::OnFaviconChangedCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return favicon_changed_callback_list_.Add(callback);
+}
+
+void HistoryService::NotifyFaviconChanged(
+ const std::set<GURL>& changed_favicons) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ favicon_changed_callback_list_.Notify(changed_favicons);
+}
diff --git a/components/history/core/browser/history_service.h b/components/history/core/browser/history_service.h
new file mode 100644
index 0000000..dda6ef8
--- /dev/null
+++ b/components/history/core/browser/history_service.h
@@ -0,0 +1,812 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_HISTORY_CORE_BROWSER_HISTORY_SERVICE_H_
+#define COMPONENTS_HISTORY_CORE_BROWSER_HISTORY_SERVICE_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/callback_list.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "base/strings/string16.h"
+#include "base/task/cancelable_task_tracker.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/time.h"
+#include "components/favicon_base/favicon_callback.h"
+#include "components/favicon_base/favicon_usage_data.h"
+#include "components/history/core/browser/delete_directive_handler.h"
+#include "components/history/core/browser/keyword_id.h"
+#include "components/history/core/browser/typed_url_syncable_service.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "sql/init_status.h"
+#include "sync/api/syncable_service.h"
+#include "ui/base/page_transition_types.h"
+
+class GURL;
+class InMemoryURLIndexTest;
+class PageUsageRequest;
+class SkBitmap;
+
+namespace base {
+class FilePath;
+class Thread;
+}
+
+namespace history {
+
+struct DownloadRow;
+struct HistoryAddPageArgs;
+class HistoryBackend;
+class HistoryClient;
+class HistoryDBTask;
+class HistoryDatabase;
+struct HistoryDatabaseParams;
+class HistoryQueryTest;
+class HistoryServiceObserver;
+class HistoryTest;
+class InMemoryHistoryBackend;
+struct KeywordSearchTermVisit;
+class PageUsageData;
+class URLDatabase;
+class VisitDelegate;
+class VisitFilter;
+class WebHistoryService;
+
+} // namespace history
+
+// The history service records page titles, and visit times, as well as
+// (eventually) information about autocomplete.
+//
+// This service is thread safe. Each request callback is invoked in the
+// thread that made the request.
+class HistoryService : public syncer::SyncableService, public KeyedService {
+ public:
+ // Miscellaneous commonly-used types.
+ typedef std::vector<history::PageUsageData*> PageUsageDataList;
+
+ // Must call Init after construction. The empty constructor provided only for
+ // unit tests. When using the full constructor, |history_client| may only be
+ // null during testing, while |visit_delegate| may be null if the embedder use
+ // another way to track visited links.
+ HistoryService();
+ HistoryService(history::HistoryClient* history_client,
+ scoped_ptr<history::VisitDelegate> visit_delegate);
+ ~HistoryService() override;
+
+ // Initializes the history service, returning true on success. On false, do
+ // not call any other functions. The given directory will be used for storing
+ // the history files. |languages| is a comma-separated list of languages to
+ // use when interpreting URLs, it must not be empty (except during testing).
+ bool Init(const std::string& languages,
+ const history::HistoryDatabaseParams& history_database_params) {
+ return Init(false, languages, history_database_params);
+ }
+
+ // Triggers the backend to load if it hasn't already, and then returns whether
+ // it's finished loading.
+ // Note: Virtual needed for mocking.
+ virtual bool BackendLoaded();
+
+ // Returns true if the backend has finished loading.
+ bool backend_loaded() const { return backend_loaded_; }
+
+ // Context ids are used to scope page IDs (see AddPage). These contexts
+ // must tell us when they are being invalidated so that we can clear
+ // out any cached data associated with that context.
+ void ClearCachedDataForContextID(history::ContextID context_id);
+
+ // Triggers the backend to load if it hasn't already, and then returns the
+ // in-memory URL database. The returned pointer may be null if the in-memory
+ // database has not been loaded yet. This pointer is owned by the history
+ // system. Callers should not store or cache this value.
+ //
+ // TODO(brettw) this should return the InMemoryHistoryBackend.
+ history::URLDatabase* InMemoryDatabase();
+
+ // Following functions get URL information from in-memory database.
+ // They return false if database is not available (e.g. not loaded yet) or the
+ // URL does not exist.
+
+ // Reads the number of times the user has typed the given URL.
+ bool GetTypedCountForURL(const GURL& url, int* typed_count);
+
+ // Reads the last visit time for the given URL.
+ bool GetLastVisitTimeForURL(const GURL& url, base::Time* last_visit);
+
+ // Reads the number of times this URL has been visited.
+ bool GetVisitCountForURL(const GURL& url, int* visit_count);
+
+ // Returns a pointer to the TypedUrlSyncableService owned by HistoryBackend.
+ // This method should only be called from the history thread, because the
+ // returned service is intended to be accessed only via the history thread.
+ history::TypedUrlSyncableService* GetTypedUrlSyncableService() const;
+
+ // KeyedService:
+ void Shutdown() override;
+
+ // Navigation ----------------------------------------------------------------
+
+ // Adds the given canonical URL to history with the given time as the visit
+ // time. Referrer may be the empty string.
+ //
+ // The supplied context id is used to scope the given page ID. Page IDs
+ // are only unique inside a given context, so we need that to differentiate
+ // them.
+ //
+ // The context/page ids can be null if there is no meaningful tracking
+ // information that can be performed on the given URL. The 'nav_entry_id'
+ // should be the unique ID of the current navigation entry in the given
+ // process.
+ //
+ // 'redirects' is an array of redirect URLs leading to this page, with the
+ // page itself as the last item (so when there is no redirect, it will have
+ // one entry). If there are no redirects, this array may also be empty for
+ // the convenience of callers.
+ //
+ // 'did_replace_entry' is true when the navigation entry for this page has
+ // replaced the existing entry. A non-user initiated redirect causes such
+ // replacement.
+ //
+ // All "Add Page" functions will update the visited link database.
+ void AddPage(const GURL& url,
+ base::Time time,
+ history::ContextID context_id,
+ int nav_entry_id,
+ const GURL& referrer,
+ const history::RedirectList& redirects,
+ ui::PageTransition transition,
+ history::VisitSource visit_source,
+ bool did_replace_entry);
+
+ // For adding pages to history where no tracking information can be done.
+ void AddPage(const GURL& url,
+ base::Time time,
+ history::VisitSource visit_source);
+
+ // All AddPage variants end up here.
+ void AddPage(const history::HistoryAddPageArgs& add_page_args);
+
+ // Adds an entry for the specified url without creating a visit. This should
+ // only be used when bookmarking a page, otherwise the row leaks in the
+ // history db (it never gets cleaned).
+ void AddPageNoVisitForBookmark(const GURL& url, const base::string16& title);
+
+ // Sets the title for the given page. The page should be in history. If it
+ // is not, this operation is ignored.
+ void SetPageTitle(const GURL& url, const base::string16& title);
+
+ // Updates the history database with a page's ending time stamp information.
+ // The page can be identified by the combination of the context id, the
+ // navigation entry id and the url.
+ void UpdateWithPageEndTime(history::ContextID context_id,
+ int nav_entry_id,
+ const GURL& url,
+ base::Time end_ts);
+
+ // Querying ------------------------------------------------------------------
+
+ // Returns the information about the requested URL. If the URL is found,
+ // success will be true and the information will be in the URLRow parameter.
+ // On success, the visits, if requested, will be sorted by date. If they have
+ // not been requested, the pointer will be valid, but the vector will be
+ // empty.
+ //
+ // If success is false, neither the row nor the vector will be valid.
+ typedef base::Callback<void(
+ bool, // Success flag, when false, nothing else is valid.
+ const history::URLRow&,
+ const history::VisitVector&)> QueryURLCallback;
+
+ // Queries the basic information about the URL in the history database. If
+ // the caller is interested in the visits (each time the URL is visited),
+ // set |want_visits| to true. If these are not needed, the function will be
+ // faster by setting this to false.
+ base::CancelableTaskTracker::TaskId QueryURL(
+ const GURL& url,
+ bool want_visits,
+ const QueryURLCallback& callback,
+ base::CancelableTaskTracker* tracker);
+
+ // Provides the result of a query. See QueryResults in history_types.h.
+ // The common use will be to use QueryResults.Swap to suck the contents of
+ // the results out of the passed in parameter and take ownership of them.
+ typedef base::Callback<void(history::QueryResults*)> QueryHistoryCallback;
+
+ // Queries all history with the given options (see QueryOptions in
+ // history_types.h). If empty, all results matching the given options
+ // will be returned.
+ base::CancelableTaskTracker::TaskId QueryHistory(
+ const base::string16& text_query,
+ const history::QueryOptions& options,
+ const QueryHistoryCallback& callback,
+ base::CancelableTaskTracker* tracker);
+
+ // Called when the results of QueryRedirectsFrom are available.
+ // The given vector will contain a list of all redirects, not counting
+ // the original page. If A redirects to B which redirects to C, the vector
+ // will contain [B, C], and A will be in 'from_url'.
+ //
+ // For QueryRedirectsTo, the order is reversed. For A->B->C, the vector will
+ // contain [B, A] and C will be in 'to_url'.
+ //
+ // If there is no such URL in the database or the most recent visit has no
+ // redirect, the vector will be empty. If the given page has redirected to
+ // multiple destinations, this will pick a random one.
+ typedef base::Callback<void(const history::RedirectList*)>
+ QueryRedirectsCallback;
+
+ // Schedules a query for the most recent redirect coming out of the given
+ // URL. See the RedirectQuerySource above, which is guaranteed to be called
+ // if the request is not canceled.
+ base::CancelableTaskTracker::TaskId QueryRedirectsFrom(
+ const GURL& from_url,
+ const QueryRedirectsCallback& callback,
+ base::CancelableTaskTracker* tracker);
+
+ // Schedules a query to get the most recent redirects ending at the given
+ // URL.
+ base::CancelableTaskTracker::TaskId QueryRedirectsTo(
+ const GURL& to_url,
+ const QueryRedirectsCallback& callback,
+ base::CancelableTaskTracker* tracker);
+
+ // Requests the number of user-visible visits (i.e. no redirects or subframes)
+ // to all urls on the same scheme/host/port as |url|. This is only valid for
+ // HTTP and HTTPS URLs.
+ typedef base::Callback<void(
+ bool, // Were we able to determine the # of visits?
+ int, // Number of visits.
+ base::Time)> // Time of first visit. Only set if bool
+ // is true and int is > 0.
+ GetVisibleVisitCountToHostCallback;
+
+ base::CancelableTaskTracker::TaskId GetVisibleVisitCountToHost(
+ const GURL& url,
+ const GetVisibleVisitCountToHostCallback& callback,
+ base::CancelableTaskTracker* tracker);
+
+ // Request the |result_count| most visited URLs and the chain of
+ // redirects leading to each of these URLs. |days_back| is the
+ // number of days of history to use. Used by TopSites.
+ typedef base::Callback<void(const history::MostVisitedURLList*)>
+ QueryMostVisitedURLsCallback;
+
+ base::CancelableTaskTracker::TaskId QueryMostVisitedURLs(
+ int result_count,
+ int days_back,
+ const QueryMostVisitedURLsCallback& callback,
+ base::CancelableTaskTracker* tracker);
+
+ // Request the |result_count| URLs filtered and sorted based on the |filter|.
+ // If |extended_info| is true, additional data will be provided in the
+ // results. Computing this additional data is expensive, likely to become
+ // more expensive as additional data points are added in future changes, and
+ // not useful in most cases. Set |extended_info| to true only if you
+ // explicitly require the additional data.
+ typedef base::Callback<void(const history::FilteredURLList*)>
+ QueryFilteredURLsCallback;
+
+ base::CancelableTaskTracker::TaskId QueryFilteredURLs(
+ int result_count,
+ const history::VisitFilter& filter,
+ bool extended_info,
+ const QueryFilteredURLsCallback& callback,
+ base::CancelableTaskTracker* tracker);
+
+ // Database management operations --------------------------------------------
+
+ // Delete all the information related to a single url.
+ void DeleteURL(const GURL& url);
+
+ // Delete all the information related to a list of urls. (Deleting
+ // URLs one by one is slow as it has to flush to disk each time.)
+ void DeleteURLsForTest(const std::vector<GURL>& urls);
+
+ // Removes all visits in the selected time range (including the
+ // start time), updating the URLs accordingly. This deletes any
+ // associated data. This function also deletes the associated
+ // favicons, if they are no longer referenced. |callback| runs when
+ // the expiration is complete. You may use null Time values to do an
+ // unbounded delete in either direction.
+ // If |restrict_urls| is not empty, only visits to the URLs in this set are
+ // removed.
+ void ExpireHistoryBetween(const std::set<GURL>& restrict_urls,
+ base::Time begin_time,
+ base::Time end_time,
+ const base::Closure& callback,
+ base::CancelableTaskTracker* tracker);
+
+ // Removes all visits to specified URLs in specific time ranges.
+ // This is the equivalent ExpireHistoryBetween() once for each element in the
+ // vector. The fields of |ExpireHistoryArgs| map directly to the arguments of
+ // of ExpireHistoryBetween().
+ void ExpireHistory(const std::vector<history::ExpireHistoryArgs>& expire_list,
+ const base::Closure& callback,
+ base::CancelableTaskTracker* tracker);
+
+ // Removes all visits to the given URLs in the specified time range. Calls
+ // ExpireHistoryBetween() to delete local visits, and handles deletion of
+ // synced visits if appropriate.
+ void ExpireLocalAndRemoteHistoryBetween(
+ history::WebHistoryService* web_history,
+ const std::set<GURL>& restrict_urls,
+ base::Time begin_time,
+ base::Time end_time,
+ const base::Closure& callback,
+ base::CancelableTaskTracker* tracker);
+
+ // Processes the given |delete_directive| and sends it to the
+ // SyncChangeProcessor (if it exists). Returns any error resulting
+ // from sending the delete directive to sync.
+ syncer::SyncError ProcessLocalDeleteDirective(
+ const sync_pb::HistoryDeleteDirectiveSpecifics& delete_directive);
+
+ // Downloads -----------------------------------------------------------------
+
+ // Implemented by the caller of 'CreateDownload' below, and is called when the
+ // history service has created a new entry for a download in the history db.
+ typedef base::Callback<void(bool)> DownloadCreateCallback;
+
+ // Begins a history request to create a new row for a download. 'info'
+ // contains all the download's creation state, and 'callback' runs when the
+ // history service request is complete. The callback is called on the thread
+ // that calls CreateDownload().
+ void CreateDownload(const history::DownloadRow& info,
+ const DownloadCreateCallback& callback);
+
+ // Implemented by the caller of 'GetNextDownloadId' below, and is called with
+ // the maximum id of all downloads records in the database plus 1.
+ typedef base::Callback<void(uint32)> DownloadIdCallback;
+
+ // Responds on the calling thread with the maximum id of all downloads records
+ // in the database plus 1.
+ void GetNextDownloadId(const DownloadIdCallback& callback);
+
+ // Implemented by the caller of 'QueryDownloads' below, and is called when the
+ // history service has retrieved a list of all download state. The call
+ typedef base::Callback<void(scoped_ptr<std::vector<history::DownloadRow>>)>
+ DownloadQueryCallback;
+
+ // Begins a history request to retrieve the state of all downloads in the
+ // history db. 'callback' runs when the history service request is complete,
+ // at which point 'info' contains an array of history::DownloadRow, one per
+ // download. The callback is called on the thread that calls QueryDownloads().
+ void QueryDownloads(const DownloadQueryCallback& callback);
+
+ // Called to update the history service about the current state of a download.
+ // This is a 'fire and forget' query, so just pass the relevant state info to
+ // the database with no need for a callback.
+ void UpdateDownload(const history::DownloadRow& data);
+
+ // Permanently remove some downloads from the history system. This is a 'fire
+ // and forget' operation.
+ void RemoveDownloads(const std::set<uint32>& ids);
+
+ // Keyword search terms -----------------------------------------------------
+
+ // Sets the search terms for the specified url and keyword. url_id gives the
+ // id of the url, keyword_id the id of the keyword and term the search term.
+ void SetKeywordSearchTermsForURL(const GURL& url,
+ history::KeywordID keyword_id,
+ const base::string16& term);
+
+ // Deletes all search terms for the specified keyword.
+ void DeleteAllSearchTermsForKeyword(history::KeywordID keyword_id);
+
+ // Deletes any search term corresponding to |url|.
+ void DeleteKeywordSearchTermForURL(const GURL& url);
+
+ // Deletes all URL and search term entries matching the given |term| and
+ // |keyword_id|.
+ void DeleteMatchingURLsForKeyword(history::KeywordID keyword_id,
+ const base::string16& term);
+
+ // Bookmarks -----------------------------------------------------------------
+
+ // Notification that a URL is no longer bookmarked.
+ void URLsNoLongerBookmarked(const std::set<GURL>& urls);
+
+ // Observers -----------------------------------------------------------------
+
+ // Adds/Removes an Observer.
+ void AddObserver(history::HistoryServiceObserver* observer);
+ void RemoveObserver(history::HistoryServiceObserver* observer);
+
+ // Generic Stuff -------------------------------------------------------------
+
+ // Schedules a HistoryDBTask for running on the history backend thread. See
+ // HistoryDBTask for details on what this does. Takes ownership of |task|.
+ virtual base::CancelableTaskTracker::TaskId ScheduleDBTask(
+ scoped_ptr<history::HistoryDBTask> task,
+ base::CancelableTaskTracker* tracker);
+
+ // This callback is invoked when favicon change for urls.
+ typedef base::Callback<void(const std::set<GURL>&)> OnFaviconChangedCallback;
+
+ // Add a callback to the list. The callback will remain registered until the
+ // returned Subscription is destroyed. This must occurs before HistoryService
+ // is destroyed.
+ scoped_ptr<base::CallbackList<void(const std::set<GURL>&)>::Subscription>
+ AddFaviconChangedCallback(const OnFaviconChangedCallback& callback)
+ WARN_UNUSED_RESULT;
+
+ // Testing -------------------------------------------------------------------
+
+ // Runs |flushed| after bouncing off the history thread.
+ void FlushForTest(const base::Closure& flushed);
+
+ // Designed for unit tests, this passes the given task on to the history
+ // backend to be called once the history backend has terminated. This allows
+ // callers to know when the history thread is complete and the database files
+ // can be deleted and the next test run. Otherwise, the history thread may
+ // still be running, causing problems in subsequent tests.
+ //
+ // There can be only one closing task, so this will override any previously
+ // set task. We will take ownership of the pointer and delete it when done.
+ // The task will be run on the calling thread (this function is threadsafe).
+ void SetOnBackendDestroyTask(const base::Closure& task);
+
+ // Used for unit testing and potentially importing to get known information
+ // into the database. This assumes the URL doesn't exist in the database
+ //
+ // Calling this function many times may be slow because each call will
+ // dispatch to the history thread and will be a separate database
+ // transaction. If this functionality is needed for importing many URLs,
+ // callers should use AddPagesWithDetails() instead.
+ //
+ // Note that this routine (and AddPageWithDetails()) always adds a single
+ // visit using the |last_visit| timestamp, and a PageTransition type of LINK,
+ // if |visit_source| != SYNCED.
+ void AddPageWithDetails(const GURL& url,
+ const base::string16& title,
+ int visit_count,
+ int typed_count,
+ base::Time last_visit,
+ bool hidden,
+ history::VisitSource visit_source);
+
+ // The same as AddPageWithDetails() but takes a vector.
+ void AddPagesWithDetails(const history::URLRows& info,
+ history::VisitSource visit_source);
+
+ base::WeakPtr<HistoryService> AsWeakPtr();
+
+ // syncer::SyncableService implementation.
+ syncer::SyncMergeResult MergeDataAndStartSyncing(
+ syncer::ModelType type,
+ const syncer::SyncDataList& initial_sync_data,
+ scoped_ptr<syncer::SyncChangeProcessor> sync_processor,
+ scoped_ptr<syncer::SyncErrorFactory> error_handler) override;
+ void StopSyncing(syncer::ModelType type) override;
+ syncer::SyncDataList GetAllSyncData(syncer::ModelType type) const override;
+ syncer::SyncError ProcessSyncChanges(
+ const tracked_objects::Location& from_here,
+ const syncer::SyncChangeList& change_list) override;
+
+ protected:
+ // These are not currently used, hopefully we can do something in the future
+ // to ensure that the most important things happen first.
+ enum SchedulePriority {
+ PRIORITY_UI, // The highest priority (must respond to UI events).
+ PRIORITY_NORMAL, // Normal stuff like adding a page.
+ PRIORITY_LOW, // Low priority things like indexing or expiration.
+ };
+
+ private:
+ class BackendDelegate;
+ friend class base::RefCountedThreadSafe<HistoryService>;
+ friend class BackendDelegate;
+ friend class FaviconService;
+ friend class history::HistoryBackend;
+ friend class history::HistoryQueryTest;
+ friend class HistoryOperation;
+ friend class HistoryQuickProviderTest;
+ friend class history::HistoryTest;
+ friend class HistoryURLProvider;
+ friend class HistoryURLProviderTest;
+ friend class ::InMemoryURLIndexTest;
+ template <typename Info, typename Callback>
+ friend class DownloadRequest;
+ friend class PageUsageRequest;
+ friend class RedirectRequest;
+ friend class SyncBookmarkDataTypeControllerTest;
+ friend class TestingProfile;
+
+ // Called on shutdown, this will tell the history backend to complete and
+ // will release pointers to it. No other functions should be called once
+ // cleanup has happened that may dispatch to the history thread (because it
+ // will be null).
+ //
+ // In practice, this will be called by the service manager (BrowserProcess)
+ // when it is being destroyed. Because that reference is being destroyed, it
+ // should be impossible for anybody else to call the service, even if it is
+ // still in memory (pending requests may be holding a reference to us).
+ void Cleanup();
+
+ // Low-level Init(). Same as the public version, but adds a |no_db| parameter
+ // that is only set by unittests which causes the backend to not init its DB.
+ bool Init(bool no_db,
+ const std::string& languages,
+ const history::HistoryDatabaseParams& history_database_params);
+
+ // Called by the HistoryURLProvider class to schedule an autocomplete, it
+ // will be called back on the internal history thread with the history
+ // database so it can query. See history_autocomplete.cc for a diagram.
+ void ScheduleAutocomplete(const base::Callback<
+ void(history::HistoryBackend*, history::URLDatabase*)>& callback);
+
+ // Notification from the backend that it has finished loading. Sends
+ // notification (NOTIFY_HISTORY_LOADED) and sets backend_loaded_ to true.
+ void OnDBLoaded();
+
+ // Helper function for getting URL information.
+ // Reads a URLRow from in-memory database. Returns false if database is not
+ // available or the URL does not exist.
+ bool GetRowForURL(const GURL& url, history::URLRow* url_row);
+
+ // Observers ----------------------------------------------------------------
+
+ // Notify all Observers registered that the VisitDatabase was changed.
+ void NotifyAddVisit(const history::BriefVisitInfo& info);
+
+ // Notify all HistoryServiceObservers registered that user is visiting a URL.
+ // The |row| ID will be set to the value that is currently in effect in the
+ // main history database. |redirects| is the list of redirects leading up to
+ // the URL. If we have a redirect chain A -> B -> C and user is visiting C,
+ // then |redirects[0]=B| and |redirects[1]=A|. If there are no redirects,
+ // |redirects| is an empty vector.
+ void NotifyURLVisited(ui::PageTransition transition,
+ const history::URLRow& row,
+ const history::RedirectList& redirects,
+ base::Time visit_time);
+
+ // Notify all HistoryServiceObservers registered that URLs have been added or
+ // modified. |changed_urls| contains the list of affects URLs.
+ void NotifyURLsModified(const history::URLRows& changed_urls);
+
+ // Notify all HistoryServiceObservers registered that URLs have been deleted.
+ // |all_history| is set to true, if all the URLs are deleted.
+ // When set to true, |deleted_rows| and |favicon_urls| are
+ // undefined.
+ // |expired| is set to true, if the URL deletion is due to expiration.
+ // |deleted_rows| list of the deleted URLs.
+ // |favicon_urls| list of favicon URLs that correspond to the deleted URLs.
+ void NotifyURLsDeleted(bool all_history,
+ bool expired,
+ const history::URLRows& deleted_rows,
+ const std::set<GURL>& favicon_urls);
+
+ // Notify all HistoryServiceObservers registered that the
+ // HistoryService has finished loading.
+ void NotifyHistoryServiceLoaded();
+
+ // Notify all HistoryServiceObservers registered that HistoryService is being
+ // deleted.
+ void NotifyHistoryServiceBeingDeleted();
+
+ // Notify all HistoryServiceObservers registered that a keyword search term
+ // has been updated. |row| contains the URL information for search |term|.
+ // |keyword_id| associated with a URL and search term.
+ void NotifyKeywordSearchTermUpdated(const history::URLRow& row,
+ history::KeywordID keyword_id,
+ const base::string16& term);
+
+ // Notify all HistoryServiceObservers registered that keyword search term is
+ // deleted. |url_id| is the id of the url row.
+ void NotifyKeywordSearchTermDeleted(history::URLID url_id);
+
+ // Favicon -------------------------------------------------------------------
+
+ // These favicon methods are exposed to the FaviconService. Instead of calling
+ // these methods directly you should call the respective method on the
+ // FaviconService.
+
+ // Used by FaviconService to get the favicon bitmaps from the history backend
+ // whose edge sizes most closely match |desired_sizes| for |icon_types|. If
+ // |desired_sizes| has a '0' entry, the largest favicon bitmap for
+ // |icon_types| is returned. The returned FaviconBitmapResults will have at
+ // most one result for each entry in |desired_sizes|. If a favicon bitmap is
+ // determined to be the best candidate for multiple |desired_sizes| there will
+ // be fewer results.
+ // If |icon_types| has several types, results for only a single type will be
+ // returned in the priority of TOUCH_PRECOMPOSED_ICON, TOUCH_ICON, and
+ // FAVICON.
+ base::CancelableTaskTracker::TaskId GetFavicons(
+ const std::vector<GURL>& icon_urls,
+ int icon_types,
+ const std::vector<int>& desired_sizes,
+ const favicon_base::FaviconResultsCallback& callback,
+ base::CancelableTaskTracker* tracker);
+
+ // Used by the FaviconService to get favicons mapped to |page_url| for
+ // |icon_types| whose edge sizes most closely match |desired_sizes|. If
+ // |desired_sizes| has a '0' entry, the largest favicon bitmap for
+ // |icon_types| is returned. The returned FaviconBitmapResults will have at
+ // most one result for each entry in |desired_sizes|. If a favicon bitmap is
+ // determined to be the best candidate for multiple |desired_sizes| there
+ // will be fewer results. If |icon_types| has several types, results for only
+ // a single type will be returned in the priority of TOUCH_PRECOMPOSED_ICON,
+ // TOUCH_ICON, and FAVICON.
+ base::CancelableTaskTracker::TaskId GetFaviconsForURL(
+ const GURL& page_url,
+ int icon_types,
+ const std::vector<int>& desired_sizes,
+ const favicon_base::FaviconResultsCallback& callback,
+ base::CancelableTaskTracker* tracker);
+
+ // Used by FaviconService to find the first favicon bitmap whose width and
+ // height are greater than that of |minimum_size_in_pixels|. This searches
+ // for icons by IconType. Each element of |icon_types| is a bitmask of
+ // IconTypes indicating the types to search for.
+ // If the largest icon of |icon_types[0]| is not larger than
+ // |minimum_size_in_pixel|, the next icon types of
+ // |icon_types| will be searched and so on.
+ // If no icon is larger than |minimum_size_in_pixel|, the largest one of all
+ // icon types in |icon_types| is returned.
+ // This feature is especially useful when some types of icon is preferred as
+ // long as its size is larger than a specific value.
+ base::CancelableTaskTracker::TaskId GetLargestFaviconForURL(
+ const GURL& page_url,
+ const std::vector<int>& icon_types,
+ int minimum_size_in_pixels,
+ const favicon_base::FaviconRawBitmapCallback& callback,
+ base::CancelableTaskTracker* tracker);
+
+ // Used by the FaviconService to get the favicon bitmap which most closely
+ // matches |desired_size| from the favicon with |favicon_id| from the history
+ // backend. If |desired_size| is 0, the largest favicon bitmap for
+ // |favicon_id| is returned.
+ base::CancelableTaskTracker::TaskId GetFaviconForID(
+ favicon_base::FaviconID favicon_id,
+ int desired_size,
+ const favicon_base::FaviconResultsCallback& callback,
+ base::CancelableTaskTracker* tracker);
+
+ // Used by the FaviconService to replace the favicon mappings to |page_url|
+ // for |icon_types| on the history backend.
+ // Sample |icon_urls|:
+ // { ICON_URL1 -> TOUCH_ICON, known to the database,
+ // ICON_URL2 -> TOUCH_ICON, not known to the database,
+ // ICON_URL3 -> TOUCH_PRECOMPOSED_ICON, known to the database }
+ // The new mappings are computed from |icon_urls| with these rules:
+ // 1) Any urls in |icon_urls| which are not already known to the database are
+ // rejected.
+ // Sample new mappings to |page_url|: { ICON_URL1, ICON_URL3 }
+ // 2) If |icon_types| has multiple types, the mappings are only set for the
+ // largest icon type.
+ // Sample new mappings to |page_url|: { ICON_URL3 }
+ // |icon_types| can only have multiple IconTypes if
+ // |icon_types| == TOUCH_ICON | TOUCH_PRECOMPOSED_ICON.
+ // The favicon bitmaps whose edge sizes most closely match |desired_sizes|
+ // from the favicons which were just mapped to |page_url| are returned. If
+ // |desired_sizes| has a '0' entry, the largest favicon bitmap is returned.
+ base::CancelableTaskTracker::TaskId UpdateFaviconMappingsAndFetch(
+ const GURL& page_url,
+ const std::vector<GURL>& icon_urls,
+ int icon_types,
+ const std::vector<int>& desired_sizes,
+ const favicon_base::FaviconResultsCallback& callback,
+ base::CancelableTaskTracker* tracker);
+
+ // Used by FaviconService to set a favicon for |page_url| and |icon_url| with
+ // |pixel_size|.
+ // Example:
+ // |page_url|: www.google.com
+ // 2 favicons in history for |page_url|:
+ // www.google.com/a.ico 16x16
+ // www.google.com/b.ico 32x32
+ // MergeFavicon(|page_url|, www.google.com/a.ico, ..., ..., 16x16)
+ //
+ // Merging occurs in the following manner:
+ // 1) |page_url| is set to map to only to |icon_url|. In order to not lose
+ // data, favicon bitmaps mapped to |page_url| but not to |icon_url| are
+ // copied to the favicon at |icon_url|.
+ // For the example above, |page_url| will only be mapped to a.ico.
+ // The 32x32 favicon bitmap at b.ico is copied to a.ico
+ // 2) |bitmap_data| is added to the favicon at |icon_url|, overwriting any
+ // favicon bitmaps of |pixel_size|.
+ // For the example above, |bitmap_data| overwrites the 16x16 favicon
+ // bitmap for a.ico.
+ // TODO(pkotwicz): Remove once no longer required by sync.
+ void MergeFavicon(const GURL& page_url,
+ const GURL& icon_url,
+ favicon_base::IconType icon_type,
+ scoped_refptr<base::RefCountedMemory> bitmap_data,
+ const gfx::Size& pixel_size);
+
+ // Used by the FaviconService to replace all of the favicon bitmaps mapped to
+ // |page_url| for |icon_type|.
+ // Use MergeFavicon() if |bitmaps| is incomplete, and favicon bitmaps in the
+ // database should be preserved if possible. For instance, favicon bitmaps
+ // from sync are 1x only. MergeFavicon() is used to avoid deleting the 2x
+ // favicon bitmap if it is present in the history backend.
+ void SetFavicons(const GURL& page_url,
+ favicon_base::IconType icon_type,
+ const GURL& icon_url,
+ const std::vector<SkBitmap>& bitmaps);
+
+ // Used by the FaviconService to mark the favicon for the page as being out
+ // of date.
+ void SetFaviconsOutOfDateForPage(const GURL& page_url);
+
+ // Used by the FaviconService to clone favicons from one page to another,
+ // provided that other page does not already have favicons.
+ void CloneFavicons(const GURL& old_page_url, const GURL& new_page_url);
+
+ // Used by the FaviconService for importing many favicons for many pages at
+ // once. The pages must exist, any favicon sets for unknown pages will be
+ // discarded. Existing favicons will not be overwritten.
+ void SetImportedFavicons(
+ const favicon_base::FaviconUsageDataList& favicon_usage);
+
+ // Sets the in-memory URL database. This is called by the backend once the
+ // database is loaded to make it available.
+ void SetInMemoryBackend(
+ scoped_ptr<history::InMemoryHistoryBackend> mem_backend);
+
+ // Called by our BackendDelegate when there is a problem reading the database.
+ void NotifyProfileError(sql::InitStatus init_status);
+
+ // Call to schedule a given task for running on the history thread with the
+ // specified priority. The task will have ownership taken.
+ void ScheduleTask(SchedulePriority priority, const base::Closure& task);
+
+ // Invokes all callback registered by AddFaviconChangedCallback.
+ void NotifyFaviconChanged(const std::set<GURL>& changed_favicons);
+
+ base::ThreadChecker thread_checker_;
+
+ // The thread used by the history service to run complicated operations.
+ // |thread_| is null once Cleanup() is called.
+ base::Thread* thread_;
+
+ // This class has most of the implementation and runs on the 'thread_'.
+ // You MUST communicate with this class ONLY through the thread_'s
+ // message_loop().
+ //
+ // This pointer will be null once Cleanup() has been called, meaning no
+ // more calls should be made to the history thread.
+ scoped_refptr<history::HistoryBackend> history_backend_;
+
+ // A cache of the user-typed URLs kept in memory that is used by the
+ // autocomplete system. This will be null until the database has been created
+ // on the background thread.
+ // TODO(mrossetti): Consider changing ownership. See http://crbug.com/138321
+ scoped_ptr<history::InMemoryHistoryBackend> in_memory_backend_;
+
+ // The history service will inform its VisitDelegate of URLs recorded and
+ // removed from the history database. This may be null during testing.
+ scoped_ptr<history::VisitDelegate> visit_delegate_;
+
+ // The history client, may be null when testing. The object should otherwise
+ // outlive |HistoryService|.
+ history::HistoryClient* history_client_;
+
+ // Has the backend finished loading? The backend is loaded once Init has
+ // completed.
+ bool backend_loaded_;
+
+ ObserverList<history::HistoryServiceObserver> observers_;
+ base::CallbackList<void(const std::set<GURL>&)>
+ favicon_changed_callback_list_;
+
+ history::DeleteDirectiveHandler delete_directive_handler_;
+
+ // All vended weak pointers are invalidated in Cleanup().
+ base::WeakPtrFactory<HistoryService> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(HistoryService);
+};
+
+#endif // COMPONENTS_HISTORY_CORE_BROWSER_HISTORY_SERVICE_H_
diff --git a/components/history/core/browser/in_memory_history_backend.cc b/components/history/core/browser/in_memory_history_backend.cc
new file mode 100644
index 0000000..ab540e6
--- /dev/null
+++ b/components/history/core/browser/in_memory_history_backend.cc
@@ -0,0 +1,111 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/history/core/browser/in_memory_history_backend.h"
+
+#include <set>
+#include <vector>
+
+#include "base/command_line.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "components/history/core/browser/history_service.h"
+#include "components/history/core/browser/in_memory_database.h"
+#include "components/history/core/browser/url_database.h"
+
+namespace history {
+
+InMemoryHistoryBackend::InMemoryHistoryBackend()
+ : history_service_observer_(this) {
+}
+
+InMemoryHistoryBackend::~InMemoryHistoryBackend() {
+}
+
+bool InMemoryHistoryBackend::Init(const base::FilePath& history_filename) {
+ db_.reset(new InMemoryDatabase);
+ return db_->InitFromDisk(history_filename);
+}
+
+void InMemoryHistoryBackend::AttachToHistoryService(
+ HistoryService* history_service) {
+ DCHECK(db_);
+ DCHECK(history_service);
+ history_service_observer_.Add(history_service);
+}
+
+void InMemoryHistoryBackend::DeleteAllSearchTermsForKeyword(
+ KeywordID keyword_id) {
+ // For simplicity, this will not remove the corresponding URLRows, but
+ // this is okay, as the main database does not do so either.
+ db_->DeleteAllSearchTermsForKeyword(keyword_id);
+}
+
+void InMemoryHistoryBackend::OnURLVisited(HistoryService* history_service,
+ ui::PageTransition transition,
+ const URLRow& row,
+ const RedirectList& redirects,
+ base::Time visit_time) {
+ OnURLVisitedOrModified(row);
+}
+
+void InMemoryHistoryBackend::OnURLsModified(HistoryService* history_service,
+ const URLRows& changed_urls) {
+ for (const auto& row : changed_urls) {
+ OnURLVisitedOrModified(row);
+ }
+}
+
+void InMemoryHistoryBackend::OnURLsDeleted(HistoryService* history_service,
+ bool all_history,
+ bool expired,
+ const URLRows& deleted_rows,
+ const std::set<GURL>& favicon_urls) {
+ DCHECK(db_);
+
+ if (all_history) {
+ // When all history is deleted, the individual URLs won't be listed. Just
+ // create a new database to quickly clear everything out.
+ db_.reset(new InMemoryDatabase);
+ if (!db_->InitFromScratch())
+ db_.reset();
+ return;
+ }
+
+ // Delete all matching URLs in our database.
+ for (const auto& row : deleted_rows) {
+ // This will also delete the corresponding keyword search term.
+ // Ignore errors, as we typically only cache a subset of URLRows.
+ db_->DeleteURLRow(row.id());
+ }
+}
+
+void InMemoryHistoryBackend::OnKeywordSearchTermUpdated(
+ HistoryService* history_service,
+ const URLRow& row,
+ KeywordID keyword_id,
+ const base::string16& term) {
+ DCHECK(row.id());
+ db_->InsertOrUpdateURLRowByID(row);
+ db_->SetKeywordSearchTermsForURL(row.id(), keyword_id, term);
+}
+
+void InMemoryHistoryBackend::OnKeywordSearchTermDeleted(
+ HistoryService* history_service,
+ URLID url_id) {
+ // For simplicity, this will not remove the corresponding URLRow, but this is
+ // okay, as the main database does not do so either.
+ db_->DeleteKeywordSearchTermForURL(url_id);
+}
+
+void InMemoryHistoryBackend::OnURLVisitedOrModified(const URLRow& url_row) {
+ DCHECK(db_);
+ DCHECK(url_row.id());
+ if (url_row.typed_count() || db_->GetKeywordSearchTermRow(url_row.id(), NULL))
+ db_->InsertOrUpdateURLRowByID(url_row);
+ else
+ db_->DeleteURLRow(url_row.id());
+}
+
+} // namespace history
diff --git a/components/history/core/browser/in_memory_history_backend.h b/components/history/core/browser/in_memory_history_backend.h
new file mode 100644
index 0000000..51ad318
--- /dev/null
+++ b/components/history/core/browser/in_memory_history_backend.h
@@ -0,0 +1,105 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// The InMemoryHistoryBackend is a wrapper around the in-memory URL database.
+// It maintains an in-memory cache of a subset of history that is required for
+// low-latency operations, such as in-line autocomplete.
+//
+// The in-memory cache provides the following guarantees:
+// (1.) It will always contain URLRows that either have a |typed_count| > 0; or
+// that have a corresponding search term, in which case information about
+// the search term is also stored.
+// (2.) It will be an actual subset, i.e., it will contain verbatim data, and
+// will never contain more data that can be found in the main database.
+//
+// The InMemoryHistoryBackend is created on the history thread and passed to the
+// main thread where operations can be completed synchronously. It listens for
+// notifications from the "regular" history backend and keeps itself in sync.
+
+#ifndef COMPONENTS_HISTORY_CORE_BROWSER_IN_MEMORY_HISTORY_BACKEND_H_
+#define COMPONENTS_HISTORY_CORE_BROWSER_IN_MEMORY_HISTORY_BACKEND_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/scoped_observer.h"
+#include "components/history/core/browser/history_service_observer.h"
+#include "components/history/core/browser/keyword_id.h"
+
+class HistoryService;
+
+namespace base {
+class FilePath;
+}
+
+namespace history {
+
+class InMemoryDatabase;
+class URLDatabase;
+class URLRow;
+
+class InMemoryHistoryBackend : public HistoryServiceObserver {
+ public:
+ InMemoryHistoryBackend();
+ ~InMemoryHistoryBackend() override;
+
+ // Initializes the backend from the history database pointed to by the
+ // full path in |history_filename|.
+ bool Init(const base::FilePath& history_filename);
+
+ // Does initialization work when this object is attached to the history
+ // system on the main thread. The argument is the profile with which the
+ // attached history service is under.
+ void AttachToHistoryService(HistoryService* history_service);
+
+ // Deletes all search terms for the specified keyword.
+ void DeleteAllSearchTermsForKeyword(KeywordID keyword_id);
+
+ // Returns the underlying database associated with this backend. The current
+ // autocomplete code was written fro this, but it should probably be removed
+ // so that it can deal directly with this object, rather than the DB.
+ InMemoryDatabase* db() const { return db_.get(); }
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest, DeleteAll);
+ FRIEND_TEST_ALL_PREFIXES(InMemoryHistoryBackendTest, OnURLsDeletedEnMasse);
+ friend class HistoryBackendTestBase;
+ friend class InMemoryHistoryBackendTest;
+
+ // HistoryServiceObserver:
+ void OnURLVisited(HistoryService* history_service,
+ ui::PageTransition transition,
+ const URLRow& row,
+ const RedirectList& redirects,
+ base::Time visit_time) override;
+ void OnURLsModified(HistoryService* history_service,
+ const URLRows& changed_urls) override;
+ void OnURLsDeleted(HistoryService* history_service,
+ bool all_history,
+ bool expired,
+ const URLRows& deleted_rows,
+ const std::set<GURL>& favicon_urls) override;
+ void OnKeywordSearchTermUpdated(HistoryService* history_service,
+ const URLRow& row,
+ KeywordID keyword_id,
+ const base::string16& term) override;
+ void OnKeywordSearchTermDeleted(HistoryService* history_service,
+ URLID url_id) override;
+
+ // Handler for HISTORY_URL_VISITED and HISTORY_URLS_MODIFIED.
+ void OnURLVisitedOrModified(const URLRow& url_row);
+
+ scoped_ptr<InMemoryDatabase> db_;
+
+ ScopedObserver<HistoryService, HistoryServiceObserver>
+ history_service_observer_;
+
+ DISALLOW_COPY_AND_ASSIGN(InMemoryHistoryBackend);
+};
+
+} // namespace history
+
+#endif // COMPONENTS_HISTORY_CORE_BROWSER_IN_MEMORY_HISTORY_BACKEND_H_
diff --git a/components/history/core/browser/typed_url_syncable_service.cc b/components/history/core/browser/typed_url_syncable_service.cc
new file mode 100644
index 0000000..4ce8903
--- /dev/null
+++ b/components/history/core/browser/typed_url_syncable_service.cc
@@ -0,0 +1,439 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/history/core/browser/typed_url_syncable_service.h"
+
+#include "base/auto_reset.h"
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/utf_string_conversions.h"
+#include "components/history/core/browser/history_backend.h"
+#include "net/base/net_util.h"
+#include "sync/protocol/sync.pb.h"
+#include "sync/protocol/typed_url_specifics.pb.h"
+
+namespace {
+
+// The server backend can't handle arbitrarily large node sizes, so to keep
+// the size under control we limit the visit array.
+static const int kMaxTypedUrlVisits = 100;
+
+// There's no limit on how many visits the history DB could have for a given
+// typed URL, so we limit how many we fetch from the DB to avoid crashes due to
+// running out of memory (http://crbug.com/89793). This value is different
+// from kMaxTypedUrlVisits, as some of the visits fetched from the DB may be
+// RELOAD visits, which will be stripped.
+static const int kMaxVisitsToFetch = 1000;
+
+// This is the threshold at which we start throttling sync updates for typed
+// URLs - any URLs with a typed_count >= this threshold will be throttled.
+static const int kTypedUrlVisitThrottleThreshold = 10;
+
+// This is the multiple we use when throttling sync updates. If the multiple is
+// N, we sync up every Nth update (i.e. when typed_count % N == 0).
+static const int kTypedUrlVisitThrottleMultiple = 10;
+
+} // namespace
+
+namespace history {
+
+const char kTypedUrlTag[] = "google_chrome_typed_urls";
+
+static bool CheckVisitOrdering(const VisitVector& visits) {
+ int64 previous_visit_time = 0;
+ for (VisitVector::const_iterator visit = visits.begin();
+ visit != visits.end(); ++visit) {
+ if (visit != visits.begin()) {
+ // We allow duplicate visits here - they shouldn't really be allowed, but
+ // they still seem to show up sometimes and we haven't figured out the
+ // source, so we just log an error instead of failing an assertion.
+ // (http://crbug.com/91473).
+ if (previous_visit_time == visit->visit_time.ToInternalValue())
+ DVLOG(1) << "Duplicate visit time encountered";
+ else if (previous_visit_time > visit->visit_time.ToInternalValue())
+ return false;
+ }
+
+ previous_visit_time = visit->visit_time.ToInternalValue();
+ }
+ return true;
+}
+
+TypedUrlSyncableService::TypedUrlSyncableService(
+ HistoryBackend* history_backend)
+ : history_backend_(history_backend),
+ processing_syncer_changes_(false),
+ expected_loop_(base::MessageLoop::current()) {
+ DCHECK(history_backend_);
+ DCHECK(expected_loop_ == base::MessageLoop::current());
+}
+
+TypedUrlSyncableService::~TypedUrlSyncableService() {
+ DCHECK(expected_loop_ == base::MessageLoop::current());
+}
+
+syncer::SyncMergeResult TypedUrlSyncableService::MergeDataAndStartSyncing(
+ syncer::ModelType type,
+ const syncer::SyncDataList& initial_sync_data,
+ scoped_ptr<syncer::SyncChangeProcessor> sync_processor,
+ scoped_ptr<syncer::SyncErrorFactory> error_handler) {
+ DCHECK(expected_loop_ == base::MessageLoop::current());
+ DCHECK(!sync_processor_.get());
+ DCHECK(sync_processor.get());
+ DCHECK(error_handler.get());
+ DCHECK_EQ(type, syncer::TYPED_URLS);
+
+ syncer::SyncMergeResult merge_result(type);
+ sync_processor_ = sync_processor.Pass();
+ sync_error_handler_ = error_handler.Pass();
+
+ // TODO(mgist): Add implementation
+
+ return merge_result;
+}
+
+void TypedUrlSyncableService::StopSyncing(syncer::ModelType type) {
+ DCHECK(expected_loop_ == base::MessageLoop::current());
+ DCHECK_EQ(type, syncer::TYPED_URLS);
+
+ sync_processor_.reset();
+ sync_error_handler_.reset();
+}
+
+syncer::SyncDataList TypedUrlSyncableService::GetAllSyncData(
+ syncer::ModelType type) const {
+ DCHECK(expected_loop_ == base::MessageLoop::current());
+ syncer::SyncDataList list;
+
+ // TODO(mgist): Add implementation
+
+ return list;
+}
+
+syncer::SyncError TypedUrlSyncableService::ProcessSyncChanges(
+ const tracked_objects::Location& from_here,
+ const syncer::SyncChangeList& change_list) {
+ DCHECK(expected_loop_ == base::MessageLoop::current());
+
+ // TODO(mgist): Add implementation
+
+ return syncer::SyncError(FROM_HERE, syncer::SyncError::DATATYPE_ERROR,
+ "Typed url syncable service is not implemented.",
+ syncer::TYPED_URLS);
+}
+
+void TypedUrlSyncableService::OnUrlsModified(URLRows* changed_urls) {
+ DCHECK(expected_loop_ == base::MessageLoop::current());
+ DCHECK(changed_urls);
+
+ if (processing_syncer_changes_)
+ return; // These are changes originating from us, ignore.
+ if (!sync_processor_.get())
+ return; // Sync processor not yet initialized, don't sync.
+
+ // Create SyncChangeList.
+ syncer::SyncChangeList changes;
+
+ for (URLRows::iterator url = changed_urls->begin();
+ url != changed_urls->end(); ++url) {
+ // Only care if the modified URL is typed.
+ if (url->typed_count() > 0) {
+ // If there were any errors updating the sync node, just ignore them and
+ // continue on to process the next URL.
+ CreateOrUpdateSyncNode(*url, &changes);
+ }
+ }
+
+ // Send SyncChangeList to server if there are any changes.
+ if (changes.size() > 0)
+ sync_processor_->ProcessSyncChanges(FROM_HERE, changes);
+}
+
+void TypedUrlSyncableService::OnUrlVisited(ui::PageTransition transition,
+ URLRow* row) {
+ DCHECK(expected_loop_ == base::MessageLoop::current());
+ DCHECK(row);
+
+ if (processing_syncer_changes_)
+ return; // These are changes originating from us, ignore.
+ if (!sync_processor_.get())
+ return; // Sync processor not yet initialized, don't sync.
+ if (!ShouldSyncVisit(transition, row))
+ return;
+
+ // Create SyncChangeList.
+ syncer::SyncChangeList changes;
+
+ CreateOrUpdateSyncNode(*row, &changes);
+
+ // Send SyncChangeList to server if there are any changes.
+ if (changes.size() > 0)
+ sync_processor_->ProcessSyncChanges(FROM_HERE, changes);
+}
+
+void TypedUrlSyncableService::OnUrlsDeleted(bool all_history,
+ bool expired,
+ URLRows* rows) {
+ DCHECK(expected_loop_ == base::MessageLoop::current());
+
+ if (processing_syncer_changes_)
+ return; // These are changes originating from us, ignore.
+ if (!sync_processor_.get())
+ return; // Sync processor not yet initialized, don't sync.
+
+ // Ignore URLs expired due to old age (we don't want to sync them as deletions
+ // to avoid extra traffic up to the server, and also to make sure that a
+ // client with a bad clock setting won't go on an expiration rampage and
+ // delete all history from every client). The server will gracefully age out
+ // the sync DB entries when they've been idle for long enough.
+ if (expired)
+ return;
+
+ // Create SyncChangeList.
+ syncer::SyncChangeList changes;
+
+ if (all_history) {
+ // Delete all synced typed urls.
+ for (std::set<GURL>::const_iterator url = synced_typed_urls_.begin();
+ url != synced_typed_urls_.end(); ++url) {
+ VisitVector visits;
+ URLRow row(*url);
+ AddTypedUrlToChangeList(syncer::SyncChange::ACTION_DELETE, row, visits,
+ url->spec(), &changes);
+ }
+ // Clear cache of server state.
+ synced_typed_urls_.clear();
+ } else {
+ DCHECK(rows);
+ // Delete rows.
+ for (URLRows::const_iterator row = rows->begin(); row != rows->end();
+ ++row) {
+ // Add specifics to change list for all synced urls that were deleted.
+ if (synced_typed_urls_.find(row->url()) != synced_typed_urls_.end()) {
+ VisitVector visits;
+ AddTypedUrlToChangeList(syncer::SyncChange::ACTION_DELETE, *row, visits,
+ row->url().spec(), &changes);
+ // Delete typed url from cache.
+ synced_typed_urls_.erase(row->url());
+ }
+ }
+ }
+
+ // Send SyncChangeList to server if there are any changes.
+ if (changes.size() > 0)
+ sync_processor_->ProcessSyncChanges(FROM_HERE, changes);
+}
+
+bool TypedUrlSyncableService::ShouldIgnoreUrl(const GURL& url) {
+ // Ignore empty URLs. Not sure how this can happen (maybe import from other
+ // busted browsers, or misuse of the history API, or just plain bugs) but we
+ // can't deal with them.
+ if (url.spec().empty())
+ return true;
+
+ // Ignore local file URLs.
+ if (url.SchemeIsFile())
+ return true;
+
+ // Ignore localhost URLs.
+ if (net::IsLocalhost(url.host()))
+ return true;
+
+ return false;
+}
+
+bool TypedUrlSyncableService::ShouldSyncVisit(
+ ui::PageTransition page_transition,
+ URLRow* row) {
+ if (!row)
+ return false;
+ int typed_count = row->typed_count();
+ ui::PageTransition transition = ui::PageTransitionFromInt(
+ page_transition & ui::PAGE_TRANSITION_CORE_MASK);
+
+ // Just use an ad-hoc criteria to determine whether to ignore this
+ // notification. For most users, the distribution of visits is roughly a bell
+ // curve with a long tail - there are lots of URLs with < 5 visits so we want
+ // to make sure we sync up every visit to ensure the proper ordering of
+ // suggestions. But there are relatively few URLs with > 10 visits, and those
+ // tend to be more broadly distributed such that there's no need to sync up
+ // every visit to preserve their relative ordering.
+ return (transition == ui::PAGE_TRANSITION_TYPED && typed_count > 0 &&
+ (typed_count < kTypedUrlVisitThrottleThreshold ||
+ (typed_count % kTypedUrlVisitThrottleMultiple) == 0));
+}
+
+bool TypedUrlSyncableService::CreateOrUpdateSyncNode(
+ URLRow url,
+ syncer::SyncChangeList* changes) {
+ DCHECK_GT(url.typed_count(), 0);
+
+ if (ShouldIgnoreUrl(url.url()))
+ return true;
+
+ // Get the visits for this node.
+ VisitVector visit_vector;
+ if (!FixupURLAndGetVisits(&url, &visit_vector)) {
+ DLOG(ERROR) << "Could not load visits for url: " << url.url();
+ return false;
+ }
+ DCHECK(!visit_vector.empty());
+
+ std::string title = url.url().spec();
+ syncer::SyncChange::SyncChangeType change_type;
+
+ // If server already has URL, then send a sync update, else add it.
+ change_type = (synced_typed_urls_.find(url.url()) != synced_typed_urls_.end())
+ ? syncer::SyncChange::ACTION_UPDATE
+ : syncer::SyncChange::ACTION_ADD;
+
+ // Ensure cache of server state is up to date.
+ synced_typed_urls_.insert(url.url());
+
+ AddTypedUrlToChangeList(change_type, url, visit_vector, title, changes);
+
+ return true;
+}
+
+void TypedUrlSyncableService::AddTypedUrlToChangeList(
+ syncer::SyncChange::SyncChangeType change_type,
+ const URLRow& row,
+ const VisitVector& visits,
+ std::string title,
+ syncer::SyncChangeList* change_list) {
+ sync_pb::EntitySpecifics entity_specifics;
+ sync_pb::TypedUrlSpecifics* typed_url = entity_specifics.mutable_typed_url();
+
+ if (change_type == syncer::SyncChange::ACTION_DELETE) {
+ typed_url->set_url(row.url().spec());
+ } else {
+ WriteToTypedUrlSpecifics(row, visits, typed_url);
+ }
+
+ change_list->push_back(syncer::SyncChange(
+ FROM_HERE, change_type, syncer::SyncData::CreateLocalData(
+ kTypedUrlTag, title, entity_specifics)));
+}
+
+void TypedUrlSyncableService::WriteToTypedUrlSpecifics(
+ const URLRow& url,
+ const VisitVector& visits,
+ sync_pb::TypedUrlSpecifics* typed_url) {
+ DCHECK(!url.last_visit().is_null());
+ DCHECK(!visits.empty());
+ DCHECK_EQ(url.last_visit().ToInternalValue(),
+ visits.back().visit_time.ToInternalValue());
+
+ typed_url->set_url(url.url().spec());
+ typed_url->set_title(base::UTF16ToUTF8(url.title()));
+ typed_url->set_hidden(url.hidden());
+
+ DCHECK(CheckVisitOrdering(visits));
+
+ bool only_typed = false;
+ int skip_count = 0;
+
+ if (visits.size() > static_cast<size_t>(kMaxTypedUrlVisits)) {
+ int typed_count = 0;
+ int total = 0;
+ // Walk the passed-in visit vector and count the # of typed visits.
+ for (VisitVector::const_iterator visit = visits.begin();
+ visit != visits.end(); ++visit) {
+ ui::PageTransition transition = ui::PageTransitionFromInt(
+ visit->transition & ui::PAGE_TRANSITION_CORE_MASK);
+ // We ignore reload visits.
+ if (transition == ui::PAGE_TRANSITION_RELOAD)
+ continue;
+ ++total;
+ if (transition == ui::PAGE_TRANSITION_TYPED)
+ ++typed_count;
+ }
+ // We should have at least one typed visit. This can sometimes happen if
+ // the history DB has an inaccurate count for some reason (there's been
+ // bugs in the history code in the past which has left users in the wild
+ // with incorrect counts - http://crbug.com/84258).
+ DCHECK(typed_count > 0);
+
+ if (typed_count > kMaxTypedUrlVisits) {
+ only_typed = true;
+ skip_count = typed_count - kMaxTypedUrlVisits;
+ } else if (total > kMaxTypedUrlVisits) {
+ skip_count = total - kMaxTypedUrlVisits;
+ }
+ }
+
+ for (VisitVector::const_iterator visit = visits.begin();
+ visit != visits.end(); ++visit) {
+ ui::PageTransition transition =
+ ui::PageTransitionStripQualifier(visit->transition);
+ // Skip reload visits.
+ if (transition == ui::PAGE_TRANSITION_RELOAD)
+ continue;
+
+ // If we only have room for typed visits, then only add typed visits.
+ if (only_typed && transition != ui::PAGE_TRANSITION_TYPED)
+ continue;
+
+ if (skip_count > 0) {
+ // We have too many entries to fit, so we need to skip the oldest ones.
+ // Only skip typed URLs if there are too many typed URLs to fit.
+ if (only_typed || transition != ui::PAGE_TRANSITION_TYPED) {
+ --skip_count;
+ continue;
+ }
+ }
+ typed_url->add_visits(visit->visit_time.ToInternalValue());
+ typed_url->add_visit_transitions(visit->transition);
+ }
+ DCHECK_EQ(skip_count, 0);
+
+ if (typed_url->visits_size() == 0) {
+ // If we get here, it's because we don't actually have any TYPED visits
+ // even though the visit's typed_count > 0 (corrupted typed_count). So
+ // let's go ahead and add a RELOAD visit at the most recent visit since
+ // it's not legal to have an empty visit array (yet another workaround
+ // for http://crbug.com/84258).
+ typed_url->add_visits(url.last_visit().ToInternalValue());
+ typed_url->add_visit_transitions(ui::PAGE_TRANSITION_RELOAD);
+ }
+ CHECK_GT(typed_url->visits_size(), 0);
+ CHECK_LE(typed_url->visits_size(), kMaxTypedUrlVisits);
+ CHECK_EQ(typed_url->visits_size(), typed_url->visit_transitions_size());
+}
+
+bool TypedUrlSyncableService::FixupURLAndGetVisits(URLRow* url,
+ VisitVector* visits) {
+ ++num_db_accesses_;
+ CHECK(history_backend_);
+ if (!history_backend_->GetMostRecentVisitsForURL(url->id(), kMaxVisitsToFetch,
+ visits)) {
+ ++num_db_errors_;
+ return false;
+ }
+
+ // Sometimes (due to a bug elsewhere in the history or sync code, or due to
+ // a crash between adding a URL to the history database and updating the
+ // visit DB) the visit vector for a URL can be empty. If this happens, just
+ // create a new visit whose timestamp is the same as the last_visit time.
+ // This is a workaround for http://crbug.com/84258.
+ if (visits->empty()) {
+ DVLOG(1) << "Found empty visits for URL: " << url->url();
+ VisitRow visit(url->id(), url->last_visit(), 0, ui::PAGE_TRANSITION_TYPED,
+ 0);
+ visits->push_back(visit);
+ }
+
+ // GetMostRecentVisitsForURL() returns the data in the opposite order that
+ // we need it, so reverse it.
+ std::reverse(visits->begin(), visits->end());
+
+ // Sometimes, the last_visit field in the URL doesn't match the timestamp of
+ // the last visit in our visit array (they come from different tables, so
+ // crashes/bugs can cause them to mismatch), so just set it here.
+ url->set_last_visit(visits->back().visit_time);
+ DCHECK(CheckVisitOrdering(*visits));
+ return true;
+}
+
+} // namespace history
diff --git a/components/history/core/browser/typed_url_syncable_service.h b/components/history/core/browser/typed_url_syncable_service.h
new file mode 100644
index 0000000..15c865e
--- /dev/null
+++ b/components/history/core/browser/typed_url_syncable_service.h
@@ -0,0 +1,161 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_HISTORY_CORE_BROWSER_TYPED_URL_SYNCABLE_SERVICE_H_
+#define COMPONENTS_HISTORY_CORE_BROWSER_TYPED_URL_SYNCABLE_SERVICE_H_
+
+#include <set>
+#include <vector>
+
+#include "components/history/core/browser/history_types.h"
+#include "sync/api/sync_change.h"
+#include "sync/api/sync_data.h"
+#include "sync/api/sync_error.h"
+#include "sync/api/sync_error_factory.h"
+#include "sync/api/syncable_service.h"
+#include "ui/base/page_transition_types.h"
+
+class GURL;
+class TypedUrlSyncableServiceTest;
+
+namespace base {
+class MessageLoop;
+};
+
+namespace sync_pb {
+class TypedUrlSpecifics;
+};
+
+namespace history {
+
+class HistoryBackend;
+class URLRow;
+
+extern const char kTypedUrlTag[];
+
+class TypedUrlSyncableService : public syncer::SyncableService {
+ public:
+ explicit TypedUrlSyncableService(HistoryBackend* history_backend);
+ ~TypedUrlSyncableService() override;
+
+ static syncer::ModelType model_type() { return syncer::TYPED_URLS; }
+
+ // syncer::SyncableService implementation.
+ syncer::SyncMergeResult MergeDataAndStartSyncing(
+ syncer::ModelType type,
+ const syncer::SyncDataList& initial_sync_data,
+ scoped_ptr<syncer::SyncChangeProcessor> sync_processor,
+ scoped_ptr<syncer::SyncErrorFactory> error_handler) override;
+ void StopSyncing(syncer::ModelType type) override;
+ syncer::SyncDataList GetAllSyncData(syncer::ModelType type) const override;
+ syncer::SyncError ProcessSyncChanges(
+ const tracked_objects::Location& from_here,
+ const syncer::SyncChangeList& change_list) override;
+
+ // Called directly by HistoryBackend when local url data changes.
+ void OnUrlsModified(URLRows* changed_urls);
+ void OnUrlVisited(ui::PageTransition transition, URLRow* row);
+ void OnUrlsDeleted(bool all_history, bool expired, URLRows* rows);
+
+ protected:
+ void GetSyncedUrls(std::set<GURL>* urls) {
+ urls->insert(synced_typed_urls_.begin(), synced_typed_urls_.end());
+ }
+
+ private:
+ typedef std::vector<std::pair<URLID, URLRow>> TypedUrlUpdateVector;
+ typedef std::vector<std::pair<GURL, std::vector<VisitInfo>>>
+ TypedUrlVisitVector;
+
+ // This is a helper map used only in Merge/Process* functions. The lifetime
+ // of the iterator is longer than the map object.
+ typedef std::map<GURL,
+ std::pair<syncer::SyncChange::SyncChangeType,
+ URLRows::iterator>> TypedUrlMap;
+ // This is a helper map used to associate visit vectors from the history db
+ // to the typed urls in the above map.
+ typedef std::map<GURL, VisitVector> UrlVisitVectorMap;
+
+ // Helper function that determines if we should ignore a URL for the purposes
+ // of sync, because it contains invalid data.
+ bool ShouldIgnoreUrl(const GURL& url);
+
+ // Returns true if the caller should sync as a result of the passed visit
+ // notification. We use this to throttle the number of sync changes we send
+ // to the server so we don't hit the server for every
+ // single typed URL visit.
+ bool ShouldSyncVisit(ui::PageTransition transition, URLRow* row);
+
+ // Utility routine that either updates an existing sync node or creates a
+ // new one for the passed |typed_url| if one does not already exist. Returns
+ // false and sets an unrecoverable error if the operation failed.
+ bool CreateOrUpdateSyncNode(URLRow typed_url,
+ syncer::SyncChangeList* changes);
+
+ void AddTypedUrlToChangeList(syncer::SyncChange::SyncChangeType change_type,
+ const URLRow& row,
+ const VisitVector& visits,
+ std::string title,
+ syncer::SyncChangeList* change_list);
+
+ // Converts the passed URL information to a TypedUrlSpecifics structure for
+ // writing to the sync DB.
+ static void WriteToTypedUrlSpecifics(const URLRow& url,
+ const VisitVector& visits,
+ sync_pb::TypedUrlSpecifics* specifics);
+
+ // Fetches visits from the history DB corresponding to the passed URL. This
+ // function compensates for the fact that the history DB has rather poor data
+ // integrity (duplicate visits, visit timestamps that don't match the
+ // last_visit timestamp, huge data sets that exhaust memory when fetched,
+ // etc) by modifying the passed |url| object and |visits| vector.
+ // Returns false if we could not fetch the visits for the passed URL, and
+ // tracks DB error statistics internally for reporting via UMA.
+ virtual bool FixupURLAndGetVisits(URLRow* url, VisitVector* visits);
+
+ // TODO(sync): Consider using "delete all" sync logic instead of in-memory
+ // cache of typed urls. See http://crbug.com/231689.
+ std::set<GURL> synced_typed_urls_;
+
+ HistoryBackend* const history_backend_;
+
+ // Whether we're currently processing changes from the syncer. While this is
+ // true, we ignore any local url changes, since we triggered them.
+ bool processing_syncer_changes_;
+
+ // We receive ownership of |sync_processor_| and |error_handler_| in
+ // MergeDataAndStartSyncing() and destroy them in StopSyncing().
+ scoped_ptr<syncer::SyncChangeProcessor> sync_processor_;
+ scoped_ptr<syncer::SyncErrorFactory> sync_error_handler_;
+
+ // Statistics for the purposes of tracking the percentage of DB accesses that
+ // fail for each client via UMA.
+ int num_db_accesses_;
+ int num_db_errors_;
+
+ base::MessageLoop* expected_loop_;
+
+ FRIEND_TEST_ALL_PREFIXES(TypedUrlSyncableServiceTest,
+ AddLocalTypedUrlAndSync);
+ FRIEND_TEST_ALL_PREFIXES(TypedUrlSyncableServiceTest,
+ UpdateLocalTypedUrlAndSync);
+ FRIEND_TEST_ALL_PREFIXES(TypedUrlSyncableServiceTest,
+ LinkVisitLocalTypedUrlAndSync);
+ FRIEND_TEST_ALL_PREFIXES(TypedUrlSyncableServiceTest,
+ TypedVisitLocalTypedUrlAndSync);
+ FRIEND_TEST_ALL_PREFIXES(TypedUrlSyncableServiceTest,
+ DeleteLocalTypedUrlAndSync);
+ FRIEND_TEST_ALL_PREFIXES(TypedUrlSyncableServiceTest,
+ DeleteAllLocalTypedUrlAndSync);
+ FRIEND_TEST_ALL_PREFIXES(TypedUrlSyncableServiceTest,
+ MaxVisitLocalTypedUrlAndSync);
+ FRIEND_TEST_ALL_PREFIXES(TypedUrlSyncableServiceTest,
+ ThrottleVisitLocalTypedUrlSync);
+
+ DISALLOW_COPY_AND_ASSIGN(TypedUrlSyncableService);
+};
+
+} // namespace history
+
+#endif // COMPONENTS_HISTORY_CORE_BROWSER_TYPED_URL_SYNCABLE_SERVICE_H_
diff --git a/components/search_engines/template_url_service_unittest.cc b/components/search_engines/template_url_service_unittest.cc
index 368496c..0e771ab2 100644
--- a/components/search_engines/template_url_service_unittest.cc
+++ b/components/search_engines/template_url_service_unittest.cc
@@ -18,10 +18,10 @@
#include "base/test/simple_test_clock.h"
#include "base/threading/thread.h"
#include "base/time/time.h"
-#include "chrome/browser/history/history_service.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/search_engines/template_url_service_test_util.h"
#include "chrome/test/base/testing_profile.h"
+#include "components/history/core/browser/history_service.h"
#include "components/search_engines/keyword_web_data_service.h"
#include "components/search_engines/search_host_to_urls_map.h"
#include "components/search_engines/search_terms_data.h"