Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(231)

Side by Side Diff: chrome/browser/ui/history_ui_service.cc

Issue 2450453002: Refactor BrowsingHistoryHandler, create BrowsingHistoryService (Closed)
Patch Set: Created 4 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 #include "chrome/browser/ui/history_ui_service.h"
6
7 #include <stddef.h>
8
9 #include <algorithm>
10 #include <map>
11 #include <set>
12 #include <utility>
13
14 #include "base/bind.h"
15 #include "base/bind_helpers.h"
16 #include "base/logging.h"
17 #include "base/metrics/histogram_macros.h"
18 #include "base/strings/string16.h"
19 #include "base/strings/string_number_conversions.h"
20 #include "base/time/time.h"
21 #include "base/values.h"
22 #include "chrome/browser/banners/app_banner_settings_helper.h"
23 #include "chrome/browser/history/history_service_factory.h"
24 #include "chrome/browser/history/history_utils.h"
25 #include "chrome/browser/history/web_history_service_factory.h"
26 #include "chrome/browser/profiles/profile.h"
27 #include "chrome/browser/sync/profile_sync_service_factory.h"
28 #include "chrome/common/pref_names.h"
29 #include "components/browser_sync/profile_sync_service.h"
30 #include "components/browsing_data/core/history_notice_utils.h"
31 #include "components/history/core/browser/history_service.h"
32 #include "components/history/core/browser/history_types.h"
33 #include "components/keyed_service/core/service_access_type.h"
34 #include "components/prefs/pref_service.h"
35 #include "components/sync/driver/sync_service_observer.h"
36
37 #if defined(ENABLE_EXTENSIONS)
38 #include "chrome/browser/extensions/activity_log/activity_log.h"
39 #endif
40
41 // The amount of time to wait for a response from the WebHistoryService.
42 static const int kWebHistoryTimeoutSeconds = 3;
43
44 namespace {
45
46 // Buckets for UMA histograms.
47 enum WebHistoryQueryBuckets {
48 WEB_HISTORY_QUERY_FAILED = 0,
49 WEB_HISTORY_QUERY_SUCCEEDED,
50 WEB_HISTORY_QUERY_TIMED_OUT,
51 NUM_WEB_HISTORY_QUERY_BUCKETS
52 };
53
54 // Returns true if |entry| represents a local visit that had no corresponding
55 // visit on the server.
56 bool IsLocalOnlyResult(const HistoryUiService::HistoryEntry& entry) {
57 return entry.entry_type == HistoryUiService::HistoryEntry::LOCAL_ENTRY;
58 }
59
60 void RecordMetricsForNoticeAboutOtherFormsOfBrowsingHistory(bool shown) {
61 UMA_HISTOGRAM_BOOLEAN(
62 "History.ShownHeaderAboutOtherFormsOfBrowsingHistory",
63 shown);
64 }
65
66 } // namespace
67
68 HistoryUiService::HistoryEntry::HistoryEntry(
69 HistoryUiService::HistoryEntry::EntryType entry_type,
70 const GURL& url, const base::string16& title, base::Time time,
71 const std::string& client_id, bool is_search_result,
72 const base::string16& snippet, bool blocked_visit) {
73 this->entry_type = entry_type;
74 this->url = url;
75 this->title = title;
76 this->time = time;
77 this->client_id = client_id;
78 all_timestamps.insert(time.ToInternalValue());
79 this->is_search_result = is_search_result;
80 this->snippet = snippet;
81 this->blocked_visit = blocked_visit;
82 }
83
84 HistoryUiService::HistoryEntry::HistoryEntry()
85 : entry_type(EMPTY_ENTRY), is_search_result(false), blocked_visit(false) {
86 }
87
88 HistoryUiService::HistoryEntry::HistoryEntry(const HistoryEntry& other) =
89 default;
90
91 HistoryUiService::HistoryEntry::~HistoryEntry() {
92 }
93
94 bool HistoryUiService::HistoryEntry::SortByTimeDescending(
95 const HistoryUiService::HistoryEntry& entry1,
96 const HistoryUiService::HistoryEntry& entry2) {
97 return entry1.time > entry2.time;
98 }
99
100 HistoryUiService::QueryResultsInfo::QueryResultsInfo()
101 : reached_beginning(false),
102 has_synced_results(false) {}
103
104 HistoryUiService::QueryResultsInfo::~QueryResultsInfo() {}
105
106 HistoryUiService::HistoryUiService(
107 Profile* profile,
108 HistoryUiService::HistoryUiServiceHandler* handler)
109 : has_pending_delete_request_(false),
110 history_service_observer_(this),
111 web_history_service_observer_(this),
112 sync_service_observer_(this),
113 has_synced_results_(false),
114 has_other_forms_of_browsing_history_(false),
115 profile_(profile),
116 handler_(handler),
117 weak_factory_(this) {
118 // Get notifications when history is cleared.
119 history::HistoryService* local_history = HistoryServiceFactory::GetForProfile(
Theresa 2016/10/24 15:40:29 This logic used to be in BrowsingHistoryHandler::R
120 profile_, ServiceAccessType::EXPLICIT_ACCESS);
121 if (local_history)
122 history_service_observer_.Add(local_history);
123
124 // Get notifications when web history is deleted.
125 history::WebHistoryService* web_history =
126 WebHistoryServiceFactory::GetForProfile(profile_);
127 if (web_history) {
128 web_history_service_observer_.Add(web_history);
129 } else {
130 // If |web_history| is not available, it means that the history sync is
131 // disabled. Observe |sync_service| so that we can attach the listener
132 // in case it gets enabled later.
133 browser_sync::ProfileSyncService* sync_service =
134 ProfileSyncServiceFactory::GetForProfile(profile_);
135 if (sync_service)
136 sync_service_observer_.Add(sync_service);
137 }
138 }
139
140 HistoryUiService::~HistoryUiService() {
141 query_task_tracker_.TryCancelAll();
142 web_history_request_.reset();
143 }
144
145 void HistoryUiService::OnStateChanged() {
146 // If the history sync was enabled, start observing WebHistoryService.
147 // This method should not be called after we already added the observer.
148 history::WebHistoryService* web_history =
149 WebHistoryServiceFactory::GetForProfile(profile_);
150 if (web_history) {
151 DCHECK(!web_history_service_observer_.IsObserving(web_history));
152 web_history_service_observer_.Add(web_history);
153 sync_service_observer_.RemoveAll();
154 }
155 }
156
157 void HistoryUiService::WebHistoryTimeout() {
158 has_synced_results_ = false;
159 // TODO(dubroy): Communicate the failure to the front end.
160 if (!query_task_tracker_.HasTrackedTasks())
161 ReturnResultsToHandler();
162
163 UMA_HISTOGRAM_ENUMERATION(
164 "WebHistory.QueryCompletion",
165 WEB_HISTORY_QUERY_TIMED_OUT, NUM_WEB_HISTORY_QUERY_BUCKETS);
166 }
167
168 void HistoryUiService::QueryHistory(
169 const base::string16& search_text,
170 const history::QueryOptions& options) {
171 // Anything in-flight is invalid.
172 query_task_tracker_.TryCancelAll();
173 web_history_request_.reset();
174
175 query_results_.clear();
176
177 history::HistoryService* hs = HistoryServiceFactory::GetForProfile(
178 profile_, ServiceAccessType::EXPLICIT_ACCESS);
179 hs->QueryHistory(search_text,
180 options,
181 base::Bind(&HistoryUiService::QueryComplete,
182 base::Unretained(this),
183 search_text,
184 options),
185 &query_task_tracker_);
186
187 history::WebHistoryService* web_history =
188 WebHistoryServiceFactory::GetForProfile(profile_);
189
190 // Set this to false until the results actually arrive.
191 query_results_info_.has_synced_results = false;
192
193 if (web_history) {
194 web_history_query_results_.clear();
195 web_history_request_ = web_history->QueryHistory(
196 search_text,
197 options,
198 base::Bind(&HistoryUiService::WebHistoryQueryComplete,
199 base::Unretained(this),
200 search_text, options,
201 base::TimeTicks::Now()));
202 // Start a timer so we know when to give up.
203 web_history_timer_.Start(
204 FROM_HERE, base::TimeDelta::FromSeconds(kWebHistoryTimeoutSeconds),
205 this, &HistoryUiService::WebHistoryTimeout);
206
207 // Test the existence of other forms of browsing history.
208 browsing_data::ShouldShowNoticeAboutOtherFormsOfBrowsingHistory(
209 ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile_),
210 web_history,
211 base::Bind(
212 &HistoryUiService::OtherFormsOfBrowsingHistoryQueryComplete,
213 weak_factory_.GetWeakPtr()));
214 } else {
215 // The notice could not have been shown, because there is no web history.
216 RecordMetricsForNoticeAboutOtherFormsOfBrowsingHistory(false);
217 has_synced_results_ = false;
218 has_other_forms_of_browsing_history_ = false;
219 }
220 }
221
222 void HistoryUiService::RemoveVisits(
223 std::vector<std::unique_ptr<HistoryUiService::HistoryEntry>>* items) {
Theresa 2016/10/24 15:40:29 This method changed to work with HistoryEntry item
224 if (delete_task_tracker_.HasTrackedTasks() ||
225 has_pending_delete_request_ ||
226 !profile_->GetPrefs()->GetBoolean(prefs::kAllowDeletingBrowserHistory)) {
227 handler_->OnRemoveVisitsFailed();
228 return;
229 }
230
231 history::HistoryService* history_service =
232 HistoryServiceFactory::GetForProfile(profile_,
233 ServiceAccessType::EXPLICIT_ACCESS);
234 history::WebHistoryService* web_history =
235 WebHistoryServiceFactory::GetForProfile(profile_);
236
237 base::Time now = base::Time::Now();
238 std::vector<history::ExpireHistoryArgs> expire_list;
239 expire_list.reserve(items->size());
240
241 DCHECK(urls_to_be_deleted_.empty());
242 for (auto it = items->begin(); it != items->end(); ++it) {
243 // In order to ensure that visits will be deleted from the server and other
244 // clients (even if they are offline), create a sync delete directive for
245 // each visit to be deleted.
246 sync_pb::HistoryDeleteDirectiveSpecifics delete_directive;
247 sync_pb::GlobalIdDirective* global_id_directive =
248 delete_directive.mutable_global_id_directive();
249 history::ExpireHistoryArgs* expire_args = NULL;
250
251 for (auto ts_iterator = it->get()->all_timestamps.begin();
252 ts_iterator != it->get()->all_timestamps.end(); ++ts_iterator) {
253 base::Time visit_time = base::Time::FromInternalValue(*ts_iterator);
254 if (!expire_args) {
255 GURL gurl(it->get()->url);
256 expire_list.resize(expire_list.size() + 1);
257 expire_args = &expire_list.back();
258 expire_args->SetTimeRangeForOneDay(visit_time);
259 expire_args->urls.insert(gurl);
260 urls_to_be_deleted_.insert(gurl);
261 }
262 // The local visit time is treated as a global ID for the visit.
263 global_id_directive->add_global_id(*ts_iterator);
264 }
265
266 // Set the start and end time in microseconds since the Unix epoch.
267 global_id_directive->set_start_time_usec(
268 (expire_args->begin_time - base::Time::UnixEpoch()).InMicroseconds());
269
270 // Delete directives shouldn't have an end time in the future.
271 // TODO(dubroy): Use sane time (crbug.com/146090) here when it's ready.
272 base::Time end_time = std::min(expire_args->end_time, now);
273
274 // -1 because end time in delete directives is inclusive.
275 global_id_directive->set_end_time_usec(
276 (end_time - base::Time::UnixEpoch()).InMicroseconds() - 1);
277
278 // TODO(dubroy): Figure out the proper way to handle an error here.
279 if (web_history)
280 history_service->ProcessLocalDeleteDirective(delete_directive);
281 }
282
283 history_service->ExpireHistory(
284 expire_list,
285 base::Bind(&HistoryUiService::RemoveComplete,
286 base::Unretained(this)),
287 &delete_task_tracker_);
288
289 if (web_history) {
290 has_pending_delete_request_ = true;
291 web_history->ExpireHistory(
292 expire_list,
293 base::Bind(&HistoryUiService::RemoveWebHistoryComplete,
294 weak_factory_.GetWeakPtr()));
295 }
296
297 #if defined(ENABLE_EXTENSIONS)
298 // If the profile has activity logging enabled also clean up any URLs from
299 // the extension activity log. The extension activity log contains URLS
300 // which websites an extension has activity on so it will indirectly
301 // contain websites that a user has visited.
302 extensions::ActivityLog* activity_log =
303 extensions::ActivityLog::GetInstance(profile_);
304 for (std::vector<history::ExpireHistoryArgs>::const_iterator it =
305 expire_list.begin(); it != expire_list.end(); ++it) {
306 activity_log->RemoveURLs(it->urls);
307 }
308 #endif
309
310 for (const history::ExpireHistoryArgs& expire_entry : expire_list)
311 AppBannerSettingsHelper::ClearHistoryForURLs(profile_, expire_entry.urls);
312 }
313
314 // static
315 void HistoryUiService::MergeDuplicateResults(
316 std::vector<HistoryUiService::HistoryEntry>* results) {
317 std::vector<HistoryUiService::HistoryEntry> new_results;
318 // Pre-reserve the size of the new vector. Since we're working with pointers
319 // later on not doing this could lead to the vector being resized and to
320 // pointers to invalid locations.
321 new_results.reserve(results->size());
322 // Maps a URL to the most recent entry on a particular day.
323 std::map<GURL, HistoryUiService::HistoryEntry*> current_day_entries;
324
325 // Keeps track of the day that |current_day_urls| is holding the URLs for,
326 // in order to handle removing per-day duplicates.
327 base::Time current_day_midnight;
328
329 std::sort(
330 results->begin(), results->end(), HistoryEntry::SortByTimeDescending);
331
332 for (std::vector<HistoryUiService::HistoryEntry>::const_iterator it =
333 results->begin(); it != results->end(); ++it) {
334 // Reset the list of found URLs when a visit from a new day is encountered.
335 if (current_day_midnight != it->time.LocalMidnight()) {
336 current_day_entries.clear();
337 current_day_midnight = it->time.LocalMidnight();
338 }
339
340 // Keep this visit if it's the first visit to this URL on the current day.
341 if (current_day_entries.count(it->url) == 0) {
342 new_results.push_back(*it);
343 current_day_entries[it->url] = &new_results.back();
344 } else {
345 // Keep track of the timestamps of all visits to the URL on the same day.
346 HistoryUiService::HistoryEntry* entry =
347 current_day_entries[it->url];
348 entry->all_timestamps.insert(
349 it->all_timestamps.begin(), it->all_timestamps.end());
350
351 if (entry->entry_type != it->entry_type) {
352 entry->entry_type =
353 HistoryUiService::HistoryEntry::COMBINED_ENTRY;
354 }
355 }
356 }
357 results->swap(new_results);
358 }
359
360 void HistoryUiService::QueryComplete(
361 const base::string16& search_text,
362 const history::QueryOptions& options,
363 history::QueryResults* results) {
364 DCHECK_EQ(0U, query_results_.size());
365 query_results_.reserve(results->size());
366
367 for (size_t i = 0; i < results->size(); ++i) {
368 history::URLResult const &page = (*results)[i];
369 // TODO(dubroy): Use sane time (crbug.com/146090) here when it's ready.
370 query_results_.push_back(
371 HistoryEntry(
372 HistoryEntry::LOCAL_ENTRY,
373 page.url(),
374 page.title(),
375 page.visit_time(),
376 std::string(),
377 !search_text.empty(),
378 page.snippet().text(),
379 page.blocked_visit()));
380 }
381
382 query_results_info_.search_text = search_text;
383 query_results_info_.reached_beginning = results->reached_beginning();
384 query_results_info_.start_time = options.begin_time;
385 if (!options.end_time.is_null()) {
386 query_results_info_.end_time =
387 options.end_time - base::TimeDelta::FromDays(1);
388 } else {
389 query_results_info_.end_time = base::Time::Now();
390 }
391
392 if (!web_history_timer_.IsRunning())
393 ReturnResultsToHandler();
394 }
395
396 void HistoryUiService::ReturnResultsToHandler() {
397 // Combine the local and remote results into |query_results_|, and remove
398 // any duplicates.
399 if (!web_history_query_results_.empty()) {
400 int local_result_count = query_results_.size();
401 query_results_.insert(query_results_.end(),
402 web_history_query_results_.begin(),
403 web_history_query_results_.end());
404 MergeDuplicateResults(&query_results_);
405
406 if (local_result_count) {
407 // In the best case, we expect that all local results are duplicated on
408 // the server. Keep track of how many are missing.
409 int missing_count = std::count_if(
410 query_results_.begin(), query_results_.end(), IsLocalOnlyResult);
411 UMA_HISTOGRAM_PERCENTAGE("WebHistory.LocalResultMissingOnServer",
412 missing_count * 100.0 / local_result_count);
413 }
414 }
415
416 handler_->OnQueryComplete(&query_results_, &query_results_info_);
417 handler_->HasOtherFormsOfBrowsingHistory(
418 has_other_forms_of_browsing_history_, has_synced_results_);
419
420 query_results_.clear();
421 web_history_query_results_.clear();
422 }
423
424 void HistoryUiService::WebHistoryQueryComplete(
425 const base::string16& search_text,
426 const history::QueryOptions& options,
427 base::TimeTicks start_time,
428 history::WebHistoryService::Request* request,
429 const base::DictionaryValue* results_value) {
430 base::TimeDelta delta = base::TimeTicks::Now() - start_time;
431 UMA_HISTOGRAM_TIMES("WebHistory.ResponseTime", delta);
432
433 // If the response came in too late, do nothing.
434 // TODO(dubroy): Maybe show a banner, and prompt the user to reload?
435 if (!web_history_timer_.IsRunning())
436 return;
437 web_history_timer_.Stop();
438
439 UMA_HISTOGRAM_ENUMERATION(
440 "WebHistory.QueryCompletion",
441 results_value ? WEB_HISTORY_QUERY_SUCCEEDED : WEB_HISTORY_QUERY_FAILED,
442 NUM_WEB_HISTORY_QUERY_BUCKETS);
443
444 DCHECK_EQ(0U, web_history_query_results_.size());
445 const base::ListValue* events = NULL;
446 if (results_value && results_value->GetList("event", &events)) {
447 web_history_query_results_.reserve(events->GetSize());
448 for (unsigned int i = 0; i < events->GetSize(); ++i) {
449 const base::DictionaryValue* event = NULL;
450 const base::DictionaryValue* result = NULL;
451 const base::ListValue* results = NULL;
452 const base::ListValue* ids = NULL;
453 base::string16 url;
454 base::string16 title;
455 base::Time visit_time;
456
457 if (!(events->GetDictionary(i, &event) &&
458 event->GetList("result", &results) &&
459 results->GetDictionary(0, &result) &&
460 result->GetString("url", &url) &&
461 result->GetList("id", &ids) &&
462 ids->GetSize() > 0)) {
463 continue;
464 }
465
466 // Ignore any URLs that should not be shown in the history page.
467 GURL gurl(url);
468 if (!CanAddURLToHistory(gurl))
469 continue;
470
471 // Title is optional, so the return value is ignored here.
472 result->GetString("title", &title);
473
474 // Extract the timestamps of all the visits to this URL.
475 // They are referred to as "IDs" by the server.
476 for (int j = 0; j < static_cast<int>(ids->GetSize()); ++j) {
477 const base::DictionaryValue* id = NULL;
478 std::string timestamp_string;
479 int64_t timestamp_usec = 0;
480
481 if (!ids->GetDictionary(j, &id) ||
482 !id->GetString("timestamp_usec", &timestamp_string) ||
483 !base::StringToInt64(timestamp_string, &timestamp_usec)) {
484 NOTREACHED() << "Unable to extract timestamp.";
485 continue;
486 }
487 // The timestamp on the server is a Unix time.
488 base::Time time = base::Time::UnixEpoch() +
489 base::TimeDelta::FromMicroseconds(timestamp_usec);
490
491 // Get the ID of the client that this visit came from.
492 std::string client_id;
493 id->GetString("client_id", &client_id);
494
495 web_history_query_results_.push_back(
496 HistoryEntry(
497 HistoryEntry::REMOTE_ENTRY,
498 gurl,
499 title,
500 time,
501 client_id,
502 !search_text.empty(),
503 base::string16(),
504 /* blocked_visit */ false));
505 }
506 }
507 }
508 has_synced_results_ = results_value != nullptr;
509 query_results_info_.has_synced_results = has_synced_results_;
510 if (!query_task_tracker_.HasTrackedTasks())
511 ReturnResultsToHandler();
512 }
513
514 void HistoryUiService::OtherFormsOfBrowsingHistoryQueryComplete(
515 bool found_other_forms_of_browsing_history) {
516 has_other_forms_of_browsing_history_ = found_other_forms_of_browsing_history;
517
518 RecordMetricsForNoticeAboutOtherFormsOfBrowsingHistory(
519 has_other_forms_of_browsing_history_);
520
521 handler_->HasOtherFormsOfBrowsingHistory(
522 has_other_forms_of_browsing_history_, has_synced_results_);
523 }
524
525 void HistoryUiService::RemoveComplete() {
526 urls_to_be_deleted_.clear();
527
528 // Notify the handler that the deletion request is complete, but only if
529 // web history delete request is not still pending.
530 if (!has_pending_delete_request_)
531 handler_->OnRemoveVisitsComplete();
532 }
533
534 void HistoryUiService::RemoveWebHistoryComplete(bool success) {
535 has_pending_delete_request_ = false;
536 // TODO(dubroy): Should we handle failure somehow? Delete directives will
537 // ensure that the visits are eventually deleted, so maybe it's not necessary.
538 if (!delete_task_tracker_.HasTrackedTasks())
539 RemoveComplete();
540 }
541
542 // Helper function for Observe that determines if there are any differences
543 // between the URLs noticed for deletion and the ones we are expecting.
544 static bool DeletionsDiffer(const history::URLRows& deleted_rows,
545 const std::set<GURL>& urls_to_be_deleted) {
546 if (deleted_rows.size() != urls_to_be_deleted.size())
547 return true;
548 for (const auto& i : deleted_rows) {
549 if (urls_to_be_deleted.find(i.url()) == urls_to_be_deleted.end())
550 return true;
551 }
552 return false;
553 }
554
555 void HistoryUiService::OnURLsDeleted(
556 history::HistoryService* history_service,
557 bool all_history,
558 bool expired,
559 const history::URLRows& deleted_rows,
560 const std::set<GURL>& favicon_urls) {
561 if (all_history || DeletionsDiffer(deleted_rows, urls_to_be_deleted_))
562 handler_->HistoryDeleted();
563 }
564
565 void HistoryUiService::OnWebHistoryDeleted() {
566 handler_->HistoryDeleted();
567 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698