| 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];
|
| + }
|
| + }
|
| +}
|
|
|