Backed out 5 changesets (bug 1678619) for causing memory leaks. CLOSED TREE

Backed out changeset 64be4bea09dd (bug 1678619)
Backed out changeset 308fda30c166 (bug 1678619)
Backed out changeset efb9bdef89b8 (bug 1678619)
Backed out changeset d26f45eac0b9 (bug 1678619)
Backed out changeset b2f22d6d2725 (bug 1678619)
This commit is contained in:
Butkovits Atila 2021-01-15 12:56:01 +02:00
Родитель 40d5f1493d
Коммит db44a41c6c
36 изменённых файлов: 633 добавлений и 277 удалений

Просмотреть файл

@ -104,6 +104,8 @@ const getHistoryObserver = () => {
}
onBeginUpdateBatch() {}
onEndUpdateBatch() {}
onFrecencyChanged() {}
onManyFrecenciesChanged() {}
onDeleteVisits(uri, partialRemoval, guid, reason) {
if (!partialRemoval) {
this.emit("visitRemoved", { allHistory: false, urls: [uri.spec] });

Просмотреть файл

@ -73,6 +73,10 @@ class HistoryObserver extends Observer {
onEndUpdateBatch() {}
onFrecencyChanged() {}
onManyFrecenciesChanged() {}
onDeleteVisits() {}
}

Просмотреть файл

@ -766,6 +766,8 @@ describe("PlacesFeed", () => {
it("should have a various empty functions for xpconnect happiness", () => {
observer.onBeginUpdateBatch();
observer.onEndUpdateBatch();
observer.onFrecencyChanged();
observer.onManyFrecenciesChanged();
observer.onDeleteVisits();
});
});

Просмотреть файл

@ -46,7 +46,6 @@ class PlacesEvent : public nsWrapperCache {
virtual const PlacesHistoryCleared* AsPlacesHistoryCleared() const {
return nullptr;
}
virtual const PlacesRanking* AsPlacesRanking() const { return nullptr; }
protected:
virtual ~PlacesEvent() = default;

Просмотреть файл

@ -1,39 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_PlacesRanking_h
#define mozilla_dom_PlacesRanking_h
#include "mozilla/dom/PlacesEvent.h"
namespace mozilla {
namespace dom {
class PlacesRanking final : public PlacesEvent {
public:
explicit PlacesRanking() : PlacesEvent(PlacesEventType::Pages_rank_changed) {}
static already_AddRefed<PlacesRanking> Constructor(
const GlobalObject& aGlobal) {
RefPtr<PlacesRanking> event = new PlacesRanking();
return event.forget();
}
JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override {
return PlacesRanking_Binding::Wrap(aCx, this, aGivenProto);
}
const PlacesRanking* AsPlacesRanking() const override { return this; }
private:
~PlacesRanking() = default;
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_PlacesRanking_h

Просмотреть файл

@ -222,7 +222,6 @@ EXPORTS.mozilla.dom += [
"PlacesFavicon.h",
"PlacesHistoryCleared.h",
"PlacesObservers.h",
"PlacesRanking.h",
"PlacesVisit.h",
"PlacesVisitTitle.h",
"PlacesWeakCallbackWrapper.h",

Просмотреть файл

@ -27,10 +27,6 @@ enum PlacesEventType {
* data: PlacesHistoryCleared. Fired whenever history is cleared.
*/
"history-cleared",
/**
* data: PlacesRanking. Fired whenever pages ranking is changed.
*/
"pages-rank-changed",
};
[ChromeOnly, Exposed=Window]
@ -259,8 +255,3 @@ interface PlacesVisitTitle : PlacesEvent {
interface PlacesHistoryCleared : PlacesEvent {
constructor();
};
[ChromeOnly, Exposed=Window]
interface PlacesRanking : PlacesEvent {
constructor();
};

Просмотреть файл

@ -633,6 +633,8 @@ async function promiseVisit(expectedType, expectedURI) {
},
onBeginUpdateBatch() {},
onEndUpdateBatch() {},
onFrecencyChanged() {},
onManyFrecenciesChanged() {},
onDeleteURI(uri) {
done("removed", uri.spec);
},

Просмотреть файл

@ -345,6 +345,8 @@ var DownloadCache = {
},
onBeginUpdateBatch() {},
onEndUpdateBatch() {},
onFrecencyChanged() {},
onManyFrecenciesChanged() {},
onDeleteVisits() {},
};

Просмотреть файл

@ -843,9 +843,8 @@ var Bookmarks = Object.freeze({
item.url.href != updatedItem.url.href
) {
// ...though we don't wait for the calculation.
updateFrecency(db, [item.url, updatedItem.url]).catch(
Cu.reportError
);
updateFrecency(db, [item.url]).catch(Cu.reportError);
updateFrecency(db, [updatedItem.url]).catch(Cu.reportError);
}
// Notify onItemChanged to listeners.
@ -1378,7 +1377,7 @@ var Bookmarks = Object.freeze({
// We don't wait for the frecency calculation.
if (urls && urls.length) {
await PlacesUtils.keywords.eraseEverything();
updateFrecency(db, urls).catch(Cu.reportError);
updateFrecency(db, urls, true).catch(Cu.reportError);
}
}
);
@ -2231,7 +2230,7 @@ function insertBookmarkTree(items, source, parent, urls, lastAddedForParent) {
});
// We don't wait for the frecency calculation.
updateFrecency(db, urls).catch(Cu.reportError);
updateFrecency(db, urls, true).catch(Cu.reportError);
return items;
}
@ -2739,7 +2738,7 @@ function removeBookmarks(items, options) {
if (urls.length) {
await PlacesUtils.keywords.removeFromURLsIfNotBookmarked(urls);
updateFrecency(db, urls).catch(Cu.reportError);
updateFrecency(db, urls, urls.length > 1).catch(Cu.reportError);
}
}
);
@ -3035,15 +3034,25 @@ function validateBookmarkObject(name, input, behavior) {
* the Sqlite.jsm connection handle.
* @param urls
* the array of URLs to update.
* @param [optional] collapseNotifications
* whether we can send just one onManyFrecenciesChanged
* notification instead of sending one notification for every URL.
*/
var updateFrecency = async function(db, urls) {
var updateFrecency = async function(db, urls, collapseNotifications = false) {
let hrefs = urls.map(url => url.href);
let frecencyClause = "CALCULATE_FRECENCY(id)";
if (!collapseNotifications) {
frecencyClause =
"NOTIFY_FRECENCY(" +
frecencyClause +
", url, guid, hidden, last_visit_date)";
}
// We just use the hashes, since updating a few additional urls won't hurt.
for (let chunk of PlacesUtils.chunkArray(hrefs, db.variableLimit)) {
await db.execute(
`UPDATE moz_places
SET hidden = (url_hash BETWEEN hash("place", "prefix_lo") AND hash("place", "prefix_hi")),
frecency = CALCULATE_FRECENCY(id)
frecency = ${frecencyClause}
WHERE url_hash IN (${sqlBindPlaceholders(chunk, "hash(", ")")})`,
chunk
);
@ -3052,7 +3061,10 @@ var updateFrecency = async function(db, urls) {
// Trigger frecency updates for all affected origins.
await db.executeCached(`DELETE FROM moz_updateoriginsupdate_temp`);
PlacesObservers.notifyListeners([new PlacesRanking()]);
if (collapseNotifications) {
let observers = PlacesUtils.history.getObservers();
notify(observers, "onManyFrecenciesChanged");
}
};
/**

Просмотреть файл

@ -1493,6 +1493,8 @@ nsresult 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);
rv = StoreLastInsertedIdFunction::create(mMainConn);
NS_ENSURE_SUCCESS(rv, rv);
rv = HashFunction::create(mMainConn);

Просмотреть файл

@ -20,7 +20,6 @@
#include "PlaceInfo.h"
#include "VisitInfo.h"
#include "nsPlacesMacros.h"
#include "NotifyRankingChanged.h"
#include "mozilla/storage.h"
#include "mozilla/dom/Link.h"
@ -736,6 +735,20 @@ bool CanAddURI(nsIURI* aURI, const nsCString& aGUID = ""_ns,
return false;
}
class NotifyManyFrecenciesChanged final : public Runnable {
public:
NotifyManyFrecenciesChanged()
: Runnable("places::NotifyManyFrecenciesChanged") {}
NS_IMETHOD Run() override {
MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
NS_ENSURE_STATE(navHistory);
navHistory->NotifyManyFrecenciesChanged();
return NS_OK;
}
};
/**
* Adds a visit to the database.
*/
@ -750,10 +763,14 @@ class InsertVisitedURIs final : public Runnable {
* The locations to record visits.
* @param [optional] aCallback
* The callback to notify about the visit.
* @param [optional] aGroupNotifications
* Whether to group any observer notifications rather than
* sending them out individually.
*/
static nsresult Start(mozIStorageConnection* aConnection,
nsTArray<VisitData>&& aPlaces,
mozIVisitInfoCallback* aCallback = nullptr,
bool aGroupNotifications = false,
uint32_t aInitialUpdatedCount = 0) {
MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
MOZ_ASSERT(aPlaces.Length() > 0, "Must pass a non-empty array!");
@ -777,8 +794,8 @@ class InsertVisitedURIs final : public Runnable {
Unused << aCallback->GetIgnoreResults(&ignoreResults);
}
RefPtr<InsertVisitedURIs> event = new InsertVisitedURIs(
aConnection, std::move(aPlaces), callback, ignoreErrors, ignoreResults,
aInitialUpdatedCount);
aConnection, std::move(aPlaces), callback, aGroupNotifications,
ignoreErrors, ignoreResults, aInitialUpdatedCount);
// Get the target thread, and then start the work!
nsCOMPtr<nsIEventTarget> target = do_GetInterface(aConnection);
@ -797,8 +814,8 @@ class InsertVisitedURIs final : public Runnable {
// whatever we can and then notify the main thread we're done.
nsresult rv = InnerRun();
if (mSuccessfulUpdatedCount > 0) {
NS_DispatchToMainThread(new NotifyRankingChanged());
if (mSuccessfulUpdatedCount > 0 && mGroupNotifications) {
NS_DispatchToMainThread(new NotifyManyFrecenciesChanged());
}
if (!!mCallback) {
NS_DispatchToMainThread(
@ -956,11 +973,13 @@ class InsertVisitedURIs final : public Runnable {
InsertVisitedURIs(
mozIStorageConnection* aConnection, nsTArray<VisitData>&& aPlaces,
const nsMainThreadPtrHandle<mozIVisitInfoCallback>& aCallback,
bool aIgnoreErrors, bool aIgnoreResults, uint32_t aInitialUpdatedCount)
bool aGroupNotifications, bool aIgnoreErrors, bool aIgnoreResults,
uint32_t aInitialUpdatedCount)
: Runnable("places::InsertVisitedURIs"),
mDBConn(aConnection),
mPlaces(std::move(aPlaces)),
mCallback(aCallback),
mGroupNotifications(aGroupNotifications),
mIgnoreErrors(aIgnoreErrors),
mIgnoreResults(aIgnoreResults),
mSuccessfulUpdatedCount(aInitialUpdatedCount),
@ -999,7 +1018,7 @@ class InsertVisitedURIs final : public Runnable {
}
// Otherwise, the page was not in moz_places, so now we have to add it.
else {
rv = mHistory->InsertPlace(aPlace);
rv = mHistory->InsertPlace(aPlace, !mGroupNotifications);
NS_ENSURE_SUCCESS(rv, rv);
aPlace.placeId = nsNavHistory::sLastInsertedPlaceId;
}
@ -1110,10 +1129,24 @@ class InsertVisitedURIs final : public Runnable {
nsresult rv;
{ // First, set our frecency to the proper value.
nsCOMPtr<mozIStorageStatement> stmt = mHistory->GetStatement(
"UPDATE moz_places "
"SET frecency = CALCULATE_FRECENCY(:page_id, :redirect) "
"WHERE id = :page_id");
nsCOMPtr<mozIStorageStatement> stmt;
if (!mGroupNotifications) {
// If we're notifying for individual frecency updates, use
// the notify_frecency sql function which will call us back.
stmt = mHistory->GetStatement(
"UPDATE moz_places "
"SET frecency = NOTIFY_FRECENCY("
"CALCULATE_FRECENCY(:page_id, :redirect), "
"url, guid, hidden, last_visit_date"
") "
"WHERE id = :page_id");
} else {
// otherwise, just update the frecency without notifying.
stmt = mHistory->GetStatement(
"UPDATE moz_places "
"SET frecency = CALCULATE_FRECENCY(:page_id, :redirect) "
"WHERE id = :page_id");
}
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);
@ -1153,6 +1186,8 @@ class InsertVisitedURIs final : public Runnable {
nsMainThreadPtrHandle<mozIVisitInfoCallback> mCallback;
bool mGroupNotifications;
bool mIgnoreErrors;
bool mIgnoreResults;
@ -1436,7 +1471,8 @@ nsresult History::QueueVisitedStatement(RefPtr<VisitedQuery> aQuery) {
return NS_OK;
}
nsresult History::InsertPlace(VisitData& aPlace) {
nsresult History::InsertPlace(VisitData& aPlace,
bool aShouldNotifyFrecencyChanged) {
MOZ_ASSERT(aPlace.placeId == 0, "should not have a valid place id!");
MOZ_ASSERT(!aPlace.shouldUpdateHidden, "We should not need to update hidden");
MOZ_ASSERT(!NS_IsMainThread(), "must be called off of the main thread!");
@ -1480,6 +1516,14 @@ nsresult History::InsertPlace(VisitData& aPlace) {
rv = stmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
// Post an onFrecencyChanged observer notification.
if (aShouldNotifyFrecencyChanged) {
const nsNavHistory* navHistory = nsNavHistory::GetConstHistoryService();
NS_ENSURE_STATE(navHistory);
navHistory->DispatchFrecencyChangedNotification(
aPlace.spec, frecency, aPlace.guid, aPlace.hidden, aPlace.visitTime);
}
return NS_OK;
}
@ -1929,7 +1973,8 @@ History::SetURITitle(nsIURI* aURI, const nsAString& aTitle) {
NS_IMETHODIMP
History::UpdatePlaces(JS::Handle<JS::Value> aPlaceInfos,
mozIVisitInfoCallback* aCallback, JSContext* aCtx) {
mozIVisitInfoCallback* aCallback,
bool aGroupNotifications, JSContext* aCtx) {
NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_UNEXPECTED);
NS_ENSURE_TRUE(!aPlaceInfos.isPrimitive(), NS_ERROR_INVALID_ARG);
@ -2067,8 +2112,9 @@ History::UpdatePlaces(JS::Handle<JS::Value> aPlaceInfos,
// CanAddURI, which isn't an error. If we have no visits to add, however,
// we should not call InsertVisitedURIs::Start.
if (visitData.Length()) {
nsresult rv = InsertVisitedURIs::Start(dbConn, std::move(visitData),
callback, initialUpdatedCount);
nsresult rv =
InsertVisitedURIs::Start(dbConn, std::move(visitData), callback,
aGroupNotifications, initialUpdatedCount);
NS_ENSURE_SUCCESS(rv, rv);
} else if (aCallback) {
// Be sure to notify that all of our operations are complete. This

Просмотреть файл

@ -72,8 +72,14 @@ class History final : public BaseHistory,
*
* @param aVisitData
* The visit data to use to populate a new row in moz_places.
* @param aShouldNotifyFrecencyChanged
* Whether to dispatch OnFrecencyChanged notifications.
* Defaults to true. Set to false if you (the caller) are
* doing many inserts and will dispatch your own
* OnManyFrecenciesChanged notification.
*/
nsresult InsertPlace(VisitData& aVisitData);
nsresult InsertPlace(VisitData& aVisitData,
bool aShouldNotifyFrecencyChanged = true);
/**
* Updates an entry in moz_places with the data in aVisitData.

Просмотреть файл

@ -861,8 +861,9 @@ var invalidateFrecencies = async function(db, idList) {
for (let chunk of PlacesUtils.chunkArray(idList, db.variableLimit)) {
await db.execute(
`UPDATE moz_places
SET frecency = CALCULATE_FRECENCY(id)
WHERE id in (${sqlBindPlaceholders(chunk)})`,
SET frecency = NOTIFY_FRECENCY(
CALCULATE_FRECENCY(id), url, guid, hidden, last_visit_date
) WHERE id in (${sqlBindPlaceholders(chunk)})`,
chunk
);
await db.execute(
@ -873,9 +874,6 @@ var invalidateFrecencies = async function(db, idList) {
chunk
);
}
PlacesObservers.notifyListeners([new PlacesRanking()]);
// Trigger frecency updates for all affected origins.
await db.execute(`DELETE FROM moz_updateoriginsupdate_temp`);
};
@ -918,10 +916,11 @@ var clear = async function(db) {
WHERE frecency > 0`);
});
PlacesObservers.notifyListeners([
new PlacesHistoryCleared(),
new PlacesRanking(),
]);
let observers = PlacesUtils.history.getObservers();
// Notify frecency change observers.
notify(observers, "onManyFrecenciesChanged");
PlacesObservers.notifyListeners([new PlacesHistoryCleared()]);
// Trigger frecency updates for all affected origins.
await db.execute(`DELETE FROM moz_updateoriginsupdate_temp`);
@ -1619,27 +1618,31 @@ var insertMany = function(db, pageInfos, onResult, onError) {
}
return new Promise((resolve, reject) => {
asyncHistory.updatePlaces(infos, {
handleError: (resultCode, result) => {
let pageInfo = mergeUpdateInfoIntoPageInfo(result);
onErrorData.push(pageInfo);
asyncHistory.updatePlaces(
infos,
{
handleError: (resultCode, result) => {
let pageInfo = mergeUpdateInfoIntoPageInfo(result);
onErrorData.push(pageInfo);
},
handleResult: result => {
let pageInfo = mergeUpdateInfoIntoPageInfo(result);
onResultData.push(pageInfo);
},
ignoreErrors: !onError,
ignoreResults: !onResult,
handleCompletion: updatedCount => {
notifyOnResult(onResultData, onResult);
notifyOnResult(onErrorData, onError);
if (updatedCount > 0) {
resolve();
} else {
reject({ message: "No items were added to history." });
}
},
},
handleResult: result => {
let pageInfo = mergeUpdateInfoIntoPageInfo(result);
onResultData.push(pageInfo);
},
ignoreErrors: !onError,
ignoreResults: !onResult,
handleCompletion: updatedCount => {
notifyOnResult(onResultData, onResult);
notifyOnResult(onErrorData, onError);
if (updatedCount > 0) {
resolve();
} else {
reject({ message: "No items were added to history." });
}
},
});
true
);
});
};

Просмотреть файл

@ -1,41 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_places_NotifyRankingChanged_h_
#define mozilla_places_NotifyRankingChanged_h_
#include "mozilla/dom/PlacesObservers.h"
#include "mozilla/dom/PlacesRanking.h"
using namespace mozilla::dom;
namespace mozilla {
namespace places {
class NotifyRankingChanged final : public Runnable {
public:
NotifyRankingChanged() : Runnable("places::NotifyRankingChanged") {}
// MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is marked
// MOZ_CAN_RUN_SCRIPT. See bug 1535398.
MOZ_CAN_RUN_SCRIPT_BOUNDARY
NS_IMETHOD Run() override {
MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
RefPtr<PlacesRanking> event = new PlacesRanking();
Sequence<OwningNonNull<PlacesEvent>> events;
bool success = !!events.AppendElement(event.forget(), fallible);
MOZ_RELEASE_ASSERT(success);
PlacesObservers::NotifyListeners(events);
return NS_OK;
}
};
} // namespace places
} // namespace mozilla
#endif // mozilla_places_NotifyRankingChanged_h_

Просмотреть файл

@ -861,6 +861,54 @@ FixupURLFunction::OnFunctionCall(mozIStorageValueArray* aArguments,
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//// Frecency Changed Notification Function
/* static */
nsresult FrecencyNotificationFunction::create(mozIStorageConnection* aDBConn) {
RefPtr<FrecencyNotificationFunction> function =
new FrecencyNotificationFunction();
nsresult rv = aDBConn->CreateFunction("notify_frecency"_ns, 5, function);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMPL_ISUPPORTS(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);
RefPtr<nsVariant> result = new nsVariant();
rv = result->SetAsInt32(newFrecency);
NS_ENSURE_SUCCESS(rv, rv);
result.forget(_result);
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//// Store Last Inserted Id Function

Просмотреть файл

@ -316,6 +316,43 @@ class FixupURLFunction final : public mozIStorageFunction {
~FixupURLFunction() = default;
};
////////////////////////////////////////////////////////////////////////////////
//// 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 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);
private:
~FrecencyNotificationFunction() = default;
};
////////////////////////////////////////////////////////////////////////////////
//// Store Last Inserted Id Function

Просмотреть файл

@ -28,7 +28,6 @@ if CONFIG["MOZ_PLACES"]:
"Database.h",
"History.h",
"INativePlacesEventCallback.h",
"NotifyRankingChanged.h",
"PageIconProtocolHandler.h",
"Shutdown.h",
"SyncedBookmarksMirror.h",

Просмотреть файл

@ -152,6 +152,9 @@ interface mozIAsyncHistory : nsISupports
* @param [optional] aCallback
* A mozIVisitInfoCallback object which consists of callbacks to be
* notified for successful and/or failed changes.
* @param [optional] aGroupNotifications
* If set to true, the implementation will attempt to avoid using
* per-place/visit notifications as much as possible.
*
* @throws NS_ERROR_INVALID_ARG
* - Passing in NULL for aPlaceInfo.
@ -165,7 +168,8 @@ interface mozIAsyncHistory : nsISupports
*/
[implicit_jscontext]
void updatePlaces(in jsval aPlaceInfo,
[optional] in mozIVisitInfoCallback aCallback);
[optional] in mozIVisitInfoCallback aCallback,
[optional] in bool aGroupNotifications);
/**
* Checks if a given URI has been visited.

Просмотреть файл

@ -579,6 +579,37 @@ interface nsINavHistoryObserver : nsISupports
*/
void onEndUpdateBatch();
/**
* 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.
*/

Просмотреть файл

@ -19,7 +19,6 @@
#include "DateTimeFormat.h"
#include "History.h"
#include "Helpers.h"
#include "NotifyRankingChanged.h"
#include "nsTArray.h"
#include "nsCollationCID.h"
@ -223,20 +222,13 @@ class FixAndDecayFrecencyRunnable final : public Runnable {
mDecayRate(aDecayRate),
mDecayReason(mozIStorageStatementCallback::REASON_FINISHED) {}
// MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is marked
// MOZ_CAN_RUN_SCRIPT. See bug 1535398.
MOZ_CAN_RUN_SCRIPT_BOUNDARY
NS_IMETHOD Run() override {
NS_IMETHOD
Run() override {
if (NS_IsMainThread()) {
nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
NS_ENSURE_STATE(navHistory);
navHistory->DecayFrecencyCompleted();
if (mozIStorageStatementCallback::REASON_FINISHED == mDecayReason) {
NotifyRankingChanged().Run();
}
navHistory->DecayFrecencyCompleted(mDecayReason);
return NS_OK;
}
@ -609,6 +601,42 @@ void nsNavHistory::UpdateDaysOfHistory(PRTime visitTime) {
}
}
void nsNavHistory::NotifyFrecencyChanged(const nsACString& aSpec,
int32_t aNewFrecency,
const nsACString& aGUID, bool aHidden,
PRTime aLastVisitDate) {
MOZ_ASSERT(!aGUID.IsEmpty());
nsCOMPtr<nsIURI> uri;
Unused << NS_NewURI(getter_AddRefs(uri), aSpec);
// We cannot assert since some automated tests are checking this path.
NS_WARNING_ASSERTION(uri,
"Invalid URI in nsNavHistory::NotifyFrecencyChanged");
// Notify a frecency change only if we have a valid uri, otherwise
// the observer couldn't gather any useful data from the notification.
if (!uri) {
return;
}
NOTIFY_OBSERVERS(
mCanNotify, mObservers, nsINavHistoryObserver,
OnFrecencyChanged(uri, aNewFrecency, aGUID, aHidden, aLastVisitDate));
}
void nsNavHistory::NotifyManyFrecenciesChanged() {
NOTIFY_OBSERVERS(mCanNotify, mObservers, nsINavHistoryObserver,
OnManyFrecenciesChanged());
}
void nsNavHistory::DispatchFrecencyChangedNotification(
const nsACString& aSpec, int32_t aNewFrecency, const nsACString& aGUID,
bool aHidden, PRTime aLastVisitDate) const {
Unused << NS_DispatchToMainThread(
NewRunnableMethod<nsCString, int32_t, nsCString, bool, PRTime>(
"nsNavHistory::NotifyFrecencyChanged",
const_cast<nsNavHistory*>(this), &nsNavHistory::NotifyFrecencyChanged,
aSpec, aNewFrecency, aGUID, aHidden, aLastVisitDate));
}
NS_IMETHODIMP
nsNavHistory::RecalculateOriginFrecencyStats(nsIObserver* aCallback) {
RefPtr<nsNavHistory> self(this);
@ -2185,9 +2213,12 @@ nsNavHistory::DecayFrecency() {
return target->Dispatch(runnable, NS_DISPATCH_NORMAL);
}
void nsNavHistory::DecayFrecencyCompleted() {
void nsNavHistory::DecayFrecencyCompleted(uint16_t reason) {
MOZ_ASSERT(mDecayFrecencyPendingCount > 0);
mDecayFrecencyPendingCount--;
if (mozIStorageStatementCallback::REASON_FINISHED == reason) {
NotifyManyFrecenciesChanged();
}
}
bool nsNavHistory::IsFrecencyDecaying() const {
@ -3236,10 +3267,11 @@ nsresult 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);
NS_DispatchToMainThread(new NotifyRankingChanged());
nsresult rv = updateFrecencyStmt->BindInt64ByName("page_id"_ns, aPlaceId);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<mozIStorageAsyncStatement> updateHiddenStmt = mDB->GetAsyncStatement(

Просмотреть файл

@ -345,6 +345,27 @@ class nsNavHistory final : public nsSupportsWeakReference,
*/
void UpdateDaysOfHistory(PRTime visitTime);
/**
* Fires onFrecencyChanged event to nsINavHistoryService observers
*/
void NotifyFrecencyChanged(const nsACString& aSpec, 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;
/**
* Returns true if frecency is currently being decayed.
*
@ -379,7 +400,7 @@ class nsNavHistory final : public nsSupportsWeakReference,
const RefPtr<nsNavHistoryQuery>& aQuery,
nsNavHistoryQueryOptions* aOptions);
void DecayFrecencyCompleted();
void DecayFrecencyCompleted(uint16_t reason);
private:
~nsNavHistory();

Просмотреть файл

@ -2257,6 +2257,18 @@ nsresult nsNavHistoryQueryResultNode::OnTitleChanged(
return ChangeTitles(aURI, newTitle, true, onlyOneEntry);
}
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.
@ -4211,6 +4223,16 @@ void nsNavHistoryResult::HandlePlacesEvent(const PlacesEventSequence& aEvents) {
}
}
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,
uint16_t aReason) {

Просмотреть файл

@ -64,6 +64,10 @@ class nsTrimInt64HashKey : public PLDHashEntryHdr {
// and nsINavHistoryObserver (some methods, such as BeginUpdateBatch overlap)
#define NS_DECL_BOOKMARK_HISTORY_OBSERVER_BASE(...) \
NS_DECL_NSINAVBOOKMARKOBSERVER \
NS_IMETHOD OnFrecencyChanged(nsIURI* aURI, int32_t aNewFrecency, \
const nsACString& aGUID, bool aHidden, \
PRTime aLastVisitDate) __VA_ARGS__; \
NS_IMETHOD OnManyFrecenciesChanged() __VA_ARGS__; \
NS_IMETHOD OnDeleteURI(nsIURI* aURI, const nsACString& aGUID, \
uint16_t aReason) __VA_ARGS__; \
NS_IMETHOD OnDeleteVisits(nsIURI* aURI, bool aPartialRemoval, \

Просмотреть файл

@ -1,6 +1,19 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
function promiseManyFrecenciesChanged() {
return new Promise(resolve => {
let obs = new NavHistoryObserver();
obs.onManyFrecenciesChanged = () => {
Assert.ok(true, "onManyFrecenciesChanged is triggered.");
PlacesUtils.history.removeObserver(obs);
resolve();
};
PlacesUtils.history.addObserver(obs);
});
}
add_task(async function test_eraseEverything() {
await PlacesTestUtils.addVisits({
uri: NetUtil.newURI("http://example.com/"),
@ -70,16 +83,12 @@ add_task(async function test_eraseEverything() {
Assert.ok(frecencyForUrl("http://example.com/") > frecencyForExample);
Assert.ok(frecencyForUrl("http://example.com/") > frecencyForMozilla);
const promise = PlacesTestUtils.waitForNotification(
"pages-rank-changed",
() => true,
"places"
);
let manyFrecenciesPromise = promiseManyFrecenciesChanged();
await PlacesUtils.bookmarks.eraseEverything();
// Ensure we get an pages-rank-changed event.
await promise;
// Ensure we get an onManyFrecenciesChanged notification.
await manyFrecenciesPromise;
Assert.equal(frecencyForUrl("http://example.com/"), frecencyForExample);
Assert.equal(frecencyForUrl("http://example.com/"), frecencyForMozilla);

Просмотреть файл

@ -3,11 +3,23 @@
const UNVISITED_BOOKMARK_BONUS = 140;
function promiseRankingChanged() {
function promiseFrecencyChanged(expectedURI, expectedFrecency) {
return PlacesTestUtils.waitForNotification(
"pages-rank-changed",
() => true,
"places"
"onFrecencyChanged",
(uri, newFrecency) => {
Assert.equal(
uri.spec,
expectedURI,
"onFrecencyChanged is triggered for the correct uri."
);
Assert.equal(
newFrecency,
expectedFrecency,
"onFrecencyChanged has the expected frecency"
);
return true;
},
"history"
);
}
@ -106,7 +118,10 @@ add_task(async function remove_roots_fail() {
add_task(async function remove_bookmark() {
// When removing a bookmark we need to check the frecency. First we confirm
// that there is a normal update when it is inserted.
let promise = promiseRankingChanged();
let frecencyChangedPromise = promiseFrecencyChanged(
"http://example.com/",
UNVISITED_BOOKMARK_BONUS
);
let bm1 = await PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
@ -115,20 +130,23 @@ add_task(async function remove_bookmark() {
});
checkBookmarkObject(bm1);
await promise;
await frecencyChangedPromise;
// This second one checks the frecency is changed when we remove the bookmark.
promise = promiseRankingChanged();
frecencyChangedPromise = promiseFrecencyChanged("http://example.com/", 0);
await PlacesUtils.bookmarks.remove(bm1.guid);
await promise;
await frecencyChangedPromise;
});
add_task(async function remove_multiple_bookmarks_simple() {
// When removing a bookmark we need to check the frecency. First we confirm
// that there is a normal update when it is inserted.
const promise1 = promiseRankingChanged();
let frecencyChangedPromise = promiseFrecencyChanged(
"http://example.com/",
UNVISITED_BOOKMARK_BONUS
);
let bm1 = await PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
@ -137,7 +155,10 @@ add_task(async function remove_multiple_bookmarks_simple() {
});
checkBookmarkObject(bm1);
const promise2 = promiseRankingChanged();
let frecencyChangedPromise1 = promiseFrecencyChanged(
"http://example1.com/",
UNVISITED_BOOKMARK_BONUS
);
let bm2 = await PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
@ -146,15 +167,19 @@ add_task(async function remove_multiple_bookmarks_simple() {
});
checkBookmarkObject(bm2);
await Promise.all([promise1, promise2]);
await Promise.all([frecencyChangedPromise, frecencyChangedPromise1]);
// We should get a pages-rank-changed event with the removal of
// We should get an onManyFrecenciesChanged notification with the removal of
// multiple bookmarks.
const promise3 = promiseRankingChanged();
let manyFrencenciesPromise = PlacesTestUtils.waitForNotification(
"onManyFrecenciesChanged",
() => true,
"history"
);
await PlacesUtils.bookmarks.remove([bm1, bm2]);
await promise3;
await manyFrencenciesPromise;
});
add_task(async function remove_multiple_bookmarks_complex() {
@ -284,7 +309,7 @@ add_task(async function remove_folder() {
await PlacesUtils.bookmarks.remove(bm1.guid);
Assert.strictEqual(await PlacesUtils.bookmarks.fetch(bm1.guid), null);
// No wait for pages-rank-changed event in this test as the folder doesn't have
// No wait for onManyFrecenciesChanged in this test as the folder doesn't have
// any children that would need updating.
});
@ -311,12 +336,12 @@ add_task(async function test_contents_removed() {
false,
false
);
const promise = promiseRankingChanged();
let frecencyChangedPromise = promiseFrecencyChanged("http://example.com/", 0);
await PlacesUtils.bookmarks.remove(folder1);
Assert.strictEqual(await PlacesUtils.bookmarks.fetch(folder1.guid), null);
Assert.strictEqual(await PlacesUtils.bookmarks.fetch(bm1.guid), null);
await promise;
await frecencyChangedPromise;
let expectedNotifications = [
{
@ -356,13 +381,13 @@ add_task(async function test_nested_contents_removed() {
title: "",
});
const promise = promiseRankingChanged();
let frecencyChangedPromise = promiseFrecencyChanged("http://example.com/", 0);
await PlacesUtils.bookmarks.remove(folder1);
Assert.strictEqual(await PlacesUtils.bookmarks.fetch(folder1.guid), null);
Assert.strictEqual(await PlacesUtils.bookmarks.fetch(folder2.guid), null);
Assert.strictEqual(await PlacesUtils.bookmarks.fetch(bm1.guid), null);
await promise;
await frecencyChangedPromise;
});
add_task(async function remove_folder_empty_title() {

Просмотреть файл

@ -31,7 +31,7 @@ function VisitInfo(aTransitionType, aVisitTime) {
this.visitDate = aVisitTime || Date.now() * 1000;
}
function promiseUpdatePlaces(aPlaces, aOptions = {}) {
function promiseUpdatePlaces(aPlaces, aOptions, aBatchFrecencyNotifications) {
return new Promise((resolve, reject) => {
asyncHistory.updatePlaces(
aPlaces,
@ -54,7 +54,8 @@ function promiseUpdatePlaces(aPlaces, aOptions = {}) {
},
},
aOptions
)
),
aBatchFrecencyNotifications
);
});
}
@ -1146,15 +1147,24 @@ add_task(async function test_omit_frecency_notifications() {
visits: [new VisitInfo(TRANSITION_TYPED)],
},
];
const promiseRankingChanged = PlacesTestUtils.waitForNotification(
"pages-rank-changed",
() => true,
"places"
);
await promiseUpdatePlaces(places);
await promiseRankingChanged;
let promiseFrecenciesChanged = new Promise(resolve => {
let frecencyObserverCheck = {
onFrecencyChanged() {
ok(
false,
"Should not fire frecencyChanged because we explicitly asked not to do so."
);
},
onManyFrecenciesChanged() {
ok(true, "Should fire many frecencies changed notification instead.");
PlacesUtils.history.removeObserver(frecencyObserverCheck);
resolve();
},
};
PlacesUtils.history.addObserver(frecencyObserverCheck);
});
await promiseUpdatePlaces(places, {}, true);
await promiseFrecenciesChanged;
});
add_task(async function test_ignore_errors() {

Просмотреть файл

@ -51,6 +51,21 @@ add_task(async function test_insertMany() {
};
let inserter = async function(name, filter, useCallbacks) {
function promiseManyFrecenciesChanged() {
return new Promise((resolve, reject) => {
let obs = new NavHistoryObserver();
obs.onManyFrecenciesChanged = () => {
PlacesUtils.history.removeObserver(obs);
resolve();
};
obs.onFrecencyChanged = () => {
PlacesUtils.history.removeObserver(obs);
reject();
};
PlacesUtils.history.addObserver(obs);
});
}
info(name);
info(`filter: ${filter}`);
info(`useCallbacks: ${useCallbacks}`);
@ -112,13 +127,9 @@ add_task(async function test_insertMany() {
"onError callback was called for each bad url"
);
} else {
const promiseRankingChanged = PlacesTestUtils.waitForNotification(
"pages-rank-changed",
() => true,
"places"
);
let promiseManyFrecencies = promiseManyFrecenciesChanged();
result = await PlacesUtils.history.insertMany(pageInfos);
await promiseRankingChanged;
await promiseManyFrecencies;
}
Assert.equal(undefined, result, "insertMany returned undefined");

Просмотреть файл

@ -45,6 +45,25 @@ add_task(async function test_remove_single() {
observer = {
onBeginUpdateBatch() {},
onEndUpdateBatch() {},
onFrecencyChanged(aURI) {
try {
Assert.ok(!shouldRemove, "Observing onFrecencyChanged");
Assert.equal(
aURI.spec,
uri.spec,
"Observing effect on the right uri"
);
} finally {
resolve();
}
},
onManyFrecenciesChanged() {
try {
Assert.ok(!shouldRemove, "Observing onManyFrecenciesChanged");
} finally {
resolve();
}
},
onDeleteURI(aURI) {
try {
Assert.ok(shouldRemove, "Observing onDeleteURI");
@ -79,21 +98,13 @@ add_task(async function test_remove_single() {
reject("Unexpected history-cleared event happens");
break;
}
case "pages-rank-changed": {
try {
Assert.ok(!shouldRemove, "Observing pages-rank-changed event");
} finally {
resolve();
}
break;
}
}
}
};
});
PlacesUtils.history.addObserver(observer);
PlacesObservers.addListener(
["page-title-changed", "history-cleared", "pages-rank-changed"],
["page-title-changed", "history-cleared"],
placesEventListener
);
@ -121,7 +132,7 @@ add_task(async function test_remove_single() {
await promiseObserved;
PlacesUtils.history.removeObserver(observer);
PlacesObservers.removeListener(
["page-title-changed", "history-cleared", "pages-rank-changed"],
["page-title-changed", "history-cleared"],
placesEventListener
);

Просмотреть файл

@ -464,6 +464,8 @@ function getObserverPromise(bookmarkedUri) {
observer = {
onBeginUpdateBatch() {},
onEndUpdateBatch() {},
onFrecencyChanged(aURI) {},
onManyFrecenciesChanged() {},
onDeleteURI(aURI) {
try {
Assert.notEqual(

Просмотреть файл

@ -40,6 +40,8 @@ add_task(async function test_remove_many() {
onResultCalled: false,
// `true` once `onDeleteVisits` has been called for this page
onDeleteVisitsCalled: false,
// `true` once `onFrecencyChangedCalled` has been called for this page
onFrecencyChangedCalled: false,
// `true` once `onDeleteURI` has been called for this page
onDeleteURICalled: false,
};
@ -76,13 +78,27 @@ add_task(async function test_remove_many() {
}
}
let onPageRankingChanged = false;
let observer = {
onBeginUpdateBatch() {},
onEndUpdateBatch() {},
onVisits(aVisits) {
Assert.ok(false, "Unexpected call to onVisits " + aVisits.length);
},
onFrecencyChanged(aURI) {
let origin = pages.find(x => x.uri.spec == aURI.spec);
Assert.ok(origin);
Assert.ok(
origin.hasBookmark,
"Observing onFrecencyChanged on a page with a bookmark"
);
origin.onFrecencyChangedCalled = true;
},
onManyFrecenciesChanged() {
Assert.ok(
false,
"Observing onManyFrecenciesChanges, this is most likely correct but not covered by this test"
);
},
onDeleteURI(aURI) {
let origin = pages.find(x => x.uri.spec == aURI.spec);
Assert.ok(origin);
@ -122,16 +138,12 @@ add_task(async function test_remove_many() {
Assert.ok(false, "Unexpected history-cleared event happens");
break;
}
case "pages-rank-changed": {
onPageRankingChanged = true;
break;
}
}
}
};
PlacesObservers.addListener(
["page-title-changed", "history-cleared", "pages-rank-changed"],
["page-title-changed", "history-cleared"],
placesEventListener
);
@ -151,7 +163,7 @@ add_task(async function test_remove_many() {
PlacesUtils.history.removeObserver(observer);
PlacesObservers.removeListener(
["page-title-changed", "history-cleared", "pages-rank-changed"],
["page-title-changed", "history-cleared"],
placesEventListener
);
@ -172,20 +184,17 @@ add_task(async function test_remove_many() {
page.hasBookmark,
"Page is present only if it also has bookmarks"
);
Assert.notEqual(
page.onDeleteURICalled,
Assert.equal(
page.onFrecencyChangedCalled,
page.onDeleteVisitsCalled,
"Either only onDeleteVisits or onDeleteVisitsCalled should be called"
"onDeleteVisits was called iff onFrecencyChanged was called"
);
Assert.ok(
page.onFrecencyChangedCalled ^ page.onDeleteURICalled,
"Either onFrecencyChanged or onDeleteURI was called"
);
}
Assert.equal(
onPageRankingChanged,
pages.some(p => p.onDeleteVisitsCalled) ||
pages.some(p => p.onDeleteURICalled),
"page-rank-changed was fired if onDeleteVisitsCalled or onDeleteURICalled was called"
);
Assert.notEqual(
visits_in_database(WITNESS_URI),
0,

Просмотреть файл

@ -24,7 +24,7 @@ add_task(async function test_removeVisitsByFilter() {
let bookmarkIndices = new Set(options.bookmarks);
let visits = [];
let rankingChangePromises = [];
let frecencyChangePromises = new Map();
let uriDeletePromises = new Map();
let getURL = options.url
? i =>
@ -64,6 +64,7 @@ add_task(async function test_removeVisitsByFilter() {
// `true` if there is a bookmark for this URI, i.e. of the page
// should not be entirely removed.
hasBookmark,
onFrecencyChanged: null,
onDeleteURI: null,
},
};
@ -134,7 +135,10 @@ add_task(async function test_removeVisitsByFilter() {
(options.url &&
remainingItems.some(v => v.uri.spec == removedItems[i].uri.spec))
) {
rankingChangePromises.push(PromiseUtils.defer());
frecencyChangePromises.set(
removedItems[i].uri.spec,
PromiseUtils.defer()
);
} else if (!options.url || i == 0) {
uriDeletePromises.set(removedItems[i].uri.spec, PromiseUtils.defer());
}
@ -144,6 +148,18 @@ add_task(async function test_removeVisitsByFilter() {
deferred: PromiseUtils.defer(),
onBeginUpdateBatch() {},
onEndUpdateBatch() {},
onFrecencyChanged(aURI) {
info("onFrecencyChanged " + aURI.spec);
let deferred = frecencyChangePromises.get(aURI.spec);
Assert.ok(!!deferred, "Observing onFrecencyChanged");
deferred.resolve();
},
onManyFrecenciesChanged() {
info("Many frecencies changed");
for (let [, deferred] of frecencyChangePromises) {
deferred.resolve();
}
},
onDeleteURI(aURI) {
info("onDeleteURI " + aURI.spec);
let deferred = uriDeletePromises.get(aURI.spec);
@ -170,18 +186,11 @@ add_task(async function test_removeVisitsByFilter() {
this.deferred.reject("Unexpected history-cleared event happens");
break;
}
case "pages-rank-changed": {
info("pages-rank-changed");
for (const deferred of rankingChangePromises) {
deferred.resolve();
}
break;
}
}
}
};
PlacesObservers.addListener(
["page-title-changed", "history-cleared", "pages-rank-changed"],
["page-title-changed", "history-cleared"],
placesEventListener
);
@ -259,10 +268,10 @@ add_task(async function test_removeVisitsByFilter() {
info("Checking URI delete promises.");
await Promise.all(Array.from(uriDeletePromises.values()));
info("Checking frecency change promises.");
await Promise.all(rankingChangePromises);
await Promise.all(Array.from(frecencyChangePromises.values()));
PlacesUtils.history.removeObserver(observer);
PlacesObservers.removeListener(
["page-title-changed", "history-cleared", "pages-rank-changed"],
["page-title-changed", "history-cleared"],
placesEventListener
);
};

Просмотреть файл

@ -5,16 +5,48 @@ const { PromiseUtils } = ChromeUtils.import(
const PREF_FREC_DECAY_RATE_DEF = 0.975;
/**
* Promises that the pages-rank-changed event has been seen.
* Promises that the frecency has been changed, and is the new value.
*
* @param {nsIURI} expectedURI The URI to check frecency for.
* @param {Number} expectedFrecency The expected frecency for the URI.
* @returns {Promise} A promise which is resolved when the URI is seen.
*/
function promiseFrecencyChanged(expectedURI, expectedFrecency) {
let deferred = PromiseUtils.defer();
let obs = new NavHistoryObserver();
obs.onFrecencyChanged = (uri, newFrecency, guid, hidden, visitDate) => {
PlacesUtils.history.removeObserver(obs);
Assert.ok(!!uri, "uri should not be null");
Assert.ok(
uri.equals(NetUtil.newURI(expectedURI)),
"uri should be the expected one"
);
Assert.equal(
newFrecency,
expectedFrecency,
"Frecency should be the expected one"
);
deferred.resolve();
};
PlacesUtils.history.addObserver(obs);
return deferred.promise;
}
/**
* Promises that the many frecencies changed notification has been seen.
*
* @returns {Promise} A promise which is resolved when the notification is seen.
*/
function promiseRankingChanged() {
return PlacesTestUtils.waitForNotification(
"pages-rank-changed",
() => true,
"places"
);
function promiseManyFrecenciesChanged() {
let deferred = PromiseUtils.defer();
let obs = new NavHistoryObserver();
obs.onManyFrecenciesChanged = () => {
PlacesUtils.history.removeObserver(obs);
Assert.ok(true);
deferred.resolve();
};
PlacesUtils.history.addObserver(obs);
return deferred.promise;
}
add_task(async function setup() {
@ -31,7 +63,7 @@ add_task(async function test_frecency_decay() {
// Add a bookmark and check its frecency.
let url = "http://example.com/b";
let promiseOne = promiseRankingChanged();
let promiseOne = promiseFrecencyChanged(url, unvisitedBookmarkFrecency);
await PlacesUtils.bookmarks.insert({
url,
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
@ -39,7 +71,7 @@ add_task(async function test_frecency_decay() {
await promiseOne;
// Trigger DecayFrecency.
let promiseMany = promiseRankingChanged();
let promiseMany = promiseManyFrecenciesChanged();
PlacesUtils.history
.QueryInterface(Ci.nsIObserver)
.observe(null, "idle-daily", "");

Просмотреть файл

@ -11,7 +11,7 @@ add_task(
// two birds with one stone and expect two notifications. Trigger the path by
// adding a download.
let url = Services.io.newURI("http://example.com/a");
let promise = onRankingChanged();
let promises = [onFrecencyChanged(url), onFrecencyChanged(url)];
await PlacesUtils.history.insert({
url,
visits: [
@ -20,14 +20,14 @@ add_task(
},
],
});
await promise;
await Promise.all(promises);
}
);
// nsNavHistory::UpdateFrecency
add_task(async function test_nsNavHistory_UpdateFrecency() {
let url = Services.io.newURI("http://example.com/b");
let promise = onRankingChanged();
let promise = onFrecencyChanged(url);
await PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url,
@ -47,14 +47,14 @@ add_task(async function test_invalidateFrecencies() {
url,
title: "test",
});
let promise = onRankingChanged();
let promise = onFrecencyChanged(url);
await PlacesUtils.history.removeByFilter({ host: url.host });
await promise;
});
// History.jsm clear()
add_task(async function test_clear() {
await Promise.all([onRankingChanged(), PlacesUtils.history.clear()]);
await Promise.all([onManyFrecenciesChanged(), PlacesUtils.history.clear()]);
});
// nsNavHistory::FixAndDecayFrecency
@ -64,13 +64,30 @@ add_task(async function test_nsNavHistory_FixAndDecayFrecency() {
PlacesUtils.history
.QueryInterface(Ci.nsIObserver)
.observe(null, "idle-daily", "");
await Promise.all([onRankingChanged()]);
await Promise.all([onManyFrecenciesChanged()]);
});
function onRankingChanged() {
return PlacesTestUtils.waitForNotification(
"pages-rank-changed",
() => true,
"places"
);
function onFrecencyChanged(expectedURI) {
return new Promise(resolve => {
let obs = new NavHistoryObserver();
obs.onFrecencyChanged = (uri, newFrecency, guid, hidden, visitDate) => {
PlacesUtils.history.removeObserver(obs);
Assert.ok(!!uri);
Assert.ok(uri.equals(expectedURI));
resolve();
};
PlacesUtils.history.addObserver(obs);
});
}
function onManyFrecenciesChanged() {
return new Promise(resolve => {
let obs = new NavHistoryObserver();
obs.onManyFrecenciesChanged = () => {
PlacesUtils.history.removeObserver(obs);
Assert.ok(true);
resolve();
};
PlacesUtils.history.addObserver(obs);
});
}

Просмотреть файл

@ -593,6 +593,13 @@ var PlacesProvider = {
**/
_batchProcessingDepth: 0,
/**
* A flag that tracks whether onFrecencyChanged was notified while a batch
* operation was in progress, to tell us whether to take special action after
* the batch operation completes.
**/
_batchCalledFrecencyChanged: false,
/**
* Set this to change the maximum number of links the provider will provide.
*/
@ -607,12 +614,7 @@ var PlacesProvider = {
this.handlePlacesEvents.bind(this)
);
PlacesObservers.addListener(
[
"page-visited",
"page-title-changed",
"history-cleared",
"pages-rank-changed",
],
["page-visited", "page-title-changed", "history-cleared"],
this._placesObserver
);
},
@ -718,6 +720,10 @@ var PlacesProvider = {
onEndUpdateBatch() {
this._batchProcessingDepth -= 1;
if (this._batchProcessingDepth == 0 && this._batchCalledFrecencyChanged) {
this.onManyFrecenciesChanged();
this._batchCalledFrecencyChanged = false;
}
},
handlePlacesEvents(aEvents) {
@ -745,10 +751,6 @@ var PlacesProvider = {
this.onClearHistory();
break;
}
case "pages-rank-changed": {
this.onManyFrecenciesChanged();
break;
}
}
}
},
@ -764,7 +766,39 @@ var PlacesProvider = {
this._callObservers("onClearHistory");
},
onManyFrecenciesChanged() {
/**
* Called by the history service.
*/
onFrecencyChanged: function PlacesProvider_onFrecencyChanged(
aURI,
aNewFrecency,
aGUID,
aHidden,
aLastVisitDate
) {
// If something is doing a batch update of history entries we don't want
// to do lots of work for each record. So we just track the fact we need
// to call onManyFrecenciesChanged() once the batch is complete.
if (this._batchProcessingDepth > 0) {
this._batchCalledFrecencyChanged = true;
return;
}
// The implementation of the query in getLinks excludes hidden and
// unvisited pages, so it's important to exclude them here, too.
if (!aHidden && aLastVisitDate) {
this._callObservers("onLinkChanged", {
url: aURI.spec,
frecency: aNewFrecency,
lastVisitDate: aLastVisitDate,
type: "history",
});
}
},
/**
* Called by the history service.
*/
onManyFrecenciesChanged: function PlacesProvider_onManyFrecenciesChanged() {
this._callObservers("onManyLinksChanged");
},

Просмотреть файл

@ -431,7 +431,6 @@ module.exports = {
PlacesEvent: false,
PlacesHistoryCleared: false,
PlacesObservers: false,
PlacesRanking: false,
PlacesVisit: false,
PlacesVisitTitle: false,
PlacesWeakCallbackWrapper: false,