history_backend.cc revision 6d86b77056ed63eb6871182f42a9fd5f07550f90
15c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)// Copyright (c) 2012 The Chromium Authors. All rights reserved. 25c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be 35c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)// found in the LICENSE file. 45c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 55c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "chrome/browser/history/history_backend.h" 65c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 75c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include <algorithm> 85c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include <functional> 95c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include <list> 105c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include <map> 115c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include <set> 125c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include <vector> 135c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 145c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "base/basictypes.h" 155c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "base/bind.h" 165c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "base/compiler_specific.h" 175c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "base/files/file_enumerator.h" 185c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "base/memory/scoped_ptr.h" 195c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "base/memory/scoped_vector.h" 205c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "base/message_loop/message_loop.h" 215c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "base/metrics/histogram.h" 225c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "base/rand_util.h" 235c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "base/strings/string_util.h" 245c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "base/strings/utf_string_conversions.h" 255c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "base/time/time.h" 265c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "chrome/browser/autocomplete/history_url_provider.h" 275c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "chrome/browser/chrome_notification_types.h" 285c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "chrome/browser/favicon/favicon_changed_details.h" 29197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch#include "chrome/browser/history/download_row.h" 305d92fedcae5e801a8b224de090094f2d9df0b54aTorne (Richard Coles)#include "chrome/browser/history/history_db_task.h" 31c1847b1379d12d0e05df27436bf19a9b1bf12deaTorne (Richard Coles)#include "chrome/browser/history/history_notifications.h" 32d5428f32f5d1719f774f62e19147104ca245a3abTorne (Richard Coles)#include "chrome/browser/history/in_memory_history_backend.h" 331e202183a5dc46166763171984b285173f8585e5Torne (Richard Coles)#include "chrome/browser/history/page_usage_data.h" 3409380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#include "chrome/browser/history/top_sites.h" 353c9e4aeaee9f9b0a9a814da07bcb33319c7ea363Ben Murdoch#include "chrome/browser/history/typed_url_syncable_service.h" 36591b958dee2cf159d33a0b931e6231072eaf38d5Ben Murdoch#include "chrome/browser/history/visit_filter.h" 37d5428f32f5d1719f774f62e19147104ca245a3abTorne (Richard Coles)#include "chrome/common/chrome_constants.h" 38d5428f32f5d1719f774f62e19147104ca245a3abTorne (Richard Coles)#include "chrome/common/importer/imported_favicon_usage.h" 39591b958dee2cf159d33a0b931e6231072eaf38d5Ben Murdoch#include "chrome/common/url_constants.h" 403c9e4aeaee9f9b0a9a814da07bcb33319c7ea363Ben Murdoch#include "components/favicon_base/select_favicon_frames.h" 41591b958dee2cf159d33a0b931e6231072eaf38d5Ben Murdoch#include "components/history/core/browser/history_client.h" 42591b958dee2cf159d33a0b931e6231072eaf38d5Ben Murdoch#include "grit/chromium_strings.h" 435c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "grit/generated_resources.h" 445c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "net/base/registry_controlled_domains/registry_controlled_domain.h" 455c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "sql/error_delegate_util.h" 465c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "url/gurl.h" 475c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 48c1847b1379d12d0e05df27436bf19a9b1bf12deaTorne (Richard Coles)#if defined(OS_ANDROID) 495c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "chrome/browser/history/android/android_provider_backend.h" 50f5e4ad553afbc08dd2e729bb77e937a9a94d5827Torne (Richard Coles)#endif 517242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci 525c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)using base::Time; 537242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucciusing base::TimeDelta; 54197021e6b966cfb06891637935ef33fff06433d1Ben Murdochusing base::TimeTicks; 55521d96ec04ace82590870fb04353ec4f82bb150fTorne (Richard Coles) 565c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)/* The HistoryBackend consists of two components: 575c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 585c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) HistoryDatabase (stores past 3 months of history) 595c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) URLDatabase (stores a list of URLs) 607242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci DownloadDatabase (stores a list of downloads) 615c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) VisitDatabase (stores a list of visits for the URLs) 62d5428f32f5d1719f774f62e19147104ca245a3abTorne (Richard Coles) VisitSegmentDatabase (stores groups of URLs for the most visited view). 635c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 645c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) ExpireHistoryBackend (manages deleting things older than 3 months) 65d5428f32f5d1719f774f62e19147104ca245a3abTorne (Richard Coles)*/ 66d5428f32f5d1719f774f62e19147104ca245a3abTorne (Richard Coles) 67d5428f32f5d1719f774f62e19147104ca245a3abTorne (Richard Coles)namespace history { 68d5428f32f5d1719f774f62e19147104ca245a3abTorne (Richard Coles) 69d5428f32f5d1719f774f62e19147104ca245a3abTorne (Richard Coles)// How long we keep segment data for in days. Currently 3 months. 70d5428f32f5d1719f774f62e19147104ca245a3abTorne (Richard Coles)// This value needs to be greater or equal to 71d5428f32f5d1719f774f62e19147104ca245a3abTorne (Richard Coles)// MostVisitedModel::kMostVisitedScope but we don't want to introduce a direct 72d5428f32f5d1719f774f62e19147104ca245a3abTorne (Richard Coles)// dependency between MostVisitedModel and the history backend. 73d5428f32f5d1719f774f62e19147104ca245a3abTorne (Richard Coles)const int kSegmentDataRetention = 90; 74d5428f32f5d1719f774f62e19147104ca245a3abTorne (Richard Coles) 75d5428f32f5d1719f774f62e19147104ca245a3abTorne (Richard Coles)// How long we'll wait to do a commit, so that things are batched together. 76d5428f32f5d1719f774f62e19147104ca245a3abTorne (Richard Coles)const int kCommitIntervalSeconds = 10; 77d5428f32f5d1719f774f62e19147104ca245a3abTorne (Richard Coles) 78d5428f32f5d1719f774f62e19147104ca245a3abTorne (Richard Coles)// The amount of time before we re-fetch the favicon. 79d5428f32f5d1719f774f62e19147104ca245a3abTorne (Richard Coles)const int kFaviconRefetchDays = 7; 80d5428f32f5d1719f774f62e19147104ca245a3abTorne (Richard Coles) 81d5428f32f5d1719f774f62e19147104ca245a3abTorne (Richard Coles)// The maximum number of items we'll allow in the redirect list before 82d5428f32f5d1719f774f62e19147104ca245a3abTorne (Richard Coles)// deleting some. 83d5428f32f5d1719f774f62e19147104ca245a3abTorne (Richard Coles)const int kMaxRedirectCount = 32; 84d5428f32f5d1719f774f62e19147104ca245a3abTorne (Richard Coles) 85d5428f32f5d1719f774f62e19147104ca245a3abTorne (Richard Coles)// The number of days old a history entry can be before it is considered "old" 86d5428f32f5d1719f774f62e19147104ca245a3abTorne (Richard Coles)// and is deleted. 877242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucciconst int kExpireDaysThreshold = 90; 887242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci 897242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci#if defined(OS_ANDROID) 907242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci// The maximum number of top sites to track when recording top page visit stats. 917242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucciconst size_t kPageVisitStatsMaxTopSites = 50; 927242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci#endif 935c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 947242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci// Converts from PageUsageData to MostVisitedURL. |redirects| is a 955c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)// list of redirects for this URL. Empty list means no redirects. 965c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)MostVisitedURL MakeMostVisitedURL(const PageUsageData& page_data, 975c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) const RedirectList& redirects) { 987242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci MostVisitedURL mv; 995c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) mv.url = page_data.GetURL(); 1005c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) mv.title = page_data.GetTitle(); 10102772c6a72f1ee0b226341a4f4439970c29fc861Ben Murdoch if (redirects.empty()) { 102d5428f32f5d1719f774f62e19147104ca245a3abTorne (Richard Coles) // Redirects must contain at least the target url. 1035c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) mv.redirects.push_back(mv.url); 1045c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) } else { 1055c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) mv.redirects = redirects; 1065c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) if (mv.redirects[mv.redirects.size() - 1] != mv.url) { 107d5428f32f5d1719f774f62e19147104ca245a3abTorne (Richard Coles) // The last url must be the target url. 1085c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) mv.redirects.push_back(mv.url); 1095c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) } 1105c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) } 1115c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) return mv; 1125c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)} 113d5428f32f5d1719f774f62e19147104ca245a3abTorne (Richard Coles) 1145c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)// This task is run on a timer so that commits happen at regular intervals 1155c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)// so they are batched together. The important thing about this class is that 1165c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)// it supports canceling of the task so the reference to the backend will be 1175c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)// freed. The problem is that when history is shutting down, there is likely 1185c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)// to be one of these commits still pending and holding a reference. 1195c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)// 1205c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)// The backend can call Cancel to have this task release the reference. The 1215c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)// task will still run (if we ever get to processing the event before 1225c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)// shutdown), but it will not do anything. 1235c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)// 1245c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)// Note that this is a refcounted object and is not a task in itself. It should 12553e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)// be assigned to a RunnableMethod. 1267242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci// 12709380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)// TODO(brettw): bug 1165182: This should be replaced with a 12809380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)// base::WeakPtrFactory which will handle everything automatically (like we do 12909380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)// in ExpireHistoryBackend). 130f79f16f17ddc4f842d7b7a38603e280e94be826aTorne (Richard Coles)class CommitLaterTask : public base::RefCounted<CommitLaterTask> { 131f79f16f17ddc4f842d7b7a38603e280e94be826aTorne (Richard Coles) public: 132197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch explicit CommitLaterTask(HistoryBackend* history_backend) 133197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch : history_backend_(history_backend) { 134197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch } 135197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch 136197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch // The backend will call this function if it is being destroyed so that we 137197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch // release our reference. 138197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch void Cancel() { 139197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch history_backend_ = NULL; 140f79f16f17ddc4f842d7b7a38603e280e94be826aTorne (Richard Coles) } 141d5428f32f5d1719f774f62e19147104ca245a3abTorne (Richard Coles) 142d5428f32f5d1719f774f62e19147104ca245a3abTorne (Richard Coles) void RunCommit() { 1435c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) if (history_backend_.get()) 144926b001d589ce2f10facb93dd4b87578ea35a855Torne (Richard Coles) history_backend_->Commit(); 145926b001d589ce2f10facb93dd4b87578ea35a855Torne (Richard Coles) } 14609380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) 14709380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) private: 14809380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) friend class base::RefCounted<CommitLaterTask>; 1495c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 1507242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci ~CommitLaterTask() {} 1515c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 1525c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) scoped_refptr<HistoryBackend> history_backend_; 1535c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)}; 1547242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci 1557242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci// HistoryBackend::QueryURLResult ---------------------------------------------- 1567242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci 1577242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano TucciHistoryBackend::QueryURLResult::QueryURLResult() : success(false) { 1587242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci} 1597242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci 1607242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano TucciHistoryBackend::QueryURLResult::~QueryURLResult() { 1617242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci} 1627242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci 1637242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci// HistoryBackend -------------------------------------------------------------- 1647242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci 1657242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano TucciHistoryBackend::HistoryBackend(const base::FilePath& history_dir, 1667242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci Delegate* delegate, 1677242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci HistoryClient* history_client) 1687242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci : delegate_(delegate), 1697242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci history_dir_(history_dir), 1707242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci scheduled_kill_db_(false), 1717242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci expirer_(this, history_client), 1727242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci recent_redirects_(kMaxRedirectCount), 1737242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci backend_destroy_message_loop_(NULL), 1747242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci segment_queried_(false), 1757242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci history_client_(history_client) { 1767242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci} 1775c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 1785c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)HistoryBackend::~HistoryBackend() { 1795c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) DCHECK(!scheduled_commit_.get()) << "Deleting without cleanup"; 180f79f16f17ddc4f842d7b7a38603e280e94be826aTorne (Richard Coles) ReleaseDBTasks(); 1817242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci 1825c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#if defined(OS_ANDROID) 1835c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) // Release AndroidProviderBackend before other objects. 1847242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci android_provider_backend_.reset(); 1857242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci#endif 1867242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci 187926b001d589ce2f10facb93dd4b87578ea35a855Torne (Richard Coles) // First close the databases before optionally running the "destroy" task. 188926b001d589ce2f10facb93dd4b87578ea35a855Torne (Richard Coles) CloseAllDatabases(); 1891e202183a5dc46166763171984b285173f8585e5Torne (Richard Coles) 190e52495584422c5edb5b2944981473a2e208da323Torne (Richard Coles) if (!backend_destroy_task_.is_null()) { 191d5428f32f5d1719f774f62e19147104ca245a3abTorne (Richard Coles) // Notify an interested party (typically a unit test) that we're done. 192d5428f32f5d1719f774f62e19147104ca245a3abTorne (Richard Coles) DCHECK(backend_destroy_message_loop_); 1935c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) backend_destroy_message_loop_->PostTask(FROM_HERE, backend_destroy_task_); 1947242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci } 1957242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci 1967242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci#if defined(OS_ANDROID) 1975c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) sql::Connection::Delete(GetAndroidCacheFileName()); 198197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch#endif 1997242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci} 2007242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci 201197021e6b966cfb06891637935ef33fff06433d1Ben Murdochvoid HistoryBackend::Init(const std::string& languages, bool force_fail) { 2027242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci if (!force_fail) 203d5428f32f5d1719f774f62e19147104ca245a3abTorne (Richard Coles) InitImpl(languages); 2045c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) delegate_->DBLoaded(); 2053c9e4aeaee9f9b0a9a814da07bcb33319c7ea363Ben Murdoch typed_url_syncable_service_.reset(new TypedUrlSyncableService(this)); 2063c9e4aeaee9f9b0a9a814da07bcb33319c7ea363Ben Murdoch memory_pressure_listener_.reset(new base::MemoryPressureListener( 2073c9e4aeaee9f9b0a9a814da07bcb33319c7ea363Ben Murdoch base::Bind(&HistoryBackend::OnMemoryPressure, base::Unretained(this)))); 2085d92fedcae5e801a8b224de090094f2d9df0b54aTorne (Richard Coles)#if defined(OS_ANDROID) 2095c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) PopulateMostVisitedURLMap(); 2105c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#endif 2117242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci} 2123c9e4aeaee9f9b0a9a814da07bcb33319c7ea363Ben Murdoch 2133c9e4aeaee9f9b0a9a814da07bcb33319c7ea363Ben Murdochvoid HistoryBackend::SetOnBackendDestroyTask(base::MessageLoop* message_loop, 214f79f16f17ddc4f842d7b7a38603e280e94be826aTorne (Richard Coles) const base::Closure& task) { 215f79f16f17ddc4f842d7b7a38603e280e94be826aTorne (Richard Coles) if (!backend_destroy_task_.is_null()) 21609380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) DLOG(WARNING) << "Setting more than one destroy task, overriding"; 21709380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) backend_destroy_message_loop_ = message_loop; 21809380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) backend_destroy_task_ = task; 21909380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)} 2207242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci 2217242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tuccivoid HistoryBackend::Closing() { 2227242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci // Any scheduled commit will have a reference to us, we must make it 2237242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci // release that reference before we can be destroyed. 2247242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci CancelScheduledCommit(); 2257242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci 2267242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci // Release our reference to the delegate, this reference will be keeping the 2275c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) // history service alive. 2285c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) delegate_.reset(); 2295c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)} 2305c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 2315c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)void HistoryBackend::ClearCachedDataForContextID(ContextID context_id) { 232 tracker_.ClearCachedDataForContextID(context_id); 233} 234 235base::FilePath HistoryBackend::GetThumbnailFileName() const { 236 return history_dir_.Append(chrome::kThumbnailsFilename); 237} 238 239base::FilePath HistoryBackend::GetFaviconsFileName() const { 240 return history_dir_.Append(chrome::kFaviconsFilename); 241} 242 243base::FilePath HistoryBackend::GetArchivedFileName() const { 244 return history_dir_.Append(chrome::kArchivedHistoryFilename); 245} 246 247#if defined(OS_ANDROID) 248base::FilePath HistoryBackend::GetAndroidCacheFileName() const { 249 return history_dir_.Append(chrome::kAndroidCacheFilename); 250} 251#endif 252 253SegmentID HistoryBackend::GetLastSegmentID(VisitID from_visit) { 254 // Set is used to detect referrer loops. Should not happen, but can 255 // if the database is corrupt. 256 std::set<VisitID> visit_set; 257 VisitID visit_id = from_visit; 258 while (visit_id) { 259 VisitRow row; 260 if (!db_->GetRowForVisit(visit_id, &row)) 261 return 0; 262 if (row.segment_id) 263 return row.segment_id; // Found a visit in this change with a segment. 264 265 // Check the referrer of this visit, if any. 266 visit_id = row.referring_visit; 267 268 if (visit_set.find(visit_id) != visit_set.end()) { 269 NOTREACHED() << "Loop in referer chain, giving up"; 270 break; 271 } 272 visit_set.insert(visit_id); 273 } 274 return 0; 275} 276 277SegmentID HistoryBackend::UpdateSegments( 278 const GURL& url, 279 VisitID from_visit, 280 VisitID visit_id, 281 content::PageTransition transition_type, 282 const Time ts) { 283 if (!db_) 284 return 0; 285 286 // We only consider main frames. 287 if (!content::PageTransitionIsMainFrame(transition_type)) 288 return 0; 289 290 SegmentID segment_id = 0; 291 content::PageTransition t = 292 content::PageTransitionStripQualifier(transition_type); 293 294 // Are we at the beginning of a new segment? 295 // Note that navigating to an existing entry (with back/forward) reuses the 296 // same transition type. We are not adding it as a new segment in that case 297 // because if this was the target of a redirect, we might end up with 298 // 2 entries for the same final URL. Ex: User types google.net, gets 299 // redirected to google.com. A segment is created for google.net. On 300 // google.com users navigates through a link, then press back. That last 301 // navigation is for the entry google.com transition typed. We end up adding 302 // a segment for that one as well. So we end up with google.net and google.com 303 // in the segment table, showing as 2 entries in the NTP. 304 // Note also that we should still be updating the visit count for that segment 305 // which we are not doing now. It should be addressed when 306 // http://crbug.com/96860 is fixed. 307 if ((t == content::PAGE_TRANSITION_TYPED || 308 t == content::PAGE_TRANSITION_AUTO_BOOKMARK) && 309 (transition_type & content::PAGE_TRANSITION_FORWARD_BACK) == 0) { 310 // If so, create or get the segment. 311 std::string segment_name = db_->ComputeSegmentName(url); 312 URLID url_id = db_->GetRowForURL(url, NULL); 313 if (!url_id) 314 return 0; 315 316 if (!(segment_id = db_->GetSegmentNamed(segment_name))) { 317 if (!(segment_id = db_->CreateSegment(url_id, segment_name))) { 318 NOTREACHED(); 319 return 0; 320 } 321 } else { 322 // Note: if we update an existing segment, we update the url used to 323 // represent that segment in order to minimize stale most visited 324 // images. 325 db_->UpdateSegmentRepresentationURL(segment_id, url_id); 326 } 327 } else { 328 // Note: it is possible there is no segment ID set for this visit chain. 329 // This can happen if the initial navigation wasn't AUTO_BOOKMARK or 330 // TYPED. (For example GENERATED). In this case this visit doesn't count 331 // toward any segment. 332 if (!(segment_id = GetLastSegmentID(from_visit))) 333 return 0; 334 } 335 336 // Set the segment in the visit. 337 if (!db_->SetSegmentID(visit_id, segment_id)) { 338 NOTREACHED(); 339 return 0; 340 } 341 342 // Finally, increase the counter for that segment / day. 343 if (!db_->IncreaseSegmentVisitCount(segment_id, ts, 1)) { 344 NOTREACHED(); 345 return 0; 346 } 347 return segment_id; 348} 349 350void HistoryBackend::UpdateWithPageEndTime(ContextID context_id, 351 int32 page_id, 352 const GURL& url, 353 Time end_ts) { 354 // Will be filled with the URL ID and the visit ID of the last addition. 355 VisitID visit_id = tracker_.GetLastVisit(context_id, page_id, url); 356 UpdateVisitDuration(visit_id, end_ts); 357} 358 359void HistoryBackend::UpdateVisitDuration(VisitID visit_id, const Time end_ts) { 360 if (!db_) 361 return; 362 363 // Get the starting visit_time for visit_id. 364 VisitRow visit_row; 365 if (db_->GetRowForVisit(visit_id, &visit_row)) { 366 // We should never have a negative duration time even when time is skewed. 367 visit_row.visit_duration = end_ts > visit_row.visit_time ? 368 end_ts - visit_row.visit_time : TimeDelta::FromMicroseconds(0); 369 db_->UpdateVisitRow(visit_row); 370 } 371} 372 373void HistoryBackend::AddPage(const HistoryAddPageArgs& request) { 374 if (!db_) 375 return; 376 377 // Will be filled with the URL ID and the visit ID of the last addition. 378 std::pair<URLID, VisitID> last_ids(0, tracker_.GetLastVisit( 379 request.context_id, request.page_id, request.referrer)); 380 381 VisitID from_visit_id = last_ids.second; 382 383 // If a redirect chain is given, we expect the last item in that chain to be 384 // the final URL. 385 DCHECK(request.redirects.empty() || 386 request.redirects.back() == request.url); 387 388 // If the user is adding older history, we need to make sure our times 389 // are correct. 390 if (request.time < first_recorded_time_) 391 first_recorded_time_ = request.time; 392 393 content::PageTransition request_transition = request.transition; 394 content::PageTransition stripped_transition = 395 content::PageTransitionStripQualifier(request_transition); 396 bool is_keyword_generated = 397 (stripped_transition == content::PAGE_TRANSITION_KEYWORD_GENERATED); 398 399 // If the user is navigating to a not-previously-typed intranet hostname, 400 // change the transition to TYPED so that the omnibox will learn that this is 401 // a known host. 402 bool has_redirects = request.redirects.size() > 1; 403 if (content::PageTransitionIsMainFrame(request_transition) && 404 (stripped_transition != content::PAGE_TRANSITION_TYPED) && 405 !is_keyword_generated) { 406 const GURL& origin_url(has_redirects ? 407 request.redirects[0] : request.url); 408 if (origin_url.SchemeIs(url::kHttpScheme) || 409 origin_url.SchemeIs(url::kHttpsScheme) || 410 origin_url.SchemeIs(url::kFtpScheme)) { 411 std::string host(origin_url.host()); 412 size_t registry_length = 413 net::registry_controlled_domains::GetRegistryLength( 414 host, 415 net::registry_controlled_domains::EXCLUDE_UNKNOWN_REGISTRIES, 416 net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES); 417 if (registry_length == 0 && !db_->IsTypedHost(host)) { 418 stripped_transition = content::PAGE_TRANSITION_TYPED; 419 request_transition = 420 content::PageTransitionFromInt( 421 stripped_transition | 422 content::PageTransitionGetQualifier(request_transition)); 423 } 424 } 425 } 426 427 if (!has_redirects) { 428 // The single entry is both a chain start and end. 429 content::PageTransition t = content::PageTransitionFromInt( 430 request_transition | 431 content::PAGE_TRANSITION_CHAIN_START | 432 content::PAGE_TRANSITION_CHAIN_END); 433 434 // No redirect case (one element means just the page itself). 435 last_ids = AddPageVisit(request.url, request.time, 436 last_ids.second, t, request.visit_source); 437 438 // Update the segment for this visit. KEYWORD_GENERATED visits should not 439 // result in changing most visited, so we don't update segments (most 440 // visited db). 441 if (!is_keyword_generated) { 442 UpdateSegments(request.url, from_visit_id, last_ids.second, t, 443 request.time); 444 445 // Update the referrer's duration. 446 UpdateVisitDuration(from_visit_id, request.time); 447 } 448 } else { 449 // Redirect case. Add the redirect chain. 450 451 content::PageTransition redirect_info = 452 content::PAGE_TRANSITION_CHAIN_START; 453 454 RedirectList redirects = request.redirects; 455 if (redirects[0].SchemeIs(url::kAboutScheme)) { 456 // When the redirect source + referrer is "about" we skip it. This 457 // happens when a page opens a new frame/window to about:blank and then 458 // script sets the URL to somewhere else (used to hide the referrer). It 459 // would be nice to keep all these redirects properly but we don't ever 460 // see the initial about:blank load, so we don't know where the 461 // subsequent client redirect came from. 462 // 463 // In this case, we just don't bother hooking up the source of the 464 // redirects, so we remove it. 465 redirects.erase(redirects.begin()); 466 } else if (request_transition & content::PAGE_TRANSITION_CLIENT_REDIRECT) { 467 redirect_info = content::PAGE_TRANSITION_CLIENT_REDIRECT; 468 // The first entry in the redirect chain initiated a client redirect. 469 // We don't add this to the database since the referrer is already 470 // there, so we skip over it but change the transition type of the first 471 // transition to client redirect. 472 // 473 // The referrer is invalid when restoring a session that features an 474 // https tab that redirects to a different host or to http. In this 475 // case we don't need to reconnect the new redirect with the existing 476 // chain. 477 if (request.referrer.is_valid()) { 478 DCHECK(request.referrer == redirects[0]); 479 redirects.erase(redirects.begin()); 480 481 // If the navigation entry for this visit has replaced that for the 482 // first visit, remove the CHAIN_END marker from the first visit. This 483 // can be called a lot, for example, the page cycler, and most of the 484 // time we won't have changed anything. 485 VisitRow visit_row; 486 if (request.did_replace_entry && 487 db_->GetRowForVisit(last_ids.second, &visit_row) && 488 visit_row.transition & content::PAGE_TRANSITION_CHAIN_END) { 489 visit_row.transition = content::PageTransitionFromInt( 490 visit_row.transition & ~content::PAGE_TRANSITION_CHAIN_END); 491 db_->UpdateVisitRow(visit_row); 492 } 493 } 494 } 495 496 for (size_t redirect_index = 0; redirect_index < redirects.size(); 497 redirect_index++) { 498 content::PageTransition t = 499 content::PageTransitionFromInt(stripped_transition | redirect_info); 500 501 // If this is the last transition, add a CHAIN_END marker 502 if (redirect_index == (redirects.size() - 1)) { 503 t = content::PageTransitionFromInt( 504 t | content::PAGE_TRANSITION_CHAIN_END); 505 } 506 507 // Record all redirect visits with the same timestamp. We don't display 508 // them anyway, and if we ever decide to, we can reconstruct their order 509 // from the redirect chain. 510 last_ids = AddPageVisit(redirects[redirect_index], 511 request.time, last_ids.second, 512 t, request.visit_source); 513 if (t & content::PAGE_TRANSITION_CHAIN_START) { 514 // Update the segment for this visit. 515 UpdateSegments(redirects[redirect_index], 516 from_visit_id, last_ids.second, t, request.time); 517 518 // Update the visit_details for this visit. 519 UpdateVisitDuration(from_visit_id, request.time); 520 } 521 522 // Subsequent transitions in the redirect list must all be server 523 // redirects. 524 redirect_info = content::PAGE_TRANSITION_SERVER_REDIRECT; 525 } 526 527 // Last, save this redirect chain for later so we can set titles & favicons 528 // on the redirected pages properly. 529 recent_redirects_.Put(request.url, redirects); 530 } 531 532 // TODO(brettw) bug 1140015: Add an "add page" notification so the history 533 // views can keep in sync. 534 535 // Add the last visit to the tracker so we can get outgoing transitions. 536 // TODO(evanm): Due to http://b/1194536 we lose the referrers of a subframe 537 // navigation anyway, so last_visit_id is always zero for them. But adding 538 // them here confuses main frame history, so we skip them for now. 539 if (stripped_transition != content::PAGE_TRANSITION_AUTO_SUBFRAME && 540 stripped_transition != content::PAGE_TRANSITION_MANUAL_SUBFRAME && 541 !is_keyword_generated) { 542 tracker_.AddVisit(request.context_id, request.page_id, request.url, 543 last_ids.second); 544 } 545 546 ScheduleCommit(); 547} 548 549void HistoryBackend::InitImpl(const std::string& languages) { 550 DCHECK(!db_) << "Initializing HistoryBackend twice"; 551 // In the rare case where the db fails to initialize a dialog may get shown 552 // the blocks the caller, yet allows other messages through. For this reason 553 // we only set db_ to the created database if creation is successful. That 554 // way other methods won't do anything as db_ is still NULL. 555 556 TimeTicks beginning_time = TimeTicks::Now(); 557 558 // Compute the file names. 559 base::FilePath history_name = history_dir_.Append(chrome::kHistoryFilename); 560 base::FilePath thumbnail_name = GetFaviconsFileName(); 561 base::FilePath archived_name = GetArchivedFileName(); 562 563 // Delete the old index database files which are no longer used. 564 DeleteFTSIndexDatabases(); 565 566 // History database. 567 db_.reset(new HistoryDatabase()); 568 569 // Unretained to avoid a ref loop with db_. 570 db_->set_error_callback( 571 base::Bind(&HistoryBackend::DatabaseErrorCallback, 572 base::Unretained(this))); 573 574 sql::InitStatus status = db_->Init(history_name); 575 switch (status) { 576 case sql::INIT_OK: 577 break; 578 case sql::INIT_FAILURE: { 579 // A NULL db_ will cause all calls on this object to notice this error 580 // and to not continue. If the error callback scheduled killing the 581 // database, the task it posted has not executed yet. Try killing the 582 // database now before we close it. 583 bool kill_db = scheduled_kill_db_; 584 if (kill_db) 585 KillHistoryDatabase(); 586 UMA_HISTOGRAM_BOOLEAN("History.AttemptedToFixProfileError", kill_db); 587 delegate_->NotifyProfileError(status); 588 db_.reset(); 589 return; 590 } 591 default: 592 NOTREACHED(); 593 } 594 595 // Fill the in-memory database and send it back to the history service on the 596 // main thread. 597 { 598 scoped_ptr<InMemoryHistoryBackend> mem_backend(new InMemoryHistoryBackend); 599 if (mem_backend->Init(history_name, db_.get())) 600 delegate_->SetInMemoryBackend(mem_backend.Pass()); 601 } 602 db_->BeginExclusiveMode(); // Must be after the mem backend read the data. 603 604 // Thumbnail database. 605 // TODO(shess): "thumbnail database" these days only stores 606 // favicons. Thumbnails are stored in "top sites". Consider 607 // renaming "thumbnail" references to "favicons" or something of the 608 // sort. 609 thumbnail_db_.reset(new ThumbnailDatabase()); 610 if (thumbnail_db_->Init(thumbnail_name) != sql::INIT_OK) { 611 // Unlike the main database, we don't error out when the database is too 612 // new because this error is much less severe. Generally, this shouldn't 613 // happen since the thumbnail and main database versions should be in sync. 614 // We'll just continue without thumbnails & favicons in this case or any 615 // other error. 616 LOG(WARNING) << "Could not initialize the thumbnail database."; 617 thumbnail_db_.reset(); 618 } 619 620 // Nuke any files corresponding to the legacy Archived History Database, which 621 // previously retained expired (> 3 months old) history entries, but, in the 622 // end, was not used for much, and consequently has been removed as of M37. 623 // TODO(engedy): Remove this code after the end of 2014. 624 sql::Connection::Delete(archived_name); 625 626 // Generate the history and thumbnail database metrics only after performing 627 // any migration work. 628 if (base::RandInt(1, 100) == 50) { 629 // Only do this computation sometimes since it can be expensive. 630 db_->ComputeDatabaseMetrics(history_name); 631 if (thumbnail_db_) 632 thumbnail_db_->ComputeDatabaseMetrics(); 633 } 634 635 expirer_.SetDatabases(db_.get(), thumbnail_db_.get()); 636 637 // Open the long-running transaction. 638 db_->BeginTransaction(); 639 if (thumbnail_db_) 640 thumbnail_db_->BeginTransaction(); 641 642 // Get the first item in our database. 643 db_->GetStartDate(&first_recorded_time_); 644 645 // Start expiring old stuff. 646 expirer_.StartExpiringOldStuff(TimeDelta::FromDays(kExpireDaysThreshold)); 647 648#if defined(OS_ANDROID) 649 if (thumbnail_db_) { 650 android_provider_backend_.reset( 651 new AndroidProviderBackend(GetAndroidCacheFileName(), 652 db_.get(), 653 thumbnail_db_.get(), 654 history_client_, 655 delegate_.get())); 656 } 657#endif 658 659 HISTOGRAM_TIMES("History.InitTime", 660 TimeTicks::Now() - beginning_time); 661} 662 663void HistoryBackend::OnMemoryPressure( 664 base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level) { 665 bool trim_aggressively = memory_pressure_level == 666 base::MemoryPressureListener::MEMORY_PRESSURE_CRITICAL; 667 if (db_) 668 db_->TrimMemory(trim_aggressively); 669 if (thumbnail_db_) 670 thumbnail_db_->TrimMemory(trim_aggressively); 671} 672 673void HistoryBackend::CloseAllDatabases() { 674 if (db_) { 675 // Commit the long-running transaction. 676 db_->CommitTransaction(); 677 db_.reset(); 678 // Forget the first recorded time since the database is closed. 679 first_recorded_time_ = base::Time(); 680 } 681 if (thumbnail_db_) { 682 thumbnail_db_->CommitTransaction(); 683 thumbnail_db_.reset(); 684 } 685} 686 687std::pair<URLID, VisitID> HistoryBackend::AddPageVisit( 688 const GURL& url, 689 Time time, 690 VisitID referring_visit, 691 content::PageTransition transition, 692 VisitSource visit_source) { 693 // Top-level frame navigations are visible, everything else is hidden 694 bool new_hidden = !content::PageTransitionIsMainFrame(transition); 695 696 // NOTE: This code must stay in sync with 697 // ExpireHistoryBackend::ExpireURLsForVisits(). 698 // TODO(pkasting): http://b/1148304 We shouldn't be marking so many URLs as 699 // typed, which would eliminate the need for this code. 700 int typed_increment = 0; 701 content::PageTransition transition_type = 702 content::PageTransitionStripQualifier(transition); 703 if ((transition_type == content::PAGE_TRANSITION_TYPED && 704 !content::PageTransitionIsRedirect(transition)) || 705 transition_type == content::PAGE_TRANSITION_KEYWORD_GENERATED) 706 typed_increment = 1; 707 708#if defined(OS_ANDROID) 709 // Only count the page visit if it came from user browsing and only count it 710 // once when cycling through a redirect chain. 711 if (visit_source == SOURCE_BROWSED && 712 (transition & content::PAGE_TRANSITION_CHAIN_END) != 0) { 713 RecordTopPageVisitStats(url); 714 } 715#endif 716 717 // See if this URL is already in the DB. 718 URLRow url_info(url); 719 URLID url_id = db_->GetRowForURL(url, &url_info); 720 if (url_id) { 721 // Update of an existing row. 722 if (content::PageTransitionStripQualifier(transition) != 723 content::PAGE_TRANSITION_RELOAD) 724 url_info.set_visit_count(url_info.visit_count() + 1); 725 if (typed_increment) 726 url_info.set_typed_count(url_info.typed_count() + typed_increment); 727 if (url_info.last_visit() < time) 728 url_info.set_last_visit(time); 729 730 // Only allow un-hiding of pages, never hiding. 731 if (!new_hidden) 732 url_info.set_hidden(false); 733 734 db_->UpdateURLRow(url_id, url_info); 735 } else { 736 // Addition of a new row. 737 url_info.set_visit_count(1); 738 url_info.set_typed_count(typed_increment); 739 url_info.set_last_visit(time); 740 url_info.set_hidden(new_hidden); 741 742 url_id = db_->AddURL(url_info); 743 if (!url_id) { 744 NOTREACHED() << "Adding URL failed."; 745 return std::make_pair(0, 0); 746 } 747 url_info.id_ = url_id; 748 } 749 750 // Add the visit with the time to the database. 751 VisitRow visit_info(url_id, time, referring_visit, transition, 0); 752 VisitID visit_id = db_->AddVisit(&visit_info, visit_source); 753 NotifyVisitObservers(visit_info); 754 755 if (visit_info.visit_time < first_recorded_time_) 756 first_recorded_time_ = visit_info.visit_time; 757 758 // Broadcast a notification of the visit. 759 if (visit_id) { 760 if (typed_url_syncable_service_.get()) 761 typed_url_syncable_service_->OnUrlVisited(transition, &url_info); 762 763 scoped_ptr<URLVisitedDetails> details(new URLVisitedDetails); 764 details->transition = transition; 765 details->row = url_info; 766 details->visit_time = time; 767 // TODO(meelapshah) Disabled due to potential PageCycler regression. 768 // Re-enable this. 769 // GetMostRecentRedirectsTo(url, &details->redirects); 770 BroadcastNotifications(chrome::NOTIFICATION_HISTORY_URL_VISITED, 771 details.PassAs<HistoryDetails>()); 772 } else { 773 VLOG(0) << "Failed to build visit insert statement: " 774 << "url_id = " << url_id; 775 } 776 777 return std::make_pair(url_id, visit_id); 778} 779 780void HistoryBackend::AddPagesWithDetails(const URLRows& urls, 781 VisitSource visit_source) { 782 if (!db_) 783 return; 784 785 scoped_ptr<URLsModifiedDetails> modified(new URLsModifiedDetails); 786 for (URLRows::const_iterator i = urls.begin(); i != urls.end(); ++i) { 787 DCHECK(!i->last_visit().is_null()); 788 789 // As of M37, we no longer maintain an archived database, ignore old visits. 790 if (IsExpiredVisitTime(i->last_visit())) 791 continue; 792 793 URLRow existing_url; 794 URLID url_id = db_->GetRowForURL(i->url(), &existing_url); 795 if (!url_id) { 796 // Add the page if it doesn't exist. 797 url_id = db_->AddURL(*i); 798 if (!url_id) { 799 NOTREACHED() << "Could not add row to DB"; 800 return; 801 } 802 803 modified->changed_urls.push_back(*i); 804 modified->changed_urls.back().set_id(url_id); // i->id_ is likely 0. 805 } 806 807 // Sync code manages the visits itself. 808 if (visit_source != SOURCE_SYNCED) { 809 // Make up a visit to correspond to the last visit to the page. 810 VisitRow visit_info(url_id, i->last_visit(), 0, 811 content::PageTransitionFromInt( 812 content::PAGE_TRANSITION_LINK | 813 content::PAGE_TRANSITION_CHAIN_START | 814 content::PAGE_TRANSITION_CHAIN_END), 0); 815 if (!db_->AddVisit(&visit_info, visit_source)) { 816 NOTREACHED() << "Adding visit failed."; 817 return; 818 } 819 NotifyVisitObservers(visit_info); 820 821 if (visit_info.visit_time < first_recorded_time_) 822 first_recorded_time_ = visit_info.visit_time; 823 } 824 } 825 826 if (typed_url_syncable_service_.get()) 827 typed_url_syncable_service_->OnUrlsModified(&modified->changed_urls); 828 829 // Broadcast a notification for typed URLs that have been modified. This 830 // will be picked up by the in-memory URL database on the main thread. 831 // 832 // TODO(brettw) bug 1140015: Add an "add page" notification so the history 833 // views can keep in sync. 834 BroadcastNotifications(chrome::NOTIFICATION_HISTORY_URLS_MODIFIED, 835 modified.PassAs<HistoryDetails>()); 836 837 ScheduleCommit(); 838} 839 840bool HistoryBackend::IsExpiredVisitTime(const base::Time& time) { 841 return time < expirer_.GetCurrentExpirationTime(); 842} 843 844void HistoryBackend::SetPageTitle(const GURL& url, 845 const base::string16& title) { 846 if (!db_) 847 return; 848 849 // Search for recent redirects which should get the same title. We make a 850 // dummy list containing the exact URL visited if there are no redirects so 851 // the processing below can be the same. 852 history::RedirectList dummy_list; 853 history::RedirectList* redirects; 854 RedirectCache::iterator iter = recent_redirects_.Get(url); 855 if (iter != recent_redirects_.end()) { 856 redirects = &iter->second; 857 858 // This redirect chain should have the destination URL as the last item. 859 DCHECK(!redirects->empty()); 860 DCHECK(redirects->back() == url); 861 } else { 862 // No redirect chain stored, make up one containing the URL we want so we 863 // can use the same logic below. 864 dummy_list.push_back(url); 865 redirects = &dummy_list; 866 } 867 868 scoped_ptr<URLsModifiedDetails> details(new URLsModifiedDetails); 869 for (size_t i = 0; i < redirects->size(); i++) { 870 URLRow row; 871 URLID row_id = db_->GetRowForURL(redirects->at(i), &row); 872 if (row_id && row.title() != title) { 873 row.set_title(title); 874 db_->UpdateURLRow(row_id, row); 875 details->changed_urls.push_back(row); 876 } 877 } 878 879 // Broadcast notifications for any URLs that have changed. This will 880 // update the in-memory database and the InMemoryURLIndex. 881 if (!details->changed_urls.empty()) { 882 if (typed_url_syncable_service_.get()) 883 typed_url_syncable_service_->OnUrlsModified(&details->changed_urls); 884 BroadcastNotifications(chrome::NOTIFICATION_HISTORY_URLS_MODIFIED, 885 details.PassAs<HistoryDetails>()); 886 ScheduleCommit(); 887 } 888} 889 890void HistoryBackend::AddPageNoVisitForBookmark(const GURL& url, 891 const base::string16& title) { 892 if (!db_) 893 return; 894 895 URLRow url_info(url); 896 URLID url_id = db_->GetRowForURL(url, &url_info); 897 if (url_id) { 898 // URL is already known, nothing to do. 899 return; 900 } 901 902 if (!title.empty()) { 903 url_info.set_title(title); 904 } else { 905 url_info.set_title(base::UTF8ToUTF16(url.spec())); 906 } 907 908 url_info.set_last_visit(Time::Now()); 909 // Mark the page hidden. If the user types it in, it'll unhide. 910 url_info.set_hidden(true); 911 912 db_->AddURL(url_info); 913} 914 915void HistoryBackend::IterateURLs( 916 const scoped_refptr<visitedlink::VisitedLinkDelegate::URLEnumerator>& 917 iterator) { 918 if (db_) { 919 HistoryDatabase::URLEnumerator e; 920 if (db_->InitURLEnumeratorForEverything(&e)) { 921 URLRow info; 922 while (e.GetNextURL(&info)) { 923 iterator->OnURL(info.url()); 924 } 925 iterator->OnComplete(true); // Success. 926 return; 927 } 928 } 929 iterator->OnComplete(false); // Failure. 930} 931 932bool HistoryBackend::GetAllTypedURLs(URLRows* urls) { 933 if (db_) 934 return db_->GetAllTypedUrls(urls); 935 return false; 936} 937 938bool HistoryBackend::GetVisitsForURL(URLID id, VisitVector* visits) { 939 if (db_) 940 return db_->GetVisitsForURL(id, visits); 941 return false; 942} 943 944bool HistoryBackend::GetMostRecentVisitsForURL(URLID id, 945 int max_visits, 946 VisitVector* visits) { 947 if (db_) 948 return db_->GetMostRecentVisitsForURL(id, max_visits, visits); 949 return false; 950} 951 952bool HistoryBackend::UpdateURL(URLID id, const history::URLRow& url) { 953 if (db_) 954 return db_->UpdateURLRow(id, url); 955 return false; 956} 957 958bool HistoryBackend::AddVisits(const GURL& url, 959 const std::vector<VisitInfo>& visits, 960 VisitSource visit_source) { 961 if (db_) { 962 for (std::vector<VisitInfo>::const_iterator visit = visits.begin(); 963 visit != visits.end(); ++visit) { 964 if (!AddPageVisit( 965 url, visit->first, 0, visit->second, visit_source).first) { 966 return false; 967 } 968 } 969 ScheduleCommit(); 970 return true; 971 } 972 return false; 973} 974 975bool HistoryBackend::RemoveVisits(const VisitVector& visits) { 976 if (!db_) 977 return false; 978 979 expirer_.ExpireVisits(visits); 980 ScheduleCommit(); 981 return true; 982} 983 984bool HistoryBackend::GetVisitsSource(const VisitVector& visits, 985 VisitSourceMap* sources) { 986 if (!db_) 987 return false; 988 989 db_->GetVisitsSource(visits, sources); 990 return true; 991} 992 993bool HistoryBackend::GetURL(const GURL& url, history::URLRow* url_row) { 994 if (db_) 995 return db_->GetRowForURL(url, url_row) != 0; 996 return false; 997} 998 999HistoryBackend::QueryURLResult HistoryBackend::QueryURL(const GURL& url, 1000 bool want_visits) { 1001 QueryURLResult result; 1002 result.success = db_ && db_->GetRowForURL(url, &result.row); 1003 // Optionally query the visits. 1004 if (result.success && want_visits) 1005 db_->GetVisitsForURL(result.row.id(), &result.visits); 1006 return result; 1007} 1008 1009TypedUrlSyncableService* HistoryBackend::GetTypedUrlSyncableService() const { 1010 return typed_url_syncable_service_.get(); 1011} 1012 1013// Segment usage --------------------------------------------------------------- 1014 1015void HistoryBackend::DeleteOldSegmentData() { 1016 if (db_) 1017 db_->DeleteSegmentData(Time::Now() - 1018 TimeDelta::FromDays(kSegmentDataRetention)); 1019} 1020 1021void HistoryBackend::QuerySegmentUsage( 1022 scoped_refptr<QuerySegmentUsageRequest> request, 1023 const Time from_time, 1024 int max_result_count) { 1025 if (request->canceled()) 1026 return; 1027 1028 if (db_) { 1029 db_->QuerySegmentUsage(from_time, max_result_count, &request->value.get()); 1030 1031 // If this is the first time we query segments, invoke 1032 // DeleteOldSegmentData asynchronously. We do this to cleanup old 1033 // entries. 1034 if (!segment_queried_) { 1035 segment_queried_ = true; 1036 base::MessageLoop::current()->PostTask( 1037 FROM_HERE, 1038 base::Bind(&HistoryBackend::DeleteOldSegmentData, this)); 1039 } 1040 } 1041 request->ForwardResult(request->handle(), &request->value.get()); 1042} 1043 1044// Keyword visits -------------------------------------------------------------- 1045 1046void HistoryBackend::SetKeywordSearchTermsForURL(const GURL& url, 1047 TemplateURLID keyword_id, 1048 const base::string16& term) { 1049 if (!db_) 1050 return; 1051 1052 // Get the ID for this URL. 1053 URLRow row; 1054 if (!db_->GetRowForURL(url, &row)) { 1055 // There is a small possibility the url was deleted before the keyword 1056 // was added. Ignore the request. 1057 return; 1058 } 1059 1060 db_->SetKeywordSearchTermsForURL(row.id(), keyword_id, term); 1061 1062 BroadcastNotifications( 1063 chrome::NOTIFICATION_HISTORY_KEYWORD_SEARCH_TERM_UPDATED, 1064 scoped_ptr<HistoryDetails>( 1065 new KeywordSearchUpdatedDetails(row, keyword_id, term))); 1066 ScheduleCommit(); 1067} 1068 1069void HistoryBackend::DeleteAllSearchTermsForKeyword( 1070 TemplateURLID keyword_id) { 1071 if (!db_) 1072 return; 1073 1074 db_->DeleteAllSearchTermsForKeyword(keyword_id); 1075 ScheduleCommit(); 1076} 1077 1078void HistoryBackend::GetMostRecentKeywordSearchTerms( 1079 scoped_refptr<GetMostRecentKeywordSearchTermsRequest> request, 1080 TemplateURLID keyword_id, 1081 const base::string16& prefix, 1082 int max_count) { 1083 if (request->canceled()) 1084 return; 1085 1086 if (db_) { 1087 db_->GetMostRecentKeywordSearchTerms(keyword_id, prefix, max_count, 1088 &(request->value)); 1089 } 1090 request->ForwardResult(request->handle(), &request->value); 1091} 1092 1093void HistoryBackend::DeleteKeywordSearchTermForURL(const GURL& url) { 1094 if (!db_) 1095 return; 1096 1097 URLID url_id = db_->GetRowForURL(url, NULL); 1098 if (!url_id) 1099 return; 1100 db_->DeleteKeywordSearchTermForURL(url_id); 1101 1102 BroadcastNotifications( 1103 chrome::NOTIFICATION_HISTORY_KEYWORD_SEARCH_TERM_DELETED, 1104 scoped_ptr<HistoryDetails>(new KeywordSearchDeletedDetails(url_id))); 1105 ScheduleCommit(); 1106} 1107 1108void HistoryBackend::DeleteMatchingURLsForKeyword(TemplateURLID keyword_id, 1109 const base::string16& term) { 1110 if (!db_) 1111 return; 1112 1113 std::vector<KeywordSearchTermRow> rows; 1114 if (db_->GetKeywordSearchTermRows(term, &rows)) { 1115 std::vector<GURL> items_to_delete; 1116 URLRow row; 1117 for (std::vector<KeywordSearchTermRow>::iterator it = rows.begin(); 1118 it != rows.end(); ++it) { 1119 if ((it->keyword_id == keyword_id) && db_->GetURLRow(it->url_id, &row)) 1120 items_to_delete.push_back(row.url()); 1121 } 1122 DeleteURLs(items_to_delete); 1123 } 1124} 1125 1126// Downloads ------------------------------------------------------------------- 1127 1128uint32 HistoryBackend::GetNextDownloadId() { 1129 return db_ ? db_->GetNextDownloadId() : content::DownloadItem::kInvalidId; 1130} 1131 1132// Get all the download entries from the database. 1133void HistoryBackend::QueryDownloads(std::vector<DownloadRow>* rows) { 1134 if (db_) 1135 db_->QueryDownloads(rows); 1136} 1137 1138// Update a particular download entry. 1139void HistoryBackend::UpdateDownload(const history::DownloadRow& data) { 1140 if (!db_) 1141 return; 1142 db_->UpdateDownload(data); 1143 ScheduleCommit(); 1144} 1145 1146bool HistoryBackend::CreateDownload(const history::DownloadRow& history_info) { 1147 if (!db_) 1148 return false; 1149 bool success = db_->CreateDownload(history_info); 1150 ScheduleCommit(); 1151 return success; 1152} 1153 1154void HistoryBackend::RemoveDownloads(const std::set<uint32>& ids) { 1155 if (!db_) 1156 return; 1157 size_t downloads_count_before = db_->CountDownloads(); 1158 base::TimeTicks started_removing = base::TimeTicks::Now(); 1159 // HistoryBackend uses a long-running Transaction that is committed 1160 // periodically, so this loop doesn't actually hit the disk too hard. 1161 for (std::set<uint32>::const_iterator it = ids.begin(); 1162 it != ids.end(); ++it) { 1163 db_->RemoveDownload(*it); 1164 } 1165 ScheduleCommit(); 1166 base::TimeTicks finished_removing = base::TimeTicks::Now(); 1167 size_t downloads_count_after = db_->CountDownloads(); 1168 1169 DCHECK_LE(downloads_count_after, downloads_count_before); 1170 if (downloads_count_after > downloads_count_before) 1171 return; 1172 size_t num_downloads_deleted = downloads_count_before - downloads_count_after; 1173 UMA_HISTOGRAM_COUNTS("Download.DatabaseRemoveDownloadsCount", 1174 num_downloads_deleted); 1175 base::TimeDelta micros = (1000 * (finished_removing - started_removing)); 1176 UMA_HISTOGRAM_TIMES("Download.DatabaseRemoveDownloadsTime", micros); 1177 if (num_downloads_deleted > 0) { 1178 UMA_HISTOGRAM_TIMES("Download.DatabaseRemoveDownloadsTimePerRecord", 1179 (1000 * micros) / num_downloads_deleted); 1180 } 1181 DCHECK_GE(ids.size(), num_downloads_deleted); 1182 if (ids.size() < num_downloads_deleted) 1183 return; 1184 UMA_HISTOGRAM_COUNTS("Download.DatabaseRemoveDownloadsCountNotRemoved", 1185 ids.size() - num_downloads_deleted); 1186} 1187 1188void HistoryBackend::QueryHistory(scoped_refptr<QueryHistoryRequest> request, 1189 const base::string16& text_query, 1190 const QueryOptions& options) { 1191 if (request->canceled()) 1192 return; 1193 1194 TimeTicks beginning_time = TimeTicks::Now(); 1195 1196 if (db_) { 1197 if (text_query.empty()) { 1198 // Basic history query for the main database. 1199 QueryHistoryBasic(options, &request->value); 1200 } else { 1201 // Text history query. 1202 QueryHistoryText(text_query, options, &request->value); 1203 } 1204 } 1205 1206 request->ForwardResult(request->handle(), &request->value); 1207 1208 UMA_HISTOGRAM_TIMES("History.QueryHistory", 1209 TimeTicks::Now() - beginning_time); 1210} 1211 1212// Basic time-based querying of history. 1213void HistoryBackend::QueryHistoryBasic(const QueryOptions& options, 1214 QueryResults* result) { 1215 // First get all visits. 1216 VisitVector visits; 1217 bool has_more_results = db_->GetVisibleVisitsInRange(options, &visits); 1218 DCHECK(static_cast<int>(visits.size()) <= options.EffectiveMaxCount()); 1219 1220 // Now add them and the URL rows to the results. 1221 URLResult url_result; 1222 for (size_t i = 0; i < visits.size(); i++) { 1223 const VisitRow visit = visits[i]; 1224 1225 // Add a result row for this visit, get the URL info from the DB. 1226 if (!db_->GetURLRow(visit.url_id, &url_result)) { 1227 VLOG(0) << "Failed to get id " << visit.url_id 1228 << " from history.urls."; 1229 continue; // DB out of sync and URL doesn't exist, try to recover. 1230 } 1231 1232 if (!url_result.url().is_valid()) { 1233 VLOG(0) << "Got invalid URL from history.urls with id " 1234 << visit.url_id << ": " 1235 << url_result.url().possibly_invalid_spec(); 1236 continue; // Don't report invalid URLs in case of corruption. 1237 } 1238 1239 url_result.set_visit_time(visit.visit_time); 1240 1241 // Set whether the visit was blocked for a managed user by looking at the 1242 // transition type. 1243 url_result.set_blocked_visit( 1244 (visit.transition & content::PAGE_TRANSITION_BLOCKED) != 0); 1245 1246 // We don't set any of the query-specific parts of the URLResult, since 1247 // snippets and stuff don't apply to basic querying. 1248 result->AppendURLBySwapping(&url_result); 1249 } 1250 1251 if (!has_more_results && options.begin_time <= first_recorded_time_) 1252 result->set_reached_beginning(true); 1253} 1254 1255// Text-based querying of history. 1256void HistoryBackend::QueryHistoryText(const base::string16& text_query, 1257 const QueryOptions& options, 1258 QueryResults* result) { 1259 URLRows text_matches; 1260 db_->GetTextMatches(text_query, &text_matches); 1261 1262 std::vector<URLResult> matching_visits; 1263 VisitVector visits; // Declare outside loop to prevent re-construction. 1264 for (size_t i = 0; i < text_matches.size(); i++) { 1265 const URLRow& text_match = text_matches[i]; 1266 // Get all visits for given URL match. 1267 db_->GetVisibleVisitsForURL(text_match.id(), options, &visits); 1268 for (size_t j = 0; j < visits.size(); j++) { 1269 URLResult url_result(text_match); 1270 url_result.set_visit_time(visits[j].visit_time); 1271 matching_visits.push_back(url_result); 1272 } 1273 } 1274 1275 std::sort(matching_visits.begin(), matching_visits.end(), 1276 URLResult::CompareVisitTime); 1277 1278 size_t max_results = options.max_count == 0 ? 1279 std::numeric_limits<size_t>::max() : static_cast<int>(options.max_count); 1280 for (std::vector<URLResult>::iterator it = matching_visits.begin(); 1281 it != matching_visits.end() && result->size() < max_results; ++it) { 1282 result->AppendURLBySwapping(&(*it)); 1283 } 1284 1285 if (matching_visits.size() == result->size() && 1286 options.begin_time <= first_recorded_time_) 1287 result->set_reached_beginning(true); 1288} 1289 1290// Frontend to GetMostRecentRedirectsFrom from the history thread. 1291void HistoryBackend::QueryRedirectsFrom( 1292 scoped_refptr<QueryRedirectsRequest> request, 1293 const GURL& url) { 1294 if (request->canceled()) 1295 return; 1296 bool success = GetMostRecentRedirectsFrom(url, &request->value); 1297 request->ForwardResult(request->handle(), url, success, &request->value); 1298} 1299 1300void HistoryBackend::QueryRedirectsTo( 1301 scoped_refptr<QueryRedirectsRequest> request, 1302 const GURL& url) { 1303 if (request->canceled()) 1304 return; 1305 bool success = GetMostRecentRedirectsTo(url, &request->value); 1306 request->ForwardResult(request->handle(), url, success, &request->value); 1307} 1308 1309void HistoryBackend::GetVisibleVisitCountToHost( 1310 scoped_refptr<GetVisibleVisitCountToHostRequest> request, 1311 const GURL& url) { 1312 if (request->canceled()) 1313 return; 1314 int count = 0; 1315 Time first_visit; 1316 const bool success = db_.get() && 1317 db_->GetVisibleVisitCountToHost(url, &count, &first_visit); 1318 request->ForwardResult(request->handle(), success, count, first_visit); 1319} 1320 1321void HistoryBackend::QueryTopURLsAndRedirects( 1322 scoped_refptr<QueryTopURLsAndRedirectsRequest> request, 1323 int result_count) { 1324 if (request->canceled()) 1325 return; 1326 1327 if (!db_) { 1328 request->ForwardResult(request->handle(), false, NULL, NULL); 1329 return; 1330 } 1331 1332 std::vector<GURL>* top_urls = &request->value.a; 1333 history::RedirectMap* redirects = &request->value.b; 1334 1335 ScopedVector<PageUsageData> data; 1336 db_->QuerySegmentUsage(base::Time::Now() - base::TimeDelta::FromDays(90), 1337 result_count, &data.get()); 1338 1339 for (size_t i = 0; i < data.size(); ++i) { 1340 top_urls->push_back(data[i]->GetURL()); 1341 RefCountedVector<GURL>* list = new RefCountedVector<GURL>; 1342 GetMostRecentRedirectsFrom(top_urls->back(), &list->data); 1343 (*redirects)[top_urls->back()] = list; 1344 } 1345 1346 request->ForwardResult(request->handle(), true, top_urls, redirects); 1347} 1348 1349// Will replace QueryTopURLsAndRedirectsRequest. 1350void HistoryBackend::QueryMostVisitedURLs( 1351 scoped_refptr<QueryMostVisitedURLsRequest> request, 1352 int result_count, 1353 int days_back) { 1354 if (request->canceled()) 1355 return; 1356 1357 if (!db_) { 1358 // No History Database - return an empty list. 1359 request->ForwardResult(request->handle(), MostVisitedURLList()); 1360 return; 1361 } 1362 1363 MostVisitedURLList* result = &request->value; 1364 QueryMostVisitedURLsImpl(result_count, days_back, result); 1365 request->ForwardResult(request->handle(), *result); 1366} 1367 1368void HistoryBackend::QueryFilteredURLs( 1369 scoped_refptr<QueryFilteredURLsRequest> request, 1370 int result_count, 1371 const history::VisitFilter& filter, 1372 bool extended_info) { 1373 if (request->canceled()) 1374 return; 1375 1376 base::Time request_start = base::Time::Now(); 1377 1378 if (!db_) { 1379 // No History Database - return an empty list. 1380 request->ForwardResult(request->handle(), FilteredURLList()); 1381 return; 1382 } 1383 1384 VisitVector visits; 1385 db_->GetDirectVisitsDuringTimes(filter, 0, &visits); 1386 1387 std::map<URLID, double> score_map; 1388 for (size_t i = 0; i < visits.size(); ++i) { 1389 score_map[visits[i].url_id] += filter.GetVisitScore(visits[i]); 1390 } 1391 1392 // TODO(georgey): experiment with visit_segment database granularity (it is 1393 // currently 24 hours) to use it directly instead of using visits database, 1394 // which is considerably slower. 1395 ScopedVector<PageUsageData> data; 1396 data.reserve(score_map.size()); 1397 for (std::map<URLID, double>::iterator it = score_map.begin(); 1398 it != score_map.end(); ++it) { 1399 PageUsageData* pud = new PageUsageData(it->first); 1400 pud->SetScore(it->second); 1401 data.push_back(pud); 1402 } 1403 1404 // Limit to the top |result_count| results. 1405 std::sort(data.begin(), data.end(), PageUsageData::Predicate); 1406 if (result_count && implicit_cast<int>(data.size()) > result_count) 1407 data.resize(result_count); 1408 1409 for (size_t i = 0; i < data.size(); ++i) { 1410 URLRow info; 1411 if (db_->GetURLRow(data[i]->GetID(), &info)) { 1412 data[i]->SetURL(info.url()); 1413 data[i]->SetTitle(info.title()); 1414 } 1415 } 1416 1417 FilteredURLList& result = request->value; 1418 for (size_t i = 0; i < data.size(); ++i) { 1419 PageUsageData* current_data = data[i]; 1420 FilteredURL url(*current_data); 1421 1422 if (extended_info) { 1423 VisitVector visits; 1424 db_->GetVisitsForURL(current_data->GetID(), &visits); 1425 if (visits.size() > 0) { 1426 url.extended_info.total_visits = visits.size(); 1427 for (size_t i = 0; i < visits.size(); ++i) { 1428 url.extended_info.duration_opened += 1429 visits[i].visit_duration.InSeconds(); 1430 if (visits[i].visit_time > url.extended_info.last_visit_time) { 1431 url.extended_info.last_visit_time = visits[i].visit_time; 1432 } 1433 } 1434 // TODO(macourteau): implement the url.extended_info.visits stat. 1435 } 1436 } 1437 result.push_back(url); 1438 } 1439 1440 int delta_time = std::max(1, std::min(999, 1441 static_cast<int>((base::Time::Now() - request_start).InMilliseconds()))); 1442 STATIC_HISTOGRAM_POINTER_BLOCK( 1443 "NewTabPage.SuggestedSitesLoadTime", 1444 Add(delta_time), 1445 base::LinearHistogram::FactoryGet("NewTabPage.SuggestedSitesLoadTime", 1446 1, 1000, 100, base::Histogram::kUmaTargetedHistogramFlag)); 1447 1448 request->ForwardResult(request->handle(), result); 1449} 1450 1451void HistoryBackend::QueryMostVisitedURLsImpl(int result_count, 1452 int days_back, 1453 MostVisitedURLList* result) { 1454 if (!db_) 1455 return; 1456 1457 ScopedVector<PageUsageData> data; 1458 db_->QuerySegmentUsage(base::Time::Now() - 1459 base::TimeDelta::FromDays(days_back), 1460 result_count, &data.get()); 1461 1462 for (size_t i = 0; i < data.size(); ++i) { 1463 PageUsageData* current_data = data[i]; 1464 RedirectList redirects; 1465 GetMostRecentRedirectsFrom(current_data->GetURL(), &redirects); 1466 MostVisitedURL url = MakeMostVisitedURL(*current_data, redirects); 1467 result->push_back(url); 1468 } 1469} 1470 1471void HistoryBackend::GetRedirectsFromSpecificVisit( 1472 VisitID cur_visit, history::RedirectList* redirects) { 1473 // Follow any redirects from the given visit and add them to the list. 1474 // It *should* be impossible to get a circular chain here, but we check 1475 // just in case to avoid infinite loops. 1476 GURL cur_url; 1477 std::set<VisitID> visit_set; 1478 visit_set.insert(cur_visit); 1479 while (db_->GetRedirectFromVisit(cur_visit, &cur_visit, &cur_url)) { 1480 if (visit_set.find(cur_visit) != visit_set.end()) { 1481 NOTREACHED() << "Loop in visit chain, giving up"; 1482 return; 1483 } 1484 visit_set.insert(cur_visit); 1485 redirects->push_back(cur_url); 1486 } 1487} 1488 1489void HistoryBackend::GetRedirectsToSpecificVisit( 1490 VisitID cur_visit, 1491 history::RedirectList* redirects) { 1492 // Follow redirects going to cur_visit. These are added to |redirects| in 1493 // the order they are found. If a redirect chain looks like A -> B -> C and 1494 // |cur_visit| = C, redirects will be {B, A} in that order. 1495 if (!db_) 1496 return; 1497 1498 GURL cur_url; 1499 std::set<VisitID> visit_set; 1500 visit_set.insert(cur_visit); 1501 while (db_->GetRedirectToVisit(cur_visit, &cur_visit, &cur_url)) { 1502 if (visit_set.find(cur_visit) != visit_set.end()) { 1503 NOTREACHED() << "Loop in visit chain, giving up"; 1504 return; 1505 } 1506 visit_set.insert(cur_visit); 1507 redirects->push_back(cur_url); 1508 } 1509} 1510 1511bool HistoryBackend::GetMostRecentRedirectsFrom( 1512 const GURL& from_url, 1513 history::RedirectList* redirects) { 1514 redirects->clear(); 1515 if (!db_) 1516 return false; 1517 1518 URLID from_url_id = db_->GetRowForURL(from_url, NULL); 1519 VisitID cur_visit = db_->GetMostRecentVisitForURL(from_url_id, NULL); 1520 if (!cur_visit) 1521 return false; // No visits for URL. 1522 1523 GetRedirectsFromSpecificVisit(cur_visit, redirects); 1524 return true; 1525} 1526 1527bool HistoryBackend::GetMostRecentRedirectsTo( 1528 const GURL& to_url, 1529 history::RedirectList* redirects) { 1530 redirects->clear(); 1531 if (!db_) 1532 return false; 1533 1534 URLID to_url_id = db_->GetRowForURL(to_url, NULL); 1535 VisitID cur_visit = db_->GetMostRecentVisitForURL(to_url_id, NULL); 1536 if (!cur_visit) 1537 return false; // No visits for URL. 1538 1539 GetRedirectsToSpecificVisit(cur_visit, redirects); 1540 return true; 1541} 1542 1543void HistoryBackend::ScheduleAutocomplete(HistoryURLProvider* provider, 1544 HistoryURLProviderParams* params) { 1545 // ExecuteWithDB should handle the NULL database case. 1546 provider->ExecuteWithDB(this, db_.get(), params); 1547} 1548 1549void HistoryBackend::DeleteFTSIndexDatabases() { 1550 // Find files on disk matching the text databases file pattern so we can 1551 // quickly test for and delete them. 1552 base::FilePath::StringType filepattern = 1553 FILE_PATH_LITERAL("History Index *"); 1554 base::FileEnumerator enumerator( 1555 history_dir_, false, base::FileEnumerator::FILES, filepattern); 1556 int num_databases_deleted = 0; 1557 base::FilePath current_file; 1558 while (!(current_file = enumerator.Next()).empty()) { 1559 if (sql::Connection::Delete(current_file)) 1560 num_databases_deleted++; 1561 } 1562 UMA_HISTOGRAM_COUNTS("History.DeleteFTSIndexDatabases", 1563 num_databases_deleted); 1564} 1565 1566void HistoryBackend::GetFavicons( 1567 const std::vector<GURL>& icon_urls, 1568 int icon_types, 1569 const std::vector<int>& desired_sizes, 1570 std::vector<favicon_base::FaviconRawBitmapResult>* bitmap_results) { 1571 UpdateFaviconMappingsAndFetchImpl(NULL, icon_urls, icon_types, desired_sizes, 1572 bitmap_results); 1573} 1574 1575void HistoryBackend::GetLargestFaviconForURL( 1576 const GURL& page_url, 1577 const std::vector<int>& icon_types, 1578 int minimum_size_in_pixels, 1579 favicon_base::FaviconRawBitmapResult* favicon_bitmap_result) { 1580 DCHECK(favicon_bitmap_result); 1581 1582 if (!db_ || !thumbnail_db_) 1583 return; 1584 1585 TimeTicks beginning_time = TimeTicks::Now(); 1586 1587 std::vector<IconMapping> icon_mappings; 1588 if (!thumbnail_db_->GetIconMappingsForPageURL(page_url, &icon_mappings) || 1589 icon_mappings.empty()) 1590 return; 1591 1592 int required_icon_types = 0; 1593 for (std::vector<int>::const_iterator i = icon_types.begin(); 1594 i != icon_types.end(); ++i) { 1595 required_icon_types |= *i; 1596 } 1597 1598 // Find the largest bitmap for each IconType placing in 1599 // |largest_favicon_bitmaps|. 1600 std::map<favicon_base::IconType, FaviconBitmap> largest_favicon_bitmaps; 1601 for (std::vector<IconMapping>::const_iterator i = icon_mappings.begin(); 1602 i != icon_mappings.end(); ++i) { 1603 if (!(i->icon_type & required_icon_types)) 1604 continue; 1605 std::vector<FaviconBitmapIDSize> bitmap_id_sizes; 1606 thumbnail_db_->GetFaviconBitmapIDSizes(i->icon_id, &bitmap_id_sizes); 1607 FaviconBitmap& largest = largest_favicon_bitmaps[i->icon_type]; 1608 for (std::vector<FaviconBitmapIDSize>::const_iterator j = 1609 bitmap_id_sizes.begin(); j != bitmap_id_sizes.end(); ++j) { 1610 if (largest.bitmap_id == 0 || 1611 (largest.pixel_size.width() < j->pixel_size.width() && 1612 largest.pixel_size.height() < j->pixel_size.height())) { 1613 largest.icon_id = i->icon_id; 1614 largest.bitmap_id = j->bitmap_id; 1615 largest.pixel_size = j->pixel_size; 1616 } 1617 } 1618 } 1619 if (largest_favicon_bitmaps.empty()) 1620 return; 1621 1622 // Find an icon which is larger than minimum_size_in_pixels in the order of 1623 // icon_types. 1624 FaviconBitmap largest_icon; 1625 for (std::vector<int>::const_iterator t = icon_types.begin(); 1626 t != icon_types.end(); ++t) { 1627 for (std::map<favicon_base::IconType, FaviconBitmap>::const_iterator f = 1628 largest_favicon_bitmaps.begin(); 1629 f != largest_favicon_bitmaps.end(); 1630 ++f) { 1631 if (f->first & *t && 1632 (largest_icon.bitmap_id == 0 || 1633 (largest_icon.pixel_size.height() < f->second.pixel_size.height() && 1634 largest_icon.pixel_size.width() < f->second.pixel_size.width()))) { 1635 largest_icon = f->second; 1636 } 1637 } 1638 if (largest_icon.pixel_size.width() > minimum_size_in_pixels && 1639 largest_icon.pixel_size.height() > minimum_size_in_pixels) 1640 break; 1641 } 1642 1643 GURL icon_url; 1644 favicon_base::IconType icon_type; 1645 if (!thumbnail_db_->GetFaviconHeader(largest_icon.icon_id, &icon_url, 1646 &icon_type)) { 1647 return; 1648 } 1649 1650 base::Time last_updated; 1651 favicon_base::FaviconRawBitmapResult bitmap_result; 1652 bitmap_result.icon_url = icon_url; 1653 bitmap_result.icon_type = icon_type; 1654 if (!thumbnail_db_->GetFaviconBitmap(largest_icon.bitmap_id, 1655 &last_updated, 1656 &bitmap_result.bitmap_data, 1657 &bitmap_result.pixel_size)) { 1658 return; 1659 } 1660 1661 bitmap_result.expired = (Time::Now() - last_updated) > 1662 TimeDelta::FromDays(kFaviconRefetchDays); 1663 if (bitmap_result.is_valid()) 1664 *favicon_bitmap_result = bitmap_result; 1665 1666 HISTOGRAM_TIMES("History.GetLargestFaviconForURL", 1667 TimeTicks::Now() - beginning_time); 1668} 1669 1670void HistoryBackend::GetFaviconsForURL( 1671 const GURL& page_url, 1672 int icon_types, 1673 const std::vector<int>& desired_sizes, 1674 std::vector<favicon_base::FaviconRawBitmapResult>* bitmap_results) { 1675 DCHECK(bitmap_results); 1676 GetFaviconsFromDB(page_url, icon_types, desired_sizes, bitmap_results); 1677} 1678 1679void HistoryBackend::GetFaviconForID( 1680 favicon_base::FaviconID favicon_id, 1681 int desired_size, 1682 std::vector<favicon_base::FaviconRawBitmapResult>* bitmap_results) { 1683 std::vector<favicon_base::FaviconID> favicon_ids; 1684 favicon_ids.push_back(favicon_id); 1685 std::vector<int> desired_sizes; 1686 desired_sizes.push_back(desired_size); 1687 1688 // Get results from DB. 1689 GetFaviconBitmapResultsForBestMatch(favicon_ids, 1690 desired_sizes, 1691 bitmap_results); 1692} 1693 1694void HistoryBackend::UpdateFaviconMappingsAndFetch( 1695 const GURL& page_url, 1696 const std::vector<GURL>& icon_urls, 1697 int icon_types, 1698 const std::vector<int>& desired_sizes, 1699 std::vector<favicon_base::FaviconRawBitmapResult>* bitmap_results) { 1700 UpdateFaviconMappingsAndFetchImpl(&page_url, icon_urls, icon_types, 1701 desired_sizes, bitmap_results); 1702} 1703 1704void HistoryBackend::MergeFavicon( 1705 const GURL& page_url, 1706 const GURL& icon_url, 1707 favicon_base::IconType icon_type, 1708 scoped_refptr<base::RefCountedMemory> bitmap_data, 1709 const gfx::Size& pixel_size) { 1710 if (!thumbnail_db_ || !db_) 1711 return; 1712 1713 favicon_base::FaviconID favicon_id = 1714 thumbnail_db_->GetFaviconIDForFaviconURL(icon_url, icon_type, NULL); 1715 1716 if (!favicon_id) { 1717 // There is no favicon at |icon_url|, create it. 1718 favicon_id = thumbnail_db_->AddFavicon(icon_url, icon_type); 1719 } 1720 1721 std::vector<FaviconBitmapIDSize> bitmap_id_sizes; 1722 thumbnail_db_->GetFaviconBitmapIDSizes(favicon_id, &bitmap_id_sizes); 1723 1724 // If there is already a favicon bitmap of |pixel_size| at |icon_url|, 1725 // replace it. 1726 bool bitmap_identical = false; 1727 bool replaced_bitmap = false; 1728 for (size_t i = 0; i < bitmap_id_sizes.size(); ++i) { 1729 if (bitmap_id_sizes[i].pixel_size == pixel_size) { 1730 if (IsFaviconBitmapDataEqual(bitmap_id_sizes[i].bitmap_id, bitmap_data)) { 1731 thumbnail_db_->SetFaviconBitmapLastUpdateTime( 1732 bitmap_id_sizes[i].bitmap_id, base::Time::Now()); 1733 bitmap_identical = true; 1734 } else { 1735 thumbnail_db_->SetFaviconBitmap(bitmap_id_sizes[i].bitmap_id, 1736 bitmap_data, base::Time::Now()); 1737 replaced_bitmap = true; 1738 } 1739 break; 1740 } 1741 } 1742 1743 // Create a vector of the pixel sizes of the favicon bitmaps currently at 1744 // |icon_url|. 1745 std::vector<gfx::Size> favicon_sizes; 1746 for (size_t i = 0; i < bitmap_id_sizes.size(); ++i) 1747 favicon_sizes.push_back(bitmap_id_sizes[i].pixel_size); 1748 1749 if (!replaced_bitmap && !bitmap_identical) { 1750 // Set the preexisting favicon bitmaps as expired as the preexisting favicon 1751 // bitmaps are not consistent with the merged in data. 1752 thumbnail_db_->SetFaviconOutOfDate(favicon_id); 1753 1754 // Delete an arbitrary favicon bitmap to avoid going over the limit of 1755 // |kMaxFaviconBitmapsPerIconURL|. 1756 if (bitmap_id_sizes.size() >= kMaxFaviconBitmapsPerIconURL) { 1757 thumbnail_db_->DeleteFaviconBitmap(bitmap_id_sizes[0].bitmap_id); 1758 favicon_sizes.erase(favicon_sizes.begin()); 1759 } 1760 thumbnail_db_->AddFaviconBitmap(favicon_id, bitmap_data, base::Time::Now(), 1761 pixel_size); 1762 favicon_sizes.push_back(pixel_size); 1763 } 1764 1765 // A site may have changed the favicons that it uses for |page_url|. 1766 // Example Scenario: 1767 // page_url = news.google.com 1768 // Initial State: www.google.com/favicon.ico 16x16, 32x32 1769 // MergeFavicon(news.google.com, news.google.com/news_specific.ico, ..., 1770 // ..., 16x16) 1771 // 1772 // Difficulties: 1773 // 1. Sync requires that a call to GetFaviconsForURL() returns the 1774 // |bitmap_data| passed into MergeFavicon(). 1775 // - It is invalid for the 16x16 bitmap for www.google.com/favicon.ico to 1776 // stay mapped to news.google.com because it would be unclear which 16x16 1777 // bitmap should be returned via GetFaviconsForURL(). 1778 // 1779 // 2. www.google.com/favicon.ico may be mapped to more than just 1780 // news.google.com (eg www.google.com). 1781 // - The 16x16 bitmap cannot be deleted from www.google.com/favicon.ico 1782 // 1783 // To resolve these problems, we copy all of the favicon bitmaps previously 1784 // mapped to news.google.com (|page_url|) and add them to the favicon at 1785 // news.google.com/news_specific.ico (|icon_url|). The favicon sizes for 1786 // |icon_url| are set to default to indicate that |icon_url| has incomplete 1787 // / incorrect data. 1788 // Difficulty 1: All but news.google.com/news_specific.ico are unmapped from 1789 // news.google.com 1790 // Difficulty 2: The favicon bitmaps for www.google.com/favicon.ico are not 1791 // modified. 1792 1793 std::vector<IconMapping> icon_mappings; 1794 thumbnail_db_->GetIconMappingsForPageURL(page_url, icon_type, &icon_mappings); 1795 1796 // Copy the favicon bitmaps mapped to |page_url| to the favicon at |icon_url| 1797 // till the limit of |kMaxFaviconBitmapsPerIconURL| is reached. 1798 for (size_t i = 0; i < icon_mappings.size(); ++i) { 1799 if (favicon_sizes.size() >= kMaxFaviconBitmapsPerIconURL) 1800 break; 1801 1802 if (icon_mappings[i].icon_url == icon_url) 1803 continue; 1804 1805 std::vector<FaviconBitmap> bitmaps_to_copy; 1806 thumbnail_db_->GetFaviconBitmaps(icon_mappings[i].icon_id, 1807 &bitmaps_to_copy); 1808 for (size_t j = 0; j < bitmaps_to_copy.size(); ++j) { 1809 // Do not add a favicon bitmap at a pixel size for which there is already 1810 // a favicon bitmap mapped to |icon_url|. The one there is more correct 1811 // and having multiple equally sized favicon bitmaps for |page_url| is 1812 // ambiguous in terms of GetFaviconsForURL(). 1813 std::vector<gfx::Size>::iterator it = std::find(favicon_sizes.begin(), 1814 favicon_sizes.end(), bitmaps_to_copy[j].pixel_size); 1815 if (it != favicon_sizes.end()) 1816 continue; 1817 1818 // Add the favicon bitmap as expired as it is not consistent with the 1819 // merged in data. 1820 thumbnail_db_->AddFaviconBitmap(favicon_id, 1821 bitmaps_to_copy[j].bitmap_data, base::Time(), 1822 bitmaps_to_copy[j].pixel_size); 1823 favicon_sizes.push_back(bitmaps_to_copy[j].pixel_size); 1824 1825 if (favicon_sizes.size() >= kMaxFaviconBitmapsPerIconURL) 1826 break; 1827 } 1828 } 1829 1830 // Update the favicon mappings such that only |icon_url| is mapped to 1831 // |page_url|. 1832 bool mapping_changed = false; 1833 if (icon_mappings.size() != 1 || icon_mappings[0].icon_url != icon_url) { 1834 std::vector<favicon_base::FaviconID> favicon_ids; 1835 favicon_ids.push_back(favicon_id); 1836 SetFaviconMappingsForPageAndRedirects(page_url, icon_type, favicon_ids); 1837 mapping_changed = true; 1838 } 1839 1840 if (mapping_changed || !bitmap_identical) 1841 SendFaviconChangedNotificationForPageAndRedirects(page_url); 1842 ScheduleCommit(); 1843} 1844 1845void HistoryBackend::SetFavicons( 1846 const GURL& page_url, 1847 favicon_base::IconType icon_type, 1848 const std::vector<favicon_base::FaviconRawBitmapData>& 1849 favicon_bitmap_data) { 1850 if (!thumbnail_db_ || !db_) 1851 return; 1852 1853 DCHECK(ValidateSetFaviconsParams(favicon_bitmap_data)); 1854 1855 // Build map of FaviconRawBitmapData for each icon url. 1856 typedef std::map<GURL, std::vector<favicon_base::FaviconRawBitmapData> > 1857 BitmapDataByIconURL; 1858 BitmapDataByIconURL grouped_by_icon_url; 1859 for (size_t i = 0; i < favicon_bitmap_data.size(); ++i) { 1860 const GURL& icon_url = favicon_bitmap_data[i].icon_url; 1861 grouped_by_icon_url[icon_url].push_back(favicon_bitmap_data[i]); 1862 } 1863 1864 // Track whether the method modifies or creates any favicon bitmaps, favicons 1865 // or icon mappings. 1866 bool data_modified = false; 1867 1868 std::vector<favicon_base::FaviconID> icon_ids; 1869 for (BitmapDataByIconURL::const_iterator it = grouped_by_icon_url.begin(); 1870 it != grouped_by_icon_url.end(); ++it) { 1871 const GURL& icon_url = it->first; 1872 favicon_base::FaviconID icon_id = 1873 thumbnail_db_->GetFaviconIDForFaviconURL(icon_url, icon_type, NULL); 1874 1875 if (!icon_id) { 1876 // TODO(pkotwicz): Remove the favicon sizes attribute from 1877 // ThumbnailDatabase::AddFavicon(). 1878 icon_id = thumbnail_db_->AddFavicon(icon_url, icon_type); 1879 data_modified = true; 1880 } 1881 icon_ids.push_back(icon_id); 1882 1883 if (!data_modified) 1884 SetFaviconBitmaps(icon_id, it->second, &data_modified); 1885 else 1886 SetFaviconBitmaps(icon_id, it->second, NULL); 1887 } 1888 1889 data_modified |= 1890 SetFaviconMappingsForPageAndRedirects(page_url, icon_type, icon_ids); 1891 1892 if (data_modified) { 1893 // Send notification to the UI as an icon mapping, favicon, or favicon 1894 // bitmap was changed by this function. 1895 SendFaviconChangedNotificationForPageAndRedirects(page_url); 1896 } 1897 ScheduleCommit(); 1898} 1899 1900void HistoryBackend::SetFaviconsOutOfDateForPage(const GURL& page_url) { 1901 std::vector<IconMapping> icon_mappings; 1902 1903 if (!thumbnail_db_ || 1904 !thumbnail_db_->GetIconMappingsForPageURL(page_url, 1905 &icon_mappings)) 1906 return; 1907 1908 for (std::vector<IconMapping>::iterator m = icon_mappings.begin(); 1909 m != icon_mappings.end(); ++m) { 1910 thumbnail_db_->SetFaviconOutOfDate(m->icon_id); 1911 } 1912 ScheduleCommit(); 1913} 1914 1915void HistoryBackend::CloneFavicons(const GURL& old_page_url, 1916 const GURL& new_page_url) { 1917 if (!thumbnail_db_) 1918 return; 1919 1920 // Prevent cross-domain cloning. 1921 if (old_page_url.GetOrigin() != new_page_url.GetOrigin()) 1922 return; 1923 1924 thumbnail_db_->CloneIconMappings(old_page_url, new_page_url); 1925 ScheduleCommit(); 1926} 1927 1928void HistoryBackend::SetImportedFavicons( 1929 const std::vector<ImportedFaviconUsage>& favicon_usage) { 1930 if (!db_ || !thumbnail_db_) 1931 return; 1932 1933 Time now = Time::Now(); 1934 1935 // Track all URLs that had their favicons set or updated. 1936 std::set<GURL> favicons_changed; 1937 1938 for (size_t i = 0; i < favicon_usage.size(); i++) { 1939 favicon_base::FaviconID favicon_id = 1940 thumbnail_db_->GetFaviconIDForFaviconURL( 1941 favicon_usage[i].favicon_url, favicon_base::FAVICON, NULL); 1942 if (!favicon_id) { 1943 // This favicon doesn't exist yet, so we create it using the given data. 1944 // TODO(pkotwicz): Pass in real pixel size. 1945 favicon_id = thumbnail_db_->AddFavicon( 1946 favicon_usage[i].favicon_url, 1947 favicon_base::FAVICON, 1948 new base::RefCountedBytes(favicon_usage[i].png_data), 1949 now, 1950 gfx::Size()); 1951 } 1952 1953 // Save the mapping from all the URLs to the favicon. 1954 HistoryClient* history_client = GetHistoryClient(); 1955 for (std::set<GURL>::const_iterator url = favicon_usage[i].urls.begin(); 1956 url != favicon_usage[i].urls.end(); ++url) { 1957 URLRow url_row; 1958 if (!db_->GetRowForURL(*url, &url_row)) { 1959 // If the URL is present as a bookmark, add the url in history to 1960 // save the favicon mapping. This will match with what history db does 1961 // for regular bookmarked URLs with favicons - when history db is 1962 // cleaned, we keep an entry in the db with 0 visits as long as that 1963 // url is bookmarked. 1964 if (history_client && history_client->IsBookmarked(*url)) { 1965 URLRow url_info(*url); 1966 url_info.set_visit_count(0); 1967 url_info.set_typed_count(0); 1968 url_info.set_last_visit(base::Time()); 1969 url_info.set_hidden(false); 1970 db_->AddURL(url_info); 1971 thumbnail_db_->AddIconMapping(*url, favicon_id); 1972 favicons_changed.insert(*url); 1973 } 1974 } else { 1975 if (!thumbnail_db_->GetIconMappingsForPageURL( 1976 *url, favicon_base::FAVICON, NULL)) { 1977 // URL is present in history, update the favicon *only* if it is not 1978 // set already. 1979 thumbnail_db_->AddIconMapping(*url, favicon_id); 1980 favicons_changed.insert(*url); 1981 } 1982 } 1983 } 1984 } 1985 1986 if (!favicons_changed.empty()) { 1987 // Send the notification about the changed favicon URLs. 1988 scoped_ptr<FaviconChangedDetails> changed_details( 1989 new FaviconChangedDetails); 1990 changed_details->urls.swap(favicons_changed); 1991 BroadcastNotifications(chrome::NOTIFICATION_FAVICON_CHANGED, 1992 changed_details.PassAs<HistoryDetails>()); 1993 } 1994} 1995 1996void HistoryBackend::UpdateFaviconMappingsAndFetchImpl( 1997 const GURL* page_url, 1998 const std::vector<GURL>& icon_urls, 1999 int icon_types, 2000 const std::vector<int>& desired_sizes, 2001 std::vector<favicon_base::FaviconRawBitmapResult>* bitmap_results) { 2002 // If |page_url| is specified, |icon_types| must be either a single icon 2003 // type or icon types which are equivalent. 2004 DCHECK(!page_url || icon_types == favicon_base::FAVICON || 2005 icon_types == favicon_base::TOUCH_ICON || 2006 icon_types == favicon_base::TOUCH_PRECOMPOSED_ICON || 2007 icon_types == 2008 (favicon_base::TOUCH_ICON | favicon_base::TOUCH_PRECOMPOSED_ICON)); 2009 bitmap_results->clear(); 2010 2011 if (!thumbnail_db_) { 2012 return; 2013 } 2014 2015 std::vector<favicon_base::FaviconID> favicon_ids; 2016 2017 // The icon type for which the mappings will the updated and data will be 2018 // returned. 2019 favicon_base::IconType selected_icon_type = favicon_base::INVALID_ICON; 2020 2021 for (size_t i = 0; i < icon_urls.size(); ++i) { 2022 const GURL& icon_url = icon_urls[i]; 2023 favicon_base::IconType icon_type_out; 2024 const favicon_base::FaviconID favicon_id = 2025 thumbnail_db_->GetFaviconIDForFaviconURL( 2026 icon_url, icon_types, &icon_type_out); 2027 2028 if (favicon_id) { 2029 // Return and update icon mappings only for the largest icon type. As 2030 // |icon_urls| is not sorted in terms of icon type, clear |favicon_ids| 2031 // if an |icon_url| with a larger icon type is found. 2032 if (icon_type_out > selected_icon_type) { 2033 selected_icon_type = icon_type_out; 2034 favicon_ids.clear(); 2035 } 2036 if (icon_type_out == selected_icon_type) 2037 favicon_ids.push_back(favicon_id); 2038 } 2039 } 2040 2041 if (page_url && !favicon_ids.empty()) { 2042 bool mappings_updated = 2043 SetFaviconMappingsForPageAndRedirects(*page_url, selected_icon_type, 2044 favicon_ids); 2045 if (mappings_updated) { 2046 SendFaviconChangedNotificationForPageAndRedirects(*page_url); 2047 ScheduleCommit(); 2048 } 2049 } 2050 2051 GetFaviconBitmapResultsForBestMatch(favicon_ids, desired_sizes, 2052 bitmap_results); 2053} 2054 2055void HistoryBackend::SetFaviconBitmaps( 2056 favicon_base::FaviconID icon_id, 2057 const std::vector<favicon_base::FaviconRawBitmapData>& favicon_bitmap_data, 2058 bool* favicon_bitmaps_changed) { 2059 if (favicon_bitmaps_changed) 2060 *favicon_bitmaps_changed = false; 2061 2062 std::vector<FaviconBitmapIDSize> bitmap_id_sizes; 2063 thumbnail_db_->GetFaviconBitmapIDSizes(icon_id, &bitmap_id_sizes); 2064 2065 std::vector<favicon_base::FaviconRawBitmapData> to_add = favicon_bitmap_data; 2066 2067 for (size_t i = 0; i < bitmap_id_sizes.size(); ++i) { 2068 const gfx::Size& pixel_size = bitmap_id_sizes[i].pixel_size; 2069 std::vector<favicon_base::FaviconRawBitmapData>::iterator match_it = 2070 to_add.end(); 2071 for (std::vector<favicon_base::FaviconRawBitmapData>::iterator it = 2072 to_add.begin(); 2073 it != to_add.end(); 2074 ++it) { 2075 if (it->pixel_size == pixel_size) { 2076 match_it = it; 2077 break; 2078 } 2079 } 2080 2081 FaviconBitmapID bitmap_id = bitmap_id_sizes[i].bitmap_id; 2082 if (match_it == to_add.end()) { 2083 thumbnail_db_->DeleteFaviconBitmap(bitmap_id); 2084 2085 if (favicon_bitmaps_changed) 2086 *favicon_bitmaps_changed = true; 2087 } else { 2088 if (favicon_bitmaps_changed && 2089 !*favicon_bitmaps_changed && 2090 IsFaviconBitmapDataEqual(bitmap_id, match_it->bitmap_data)) { 2091 thumbnail_db_->SetFaviconBitmapLastUpdateTime( 2092 bitmap_id, base::Time::Now()); 2093 } else { 2094 thumbnail_db_->SetFaviconBitmap(bitmap_id, match_it->bitmap_data, 2095 base::Time::Now()); 2096 2097 if (favicon_bitmaps_changed) 2098 *favicon_bitmaps_changed = true; 2099 } 2100 to_add.erase(match_it); 2101 } 2102 } 2103 2104 for (size_t i = 0; i < to_add.size(); ++i) { 2105 thumbnail_db_->AddFaviconBitmap(icon_id, to_add[i].bitmap_data, 2106 base::Time::Now(), to_add[i].pixel_size); 2107 2108 if (favicon_bitmaps_changed) 2109 *favicon_bitmaps_changed = true; 2110 } 2111} 2112 2113bool HistoryBackend::ValidateSetFaviconsParams(const std::vector< 2114 favicon_base::FaviconRawBitmapData>& favicon_bitmap_data) const { 2115 typedef std::map<GURL, size_t> BitmapsPerIconURL; 2116 BitmapsPerIconURL num_bitmaps_per_icon_url; 2117 for (size_t i = 0; i < favicon_bitmap_data.size(); ++i) { 2118 if (!favicon_bitmap_data[i].bitmap_data.get()) 2119 return false; 2120 2121 const GURL& icon_url = favicon_bitmap_data[i].icon_url; 2122 if (!num_bitmaps_per_icon_url.count(icon_url)) 2123 num_bitmaps_per_icon_url[icon_url] = 1u; 2124 else 2125 ++num_bitmaps_per_icon_url[icon_url]; 2126 } 2127 2128 if (num_bitmaps_per_icon_url.size() > kMaxFaviconsPerPage) 2129 return false; 2130 2131 for (BitmapsPerIconURL::const_iterator it = num_bitmaps_per_icon_url.begin(); 2132 it != num_bitmaps_per_icon_url.end(); ++it) { 2133 if (it->second > kMaxFaviconBitmapsPerIconURL) 2134 return false; 2135 } 2136 return true; 2137} 2138 2139bool HistoryBackend::IsFaviconBitmapDataEqual( 2140 FaviconBitmapID bitmap_id, 2141 const scoped_refptr<base::RefCountedMemory>& new_bitmap_data) { 2142 if (!new_bitmap_data.get()) 2143 return false; 2144 2145 scoped_refptr<base::RefCountedMemory> original_bitmap_data; 2146 thumbnail_db_->GetFaviconBitmap(bitmap_id, 2147 NULL, 2148 &original_bitmap_data, 2149 NULL); 2150 return new_bitmap_data->Equals(original_bitmap_data); 2151} 2152 2153bool HistoryBackend::GetFaviconsFromDB( 2154 const GURL& page_url, 2155 int icon_types, 2156 const std::vector<int>& desired_sizes, 2157 std::vector<favicon_base::FaviconRawBitmapResult>* favicon_bitmap_results) { 2158 DCHECK(favicon_bitmap_results); 2159 favicon_bitmap_results->clear(); 2160 2161 if (!db_ || !thumbnail_db_) 2162 return false; 2163 2164 // Time the query. 2165 TimeTicks beginning_time = TimeTicks::Now(); 2166 2167 // Get FaviconIDs for |page_url| and one of |icon_types|. 2168 std::vector<IconMapping> icon_mappings; 2169 thumbnail_db_->GetIconMappingsForPageURL(page_url, icon_types, 2170 &icon_mappings); 2171 std::vector<favicon_base::FaviconID> favicon_ids; 2172 for (size_t i = 0; i < icon_mappings.size(); ++i) 2173 favicon_ids.push_back(icon_mappings[i].icon_id); 2174 2175 // Populate |favicon_bitmap_results| and |icon_url_sizes|. 2176 bool success = GetFaviconBitmapResultsForBestMatch(favicon_ids, 2177 desired_sizes, favicon_bitmap_results); 2178 UMA_HISTOGRAM_TIMES("History.GetFavIconFromDB", // historical name 2179 TimeTicks::Now() - beginning_time); 2180 return success && !favicon_bitmap_results->empty(); 2181} 2182 2183bool HistoryBackend::GetFaviconBitmapResultsForBestMatch( 2184 const std::vector<favicon_base::FaviconID>& candidate_favicon_ids, 2185 const std::vector<int>& desired_sizes, 2186 std::vector<favicon_base::FaviconRawBitmapResult>* favicon_bitmap_results) { 2187 favicon_bitmap_results->clear(); 2188 2189 if (candidate_favicon_ids.empty()) 2190 return true; 2191 2192 // Find the FaviconID and the FaviconBitmapIDs which best match 2193 // |desired_size_in_dip| and |desired_scale_factors|. 2194 // TODO(pkotwicz): Select bitmap results from multiple favicons once 2195 // content::FaviconStatus supports multiple icon URLs. 2196 favicon_base::FaviconID best_favicon_id = 0; 2197 std::vector<FaviconBitmapID> best_bitmap_ids; 2198 float highest_score = kSelectFaviconFramesInvalidScore; 2199 for (size_t i = 0; i < candidate_favicon_ids.size(); ++i) { 2200 std::vector<FaviconBitmapIDSize> bitmap_id_sizes; 2201 thumbnail_db_->GetFaviconBitmapIDSizes(candidate_favicon_ids[i], 2202 &bitmap_id_sizes); 2203 2204 // Build vector of gfx::Size from |bitmap_id_sizes|. 2205 std::vector<gfx::Size> sizes; 2206 for (size_t j = 0; j < bitmap_id_sizes.size(); ++j) 2207 sizes.push_back(bitmap_id_sizes[j].pixel_size); 2208 2209 std::vector<size_t> candidate_bitmap_indices; 2210 float score = 0; 2211 SelectFaviconFrameIndices(sizes, 2212 desired_sizes, 2213 &candidate_bitmap_indices, 2214 &score); 2215 if (score > highest_score) { 2216 highest_score = score; 2217 best_favicon_id = candidate_favicon_ids[i], 2218 best_bitmap_ids.clear(); 2219 for (size_t j = 0; j < candidate_bitmap_indices.size(); ++j) { 2220 size_t candidate_index = candidate_bitmap_indices[j]; 2221 best_bitmap_ids.push_back( 2222 bitmap_id_sizes[candidate_index].bitmap_id); 2223 } 2224 } 2225 } 2226 2227 // Construct FaviconRawBitmapResults from |best_favicon_id| and 2228 // |best_bitmap_ids|. 2229 GURL icon_url; 2230 favicon_base::IconType icon_type; 2231 if (!thumbnail_db_->GetFaviconHeader(best_favicon_id, &icon_url, 2232 &icon_type)) { 2233 return false; 2234 } 2235 2236 for (size_t i = 0; i < best_bitmap_ids.size(); ++i) { 2237 base::Time last_updated; 2238 favicon_base::FaviconRawBitmapResult bitmap_result; 2239 bitmap_result.icon_url = icon_url; 2240 bitmap_result.icon_type = icon_type; 2241 if (!thumbnail_db_->GetFaviconBitmap(best_bitmap_ids[i], 2242 &last_updated, 2243 &bitmap_result.bitmap_data, 2244 &bitmap_result.pixel_size)) { 2245 return false; 2246 } 2247 2248 bitmap_result.expired = (Time::Now() - last_updated) > 2249 TimeDelta::FromDays(kFaviconRefetchDays); 2250 if (bitmap_result.is_valid()) 2251 favicon_bitmap_results->push_back(bitmap_result); 2252 } 2253 return true; 2254} 2255 2256bool HistoryBackend::SetFaviconMappingsForPageAndRedirects( 2257 const GURL& page_url, 2258 favicon_base::IconType icon_type, 2259 const std::vector<favicon_base::FaviconID>& icon_ids) { 2260 if (!thumbnail_db_) 2261 return false; 2262 2263 // Find all the pages whose favicons we should set, we want to set it for 2264 // all the pages in the redirect chain if it redirected. 2265 history::RedirectList redirects; 2266 GetCachedRecentRedirects(page_url, &redirects); 2267 2268 bool mappings_changed = false; 2269 2270 // Save page <-> favicon associations. 2271 for (history::RedirectList::const_iterator i(redirects.begin()); 2272 i != redirects.end(); ++i) { 2273 mappings_changed |= SetFaviconMappingsForPage(*i, icon_type, icon_ids); 2274 } 2275 return mappings_changed; 2276} 2277 2278bool HistoryBackend::SetFaviconMappingsForPage( 2279 const GURL& page_url, 2280 favicon_base::IconType icon_type, 2281 const std::vector<favicon_base::FaviconID>& icon_ids) { 2282 DCHECK_LE(icon_ids.size(), kMaxFaviconsPerPage); 2283 bool mappings_changed = false; 2284 2285 // Two icon types are considered 'equivalent' if one of the icon types is 2286 // TOUCH_ICON and the other is TOUCH_PRECOMPOSED_ICON. 2287 // 2288 // Sets the icon mappings from |page_url| for |icon_type| to the favicons 2289 // with |icon_ids|. Mappings for |page_url| to favicons of type |icon_type| 2290 // whose FaviconID is not in |icon_ids| are removed. All icon mappings for 2291 // |page_url| to favicons of a type equivalent to |icon_type| are removed. 2292 // Remove any favicons which are orphaned as a result of the removal of the 2293 // icon mappings. 2294 2295 std::vector<favicon_base::FaviconID> unmapped_icon_ids = icon_ids; 2296 2297 std::vector<IconMapping> icon_mappings; 2298 thumbnail_db_->GetIconMappingsForPageURL(page_url, &icon_mappings); 2299 2300 for (std::vector<IconMapping>::iterator m = icon_mappings.begin(); 2301 m != icon_mappings.end(); ++m) { 2302 std::vector<favicon_base::FaviconID>::iterator icon_id_it = std::find( 2303 unmapped_icon_ids.begin(), unmapped_icon_ids.end(), m->icon_id); 2304 2305 // If the icon mapping already exists, avoid removing it and adding it back. 2306 if (icon_id_it != unmapped_icon_ids.end()) { 2307 unmapped_icon_ids.erase(icon_id_it); 2308 continue; 2309 } 2310 2311 if ((icon_type == favicon_base::TOUCH_ICON && 2312 m->icon_type == favicon_base::TOUCH_PRECOMPOSED_ICON) || 2313 (icon_type == favicon_base::TOUCH_PRECOMPOSED_ICON && 2314 m->icon_type == favicon_base::TOUCH_ICON) || 2315 (icon_type == m->icon_type)) { 2316 thumbnail_db_->DeleteIconMapping(m->mapping_id); 2317 2318 // Removing the icon mapping may have orphaned the associated favicon so 2319 // we must recheck it. This is not super fast, but this case will get 2320 // triggered rarely, since normally a page will always map to the same 2321 // favicon IDs. It will mostly happen for favicons we import. 2322 if (!thumbnail_db_->HasMappingFor(m->icon_id)) 2323 thumbnail_db_->DeleteFavicon(m->icon_id); 2324 mappings_changed = true; 2325 } 2326 } 2327 2328 for (size_t i = 0; i < unmapped_icon_ids.size(); ++i) { 2329 thumbnail_db_->AddIconMapping(page_url, unmapped_icon_ids[i]); 2330 mappings_changed = true; 2331 } 2332 return mappings_changed; 2333} 2334 2335void HistoryBackend::GetCachedRecentRedirects( 2336 const GURL& page_url, 2337 history::RedirectList* redirect_list) { 2338 RedirectCache::iterator iter = recent_redirects_.Get(page_url); 2339 if (iter != recent_redirects_.end()) { 2340 *redirect_list = iter->second; 2341 2342 // The redirect chain should have the destination URL as the last item. 2343 DCHECK(!redirect_list->empty()); 2344 DCHECK(redirect_list->back() == page_url); 2345 } else { 2346 // No known redirects, construct mock redirect chain containing |page_url|. 2347 redirect_list->push_back(page_url); 2348 } 2349} 2350 2351void HistoryBackend::SendFaviconChangedNotificationForPageAndRedirects( 2352 const GURL& page_url) { 2353 history::RedirectList redirect_list; 2354 GetCachedRecentRedirects(page_url, &redirect_list); 2355 2356 scoped_ptr<FaviconChangedDetails> changed_details(new FaviconChangedDetails); 2357 for (size_t i = 0; i < redirect_list.size(); ++i) 2358 changed_details->urls.insert(redirect_list[i]); 2359 2360 BroadcastNotifications(chrome::NOTIFICATION_FAVICON_CHANGED, 2361 changed_details.PassAs<HistoryDetails>()); 2362} 2363 2364void HistoryBackend::Commit() { 2365 if (!db_) 2366 return; 2367 2368 // Note that a commit may not actually have been scheduled if a caller 2369 // explicitly calls this instead of using ScheduleCommit. Likewise, we 2370 // may reset the flag written by a pending commit. But this is OK! It 2371 // will merely cause extra commits (which is kind of the idea). We 2372 // could optimize more for this case (we may get two extra commits in 2373 // some cases) but it hasn't been important yet. 2374 CancelScheduledCommit(); 2375 2376 db_->CommitTransaction(); 2377 DCHECK(db_->transaction_nesting() == 0) << "Somebody left a transaction open"; 2378 db_->BeginTransaction(); 2379 2380 if (thumbnail_db_) { 2381 thumbnail_db_->CommitTransaction(); 2382 DCHECK(thumbnail_db_->transaction_nesting() == 0) << 2383 "Somebody left a transaction open"; 2384 thumbnail_db_->BeginTransaction(); 2385 } 2386} 2387 2388void HistoryBackend::ScheduleCommit() { 2389 if (scheduled_commit_.get()) 2390 return; 2391 scheduled_commit_ = new CommitLaterTask(this); 2392 base::MessageLoop::current()->PostDelayedTask( 2393 FROM_HERE, 2394 base::Bind(&CommitLaterTask::RunCommit, scheduled_commit_.get()), 2395 base::TimeDelta::FromSeconds(kCommitIntervalSeconds)); 2396} 2397 2398void HistoryBackend::CancelScheduledCommit() { 2399 if (scheduled_commit_.get()) { 2400 scheduled_commit_->Cancel(); 2401 scheduled_commit_ = NULL; 2402 } 2403} 2404 2405void HistoryBackend::ProcessDBTaskImpl() { 2406 if (!db_) { 2407 // db went away, release all the refs. 2408 ReleaseDBTasks(); 2409 return; 2410 } 2411 2412 // Remove any canceled tasks. 2413 while (!db_task_requests_.empty() && db_task_requests_.front()->canceled()) { 2414 db_task_requests_.front()->Release(); 2415 db_task_requests_.pop_front(); 2416 } 2417 if (db_task_requests_.empty()) 2418 return; 2419 2420 // Run the first task. 2421 HistoryDBTaskRequest* request = db_task_requests_.front(); 2422 db_task_requests_.pop_front(); 2423 if (request->value->RunOnDBThread(this, db_.get())) { 2424 // The task is done. Notify the callback. 2425 request->ForwardResult(); 2426 // We AddRef'd the request before adding, need to release it now. 2427 request->Release(); 2428 } else { 2429 // Tasks wants to run some more. Schedule it at the end of current tasks. 2430 db_task_requests_.push_back(request); 2431 // And process it after an invoke later. 2432 base::MessageLoop::current()->PostTask( 2433 FROM_HERE, base::Bind(&HistoryBackend::ProcessDBTaskImpl, this)); 2434 } 2435} 2436 2437void HistoryBackend::ReleaseDBTasks() { 2438 for (std::list<HistoryDBTaskRequest*>::iterator i = 2439 db_task_requests_.begin(); i != db_task_requests_.end(); ++i) { 2440 (*i)->Release(); 2441 } 2442 db_task_requests_.clear(); 2443} 2444 2445//////////////////////////////////////////////////////////////////////////////// 2446// 2447// Generic operations 2448// 2449//////////////////////////////////////////////////////////////////////////////// 2450 2451void HistoryBackend::DeleteURLs(const std::vector<GURL>& urls) { 2452 expirer_.DeleteURLs(urls); 2453 2454 db_->GetStartDate(&first_recorded_time_); 2455 // Force a commit, if the user is deleting something for privacy reasons, we 2456 // want to get it on disk ASAP. 2457 Commit(); 2458} 2459 2460void HistoryBackend::DeleteURL(const GURL& url) { 2461 expirer_.DeleteURL(url); 2462 2463 db_->GetStartDate(&first_recorded_time_); 2464 // Force a commit, if the user is deleting something for privacy reasons, we 2465 // want to get it on disk ASAP. 2466 Commit(); 2467} 2468 2469void HistoryBackend::ExpireHistoryBetween( 2470 const std::set<GURL>& restrict_urls, 2471 Time begin_time, 2472 Time end_time) { 2473 if (!db_) 2474 return; 2475 2476 if (begin_time.is_null() && (end_time.is_null() || end_time.is_max()) && 2477 restrict_urls.empty()) { 2478 // Special case deleting all history so it can be faster and to reduce the 2479 // possibility of an information leak. 2480 DeleteAllHistory(); 2481 } else { 2482 // Clearing parts of history, have the expirer do the depend 2483 expirer_.ExpireHistoryBetween(restrict_urls, begin_time, end_time); 2484 2485 // Force a commit, if the user is deleting something for privacy reasons, 2486 // we want to get it on disk ASAP. 2487 Commit(); 2488 } 2489 2490 if (begin_time <= first_recorded_time_) 2491 db_->GetStartDate(&first_recorded_time_); 2492} 2493 2494void HistoryBackend::ExpireHistoryForTimes( 2495 const std::set<base::Time>& times, 2496 base::Time begin_time, base::Time end_time) { 2497 if (times.empty() || !db_) 2498 return; 2499 2500 DCHECK(*times.begin() >= begin_time) 2501 << "Min time is before begin time: " 2502 << times.begin()->ToJsTime() << " v.s. " << begin_time.ToJsTime(); 2503 DCHECK(*times.rbegin() < end_time) 2504 << "Max time is after end time: " 2505 << times.rbegin()->ToJsTime() << " v.s. " << end_time.ToJsTime(); 2506 2507 history::QueryOptions options; 2508 options.begin_time = begin_time; 2509 options.end_time = end_time; 2510 options.duplicate_policy = QueryOptions::KEEP_ALL_DUPLICATES; 2511 QueryResults results; 2512 QueryHistoryBasic(options, &results); 2513 2514 // 1st pass: find URLs that are visited at one of |times|. 2515 std::set<GURL> urls; 2516 for (size_t i = 0; i < results.size(); ++i) { 2517 if (times.count(results[i].visit_time()) > 0) 2518 urls.insert(results[i].url()); 2519 } 2520 if (urls.empty()) 2521 return; 2522 2523 // 2nd pass: collect all visit times of those URLs. 2524 std::vector<base::Time> times_to_expire; 2525 for (size_t i = 0; i < results.size(); ++i) { 2526 if (urls.count(results[i].url())) 2527 times_to_expire.push_back(results[i].visit_time()); 2528 } 2529 2530 // Put the times in reverse chronological order and remove 2531 // duplicates (for expirer_.ExpireHistoryForTimes()). 2532 std::sort(times_to_expire.begin(), times_to_expire.end(), 2533 std::greater<base::Time>()); 2534 times_to_expire.erase( 2535 std::unique(times_to_expire.begin(), times_to_expire.end()), 2536 times_to_expire.end()); 2537 2538 // Expires by times and commit. 2539 DCHECK(!times_to_expire.empty()); 2540 expirer_.ExpireHistoryForTimes(times_to_expire); 2541 Commit(); 2542 2543 DCHECK(times_to_expire.back() >= first_recorded_time_); 2544 // Update |first_recorded_time_| if we expired it. 2545 if (times_to_expire.back() == first_recorded_time_) 2546 db_->GetStartDate(&first_recorded_time_); 2547} 2548 2549void HistoryBackend::ExpireHistory( 2550 const std::vector<history::ExpireHistoryArgs>& expire_list) { 2551 if (db_) { 2552 bool update_first_recorded_time = false; 2553 2554 for (std::vector<history::ExpireHistoryArgs>::const_iterator it = 2555 expire_list.begin(); it != expire_list.end(); ++it) { 2556 expirer_.ExpireHistoryBetween(it->urls, it->begin_time, it->end_time); 2557 2558 if (it->begin_time < first_recorded_time_) 2559 update_first_recorded_time = true; 2560 } 2561 Commit(); 2562 2563 // Update |first_recorded_time_| if any deletion might have affected it. 2564 if (update_first_recorded_time) 2565 db_->GetStartDate(&first_recorded_time_); 2566 } 2567} 2568 2569void HistoryBackend::URLsNoLongerBookmarked(const std::set<GURL>& urls) { 2570 if (!db_) 2571 return; 2572 2573 for (std::set<GURL>::const_iterator i = urls.begin(); i != urls.end(); ++i) { 2574 URLRow url_row; 2575 if (!db_->GetRowForURL(*i, &url_row)) 2576 continue; // The URL isn't in the db; nothing to do. 2577 2578 VisitVector visits; 2579 db_->GetVisitsForURL(url_row.id(), &visits); 2580 2581 if (visits.empty()) 2582 expirer_.DeleteURL(*i); // There are no more visits; nuke the URL. 2583 } 2584} 2585 2586void HistoryBackend::DatabaseErrorCallback(int error, sql::Statement* stmt) { 2587 if (!scheduled_kill_db_ && sql::IsErrorCatastrophic(error)) { 2588 scheduled_kill_db_ = true; 2589 // Don't just do the close/delete here, as we are being called by |db| and 2590 // that seems dangerous. 2591 // TODO(shess): Consider changing KillHistoryDatabase() to use 2592 // RazeAndClose(). Then it can be cleared immediately. 2593 base::MessageLoop::current()->PostTask( 2594 FROM_HERE, 2595 base::Bind(&HistoryBackend::KillHistoryDatabase, this)); 2596 } 2597} 2598 2599void HistoryBackend::KillHistoryDatabase() { 2600 scheduled_kill_db_ = false; 2601 if (!db_) 2602 return; 2603 2604 // Rollback transaction because Raze() cannot be called from within a 2605 // transaction. 2606 db_->RollbackTransaction(); 2607 bool success = db_->Raze(); 2608 UMA_HISTOGRAM_BOOLEAN("History.KillHistoryDatabaseResult", success); 2609 2610#if defined(OS_ANDROID) 2611 // Release AndroidProviderBackend before other objects. 2612 android_provider_backend_.reset(); 2613#endif 2614 2615 // The expirer keeps tabs on the active databases. Tell it about the 2616 // databases which will be closed. 2617 expirer_.SetDatabases(NULL, NULL); 2618 2619 // Reopen a new transaction for |db_| for the sake of CloseAllDatabases(). 2620 db_->BeginTransaction(); 2621 CloseAllDatabases(); 2622} 2623 2624void HistoryBackend::ProcessDBTask( 2625 scoped_refptr<HistoryDBTaskRequest> request) { 2626 DCHECK(request.get()); 2627 if (request->canceled()) 2628 return; 2629 2630 bool task_scheduled = !db_task_requests_.empty(); 2631 // Make sure we up the refcount of the request. ProcessDBTaskImpl will 2632 // release when done with the task. 2633 request->AddRef(); 2634 db_task_requests_.push_back(request.get()); 2635 if (!task_scheduled) { 2636 // No other tasks are scheduled. Process request now. 2637 ProcessDBTaskImpl(); 2638 } 2639} 2640 2641void HistoryBackend::BroadcastNotifications( 2642 int type, 2643 scoped_ptr<HistoryDetails> details) { 2644 // |delegate_| may be NULL if |this| is in the process of closing (closed by 2645 // HistoryService -> HistoryBackend::Closing(). 2646 if (delegate_) 2647 delegate_->BroadcastNotifications(type, details.Pass()); 2648} 2649 2650void HistoryBackend::NotifySyncURLsModified(URLRows* rows) { 2651 if (typed_url_syncable_service_.get()) 2652 typed_url_syncable_service_->OnUrlsModified(rows); 2653} 2654 2655void HistoryBackend::NotifySyncURLsDeleted(bool all_history, 2656 bool expired, 2657 URLRows* rows) { 2658 if (typed_url_syncable_service_.get()) 2659 typed_url_syncable_service_->OnUrlsDeleted(all_history, expired, rows); 2660} 2661 2662// Deleting -------------------------------------------------------------------- 2663 2664void HistoryBackend::DeleteAllHistory() { 2665 // Our approach to deleting all history is: 2666 // 1. Copy the bookmarks and their dependencies to new tables with temporary 2667 // names. 2668 // 2. Delete the original tables. Since tables can not share pages, we know 2669 // that any data we don't want to keep is now in an unused page. 2670 // 3. Renaming the temporary tables to match the original. 2671 // 4. Vacuuming the database to delete the unused pages. 2672 // 2673 // Since we are likely to have very few bookmarks and their dependencies 2674 // compared to all history, this is also much faster than just deleting from 2675 // the original tables directly. 2676 2677 // Get the bookmarked URLs. 2678 std::vector<URLAndTitle> starred_urls; 2679 HistoryClient* history_client = GetHistoryClient(); 2680 if (history_client) 2681 history_client->GetBookmarks(&starred_urls); 2682 2683 URLRows kept_urls; 2684 for (size_t i = 0; i < starred_urls.size(); i++) { 2685 URLRow row; 2686 if (!db_->GetRowForURL(starred_urls[i].url, &row)) 2687 continue; 2688 2689 // Clear the last visit time so when we write these rows they are "clean." 2690 row.set_last_visit(Time()); 2691 row.set_visit_count(0); 2692 row.set_typed_count(0); 2693 kept_urls.push_back(row); 2694 } 2695 2696 // Clear thumbnail and favicon history. The favicons for the given URLs will 2697 // be kept. 2698 if (!ClearAllThumbnailHistory(kept_urls)) { 2699 LOG(ERROR) << "Thumbnail history could not be cleared"; 2700 // We continue in this error case. If the user wants to delete their 2701 // history, we should delete as much as we can. 2702 } 2703 2704 // ClearAllMainHistory will change the IDs of the URLs in kept_urls. 2705 // Therefore, we clear the list afterwards to make sure nobody uses this 2706 // invalid data. 2707 if (!ClearAllMainHistory(kept_urls)) 2708 LOG(ERROR) << "Main history could not be cleared"; 2709 kept_urls.clear(); 2710 2711 db_->GetStartDate(&first_recorded_time_); 2712 2713 // Send out the notification that history is cleared. The in-memory database 2714 // will pick this up and clear itself. 2715 scoped_ptr<URLsDeletedDetails> details(new URLsDeletedDetails); 2716 details->all_history = true; 2717 NotifySyncURLsDeleted(true, false, NULL); 2718 BroadcastNotifications(chrome::NOTIFICATION_HISTORY_URLS_DELETED, 2719 details.PassAs<HistoryDetails>()); 2720} 2721 2722bool HistoryBackend::ClearAllThumbnailHistory(const URLRows& kept_urls) { 2723 if (!thumbnail_db_) { 2724 // When we have no reference to the thumbnail database, maybe there was an 2725 // error opening it. In this case, we just try to blow it away to try to 2726 // fix the error if it exists. This may fail, in which case either the 2727 // file doesn't exist or there's no more we can do. 2728 sql::Connection::Delete(GetFaviconsFileName()); 2729 2730 // Older version of the database. 2731 sql::Connection::Delete(GetThumbnailFileName()); 2732 return true; 2733 } 2734 2735 // Urls to retain mappings for. 2736 std::vector<GURL> urls_to_keep; 2737 for (URLRows::const_iterator i = kept_urls.begin(); 2738 i != kept_urls.end(); ++i) { 2739 urls_to_keep.push_back(i->url()); 2740 } 2741 2742 // Isolate from any long-running transaction. 2743 thumbnail_db_->CommitTransaction(); 2744 thumbnail_db_->BeginTransaction(); 2745 2746 // TODO(shess): If this fails, perhaps the database should be razed 2747 // or deleted. 2748 if (!thumbnail_db_->RetainDataForPageUrls(urls_to_keep)) { 2749 thumbnail_db_->RollbackTransaction(); 2750 thumbnail_db_->BeginTransaction(); 2751 return false; 2752 } 2753 2754#if defined(OS_ANDROID) 2755 // TODO (michaelbai): Add the unit test once AndroidProviderBackend is 2756 // avaliable in HistoryBackend. 2757 db_->ClearAndroidURLRows(); 2758#endif 2759 2760 // Vacuum to remove all the pages associated with the dropped tables. There 2761 // must be no transaction open on the table when we do this. We assume that 2762 // our long-running transaction is open, so we complete it and start it again. 2763 DCHECK(thumbnail_db_->transaction_nesting() == 1); 2764 thumbnail_db_->CommitTransaction(); 2765 thumbnail_db_->Vacuum(); 2766 thumbnail_db_->BeginTransaction(); 2767 return true; 2768} 2769 2770bool HistoryBackend::ClearAllMainHistory(const URLRows& kept_urls) { 2771 // Create the duplicate URL table. We will copy the kept URLs into this. 2772 if (!db_->CreateTemporaryURLTable()) 2773 return false; 2774 2775 // Insert the URLs into the temporary table. 2776 for (URLRows::const_iterator i = kept_urls.begin(); i != kept_urls.end(); 2777 ++i) { 2778 db_->AddTemporaryURL(*i); 2779 } 2780 2781 // Replace the original URL table with the temporary one. 2782 if (!db_->CommitTemporaryURLTable()) 2783 return false; 2784 2785 // Delete the old tables and recreate them empty. 2786 db_->RecreateAllTablesButURL(); 2787 2788 // Vacuum to reclaim the space from the dropped tables. This must be done 2789 // when there is no transaction open, and we assume that our long-running 2790 // transaction is currently open. 2791 db_->CommitTransaction(); 2792 db_->Vacuum(); 2793 db_->BeginTransaction(); 2794 db_->GetStartDate(&first_recorded_time_); 2795 2796 return true; 2797} 2798 2799HistoryClient* HistoryBackend::GetHistoryClient() { 2800 if (history_client_) 2801 history_client_->BlockUntilBookmarksLoaded(); 2802 return history_client_; 2803} 2804 2805void HistoryBackend::NotifyVisitObservers(const VisitRow& visit) { 2806 BriefVisitInfo info; 2807 info.url_id = visit.url_id; 2808 info.time = visit.visit_time; 2809 info.transition = visit.transition; 2810 // If we don't have a delegate yet during setup or shutdown, we will drop 2811 // these notifications. 2812 if (delegate_) 2813 delegate_->NotifyVisitDBObserversOnAddVisit(info); 2814} 2815 2816#if defined(OS_ANDROID) 2817void HistoryBackend::PopulateMostVisitedURLMap() { 2818 MostVisitedURLList most_visited_urls; 2819 QueryMostVisitedURLsImpl(kPageVisitStatsMaxTopSites, kSegmentDataRetention, 2820 &most_visited_urls); 2821 2822 DCHECK_LE(most_visited_urls.size(), kPageVisitStatsMaxTopSites); 2823 for (size_t i = 0; i < most_visited_urls.size(); ++i) { 2824 most_visited_urls_map_[most_visited_urls[i].url] = i; 2825 for (size_t j = 0; j < most_visited_urls[i].redirects.size(); ++j) 2826 most_visited_urls_map_[most_visited_urls[i].redirects[j]] = i; 2827 } 2828} 2829 2830void HistoryBackend::RecordTopPageVisitStats(const GURL& url) { 2831 int rank = kPageVisitStatsMaxTopSites; 2832 std::map<GURL, int>::const_iterator it = most_visited_urls_map_.find(url); 2833 if (it != most_visited_urls_map_.end()) 2834 rank = (*it).second; 2835 UMA_HISTOGRAM_ENUMERATION("History.TopSitesVisitsByRank", 2836 rank, kPageVisitStatsMaxTopSites + 1); 2837} 2838#endif 2839 2840} // namespace history 2841