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

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

Issue 1100763002: Inject CanAddURLToHistory into TopSitesImpl (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@prefs
Patch Set: Fix error introduced during rebase Created 5 years, 7 months 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 (c) 2013 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/top_sites_impl.h"
6
7 #include <algorithm>
8 #include <set>
9
10 #include "base/bind.h"
11 #include "base/bind_helpers.h"
12 #include "base/logging.h"
13 #include "base/md5.h"
14 #include "base/memory/ref_counted_memory.h"
15 #include "base/message_loop/message_loop_proxy.h"
16 #include "base/metrics/histogram.h"
17 #include "base/prefs/pref_service.h"
18 #include "base/prefs/scoped_user_pref_update.h"
19 #include "base/single_thread_task_runner.h"
20 #include "base/strings/string_util.h"
21 #include "base/strings/utf_string_conversions.h"
22 #include "base/task_runner.h"
23 #include "base/values.h"
24 #include "chrome/browser/history/history_utils.h"
25 #include "components/history/core/browser/history_backend.h"
26 #include "components/history/core/browser/history_db_task.h"
27 #include "components/history/core/browser/page_usage_data.h"
28 #include "components/history/core/browser/top_sites_cache.h"
29 #include "components/history/core/browser/url_utils.h"
30 #include "components/history/core/common/thumbnail_score.h"
31 #include "ui/base/l10n/l10n_util.h"
32 #include "ui/base/layout.h"
33 #include "ui/base/resource/resource_bundle.h"
34 #include "ui/gfx/image/image_util.h"
35
36 using base::DictionaryValue;
37
38 namespace history {
39 namespace {
40
41 void RunOrPostGetMostVisitedURLsCallback(
42 base::TaskRunner* task_runner,
43 bool include_forced_urls,
44 const TopSitesImpl::GetMostVisitedURLsCallback& callback,
45 const MostVisitedURLList& all_urls,
46 const MostVisitedURLList& nonforced_urls) {
47 const MostVisitedURLList* urls =
48 include_forced_urls ? &all_urls : &nonforced_urls;
49 if (task_runner->RunsTasksOnCurrentThread())
50 callback.Run(*urls);
51 else
52 task_runner->PostTask(FROM_HERE, base::Bind(callback, *urls));
53 }
54
55 // Compares two MostVisitedURL having a non-null |last_forced_time|.
56 bool ForcedURLComparator(const MostVisitedURL& first,
57 const MostVisitedURL& second) {
58 DCHECK(!first.last_forced_time.is_null() &&
59 !second.last_forced_time.is_null());
60 return first.last_forced_time < second.last_forced_time;
61 }
62
63 // How many non-forced top sites to store in the cache.
64 const size_t kNonForcedTopSitesNumber = 20;
65
66 // How many forced top sites to store in the cache.
67 const size_t kForcedTopSitesNumber = 20;
68
69 // Max number of temporary images we'll cache. See comment above
70 // temp_images_ for details.
71 const size_t kMaxTempTopImages = 8;
72
73 const int kDaysOfHistory = 90;
74 // Time from startup to first HistoryService query.
75 const int64 kUpdateIntervalSecs = 15;
76 // Intervals between requests to HistoryService.
77 const int64 kMinUpdateIntervalMinutes = 1;
78 const int64 kMaxUpdateIntervalMinutes = 60;
79
80 // Use 100 quality (highest quality) because we're very sensitive to
81 // artifacts for these small sized, highly detailed images.
82 const int kTopSitesImageQuality = 100;
83
84 } // namespace
85
86 // Initially, histogram is not recorded.
87 bool TopSitesImpl::histogram_recorded_ = false;
88
89 TopSitesImpl::TopSitesImpl(PrefService* pref_service,
90 HistoryService* history_service,
91 const char* blacklist_pref_name,
92 const PrepopulatedPageList& prepopulated_pages)
93 : backend_(nullptr),
94 cache_(new TopSitesCache()),
95 thread_safe_cache_(new TopSitesCache()),
96 last_num_urls_changed_(0),
97 prepopulated_pages_(prepopulated_pages),
98 pref_service_(pref_service),
99 blacklist_pref_name_(blacklist_pref_name),
100 history_service_(history_service),
101 loaded_(false),
102 history_service_observer_(this) {
103 DCHECK(pref_service_);
104 DCHECK(blacklist_pref_name_);
105 }
106
107 void TopSitesImpl::Init(
108 const base::FilePath& db_name,
109 const scoped_refptr<base::SingleThreadTaskRunner>& db_task_runner) {
110 // Create the backend here, rather than in the constructor, so that
111 // unit tests that do not need the backend can run without a problem.
112 backend_ = new TopSitesBackend(db_task_runner);
113 backend_->Init(db_name);
114 backend_->GetMostVisitedThumbnails(
115 base::Bind(&TopSitesImpl::OnGotMostVisitedThumbnails,
116 base::Unretained(this)),
117 &cancelable_task_tracker_);
118 }
119
120 bool TopSitesImpl::SetPageThumbnail(const GURL& url,
121 const gfx::Image& thumbnail,
122 const ThumbnailScore& score) {
123 DCHECK(thread_checker_.CalledOnValidThread());
124
125 if (!loaded_) {
126 // TODO(sky): I need to cache these and apply them after the load
127 // completes.
128 return false;
129 }
130
131 bool add_temp_thumbnail = false;
132 if (!IsKnownURL(url)) {
133 if (!IsNonForcedFull()) {
134 add_temp_thumbnail = true;
135 } else {
136 return false; // This URL is not known to us.
137 }
138 }
139
140 if (!CanAddURLToHistory(url))
141 return false; // It's not a real webpage.
142
143 scoped_refptr<base::RefCountedBytes> thumbnail_data;
144 if (!EncodeBitmap(thumbnail, &thumbnail_data))
145 return false;
146
147 if (add_temp_thumbnail) {
148 // Always remove the existing entry and then add it back. That way if we end
149 // up with too many temp thumbnails we'll prune the oldest first.
150 RemoveTemporaryThumbnailByURL(url);
151 AddTemporaryThumbnail(url, thumbnail_data.get(), score);
152 return true;
153 }
154
155 return SetPageThumbnailEncoded(url, thumbnail_data.get(), score);
156 }
157
158 bool TopSitesImpl::SetPageThumbnailToJPEGBytes(
159 const GURL& url,
160 const base::RefCountedMemory* memory,
161 const ThumbnailScore& score) {
162 DCHECK(thread_checker_.CalledOnValidThread());
163
164 if (!loaded_) {
165 // TODO(sky): I need to cache these and apply them after the load
166 // completes.
167 return false;
168 }
169
170 bool add_temp_thumbnail = false;
171 if (!IsKnownURL(url)) {
172 if (!IsNonForcedFull()) {
173 add_temp_thumbnail = true;
174 } else {
175 return false; // This URL is not known to us.
176 }
177 }
178
179 if (!CanAddURLToHistory(url))
180 return false; // It's not a real webpage.
181
182 if (add_temp_thumbnail) {
183 // Always remove the existing entry and then add it back. That way if we end
184 // up with too many temp thumbnails we'll prune the oldest first.
185 RemoveTemporaryThumbnailByURL(url);
186 AddTemporaryThumbnail(url, memory, score);
187 return true;
188 }
189
190 return SetPageThumbnailEncoded(url, memory, score);
191 }
192
193 // WARNING: this function may be invoked on any thread.
194 void TopSitesImpl::GetMostVisitedURLs(
195 const GetMostVisitedURLsCallback& callback,
196 bool include_forced_urls) {
197 MostVisitedURLList filtered_urls;
198 {
199 base::AutoLock lock(lock_);
200 if (!loaded_) {
201 // A request came in before we finished loading. Store the callback and
202 // we'll run it on current thread when we finish loading.
203 pending_callbacks_.push_back(
204 base::Bind(&RunOrPostGetMostVisitedURLsCallback,
205 base::MessageLoopProxy::current(),
206 include_forced_urls,
207 callback));
208 return;
209 }
210 if (include_forced_urls) {
211 filtered_urls = thread_safe_cache_->top_sites();
212 } else {
213 filtered_urls.assign(thread_safe_cache_->top_sites().begin() +
214 thread_safe_cache_->GetNumForcedURLs(),
215 thread_safe_cache_->top_sites().end());
216 }
217 }
218 callback.Run(filtered_urls);
219 }
220
221 bool TopSitesImpl::GetPageThumbnail(
222 const GURL& url,
223 bool prefix_match,
224 scoped_refptr<base::RefCountedMemory>* bytes) {
225 // WARNING: this may be invoked on any thread.
226 // Perform exact match.
227 {
228 base::AutoLock lock(lock_);
229 if (thread_safe_cache_->GetPageThumbnail(url, bytes))
230 return true;
231 }
232
233 // Resource bundle is thread safe.
234 for (const auto& prepopulated_page : prepopulated_pages_) {
235 if (url == prepopulated_page.most_visited.url) {
236 *bytes =
237 ResourceBundle::GetSharedInstance().LoadDataResourceBytesForScale(
238 prepopulated_page.thumbnail_id, ui::SCALE_FACTOR_100P);
239 return true;
240 }
241 }
242
243 if (prefix_match) {
244 // If http or https, search with |url| first, then try the other one.
245 std::vector<GURL> url_list;
246 url_list.push_back(url);
247 if (url.SchemeIsHTTPOrHTTPS())
248 url_list.push_back(ToggleHTTPAndHTTPS(url));
249
250 for (std::vector<GURL>::iterator it = url_list.begin();
251 it != url_list.end(); ++it) {
252 base::AutoLock lock(lock_);
253
254 GURL canonical_url;
255 // Test whether any stored URL is a prefix of |url|.
256 canonical_url = thread_safe_cache_->GetGeneralizedCanonicalURL(*it);
257 if (!canonical_url.is_empty() &&
258 thread_safe_cache_->GetPageThumbnail(canonical_url, bytes)) {
259 return true;
260 }
261 }
262 }
263
264 return false;
265 }
266
267 bool TopSitesImpl::GetPageThumbnailScore(const GURL& url,
268 ThumbnailScore* score) {
269 // WARNING: this may be invoked on any thread.
270 base::AutoLock lock(lock_);
271 return thread_safe_cache_->GetPageThumbnailScore(url, score);
272 }
273
274 bool TopSitesImpl::GetTemporaryPageThumbnailScore(const GURL& url,
275 ThumbnailScore* score) {
276 for (TempImages::iterator i = temp_images_.begin(); i != temp_images_.end();
277 ++i) {
278 if (i->first == url) {
279 *score = i->second.thumbnail_score;
280 return true;
281 }
282 }
283 return false;
284 }
285
286
287 // Returns the index of |url| in |urls|, or -1 if not found.
288 static int IndexOf(const MostVisitedURLList& urls, const GURL& url) {
289 for (size_t i = 0; i < urls.size(); i++) {
290 if (urls[i].url == url)
291 return i;
292 }
293 return -1;
294 }
295
296 void TopSitesImpl::SyncWithHistory() {
297 DCHECK(thread_checker_.CalledOnValidThread());
298 if (loaded_ && temp_images_.size()) {
299 // If we have temporary thumbnails it means there isn't much data, and most
300 // likely the user is first running Chrome. During this time we throttle
301 // updating from history by 30 seconds. If the user creates a new tab page
302 // during this window of time we force updating from history so that the new
303 // tab page isn't so far out of date.
304 timer_.Stop();
305 StartQueryForMostVisited();
306 }
307 }
308
309 bool TopSitesImpl::HasBlacklistedItems() const {
310 const base::DictionaryValue* blacklist =
311 pref_service_->GetDictionary(blacklist_pref_name_);
312 return blacklist && !blacklist->empty();
313 }
314
315 void TopSitesImpl::AddBlacklistedURL(const GURL& url) {
316 DCHECK(thread_checker_.CalledOnValidThread());
317
318 base::Value* dummy = base::Value::CreateNullValue();
319 {
320 DictionaryPrefUpdate update(pref_service_, blacklist_pref_name_);
321 base::DictionaryValue* blacklist = update.Get();
322 blacklist->SetWithoutPathExpansion(GetURLHash(url), dummy);
323 }
324
325 ResetThreadSafeCache();
326 NotifyTopSitesChanged();
327 }
328
329 void TopSitesImpl::RemoveBlacklistedURL(const GURL& url) {
330 DCHECK(thread_checker_.CalledOnValidThread());
331 {
332 DictionaryPrefUpdate update(pref_service_, blacklist_pref_name_);
333 base::DictionaryValue* blacklist = update.Get();
334 blacklist->RemoveWithoutPathExpansion(GetURLHash(url), nullptr);
335 }
336 ResetThreadSafeCache();
337 NotifyTopSitesChanged();
338 }
339
340 bool TopSitesImpl::IsBlacklisted(const GURL& url) {
341 DCHECK(thread_checker_.CalledOnValidThread());
342 const base::DictionaryValue* blacklist =
343 pref_service_->GetDictionary(blacklist_pref_name_);
344 return blacklist && blacklist->HasKey(GetURLHash(url));
345 }
346
347 void TopSitesImpl::ClearBlacklistedURLs() {
348 DCHECK(thread_checker_.CalledOnValidThread());
349 {
350 DictionaryPrefUpdate update(pref_service_, blacklist_pref_name_);
351 base::DictionaryValue* blacklist = update.Get();
352 blacklist->Clear();
353 }
354 ResetThreadSafeCache();
355 NotifyTopSitesChanged();
356 }
357
358 void TopSitesImpl::ShutdownOnUIThread() {
359 history_service_ = nullptr;
360 history_service_observer_.RemoveAll();
361 // Cancel all requests so that the service doesn't callback to us after we've
362 // invoked Shutdown (this could happen if we have a pending request and
363 // Shutdown is invoked).
364 cancelable_task_tracker_.TryCancelAll();
365 if (backend_)
366 backend_->Shutdown();
367 }
368
369 // static
370 void TopSitesImpl::DiffMostVisited(const MostVisitedURLList& old_list,
371 const MostVisitedURLList& new_list,
372 TopSitesDelta* delta) {
373
374 // Add all the old URLs for quick lookup. This maps URLs to the corresponding
375 // index in the input.
376 std::map<GURL, size_t> all_old_urls;
377 size_t num_old_forced = 0;
378 for (size_t i = 0; i < old_list.size(); i++) {
379 if (!old_list[i].last_forced_time.is_null())
380 num_old_forced++;
381 DCHECK(old_list[i].last_forced_time.is_null() || i < num_old_forced)
382 << "Forced URLs must all appear before non-forced URLs.";
383 all_old_urls[old_list[i].url] = i;
384 }
385
386 // Check all the URLs in the new set to see which ones are new or just moved.
387 // When we find a match in the old set, we'll reset its index to our special
388 // marker. This allows us to quickly identify the deleted ones in a later
389 // pass.
390 const size_t kAlreadyFoundMarker = static_cast<size_t>(-1);
391 int rank = -1; // Forced URLs have a rank of -1.
392 for (size_t i = 0; i < new_list.size(); i++) {
393 // Increase the rank if we're going through forced URLs. This works because
394 // non-forced URLs all come after forced URLs.
395 if (new_list[i].last_forced_time.is_null())
396 rank++;
397 DCHECK(new_list[i].last_forced_time.is_null() == (rank != -1))
398 << "Forced URLs must all appear before non-forced URLs.";
399 std::map<GURL, size_t>::iterator found = all_old_urls.find(new_list[i].url);
400 if (found == all_old_urls.end()) {
401 MostVisitedURLWithRank added;
402 added.url = new_list[i];
403 added.rank = rank;
404 delta->added.push_back(added);
405 } else {
406 DCHECK(found->second != kAlreadyFoundMarker)
407 << "Same URL appears twice in the new list.";
408 int old_rank = found->second >= num_old_forced ?
409 found->second - num_old_forced : -1;
410 if (old_rank != rank ||
411 old_list[found->second].last_forced_time !=
412 new_list[i].last_forced_time) {
413 MostVisitedURLWithRank moved;
414 moved.url = new_list[i];
415 moved.rank = rank;
416 delta->moved.push_back(moved);
417 }
418 found->second = kAlreadyFoundMarker;
419 }
420 }
421
422 // Any member without the special marker in the all_old_urls list means that
423 // there wasn't a "new" URL that mapped to it, so it was deleted.
424 for (std::map<GURL, size_t>::const_iterator i = all_old_urls.begin();
425 i != all_old_urls.end(); ++i) {
426 if (i->second != kAlreadyFoundMarker)
427 delta->deleted.push_back(old_list[i->second]);
428 }
429 }
430
431 base::CancelableTaskTracker::TaskId TopSitesImpl::StartQueryForMostVisited() {
432 DCHECK(loaded_);
433 if (!history_service_)
434 return base::CancelableTaskTracker::kBadTaskId;
435
436 return history_service_->QueryMostVisitedURLs(
437 num_results_to_request_from_history(), kDaysOfHistory,
438 base::Bind(&TopSitesImpl::OnTopSitesAvailableFromHistory,
439 base::Unretained(this)),
440 &cancelable_task_tracker_);
441 }
442
443 bool TopSitesImpl::IsKnownURL(const GURL& url) {
444 return loaded_ && cache_->IsKnownURL(url);
445 }
446
447 const std::string& TopSitesImpl::GetCanonicalURLString(const GURL& url) const {
448 return cache_->GetCanonicalURL(url).spec();
449 }
450
451 bool TopSitesImpl::IsNonForcedFull() {
452 return loaded_ && cache_->GetNumNonForcedURLs() >= kNonForcedTopSitesNumber;
453 }
454
455 bool TopSitesImpl::IsForcedFull() {
456 return loaded_ && cache_->GetNumForcedURLs() >= kForcedTopSitesNumber;
457 }
458
459 TopSitesImpl::~TopSitesImpl() {
460 }
461
462 bool TopSitesImpl::SetPageThumbnailNoDB(
463 const GURL& url,
464 const base::RefCountedMemory* thumbnail_data,
465 const ThumbnailScore& score) {
466 // This should only be invoked when we know about the url.
467 DCHECK(cache_->IsKnownURL(url));
468
469 const MostVisitedURL& most_visited =
470 cache_->top_sites()[cache_->GetURLIndex(url)];
471 Images* image = cache_->GetImage(url);
472
473 // When comparing the thumbnail scores, we need to take into account the
474 // redirect hops, which are not generated when the thumbnail is because the
475 // redirects weren't known. We fill that in here since we know the redirects.
476 ThumbnailScore new_score_with_redirects(score);
477 new_score_with_redirects.redirect_hops_from_dest =
478 GetRedirectDistanceForURL(most_visited, url);
479
480 if (!ShouldReplaceThumbnailWith(image->thumbnail_score,
481 new_score_with_redirects) &&
482 image->thumbnail.get())
483 return false; // The one we already have is better.
484
485 image->thumbnail = const_cast<base::RefCountedMemory*>(thumbnail_data);
486 image->thumbnail_score = new_score_with_redirects;
487
488 ResetThreadSafeImageCache();
489 return true;
490 }
491
492 bool TopSitesImpl::SetPageThumbnailEncoded(
493 const GURL& url,
494 const base::RefCountedMemory* thumbnail,
495 const ThumbnailScore& score) {
496 if (!SetPageThumbnailNoDB(url, thumbnail, score))
497 return false;
498
499 // Update the database.
500 if (!cache_->IsKnownURL(url))
501 return false;
502
503 size_t index = cache_->GetURLIndex(url);
504 int url_rank = index - cache_->GetNumForcedURLs();
505 const MostVisitedURL& most_visited = cache_->top_sites()[index];
506 backend_->SetPageThumbnail(most_visited,
507 url_rank < 0 ? -1 : url_rank,
508 *(cache_->GetImage(most_visited.url)));
509 return true;
510 }
511
512 // static
513 bool TopSitesImpl::EncodeBitmap(const gfx::Image& bitmap,
514 scoped_refptr<base::RefCountedBytes>* bytes) {
515 if (bitmap.IsEmpty())
516 return false;
517 *bytes = new base::RefCountedBytes();
518 std::vector<unsigned char> data;
519 if (!gfx::JPEG1xEncodedDataFromImage(bitmap, kTopSitesImageQuality, &data))
520 return false;
521
522 // As we're going to cache this data, make sure the vector is only as big as
523 // it needs to be, as JPEGCodec::Encode() over-allocates data.capacity().
524 // (In a C++0x future, we can just call shrink_to_fit() in Encode())
525 (*bytes)->data() = data;
526 return true;
527 }
528
529 void TopSitesImpl::RemoveTemporaryThumbnailByURL(const GURL& url) {
530 for (TempImages::iterator i = temp_images_.begin(); i != temp_images_.end();
531 ++i) {
532 if (i->first == url) {
533 temp_images_.erase(i);
534 return;
535 }
536 }
537 }
538
539 void TopSitesImpl::AddTemporaryThumbnail(
540 const GURL& url,
541 const base::RefCountedMemory* thumbnail,
542 const ThumbnailScore& score) {
543 if (temp_images_.size() == kMaxTempTopImages)
544 temp_images_.erase(temp_images_.begin());
545
546 TempImage image;
547 image.first = url;
548 image.second.thumbnail = const_cast<base::RefCountedMemory*>(thumbnail);
549 image.second.thumbnail_score = score;
550 temp_images_.push_back(image);
551 }
552
553 void TopSitesImpl::TimerFired() {
554 StartQueryForMostVisited();
555 }
556
557 // static
558 int TopSitesImpl::GetRedirectDistanceForURL(const MostVisitedURL& most_visited,
559 const GURL& url) {
560 for (size_t i = 0; i < most_visited.redirects.size(); i++) {
561 if (most_visited.redirects[i] == url)
562 return static_cast<int>(most_visited.redirects.size() - i - 1);
563 }
564 NOTREACHED() << "URL should always be found.";
565 return 0;
566 }
567
568 PrepopulatedPageList TopSitesImpl::GetPrepopulatedPages() {
569 return prepopulated_pages_;
570 }
571
572 bool TopSitesImpl::loaded() const {
573 return loaded_;
574 }
575
576 bool TopSitesImpl::AddForcedURL(const GURL& url, const base::Time& time) {
577 DCHECK(thread_checker_.CalledOnValidThread());
578 size_t num_forced = cache_->GetNumForcedURLs();
579 MostVisitedURLList new_list(cache_->top_sites());
580 MostVisitedURL new_url;
581
582 if (cache_->IsKnownURL(url)) {
583 size_t index = cache_->GetURLIndex(url);
584 // Do nothing if we currently have that URL as non-forced.
585 if (new_list[index].last_forced_time.is_null())
586 return false;
587
588 // Update the |last_forced_time| of the already existing URL. Delete it and
589 // reinsert it at the right location.
590 new_url = new_list[index];
591 new_list.erase(new_list.begin() + index);
592 num_forced--;
593 } else {
594 new_url.url = url;
595 new_url.redirects.push_back(url);
596 }
597 new_url.last_forced_time = time;
598 // Add forced URLs and sort. Added to the end of the list of forced URLs
599 // since this is almost always where it needs to go, unless the user's local
600 // clock is fiddled with.
601 MostVisitedURLList::iterator mid = new_list.begin() + num_forced;
602 new_list.insert(mid, new_url);
603 mid = new_list.begin() + num_forced; // Mid was invalidated.
604 std::inplace_merge(new_list.begin(), mid, mid + 1, ForcedURLComparator);
605 SetTopSites(new_list, CALL_LOCATION_FROM_OTHER_PLACES);
606 return true;
607 }
608
609 void TopSitesImpl::OnNavigationCommitted(const GURL& url) {
610 DCHECK(thread_checker_.CalledOnValidThread());
611 if (!loaded_ || IsNonForcedFull())
612 return;
613
614 if (!cache_->IsKnownURL(url) && CanAddURLToHistory(url)) {
615 // To avoid slamming history we throttle requests when the url updates. To
616 // do otherwise negatively impacts perf tests.
617 RestartQueryForTopSitesTimer(GetUpdateDelay());
618 }
619 }
620
621 bool TopSitesImpl::AddPrepopulatedPages(MostVisitedURLList* urls,
622 size_t num_forced_urls) {
623 bool added = false;
624 for (const auto& prepopulated_page : prepopulated_pages_) {
625 if (urls->size() - num_forced_urls < kNonForcedTopSitesNumber &&
626 IndexOf(*urls, prepopulated_page.most_visited.url) == -1) {
627 urls->push_back(prepopulated_page.most_visited);
628 added = true;
629 }
630 }
631 return added;
632 }
633
634 size_t TopSitesImpl::MergeCachedForcedURLs(MostVisitedURLList* new_list) {
635 DCHECK(thread_checker_.CalledOnValidThread());
636 // Add all the new URLs for quick lookup. Take that opportunity to count the
637 // number of forced URLs in |new_list|.
638 std::set<GURL> all_new_urls;
639 size_t num_forced = 0;
640 for (size_t i = 0; i < new_list->size(); ++i) {
641 for (size_t j = 0; j < (*new_list)[i].redirects.size(); j++) {
642 all_new_urls.insert((*new_list)[i].redirects[j]);
643 }
644 if (!(*new_list)[i].last_forced_time.is_null())
645 ++num_forced;
646 }
647
648 // Keep the forced URLs from |cache_| that are not found in |new_list|.
649 MostVisitedURLList filtered_forced_urls;
650 for (size_t i = 0; i < cache_->GetNumForcedURLs(); ++i) {
651 if (all_new_urls.find(cache_->top_sites()[i].url) == all_new_urls.end())
652 filtered_forced_urls.push_back(cache_->top_sites()[i]);
653 }
654 num_forced += filtered_forced_urls.size();
655
656 // Prepend forced URLs and sort in order of ascending |last_forced_time|.
657 new_list->insert(new_list->begin(), filtered_forced_urls.begin(),
658 filtered_forced_urls.end());
659 std::inplace_merge(
660 new_list->begin(), new_list->begin() + filtered_forced_urls.size(),
661 new_list->begin() + num_forced, ForcedURLComparator);
662
663 // Drop older forced URLs if the list overflows. Since forced URLs are always
664 // sort in increasing order of |last_forced_time|, drop the first ones.
665 if (num_forced > kForcedTopSitesNumber) {
666 new_list->erase(new_list->begin(),
667 new_list->begin() + (num_forced - kForcedTopSitesNumber));
668 num_forced = kForcedTopSitesNumber;
669 }
670
671 return num_forced;
672 }
673
674 void TopSitesImpl::ApplyBlacklist(const MostVisitedURLList& urls,
675 MostVisitedURLList* out) {
676 // Log the number of times ApplyBlacklist is called so we can compute the
677 // average number of blacklisted items per user.
678 const base::DictionaryValue* blacklist =
679 pref_service_->GetDictionary(blacklist_pref_name_);
680 UMA_HISTOGRAM_BOOLEAN("TopSites.NumberOfApplyBlacklist", true);
681 UMA_HISTOGRAM_COUNTS_100("TopSites.NumberOfBlacklistedItems",
682 (blacklist ? blacklist->size() : 0));
683 size_t num_non_forced_urls = 0;
684 size_t num_forced_urls = 0;
685 for (size_t i = 0; i < urls.size(); ++i) {
686 if (!IsBlacklisted(urls[i].url)) {
687 if (urls[i].last_forced_time.is_null()) {
688 // Non-forced URL.
689 if (num_non_forced_urls >= kNonForcedTopSitesNumber)
690 continue;
691 num_non_forced_urls++;
692 } else {
693 // Forced URL.
694 if (num_forced_urls >= kForcedTopSitesNumber)
695 continue;
696 num_forced_urls++;
697 }
698 out->push_back(urls[i]);
699 }
700 }
701 }
702
703 std::string TopSitesImpl::GetURLHash(const GURL& url) {
704 // We don't use canonical URLs here to be able to blacklist only one of
705 // the two 'duplicate' sites, e.g. 'gmail.com' and 'mail.google.com'.
706 return base::MD5String(url.spec());
707 }
708
709 base::TimeDelta TopSitesImpl::GetUpdateDelay() {
710 if (cache_->top_sites().size() <= prepopulated_pages_.size())
711 return base::TimeDelta::FromSeconds(30);
712
713 int64 range = kMaxUpdateIntervalMinutes - kMinUpdateIntervalMinutes;
714 int64 minutes = kMaxUpdateIntervalMinutes -
715 last_num_urls_changed_ * range / cache_->top_sites().size();
716 return base::TimeDelta::FromMinutes(minutes);
717 }
718
719 void TopSitesImpl::SetTopSites(const MostVisitedURLList& new_top_sites,
720 const CallLocation location) {
721 DCHECK(thread_checker_.CalledOnValidThread());
722
723 MostVisitedURLList top_sites(new_top_sites);
724 size_t num_forced_urls = MergeCachedForcedURLs(&top_sites);
725 AddPrepopulatedPages(&top_sites, num_forced_urls);
726
727 TopSitesDelta delta;
728 DiffMostVisited(cache_->top_sites(), top_sites, &delta);
729
730 TopSitesBackend::RecordHistogram record_or_not =
731 TopSitesBackend::RECORD_HISTOGRAM_NO;
732
733 // Record the delta size into a histogram if this function is called from
734 // function OnGotMostVisitedThumbnails and no histogram value has been
735 // recorded before.
736 if (location == CALL_LOCATION_FROM_ON_GOT_MOST_VISITED_THUMBNAILS &&
737 !histogram_recorded_) {
738 size_t delta_size =
739 delta.deleted.size() + delta.added.size() + delta.moved.size();
740 UMA_HISTOGRAM_COUNTS_100("History.FirstSetTopSitesDeltaSize", delta_size);
741 // Will be passed to TopSitesBackend to let it record the histogram too.
742 record_or_not = TopSitesBackend::RECORD_HISTOGRAM_YES;
743 // Change it to true so that the histogram will not be recorded any more.
744 histogram_recorded_ = true;
745 }
746
747 if (!delta.deleted.empty() || !delta.added.empty() || !delta.moved.empty()) {
748 backend_->UpdateTopSites(delta, record_or_not);
749 }
750
751 last_num_urls_changed_ = delta.added.size() + delta.moved.size();
752
753 // We always do the following steps (setting top sites in cache, and resetting
754 // thread safe cache ...) as this method is invoked during startup at which
755 // point the caches haven't been updated yet.
756 cache_->SetTopSites(top_sites);
757
758 // See if we have any tmp thumbnails for the new sites.
759 if (!temp_images_.empty()) {
760 for (size_t i = 0; i < top_sites.size(); ++i) {
761 const MostVisitedURL& mv = top_sites[i];
762 GURL canonical_url = cache_->GetCanonicalURL(mv.url);
763 // At the time we get the thumbnail redirects aren't known, so we have to
764 // iterate through all the images.
765 for (TempImages::iterator it = temp_images_.begin();
766 it != temp_images_.end(); ++it) {
767 if (canonical_url == cache_->GetCanonicalURL(it->first)) {
768 SetPageThumbnailEncoded(
769 mv.url, it->second.thumbnail.get(), it->second.thumbnail_score);
770 temp_images_.erase(it);
771 break;
772 }
773 }
774 }
775 }
776
777 if (top_sites.size() - num_forced_urls >= kNonForcedTopSitesNumber)
778 temp_images_.clear();
779
780 ResetThreadSafeCache();
781 ResetThreadSafeImageCache();
782 NotifyTopSitesChanged();
783
784 // Restart the timer that queries history for top sites. This is done to
785 // ensure we stay in sync with history.
786 RestartQueryForTopSitesTimer(GetUpdateDelay());
787 }
788
789 int TopSitesImpl::num_results_to_request_from_history() const {
790 DCHECK(thread_checker_.CalledOnValidThread());
791
792 const base::DictionaryValue* blacklist =
793 pref_service_->GetDictionary(blacklist_pref_name_);
794 return kNonForcedTopSitesNumber + (blacklist ? blacklist->size() : 0);
795 }
796
797 void TopSitesImpl::MoveStateToLoaded() {
798 DCHECK(thread_checker_.CalledOnValidThread());
799
800 MostVisitedURLList filtered_urls_all;
801 MostVisitedURLList filtered_urls_nonforced;
802 PendingCallbacks pending_callbacks;
803 {
804 base::AutoLock lock(lock_);
805
806 if (loaded_)
807 return; // Don't do anything if we're already loaded.
808 loaded_ = true;
809
810 // Now that we're loaded we can service the queued up callbacks. Copy them
811 // here and service them outside the lock.
812 if (!pending_callbacks_.empty()) {
813 // We always filter out forced URLs because callers of GetMostVisitedURLs
814 // are not interested in them.
815 filtered_urls_all = thread_safe_cache_->top_sites();
816 filtered_urls_nonforced.assign(thread_safe_cache_->top_sites().begin() +
817 thread_safe_cache_->GetNumForcedURLs(),
818 thread_safe_cache_->top_sites().end());
819 pending_callbacks.swap(pending_callbacks_);
820 }
821 }
822
823 for (size_t i = 0; i < pending_callbacks.size(); i++)
824 pending_callbacks[i].Run(filtered_urls_all, filtered_urls_nonforced);
825
826 if (history_service_)
827 history_service_observer_.Add(history_service_);
828
829 NotifyTopSitesLoaded();
830 }
831
832 void TopSitesImpl::ResetThreadSafeCache() {
833 base::AutoLock lock(lock_);
834 MostVisitedURLList cached;
835 ApplyBlacklist(cache_->top_sites(), &cached);
836 thread_safe_cache_->SetTopSites(cached);
837 }
838
839 void TopSitesImpl::ResetThreadSafeImageCache() {
840 base::AutoLock lock(lock_);
841 thread_safe_cache_->SetThumbnails(cache_->images());
842 }
843
844 void TopSitesImpl::RestartQueryForTopSitesTimer(base::TimeDelta delta) {
845 if (timer_.IsRunning() && ((timer_start_time_ + timer_.GetCurrentDelay()) <
846 (base::TimeTicks::Now() + delta))) {
847 return;
848 }
849
850 timer_start_time_ = base::TimeTicks::Now();
851 timer_.Stop();
852 timer_.Start(FROM_HERE, delta, this, &TopSitesImpl::TimerFired);
853 }
854
855 void TopSitesImpl::OnGotMostVisitedThumbnails(
856 const scoped_refptr<MostVisitedThumbnails>& thumbnails) {
857 DCHECK(thread_checker_.CalledOnValidThread());
858
859 // Set the top sites directly in the cache so that SetTopSites diffs
860 // correctly.
861 cache_->SetTopSites(thumbnails->most_visited);
862 SetTopSites(thumbnails->most_visited,
863 CALL_LOCATION_FROM_ON_GOT_MOST_VISITED_THUMBNAILS);
864 cache_->SetThumbnails(thumbnails->url_to_images_map);
865
866 ResetThreadSafeImageCache();
867
868 MoveStateToLoaded();
869
870 // Start a timer that refreshes top sites from history.
871 RestartQueryForTopSitesTimer(
872 base::TimeDelta::FromSeconds(kUpdateIntervalSecs));
873 }
874
875 void TopSitesImpl::OnTopSitesAvailableFromHistory(
876 const MostVisitedURLList* pages) {
877 DCHECK(pages);
878 SetTopSites(*pages, CALL_LOCATION_FROM_OTHER_PLACES);
879 }
880
881 void TopSitesImpl::OnURLsDeleted(HistoryService* history_service,
882 bool all_history,
883 bool expired,
884 const URLRows& deleted_rows,
885 const std::set<GURL>& favicon_urls) {
886 if (!loaded_)
887 return;
888
889 if (all_history) {
890 SetTopSites(MostVisitedURLList(), CALL_LOCATION_FROM_OTHER_PLACES);
891 backend_->ResetDatabase();
892 } else {
893 std::set<size_t> indices_to_delete; // Indices into top_sites_.
894 for (const auto& row : deleted_rows) {
895 if (cache_->IsKnownURL(row.url()))
896 indices_to_delete.insert(cache_->GetURLIndex(row.url()));
897 }
898
899 if (indices_to_delete.empty())
900 return;
901
902 MostVisitedURLList new_top_sites(cache_->top_sites());
903 for (std::set<size_t>::reverse_iterator i = indices_to_delete.rbegin();
904 i != indices_to_delete.rend(); i++) {
905 new_top_sites.erase(new_top_sites.begin() + *i);
906 }
907 SetTopSites(new_top_sites, CALL_LOCATION_FROM_OTHER_PLACES);
908 }
909 StartQueryForMostVisited();
910 }
911
912 } // namespace history
OLDNEW
« no previous file with comments | « chrome/browser/history/top_sites_impl.h ('k') | chrome/browser/history/top_sites_impl_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698