Index: ios/chrome/browser/ui/history/history_service_facade.mm |
diff --git a/ios/chrome/browser/ui/history/history_service_facade.mm b/ios/chrome/browser/ui/history/history_service_facade.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..7f68582219adde88b4956dd3ab221d4cbb0fcc58 |
--- /dev/null |
+++ b/ios/chrome/browser/ui/history/history_service_facade.mm |
@@ -0,0 +1,463 @@ |
+// Copyright 2016 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. |
+ |
+#import "ios/chrome/browser/ui/history/history_service_facade.h" |
+ |
+#include <stddef.h> |
+ |
+#include "base/bind.h" |
+#include "base/bind_helpers.h" |
+#include "base/i18n/time_formatting.h" |
+#include "base/mac/bind_objc_block.h" |
+#include "base/memory/weak_ptr.h" |
+#include "base/metrics/histogram.h" |
+#include "base/strings/string16.h" |
+#include "base/strings/string_number_conversions.h" |
+#include "base/strings/utf_string_conversions.h" |
+#include "base/time/time.h" |
+#include "base/values.h" |
+#include "components/browser_sync/profile_sync_service.h" |
+#include "components/browsing_data/core/history_notice_utils.h" |
+#include "components/history/core/browser/history_service.h" |
+#include "components/history/core/browser/history_types.h" |
+#include "components/history/core/browser/web_history_service.h" |
+#include "components/keyed_service/core/service_access_type.h" |
+#include "components/prefs/pref_service.h" |
+#include "components/query_parser/snippet.h" |
+#include "components/sync/protocol/history_delete_directive_specifics.pb.h" |
+#include "components/sync/protocol/sync_enums.pb.h" |
+#include "ios/chrome/browser/browser_state/chrome_browser_state.h" |
+#include "ios/chrome/browser/history/history_service_factory.h" |
+#include "ios/chrome/browser/history/history_utils.h" |
+#include "ios/chrome/browser/history/web_history_service_factory.h" |
+#include "ios/chrome/browser/pref_names.h" |
+#include "ios/chrome/browser/sync/ios_chrome_profile_sync_service_factory.h" |
+#include "ios/chrome/browser/ui/history/history_entry.h" |
+#include "ios/chrome/browser/ui/history/history_service_facade_delegate.h" |
+#include "ios/chrome/browser/ui/history/history_util.h" |
+ |
+// The amount of time to wait for a response from the WebHistoryService. |
+static const int kWebHistoryTimeoutSeconds = 3; |
+ |
+namespace { |
+ |
+// Buckets for UMA histograms. |
+enum WebHistoryQueryBuckets { |
+ WEB_HISTORY_QUERY_FAILED = 0, |
+ WEB_HISTORY_QUERY_SUCCEEDED, |
+ WEB_HISTORY_QUERY_TIMED_OUT, |
+ NUM_WEB_HISTORY_QUERY_BUCKETS |
+}; |
+ |
+// Returns true if |entry| represents a local visit that had no corresponding |
+// visit on the server. |
+bool IsLocalOnlyResult(const history::HistoryEntry& entry) { |
+ return entry.entry_type == history::HistoryEntry::LOCAL_ENTRY; |
+} |
+ |
+// Returns true if there are any differences between the URLs observed deleted |
+// and the ones we are expecting to be deleted. |
+static bool DeletionsDiffer(const history::URLRows& observed_deletions, |
+ const std::set<GURL>& expected_deletions) { |
+ if (observed_deletions.size() != expected_deletions.size()) |
+ return true; |
+ for (const auto& i : observed_deletions) { |
+ if (expected_deletions.find(i.url()) == expected_deletions.end()) |
+ return true; |
+ } |
+ return false; |
+} |
+ |
+} // namespace |
+ |
+#pragma mark - QueryResult |
+ |
+HistoryServiceFacade::QueryResult::QueryResult() |
+ : query(base::string16()), |
+ query_start_time(base::string16()), |
+ query_end_time(base::string16()), |
+ finished(false), |
+ has_synced_results(false), |
+ sync_finished(false), |
+ entries(std::vector<history::HistoryEntry>()) {} |
+ |
+HistoryServiceFacade::QueryResult::QueryResult(const QueryResult& other) = |
+ default; |
+ |
+HistoryServiceFacade::QueryResult::~QueryResult() {} |
+ |
+#pragma mark - RemovedEntry |
+ |
+HistoryServiceFacade::RemovedEntry::RemovedEntry(const GURL& url, |
+ const base::Time& timestamp) |
+ : url(url) { |
+ timestamps = std::vector<base::Time>(); |
+ timestamps.push_back(timestamp); |
+} |
+ |
+HistoryServiceFacade::RemovedEntry::RemovedEntry( |
+ const GURL& url, |
+ const std::vector<base::Time>& timestamps) |
+ : url(url), timestamps(timestamps) {} |
+ |
+HistoryServiceFacade::RemovedEntry::RemovedEntry(const RemovedEntry& other) = |
+ default; |
+ |
+HistoryServiceFacade::RemovedEntry::~RemovedEntry() {} |
+ |
+#pragma mark - HistoryServiceFacade |
+ |
+HistoryServiceFacade::HistoryServiceFacade( |
+ ios::ChromeBrowserState* browser_state, |
+ id<HistoryServiceFacadeDelegate> delegate) |
+ : has_pending_delete_request_(false), |
+ history_service_observer_(this), |
+ browser_state_(browser_state), |
+ delegate_(delegate), |
+ weak_factory_(this) { |
+ // Register as observer of HistoryService. |
+ history::HistoryService* history_service = |
+ ios::HistoryServiceFactory::GetForBrowserState( |
+ browser_state, ServiceAccessType::EXPLICIT_ACCESS); |
+ if (history_service) |
+ history_service_observer_.Add(history_service); |
+} |
+ |
+HistoryServiceFacade::~HistoryServiceFacade() { |
+ query_task_tracker_.TryCancelAll(); |
+ web_history_request_.reset(); |
+ delegate_.reset(); |
+} |
+ |
+void HistoryServiceFacade::QueryHistory(const base::string16& search_text, |
+ const history::QueryOptions& options) { |
+ // Anything in-flight is invalid. |
+ query_task_tracker_.TryCancelAll(); |
+ web_history_request_.reset(); |
+ |
+ // Reset results. |
+ query_results_.clear(); |
+ results_info_value_ = QueryResult(); |
+ |
+ // Query local history. |
+ history::HistoryService* history_service = |
+ ios::HistoryServiceFactory::GetForBrowserState( |
+ browser_state_, ServiceAccessType::EXPLICIT_ACCESS); |
+ if (history_service) { |
+ history_service->QueryHistory( |
+ search_text, options, |
+ base::Bind(&HistoryServiceFacade::QueryComplete, base::Unretained(this), |
+ search_text, options), |
+ &query_task_tracker_); |
+ } |
+ |
+ // Query synced history. |
+ history::WebHistoryService* web_history = |
+ ios::WebHistoryServiceFactory::GetForBrowserState(browser_state_); |
+ if (web_history) { |
+ web_history_query_results_.clear(); |
+ web_history_request_ = web_history->QueryHistory( |
+ search_text, options, |
+ base::Bind(&HistoryServiceFacade::WebHistoryQueryComplete, |
+ base::Unretained(this), search_text, options, |
+ base::TimeTicks::Now())); |
+ // Start a timer so we know when to give up. |
+ web_history_timer_.Start( |
+ FROM_HERE, base::TimeDelta::FromSeconds(kWebHistoryTimeoutSeconds), |
+ this, &HistoryServiceFacade::WebHistoryTimeout); |
+ } |
+} |
+ |
+void HistoryServiceFacade::RemoveHistoryEntries( |
+ const std::vector<RemovedEntry>& entries) { |
+ // Early return if there is a deletion in progress. |
+ if (delete_task_tracker_.HasTrackedTasks() || has_pending_delete_request_) { |
+ return; |
+ } |
+ |
+ history::HistoryService* history_service = |
+ ios::HistoryServiceFactory::GetForBrowserState( |
+ browser_state_, ServiceAccessType::EXPLICIT_ACCESS); |
+ history::WebHistoryService* web_history = |
+ ios::WebHistoryServiceFactory::GetForBrowserState(browser_state_); |
+ |
+ base::Time now = base::Time::Now(); |
+ std::vector<history::ExpireHistoryArgs> expire_list; |
+ expire_list.reserve(entries.size()); |
+ |
+ DCHECK(urls_to_be_deleted_.empty()); |
+ for (const RemovedEntry& entry : entries) { |
+ GURL url = entry.url; |
+ DCHECK(entry.timestamps.size() > 0); |
+ |
+ // In order to ensure that visits will be deleted from the server and other |
+ // clients (even if they are offline), create a sync delete directive for |
+ // each visit to be deleted. |
+ sync_pb::HistoryDeleteDirectiveSpecifics delete_directive; |
+ sync_pb::GlobalIdDirective* global_id_directive = |
+ delete_directive.mutable_global_id_directive(); |
+ |
+ expire_list.resize(expire_list.size() + 1); |
+ history::ExpireHistoryArgs* expire_args = &expire_list.back(); |
+ expire_args->SetTimeRangeForOneDay(entry.timestamps.front()); |
+ expire_args->urls.insert(entry.url); |
+ urls_to_be_deleted_.insert(entry.url); |
+ |
+ for (base::Time visit_time : entry.timestamps) { |
+ // The local visit time is treated as a global ID for the visit. |
+ global_id_directive->add_global_id(visit_time.ToInternalValue()); |
+ } |
+ |
+ // Set the start and end time in microseconds since the Unix epoch. |
+ global_id_directive->set_start_time_usec( |
+ (expire_args->begin_time - base::Time::UnixEpoch()).InMicroseconds()); |
+ |
+ // Delete directives shouldn't have an end time in the future. |
+ base::Time end_time = std::min(expire_args->end_time, now); |
+ |
+ // -1 because end time in delete directives is inclusive. |
+ global_id_directive->set_end_time_usec( |
+ (end_time - base::Time::UnixEpoch()).InMicroseconds() - 1); |
+ |
+ if (web_history) |
+ history_service->ProcessLocalDeleteDirective(delete_directive); |
+ } |
+ |
+ if (history_service) { |
+ history_service->ExpireHistory( |
+ expire_list, base::Bind(&HistoryServiceFacade::RemoveComplete, |
+ base::Unretained(this)), |
+ &delete_task_tracker_); |
+ } |
+ |
+ if (web_history) { |
+ has_pending_delete_request_ = true; |
+ web_history->ExpireHistory( |
+ expire_list, base::Bind(&HistoryServiceFacade::RemoveWebHistoryComplete, |
+ weak_factory_.GetWeakPtr())); |
+ } |
+} |
+ |
+void HistoryServiceFacade::QueryOtherFormsOfBrowsingHistory() { |
+ browser_sync::ProfileSyncService* sync_service = |
+ IOSChromeProfileSyncServiceFactory::GetForBrowserState(browser_state_); |
+ history::WebHistoryService* history_service = |
+ ios::WebHistoryServiceFactory::GetForBrowserState(browser_state_); |
+ browsing_data::ShouldShowNoticeAboutOtherFormsOfBrowsingHistory( |
+ sync_service, history_service, |
+ base::Bind( |
+ &HistoryServiceFacade::OtherFormsOfBrowsingHistoryQueryComplete, |
+ weak_factory_.GetWeakPtr())); |
+} |
+ |
+#pragma mark - Private methods |
+ |
+void HistoryServiceFacade::WebHistoryTimeout() { |
+ // If there are no outstanding tasks, send results to front end. Would also |
+ // be good to communicate the failure to the front end. |
+ if (!query_task_tracker_.HasTrackedTasks()) |
+ ReturnResultsToFrontEnd(); |
+ |
+ UMA_HISTOGRAM_ENUMERATION("WebHistory.QueryCompletion", |
+ WEB_HISTORY_QUERY_TIMED_OUT, |
+ NUM_WEB_HISTORY_QUERY_BUCKETS); |
+} |
+ |
+void HistoryServiceFacade::QueryComplete(const base::string16& search_text, |
+ const history::QueryOptions& options, |
+ history::QueryResults* results) { |
+ DCHECK_EQ(0U, query_results_.size()); |
+ query_results_.reserve(results->size()); |
+ |
+ for (history::URLResult* result : *results) { |
+ query_results_.push_back(history::HistoryEntry( |
+ history::HistoryEntry::LOCAL_ENTRY, result->url(), result->title(), |
+ result->visit_time(), std::string(), !search_text.empty(), |
+ result->snippet().text(), result->blocked_visit())); |
+ } |
+ |
+ results_info_value_.query = search_text; |
+ results_info_value_.finished = results->reached_beginning(); |
+ results_info_value_.query_start_time = |
+ history::GetRelativeDateLocalized((options.begin_time)); |
+ |
+ // Add the specific dates that were searched to display them. |
+ // Should put today if the start is in the future. |
+ if (!options.end_time.is_null()) { |
+ results_info_value_.query_end_time = history::GetRelativeDateLocalized( |
+ options.end_time - base::TimeDelta::FromDays(1)); |
+ } else { |
+ results_info_value_.query_end_time = |
+ history::GetRelativeDateLocalized(base::Time::Now()); |
+ } |
+ if (!web_history_timer_.IsRunning()) |
+ ReturnResultsToFrontEnd(); |
+} |
+ |
+void HistoryServiceFacade::WebHistoryQueryComplete( |
+ const base::string16& search_text, |
+ const history::QueryOptions& options, |
+ base::TimeTicks start_time, |
+ history::WebHistoryService::Request* request, |
+ const base::DictionaryValue* results_value) { |
+ base::TimeDelta delta = base::TimeTicks::Now() - start_time; |
+ UMA_HISTOGRAM_TIMES("WebHistory.ResponseTime", delta); |
+ |
+ // If the response came in too late, do nothing. |
+ if (!web_history_timer_.IsRunning()) |
+ return; |
+ web_history_timer_.Stop(); |
+ |
+ UMA_HISTOGRAM_ENUMERATION( |
+ "WebHistory.QueryCompletion", |
+ results_value ? WEB_HISTORY_QUERY_SUCCEEDED : WEB_HISTORY_QUERY_FAILED, |
+ NUM_WEB_HISTORY_QUERY_BUCKETS); |
+ |
+ DCHECK_EQ(0U, web_history_query_results_.size()); |
+ const base::ListValue* events = NULL; |
+ if (results_value && results_value->GetList("event", &events)) { |
+ web_history_query_results_.reserve(events->GetSize()); |
+ for (unsigned int i = 0; i < events->GetSize(); ++i) { |
+ const base::DictionaryValue* event = NULL; |
+ const base::DictionaryValue* result = NULL; |
+ const base::ListValue* results = NULL; |
+ const base::ListValue* ids = NULL; |
+ base::string16 url; |
+ base::string16 title; |
+ base::Time visit_time; |
+ |
+ if (!(events->GetDictionary(i, &event) && |
+ event->GetList("result", &results) && |
+ results->GetDictionary(0, &result) && |
+ result->GetString("url", &url) && result->GetList("id", &ids) && |
+ ids->GetSize() > 0)) { |
+ LOG(WARNING) << "Improperly formed JSON response from history server."; |
+ continue; |
+ } |
+ |
+ // Ignore any URLs that should not be shown in the history page. |
+ GURL gurl(url); |
+ if (!ios::CanAddURLToHistory(gurl)) |
+ continue; |
+ |
+ // Title is optional, so the return value is ignored here. |
+ result->GetString("title", &title); |
+ |
+ // Extract the timestamps of all the visits to this URL. |
+ // They are referred to as "IDs" by the server. |
+ for (int j = 0; j < static_cast<int>(ids->GetSize()); ++j) { |
+ const base::DictionaryValue* id = NULL; |
+ std::string timestamp_string; |
+ int64_t timestamp_usec = 0; |
+ |
+ if (!ids->GetDictionary(j, &id) || |
+ !id->GetString("timestamp_usec", ×tamp_string) || |
+ !base::StringToInt64(timestamp_string, ×tamp_usec)) { |
+ NOTREACHED() << "Unable to extract timestamp."; |
+ continue; |
+ } |
+ // The timestamp on the server is a Unix time. |
+ base::Time time = base::Time::UnixEpoch() + |
+ base::TimeDelta::FromMicroseconds(timestamp_usec); |
+ |
+ // Get the ID of the client that this visit came from. |
+ std::string client_id; |
+ id->GetString("client_id", &client_id); |
+ |
+ web_history_query_results_.push_back(history::HistoryEntry( |
+ history::HistoryEntry::REMOTE_ENTRY, gurl, title, time, client_id, |
+ !search_text.empty(), base::string16(), |
+ /* blocked_visit */ false)); |
+ } |
+ } |
+ } |
+ |
+ results_info_value_.has_synced_results = results_value != NULL; |
+ if (results_value) { |
+ std::string continuation_token; |
+ results_value->GetString("continuation_token", &continuation_token); |
+ results_info_value_.sync_finished = continuation_token.empty(); |
+ } |
+ if (!query_task_tracker_.HasTrackedTasks()) |
+ ReturnResultsToFrontEnd(); |
+} |
+ |
+void HistoryServiceFacade::RemoveComplete() { |
+ urls_to_be_deleted_.clear(); |
+ |
+ // Notify the delegate that the deletion request is complete, but only if a |
+ // web history delete request is not still pending. |
+ if (has_pending_delete_request_) |
+ return; |
+ if ([delegate_ respondsToSelector: |
+ @selector(historyServiceFacadeDidCompleteEntryRemoval:)]) { |
+ [delegate_ historyServiceFacadeDidCompleteEntryRemoval:this]; |
+ } |
+} |
+ |
+void HistoryServiceFacade::RemoveWebHistoryComplete(bool success) { |
+ has_pending_delete_request_ = false; |
+ if (!delete_task_tracker_.HasTrackedTasks()) |
+ RemoveComplete(); |
+} |
+ |
+void HistoryServiceFacade::OtherFormsOfBrowsingHistoryQueryComplete( |
+ bool found_other_forms_of_browsing_history) { |
+ if ([delegate_ respondsToSelector: |
+ @selector(historyServiceFacade: |
+ shouldShowNoticeAboutOtherFormsOfBrowsingHistory:)]) { |
+ [delegate_ historyServiceFacade:this |
+ shouldShowNoticeAboutOtherFormsOfBrowsingHistory: |
+ found_other_forms_of_browsing_history]; |
+ } |
+} |
+ |
+void HistoryServiceFacade::ReturnResultsToFrontEnd() { |
+ // Combine the local and remote results into |query_results_|, and remove |
+ // any duplicates. |
+ if (!web_history_query_results_.empty()) { |
+ int local_result_count = query_results_.size(); |
+ query_results_.insert(query_results_.end(), |
+ web_history_query_results_.begin(), |
+ web_history_query_results_.end()); |
+ history::MergeDuplicateHistoryEntries(&query_results_); |
+ |
+ if (local_result_count) { |
+ // In the best case, we expect that all local results are duplicated on |
+ // the server. Keep track of how many are missing. |
+ int missing_count = std::count_if( |
+ query_results_.begin(), query_results_.end(), IsLocalOnlyResult); |
+ UMA_HISTOGRAM_PERCENTAGE("WebHistory.LocalResultMissingOnServer", |
+ missing_count * 100.0 / local_result_count); |
+ } |
+ } |
+ |
+ // Send results to delegate. Results may be empty. |
+ results_info_value_.entries = query_results_; |
+ if ([delegate_ respondsToSelector:@selector(historyServiceFacade: |
+ didReceiveQueryResult:)]) { |
+ [delegate_ historyServiceFacade:this |
+ didReceiveQueryResult:results_info_value_]; |
+ } |
+ |
+ // Reset results variables. |
+ results_info_value_ = QueryResult(); |
+ query_results_.clear(); |
+ web_history_query_results_.clear(); |
+} |
+ |
+void HistoryServiceFacade::OnURLsDeleted( |
+ history::HistoryService* history_service, |
+ bool all_history, |
+ bool expired, |
+ const history::URLRows& deleted_rows, |
+ const std::set<GURL>& favicon_urls) { |
+ if (all_history || DeletionsDiffer(deleted_rows, urls_to_be_deleted_)) { |
+ if ([delegate_ |
+ respondsToSelector: |
+ @selector(historyServiceFacadeDidObserveHistoryDeletion:)]) { |
+ [delegate_ historyServiceFacadeDidObserveHistoryDeletion:this]; |
+ } |
+ } |
+} |