Bug 911307 - Reflect changes to top sites immediately in about:newtab (part 1, Places patch). r=mak

This commit is contained in:
Drew Willcoxon 2014-03-28 23:30:04 -07:00
parent b9b8a97985
commit 7a7b8a3778
13 changed files with 509 additions and 23 deletions

View File

@ -2338,6 +2338,22 @@ nsDownloadManager::OnTitleChanged(nsIURI *aURI,
return NS_OK;
}
NS_IMETHODIMP
nsDownloadManager::OnFrecencyChanged(nsIURI* aURI,
int32_t aNewFrecency,
const nsACString& aGUID,
bool aHidden,
PRTime aLastVisitDate)
{
return NS_OK;
}
NS_IMETHODIMP
nsDownloadManager::OnManyFrecenciesChanged()
{
return NS_OK;
}
NS_IMETHODIMP
nsDownloadManager::OnDeleteURI(nsIURI *aURI,
const nsACString& aGUID,

View File

@ -941,6 +941,8 @@ Database::InitFunctions()
NS_ENSURE_SUCCESS(rv, rv);
rv = FixupURLFunction::create(mMainConn);
NS_ENSURE_SUCCESS(rv, rv);
rv = FrecencyNotificationFunction::create(mMainConn);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}

View File

@ -1185,7 +1185,10 @@ private:
if (aPlace.placeId) {
stmt = mHistory->GetStatement(
"UPDATE moz_places "
"SET frecency = CALCULATE_FRECENCY(:page_id) "
"SET frecency = NOTIFY_FRECENCY("
"CALCULATE_FRECENCY(:page_id), "
"url, guid, hidden, last_visit_date"
") "
"WHERE id = :page_id"
);
NS_ENSURE_STATE(stmt);
@ -1195,7 +1198,9 @@ private:
else {
stmt = mHistory->GetStatement(
"UPDATE moz_places "
"SET frecency = CALCULATE_FRECENCY(id) "
"SET frecency = NOTIFY_FRECENCY("
"CALCULATE_FRECENCY(id), url, guid, hidden, last_visit_date"
") "
"WHERE url = :page_url"
);
NS_ENSURE_STATE(stmt);
@ -2037,13 +2042,14 @@ History::InsertPlace(const VisitData& aPlace)
NS_ENSURE_SUCCESS(rv, rv);
rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("url"), aPlace.spec);
NS_ENSURE_SUCCESS(rv, rv);
nsString title = aPlace.title;
// Empty strings should have no title, just like nsNavHistory::SetPageTitle.
if (aPlace.title.IsEmpty()) {
if (title.IsEmpty()) {
rv = stmt->BindNullByName(NS_LITERAL_CSTRING("title"));
}
else {
rv = stmt->BindStringByName(NS_LITERAL_CSTRING("title"),
StringHead(aPlace.title, TITLE_LENGTH_MAX));
title.Assign(StringHead(aPlace.title, TITLE_LENGTH_MAX));
rv = stmt->BindStringByName(NS_LITERAL_CSTRING("title"), title);
}
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("typed"), aPlace.typed);
@ -2065,6 +2071,13 @@ History::InsertPlace(const VisitData& aPlace)
rv = stmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
// Post an onFrecencyChanged observer notification.
const nsNavHistory* navHistory = nsNavHistory::GetConstHistoryService();
NS_ENSURE_STATE(navHistory);
navHistory->DispatchFrecencyChangedNotification(aPlace.spec, frecency, guid,
aPlace.hidden,
aPlace.visitTime);
return NS_OK;
}

View File

@ -749,5 +749,63 @@ namespace places {
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//// Frecency Changed Notification Function
/* static */
nsresult
FrecencyNotificationFunction::create(mozIStorageConnection *aDBConn)
{
nsRefPtr<FrecencyNotificationFunction> function =
new FrecencyNotificationFunction();
nsresult rv = aDBConn->CreateFunction(
NS_LITERAL_CSTRING("notify_frecency"), 5, function
);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMPL_ISUPPORTS1(
FrecencyNotificationFunction,
mozIStorageFunction
)
NS_IMETHODIMP
FrecencyNotificationFunction::OnFunctionCall(mozIStorageValueArray *aArgs,
nsIVariant **_result)
{
uint32_t numArgs;
nsresult rv = aArgs->GetNumEntries(&numArgs);
NS_ENSURE_SUCCESS(rv, rv);
MOZ_ASSERT(numArgs == 5);
int32_t newFrecency = aArgs->AsInt32(0);
nsAutoCString spec;
rv = aArgs->GetUTF8String(1, spec);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString guid;
rv = aArgs->GetUTF8String(2, guid);
NS_ENSURE_SUCCESS(rv, rv);
bool hidden = static_cast<bool>(aArgs->AsInt32(3));
PRTime lastVisitDate = static_cast<PRTime>(aArgs->AsInt64(4));
const nsNavHistory* navHistory = nsNavHistory::GetConstHistoryService();
NS_ENSURE_STATE(navHistory);
navHistory->DispatchFrecencyChangedNotification(spec, newFrecency, guid,
hidden, lastVisitDate);
nsCOMPtr<nsIWritableVariant> result =
do_CreateInstance("@mozilla.org/variant;1");
NS_ENSURE_STATE(result);
rv = result->SetAsInt32(newFrecency);
NS_ENSURE_SUCCESS(rv, rv);
NS_ADDREF(*_result = result);
return NS_OK;
}
} // namespace places
} // namespace mozilla

View File

@ -280,6 +280,43 @@ public:
static nsresult create(mozIStorageConnection *aDBConn);
};
////////////////////////////////////////////////////////////////////////////////
//// Frecency Changed Notification Function
/**
* For a given place, posts a runnable to the main thread that calls
* onFrecencyChanged on nsNavHistory's nsINavHistoryObservers. The passed-in
* newFrecency value is returned unchanged.
*
* @param newFrecency
* The place's new frecency.
* @param url
* The place's URL.
* @param guid
* The place's GUID.
* @param hidden
* The place's hidden boolean.
* @param lastVisitDate
* The place's last visit date.
* @return newFrecency
*/
class FrecencyNotificationFunction MOZ_FINAL : public mozIStorageFunction
{
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_MOZISTORAGEFUNCTION
/**
* Registers the function with the specified database connection.
*
* @param aDBConn
* The database connection to register with.
*/
static nsresult create(mozIStorageConnection *aDBConn);
};
} // namespace places
} // namespace storage

View File

@ -610,7 +610,7 @@ interface nsINavHistoryResult : nsISupports
* DANGER! If you are in the middle of a batch transaction, there may be a
* database transaction active. You can still access the DB, but be careful.
*/
[scriptable, uuid(45e2970b-9b00-4473-9938-39d6beaf4248)]
[scriptable, uuid(0f0f45b0-13a1-44ae-a0ab-c6046ec6d4da)]
interface nsINavHistoryObserver : nsISupports
{
/**
@ -675,6 +675,37 @@ interface nsINavHistoryObserver : nsISupports
in AString aPageTitle,
in ACString aGUID);
/**
* Called when an individual page's frecency has changed.
*
* This is not called for pages whose frecencies change as the result of some
* large operation where some large or unknown number of frecencies change at
* once. Use onManyFrecenciesChanged to detect such changes.
*
* @param aURI
* The page's URI.
* @param aNewFrecency
* The page's new frecency.
* @param aGUID
* The page's GUID.
* @param aHidden
* True if the page is marked as hidden.
* @param aVisitDate
* The page's last visit date.
*/
void onFrecencyChanged(in nsIURI aURI,
in long aNewFrecency,
in ACString aGUID,
in boolean aHidden,
in PRTime aVisitDate);
/**
* Called when the frecencies of many pages have changed at once.
*
* onFrecencyChanged is not called for each of those pages.
*/
void onManyFrecenciesChanged();
/**
* Removed by the user.
*/

View File

@ -2841,6 +2841,24 @@ nsNavBookmarks::OnTitleChanged(nsIURI* aURI,
}
NS_IMETHODIMP
nsNavBookmarks::OnFrecencyChanged(nsIURI* aURI,
int32_t aNewFrecency,
const nsACString& aGUID,
bool aHidden,
PRTime aLastVisitDate)
{
return NS_OK;
}
NS_IMETHODIMP
nsNavBookmarks::OnManyFrecenciesChanged()
{
return NS_OK;
}
NS_IMETHODIMP
nsNavBookmarks::OnPageChanged(nsIURI* aURI,
uint32_t aChangedAttribute,

View File

@ -535,6 +535,82 @@ nsNavHistory::NotifyTitleChange(nsIURI* aURI,
nsINavHistoryObserver, OnTitleChanged(aURI, aTitle, aGUID));
}
void
nsNavHistory::NotifyFrecencyChanged(nsIURI* aURI,
int32_t aNewFrecency,
const nsACString& aGUID,
bool aHidden,
PRTime aLastVisitDate)
{
MOZ_ASSERT(!aGUID.IsEmpty());
NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
nsINavHistoryObserver,
OnFrecencyChanged(aURI, aNewFrecency, aGUID, aHidden,
aLastVisitDate));
}
void
nsNavHistory::NotifyManyFrecenciesChanged()
{
NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
nsINavHistoryObserver,
OnManyFrecenciesChanged());
}
namespace {
class FrecencyNotification : public nsRunnable
{
public:
FrecencyNotification(const nsACString& aSpec,
int32_t aNewFrecency,
const nsACString& aGUID,
bool aHidden,
PRTime aLastVisitDate)
: mSpec(aSpec)
, mNewFrecency(aNewFrecency)
, mGUID(aGUID)
, mHidden(aHidden)
, mLastVisitDate(aLastVisitDate)
{
}
NS_IMETHOD Run()
{
MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread");
nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
if (navHistory) {
nsCOMPtr<nsIURI> uri;
(void)NS_NewURI(getter_AddRefs(uri), mSpec);
navHistory->NotifyFrecencyChanged(uri, mNewFrecency, mGUID, mHidden,
mLastVisitDate);
}
return NS_OK;
}
private:
nsCString mSpec;
int32_t mNewFrecency;
nsCString mGUID;
bool mHidden;
PRTime mLastVisitDate;
};
} // anonymous namespace
void
nsNavHistory::DispatchFrecencyChangedNotification(const nsACString& aSpec,
int32_t aNewFrecency,
const nsACString& aGUID,
bool aHidden,
PRTime aLastVisitDate) const
{
nsCOMPtr<nsIRunnable> notif = new FrecencyNotification(aSpec, aNewFrecency,
aGUID, aHidden,
aLastVisitDate);
(void)NS_DispatchToMainThread(notif);
}
int32_t
nsNavHistory::GetDaysOfHistory() {
MOZ_ASSERT(NS_IsMainThread(), "This can only be called on the main thread");
@ -928,32 +1004,68 @@ nsNavHistory::GetHasHistoryEntries(bool* aHasEntries)
}
namespace {
class InvalidateAllFrecenciesCallback : public AsyncStatementCallback
{
public:
InvalidateAllFrecenciesCallback()
{
}
NS_IMETHOD HandleCompletion(uint16_t aReason)
{
if (aReason == REASON_FINISHED) {
nsNavHistory *navHistory = nsNavHistory::GetHistoryService();
NS_ENSURE_STATE(navHistory);
navHistory->NotifyManyFrecenciesChanged();
}
return NS_OK;
}
};
} // anonymous namespace
nsresult
nsNavHistory::invalidateFrecencies(const nsCString& aPlaceIdsQueryString)
{
// Exclude place: queries by setting their frecency to zero.
nsAutoCString invalideFrecenciesSQLFragment(
"UPDATE moz_places SET frecency = (CASE "
"WHEN url BETWEEN 'place:' AND 'place;' "
"THEN 0 "
"ELSE -1 "
"END) "
nsCString invalidFrecenciesSQLFragment(
"UPDATE moz_places SET frecency = "
);
if (!aPlaceIdsQueryString.IsEmpty())
invalidFrecenciesSQLFragment.AppendLiteral("NOTIFY_FRECENCY(");
invalidFrecenciesSQLFragment.AppendLiteral(
"(CASE "
"WHEN url BETWEEN 'place:' AND 'place;' "
"THEN 0 "
"ELSE -1 "
"END) "
);
if (!aPlaceIdsQueryString.IsEmpty()) {
invalidFrecenciesSQLFragment.AppendLiteral(
", url, guid, hidden, last_visit_date) "
);
}
invalidFrecenciesSQLFragment.AppendLiteral(
"WHERE frecency > 0 "
);
if (!aPlaceIdsQueryString.IsEmpty()) {
invalideFrecenciesSQLFragment.AppendLiteral("AND id IN(");
invalideFrecenciesSQLFragment.Append(aPlaceIdsQueryString);
invalideFrecenciesSQLFragment.AppendLiteral(")");
invalidFrecenciesSQLFragment.AppendLiteral("AND id IN(");
invalidFrecenciesSQLFragment.Append(aPlaceIdsQueryString);
invalidFrecenciesSQLFragment.AppendLiteral(")");
}
nsRefPtr<InvalidateAllFrecenciesCallback> cb =
aPlaceIdsQueryString.IsEmpty() ? new InvalidateAllFrecenciesCallback()
: nullptr;
nsCOMPtr<mozIStorageAsyncStatement> stmt = mDB->GetAsyncStatement(
invalideFrecenciesSQLFragment
invalidFrecenciesSQLFragment
);
NS_ENSURE_STATE(stmt);
nsCOMPtr<mozIStoragePendingStatement> ps;
nsresult rv = stmt->ExecuteAsync(nullptr, getter_AddRefs(ps));
nsresult rv = stmt->ExecuteAsync(cb, getter_AddRefs(ps));
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
@ -3078,6 +3190,30 @@ nsNavHistory::Observe(nsISupports *aSubject, const char *aTopic,
}
namespace {
class DecayFrecencyCallback : public AsyncStatementTelemetryTimer
{
public:
DecayFrecencyCallback()
: AsyncStatementTelemetryTimer(Telemetry::PLACES_IDLE_FRECENCY_DECAY_TIME_MS)
{
}
NS_IMETHOD HandleCompletion(uint16_t aReason)
{
(void)AsyncStatementTelemetryTimer::HandleCompletion(aReason);
if (aReason == REASON_FINISHED) {
nsNavHistory *navHistory = nsNavHistory::GetHistoryService();
NS_ENSURE_STATE(navHistory);
navHistory->NotifyManyFrecenciesChanged();
}
return NS_OK;
}
};
} // anonymous namespace
nsresult
nsNavHistory::DecayFrecency()
{
@ -3115,8 +3251,7 @@ nsNavHistory::DecayFrecency()
deleteAdaptive.get()
};
nsCOMPtr<mozIStoragePendingStatement> ps;
nsRefPtr<AsyncStatementTelemetryTimer> cb =
new AsyncStatementTelemetryTimer(Telemetry::PLACES_IDLE_FRECENCY_DECAY_TIME_MS);
nsRefPtr<DecayFrecencyCallback> cb = new DecayFrecencyCallback();
rv = mDB->MainConn()->ExecuteAsync(stmts, ArrayLength(stmts), cb,
getter_AddRefs(ps));
NS_ENSURE_SUCCESS(rv, rv);
@ -4312,7 +4447,9 @@ nsNavHistory::UpdateFrecency(int64_t aPlaceId)
{
nsCOMPtr<mozIStorageAsyncStatement> updateFrecencyStmt = mDB->GetAsyncStatement(
"UPDATE moz_places "
"SET frecency = CALCULATE_FRECENCY(:page_id) "
"SET frecency = NOTIFY_FRECENCY("
"CALCULATE_FRECENCY(:page_id), url, guid, hidden, last_visit_date"
") "
"WHERE id = :page_id"
);
NS_ENSURE_STATE(updateFrecencyStmt);
@ -4345,6 +4482,31 @@ nsNavHistory::UpdateFrecency(int64_t aPlaceId)
}
namespace {
class FixInvalidFrecenciesCallback : public AsyncStatementCallbackNotifier
{
public:
FixInvalidFrecenciesCallback()
: AsyncStatementCallbackNotifier(TOPIC_FRECENCY_UPDATED)
{
}
NS_IMETHOD HandleCompletion(uint16_t aReason)
{
nsresult rv = AsyncStatementCallbackNotifier::HandleCompletion(aReason);
NS_ENSURE_SUCCESS(rv, rv);
if (aReason == REASON_FINISHED) {
nsNavHistory *navHistory = nsNavHistory::GetHistoryService();
NS_ENSURE_STATE(navHistory);
navHistory->NotifyManyFrecenciesChanged();
}
return NS_OK;
}
};
} // anonymous namespace
nsresult
nsNavHistory::FixInvalidFrecencies()
{
@ -4355,8 +4517,8 @@ nsNavHistory::FixInvalidFrecencies()
);
NS_ENSURE_STATE(stmt);
nsRefPtr<AsyncStatementCallbackNotifier> callback =
new AsyncStatementCallbackNotifier(TOPIC_FRECENCY_UPDATED);
nsRefPtr<FixInvalidFrecenciesCallback> callback =
new FixInvalidFrecenciesCallback();
nsCOMPtr<mozIStoragePendingStatement> ps;
(void)stmt->ExecuteAsync(callback, getter_AddRefs(ps));

View File

@ -418,6 +418,29 @@ public:
const nsString& title,
const nsACString& aGUID);
/**
* Fires onFrecencyChanged event to nsINavHistoryService observers
*/
void NotifyFrecencyChanged(nsIURI* aURI,
int32_t aNewFrecency,
const nsACString& aGUID,
bool aHidden,
PRTime aLastVisitDate);
/**
* Fires onManyFrecenciesChanged event to nsINavHistoryService observers
*/
void NotifyManyFrecenciesChanged();
/**
* Posts a runnable to the main thread that calls NotifyFrecencyChanged.
*/
void DispatchFrecencyChangedNotification(const nsACString& aSpec,
int32_t aNewFrecency,
const nsACString& aGUID,
bool aHidden,
PRTime aLastVisitDate) const;
bool isBatching() {
return mBatchLevel > 0;
}

View File

@ -2614,6 +2614,24 @@ nsNavHistoryQueryResultNode::OnTitleChanged(nsIURI* aURI,
}
NS_IMETHODIMP
nsNavHistoryQueryResultNode::OnFrecencyChanged(nsIURI* aURI,
int32_t aNewFrecency,
const nsACString& aGUID,
bool aHidden,
PRTime aLastVisitDate)
{
return NS_OK;
}
NS_IMETHODIMP
nsNavHistoryQueryResultNode::OnManyFrecenciesChanged()
{
return NS_OK;
}
/**
* Here, we can always live update by just deleting all occurrences of
* the given URI.
@ -4662,6 +4680,24 @@ nsNavHistoryResult::OnTitleChanged(nsIURI* aURI,
}
NS_IMETHODIMP
nsNavHistoryResult::OnFrecencyChanged(nsIURI* aURI,
int32_t aNewFrecency,
const nsACString& aGUID,
bool aHidden,
PRTime aLastVisitDate)
{
return NS_OK;
}
NS_IMETHODIMP
nsNavHistoryResult::OnManyFrecenciesChanged()
{
return NS_OK;
}
NS_IMETHODIMP
nsNavHistoryResult::OnDeleteURI(nsIURI *aURI,
const nsACString& aGUID,

View File

@ -64,6 +64,10 @@ private:
NS_DECL_NSINAVBOOKMARKOBSERVER \
NS_IMETHOD OnTitleChanged(nsIURI* aURI, const nsAString& aPageTitle, \
const nsACString& aGUID); \
NS_IMETHOD OnFrecencyChanged(nsIURI* aURI, int32_t aNewFrecency, \
const nsACString& aGUID, bool aHidden, \
PRTime aLastVisitDate); \
NS_IMETHOD OnManyFrecenciesChanged(); \
NS_IMETHOD OnDeleteURI(nsIURI *aURI, const nsACString& aGUID, \
uint16_t aReason); \
NS_IMETHOD OnClearHistory(); \

View File

@ -0,0 +1,85 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
function run_test() {
run_next_test();
}
// Each of these tests a path that triggers a frecency update. Together they
// hit all sites that update a frecency.
// InsertVisitedURIs::UpdateFrecency and History::InsertPlace
add_task(function test_InsertVisitedURIs_UpdateFrecency_and_History_InsertPlace() {
// InsertPlace is at the end of a path that UpdateFrecency is also on, so kill
// two birds with one stone and expect two notifications. Trigger the path by
// adding a download.
let uri = NetUtil.newURI("http://example.com/a");
Cc["@mozilla.org/browser/download-history;1"].
getService(Ci.nsIDownloadHistory).
addDownload(uri);
yield Promise.all([onFrecencyChanged(uri), onFrecencyChanged(uri)]);
});
// nsNavHistory::UpdateFrecency
add_task(function test_nsNavHistory_UpdateFrecency() {
let bm = PlacesUtils.bookmarks;
let uri = NetUtil.newURI("http://example.com/b");
bm.insertBookmark(bm.unfiledBookmarksFolder, uri,
Ci.nsINavBookmarksService.DEFAULT_INDEX, "test");
yield onFrecencyChanged(uri);
});
// nsNavHistory::invalidateFrecencies for particular pages
add_task(function test_nsNavHistory_invalidateFrecencies_somePages() {
let uri = NetUtil.newURI("http://test-nsNavHistory-invalidateFrecencies-somePages.com/");
// Bookmarking the URI is enough to add it to moz_places, and importantly, it
// means that removePagesFromHost doesn't remove it from moz_places, so its
// frecency is able to be changed.
let bm = PlacesUtils.bookmarks;
bm.insertBookmark(bm.unfiledBookmarksFolder, uri,
Ci.nsINavBookmarksService.DEFAULT_INDEX, "test");
PlacesUtils.history.removePagesFromHost(uri.host, false);
yield onFrecencyChanged(uri);
});
// nsNavHistory::invalidateFrecencies for all pages
add_task(function test_nsNavHistory_invalidateFrecencies_allPages() {
PlacesUtils.history.removeAllPages();
yield onManyFrecenciesChanged();
});
// nsNavHistory::DecayFrecency and nsNavHistory::FixInvalidFrecencies
add_task(function test_nsNavHistory_DecayFrecency_and_nsNavHistory_FixInvalidFrecencies() {
// FixInvalidFrecencies is at the end of a path that DecayFrecency is also on,
// so expect two notifications. Trigger the path by making nsNavHistory
// observe the idle-daily notification.
PlacesUtils.history.QueryInterface(Ci.nsIObserver).
observe(null, "idle-daily", "");
yield Promise.all([onManyFrecenciesChanged(), onManyFrecenciesChanged()]);
});
function onFrecencyChanged(expectedURI) {
let deferred = Promise.defer();
let obs = new NavHistoryObserver();
obs.onFrecencyChanged =
(uri, newFrecency, guid, hidden, visitDate) => {
PlacesUtils.history.removeObserver(obs);
do_check_true(!!uri);
do_check_true(uri.equals(expectedURI));
deferred.resolve();
};
PlacesUtils.history.addObserver(obs, false);
return deferred.promise;
}
function onManyFrecenciesChanged() {
let deferred = Promise.defer();
let obs = new NavHistoryObserver();
obs.onManyFrecenciesChanged = () => {
PlacesUtils.history.removeObserver(obs);
do_check_true(true);
deferred.resolve();
};
PlacesUtils.history.addObserver(obs, false);
return deferred.promise;
}

View File

@ -113,6 +113,7 @@ skip-if = true
[test_null_interfaces.js]
[test_onItemChanged_tags.js]
[test_pageGuid_bookmarkGuid.js]
[test_frecency_observers.js]
[test_placeURIs.js]
[test_PlacesUtils_asyncGetBookmarkIds.js]
[test_PlacesUtils_lazyobservers.js]