Bug 516940 - Reduce and cleanup Places expiration work at shutdown, r=sdwilsh

This commit is contained in:
Marco Bonardo 2009-10-09 12:30:12 +02:00
parent 53770263be
commit b6d670967a
12 changed files with 458 additions and 577 deletions

View File

@ -21,7 +21,9 @@
*
* Contributor(s):
* Brian Ryner <bryner@brianryner.com> (original author)
* Dietrich Ayala <dietrich@mozilla.com>
* Drew Willcoxon <adw@mozilla.com>
* Marco Bonardo <mak77@bonardo.net>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@ -3030,15 +3032,6 @@ nsNavBookmarks::RemoveObserver(nsINavBookmarkObserver *aObserver)
return mObservers.RemoveWeakElement(aObserver);
}
/**
* Called by the History service when shutting down
*/
nsresult
nsNavBookmarks::OnQuit()
{
return NS_OK;
}
// nsNavBookmarks::nsINavHistoryObserver
NS_IMETHODIMP

View File

@ -21,6 +21,8 @@
*
* Contributor(s):
* Brian Ryner <bryner@brianryner.com> (original author)
* Dietrich Ayala <dietrich@mozilla.com>
* Marco Bonardo <mak77@bonardo.net>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@ -106,9 +108,6 @@ public:
*/
PRBool IsRealBookmark(PRInt64 aPlaceId);
// Called by History service when quitting.
nsresult OnQuit();
nsresult BeginUpdateBatch();
nsresult EndUpdateBatch();

View File

@ -47,7 +47,6 @@
#include "nsNavHistory.h"
#include "nsNavBookmarks.h"
#include "nsAnnotationService.h"
#include "nsIIdleService.h"
#include "nsILivemarkService.h"
#include "nsPlacesTables.h"
@ -187,12 +186,6 @@ using namespace mozilla::places;
#endif // LAZY_ADD
// Perform expiration after 5 minutes of idle time, repeating.
#define EXPIRE_IDLE_TIME_IN_MSECS (5 * 60 * PR_MSEC_PER_SEC)
// Amount of items to expire at idle time.
#define MAX_EXPIRE_RECORDS_ON_IDLE 200
// Limit the number of items in the history for performance reasons
#define EXPIRATION_CAP_SITES 40000
@ -428,7 +421,6 @@ nsNavHistory::nsNavHistory() : mBatchLevel(0),
mBatchHasTransaction(PR_FALSE),
mNowValid(PR_FALSE),
mExpireNowTimer(nsnull),
mExpire(this),
mExpireDaysMin(0),
mExpireDaysMax(0),
mExpireSites(0),
@ -447,7 +439,6 @@ nsNavHistory::nsNavHistory() : mBatchLevel(0),
gHistoryService = this;
}
// nsNavHistory::~nsNavHistory
nsNavHistory::~nsNavHistory()
@ -501,6 +492,10 @@ nsNavHistory::Init()
rv = InitAdditionalDBItems();
NS_ENSURE_SUCCESS(rv, rv);
// Initialize expiration. There's no need to do this before, since just now
// we have a valid database and a working connection.
mExpire = new nsNavHistoryExpire();
// Notify we have finished database initialization.
// Enqueue the notification, so if we init another service that requires
// nsNavHistoryService we don't recursive try to get it.
@ -528,9 +523,6 @@ nsNavHistory::Init()
mLastSessionID = 1;
}
// initialize idle timer
InitializeIdleTimer();
// recent events hash tables
NS_ENSURE_TRUE(mRecentTyped.Init(128), NS_ERROR_OUT_OF_MEMORY);
NS_ENSURE_TRUE(mRecentBookmark.Init(128), NS_ERROR_OUT_OF_MEMORY);
@ -966,25 +958,6 @@ nsNavHistory::InitAdditionalDBItems()
return NS_OK;
}
nsresult
nsNavHistory::InitializeIdleTimer()
{
if (mIdleTimer) {
mIdleTimer->Cancel();
mIdleTimer = nsnull;
}
nsresult rv;
mIdleTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
PRInt32 idleTimerTimeout = EXPIRE_IDLE_TIME_IN_MSECS;
rv = mIdleTimer->InitWithFuncCallback(IdleTimerCallback, this,
idleTimerTimeout,
nsITimer::TYPE_REPEATING_SLACK);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMETHODIMP
nsNavHistory::GetDatabaseStatus(PRUint16 *aDatabaseStatus)
{
@ -4499,7 +4472,7 @@ nsNavHistory::CleanupPlacesOnVisitsDelete(const nsCString& aPlaceIdsQueryString)
// now that visits have been removed, run annotation expiration.
// this will remove all expire-able annotations for these URIs.
(void)mExpire.OnDeleteURI();
(void)mExpire->OnDeleteVisits();
// if the entry is not bookmarked and is not a place: uri
// then we can remove it from moz_places.
@ -4887,7 +4860,7 @@ nsNavHistory::RemoveAllPages()
#endif
// expire everything
mExpire.ClearHistory();
mExpire->ClearHistory();
// Compress DB. Currently commented out because compression is very slow.
// Deleted data will be overwritten with 0s by sqlite.
@ -5498,39 +5471,6 @@ nsNavHistory::AddDocumentRedirect(nsIChannel *aOldChannel,
return NS_OK;
}
nsresult
nsNavHistory::OnIdle()
{
nsresult rv;
nsCOMPtr<nsIIdleService> idleService =
do_GetService("@mozilla.org/widget/idleservice;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
PRUint32 idleTime;
rv = idleService->GetIdleTime(&idleTime);
NS_ENSURE_SUCCESS(rv, rv);
// If we've been idle for more than EXPIRE_IDLE_TIME_IN_MSECS
// keep the expiration engine chugging along.
if (idleTime > EXPIRE_IDLE_TIME_IN_MSECS) {
mozStorageTransaction transaction(mDBConn, PR_TRUE);
PRBool keepGoing; // We don't care about this value.
(void)mExpire.ExpireItems(MAX_EXPIRE_RECORDS_ON_IDLE / 2, &keepGoing);
(void)mExpire.ExpireOrphans(MAX_EXPIRE_RECORDS_ON_IDLE / 2);
}
return NS_OK;
}
void // static
nsNavHistory::IdleTimerCallback(nsITimer* aTimer, void* aClosure)
{
nsNavHistory *history = static_cast<nsNavHistory *>(aClosure);
(void)history->OnIdle();
}
// nsIDownloadHistory **********************************************************
NS_IMETHODIMP
@ -5638,6 +5578,12 @@ nsNavHistory::NotifyOnPageExpired(nsIURI *aURI, PRTime aVisitTime,
{
ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavHistoryObserver,
OnPageExpired(aURI, aVisitTime, aWholeEntry));
if (aWholeEntry) {
// Notify our observers that the URI has been removed.
ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
nsINavHistoryObserver, OnDeleteURI(aURI))
}
return NS_OK;
}
@ -5650,23 +5596,14 @@ nsNavHistory::Observe(nsISupports *aSubject, const char *aTopic,
NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
if (strcmp(aTopic, gQuitApplicationGrantedMessage) == 0) {
if (mIdleTimer) {
mIdleTimer->Cancel();
mIdleTimer = nsnull;
}
nsresult rv;
nsCOMPtr<nsIPrefService> prefService =
do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
if (NS_SUCCEEDED(rv))
prefService->SavePrefFile(nsnull);
// notify expiring system that we're quitting, it may want to do stuff
mExpire.OnQuit();
// notify the bookmarks service we're quitting
nsNavBookmarks *bookmarks = nsNavBookmarks::GetBookmarksService();
NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY);
(void)bookmarks->OnQuit();
// Start shutdown expiration.
mExpire->OnQuit();
}
else if (strcmp(aTopic, gXpcomShutdown) == 0) {
nsresult rv;
@ -5719,7 +5656,7 @@ nsNavHistory::Observe(nsISupports *aSubject, const char *aTopic,
LoadPrefs(PR_FALSE);
if (oldDaysMin != mExpireDaysMin || oldDaysMax != mExpireDaysMax ||
oldVisits != mExpireSites)
mExpire.OnExpirationChanged();
mExpire->OnExpirationChanged();
}
else if (strcmp(aTopic, gIdleDaily) == 0) {
// Ensure our connection is still alive. The idle-daily observer is removed

View File

@ -23,6 +23,7 @@
* Brett Wilson <brettw@gmail.com> (original author)
* Edward Lee <edward.lee@engineering.uiuc.edu>
* Ehsan Akhgari <ehsan.akhgari@gmail.com>
* Marco Bonardo <mak77@bonardo.net>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@ -528,7 +529,7 @@ protected:
// expiration
friend class nsNavHistoryExpire;
nsNavHistoryExpire mExpire;
nsNavHistoryExpire *mExpire;
#ifdef LAZY_ADD
// lazy add committing
@ -692,15 +693,6 @@ protected:
nsCOMArray<nsNavHistoryQuery>* aQueries,
nsNavHistoryQueryOptions* aOptions);
/**
* Used to setup the idle timer used to perform various tasks when the user is
* idle..
*/
nsCOMPtr<nsITimer> mIdleTimer;
nsresult InitializeIdleTimer();
static void IdleTimerCallback(nsITimer* aTimer, void* aClosure);
nsresult OnIdle();
PRInt64 mTagsFolder;
PRBool mInPrivateBrowsing;

View File

@ -21,6 +21,8 @@
*
* Contributor(s):
* Brett Wilson <brettw@gmail.com> (original author)
* Dietrich Ayala <dietrich@mozilla.com>
* Marco Bonardo <mak77@bonardo.net>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@ -48,6 +50,7 @@
#include "nsIAnnotationService.h"
#include "nsPrintfCString.h"
#include "nsPlacesMacros.h"
#include "nsIIdleService.h"
struct nsNavHistoryExpireRecord {
nsNavHistoryExpireRecord(mozIStorageStatement* statement);
@ -62,176 +65,169 @@ struct nsNavHistoryExpireRecord {
PRBool erased; // set to true if/when the history entry is erased
};
// Number of things we'll expire at once. Runtime of expiration is approximately
// linear with the number of things we expire at once. This number was picked so
// we expire "several" things at once, but still run quickly. Just doing 3
// expirations at once isn't much faster than 6 due to constant overhead of
// running the query.
#define EXPIRATION_COUNT_PER_RUN 6
// The time in ms to wait before kick-off partial expiration after preferences
// are changed.
#define EXPIRATION_PARTIAL_TIMEOUT 500
// Larger expiration chunk for idle time and shutdown.
#define EXPIRATION_COUNT_PER_RUN_LARGE 50
// The time in ms to wait between each partial expiration step.
#define EXPIRATION_PARTIAL_SUBSEQUENT_TIMEOUT ((PRUint32)10 * PR_MSEC_PER_SEC)
// The time in ms to wait after AddURI to try expiration of pages. Short is
// actually better. If expiration takes an unusually long period of time, it
// will interfere with video playback in the browser, for example. Such a blip
// is not likely to be noticeable when the page has just appeared.
#define PARTIAL_EXPIRATION_TIMEOUT (3.5 * PR_MSEC_PER_SEC)
// Number of pages we'll expire at each partial expiration run. Partial
// expiration runs when expiration preferences run.
#define EXPIRATION_PAGES_PER_RUN 6
// The time in ms to wait after the initial expiration run for additional ones
#define SUBSEQUENT_EXPIRATION_TIMEOUT (20 * PR_MSEC_PER_SEC)
// The time in ms the user should be idle before we run expiration.
// This will be repeated till we find enough entries to expire, otherwise
// we will wait for a longer timeout before checking again.
#define EXPIRATION_IDLE_TIMEOUT ((PRUint32)5 * 60 * PR_MSEC_PER_SEC)
// The time in ms the user should be idle before we run expiration when the
// previous call ran out of expirable pages.
#define EXPIRATION_IDLE_LONG_TIMEOUT EXPIRATION_IDLE_TIMEOUT * 10
// Number of expirations we'll do after the most recent page is loaded before
// stopping. We don't want to keep the computer chugging forever expiring
// annotations if the user stopped using the browser.
//
// This current value of one prevents history expiration while the page is
// being shown, because expiration may interfere with media playback.
#define MAX_SEQUENTIAL_RUNS 1
// During idle we can expire a larger chunk of pages.
#define EXPIRATION_MAX_PAGES_AT_IDLE 100
// During shutdown we should cleanup any dangling moz_places record, but we
// cannot expire a too large number of entries since that would slowdown
// shutdown.
#define EXPIRATION_MAX_PAGES_AT_SHUTDOWN 100
// Expiration policy amounts in microseconds.
const PRTime EXPIRATION_POLICY_DAYS = ((PRTime)7 * 86400 * PR_USEC_PER_SEC);
const PRTime EXPIRATION_POLICY_WEEKS = ((PRTime)30 * 86400 * PR_USEC_PER_SEC);
const PRTime EXPIRATION_POLICY_MONTHS = ((PRTime)180 * 86400 * PR_USEC_PER_SEC);
// History preferences.
#define PREF_BRANCH_BASE "browser."
#define PREF_BROWSER_HISTORY_EXPIRE_DAYS "history_expire_days"
// Sanitization preferences
#define PREF_SANITIZE_ON_SHUTDOWN "privacy.sanitize.sanitizeOnShutdown"
#define PREF_SANITIZE_ITEM_HISTORY "privacy.item.history"
// Expiration policy amounts (in microseconds)
const PRTime EXPIRATION_POLICY_DAYS = ((PRTime)7 * 86400 * PR_USEC_PER_SEC);
const PRTime EXPIRATION_POLICY_WEEKS = ((PRTime)30 * 86400 * PR_USEC_PER_SEC);
const PRTime EXPIRATION_POLICY_MONTHS = ((PRTime)180 * 86400 * PR_USEC_PER_SEC);
// Expiration cap for dangling moz_places records
#define EXPIRATION_CAP_PLACES 500
// History preferences
#define PREF_BRANCH_BASE "browser."
#define PREF_BROWSER_HISTORY_EXPIRE_DAYS "history_expire_days"
// nsNavHistoryExpire::nsNavHistoryExpire
//
// Warning: don't do anything with aHistory in the constructor, since
// this is a member of the nsNavHistory, it is still being constructed
// when this is called.
nsNavHistoryExpire::nsNavHistoryExpire(nsNavHistory* aHistory) :
mHistory(aHistory),
mTimerSet(PR_FALSE),
mAnyEmptyRuns(PR_FALSE),
mNextExpirationTime(0),
mAddCount(0),
mExpiredItems(0)
nsNavHistoryExpire::nsNavHistoryExpire() :
mNextExpirationTime(0)
{
mHistory = nsNavHistory::GetHistoryService();
NS_ASSERTION(mHistory, "History service should exist at this point.");
mDBConn = mHistory->GetStorageConnection();
NS_ASSERTION(mDBConn, "History service should have a valid database connection");
// Initialize idle timer.
InitializeIdleTimer(EXPIRATION_IDLE_TIMEOUT);
}
// nsNavHistoryExpire::~nsNavHistoryExpire
nsNavHistoryExpire::~nsNavHistoryExpire()
{
// Cancel any pending timers.
if (mPartialExpirationTimer) {
mPartialExpirationTimer->Cancel();
mPartialExpirationTimer = 0;
}
if (mIdleTimer) {
mIdleTimer->Cancel();
mIdleTimer = 0;
}
}
// nsNavHistoryExpire::OnAddURI
//
// Called by history when a URI is added to history. This starts the timer
// for when we are going to expire.
//
// The current time is passed in by the history service as an optimization.
// The AddURI function has already computed the proper time, and getting the
// time again from the OS is nontrivial.
void
nsNavHistoryExpire::OnAddURI(PRTime aNow)
nsNavHistoryExpire::InitializeIdleTimer(PRUint32 aTimeInMs)
{
mAddCount ++;
if (mTimer && mTimerSet) {
mTimer->Cancel();
mTimerSet = PR_FALSE;
if (mIdleTimer) {
mIdleTimer->Cancel();
mIdleTimer = 0;
}
if (mNextExpirationTime != 0 && aNow < mNextExpirationTime)
return; // we know there's nothing to expire yet
StartTimer(PARTIAL_EXPIRATION_TIMEOUT);
mIdleTimer = do_CreateInstance("@mozilla.org/timer;1");
if (mIdleTimer) {
(void)mIdleTimer->InitWithFuncCallback(IdleTimerCallback, this, aTimeInMs,
nsITimer::TYPE_ONE_SHOT);
}
}
// nsNavHistoryExpire::OnDeleteURI
//
// Called by history when a URI is deleted from history.
// This kicks off an expiration of annotations.
//
void
nsNavHistoryExpire::OnDeleteURI()
void // static
nsNavHistoryExpire::IdleTimerCallback(nsITimer* aTimer, void* aClosure)
{
mozIStorageConnection* connection = mHistory->GetStorageConnection();
if (!connection) {
NS_NOTREACHED("No connection");
nsNavHistoryExpire* expire = static_cast<nsNavHistoryExpire*>(aClosure);
expire->mIdleTimer = 0;
expire->OnIdle();
}
void
nsNavHistoryExpire::OnIdle()
{
PRUint32 idleTime = 0;
nsCOMPtr<nsIIdleService> idleService =
do_GetService("@mozilla.org/widget/idleservice;1");
if (idleService)
(void)idleService->GetIdleTime(&idleTime);
// If we've been idle for more than EXPIRATION_IDLE_TIMEOUT
// we can expire a chunk of elements.
if (idleTime < EXPIRATION_IDLE_TIMEOUT)
return;
mozStorageTransaction transaction(mDBConn, PR_TRUE);
bool keepGoing = ExpireItems(EXPIRATION_MAX_PAGES_AT_IDLE);
ExpireOrphans(EXPIRATION_MAX_PAGES_AT_IDLE);
if (!keepGoing) {
// We have expired enough entries, so there is no more need to be agressive
// on idle for some time.
InitializeIdleTimer(EXPIRATION_IDLE_LONG_TIMEOUT);
}
nsresult rv = ExpireAnnotations(connection);
if (NS_FAILED(rv))
NS_WARNING("ExpireAnnotations failed.");
else
InitializeIdleTimer(EXPIRATION_IDLE_TIMEOUT);
}
// nsNavHistoryExpire::OnQuit
//
// Here we check for some edge cases and fix them
void
nsNavHistoryExpire::OnDeleteVisits()
{
(void)ExpireAnnotations();
}
void
nsNavHistoryExpire::OnQuit()
{
mozIStorageConnection* connection = mHistory->GetStorageConnection();
if (!connection) {
NS_NOTREACHED("No connection");
return;
// Cancel any pending timers so we won't try to expire during shutdown.
if (mPartialExpirationTimer) {
mPartialExpirationTimer->Cancel();
mPartialExpirationTimer = 0;
}
if (mIdleTimer) {
mIdleTimer->Cancel();
mIdleTimer = 0;
}
// Need to cancel any pending timers so we don't try to expire during shutdown
if (mTimer)
mTimer->Cancel();
nsCOMPtr<nsIPrefBranch> prefs =
do_GetService("@mozilla.org/preferences-service;1");
if (prefs) {
// Determine whether we can skip partially expiration of dangling entries
// because we be doing a full expiration on shutdown in ClearHistory().
PRBool sanitizeOnShutdown = PR_FALSE;
(void)prefs->GetBoolPref(PREF_SANITIZE_ON_SHUTDOWN, &sanitizeOnShutdown);
PRBool sanitizeHistory = PR_FALSE;
(void)prefs->GetBoolPref(PREF_SANITIZE_ITEM_HISTORY, &sanitizeHistory);
// Handle degenerate runs:
nsresult rv = ExpireForDegenerateRuns();
if (NS_FAILED(rv))
NS_WARNING("ExpireForDegenerateRuns failed.");
// Determine whether we can skip partially expiration of dangling entries
// because we be doing a full expiration on shutdown in ClearHistory()
nsCOMPtr<nsIPrefBranch> prefs(do_GetService("@mozilla.org/preferences-service;1"));
PRBool sanitizeOnShutdown = PR_FALSE;
PRBool sanitizeHistory = PR_FALSE;
(void)prefs->GetBoolPref(PREF_SANITIZE_ON_SHUTDOWN, &sanitizeOnShutdown);
(void)prefs->GetBoolPref(PREF_SANITIZE_ITEM_HISTORY, &sanitizeHistory);
if (sanitizeHistory && sanitizeOnShutdown)
return;
if (sanitizeHistory && sanitizeOnShutdown)
return;
}
// Get rid of all records orphaned due to expiration.
rv = ExpireOrphans(EXPIRATION_CAP_PLACES);
if (NS_FAILED(rv))
NS_WARNING("ExpireOrphans failed.");
ExpireOrphans(EXPIRATION_MAX_PAGES_AT_SHUTDOWN);
}
// nsNavHistoryExpire::ClearHistory
//
// Performance: ExpireItems sends notifications. We may want to disable this
// for clear history cases. However, my initial tests show that the
// notifications are not a significant part of clear history time.
nsresult
nsNavHistoryExpire::ClearHistory()
{
mozIStorageConnection* connection = mHistory->GetStorageConnection();
NS_ENSURE_TRUE(connection, NS_ERROR_OUT_OF_MEMORY);
mozStorageTransaction transaction(connection, PR_FALSE);
mozStorageTransaction transaction(mDBConn, PR_FALSE);
// reset frecency for all items that will _not_ be deleted
// Note, we set frecency to -visit_count since we use that value in our
// idle query to figure out which places to recalcuate frecency first.
// We must do this before deleting visits
nsresult rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
// We must do this before deleting visits.
nsresult rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"UPDATE moz_places_view SET frecency = -MAX(visit_count, 1) "
"WHERE id IN("
"SELECT h.id FROM moz_places_temp h "
@ -244,31 +240,18 @@ nsNavHistoryExpire::ClearHistory()
")"));
NS_ENSURE_SUCCESS(rv, rv);
// expire visits, then let the paranoid functions do the cleanup for us
rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
// Expire visits, then let the paranoid functions do the cleanup for us.
rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DELETE FROM moz_historyvisits_view"));
NS_ENSURE_SUCCESS(rv, rv);
rv = ExpireHistoryParanoid(connection, -1);
if (NS_FAILED(rv))
NS_WARNING("ExpireHistoryParanoid failed.");
// Expire all orphans.
ExpireOrphans(-1);
rv = ExpireFaviconsParanoid(connection);
if (NS_FAILED(rv))
NS_WARNING("ExpireFaviconsParanoid failed.");
rv = ExpireAnnotationsParanoid(connection);
if (NS_FAILED(rv))
NS_WARNING("ExpireAnnotationsParanoid failed.");
rv = ExpireInputHistoryParanoid(connection);
if (NS_FAILED(rv))
NS_WARNING("ExpireInputHistoryParanoid failed.");
// some of the remaining places could be place: urls or
// Some of the remaining places could be place: urls or
// unvisited livemark items, so setting the frecency to -1
// will cause them to show up in the url bar autocomplete
// call FixInvalidFrecenciesForExcludedPlaces() to handle that scenario
// call FixInvalidFrecenciesForExcludedPlaces to handle that scenario.
rv = mHistory->FixInvalidFrecenciesForExcludedPlaces();
if (NS_FAILED(rv))
NS_WARNING("failed to fix invalid frecencies");
@ -276,10 +259,6 @@ nsNavHistoryExpire::ClearHistory()
rv = transaction.Commit();
NS_ENSURE_SUCCESS(rv, rv);
// XXX todo
// forcibly call the "on idle" timer here to do a little work
// but the rest will happen on idle.
ENUMERATE_OBSERVERS(mHistory->canNotify(), mHistory->mCacheObservers,
mHistory->mObservers, nsINavHistoryObserver,
OnClearHistory())
@ -287,101 +266,70 @@ nsNavHistoryExpire::ClearHistory()
return NS_OK;
}
// nsNavHistoryExpire::OnExpirationChanged
//
// Called when the expiration length in days has changed. We clear any
// next expiration time, meaning that we'll try to expire stuff next time,
// and recompute the value if there's still nothing to expire.
void
nsNavHistoryExpire::OnExpirationChanged()
{
mNextExpirationTime = 0;
// kick off expiration
(void)OnAddURI(PR_Now());
// Kick off partial expiration.
// Subsequent steps will be on timer.
StartPartialExpirationTimer(EXPIRATION_PARTIAL_TIMEOUT);
}
// nsNavHistoryExpire::DoPartialExpiration
nsresult
nsNavHistoryExpire::DoPartialExpiration()
{
// expire history items
PRBool keepGoing;
nsresult rv = ExpireItems(EXPIRATION_COUNT_PER_RUN, &keepGoing);
if (NS_FAILED(rv))
NS_WARNING("ExpireItems failed.");
else if (keepGoing)
StartTimer(SUBSEQUENT_EXPIRATION_TIMEOUT);
bool keepGoing = ExpireItems(EXPIRATION_PAGES_PER_RUN);
if (keepGoing)
StartPartialExpirationTimer(EXPIRATION_PARTIAL_SUBSEQUENT_TIMEOUT);
return NS_OK;
}
// nsNavHistoryExpire::ExpireItems
//
// Here, we try to expire aNumToExpire items and their associated data,
// If we expired things and then stopped because we hit this limit,
// aKeepGoing will be set indicating we should keep expiring. If we ran
// out of things to expire, it will be unset indicating we should wait.
//
// As a special case, aNumToExpire can be 0 and we'll expire everything
// in history.
nsresult
nsNavHistoryExpire::ExpireItems(PRUint32 aNumToExpire, PRBool* aKeepGoing)
bool
nsNavHistoryExpire::ExpireItems(PRUint32 aNumToExpire)
{
mozIStorageConnection* connection = mHistory->GetStorageConnection();
NS_ENSURE_TRUE(connection, NS_ERROR_OUT_OF_MEMORY);
// Whether to keep going after this expiration step.
bool keepGoing = true;
// This transaction is important for performance. It makes the DB flush
// everything to disk in one larger operation rather than many small ones.
// Note that this transaction always commits.
mozStorageTransaction transaction(connection, PR_FALSE);
*aKeepGoing = PR_TRUE;
mozStorageTransaction transaction(mDBConn, PR_FALSE);
PRInt64 expireTime;
if (aNumToExpire == 0) {
// special case: erase all history
if (!aNumToExpire) {
// Special case: erase all pages from history.
expireTime = 0;
} else {
}
else {
expireTime = PR_Now() - GetExpirationTimeAgo(mHistory->mExpireDaysMax);
}
// find some visits to expire
// Find some visits to expire.
nsTArray<nsNavHistoryExpireRecord> expiredVisits;
nsresult rv = FindVisits(expireTime, aNumToExpire, connection,
expiredVisits);
NS_ENSURE_SUCCESS(rv, rv);
nsresult rv = FindVisits(expireTime, aNumToExpire, expiredVisits);
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "FindVisits Failed");
// if we didn't find as many things to expire as we could have, then
// we should note the next time we need to expire.
if (expiredVisits.Length() < aNumToExpire) {
*aKeepGoing = PR_FALSE;
ComputeNextExpirationTime(connection);
if (expiredVisits.Length() == 0) {
// Nothing to expire. Set the flag so we know we don't have to do any
// work on shutdown.
mAnyEmptyRuns = PR_TRUE;
return NS_OK;
}
keepGoing = false;
ComputeNextExpirationTime();
}
mExpiredItems += expiredVisits.Length();
rv = EraseVisits(connection, expiredVisits);
NS_ENSURE_SUCCESS(rv, rv);
rv = EraseVisits(expiredVisits);
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "EraseVisits Failed");
rv = EraseHistory(connection, expiredVisits);
NS_ENSURE_SUCCESS(rv, rv);
rv = EraseHistory(expiredVisits);
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "EraseHistory Failed");
// send observer messages
// Send observer messages.
nsCOMPtr<nsIURI> uri;
for (PRUint32 i = 0; i < expiredVisits.Length(); i ++) {
rv = NS_NewURI(getter_AddRefs(uri), expiredVisits[i].uri);
if (NS_FAILED(rv)) continue;
if (NS_FAILED(rv)) {
NS_WARNING("Trying to expire a corrupt uri?!");
continue;
}
// FIXME bug 325241 provide a way to observe hidden elements
if (expiredVisits[i].hidden) continue;
@ -392,65 +340,49 @@ nsNavHistoryExpire::ExpireItems(PRUint32 aNumToExpire, PRBool* aKeepGoing)
expiredVisits[i].erased));
}
// don't worry about errors here, it doesn't affect our ability to continue
rv = EraseFavicons(connection, expiredVisits);
if (NS_FAILED(rv))
NS_WARNING("EraseFavicons failed.");
rv = EraseAnnotations(connection, expiredVisits);
if (NS_FAILED(rv))
NS_WARNING("EraseAnnotations failed.");
// expire annotations by policy
rv = ExpireAnnotations(connection);
if (NS_FAILED(rv))
NS_WARNING("ExpireAnnotations failed.");
// Don't worry about errors here, it doesn't affect our ability to continue.
rv = EraseFavicons(expiredVisits);
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "EraseFavicons Failed");
rv = EraseAnnotations(expiredVisits);
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "EraseAnnotations Failed");
rv = ExpireAnnotations();
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "ExpireAnnotarions Failed");
rv = transaction.Commit();
NS_ENSURE_SUCCESS(rv, rv);
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Committing transaction Failed");
return NS_OK;
return keepGoing;
}
// nsNavHistoryExpire::ExpireOrphans
//
// Try to expire aNumToExpire items that are orphans. aNumToExpire only
// limits how many moz_places we worry about. Everything else (favicons,
// annotations, and input history) is completely expired.
nsresult
void
nsNavHistoryExpire::ExpireOrphans(PRUint32 aNumToExpire)
{
mozIStorageConnection* connection = mHistory->GetStorageConnection();
NS_ENSURE_TRUE(connection, NS_ERROR_OUT_OF_MEMORY);
mozStorageTransaction transaction(mDBConn, PR_FALSE);
mozStorageTransaction transaction(connection, PR_FALSE);
nsresult rv = ExpireHistoryParanoid(aNumToExpire);
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "ExpireHistoryParanoid Failed");
nsresult rv = ExpireHistoryParanoid(connection, aNumToExpire);
NS_ENSURE_SUCCESS(rv, rv);
rv = ExpireFaviconsParanoid();
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "ExpireFaviconsParanoid Failed");
rv = ExpireFaviconsParanoid(connection);
NS_ENSURE_SUCCESS(rv, rv);
rv = ExpireAnnotationsParanoid();
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "ExpireAnnotationsParanoid Failed");
rv = ExpireAnnotationsParanoid(connection);
NS_ENSURE_SUCCESS(rv, rv);
rv = ExpireInputHistoryParanoid(connection);
NS_ENSURE_SUCCESS(rv, rv);
rv = ExpireInputHistoryParanoid();
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "ExpireInputHistoryParanoid Failed");
rv = transaction.Commit();
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Commit Transaction Failed");
}
// nsNavHistoryExpireRecord::nsNavHistoryExpireRecord
//
// Statement should be the one created in FindVisits. The parameters must
// agree.
/**
* nsNavHistoryExpireRecord::nsNavHistoryExpireRecord
*
* Statement should be the one created in FindVisits. The parameters must
* agree.
*/
nsNavHistoryExpireRecord::nsNavHistoryExpireRecord(
mozIStorageStatement* statement)
mozIStorageStatement* statement)
{
visitID = statement->AsInt64(0);
placeID = statement->AsInt64(1);
@ -462,29 +394,13 @@ nsNavHistoryExpireRecord::nsNavHistoryExpireRecord(
erased = PR_FALSE;
}
// nsNavHistoryExpire::FindVisits
//
// Find visits to expire, meeting the following criteria:
//
// * With a visit date older than browser.history_expire_days ago.
// * With a visit date older than browser.history_expire_days_min ago
// if we have more than browser.history_expire_sites unique urls.
//
// aExpireThreshold is the time at which we will delete visits before.
// If it is zero, we will match everything.
//
// aNumToExpire is the maximum number of visits to find. If it is 0, then
// we will get all matching visits.
nsresult
nsNavHistoryExpire::FindVisits(PRTime aExpireThreshold, PRUint32 aNumToExpire,
mozIStorageConnection* aConnection,
nsTArray<nsNavHistoryExpireRecord>& aRecords)
{
// Select a limited number of visits older than a time
// Select a limited number of visits older than a time.
nsCOMPtr<mozIStorageStatement> selectStatement;
nsresult rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
nsresult rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
"SELECT v.id, v.place_id, v.visit_date, IFNULL(h_t.url, h.url), "
"IFNULL(h_t.favicon_id, h.favicon_id), "
"IFNULL(h_t.hidden, h.hidden), b.fk "
@ -507,12 +423,12 @@ nsNavHistoryExpire::FindVisits(PRTime aExpireThreshold, PRUint32 aNumToExpire,
getter_AddRefs(selectStatement));
NS_ENSURE_SUCCESS(rv, rv);
// browser.history_expire_days || match all visits
// Use browser.history_expire_days or match all visits.
PRTime expireMaxTime = aExpireThreshold ? aExpireThreshold : LL_MAXINT;
rv = selectStatement->BindInt64Parameter(0, expireMaxTime);
NS_ENSURE_SUCCESS(rv, rv);
// use LIMIT -1 to not limit
// Use LIMIT -1 to not limit.
PRInt32 numToExpire = aNumToExpire ? aNumToExpire : -1;
rv = selectStatement->BindInt64Parameter(1, numToExpire);
NS_ENSURE_SUCCESS(rv, rv);
@ -528,7 +444,7 @@ nsNavHistoryExpire::FindVisits(PRTime aExpireThreshold, PRUint32 aNumToExpire,
if (aRecords.Length() < aNumToExpire) {
// check the number of visited unique urls in the db.
nsCOMPtr<mozIStorageStatement> countStatement;
rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
"SELECT "
"(SELECT count(*) FROM moz_places_temp WHERE visit_count > 0) + "
"(SELECT count(*) FROM moz_places WHERE visit_count > 0 AND "
@ -537,14 +453,14 @@ nsNavHistoryExpire::FindVisits(PRTime aExpireThreshold, PRUint32 aNumToExpire,
NS_ENSURE_SUCCESS(rv, rv);
hasMore = PR_FALSE;
// initialize to mExpiresites to avoid expiring if something goes wrong
// initialize to mExpiresites to avoid expiring if something goes wrong.
PRInt32 pageCount = mHistory->mExpireSites;
if (NS_SUCCEEDED(countStatement->ExecuteStep(&hasMore)) && hasMore) {
rv = countStatement->GetInt32(0, &pageCount);
NS_ENSURE_SUCCESS(rv, rv);
}
// Don't find any more pages to expire if we have not reached the urls cap
// Don't find any more pages to expire if we have not reached the urls cap.
if (pageCount <= mHistory->mExpireSites)
return NS_OK;
@ -571,11 +487,8 @@ nsNavHistoryExpire::FindVisits(PRTime aExpireThreshold, PRUint32 aNumToExpire,
return NS_OK;
}
// nsNavHistoryExpire::EraseVisits
nsresult
nsNavHistoryExpire::EraseVisits(mozIStorageConnection* aConnection,
nsNavHistoryExpire::EraseVisits(
const nsTArray<nsNavHistoryExpireRecord>& aRecords)
{
// build a comma separated string of visit ids to delete
@ -607,7 +520,7 @@ nsNavHistoryExpire::EraseVisits(mozIStorageConnection* aConnection,
// keep the old frecencies when possible as an estimate for the new frecency
// unless we know it has to be invalidated.
// We must do this before deleting visits
nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
nsresult rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"UPDATE moz_places_view "
"SET frecency = -MAX(visit_count, 1) "
"WHERE id IN ( "
@ -643,7 +556,7 @@ nsNavHistoryExpire::EraseVisits(mozIStorageConnection* aConnection,
")"));
NS_ENSURE_SUCCESS(rv, rv);
rv = aConnection->ExecuteSimpleSQL(
rv = mDBConn->ExecuteSimpleSQL(
NS_LITERAL_CSTRING("DELETE FROM moz_historyvisits_view WHERE id IN (") +
deletedVisitIds +
NS_LITERAL_CSTRING(")"));
@ -652,18 +565,8 @@ nsNavHistoryExpire::EraseVisits(mozIStorageConnection* aConnection,
return NS_OK;
}
// nsNavHistoryExpire::EraseHistory
//
// This erases records in moz_places when there are no more visits.
// We need to be careful not to delete: bookmarks, items that still have
// visits and place: URIs.
//
// This will modify the input by setting the erased flag on each of the
// array elements according to whether the history item was erased or not.
nsresult
nsNavHistoryExpire::EraseHistory(mozIStorageConnection* aConnection,
nsNavHistoryExpire::EraseHistory(
nsTArray<nsNavHistoryExpireRecord>& aRecords)
{
// build a comma separated string of place ids to delete
@ -688,7 +591,7 @@ nsNavHistoryExpire::EraseHistory(mozIStorageConnection* aConnection,
if (deletedPlaceIds.IsEmpty())
return NS_OK;
nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
nsresult rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DELETE FROM moz_places_view WHERE id IN( "
"SELECT h.id "
"FROM moz_places h "
@ -717,11 +620,8 @@ nsNavHistoryExpire::EraseHistory(mozIStorageConnection* aConnection,
return NS_OK;
}
// nsNavHistoryExpire::EraseFavicons
nsresult
nsNavHistoryExpire::EraseFavicons(mozIStorageConnection* aConnection,
nsNavHistoryExpire::EraseFavicons(
const nsTArray<nsNavHistoryExpireRecord>& aRecords)
{
// build a comma separated string of favicon ids to delete
@ -745,7 +645,7 @@ nsNavHistoryExpire::EraseFavicons(mozIStorageConnection* aConnection,
return NS_OK;
// delete only if favicon id is not referenced
nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
nsresult rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DELETE FROM moz_favicons WHERE id IN ( "
"SELECT f.id FROM moz_favicons f "
"LEFT JOIN moz_places h ON f.id = h.favicon_id "
@ -759,11 +659,8 @@ nsNavHistoryExpire::EraseFavicons(mozIStorageConnection* aConnection,
return NS_OK;
}
// nsNavHistoryExpire::EraseAnnotations
nsresult
nsNavHistoryExpire::EraseAnnotations(mozIStorageConnection* aConnection,
nsNavHistoryExpire::EraseAnnotations(
const nsTArray<nsNavHistoryExpireRecord>& aRecords)
{
// remove annotations for the set of records passed in
@ -783,7 +680,7 @@ nsNavHistoryExpire::EraseAnnotations(mozIStorageConnection* aConnection,
if (placeIds.IsEmpty())
return NS_OK;
nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
nsresult rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DELETE FROM moz_annos WHERE place_id in (") +
placeIds + NS_LITERAL_CSTRING(") AND expiration != ") +
nsPrintfCString("%d", nsIAnnotationService::EXPIRE_NEVER));
@ -791,31 +688,23 @@ nsNavHistoryExpire::EraseAnnotations(mozIStorageConnection* aConnection,
return NS_OK;
}
// nsAnnotationService::ExpireAnnotations
//
// Periodic expiration of annotations that have time-sensitive
// expiration policies.
//
// NOTE: Always specify the exact policy constant, as they're
// not guaranteed to be in numerical order.
//
nsresult
nsNavHistoryExpire::ExpireAnnotations(mozIStorageConnection* aConnection)
nsNavHistoryExpire::ExpireAnnotations()
{
mozStorageTransaction transaction(aConnection, PR_FALSE);
mozStorageTransaction transaction(mDBConn, PR_FALSE);
// Note: The COALESCE is used to cover a short period where NULLs were inserted
// into the lastModified column.
PRTime now = PR_Now();
nsCOMPtr<mozIStorageStatement> expirePagesStatement;
nsresult rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
nsresult rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
"DELETE FROM moz_annos "
"WHERE expiration = ?1 AND "
"(?2 > MAX(COALESCE(lastModified, 0), dateAdded))"),
getter_AddRefs(expirePagesStatement));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<mozIStorageStatement> expireItemsStatement;
rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
"DELETE FROM moz_items_annos "
"WHERE expiration = ?1 AND "
"(?2 > MAX(COALESCE(lastModified, 0), dateAdded))"),
@ -879,7 +768,7 @@ nsNavHistoryExpire::ExpireAnnotations(mozIStorageConnection* aConnection)
NS_ENSURE_SUCCESS(rv, rv);
// remove EXPIRE_WITH_HISTORY annos for pages without visits
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DELETE FROM moz_annos WHERE expiration = ") +
nsPrintfCString("%d", nsIAnnotationService::EXPIRE_WITH_HISTORY) +
NS_LITERAL_CSTRING(" AND NOT EXISTS "
@ -896,17 +785,8 @@ nsNavHistoryExpire::ExpireAnnotations(mozIStorageConnection* aConnection)
return NS_OK;
}
// nsNavHistoryExpire::ExpireHistoryParanoid
//
// Deletes any dangling history entries that aren't associated with any
// visits, bookmarks or "place:" URIs.
//
// The aMaxRecords parameter is an optional cap on the number of
// records to delete. If its value is -1, all records will be deleted.
nsresult
nsNavHistoryExpire::ExpireHistoryParanoid(mozIStorageConnection* aConnection,
PRInt32 aMaxRecords)
nsNavHistoryExpire::ExpireHistoryParanoid(PRInt32 aMaxRecords)
{
nsCAutoString query(
"DELETE FROM moz_places_view WHERE id IN ("
@ -932,20 +812,16 @@ nsNavHistoryExpire::ExpireHistoryParanoid(mozIStorageConnection* aConnection,
query.AppendInt(aMaxRecords);
}
query.AppendLiteral(")");
nsresult rv = aConnection->ExecuteSimpleSQL(query);
nsresult rv = mDBConn->ExecuteSimpleSQL(query);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
// nsNavHistoryExpire::ExpireFaviconsParanoid
//
// Deletes any dangling favicons that aren't associated with any pages.
nsresult
nsNavHistoryExpire::ExpireFaviconsParanoid(mozIStorageConnection* aConnection)
nsNavHistoryExpire::ExpireFaviconsParanoid()
{
nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
nsresult rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DELETE FROM moz_favicons WHERE id IN ("
"SELECT f.id FROM moz_favicons f "
"LEFT JOIN moz_places h ON f.id = h.favicon_id "
@ -957,25 +833,19 @@ nsNavHistoryExpire::ExpireFaviconsParanoid(mozIStorageConnection* aConnection)
return rv;
}
// nsNavHistoryExpire::ExpireAnnotationsParanoid
//
// Deletes session annotations, dangling annotations
// and annotation names that are unused.
nsresult
nsNavHistoryExpire::ExpireAnnotationsParanoid(mozIStorageConnection* aConnection)
nsNavHistoryExpire::ExpireAnnotationsParanoid()
{
// delete session annos
nsCAutoString session_query = NS_LITERAL_CSTRING(
"DELETE FROM moz_annos WHERE expiration = ") +
nsPrintfCString("%d", nsIAnnotationService::EXPIRE_SESSION);
nsresult rv = aConnection->ExecuteSimpleSQL(session_query);
nsresult rv = mDBConn->ExecuteSimpleSQL(session_query);
NS_ENSURE_SUCCESS(rv, rv);
// delete all uri annos w/o a corresponding place id
// or without any visits *and* not EXPIRE_NEVER.
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DELETE FROM moz_annos WHERE id IN ("
"SELECT a.id FROM moz_annos a "
"LEFT JOIN moz_places h ON a.place_id = h.id "
@ -990,7 +860,7 @@ nsNavHistoryExpire::ExpireAnnotationsParanoid(mozIStorageConnection* aConnection
NS_ENSURE_SUCCESS(rv, rv);
// delete item annos w/o a corresponding item id
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DELETE FROM moz_items_annos WHERE id IN "
"(SELECT a.id FROM moz_items_annos a "
"LEFT OUTER JOIN moz_bookmarks b ON a.item_id = b.id "
@ -998,7 +868,7 @@ nsNavHistoryExpire::ExpireAnnotationsParanoid(mozIStorageConnection* aConnection
NS_ENSURE_SUCCESS(rv, rv);
// delete all anno names w/o a corresponding anno
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DELETE FROM moz_anno_attributes WHERE id IN ("
"SELECT n.id FROM moz_anno_attributes n "
"LEFT JOIN moz_annos a ON n.id = a.anno_attribute_id "
@ -1010,16 +880,11 @@ nsNavHistoryExpire::ExpireAnnotationsParanoid(mozIStorageConnection* aConnection
return NS_OK;
}
// nsNavHistoryExpire::ExpireInputHistoryParanoid
//
// Deletes dangling input history
nsresult
nsNavHistoryExpire::ExpireInputHistoryParanoid(mozIStorageConnection* aConnection)
nsNavHistoryExpire::ExpireInputHistoryParanoid()
{
// Delete dangling input history that have no associated pages
nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
nsresult rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DELETE FROM moz_inputhistory WHERE place_id IN ( "
"SELECT place_id FROM moz_inputhistory "
"LEFT JOIN moz_places h ON h.id = place_id "
@ -1032,46 +897,13 @@ nsNavHistoryExpire::ExpireInputHistoryParanoid(mozIStorageConnection* aConnectio
return NS_OK;
}
// nsNavHistoryExpire::ExpireForDegenerateRuns
//
// This checks for potential degenerate runs. For example, a tinderbox
// loads many web pages quickly and we'll never have a chance to expire.
// Particularly crazy users might also do this. If we detect this, then we
// want to force some expiration so history doesn't keep increasing.
//
// Returns true if we did anything.
PRBool
nsNavHistoryExpire::ExpireForDegenerateRuns()
{
// If there were any times that we didn't have anything to expire, this is
// not a degenerate run.
if (mAnyEmptyRuns)
return PR_FALSE;
// Expire a larger chunk of runs to catch up.
PRBool keepGoing;
nsresult rv = ExpireItems(EXPIRATION_COUNT_PER_RUN_LARGE, &keepGoing);
if (NS_FAILED(rv))
NS_WARNING("ExpireItems failed.");
return PR_TRUE;
}
// nsNavHistoryExpire::ComputeNextExpirationTime
//
// This computes mNextExpirationTime. See that var in the header file.
// It is passed the number of microseconds that things expire in.
void
nsNavHistoryExpire::ComputeNextExpirationTime(
mozIStorageConnection* aConnection)
nsNavHistoryExpire::ComputeNextExpirationTime()
{
mNextExpirationTime = 0;
nsCOMPtr<mozIStorageStatement> statement;
nsresult rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
nsresult rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
"SELECT MIN(visit_date) FROM moz_historyvisits"),
getter_AddRefs(statement));
NS_ASSERTION(NS_SUCCEEDED(rv), "Could not create statement");
@ -1087,36 +919,30 @@ nsNavHistoryExpire::ComputeNextExpirationTime(
mNextExpirationTime = minTime + GetExpirationTimeAgo(mHistory->mExpireDaysMax);
}
// nsNavHistoryExpire::StartTimer
nsresult
nsNavHistoryExpire::StartTimer(PRUint32 aMilleseconds)
void
nsNavHistoryExpire::StartPartialExpirationTimer(PRUint32 aMilleseconds)
{
if (!mTimer)
mTimer = do_CreateInstance("@mozilla.org/timer;1");
NS_ENSURE_STATE(mTimer); // returns on error
nsresult rv = mTimer->InitWithFuncCallback(TimerCallback, this,
aMilleseconds,
nsITimer::TYPE_ONE_SHOT);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
if (mPartialExpirationTimer) {
mPartialExpirationTimer->Cancel();
mPartialExpirationTimer = 0;
}
mPartialExpirationTimer = do_CreateInstance("@mozilla.org/timer;1");
if(mPartialExpirationTimer) {
(void)mPartialExpirationTimer->InitWithFuncCallback(
PartialExpirationTimerCallback, this, aMilleseconds,
nsITimer::TYPE_ONE_SHOT);
}
}
// nsNavHistoryExpire::TimerCallback
void // static
nsNavHistoryExpire::TimerCallback(nsITimer* aTimer, void* aClosure)
nsNavHistoryExpire::PartialExpirationTimerCallback(nsITimer* aTimer, void* aClosure)
{
nsNavHistoryExpire* that = static_cast<nsNavHistoryExpire*>(aClosure);
that->mTimerSet = PR_FALSE;
that->DoPartialExpiration();
nsNavHistoryExpire* expire = static_cast<nsNavHistoryExpire*>(aClosure);
expire->mPartialExpirationTimer = 0;
expire->DoPartialExpiration();
}
// nsNavHistoryExpire::GetExpirationTimeAgo
PRTime
nsNavHistoryExpire::GetExpirationTimeAgo(PRInt32 aExpireDays)
{

View File

@ -21,6 +21,8 @@
*
* Contributor(s):
* Brett Wilson <brettw@gmail.com> (original author)
* Dietrich Ayala <dietrich@mozilla.com>
* Marco Bonardo <mak77@bonardo.net>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@ -49,67 +51,156 @@ struct nsNavHistoryExpireRecord;
class nsNavHistoryExpire
{
public:
nsNavHistoryExpire(nsNavHistory* aHistory);
nsNavHistoryExpire();
~nsNavHistoryExpire();
void OnAddURI(PRTime aNow);
void OnDeleteURI();
void OnQuit();
nsresult ClearHistory();
void OnExpirationChanged();
nsresult ExpireItems(PRUint32 aNumToExpire, PRBool* aKeepGoing);
/**
* Called by history when visits are deleted from history.
* This kicks off an expiration of page annotations.
*/
void OnDeleteVisits();
nsresult ExpireOrphans(PRUint32 aNumToExpire);
/**
* Manages shutdown work and final expiration of orphans.
*/
void OnQuit();
/**
* Called when the expiration length in days has changed. We clear any
* next expiration time, meaning that we'll try to expire stuff next time,
* and recompute the value if there's still nothing to expire.
*/
void OnExpirationChanged();
/**
* Performance: ExpireItems sends notifications. We may want to disable this
* for clear history cases. However, my initial tests show that the
* notifications are not a significant part of clear history time.
*/
nsresult ClearHistory();
/**
* Tries to expire aNumToExpire pages and their associated data.
*
* @param aNumToExpire
* Number of history pages to expire, pass 0 to expire every page
* in history.
* @return true if we did not expire enough items and we should keep expiring.
* false otherwise.
*/
bool ExpireItems(PRUint32 aNumToExpire);
/**
* Tries to expire aNumToExpire items that are orphans.
*
* @param aNumToExpire
* Limits how many orphan moz_places we worry about.
* Everything else (favicons, annotations, and input history) is
* completely expired.
*/
void ExpireOrphans(PRUint32 aNumToExpire);
protected:
nsNavHistory *mHistory;
mozIStorageConnection *mDBConn;
nsNavHistory* mHistory;
nsCOMPtr<nsITimer> mTimer;
PRBool mTimerSet;
// Set when we try to expire something and find there is nothing to expire.
// This short-curcuits the shutdown logic by indicating that there probably
// isn't anything important we need to expire.
PRBool mAnyEmptyRuns;
nsCOMPtr<nsITimer> mPartialExpirationTimer;
void StartPartialExpirationTimer(PRUint32 aMilleseconds);
static void PartialExpirationTimerCallback(nsITimer *aTimer, void *aClosure);
// When we have found nothing to expire, we compute the time the next item
// will expire. This is that time so we won't try to expire anything until
// then. It is 0 when we don't need to wait to expire stuff.
PRTime mNextExpirationTime;
void ComputeNextExpirationTime(mozIStorageConnection* aConnection);
// global statistics
PRUint32 mAddCount;
PRUint32 mExpiredItems;
/**
* This computes mNextExpirationTime. See that var in the header file.
* It is passed the number of microseconds that things expire in.
*/
void ComputeNextExpirationTime();
nsresult DoPartialExpiration();
nsresult ExpireAnnotations(mozIStorageConnection* aConnection);
/**
* Creates the idle timer. We expire visits and orphans on idle.
*/
void InitializeIdleTimer(PRUint32 aTimeInMs);
nsCOMPtr<nsITimer> mIdleTimer;
static void IdleTimerCallback(nsITimer *aTimer, void *aClosure);
// parts of ExpireItems
nsresult FindVisits(PRTime aExpireThreshold, PRUint32 aNumToExpire,
mozIStorageConnection* aConnection,
nsTArray<nsNavHistoryExpireRecord>& aRecords);
nsresult EraseVisits(mozIStorageConnection* aConnection,
const nsTArray<nsNavHistoryExpireRecord>& aRecords);
nsresult EraseHistory(mozIStorageConnection* aConnection,
nsTArray<nsNavHistoryExpireRecord>& aRecords);
nsresult EraseFavicons(mozIStorageConnection* aConnection,
const nsTArray<nsNavHistoryExpireRecord>& aRecords);
nsresult EraseAnnotations(mozIStorageConnection* aConnection,
const nsTArray<nsNavHistoryExpireRecord>& aRecords);
// paranoid checks
nsresult ExpireHistoryParanoid(mozIStorageConnection* aConnection, PRInt32 aMaxRecords);
nsresult ExpireFaviconsParanoid(mozIStorageConnection* aConnection);
nsresult ExpireAnnotationsParanoid(mozIStorageConnection* aConnection);
nsresult ExpireInputHistoryParanoid(mozIStorageConnection* aConnection);
PRBool ExpireForDegenerateRuns();
nsresult StartTimer(PRUint32 aMilleseconds);
static void TimerCallback(nsITimer* aTimer, void* aClosure);
/**
* We usually expire periodically, but that could not be fast enough, so on idle
* we want to expire a bigget chunk of items to help partial expiration.
* This way we try to hit when the user is not going to suffer from UI hangs.
*/
void OnIdle();
PRTime GetExpirationTimeAgo(PRInt32 aExpireDays);
nsresult ExpireAnnotations();
/**
* Find visits to expire, meeting the following criteria:
*
* - With a visit date older than browser.history_expire_days ago.
* - With a visit date older than browser.history_expire_days_min ago
* if we have more than browser.history_expire_sites unique urls.
*
* aExpireThreshold is the time at which we will delete visits before.
* If it is zero, we will match everything.
*
* aNumToExpire is the maximum number of visits to find. If it is 0, then
* we will get all matching visits.
*/
nsresult FindVisits(PRTime aExpireThreshold, PRUint32 aNumToExpire,
nsTArray<nsNavHistoryExpireRecord> &aRecords);
nsresult EraseVisits(const nsTArray<nsNavHistoryExpireRecord> &aRecords);
/**
* This erases records in moz_places when there are no more visits.
* We need to be careful not to delete: bookmarks, items that still have
* visits and place: URIs.
*
* This will modify the input by setting the erased flag on each of the
* array elements according to whether the history item was erased or not.
*/
nsresult EraseHistory(nsTArray<nsNavHistoryExpireRecord> &aRecords);
nsresult EraseFavicons(const nsTArray<nsNavHistoryExpireRecord> &aRecords);
/**
* Periodic expiration of annotations that have time-sensitive
* expiration policies.
*
* @note Always specify the exact policy constant, as they're
* not guaranteed to be in numerical order.
*/
nsresult EraseAnnotations(const nsTArray<nsNavHistoryExpireRecord> &aRecords);
/**
* Deletes any dangling history entries that aren't associated with any
* visits, bookmarks or "place:" URIs.
*
* The aMaxRecords parameter is an optional cap on the number of
* records to delete. If its value is -1, all records will be deleted.
*/
nsresult ExpireHistoryParanoid(PRInt32 aMaxRecords);
/**
* Deletes any dangling favicons that aren't associated with any pages.
*/
nsresult ExpireFaviconsParanoid();
/**
* Deletes session annotations, dangling annotations
* and annotation names that are unused.
*/
nsresult ExpireAnnotationsParanoid();
/**
* Deletes dangling input history
*/
nsresult ExpireInputHistoryParanoid();
};

View File

@ -67,6 +67,8 @@ const kQuerySyncPlacesId = 0;
const kQuerySyncHistoryVisitsId = 1;
const kQuerySelectExpireVisitsId = 2;
const kQueryExpireVisitsId = 3;
const kQuerySelectExpireHistoryOrphansId = 4;
const kQueryExpireHistoryOrphansId = 5;
////////////////////////////////////////////////////////////////////////////////
//// nsPlacesDBFlush class
@ -267,6 +269,8 @@ nsPlacesDBFlush.prototype = {
let queries = [
kQuerySelectExpireVisitsId,
kQueryExpireVisitsId,
kQuerySelectExpireHistoryOrphansId,
kQueryExpireHistoryOrphansId,
kQuerySyncPlacesId,
kQuerySyncHistoryVisitsId,
];
@ -290,7 +294,7 @@ nsPlacesDBFlush.prototype = {
this._expiredResults.push({
uri: this._ios.newURI(row.getResultByName("url"), null, null),
visitDate: row.getResultByName("visit_date"),
wholeEntry: (row.getResultByName("visit_count") == 1)
wholeEntry: (row.getResultByName("whole_entry") == 1)
});
}
},
@ -385,6 +389,10 @@ nsPlacesDBFlush.prototype = {
params.visit_date = (Date.now() - (this._expireDays * kMSPerDay)) * 1000;
params.max_expire = kMaxExpire;
break;
case kQuerySelectExpireHistoryOrphansId:
case kQueryExpireHistoryOrphansId:
params.max_expire = kMaxExpire;
break;
}
return stmt;
@ -421,7 +429,7 @@ nsPlacesDBFlush.prototype = {
// Determine which entries will be flushed out from moz_historyvisits
// when kQueryExpireVisitsId runs.
this._cachedStatements[aQueryType] = this._db.createStatement(
"SELECT h.url, v.visit_date, h.hidden, h.visit_count " +
"SELECT h.url, v.visit_date, h.hidden, 0 AS whole_entry " +
"FROM moz_places h " +
"JOIN moz_historyvisits v ON h.id = v.place_id " +
"WHERE v.visit_date < :visit_date " +
@ -431,7 +439,7 @@ nsPlacesDBFlush.prototype = {
break;
case kQueryExpireVisitsId:
// Flush out entries from moz_historyvisits
// Expire entries from moz_historyvisits.
this._cachedStatements[aQueryType] = this._db.createStatement(
"DELETE FROM moz_historyvisits " +
"WHERE id IN ( " +
@ -444,6 +452,41 @@ nsPlacesDBFlush.prototype = {
);
break;
case kQuerySelectExpireHistoryOrphansId:
// Determine which entries will be flushed out from moz_places
// when kQueryExpireHistoryOrphansId runs.
this._cachedStatements[aQueryType] = this._db.createStatement(
"SELECT h.url, h.last_visit_date AS visit_date, h.hidden, " +
"1 as whole_entry FROM moz_places h " +
"LEFT JOIN moz_historyvisits v ON h.id = v.place_id " +
"LEFT JOIN moz_historyvisits_temp v_t ON h.id = v_t.place_id " +
"LEFT JOIN moz_bookmarks b ON h.id = b.fk " +
"WHERE v.id IS NULL " +
"AND v_t.id IS NULL " +
"AND b.id IS NULL " +
"AND SUBSTR(h.url, 1, 6) <> 'place:' " +
"LIMIT :max_expire"
);
break;
case kQueryExpireHistoryOrphansId:
// Flush out entries from moz_historyvisits.
this._cachedStatements[aQueryType] = this._db.createStatement(
"DELETE FROM moz_places_view " +
"WHERE id IN ( " +
"SELECT h.id FROM moz_places h " +
"LEFT JOIN moz_historyvisits v ON h.id = v.place_id " +
"LEFT JOIN moz_historyvisits_temp v_t ON h.id = v_t.place_id " +
"LEFT JOIN moz_bookmarks b ON h.id = b.fk " +
"WHERE v.id IS NULL " +
"AND v_t.id IS NULL " +
"AND b.id IS NULL " +
"AND SUBSTR(h.url, 1, 6) <> 'place:' " +
"LIMIT :max_expire" +
")"
);
break;
default:
throw "Unexpected statement!";
}

View File

@ -84,6 +84,7 @@ os.addObserver(observer, kSyncFinished, false);
// Used to ensure that we did in fact get notified about our expiration.
var historyObserver = {
visitTime: -1,
_runCount: 0,
onPageExpired: function(aURI, aVisitTime, aWholeEntry)
{
do_check_true(aURI.equals(uri(TEST_URI)));
@ -92,7 +93,10 @@ var historyObserver = {
do_check_eq(this.visitTime, aVisitTime);
// This was the only visit for this uri, so ensure that aWholeEntry is true.
do_check_true(aWholeEntry);
if (++this._runCount == 1)
do_check_false(aWholeEntry);
else
do_check_true(aWholeEntry);
observer.notificationReceived = true;
hs.removeObserver(this, false);

View File

@ -56,6 +56,7 @@ DummyObserver.prototype = {
os.notifyObservers(null, "dummy-observer-visited", null);
},
onTitleChanged: function(aURI, aPageTitle) {},
onBeforeDeleteURI: function(aURI) {},
onDeleteURI: function(aURI) {},
onClearHistory: function() {},
onPageChanged: function(aURI, aWhat, aValue) {},

View File

@ -64,4 +64,3 @@ if (pip.DBConnection.connectionReady) {
pip.DBConnection.close();
do_check_false(pip.DBConnection.connectionReady);
}

View File

@ -45,6 +45,8 @@
////////////////////////////////////////////////////////////////////////////////
//// Globals and Constants
let hs = Cc["@mozilla.org/browser/nav-history-service;1"].
getService(Ci.nsINavHistoryService);
let ac = Cc["@mozilla.org/autocomplete/search;1?name=history"].
getService(Ci.nsIAutoCompleteSearch);

View File

@ -522,11 +522,10 @@ function startExpireNeither() {
// set date maximum to 3
prefs.setIntPref("browser.history_expire_days", 3);
// Changing expiration preferences has already triggered expiration, it will
// run after the partial expiration timer (3,5s).
// Changing expiration preferences triggers partial expiration.
// Check results.
do_timeout(3600, "checkExpireNeither();");
do_timeout(600, "checkExpireNeither();");
}
function checkExpireNeither() {
@ -584,11 +583,10 @@ function startExpireDaysOnly() {
// set date maximum to 3
prefs.setIntPref("browser.history_expire_days", 3);
// Changing expiration preferences has already triggered expiration, it will
// run after the partial expiration timer (3,5s).
// Changing expiration preferences triggers partial expiration.
// Check results.
do_timeout(3600, "checkExpireDaysOnly();");
do_timeout(600, "checkExpireDaysOnly();");
}
function checkExpireDaysOnly() {
@ -655,11 +653,10 @@ function startExpireBoth() {
// set date minimum to 1
prefs.setIntPref("browser.history_expire_days_min", 1);
// Changing expiration preferences has already triggered expiration, it will
// run after the partial expiration timer (3,5s).
// Changing expiration preferences triggers partial expiration.
// Check results.
do_timeout(3600, "checkExpireBoth();"); // incremental expiration timer is 3500
do_timeout(600, "checkExpireBoth();"); // incremental expiration timer is 3500
}
function checkExpireBoth() {
@ -714,11 +711,10 @@ function startExpireNeitherOver() {
// set date maximum to 3
prefs.setIntPref("browser.history_expire_days", 3);
// Changing expiration preferences has already triggered expiration, it will
// run after the partial expiration timer (3,5s).
// Changing expiration preferences triggers partial expiration.
// Check results.
do_timeout(3600, "checkExpireNeitherOver();");
do_timeout(600, "checkExpireNeitherOver();");
}
function checkExpireNeitherOver() {
@ -764,11 +760,10 @@ function startExpireHistoryDisabled() {
// set date maximum to 0
prefs.setIntPref("browser.history_expire_days", 0);
// Changing expiration preferences has already triggered expiration, it will
// run after the partial expiration timer (3,5s).
// Changing expiration preferences triggers partial expiration.
// Check results.
do_timeout(3600, "checkExpireHistoryDisabled();");
do_timeout(600, "checkExpireHistoryDisabled();");
}
function checkExpireHistoryDisabled() {
@ -817,11 +812,10 @@ function startExpireBadPrefs() {
// set date maximum to 1
prefs.setIntPref("browser.history_expire_days", 1);
// Changing expiration preferences has already triggered expiration, it will
// run after the partial expiration timer (3,5s).
// Changing expiration preferences triggers partial expiration.
// Check results.
do_timeout(3600, "checkExpireBadPrefs();");
do_timeout(600, "checkExpireBadPrefs();");
}
function checkExpireBadPrefs() {