blob: cbd6754cfa069317f42bc3d4f1128a16092d3e39 [file] [log] [blame]
// 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 "app/l10n_util.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/browser_window.h"
#include "chrome/browser/chrome_thread.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/profile.h"
#include "chrome/browser/visitedlink_master.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/notification_service.h"
#include "chrome/common/thumbnail_score.h"
#include "chrome/common/url_constants.h"
#include "grit/chromium_strings.h"
#include "grit/generated_resources.h"
using base::Time;
using history::HistoryBackend;
static const char* kHistoryThreadName = "Chrome_HistoryThread";
// 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<HistoryService> history_service_;
MessageLoop* message_loop_;
};
// static
const history::StarID HistoryService::kBookmarkBarID = 1;
HistoryService::HistoryService()
: thread_(new base::Thread(kHistoryThreadName)),
profile_(NULL),
backend_loaded_(false) {
// Is NULL when running generate_profile.
if (NotificationService::current()) {
registrar_.Add(this, NotificationType::HISTORY_URLS_DELETED,
Source<Profile>(profile_));
}
}
HistoryService::HistoryService(Profile* profile)
: thread_(new base::Thread(kHistoryThreadName)),
profile_(profile),
backend_loaded_(false) {
registrar_.Add(this, NotificationType::HISTORY_URLS_DELETED,
Source<Profile>(profile_));
}
HistoryService::~HistoryService() {
// Shutdown the backend. This does nothing if Cleanup was already invoked.
Cleanup();
}
bool HistoryService::Init(const FilePath& history_dir,
BookmarkService* bookmark_service) {
if (!thread_->Start())
return false;
// Create the history backend.
scoped_refptr<HistoryBackend> 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.
base::Thread* 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<GURL>& 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,
int max_result_count,
SegmentQueryCallback* callback) {
return Schedule(PRIORITY_UI, &HistoryBackend::QuerySegmentUsage,
consumer, new history::QuerySegmentUsageRequest(callback),
from_time, max_result_count);
}
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 history::RedirectList& redirects,
bool did_replace_entry) {
AddPage(url, Time::Now(), id_scope, page_id, referrer, transition, redirects,
did_replace_entry);
}
void HistoryService::AddPage(const GURL& url,
Time time,
const void* id_scope,
int32 page_id,
const GURL& referrer,
PageTransition::Type transition,
const history::RedirectList& redirects,
bool did_replace_entry) {
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))
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<history::HistoryAddPageArgs> request(
new history::HistoryAddPageArgs(url, time, id_scope, page_id,
referrer, redirects, transition,
did_replace_entry));
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<history::URLRow> rows;
rows.push_back(row);
ScheduleAndForget(PRIORITY_NORMAL,
&HistoryBackend::AddPagesWithDetails, rows);
}
void HistoryService::AddPagesWithDetails(
const std::vector<history::URLRow>& info) {
// Add to the visited links system.
VisitedLinkMaster* visited_links;
if (profile_ && (visited_links = profile_->GetVisitedLinkMaster())) {
std::vector<GURL> urls;
urls.reserve(info.size());
for (std::vector<history::URLRow>::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<unsigned char>& image_data) {
if (!CanAddURL(page_url))
return;
ScheduleAndForget(PRIORITY_NORMAL, &HistoryBackend::SetFavIcon,
page_url, icon_url,
scoped_refptr<RefCountedBytes>(new RefCountedBytes(image_data)));
}
void HistoryService::SetFavIconOutOfDateForPage(const GURL& page_url) {
ScheduleAndForget(PRIORITY_NORMAL,
&HistoryBackend::SetFavIconOutOfDateForPage, page_url);
}
void HistoryService::SetImportedFavicons(
const std::vector<history::ImportedFavIconUsage>& 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::UpdateDownloadPath(const std::wstring& path,
int64 db_handle) {
ScheduleAndForget(PRIORITY_NORMAL, &HistoryBackend::UpdateDownloadPath,
path, 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::QueryRedirectsTo(
const GURL& to_url,
CancelableRequestConsumerBase* consumer,
QueryRedirectsCallback* callback) {
return Schedule(PRIORITY_NORMAL, &HistoryBackend::QueryRedirectsTo, consumer,
new history::QueryRedirectsRequest(callback), to_url);
}
HistoryService::Handle HistoryService::GetVisitCountToHost(
const GURL& url,
CancelableRequestConsumerBase* consumer,
GetVisitCountToHostCallback* callback) {
return Schedule(PRIORITY_UI, &HistoryBackend::GetVisitCountToHost, consumer,
new history::GetVisitCountToHostRequest(callback), url);
}
HistoryService::Handle HistoryService::QueryTopURLsAndRedirects(
int result_count,
CancelableRequestConsumerBase* consumer,
QueryTopURLsAndRedirectsCallback* callback) {
return Schedule(PRIORITY_NORMAL, &HistoryBackend::QueryTopURLsAndRedirects,
consumer, new history::QueryTopURLsAndRedirectsRequest(callback),
result_count);
}
void HistoryService::Observe(NotificationType type,
const NotificationSource& source,
const NotificationDetails& details) {
if (type != NotificationType::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<history::URLsDeletedDetails> 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<HistoryURLProvider>(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(chrome::kJavaScriptScheme) ||
url.SchemeIs(chrome::kChromeUIScheme) ||
url.SchemeIs(chrome::kViewSourceScheme) ||
url.SchemeIs(chrome::kChromeInternalScheme) ||
url.SchemeIs(chrome::kPrintScheme))
return false;
if (url.SchemeIs(chrome::kAboutScheme)) {
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() {
#if defined(OS_WIN)
// Find the last browser window to display our message box from.
Browser* cur_browser = BrowserList::GetLastActive();
// TODO(brettw): Do this some other way or beng will kick you. e.g. move to
// BrowserView.
HWND parent_hwnd =
reinterpret_cast<HWND>(cur_browser->window()->GetNativeHandle());
HWND cur_hwnd = cur_browser ? parent_hwnd : 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);
#else
// TODO(port): factor this out into platform-specific code.
NOTIMPLEMENTED();
#endif
}
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<history::HistoryDetails> 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<Profile> 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<history::HistoryDetails> det(details_deleted);
NotificationService::current()->Notify(type, source, det);
}
void HistoryService::OnDBLoaded() {
LOG(INFO) << "History backend finished loading";
backend_loaded_ = true;
NotificationService::current()->Notify(NotificationType::HISTORY_LOADED,
Source<Profile>(profile_),
Details<HistoryService>(this));
}