diff options
author | sdefresne <sdefresne@chromium.org> | 2015-03-12 11:49:12 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-03-12 18:50:31 +0000 |
commit | 506dd521e1f6934a997e051481da3fda2098a354 (patch) | |
tree | 5741833876ae8f92957ad0e9717f2d5fba991049 /components | |
parent | c4622464ba3fdb5d16cf99125ea71bc6fb83d30e (diff) | |
download | chromium_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')
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" |