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

Side by Side Diff: chrome/browser/history/browsing_history_service.cc

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

Powered by Google App Engine
This is Rietveld 408576698