summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/history/delete_directive_handler.cc340
-rw-r--r--chrome/browser/history/delete_directive_handler.h76
-rw-r--r--chrome/browser/history/history_backend.cc64
-rw-r--r--chrome/browser/history/history_backend.h10
-rw-r--r--chrome/browser/history/history_backend_unittest.cc47
-rw-r--r--chrome/browser/history/history_service.cc160
-rw-r--r--chrome/browser/history/history_service.h22
-rw-r--r--chrome/browser/history/history_types.h5
-rw-r--r--chrome/browser/history/history_unittest.cc278
-rw-r--r--chrome/browser/history/visit_database.cc26
-rw-r--r--chrome/browser/history/visit_database_unittest.cc9
-rw-r--r--chrome/chrome_browser.gypi2
-rw-r--r--sync/api/sync_data.h2
-rw-r--r--sync/api/syncable_service.h2
14 files changed, 711 insertions, 332 deletions
diff --git a/chrome/browser/history/delete_directive_handler.cc b/chrome/browser/history/delete_directive_handler.cc
new file mode 100644
index 0000000..783186a
--- /dev/null
+++ b/chrome/browser/history/delete_directive_handler.cc
@@ -0,0 +1,340 @@
+// 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 "chrome/browser/history/delete_directive_handler.h"
+
+#include "base/json/json_writer.h"
+#include "base/rand_util.h"
+#include "base/time.h"
+#include "base/values.h"
+#include "chrome/browser/history/history_backend.h"
+#include "chrome/browser/history/history_db_task.h"
+#include "chrome/browser/history/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 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();
+}
+
+base::Time UnixUsecToTime(int64 usec) {
+ return base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(usec);
+}
+
+// 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)));
+ }
+}
+
+} // 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.
+ virtual bool RunOnDBThread(history::HistoryBackend* backend,
+ history::HistoryDatabase* db) OVERRIDE;
+ virtual void DoneRunOnMainThread() OVERRIDE;
+
+ private:
+ ~DeleteDirectiveTask() {}
+
+ // 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_(ALLOW_THIS_IN_INITIALIZER_LIST(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(
+ new DeleteDirectiveTask(weak_ptr_factory_.GetWeakPtr(),
+ initial_sync_data,
+ DROP_AFTER_PROCESSING),
+ &internal_consumer_);
+ }
+}
+
+void DeleteDirectiveHandler::Stop() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ sync_processor_.reset();
+}
+
+syncer::SyncError DeleteDirectiveHandler::ProcessLocalDeleteDirective(
+ const sync_pb::HistoryDeleteDirectiveSpecifics& delete_directive) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (!sync_processor_.get()) {
+ return syncer::SyncError(
+ FROM_HERE,
+ "Cannot send local delete directive to sync",
+ syncer::HISTORY_DELETE_DIRECTIVES);
+ }
+ // Generate a random sync tag since history delete directives don't
+ // have a 'built-in' ID. 8 bytes should suffice.
+ std::string sync_tag = base::RandBytesAsString(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_.get()) {
+ return syncer::SyncError(
+ FROM_HERE, "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(
+ new DeleteDirectiveTask(weak_ptr_factory_.GetWeakPtr(),
+ delete_directives, KEEP_AFTER_PROCESSING),
+ &internal_consumer_);
+ }
+ 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/chrome/browser/history/delete_directive_handler.h b/chrome/browser/history/delete_directive_handler.h
new file mode 100644
index 0000000..51412ab
--- /dev/null
+++ b/chrome/browser/history/delete_directive_handler.h
@@ -0,0 +1,76 @@
+// 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 CHROME_BROWSER_HISTORY_DELETE_DIRECTIVE_HANDLER_H_
+#define CHROME_BROWSER_HISTORY_DELETE_DIRECTIVE_HANDLER_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "chrome/browser/common/cancelable_request.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();
+
+ // 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);
+
+ CancelableRequestConsumer internal_consumer_;
+ scoped_ptr<syncer::SyncChangeProcessor> sync_processor_;
+ base::ThreadChecker thread_checker_;
+ base::WeakPtrFactory<DeleteDirectiveHandler> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeleteDirectiveHandler);
+};
+
+} // namespace history
+
+#endif // CHROME_BROWSER_HISTORY_DELETE_DIRECTIVE_HANDLER_H_
diff --git a/chrome/browser/history/history_backend.cc b/chrome/browser/history/history_backend.cc
index 202c7a7..95980cf 100644
--- a/chrome/browser/history/history_backend.cc
+++ b/chrome/browser/history/history_backend.cc
@@ -2732,29 +2732,57 @@ void HistoryBackend::ExpireHistoryBetween(
}
void HistoryBackend::ExpireHistoryForTimes(
- const std::vector<base::Time>& times) {
- // Put the times in reverse chronological order and remove
- // duplicates (for expirer_.ExpireHistoryForTimes()).
- std::vector<base::Time> sorted_times = times;
- std::sort(sorted_times.begin(), sorted_times.end(),
- std::greater<base::Time>());
- sorted_times.erase(
- std::unique(sorted_times.begin(), sorted_times.end()),
- sorted_times.end());
+ const std::set<base::Time>& times,
+ base::Time begin_time, base::Time end_time) {
+ if (times.empty() || !db_.get())
+ return;
- if (sorted_times.empty())
+ 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(db_.get(), db_.get(), 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;
- if (db_.get()) {
- expirer_.ExpireHistoryForTimes(sorted_times);
- // Force a commit, if the user is deleting something for privacy reasons,
- // we want to get it on disk ASAP.
- Commit();
+ // 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());
}
- // Update the first recorded time if we've expired it.
- if (std::binary_search(sorted_times.begin(), sorted_times.end(),
- first_recorded_time_, std::greater<base::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_);
}
diff --git a/chrome/browser/history/history_backend.h b/chrome/browser/history/history_backend.h
index 5df4d1c..facd6e2 100644
--- a/chrome/browser/history/history_backend.h
+++ b/chrome/browser/history/history_backend.h
@@ -442,8 +442,13 @@ class HistoryBackend : public base::RefCountedThreadSafe<HistoryBackend>,
base::Time begin_time,
base::Time end_time);
- // Calls ExpireHistoryBackend::ExpireHistoryForTimes and commits the change.
- void ExpireHistoryForTimes(const std::vector<base::Time>& times);
+ // 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
@@ -548,6 +553,7 @@ class HistoryBackend : public base::RefCountedThreadSafe<HistoryBackend>,
CloneFaviconIsRestrictedToSameDomain);
FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest, QueryFilteredURLs);
FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest, UpdateVisitDuration);
+ FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest, ExpireHistoryForTimes);
friend class ::TestingProfile;
diff --git a/chrome/browser/history/history_backend_unittest.cc b/chrome/browser/history/history_backend_unittest.cc
index 04955d5..9d84486 100644
--- a/chrome/browser/history/history_backend_unittest.cc
+++ b/chrome/browser/history/history_backend_unittest.cc
@@ -2492,9 +2492,10 @@ TEST_F(HistoryBackendTest, AddPageNoVisitForBookmark) {
TEST_F(HistoryBackendTest, ExpireHistoryForTimes) {
ASSERT_TRUE(backend_.get());
- HistoryAddPageArgs args[5];
+ HistoryAddPageArgs args[10];
for (size_t i = 0; i < arraysize(args); ++i) {
- args[i].url = GURL("http://example" + base::IntToString(i) + ".com");
+ args[i].url = GURL("http://example" +
+ std::string((i % 2 == 0 ? ".com" : ".net")));
args[i].time = base::Time::FromInternalValue(i);
backend_->AddPage(args[i]);
}
@@ -2505,22 +2506,38 @@ TEST_F(HistoryBackendTest, ExpireHistoryForTimes) {
EXPECT_TRUE(backend_->GetURL(args[i].url, &row));
}
- std::vector<base::Time> times;
- times.push_back(args[0].time);
- // Insert twice to make sure we handle duplicate times correctly.
- times.push_back(args[2].time);
- times.push_back(args[2].time);
- times.push_back(args[4].time);
- backend_->ExpireHistoryForTimes(times);
+ std::set<base::Time> times;
+ times.insert(args[5].time);
+ backend_->ExpireHistoryForTimes(times,
+ base::Time::FromInternalValue(2),
+ base::Time::FromInternalValue(8));
- EXPECT_EQ(base::Time::FromInternalValue(1),
+ EXPECT_EQ(base::Time::FromInternalValue(0),
backend_->GetFirstRecordedTimeForTest());
- EXPECT_FALSE(backend_->GetURL(args[0].url, &row));
- EXPECT_TRUE(backend_->GetURL(args[1].url, &row));
- EXPECT_FALSE(backend_->GetURL(args[2].url, &row));
- EXPECT_TRUE(backend_->GetURL(args[3].url, &row));
- EXPECT_FALSE(backend_->GetURL(args[4].url, &row));
+ // Visits to http://example.com are untouched.
+ VisitVector visit_vector;
+ EXPECT_TRUE(backend_->GetVisitsForURL(
+ backend_->db_->GetRowForURL(GURL("http://example.com"), NULL),
+ &visit_vector));
+ ASSERT_EQ(5u, visit_vector.size());
+ EXPECT_EQ(base::Time::FromInternalValue(0), visit_vector[0].visit_time);
+ EXPECT_EQ(base::Time::FromInternalValue(2), visit_vector[1].visit_time);
+ EXPECT_EQ(base::Time::FromInternalValue(4), visit_vector[2].visit_time);
+ EXPECT_EQ(base::Time::FromInternalValue(6), visit_vector[3].visit_time);
+ EXPECT_EQ(base::Time::FromInternalValue(8), visit_vector[4].visit_time);
+
+ // Visits to http://example.net between [2,8] are removed.
+ visit_vector.clear();
+ EXPECT_TRUE(backend_->GetVisitsForURL(
+ backend_->db_->GetRowForURL(GURL("http://example.net"), NULL),
+ &visit_vector));
+ ASSERT_EQ(2u, visit_vector.size());
+ EXPECT_EQ(base::Time::FromInternalValue(1), visit_vector[0].visit_time);
+ EXPECT_EQ(base::Time::FromInternalValue(9), visit_vector[1].visit_time);
+
+ EXPECT_EQ(base::Time::FromInternalValue(0),
+ backend_->GetFirstRecordedTimeForTest());
}
TEST_F(HistoryBackendTest, ExpireHistory) {
diff --git a/chrome/browser/history/history_service.cc b/chrome/browser/history/history_service.cc
index e0225a6..8f3b69b 100644
--- a/chrome/browser/history/history_service.cc
+++ b/chrome/browser/history/history_service.cc
@@ -28,15 +28,11 @@
#include "base/callback.h"
#include "base/command_line.h"
#include "base/compiler_specific.h"
-#include "base/json/json_writer.h"
#include "base/location.h"
#include "base/memory/ref_counted.h"
#include "base/message_loop.h"
#include "base/path_service.h"
#include "base/prefs/pref_service.h"
-#include "base/rand_util.h"
-#include "base/sequenced_task_runner.h"
-#include "base/string_util.h"
#include "base/thread_task_runner_handle.h"
#include "base/threading/thread.h"
#include "base/time.h"
@@ -67,14 +63,7 @@
#include "content/public/browser/notification_service.h"
#include "grit/chromium_strings.h"
#include "grit/generated_resources.h"
-#include "sync/api/sync_change.h"
-#include "sync/api/sync_change_processor.h"
-#include "sync/api/sync_data.h"
-#include "sync/api/sync_error.h"
#include "sync/api/sync_error_factory.h"
-#include "sync/protocol/history_delete_directive_specifics.pb.h"
-#include "sync/protocol/proto_value_conversions.h"
-#include "sync/protocol/sync.pb.h"
#include "third_party/skia/include/core/SkBitmap.h"
using base::Time;
@@ -84,15 +73,6 @@ namespace {
static const char* kHistoryThreadName = "Chrome_HistoryThread";
-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;
-}
-
void DerefDownloadDbHandle(
const HistoryService::DownloadCreateCallback& callback,
int64* db_handle) {
@@ -1032,12 +1012,6 @@ base::WeakPtr<HistoryService> HistoryService::AsWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
-void HistoryService::ProcessDeleteDirectiveForTest(
- const sync_pb::HistoryDeleteDirectiveSpecifics& delete_directive) {
- DCHECK(thread_checker_.CalledOnValidThread());
- ProcessDeleteDirective(delete_directive);
-}
-
syncer::SyncMergeResult HistoryService::MergeDataAndStartSyncing(
syncer::ModelType type,
const syncer::SyncDataList& initial_sync_data,
@@ -1045,19 +1019,15 @@ syncer::SyncMergeResult HistoryService::MergeDataAndStartSyncing(
scoped_ptr<syncer::SyncErrorFactory> error_handler) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK_EQ(type, syncer::HISTORY_DELETE_DIRECTIVES);
- for (syncer::SyncDataList::const_iterator it = initial_sync_data.begin();
- it != initial_sync_data.end(); ++it) {
- DCHECK_EQ(it->GetDataType(), syncer::HISTORY_DELETE_DIRECTIVES);
- ProcessDeleteDirective(it->GetSpecifics().history_delete_directive());
- }
- sync_change_processor_ = sync_processor.Pass();
+ 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);
- sync_change_processor_.reset();
+ delete_directive_handler_.Stop();
}
syncer::SyncDataList HistoryService::GetAllSyncData(
@@ -1071,47 +1041,15 @@ syncer::SyncDataList HistoryService::GetAllSyncData(
syncer::SyncError HistoryService::ProcessSyncChanges(
const tracked_objects::Location& from_here,
const syncer::SyncChangeList& change_list) {
- for (syncer::SyncChangeList::const_iterator it = change_list.begin();
- it != change_list.end(); ++it) {
- switch (it->change_type()) {
- case syncer::SyncChange::ACTION_ADD:
- ProcessDeleteDirective(
- it->sync_data().GetSpecifics().history_delete_directive());
- break;
- case syncer::SyncChange::ACTION_DELETE:
- // TODO(akalin): Keep track of existing delete directives.
- break;
- default:
- NOTREACHED();
- break;
- }
- }
+ delete_directive_handler_.ProcessSyncChanges(this, change_list);
return syncer::SyncError();
}
syncer::SyncError HistoryService::ProcessLocalDeleteDirective(
const sync_pb::HistoryDeleteDirectiveSpecifics& delete_directive) {
DCHECK(thread_checker_.CalledOnValidThread());
- ProcessDeleteDirective(delete_directive);
- if (!sync_change_processor_.get()) {
- return syncer::SyncError(
- FROM_HERE,
- "Cannot send local delete directive to sync",
- syncer::HISTORY_DELETE_DIRECTIVES);
- }
- // Generate a random sync tag since history delete directives don't
- // have a 'built-in' ID. 8 bytes should suffice.
- std::string sync_tag = base::RandBytesAsString(8);
- sync_pb::EntitySpecifics entity_specifics;
- entity_specifics.mutable_history_delete_directive()->CopyFrom(
+ return delete_directive_handler_.ProcessLocalDeleteDirective(
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_change_processor_->ProcessSyncChanges(FROM_HERE, changes);
}
void HistoryService::SetInMemoryBackend(int backend_id,
@@ -1300,91 +1238,3 @@ void HistoryService::NotifyVisitDBObserversOnAddVisit(
FOR_EACH_OBSERVER(history::VisitDatabaseObserver, visit_database_observers_,
OnAddVisit(info));
}
-
-void HistoryService::ProcessDeleteDirective(
- const sync_pb::HistoryDeleteDirectiveSpecifics& delete_directive) {
- DCHECK(thread_checker_.CalledOnValidThread());
-
- DVLOG(1) << "Processing delete directive: "
- << DeleteDirectiveToString(delete_directive);
-
- base::Closure done_callback =
- base::Bind(&HistoryService::OnDeleteDirectiveProcessed,
- weak_ptr_factory_.GetWeakPtr(),
- delete_directive);
-
- // Execute |done_callback| on return unless we pass it to something
- // else.
- base::ScopedClosureRunner scoped_done_callback(done_callback);
-
- // Exactly one directive must be filled in. If not, ignore.
- if (delete_directive.has_global_id_directive() ==
- delete_directive.has_time_range_directive()) {
- return;
- }
-
- base::Closure backend_task;
-
- if (delete_directive.has_global_id_directive()) {
- const sync_pb::GlobalIdDirective& global_id_directive =
- delete_directive.global_id_directive();
- std::vector<base::Time> times;
- for (int i = 0; i < global_id_directive.global_id_size(); ++i) {
- int64 global_id = global_id_directive.global_id(i);
- // The global id is just an internal time value.
- base::Time time = base::Time::FromInternalValue(global_id);
- times.push_back(time);
- }
-
- if (!times.empty()) {
- backend_task =
- base::Bind(&HistoryBackend::ExpireHistoryForTimes,
- history_backend_, times);
- }
- } else if (delete_directive.has_time_range_directive()) {
- const sync_pb::TimeRangeDirective& time_range_directive =
- delete_directive.time_range_directive();
- // {start,end}_time_usec must both be filled in. If not, ignore.
- if (!time_range_directive.has_start_time_usec() ||
- !time_range_directive.has_end_time_usec()) {
- return;
- }
-
- // The directive is for the closed interval [start_time_usec,
- // end_time_usec], but ExpireHistoryBetween() expects the
- // half-open interval [begin_time, end_time), so add 1 to
- // end_time_usec before converting.
- base::Time begin_time =
- base::Time::UnixEpoch() +
- base::TimeDelta::FromMicroseconds(
- time_range_directive.start_time_usec());
- base::Time end_time =
- base::Time::UnixEpoch() +
- base::TimeDelta::FromMicroseconds(
- time_range_directive.end_time_usec() + 1);
-
- backend_task =
- base::Bind(&HistoryBackend::ExpireHistoryBetween,
- history_backend_, std::set<GURL>(),
- begin_time, end_time);
- }
-
- if (!backend_task.is_null()) {
- LoadBackendIfNecessary();
- DCHECK(thread_);
- if (thread_->message_loop_proxy()->PostTaskAndReply(
- FROM_HERE,
- backend_task,
- done_callback)) {
- ignore_result(scoped_done_callback.Release());
- }
- }
-}
-
-void HistoryService::OnDeleteDirectiveProcessed(
- const sync_pb::HistoryDeleteDirectiveSpecifics& delete_directive) {
- DVLOG(1) << "Processed delete directive: "
- << DeleteDirectiveToString(delete_directive);
- // TODO(akalin): Keep track of which delete directives we've already
- // processed.
-}
diff --git a/chrome/browser/history/history_service.h b/chrome/browser/history/history_service.h
index 61966b8..c4f8a67 100644
--- a/chrome/browser/history/history_service.h
+++ b/chrome/browser/history/history_service.h
@@ -22,6 +22,7 @@
#include "base/time.h"
#include "chrome/browser/common/cancelable_request.h"
#include "chrome/browser/favicon/favicon_service.h"
+#include "chrome/browser/history/delete_directive_handler.h"
#include "chrome/browser/history/history_types.h"
#include "chrome/browser/profiles/profile_keyed_service.h"
#include "chrome/browser/search_engines/template_url_id.h"
@@ -75,10 +76,6 @@ struct HistoryDetails;
} // namespace history
-namespace sync_pb {
-class HistoryDeleteDirectiveSpecifics;
-}
-
// The history service records page titles, and visit times, as well as
// (eventually) information about autocomplete.
//
@@ -602,9 +599,6 @@ class HistoryService : public CancelableRequestProvider,
base::WeakPtr<HistoryService> AsWeakPtr();
- void ProcessDeleteDirectiveForTest(
- const sync_pb::HistoryDeleteDirectiveSpecifics& delete_directive);
-
// syncer::SyncableService implementation.
virtual syncer::SyncMergeResult MergeDataAndStartSyncing(
syncer::ModelType type,
@@ -835,15 +829,6 @@ class HistoryService : public CancelableRequestProvider,
// specified priority. The task will have ownership taken.
void ScheduleTask(SchedulePriority priority, const base::Closure& task);
- // Delete local history according to the given directive (from
- // sync).
- void ProcessDeleteDirective(
- const sync_pb::HistoryDeleteDirectiveSpecifics& delete_directive);
-
- // Called when a delete directive has been processed.
- void OnDeleteDirectiveProcessed(
- const sync_pb::HistoryDeleteDirectiveSpecifics& delete_directive);
-
// Schedule ------------------------------------------------------------------
//
// Functions for scheduling operations on the history thread that have a
@@ -1066,9 +1051,6 @@ class HistoryService : public CancelableRequestProvider,
// TODO(mrossetti): Consider changing ownership. See http://crbug.com/138321
scoped_ptr<history::InMemoryHistoryBackend> in_memory_backend_;
- // Used to propagate local delete directives to sync.
- scoped_ptr<syncer::SyncChangeProcessor> sync_change_processor_;
-
// The profile, may be null when testing.
Profile* profile_;
@@ -1099,6 +1081,8 @@ class HistoryService : public CancelableRequestProvider,
ObserverList<history::VisitDatabaseObserver> visit_database_observers_;
+ history::DeleteDirectiveHandler delete_directive_handler_;
+
DISALLOW_COPY_AND_ASSIGN(HistoryService);
};
diff --git a/chrome/browser/history/history_types.h b/chrome/browser/history/history_types.h
index 3d6ff1f..68bd3f2 100644
--- a/chrome/browser/history/history_types.h
+++ b/chrome/browser/history/history_types.h
@@ -461,7 +461,10 @@ struct QueryOptions {
// Omit visits for which there is a more recent visit to the same URL on
// the same day. Each URL will appear no more than once per day, where the
// day is defined by the local timezone.
- REMOVE_DUPLICATES_PER_DAY
+ REMOVE_DUPLICATES_PER_DAY,
+
+ // Return all visits without deduping.
+ KEEP_ALL_DUPLICATES
};
// Allows the caller to specify how duplicate URLs in the result set should
diff --git a/chrome/browser/history/history_unittest.cc b/chrome/browser/history/history_unittest.cc
index 5f8e5fd..fcb8aca 100644
--- a/chrome/browser/history/history_unittest.cc
+++ b/chrome/browser/history/history_unittest.cc
@@ -38,6 +38,7 @@
#include "base/path_service.h"
#include "base/string_util.h"
#include "base/stringprintf.h"
+#include "base/threading/platform_thread.h"
#include "base/time.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/history/download_row.h"
@@ -65,6 +66,7 @@
#include "sync/api/sync_error_factory.h"
#include "sync/api/sync_merge_result.h"
#include "sync/protocol/history_delete_directive_specifics.pb.h"
+#include "sync/protocol/sync.pb.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/codec/jpeg_codec.h"
@@ -1242,107 +1244,6 @@ TEST_F(HistoryTest, HistoryDBTaskCanceled) {
ASSERT_FALSE(task->done_invoked);
}
-// Create a delete directive for a few specific history entries,
-// including ones that don't exist. The expected entries should be
-// deleted.
-TEST_F(HistoryTest, ProcessGlobalIdDeleteDirective) {
- ASSERT_TRUE(history_service_.get());
- const GURL test_url("http://www.google.com/");
- for (int64 i = 1; i <= 10; ++i) {
- base::Time t =
- base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(i);
- history_service_->AddPage(test_url, t, NULL, 0, GURL(),
- history::RedirectList(),
- content::PAGE_TRANSITION_LINK,
- history::SOURCE_BROWSED, false);
- }
-
- EXPECT_TRUE(QueryURL(history_service_.get(), test_url));
- EXPECT_EQ(10, query_url_row_.visit_count());
-
- sync_pb::HistoryDeleteDirectiveSpecifics delete_directive;
- sync_pb::GlobalIdDirective* global_id_directive =
- delete_directive.mutable_global_id_directive();
- global_id_directive->add_global_id(
- (base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(0))
- .ToInternalValue());
- global_id_directive->add_global_id(
- (base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(2))
- .ToInternalValue());
- global_id_directive->add_global_id(
- (base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(5))
- .ToInternalValue());
- global_id_directive->add_global_id(
- (base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(10))
- .ToInternalValue());
- global_id_directive->add_global_id(
- (base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(20))
- .ToInternalValue());
-
- history_service_->ProcessDeleteDirectiveForTest(delete_directive);
-
- EXPECT_TRUE(QueryURL(history_service_.get(), test_url));
- EXPECT_EQ(7, query_url_row_.visit_count());
-}
-
-// Create a delete directive for a given time range. The expected
-// entries should be deleted.
-TEST_F(HistoryTest, ProcessTimeRangeDeleteDirective) {
- ASSERT_TRUE(history_service_.get());
- const GURL test_url("http://www.google.com/");
- for (int64 i = 1; i <= 10; ++i) {
- base::Time t =
- base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(i);
- history_service_->AddPage(test_url, t, NULL, 0, GURL(),
- history::RedirectList(),
- content::PAGE_TRANSITION_LINK,
- history::SOURCE_BROWSED, false);
- }
-
- EXPECT_TRUE(QueryURL(history_service_.get(), test_url));
- EXPECT_EQ(10, query_url_row_.visit_count());
-
- sync_pb::HistoryDeleteDirectiveSpecifics delete_directive;
- sync_pb::TimeRangeDirective* time_range_directive =
- delete_directive.mutable_time_range_directive();
- time_range_directive->set_start_time_usec(2);
- time_range_directive->set_end_time_usec(9);
-
- history_service_->ProcessDeleteDirectiveForTest(delete_directive);
-
- EXPECT_TRUE(QueryURL(history_service_.get(), test_url));
- EXPECT_EQ(2, query_url_row_.visit_count());
-}
-
-// Create a local delete directive and process it while sync is
-// offline. The expected entries should be deleted, and an error
-// should be returned.
-TEST_F(HistoryTest, ProcessLocalDeleteDirectiveSyncOffline) {
- ASSERT_TRUE(history_service_.get());
- const GURL test_url("http://www.google.com/");
- for (int64 i = 1; i <= 10; ++i) {
- base::Time t =
- base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(i);
- history_service_->AddPage(test_url, t, NULL, 0, GURL(),
- history::RedirectList(),
- content::PAGE_TRANSITION_LINK,
- history::SOURCE_BROWSED, false);
- }
-
- sync_pb::HistoryDeleteDirectiveSpecifics delete_directive;
- sync_pb::GlobalIdDirective* global_id_directive =
- delete_directive.mutable_global_id_directive();
- global_id_directive->add_global_id(
- (base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(1))
- .ToInternalValue());
-
- syncer::SyncError err =
- history_service_->ProcessLocalDeleteDirective(delete_directive);
- EXPECT_TRUE(err.IsSet());
- EXPECT_TRUE(QueryURL(history_service_.get(), test_url));
- EXPECT_EQ(9, query_url_row_.visit_count());
-}
-
// Dummy SyncChangeProcessor used to help review what SyncChanges are pushed
// back up to Sync.
//
@@ -1399,9 +1300,8 @@ class SyncChangeProcessorDelegate : public syncer::SyncChangeProcessor {
};
// Create a local delete directive and process it while sync is
-// online, and then when offline. The expected entries should be
-// deleted, the delete directive should be sent to sync, no error
-// should be returned for the first time, and an error should be
+// online, and then when offline. The delete directive should be sent to sync,
+// no error should be returned for the first time, and an error should be
// returned for the second time.
TEST_F(HistoryTest, ProcessLocalDeleteDirectiveSyncOnline) {
ASSERT_TRUE(history_service_.get());
@@ -1438,15 +1338,179 @@ TEST_F(HistoryTest, ProcessLocalDeleteDirectiveSyncOnline) {
EXPECT_FALSE(err.IsSet());
EXPECT_EQ(1u, change_processor.GetChanges().size());
- EXPECT_TRUE(QueryURL(history_service_.get(), test_url));
- EXPECT_EQ(9, query_url_row_.visit_count());
-
history_service_->StopSyncing(syncer::HISTORY_DELETE_DIRECTIVES);
err = history_service_->ProcessLocalDeleteDirective(delete_directive);
EXPECT_TRUE(err.IsSet());
EXPECT_EQ(1u, change_processor.GetChanges().size());
}
+// Closure function that runs periodically to check result of delete directive
+// processing. Stop when timeout or processing ends indicated by the creation
+// of sync changes.
+void CheckDirectiveProcessingResult(
+ Time timeout, const TestChangeProcessor* change_processor,
+ uint32 num_changes) {
+ if (base::Time::Now() > timeout ||
+ change_processor->GetChanges().size() >= num_changes) {
+ return;
+ }
+
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&CheckDirectiveProcessingResult, timeout,
+ change_processor, num_changes));
+}
+
+// Create a delete directive for a few specific history entries,
+// including ones that don't exist. The expected entries should be
+// deleted.
+TEST_F(HistoryTest, ProcessGlobalIdDeleteDirective) {
+ ASSERT_TRUE(history_service_.get());
+ const GURL test_url("http://www.google.com/");
+ for (int64 i = 1; i <= 20; i++) {
+ base::Time t =
+ base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(i);
+ history_service_->AddPage(test_url, t, NULL, 0, GURL(),
+ history::RedirectList(),
+ content::PAGE_TRANSITION_LINK,
+ history::SOURCE_BROWSED, false);
+ }
+
+ EXPECT_TRUE(QueryURL(history_service_.get(), test_url));
+ EXPECT_EQ(20, query_url_row_.visit_count());
+
+ syncer::SyncDataList directives;
+ // 1st directive.
+ sync_pb::EntitySpecifics entity_specs;
+ sync_pb::GlobalIdDirective* global_id_directive =
+ entity_specs.mutable_history_delete_directive()
+ ->mutable_global_id_directive();
+ global_id_directive->add_global_id(
+ (base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(6))
+ .ToInternalValue());
+ global_id_directive->set_start_time_usec(3);
+ global_id_directive->set_end_time_usec(10);
+ directives.push_back(
+ syncer::SyncData::CreateRemoteData(1, entity_specs));
+
+ // 2nd directive.
+ global_id_directive->Clear();
+ global_id_directive->add_global_id(
+ (base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(17))
+ .ToInternalValue());
+ global_id_directive->set_start_time_usec(13);
+ global_id_directive->set_end_time_usec(19);
+ directives.push_back(
+ syncer::SyncData::CreateRemoteData(2, entity_specs));
+
+ TestChangeProcessor change_processor;
+ EXPECT_FALSE(
+ history_service_->MergeDataAndStartSyncing(
+ syncer::HISTORY_DELETE_DIRECTIVES,
+ directives,
+ scoped_ptr<syncer::SyncChangeProcessor>(
+ new SyncChangeProcessorDelegate(&change_processor)),
+ scoped_ptr<syncer::SyncErrorFactory>()).error().IsSet());
+
+ // Inject a task to check status and keep message loop filled before directive
+ // processing finishes.
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&CheckDirectiveProcessingResult,
+ base::Time::Now() + base::TimeDelta::FromSeconds(10),
+ &change_processor, 2));
+ MessageLoop::current()->RunUntilIdle();
+ EXPECT_TRUE(QueryURL(history_service_.get(), test_url));
+ ASSERT_EQ(5, query_url_row_.visit_count());
+ EXPECT_EQ(base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(1),
+ query_url_visits_[0].visit_time);
+ EXPECT_EQ(base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(2),
+ query_url_visits_[1].visit_time);
+ EXPECT_EQ(base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(11),
+ query_url_visits_[2].visit_time);
+ EXPECT_EQ(base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(12),
+ query_url_visits_[3].visit_time);
+ EXPECT_EQ(base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(20),
+ query_url_visits_[4].visit_time);
+
+ // Expect two sync changes for deleting processed directives.
+ const syncer::SyncChangeList& sync_changes = change_processor.GetChanges();
+ ASSERT_EQ(2u, sync_changes.size());
+ EXPECT_EQ(syncer::SyncChange::ACTION_DELETE, sync_changes[0].change_type());
+ EXPECT_EQ(1, sync_changes[0].sync_data().GetRemoteId());
+ EXPECT_EQ(syncer::SyncChange::ACTION_DELETE, sync_changes[1].change_type());
+ EXPECT_EQ(2, sync_changes[1].sync_data().GetRemoteId());
+}
+
+// Create delete directives for time ranges. The expected entries should be
+// deleted.
+TEST_F(HistoryTest, ProcessTimeRangeDeleteDirective) {
+ ASSERT_TRUE(history_service_.get());
+ const GURL test_url("http://www.google.com/");
+ for (int64 i = 1; i <= 10; ++i) {
+ base::Time t =
+ base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(i);
+ history_service_->AddPage(test_url, t, NULL, 0, GURL(),
+ history::RedirectList(),
+ content::PAGE_TRANSITION_LINK,
+ history::SOURCE_BROWSED, false);
+ }
+
+ EXPECT_TRUE(QueryURL(history_service_.get(), test_url));
+ EXPECT_EQ(10, query_url_row_.visit_count());
+
+ syncer::SyncDataList directives;
+ // 1st directive.
+ sync_pb::EntitySpecifics entity_specs;
+ sync_pb::TimeRangeDirective* time_range_directive =
+ entity_specs.mutable_history_delete_directive()
+ ->mutable_time_range_directive();
+ time_range_directive->set_start_time_usec(2);
+ time_range_directive->set_end_time_usec(5);
+ directives.push_back(syncer::SyncData::CreateRemoteData(1, entity_specs));
+
+ // 2nd directive.
+ time_range_directive->Clear();
+ time_range_directive->set_start_time_usec(8);
+ time_range_directive->set_end_time_usec(10);
+ directives.push_back(syncer::SyncData::CreateRemoteData(2, entity_specs));
+
+ TestChangeProcessor change_processor;
+ EXPECT_FALSE(
+ history_service_->MergeDataAndStartSyncing(
+ syncer::HISTORY_DELETE_DIRECTIVES,
+ directives,
+ scoped_ptr<syncer::SyncChangeProcessor>(
+ new SyncChangeProcessorDelegate(&change_processor)),
+ scoped_ptr<syncer::SyncErrorFactory>()).error().IsSet());
+
+ // Inject a task to check status and keep message loop filled before
+ // directive processing finishes.
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&CheckDirectiveProcessingResult,
+ base::Time::Now() + base::TimeDelta::FromSeconds(10),
+ &change_processor, 2));
+ MessageLoop::current()->RunUntilIdle();
+ EXPECT_TRUE(QueryURL(history_service_.get(), test_url));
+ ASSERT_EQ(3, query_url_row_.visit_count());
+ EXPECT_EQ(base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(1),
+ query_url_visits_[0].visit_time);
+ EXPECT_EQ(base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(6),
+ query_url_visits_[1].visit_time);
+ EXPECT_EQ(base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(7),
+ query_url_visits_[2].visit_time);
+
+ // Expect two sync changes for deleting processed directives.
+ const syncer::SyncChangeList& sync_changes = change_processor.GetChanges();
+ ASSERT_EQ(2u, sync_changes.size());
+ EXPECT_EQ(syncer::SyncChange::ACTION_DELETE, sync_changes[0].change_type());
+ EXPECT_EQ(1, sync_changes[0].sync_data().GetRemoteId());
+ EXPECT_EQ(syncer::SyncChange::ACTION_DELETE, sync_changes[1].change_type());
+ EXPECT_EQ(2, sync_changes[1].sync_data().GetRemoteId());
+}
+
TEST_F(HistoryBackendDBTest, MigratePresentations) {
// Create the db we want. Use 22 since segments didn't change in that time
// frame.
diff --git a/chrome/browser/history/visit_database.cc b/chrome/browser/history/visit_database.cc
index 289ce9d..ff76b6a 100644
--- a/chrome/browser/history/visit_database.cc
+++ b/chrome/browser/history/visit_database.cc
@@ -348,21 +348,21 @@ bool VisitDatabase::GetVisibleVisitsInRange(const QueryOptions& options,
VisitRow visit;
FillVisitRow(statement, &visit);
- if (options.duplicate_policy == QueryOptions::REMOVE_DUPLICATES_PER_DAY &&
- found_urls_midnight != visit.visit_time.LocalMidnight()) {
- found_urls.clear();
- found_urls_midnight = visit.visit_time.LocalMidnight();
- }
-
- // Make sure the URL this visit corresponds to is unique.
- if (found_urls.find(visit.url_id) == found_urls.end()) {
+ if (options.duplicate_policy != QueryOptions::KEEP_ALL_DUPLICATES) {
+ if (options.duplicate_policy == QueryOptions::REMOVE_DUPLICATES_PER_DAY &&
+ found_urls_midnight != visit.visit_time.LocalMidnight()) {
+ found_urls.clear();
+ found_urls_midnight = visit.visit_time.LocalMidnight();
+ }
+ // Make sure the URL this visit corresponds to is unique.
+ if (found_urls.find(visit.url_id) != found_urls.end())
+ continue;
found_urls.insert(visit.url_id);
-
- if (static_cast<int>(visits->size()) >= options.EffectiveMaxCount())
- return true;
-
- visits->push_back(visit);
}
+
+ if (static_cast<int>(visits->size()) >= options.EffectiveMaxCount())
+ return true;
+ visits->push_back(visit);
}
return false;
}
diff --git a/chrome/browser/history/visit_database_unittest.cc b/chrome/browser/history/visit_database_unittest.cc
index bdc33fc..77a5440 100644
--- a/chrome/browser/history/visit_database_unittest.cc
+++ b/chrome/browser/history/visit_database_unittest.cc
@@ -321,6 +321,15 @@ TEST_F(VisitDatabaseTest, GetVisibleVisitsInRange) {
EXPECT_TRUE(IsVisitInfoEqual(results[1], test_visit_rows[3]));
EXPECT_TRUE(IsVisitInfoEqual(results[2], test_visit_rows[1]));
+ // Now try without de-duping, expect to see all visible visits.
+ options.duplicate_policy = QueryOptions::KEEP_ALL_DUPLICATES;
+ GetVisibleVisitsInRange(options, &results);
+ ASSERT_EQ(static_cast<size_t>(4), results.size());
+ EXPECT_TRUE(IsVisitInfoEqual(results[0], test_visit_rows[5]));
+ EXPECT_TRUE(IsVisitInfoEqual(results[1], test_visit_rows[3]));
+ EXPECT_TRUE(IsVisitInfoEqual(results[2], test_visit_rows[1]));
+ EXPECT_TRUE(IsVisitInfoEqual(results[3], test_visit_rows[0]));
+
// Set the end time to exclude the second visit. The first visit should be
// returned. Even though the second is a more recent visit, it's not in the
// query range.
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index acb79d8..6a52c7d 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -763,6 +763,8 @@
'browser/history/android/visit_sql_handler.h',
'browser/history/archived_database.cc',
'browser/history/archived_database.h',
+ 'browser/history/delete_directive_handler.cc',
+ 'browser/history/delete_directive_handler.h',
'browser/history/download_database.cc',
'browser/history/download_database.h',
'browser/history/download_row.cc',
diff --git a/sync/api/sync_data.h b/sync/api/sync_data.h
index c3404c3..33e41a8 100644
--- a/sync/api/sync_data.h
+++ b/sync/api/sync_data.h
@@ -117,6 +117,8 @@ class SYNC_EXPORT SyncData {
// gmock printer helper.
void PrintTo(const SyncData& sync_data, std::ostream* os);
+typedef std::vector<SyncData> SyncDataList;
+
} // namespace syncer
#endif // SYNC_API_SYNC_DATA_H_
diff --git a/sync/api/syncable_service.h b/sync/api/syncable_service.h
index 8d187fc..13c282ef 100644
--- a/sync/api/syncable_service.h
+++ b/sync/api/syncable_service.h
@@ -21,8 +21,6 @@ namespace syncer {
class SyncErrorFactory;
-typedef std::vector<SyncData> SyncDataList;
-
// TODO(zea): remove SupportsWeakPtr in favor of having all SyncableService
// implementers provide a way of getting a weak pointer to themselves.
// See crbug.com/100114.