OLD | NEW |
(Empty) | |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #import "ios/chrome/browser/ui/history/history_service_facade.h" |
| 6 |
| 7 #include <stddef.h> |
| 8 |
| 9 #include "base/bind.h" |
| 10 #include "base/bind_helpers.h" |
| 11 #include "base/i18n/time_formatting.h" |
| 12 #include "base/mac/bind_objc_block.h" |
| 13 #include "base/memory/weak_ptr.h" |
| 14 #include "base/metrics/histogram.h" |
| 15 #include "base/strings/string16.h" |
| 16 #include "base/strings/string_number_conversions.h" |
| 17 #include "base/strings/utf_string_conversions.h" |
| 18 #include "base/time/time.h" |
| 19 #include "base/values.h" |
| 20 #include "components/browser_sync/profile_sync_service.h" |
| 21 #include "components/browsing_data/core/history_notice_utils.h" |
| 22 #include "components/history/core/browser/history_service.h" |
| 23 #include "components/history/core/browser/history_types.h" |
| 24 #include "components/history/core/browser/web_history_service.h" |
| 25 #include "components/keyed_service/core/service_access_type.h" |
| 26 #include "components/prefs/pref_service.h" |
| 27 #include "components/query_parser/snippet.h" |
| 28 #include "components/sync/protocol/history_delete_directive_specifics.pb.h" |
| 29 #include "components/sync/protocol/sync_enums.pb.h" |
| 30 #include "ios/chrome/browser/browser_state/chrome_browser_state.h" |
| 31 #include "ios/chrome/browser/history/history_service_factory.h" |
| 32 #include "ios/chrome/browser/history/history_utils.h" |
| 33 #include "ios/chrome/browser/history/web_history_service_factory.h" |
| 34 #include "ios/chrome/browser/pref_names.h" |
| 35 #include "ios/chrome/browser/sync/ios_chrome_profile_sync_service_factory.h" |
| 36 #include "ios/chrome/browser/ui/history/history_entry.h" |
| 37 #include "ios/chrome/browser/ui/history/history_service_facade_delegate.h" |
| 38 #include "ios/chrome/browser/ui/history/history_util.h" |
| 39 |
| 40 // The amount of time to wait for a response from the WebHistoryService. |
| 41 static const int kWebHistoryTimeoutSeconds = 3; |
| 42 |
| 43 namespace { |
| 44 |
| 45 // Buckets for UMA histograms. |
| 46 enum WebHistoryQueryBuckets { |
| 47 WEB_HISTORY_QUERY_FAILED = 0, |
| 48 WEB_HISTORY_QUERY_SUCCEEDED, |
| 49 WEB_HISTORY_QUERY_TIMED_OUT, |
| 50 NUM_WEB_HISTORY_QUERY_BUCKETS |
| 51 }; |
| 52 |
| 53 // Returns true if |entry| represents a local visit that had no corresponding |
| 54 // visit on the server. |
| 55 bool IsLocalOnlyResult(const history::HistoryEntry& entry) { |
| 56 return entry.entry_type == history::HistoryEntry::LOCAL_ENTRY; |
| 57 } |
| 58 |
| 59 // Returns true if there are any differences between the URLs observed deleted |
| 60 // and the ones we are expecting to be deleted. |
| 61 static bool DeletionsDiffer(const history::URLRows& observed_deletions, |
| 62 const std::set<GURL>& expected_deletions) { |
| 63 if (observed_deletions.size() != expected_deletions.size()) |
| 64 return true; |
| 65 for (const auto& i : observed_deletions) { |
| 66 if (expected_deletions.find(i.url()) == expected_deletions.end()) |
| 67 return true; |
| 68 } |
| 69 return false; |
| 70 } |
| 71 |
| 72 } // namespace |
| 73 |
| 74 #pragma mark - QueryResult |
| 75 |
| 76 HistoryServiceFacade::QueryResult::QueryResult() |
| 77 : query(base::string16()), |
| 78 query_start_time(base::string16()), |
| 79 query_end_time(base::string16()), |
| 80 finished(false), |
| 81 has_synced_results(false), |
| 82 sync_finished(false), |
| 83 entries(std::vector<history::HistoryEntry>()) {} |
| 84 |
| 85 HistoryServiceFacade::QueryResult::QueryResult(const QueryResult& other) = |
| 86 default; |
| 87 |
| 88 HistoryServiceFacade::QueryResult::~QueryResult() {} |
| 89 |
| 90 #pragma mark - RemovedEntry |
| 91 |
| 92 HistoryServiceFacade::RemovedEntry::RemovedEntry(const GURL& url, |
| 93 const base::Time& timestamp) |
| 94 : url(url) { |
| 95 timestamps = std::vector<base::Time>(); |
| 96 timestamps.push_back(timestamp); |
| 97 } |
| 98 |
| 99 HistoryServiceFacade::RemovedEntry::RemovedEntry( |
| 100 const GURL& url, |
| 101 const std::vector<base::Time>& timestamps) |
| 102 : url(url), timestamps(timestamps) {} |
| 103 |
| 104 HistoryServiceFacade::RemovedEntry::RemovedEntry(const RemovedEntry& other) = |
| 105 default; |
| 106 |
| 107 HistoryServiceFacade::RemovedEntry::~RemovedEntry() {} |
| 108 |
| 109 #pragma mark - HistoryServiceFacade |
| 110 |
| 111 HistoryServiceFacade::HistoryServiceFacade( |
| 112 ios::ChromeBrowserState* browser_state, |
| 113 id<HistoryServiceFacadeDelegate> delegate) |
| 114 : has_pending_delete_request_(false), |
| 115 history_service_observer_(this), |
| 116 browser_state_(browser_state), |
| 117 delegate_(delegate), |
| 118 weak_factory_(this) { |
| 119 // Register as observer of HistoryService. |
| 120 history::HistoryService* history_service = |
| 121 ios::HistoryServiceFactory::GetForBrowserState( |
| 122 browser_state, ServiceAccessType::EXPLICIT_ACCESS); |
| 123 if (history_service) |
| 124 history_service_observer_.Add(history_service); |
| 125 } |
| 126 |
| 127 HistoryServiceFacade::~HistoryServiceFacade() { |
| 128 query_task_tracker_.TryCancelAll(); |
| 129 web_history_request_.reset(); |
| 130 delegate_.reset(); |
| 131 } |
| 132 |
| 133 void HistoryServiceFacade::QueryHistory(const base::string16& search_text, |
| 134 const history::QueryOptions& options) { |
| 135 // Anything in-flight is invalid. |
| 136 query_task_tracker_.TryCancelAll(); |
| 137 web_history_request_.reset(); |
| 138 |
| 139 // Reset results. |
| 140 query_results_.clear(); |
| 141 results_info_value_ = QueryResult(); |
| 142 |
| 143 // Query local history. |
| 144 history::HistoryService* history_service = |
| 145 ios::HistoryServiceFactory::GetForBrowserState( |
| 146 browser_state_, ServiceAccessType::EXPLICIT_ACCESS); |
| 147 if (history_service) { |
| 148 history_service->QueryHistory( |
| 149 search_text, options, |
| 150 base::Bind(&HistoryServiceFacade::QueryComplete, base::Unretained(this), |
| 151 search_text, options), |
| 152 &query_task_tracker_); |
| 153 } |
| 154 |
| 155 // Query synced history. |
| 156 history::WebHistoryService* web_history = |
| 157 ios::WebHistoryServiceFactory::GetForBrowserState(browser_state_); |
| 158 if (web_history) { |
| 159 web_history_query_results_.clear(); |
| 160 web_history_request_ = web_history->QueryHistory( |
| 161 search_text, options, |
| 162 base::Bind(&HistoryServiceFacade::WebHistoryQueryComplete, |
| 163 base::Unretained(this), search_text, options, |
| 164 base::TimeTicks::Now())); |
| 165 // Start a timer so we know when to give up. |
| 166 web_history_timer_.Start( |
| 167 FROM_HERE, base::TimeDelta::FromSeconds(kWebHistoryTimeoutSeconds), |
| 168 this, &HistoryServiceFacade::WebHistoryTimeout); |
| 169 } |
| 170 } |
| 171 |
| 172 void HistoryServiceFacade::RemoveHistoryEntries( |
| 173 const std::vector<RemovedEntry>& entries) { |
| 174 // Early return if there is a deletion in progress. |
| 175 if (delete_task_tracker_.HasTrackedTasks() || has_pending_delete_request_) { |
| 176 return; |
| 177 } |
| 178 |
| 179 history::HistoryService* history_service = |
| 180 ios::HistoryServiceFactory::GetForBrowserState( |
| 181 browser_state_, ServiceAccessType::EXPLICIT_ACCESS); |
| 182 history::WebHistoryService* web_history = |
| 183 ios::WebHistoryServiceFactory::GetForBrowserState(browser_state_); |
| 184 |
| 185 base::Time now = base::Time::Now(); |
| 186 std::vector<history::ExpireHistoryArgs> expire_list; |
| 187 expire_list.reserve(entries.size()); |
| 188 |
| 189 DCHECK(urls_to_be_deleted_.empty()); |
| 190 for (const RemovedEntry& entry : entries) { |
| 191 GURL url = entry.url; |
| 192 DCHECK(entry.timestamps.size() > 0); |
| 193 |
| 194 // In order to ensure that visits will be deleted from the server and other |
| 195 // clients (even if they are offline), create a sync delete directive for |
| 196 // each visit to be deleted. |
| 197 sync_pb::HistoryDeleteDirectiveSpecifics delete_directive; |
| 198 sync_pb::GlobalIdDirective* global_id_directive = |
| 199 delete_directive.mutable_global_id_directive(); |
| 200 |
| 201 expire_list.resize(expire_list.size() + 1); |
| 202 history::ExpireHistoryArgs* expire_args = &expire_list.back(); |
| 203 expire_args->SetTimeRangeForOneDay(entry.timestamps.front()); |
| 204 expire_args->urls.insert(entry.url); |
| 205 urls_to_be_deleted_.insert(entry.url); |
| 206 |
| 207 for (base::Time visit_time : entry.timestamps) { |
| 208 // The local visit time is treated as a global ID for the visit. |
| 209 global_id_directive->add_global_id(visit_time.ToInternalValue()); |
| 210 } |
| 211 |
| 212 // Set the start and end time in microseconds since the Unix epoch. |
| 213 global_id_directive->set_start_time_usec( |
| 214 (expire_args->begin_time - base::Time::UnixEpoch()).InMicroseconds()); |
| 215 |
| 216 // Delete directives shouldn't have an end time in the future. |
| 217 base::Time end_time = std::min(expire_args->end_time, now); |
| 218 |
| 219 // -1 because end time in delete directives is inclusive. |
| 220 global_id_directive->set_end_time_usec( |
| 221 (end_time - base::Time::UnixEpoch()).InMicroseconds() - 1); |
| 222 |
| 223 if (web_history) |
| 224 history_service->ProcessLocalDeleteDirective(delete_directive); |
| 225 } |
| 226 |
| 227 if (history_service) { |
| 228 history_service->ExpireHistory( |
| 229 expire_list, base::Bind(&HistoryServiceFacade::RemoveComplete, |
| 230 base::Unretained(this)), |
| 231 &delete_task_tracker_); |
| 232 } |
| 233 |
| 234 if (web_history) { |
| 235 has_pending_delete_request_ = true; |
| 236 web_history->ExpireHistory( |
| 237 expire_list, base::Bind(&HistoryServiceFacade::RemoveWebHistoryComplete, |
| 238 weak_factory_.GetWeakPtr())); |
| 239 } |
| 240 } |
| 241 |
| 242 void HistoryServiceFacade::QueryOtherFormsOfBrowsingHistory() { |
| 243 browser_sync::ProfileSyncService* sync_service = |
| 244 IOSChromeProfileSyncServiceFactory::GetForBrowserState(browser_state_); |
| 245 history::WebHistoryService* history_service = |
| 246 ios::WebHistoryServiceFactory::GetForBrowserState(browser_state_); |
| 247 browsing_data::ShouldShowNoticeAboutOtherFormsOfBrowsingHistory( |
| 248 sync_service, history_service, |
| 249 base::Bind( |
| 250 &HistoryServiceFacade::OtherFormsOfBrowsingHistoryQueryComplete, |
| 251 weak_factory_.GetWeakPtr())); |
| 252 } |
| 253 |
| 254 #pragma mark - Private methods |
| 255 |
| 256 void HistoryServiceFacade::WebHistoryTimeout() { |
| 257 // If there are no outstanding tasks, send results to front end. Would also |
| 258 // be good to communicate the failure to the front end. |
| 259 if (!query_task_tracker_.HasTrackedTasks()) |
| 260 ReturnResultsToFrontEnd(); |
| 261 |
| 262 UMA_HISTOGRAM_ENUMERATION("WebHistory.QueryCompletion", |
| 263 WEB_HISTORY_QUERY_TIMED_OUT, |
| 264 NUM_WEB_HISTORY_QUERY_BUCKETS); |
| 265 } |
| 266 |
| 267 void HistoryServiceFacade::QueryComplete(const base::string16& search_text, |
| 268 const history::QueryOptions& options, |
| 269 history::QueryResults* results) { |
| 270 DCHECK_EQ(0U, query_results_.size()); |
| 271 query_results_.reserve(results->size()); |
| 272 |
| 273 for (history::URLResult* result : *results) { |
| 274 query_results_.push_back(history::HistoryEntry( |
| 275 history::HistoryEntry::LOCAL_ENTRY, result->url(), result->title(), |
| 276 result->visit_time(), std::string(), !search_text.empty(), |
| 277 result->snippet().text(), result->blocked_visit())); |
| 278 } |
| 279 |
| 280 results_info_value_.query = search_text; |
| 281 results_info_value_.finished = results->reached_beginning(); |
| 282 results_info_value_.query_start_time = |
| 283 history::GetRelativeDateLocalized((options.begin_time)); |
| 284 |
| 285 // Add the specific dates that were searched to display them. |
| 286 // Should put today if the start is in the future. |
| 287 if (!options.end_time.is_null()) { |
| 288 results_info_value_.query_end_time = history::GetRelativeDateLocalized( |
| 289 options.end_time - base::TimeDelta::FromDays(1)); |
| 290 } else { |
| 291 results_info_value_.query_end_time = |
| 292 history::GetRelativeDateLocalized(base::Time::Now()); |
| 293 } |
| 294 if (!web_history_timer_.IsRunning()) |
| 295 ReturnResultsToFrontEnd(); |
| 296 } |
| 297 |
| 298 void HistoryServiceFacade::WebHistoryQueryComplete( |
| 299 const base::string16& search_text, |
| 300 const history::QueryOptions& options, |
| 301 base::TimeTicks start_time, |
| 302 history::WebHistoryService::Request* request, |
| 303 const base::DictionaryValue* results_value) { |
| 304 base::TimeDelta delta = base::TimeTicks::Now() - start_time; |
| 305 UMA_HISTOGRAM_TIMES("WebHistory.ResponseTime", delta); |
| 306 |
| 307 // If the response came in too late, do nothing. |
| 308 if (!web_history_timer_.IsRunning()) |
| 309 return; |
| 310 web_history_timer_.Stop(); |
| 311 |
| 312 UMA_HISTOGRAM_ENUMERATION( |
| 313 "WebHistory.QueryCompletion", |
| 314 results_value ? WEB_HISTORY_QUERY_SUCCEEDED : WEB_HISTORY_QUERY_FAILED, |
| 315 NUM_WEB_HISTORY_QUERY_BUCKETS); |
| 316 |
| 317 DCHECK_EQ(0U, web_history_query_results_.size()); |
| 318 const base::ListValue* events = NULL; |
| 319 if (results_value && results_value->GetList("event", &events)) { |
| 320 web_history_query_results_.reserve(events->GetSize()); |
| 321 for (unsigned int i = 0; i < events->GetSize(); ++i) { |
| 322 const base::DictionaryValue* event = NULL; |
| 323 const base::DictionaryValue* result = NULL; |
| 324 const base::ListValue* results = NULL; |
| 325 const base::ListValue* ids = NULL; |
| 326 base::string16 url; |
| 327 base::string16 title; |
| 328 base::Time visit_time; |
| 329 |
| 330 if (!(events->GetDictionary(i, &event) && |
| 331 event->GetList("result", &results) && |
| 332 results->GetDictionary(0, &result) && |
| 333 result->GetString("url", &url) && result->GetList("id", &ids) && |
| 334 ids->GetSize() > 0)) { |
| 335 LOG(WARNING) << "Improperly formed JSON response from history server."; |
| 336 continue; |
| 337 } |
| 338 |
| 339 // Ignore any URLs that should not be shown in the history page. |
| 340 GURL gurl(url); |
| 341 if (!ios::CanAddURLToHistory(gurl)) |
| 342 continue; |
| 343 |
| 344 // Title is optional, so the return value is ignored here. |
| 345 result->GetString("title", &title); |
| 346 |
| 347 // Extract the timestamps of all the visits to this URL. |
| 348 // They are referred to as "IDs" by the server. |
| 349 for (int j = 0; j < static_cast<int>(ids->GetSize()); ++j) { |
| 350 const base::DictionaryValue* id = NULL; |
| 351 std::string timestamp_string; |
| 352 int64_t timestamp_usec = 0; |
| 353 |
| 354 if (!ids->GetDictionary(j, &id) || |
| 355 !id->GetString("timestamp_usec", ×tamp_string) || |
| 356 !base::StringToInt64(timestamp_string, ×tamp_usec)) { |
| 357 NOTREACHED() << "Unable to extract timestamp."; |
| 358 continue; |
| 359 } |
| 360 // The timestamp on the server is a Unix time. |
| 361 base::Time time = base::Time::UnixEpoch() + |
| 362 base::TimeDelta::FromMicroseconds(timestamp_usec); |
| 363 |
| 364 // Get the ID of the client that this visit came from. |
| 365 std::string client_id; |
| 366 id->GetString("client_id", &client_id); |
| 367 |
| 368 web_history_query_results_.push_back(history::HistoryEntry( |
| 369 history::HistoryEntry::REMOTE_ENTRY, gurl, title, time, client_id, |
| 370 !search_text.empty(), base::string16(), |
| 371 /* blocked_visit */ false)); |
| 372 } |
| 373 } |
| 374 } |
| 375 |
| 376 results_info_value_.has_synced_results = results_value != NULL; |
| 377 if (results_value) { |
| 378 std::string continuation_token; |
| 379 results_value->GetString("continuation_token", &continuation_token); |
| 380 results_info_value_.sync_finished = continuation_token.empty(); |
| 381 } |
| 382 if (!query_task_tracker_.HasTrackedTasks()) |
| 383 ReturnResultsToFrontEnd(); |
| 384 } |
| 385 |
| 386 void HistoryServiceFacade::RemoveComplete() { |
| 387 urls_to_be_deleted_.clear(); |
| 388 |
| 389 // Notify the delegate that the deletion request is complete, but only if a |
| 390 // web history delete request is not still pending. |
| 391 if (has_pending_delete_request_) |
| 392 return; |
| 393 if ([delegate_ respondsToSelector: |
| 394 @selector(historyServiceFacadeDidCompleteEntryRemoval:)]) { |
| 395 [delegate_ historyServiceFacadeDidCompleteEntryRemoval:this]; |
| 396 } |
| 397 } |
| 398 |
| 399 void HistoryServiceFacade::RemoveWebHistoryComplete(bool success) { |
| 400 has_pending_delete_request_ = false; |
| 401 if (!delete_task_tracker_.HasTrackedTasks()) |
| 402 RemoveComplete(); |
| 403 } |
| 404 |
| 405 void HistoryServiceFacade::OtherFormsOfBrowsingHistoryQueryComplete( |
| 406 bool found_other_forms_of_browsing_history) { |
| 407 if ([delegate_ respondsToSelector: |
| 408 @selector(historyServiceFacade: |
| 409 shouldShowNoticeAboutOtherFormsOfBrowsingHistory:)]) { |
| 410 [delegate_ historyServiceFacade:this |
| 411 shouldShowNoticeAboutOtherFormsOfBrowsingHistory: |
| 412 found_other_forms_of_browsing_history]; |
| 413 } |
| 414 } |
| 415 |
| 416 void HistoryServiceFacade::ReturnResultsToFrontEnd() { |
| 417 // Combine the local and remote results into |query_results_|, and remove |
| 418 // any duplicates. |
| 419 if (!web_history_query_results_.empty()) { |
| 420 int local_result_count = query_results_.size(); |
| 421 query_results_.insert(query_results_.end(), |
| 422 web_history_query_results_.begin(), |
| 423 web_history_query_results_.end()); |
| 424 history::MergeDuplicateHistoryEntries(&query_results_); |
| 425 |
| 426 if (local_result_count) { |
| 427 // In the best case, we expect that all local results are duplicated on |
| 428 // the server. Keep track of how many are missing. |
| 429 int missing_count = std::count_if( |
| 430 query_results_.begin(), query_results_.end(), IsLocalOnlyResult); |
| 431 UMA_HISTOGRAM_PERCENTAGE("WebHistory.LocalResultMissingOnServer", |
| 432 missing_count * 100.0 / local_result_count); |
| 433 } |
| 434 } |
| 435 |
| 436 // Send results to delegate. Results may be empty. |
| 437 results_info_value_.entries = query_results_; |
| 438 if ([delegate_ respondsToSelector:@selector(historyServiceFacade: |
| 439 didReceiveQueryResult:)]) { |
| 440 [delegate_ historyServiceFacade:this |
| 441 didReceiveQueryResult:results_info_value_]; |
| 442 } |
| 443 |
| 444 // Reset results variables. |
| 445 results_info_value_ = QueryResult(); |
| 446 query_results_.clear(); |
| 447 web_history_query_results_.clear(); |
| 448 } |
| 449 |
| 450 void HistoryServiceFacade::OnURLsDeleted( |
| 451 history::HistoryService* history_service, |
| 452 bool all_history, |
| 453 bool expired, |
| 454 const history::URLRows& deleted_rows, |
| 455 const std::set<GURL>& favicon_urls) { |
| 456 if (all_history || DeletionsDiffer(deleted_rows, urls_to_be_deleted_)) { |
| 457 if ([delegate_ |
| 458 respondsToSelector: |
| 459 @selector(historyServiceFacadeDidObserveHistoryDeletion:)]) { |
| 460 [delegate_ historyServiceFacadeDidObserveHistoryDeletion:this]; |
| 461 } |
| 462 } |
| 463 } |
OLD | NEW |