// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // The history system runs on a background thread so that potentially slow // database operations don't delay the browser. This backend processing is // represented by HistoryBackend. The HistoryService's job is to dispatch to // that thread. // // Main thread History thread // ----------- -------------- // HistoryService <----------------> HistoryBackend // -> HistoryDatabase // -> SQLite connection to History // -> ArchivedDatabase // -> SQLite connection to Archived History // -> TextDatabaseManager // -> SQLite connection to one month's data // -> SQLite connection to one month's data // ... // -> ThumbnailDatabase // -> SQLite connection to Thumbnails // (and favicons) #include "chrome/browser/history/history.h" #include "base/file_util.h" #include "base/message_loop.h" #include "base/path_service.h" #include "base/ref_counted.h" #include "base/task.h" #include "chrome/browser/autocomplete/history_url_provider.h" #include "chrome/browser/browser_list.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/history/download_types.h" #include "chrome/browser/history/history_backend.h" #include "chrome/browser/history/history_types.h" #include "chrome/browser/history/in_memory_database.h" #include "chrome/browser/history/in_memory_history_backend.h" #include "chrome/browser/visitedlink_master.h" #include "chrome/common/chrome_constants.h" #include "chrome/common/l10n_util.h" #include "chrome/common/notification_service.h" #include "chrome/common/sqlite_utils.h" #include "chrome/common/thumbnail_score.h" #include "chromium_strings.h" #include "generated_resources.h" using history::HistoryBackend; // Sends messages from the backend to us on the main thread. This must be a // separate class from the history service so that it can hold a reference to // the history service (otherwise we would have to manually AddRef and // Release when the Backend has a reference to us). class HistoryService::BackendDelegate : public HistoryBackend::Delegate { public: explicit BackendDelegate(HistoryService* history_service) : history_service_(history_service), message_loop_(MessageLoop::current()) { } virtual void NotifyTooNew() { // Send the backend to the history service on the main thread. message_loop_->PostTask(FROM_HERE, NewRunnableMethod(history_service_.get(), &HistoryService::NotifyTooNew)); } virtual void SetInMemoryBackend( history::InMemoryHistoryBackend* backend) { // Send the backend to the history service on the main thread. message_loop_->PostTask(FROM_HERE, NewRunnableMethod(history_service_.get(), &HistoryService::SetInMemoryBackend, backend)); } virtual void BroadcastNotifications(NotificationType type, history::HistoryDetails* details) { // Send the notification to the history service on the main thread. message_loop_->PostTask(FROM_HERE, NewRunnableMethod(history_service_.get(), &HistoryService::BroadcastNotifications, type, details)); } virtual void DBLoaded() { message_loop_->PostTask(FROM_HERE, NewRunnableMethod(history_service_.get(), &HistoryService::OnDBLoaded)); } private: scoped_refptr history_service_; MessageLoop* message_loop_; }; // static const history::StarID HistoryService::kBookmarkBarID = 1; HistoryService::HistoryService() : thread_(new ChromeThread(ChromeThread::HISTORY)), profile_(NULL), backend_loaded_(false) { if (NotificationService::current()) { // Is NULL when running generate_profile. NotificationService::current()->AddObserver( this, NOTIFY_HISTORY_URLS_DELETED, Source(profile_)); } } HistoryService::HistoryService(Profile* profile) : thread_(new ChromeThread(ChromeThread::HISTORY)), profile_(profile), backend_loaded_(false) { NotificationService::current()->AddObserver( this, NOTIFY_HISTORY_URLS_DELETED, Source(profile_)); } HistoryService::~HistoryService() { // Shutdown the backend. This does nothing if Cleanup was already invoked. Cleanup(); // Unregister for notifications. if (NotificationService::current()) { // Is NULL when running generate_profile. NotificationService::current()->RemoveObserver( this, NOTIFY_HISTORY_URLS_DELETED, Source(profile_)); } } bool HistoryService::Init(const std::wstring& history_dir, BookmarkService* bookmark_service) { if (!thread_->Start()) return false; // Create the history backend. scoped_refptr backend( new HistoryBackend(history_dir, new BackendDelegate(this), bookmark_service)); history_backend_.swap(backend); ScheduleAndForget(PRIORITY_UI, &HistoryBackend::Init); return true; } void HistoryService::Cleanup() { if (!thread_) { // We've already cleaned up. return; } // Shutdown is a little subtle. The backend's destructor must run on the // history thread since it is not threadsafe. So this thread must not be the // last thread holding a reference to the backend, or a crash could happen. // // We have a reference to the history backend. There is also an extra // reference held by our delegate installed in the backend, which // HistoryBackend::Closing will release. This means if we scheduled a call // to HistoryBackend::Closing and *then* released our backend reference, there // will be a race between us and the backend's Closing function to see who is // the last holder of a reference. If the backend thread's Closing manages to // run before we release our backend refptr, the last reference will be held // by this thread and the destructor will be called from here. // // Therefore, we create a task to run the Closing operation first. This holds // a reference to the backend. Then we release our reference, then we schedule // the task to run. After the task runs, it will delete its reference from // the history thread, ensuring everything works properly. Task* closing_task = NewRunnableMethod(history_backend_.get(), &HistoryBackend::Closing); history_backend_ = NULL; ScheduleTask(PRIORITY_NORMAL, closing_task); // Delete the thread, which joins with the background thread. We defensively // NULL the pointer before deleting it in case somebody tries to use it // during shutdown, but this shouldn't happen. ChromeThread* thread = thread_; thread_ = NULL; delete thread; } void HistoryService::NotifyRenderProcessHostDestruction(const void* host) { ScheduleAndForget(PRIORITY_NORMAL, &HistoryBackend::NotifyRenderProcessHostDestruction, host); } history::URLDatabase* HistoryService::in_memory_database() const { if (in_memory_backend_.get()) return in_memory_backend_->db(); return NULL; } void HistoryService::SetSegmentPresentationIndex(int64 segment_id, int index) { ScheduleAndForget(PRIORITY_UI, &HistoryBackend::SetSegmentPresentationIndex, segment_id, index); } void HistoryService::SetKeywordSearchTermsForURL(const GURL& url, TemplateURL::IDType keyword_id, const std::wstring& term) { ScheduleAndForget(PRIORITY_UI, &HistoryBackend::SetKeywordSearchTermsForURL, url, keyword_id, term); } void HistoryService::DeleteAllSearchTermsForKeyword( TemplateURL::IDType keyword_id) { ScheduleAndForget(PRIORITY_UI, &HistoryBackend::DeleteAllSearchTermsForKeyword, keyword_id); } HistoryService::Handle HistoryService::GetMostRecentKeywordSearchTerms( TemplateURL::IDType keyword_id, const std::wstring& prefix, int max_count, CancelableRequestConsumerBase* consumer, GetMostRecentKeywordSearchTermsCallback* callback) { return Schedule(PRIORITY_UI, &HistoryBackend::GetMostRecentKeywordSearchTerms, consumer, new history::GetMostRecentKeywordSearchTermsRequest(callback), keyword_id, prefix, max_count); } void HistoryService::URLsNoLongerBookmarked(const std::set& urls) { ScheduleAndForget(PRIORITY_NORMAL, &HistoryBackend::URLsNoLongerBookmarked, urls); } HistoryService::Handle HistoryService::ScheduleDBTask( HistoryDBTask* task, CancelableRequestConsumerBase* consumer) { history::HistoryDBTaskRequest* request = new history::HistoryDBTaskRequest( NewCallback(task, &HistoryDBTask::DoneRunOnMainThread)); request->value = task; // The value is the task to execute. return Schedule(PRIORITY_UI, &HistoryBackend::ProcessDBTask, consumer, request); } HistoryService::Handle HistoryService::QuerySegmentUsageSince( CancelableRequestConsumerBase* consumer, const Time from_time, SegmentQueryCallback* callback) { return Schedule(PRIORITY_UI, &HistoryBackend::QuerySegmentUsage, consumer, new history::QuerySegmentUsageRequest(callback), from_time); } void HistoryService::SetOnBackendDestroyTask(Task* task) { ScheduleAndForget(PRIORITY_NORMAL, &HistoryBackend::SetOnBackendDestroyTask, MessageLoop::current(), task); } void HistoryService::AddPage(const GURL& url, const void* id_scope, int32 page_id, const GURL& referrer, PageTransition::Type transition, const RedirectList& redirects) { AddPage(url, Time::Now(), id_scope, page_id, referrer, transition, redirects); } void HistoryService::AddPage(const GURL& url, Time time, const void* id_scope, int32 page_id, const GURL& referrer, PageTransition::Type transition, const RedirectList& redirects) { DCHECK(history_backend_) << "History service being called after cleanup"; // Filter out unwanted URLs. We don't add auto-subframe URLs. They are a // large part of history (think iframes for ads) and we never display them in // history UI. We will still add manual subframes, which are ones the user // has clicked on to get. if (!CanAddURL(url) || PageTransition::StripQualifier(transition) == PageTransition::AUTO_SUBFRAME) return; // Add link & all redirects to visited link list. VisitedLinkMaster* visited_links; if (profile_ && (visited_links = profile_->GetVisitedLinkMaster())) { visited_links->AddURL(url); if (!redirects.empty()) { // We should not be asked to add a page in the middle of a redirect chain. DCHECK(redirects[redirects.size() - 1] == url); // We need the !redirects.empty() condition above since size_t is unsigned // and will wrap around when we subtract one from a 0 size. for (size_t i = 0; i < redirects.size() - 1; i++) visited_links->AddURL(redirects[i]); } } scoped_refptr request( new history::HistoryAddPageArgs(url, time, id_scope, page_id, referrer, redirects, transition)); ScheduleAndForget(PRIORITY_NORMAL, &HistoryBackend::AddPage, request); } void HistoryService::SetPageTitle(const GURL& url, const std::wstring& title) { ScheduleAndForget(PRIORITY_NORMAL, &HistoryBackend::SetPageTitle, url, title); } void HistoryService::AddPageWithDetails(const GURL& url, const std::wstring& title, int visit_count, int typed_count, Time last_visit, bool hidden) { // Filter out unwanted URLs. if (!CanAddURL(url)) return; // Add to the visited links system. VisitedLinkMaster* visited_links; if (profile_ && (visited_links = profile_->GetVisitedLinkMaster())) visited_links->AddURL(url); history::URLRow row(url); row.set_title(title); row.set_visit_count(visit_count); row.set_typed_count(typed_count); row.set_last_visit(last_visit); row.set_hidden(hidden); std::vector rows; rows.push_back(row); ScheduleAndForget(PRIORITY_NORMAL, &HistoryBackend::AddPagesWithDetails, rows); } void HistoryService::AddPagesWithDetails( const std::vector& info) { // Add to the visited links system. VisitedLinkMaster* visited_links; if (profile_ && (visited_links = profile_->GetVisitedLinkMaster())) { std::vector urls; urls.reserve(info.size()); for (std::vector::const_iterator i = info.begin(); i != info.end(); ++i) urls.push_back(i->url()); visited_links->AddURLs(urls); } ScheduleAndForget(PRIORITY_NORMAL, &HistoryBackend::AddPagesWithDetails, info); } void HistoryService::SetPageContents(const GURL& url, const std::wstring& contents) { if (!CanAddURL(url)) return; ScheduleAndForget(PRIORITY_LOW, &HistoryBackend::SetPageContents, url, contents); } void HistoryService::SetPageThumbnail(const GURL& page_url, const SkBitmap& thumbnail, const ThumbnailScore& score) { if (!CanAddURL(page_url)) return; ScheduleAndForget(PRIORITY_NORMAL, &HistoryBackend::SetPageThumbnail, page_url, thumbnail, score); } HistoryService::Handle HistoryService::GetPageThumbnail( const GURL& page_url, CancelableRequestConsumerBase* consumer, ThumbnailDataCallback* callback) { return Schedule(PRIORITY_NORMAL, &HistoryBackend::GetPageThumbnail, consumer, new history::GetPageThumbnailRequest(callback), page_url); } HistoryService::Handle HistoryService::GetFavIcon( const GURL& icon_url, CancelableRequestConsumerBase* consumer, FavIconDataCallback* callback) { // We always do image requests at lower-than-UI priority even though they // appear in the UI, since they can take a long time and the user can use the // program without them. return Schedule(PRIORITY_NORMAL, &HistoryBackend::GetFavIcon, consumer, new history::GetFavIconRequest(callback), icon_url); } HistoryService::Handle HistoryService::UpdateFavIconMappingAndFetch( const GURL& page_url, const GURL& icon_url, CancelableRequestConsumerBase* consumer, FavIconDataCallback* callback) { return Schedule(PRIORITY_NORMAL, &HistoryBackend::UpdateFavIconMappingAndFetch, consumer, new history::GetFavIconRequest(callback), page_url, icon_url); } HistoryService::Handle HistoryService::GetFavIconForURL( const GURL& page_url, CancelableRequestConsumerBase* consumer, FavIconDataCallback* callback) { return Schedule(PRIORITY_UI, &HistoryBackend::GetFavIconForURL, consumer, new history::GetFavIconRequest(callback), page_url); } void HistoryService::SetFavIcon(const GURL& page_url, const GURL& icon_url, const std::vector& image_data) { if (!CanAddURL(page_url)) return; ScheduleAndForget(PRIORITY_NORMAL, &HistoryBackend::SetFavIcon, page_url, icon_url, scoped_refptr(new RefCountedBytes(image_data))); } void HistoryService::SetFavIconOutOfDateForPage(const GURL& page_url) { ScheduleAndForget(PRIORITY_NORMAL, &HistoryBackend::SetFavIconOutOfDateForPage, page_url); } void HistoryService::SetImportedFavicons( const std::vector& favicon_usage) { ScheduleAndForget(PRIORITY_NORMAL, &HistoryBackend::SetImportedFavicons, favicon_usage); } void HistoryService::IterateURLs(URLEnumerator* enumerator) { ScheduleAndForget(PRIORITY_NORMAL, &HistoryBackend::IterateURLs, enumerator); } HistoryService::Handle HistoryService::QueryURL( const GURL& url, bool want_visits, CancelableRequestConsumerBase* consumer, QueryURLCallback* callback) { return Schedule(PRIORITY_UI, &HistoryBackend::QueryURL, consumer, new history::QueryURLRequest(callback), url, want_visits); } // Downloads ------------------------------------------------------------------- // Handle creation of a download by creating an entry in the history service's // 'downloads' table. HistoryService::Handle HistoryService::CreateDownload( const DownloadCreateInfo& create_info, CancelableRequestConsumerBase* consumer, HistoryService::DownloadCreateCallback* callback) { return Schedule(PRIORITY_NORMAL, &HistoryBackend::CreateDownload, consumer, new history::DownloadCreateRequest(callback), create_info); } // Handle queries for a list of all downloads in the history database's // 'downloads' table. HistoryService::Handle HistoryService::QueryDownloads( CancelableRequestConsumerBase* consumer, DownloadQueryCallback* callback) { return Schedule(PRIORITY_NORMAL, &HistoryBackend::QueryDownloads, consumer, new history::DownloadQueryRequest(callback)); } // Handle updates for a particular download. This is a 'fire and forget' // operation, so we don't need to be called back. void HistoryService::UpdateDownload(int64 received_bytes, int32 state, int64 db_handle) { ScheduleAndForget(PRIORITY_NORMAL, &HistoryBackend::UpdateDownload, received_bytes, state, db_handle); } void HistoryService::RemoveDownload(int64 db_handle) { ScheduleAndForget(PRIORITY_NORMAL, &HistoryBackend::RemoveDownload, db_handle); } void HistoryService::RemoveDownloadsBetween(Time remove_begin, Time remove_end) { ScheduleAndForget(PRIORITY_NORMAL, &HistoryBackend::RemoveDownloadsBetween, remove_begin, remove_end); } HistoryService::Handle HistoryService::SearchDownloads( const std::wstring& search_text, CancelableRequestConsumerBase* consumer, DownloadSearchCallback* callback) { return Schedule(PRIORITY_NORMAL, &HistoryBackend::SearchDownloads, consumer, new history::DownloadSearchRequest(callback), search_text); } HistoryService::Handle HistoryService::QueryHistory( const std::wstring& text_query, const history::QueryOptions& options, CancelableRequestConsumerBase* consumer, QueryHistoryCallback* callback) { return Schedule(PRIORITY_UI, &HistoryBackend::QueryHistory, consumer, new history::QueryHistoryRequest(callback), text_query, options); } HistoryService::Handle HistoryService::QueryRedirectsFrom( const GURL& from_url, CancelableRequestConsumerBase* consumer, QueryRedirectsCallback* callback) { return Schedule(PRIORITY_UI, &HistoryBackend::QueryRedirectsFrom, consumer, new history::QueryRedirectsRequest(callback), from_url); } HistoryService::Handle HistoryService::GetVisitCountToHost( const GURL& url, CancelableRequestConsumerBase* consumer, GetVisitCountToHostCallback* callback) { return Schedule(PRIORITY_UI, &HistoryBackend::GetVisitCountToHost, consumer, new history::GetVisitCountToHostRequest(callback), url); } void HistoryService::Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) { if (type != NOTIFY_HISTORY_URLS_DELETED) { NOTREACHED(); return; } // Update the visited link system for deleted URLs. We will update the // visited link system for added URLs as soon as we get the add // notification (we don't have to wait for the backend, which allows us to // be faster to update the state). // // For deleted URLs, we don't typically know what will be deleted since // delete notifications are by time. We would also like to be more // respectful of privacy and never tell the user something is gone when it // isn't. Therefore, we update the delete URLs after the fact. if (!profile_) return; // No profile, probably unit testing. Details deleted_details(details); VisitedLinkMaster* visited_links = profile_->GetVisitedLinkMaster(); if (!visited_links) return; // Nobody to update. if (deleted_details->all_history) visited_links->DeleteAllURLs(); else // Delete individual ones. visited_links->DeleteURLs(deleted_details->urls); } void HistoryService::ScheduleAutocomplete(HistoryURLProvider* provider, HistoryURLProviderParams* params) { ScheduleAndForget(PRIORITY_UI, &HistoryBackend::ScheduleAutocomplete, scoped_refptr(provider), params); } void HistoryService::ScheduleTask(SchedulePriority priority, Task* task) { // FIXME(brettw) do prioritization. thread_->message_loop()->PostTask(FROM_HERE, task); } bool HistoryService::CanAddURL(const GURL& url) const { if (!url.is_valid()) return false; if (url.SchemeIs("javascript") || url.SchemeIs("chrome-resource") || url.SchemeIs("view-source")) return false; if (url.SchemeIs("about")) { std::string path = url.path(); if (path.empty() || LowerCaseEqualsASCII(path, "blank")) return false; // We allow all other about URLs since the user may like to see things // like "about:memory" or "about:histograms" in their history and // autocomplete. } return true; } void HistoryService::SetInMemoryBackend( history::InMemoryHistoryBackend* mem_backend) { DCHECK(!in_memory_backend_.get()) << "Setting mem DB twice"; in_memory_backend_.reset(mem_backend); // The database requires additional initialization once we own it. in_memory_backend_->AttachToHistoryService(profile_); } void HistoryService::NotifyTooNew() { // Find the last browser window to display our message box from. Browser* cur_browser = BrowserList::GetLastActive(); HWND cur_hwnd = cur_browser ? cur_browser->GetTopLevelHWND() : NULL; std::wstring title = l10n_util::GetString(IDS_PRODUCT_NAME); std::wstring message = l10n_util::GetString(IDS_PROFILE_TOO_NEW_ERROR); MessageBox(cur_hwnd, message.c_str(), title.c_str(), MB_OK | MB_ICONWARNING | MB_TOPMOST); } void HistoryService::DeleteURL(const GURL& url) { // We will update the visited links when we observe the delete notifications. ScheduleAndForget(PRIORITY_NORMAL, &HistoryBackend::DeleteURL, url); } void HistoryService::ExpireHistoryBetween( Time begin_time, Time end_time, CancelableRequestConsumerBase* consumer, ExpireHistoryCallback* callback) { // We will update the visited links when we observe the delete notifications. Schedule(PRIORITY_UI, &HistoryBackend::ExpireHistoryBetween, consumer, new history::ExpireHistoryRequest(callback), begin_time, end_time); } void HistoryService::BroadcastNotifications( NotificationType type, history::HistoryDetails* details_deleted) { // We take ownership of the passed-in pointer and delete it. It was made for // us on another thread, so the caller doesn't know when we will handle it. scoped_ptr details(details_deleted); // TODO(evanm): this is currently necessitated by generate_profile, which // runs without a browser process. generate_profile should really create // a browser process, at which point this check can then be nuked. if (!g_browser_process) return; // The source of all of our notifications is the profile. Note that this // pointer is NULL in unit tests. Source source(profile_); // The details object just contains the pointer to the object that the // backend has allocated for us. The receiver of the notification will cast // this to the proper type. Details det(details_deleted); NotificationService::current()->Notify(type, source, det); } void HistoryService::OnDBLoaded() { LOG(INFO) << "History backend finished loading"; backend_loaded_ = true; NotificationService::current()->Notify(NOTIFY_HISTORY_LOADED, Source(profile_), Details(this)); }