Bug 834539 - Replace getPageTitle with an async API. r=mak. sr=gavin.

This commit is contained in:
Asaf Romano 2013-06-18 19:24:31 +03:00
parent 01296b90c1
commit 82717d4434
12 changed files with 603 additions and 147 deletions

View File

@ -300,7 +300,7 @@ GenerateGUID(nsCString& _guid)
}
bool
IsValidGUID(const nsCString& aGUID)
IsValidGUID(const nsACString& aGUID)
{
nsCString::size_type len = aGUID.Length();
if (len != GUID_LENGTH) {

View File

@ -137,7 +137,7 @@ nsresult GenerateGUID(nsCString& _guid);
* The guid to test.
* @return true if it is a valid guid, false otherwise.
*/
bool IsValidGUID(const nsCString& aGUID);
bool IsValidGUID(const nsACString& aGUID);
/**
* Truncates the title if it's longer than TITLE_LENGTH_MAX.

View File

@ -212,6 +212,67 @@ class PlaceHashKey : public nsCStringHashKey
namespace {
/**
* Convert the given js value to a js array.
*
* @param [in] aValue
* the JS value to convert.
* @param [in] aCtx
* The JSContext for aValue.
* @param [out] _array
* the JS array.
* @param [out] _arrayLength
* _array's length.
*/
nsresult
GetJSArrayFromJSValue(const JS::Value& aValue,
JSContext* aCtx,
JSObject** _array,
uint32_t* _arrayLength) {
JS::Rooted<JSObject*> val(aCtx, aValue.toObjectOrNull());
if (JS_IsArrayObject(aCtx, val)) {
*_array = val;
(void)JS_GetArrayLength(aCtx, *_array, _arrayLength);
NS_ENSURE_ARG(*_arrayLength > 0);
}
else {
// Build a temporary array to store this one item so the code below can
// just loop.
*_arrayLength = 1;
*_array = JS_NewArrayObject(aCtx, 0, nullptr);
NS_ENSURE_TRUE(*_array, NS_ERROR_OUT_OF_MEMORY);
JSBool rc = JS_DefineElement(aCtx, *_array, 0, aValue, nullptr, nullptr, 0);
NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
}
return NS_OK;
}
/**
* Attemps to convert a given js value to a nsIURI object.
* @param aCtx
* The JSContext for aValue.
* @param aValue
* The JS value to convert.
* @return the nsIURI object, or null if aValue is not a nsIURI object.
*/
already_AddRefed<nsIURI>
GetJSValueAsURI(JSContext* aCtx,
const JS::Value& aValue) {
if (!JSVAL_IS_PRIMITIVE(aValue)) {
nsCOMPtr<nsIXPConnect> xpc = mozilla::services::GetXPConnect();
nsCOMPtr<nsIXPConnectWrappedNative> wrappedObj;
nsresult rv = xpc->GetWrappedNativeOfJSObject(aCtx, JSVAL_TO_OBJECT(aValue),
getter_AddRefs(wrappedObj));
NS_ENSURE_SUCCESS(rv, nullptr);
nsCOMPtr<nsIURI> uri = do_QueryWrappedNative(wrappedObj);
return uri.forget();
}
return nullptr;
}
/**
* Obtains an nsIURI from the "uri" property of a JSObject.
*
@ -231,18 +292,41 @@ GetURIFromJSObject(JSContext* aCtx,
JS::Rooted<JS::Value> uriVal(aCtx);
JSBool rc = JS_GetProperty(aCtx, aObject, aProperty, uriVal.address());
NS_ENSURE_TRUE(rc, nullptr);
return GetJSValueAsURI(aCtx, uriVal);
}
if (!JSVAL_IS_PRIMITIVE(uriVal)) {
nsCOMPtr<nsIXPConnect> xpc = mozilla::services::GetXPConnect();
nsCOMPtr<nsIXPConnectWrappedNative> wrappedObj;
nsresult rv = xpc->GetWrappedNativeOfJSObject(aCtx, JSVAL_TO_OBJECT(uriVal),
getter_AddRefs(wrappedObj));
NS_ENSURE_SUCCESS(rv, nullptr);
nsCOMPtr<nsIURI> uri = do_QueryWrappedNative(wrappedObj);
return uri.forget();
/**
* Attemps to convert a JS value to a string.
* @param aCtx
* The JSContext for aObject.
* @param aValue
* The JS value to convert.
* @param _string
* The string to populate with the value, or set it to void.
*/
void
GetJSValueAsString(JSContext* aCtx,
const JS::Value& aValue,
nsString& _string) {
if (JSVAL_IS_VOID(aValue) ||
!(JSVAL_IS_NULL(aValue) || JSVAL_IS_STRING(aValue))) {
_string.SetIsVoid(true);
return;
}
return nullptr;
// |null| in JS maps to the empty string.
if (JSVAL_IS_NULL(aValue)) {
_string.Truncate();
return;
}
size_t length;
const jschar* chars =
JS_GetStringCharsZAndLength(aCtx, JSVAL_TO_STRING(aValue), &length);
if (!chars) {
_string.SetIsVoid(true);
return;
}
_string.Assign(static_cast<const PRUnichar*>(chars), length);
}
/**
@ -265,24 +349,13 @@ GetStringFromJSObject(JSContext* aCtx,
{
JS::Rooted<JS::Value> val(aCtx);
JSBool rc = JS_GetProperty(aCtx, aObject, aProperty, val.address());
if (!rc || JSVAL_IS_VOID(val) ||
!(JSVAL_IS_NULL(val) || JSVAL_IS_STRING(val))) {
if (!rc) {
_string.SetIsVoid(true);
return;
}
// |null| in JS maps to the empty string.
if (JSVAL_IS_NULL(val)) {
_string.Truncate();
return;
else {
GetJSValueAsString(aCtx, val, _string);
}
size_t length;
const jschar* chars =
JS_GetStringCharsZAndLength(aCtx, JSVAL_TO_STRING(val), &length);
if (!chars) {
_string.SetIsVoid(true);
return;
}
_string.Assign(static_cast<const PRUnichar*>(chars), length);
}
/**
@ -492,8 +565,8 @@ public:
NS_IMETHOD Run()
{
NS_PRECONDITION(NS_IsMainThread(),
"This should be called on the main thread");
MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
// We are in the main thread, no need to lock.
if (mHistory->IsShuttingDown()) {
// If we are shutting down, we cannot notify the observers.
@ -563,8 +636,7 @@ public:
NS_IMETHOD Run()
{
NS_PRECONDITION(NS_IsMainThread(),
"This should be called on the main thread");
MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
NS_ENSURE_TRUE(navHistory, NS_ERROR_OUT_OF_MEMORY);
@ -581,44 +653,57 @@ private:
};
/**
* Notifies a callback object when a visit has been handled.
* Helper class for methods which notify their callers through the
* mozIVisitInfoCallback interface.
*/
class NotifyVisitInfoCallback : public nsRunnable
class NotifyPlaceInfoCallback : public nsRunnable
{
public:
NotifyVisitInfoCallback(mozIVisitInfoCallback* aCallback,
NotifyPlaceInfoCallback(mozIVisitInfoCallback* aCallback,
const VisitData& aPlace,
bool aIsSingleVisit,
nsresult aResult)
: mCallback(aCallback)
, mPlace(aPlace)
, mResult(aResult)
, mIsSingleVisit(aIsSingleVisit)
{
NS_PRECONDITION(aCallback, "Must pass a non-null callback!");
MOZ_ASSERT(aCallback, "Must pass a non-null callback!");
}
NS_IMETHOD Run()
{
NS_PRECONDITION(NS_IsMainThread(),
"This should be called on the main thread");
MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
nsCOMPtr<nsIURI> referrerURI;
if (!mPlace.referrerSpec.IsEmpty()) {
(void)NS_NewURI(getter_AddRefs(referrerURI), mPlace.referrerSpec);
}
nsCOMPtr<mozIVisitInfo> visit =
new VisitInfo(mPlace.visitId, mPlace.visitTime, mPlace.transitionType,
referrerURI.forget());
PlaceInfo::VisitsArray visits;
(void)visits.AppendElement(visit);
nsCOMPtr<nsIURI> uri;
(void)NS_NewURI(getter_AddRefs(uri), mPlace.spec);
// We do not notify about the frecency of the place.
nsCOMPtr<mozIPlaceInfo> place =
new PlaceInfo(mPlace.placeId, mPlace.guid, uri.forget(), mPlace.title,
-1, visits);
nsCOMPtr<mozIPlaceInfo> place;
if (mIsSingleVisit) {
nsCOMPtr<mozIVisitInfo> visit =
new VisitInfo(mPlace.visitId, mPlace.visitTime, mPlace.transitionType,
referrerURI.forget());
PlaceInfo::VisitsArray visits;
(void)visits.AppendElement(visit);
// The frecency isn't exposed because it may not reflect the updated value
// in the case of InsertVisitedURIs.
place =
new PlaceInfo(mPlace.placeId, mPlace.guid, uri.forget(), mPlace.title,
-1, visits);
}
else {
// Same as above.
place =
new PlaceInfo(mPlace.placeId, mPlace.guid, uri.forget(), mPlace.title,
-1);
}
if (NS_SUCCEEDED(mResult)) {
(void)mCallback->HandleResult(place);
}
@ -638,6 +723,7 @@ private:
mozIVisitInfoCallback* mCallback;
VisitData mPlace;
const nsresult mResult;
bool mIsSingleVisit;
};
/**
@ -649,7 +735,7 @@ public:
NotifyCompletion(mozIVisitInfoCallback* aCallback)
: mCallback(aCallback)
{
NS_PRECONDITION(aCallback, "Must pass a non-null callback!");
MOZ_ASSERT(aCallback, "Must pass a non-null callback!");
}
NS_IMETHOD Run()
@ -706,7 +792,7 @@ CanAddURI(nsIURI* aURI,
// We cannot add the URI. Notify the callback, if we were given one.
if (aCallback) {
// NotifyVisitInfoCallback does not hold a strong reference to the callback, so we
// NotifyPlaceInfoCallback does not hold a strong reference to the callback, so we
// have to manage it by AddRefing now and then releasing it after the event
// has run.
NS_ADDREF(aCallback);
@ -714,11 +800,11 @@ CanAddURI(nsIURI* aURI,
VisitData place(aURI);
place.guid = aGUID;
nsCOMPtr<nsIRunnable> event =
new NotifyVisitInfoCallback(aCallback, place, NS_ERROR_INVALID_ARG);
new NotifyPlaceInfoCallback(aCallback, place, true, NS_ERROR_INVALID_ARG);
(void)NS_DispatchToMainThread(event);
// Also dispatch an event to release our reference to the callback after
// NotifyVisitInfoCallback has run.
// NotifyPlaceInfoCallback has run.
nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
(void)NS_ProxyRelease(mainThread, aCallback, true);
}
@ -746,9 +832,8 @@ public:
nsTArray<VisitData>& aPlaces,
mozIVisitInfoCallback* aCallback = NULL)
{
NS_PRECONDITION(NS_IsMainThread(),
"This should be called on the main thread");
NS_PRECONDITION(aPlaces.Length() > 0, "Must pass a non-empty array!");
MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
MOZ_ASSERT(aPlaces.Length() > 0, "Must pass a non-empty array!");
nsRefPtr<InsertVisitedURIs> event =
new InsertVisitedURIs(aConnection, aPlaces, aCallback);
@ -764,8 +849,7 @@ public:
NS_IMETHOD Run()
{
NS_PRECONDITION(!NS_IsMainThread(),
"This should not be called on the main thread");
MOZ_ASSERT(!NS_IsMainThread(), "This should not be called on the main thread");
// Prevent the main thread from shutting down while this is running.
MutexAutoLock lockedScope(mHistory->GetShutdownMutex());
@ -784,15 +868,22 @@ public:
// We can avoid a database lookup if it's the same place as the last
// visit we added.
bool known = (lastPlace && lastPlace->IsSamePlaceAs(place)) ||
mHistory->FetchPageInfo(place);
bool known = lastPlace && lastPlace->IsSamePlaceAs(place);
if (!known) {
nsresult rv = mHistory->FetchPageInfo(place, &known);
if (NS_FAILED(rv)) {
nsCOMPtr<nsIRunnable> event =
new NotifyPlaceInfoCallback(mCallback, place, true, rv);
return NS_DispatchToMainThread(event);
}
}
FetchReferrerInfo(referrer, place);
nsresult rv = DoDatabaseInserts(known, place, referrer);
if (mCallback) {
nsCOMPtr<nsIRunnable> event =
new NotifyVisitInfoCallback(mCallback, place, rv);
new NotifyPlaceInfoCallback(mCallback, place, true, rv);
nsresult rv2 = NS_DispatchToMainThread(event);
NS_ENSURE_SUCCESS(rv2, rv2);
}
@ -825,8 +916,7 @@ private:
, mCallback(aCallback)
, mHistory(History::GetService())
{
NS_PRECONDITION(NS_IsMainThread(),
"This should be called on the main thread");
MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
(void)mPlaces.SwapElements(aPlaces);
(void)mReferrers.SetLength(mPlaces.Length());
@ -844,9 +934,6 @@ private:
"Passed a VisitData with a URI we cannot add to history!");
#endif
}
// We AddRef on the main thread, and release it when we are destroyed.
NS_IF_ADDREF(mCallback);
}
virtual ~InsertVisitedURIs()
@ -873,8 +960,7 @@ private:
VisitData& aPlace,
VisitData& aReferrer)
{
NS_PRECONDITION(!NS_IsMainThread(),
"This should not be called on the main thread");
MOZ_ASSERT(!NS_IsMainThread(), "This should not be called on the main thread");
// If the page was in moz_places, we need to update the entry.
nsresult rv;
@ -891,7 +977,10 @@ private:
// have a callback or when the GUID isn't known. No point in doing the
// disk I/O if we do not need it.
if (mCallback || aPlace.guid.IsEmpty()) {
bool exists = mHistory->FetchPageInfo(aPlace);
bool exists;
rv = mHistory->FetchPageInfo(aPlace, &exists);
NS_ENSURE_SUCCESS(rv, rv);
if (!exists) {
NS_NOTREACHED("should have an entry in moz_places");
}
@ -1145,12 +1234,7 @@ private:
nsTArray<VisitData> mPlaces;
nsTArray<VisitData> mReferrers;
/**
* We own a strong reference to this, but in an indirect way. We call AddRef
* in our constructor, which happens on the main thread, and proxy the relase
* of the object to the main thread in our destructor.
*/
mozIVisitInfoCallback* mCallback;
nsCOMPtr<mozIVisitInfoCallback> mCallback;
/**
* Strong reference to the History object because we do not want it to
@ -1159,6 +1243,69 @@ private:
nsRefPtr<History> mHistory;
};
class GetPlaceInfo MOZ_FINAL : public nsRunnable {
public:
/**
* Get the place info for a given place (by GUID or URI) asynchronously.
*/
static nsresult Start(mozIStorageConnection* aConnection,
VisitData& aPlace,
mozIVisitInfoCallback* aCallback) {
MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
nsRefPtr<GetPlaceInfo> event = new GetPlaceInfo(aPlace, aCallback);
// Get the target thread, and then start the work!
nsCOMPtr<nsIEventTarget> target = do_GetInterface(aConnection);
NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMETHOD Run()
{
MOZ_ASSERT(!NS_IsMainThread(), "This should not be called on the main thread");
bool exists;
nsresult rv = mHistory->FetchPageInfo(mPlace, &exists);
NS_ENSURE_SUCCESS(rv, rv);
if (!exists)
rv = NS_ERROR_NOT_AVAILABLE;
nsCOMPtr<nsIRunnable> event =
new NotifyPlaceInfoCallback(mCallback, mPlace, false, rv);
rv = NS_DispatchToMainThread(event);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
private:
GetPlaceInfo(VisitData& aPlace,
mozIVisitInfoCallback* aCallback)
: mPlace(aPlace)
, mCallback(aCallback)
, mHistory(History::GetService())
{
MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
}
virtual ~GetPlaceInfo()
{
if (mCallback) {
nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
(void)NS_ProxyRelease(mainThread, mCallback, true);
}
}
VisitData mPlace;
nsCOMPtr<mozIVisitInfoCallback> mCallback;
nsRefPtr<History> mHistory;
};
/**
* Sets the page title for a page in moz_places (if necessary).
*/
@ -1179,9 +1326,8 @@ public:
nsIURI* aURI,
const nsAString& aTitle)
{
NS_PRECONDITION(NS_IsMainThread(),
"This should be called on the main thread");
NS_PRECONDITION(aURI, "Must pass a non-null URI object!");
MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
MOZ_ASSERT(aURI, "Must pass a non-null URI object!");
nsCString spec;
nsresult rv = aURI->GetSpec(spec);
@ -1200,11 +1346,13 @@ public:
NS_IMETHOD Run()
{
NS_PRECONDITION(!NS_IsMainThread(),
"This should not be called on the main thread");
MOZ_ASSERT(!NS_IsMainThread(), "This should not be called on the main thread");
// First, see if the page exists in the database (we'll need its id later).
bool exists = mHistory->FetchPageInfo(mPlace);
bool exists;
nsresult rv = mHistory->FetchPageInfo(mPlace, &exists);
NS_ENSURE_SUCCESS(rv, rv);
if (!exists || !mPlace.titleChanged) {
// We have no record of this page, or we have no title change, so there
// is no need to do any further work.
@ -1225,8 +1373,7 @@ public:
{
mozStorageStatementScoper scoper(stmt);
nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"),
mPlace.placeId);
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), mPlace.placeId);
NS_ENSURE_SUCCESS(rv, rv);
// Empty strings should clear the title, just like
// nsNavHistory::SetPageTitle.
@ -1244,7 +1391,7 @@ public:
nsCOMPtr<nsIRunnable> event =
new NotifyTitleObservers(mPlace.spec, mPlace.title, mPlace.guid);
nsresult rv = NS_DispatchToMainThread(event);
rv = NS_DispatchToMainThread(event);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
@ -1716,9 +1863,9 @@ void
StoreAndNotifyEmbedVisit(VisitData& aPlace,
mozIVisitInfoCallback* aCallback = NULL)
{
NS_PRECONDITION(aPlace.transitionType == nsINavHistoryService::TRANSITION_EMBED,
"Must only pass TRANSITION_EMBED visits to this!");
NS_PRECONDITION(NS_IsMainThread(), "Must be called on the main thread!");
MOZ_ASSERT(aPlace.transitionType == nsINavHistoryService::TRANSITION_EMBED,
"Must only pass TRANSITION_EMBED visits to this!");
MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread!");
nsCOMPtr<nsIURI> uri;
(void)NS_NewURI(getter_AddRefs(uri), aPlace.spec);
@ -1731,16 +1878,16 @@ StoreAndNotifyEmbedVisit(VisitData& aPlace,
navHistory->registerEmbedVisit(uri, aPlace.visitTime);
if (aCallback) {
// NotifyVisitInfoCallback does not hold a strong reference to the callback,
// NotifyPlaceInfoCallback does not hold a strong reference to the callback,
// so we have to manage it by AddRefing now and then releasing it after the
// event has run.
NS_ADDREF(aCallback);
nsCOMPtr<nsIRunnable> event =
new NotifyVisitInfoCallback(aCallback, aPlace, NS_OK);
new NotifyPlaceInfoCallback(aCallback, aPlace, true, NS_OK);
(void)NS_DispatchToMainThread(event);
// Also dispatch an event to release our reference to the callback after
// NotifyVisitInfoCallback has run.
// NotifyPlaceInfoCallback has run.
nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
(void)NS_ProxyRelease(mainThread, aCallback, true);
}
@ -1972,37 +2119,65 @@ History::UpdatePlace(const VisitData& aPlace)
return NS_OK;
}
bool
History::FetchPageInfo(VisitData& _place)
nsresult
History::FetchPageInfo(VisitData& _place, bool* _exists)
{
NS_PRECONDITION(!_place.spec.IsEmpty(), "must have a non-empty spec!");
NS_PRECONDITION(!_place.spec.IsEmpty() || !_place.guid.IsEmpty(), "must have either a non-empty spec or guid!");
NS_PRECONDITION(!NS_IsMainThread(), "must be called off of the main thread!");
nsCOMPtr<mozIStorageStatement> stmt = GetStatement(
"SELECT id, title, hidden, typed, guid "
nsresult rv;
// URI takes precedence.
nsCOMPtr<mozIStorageStatement> stmt;
bool selectByURI = !_place.spec.IsEmpty();
if (selectByURI) {
stmt = GetStatement(
"SELECT guid, id, title, hidden, typed, frecency "
"FROM moz_places "
"WHERE url = :page_url "
);
NS_ENSURE_TRUE(stmt, false);
mozStorageStatementScoper scoper(stmt);
nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"),
_place.spec);
NS_ENSURE_SUCCESS(rv, false);
bool hasResult;
rv = stmt->ExecuteStep(&hasResult);
NS_ENSURE_SUCCESS(rv, false);
if (!hasResult) {
return false;
rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), _place.spec);
NS_ENSURE_SUCCESS(rv, rv);
}
else {
stmt = GetStatement(
"SELECT url, id, title, hidden, typed, frecency "
"FROM moz_places "
"WHERE guid = :guid "
);
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), _place.guid);
NS_ENSURE_SUCCESS(rv, rv);
}
rv = stmt->GetInt64(0, &_place.placeId);
NS_ENSURE_SUCCESS(rv, false);
NS_ENSURE_TRUE(stmt, rv);
mozStorageStatementScoper scoper(stmt);
rv = stmt->ExecuteStep(_exists);
NS_ENSURE_SUCCESS(rv, rv);
if (!*_exists) {
return NS_OK;
}
if (selectByURI) {
if (_place.guid.IsEmpty()) {
rv = stmt->GetUTF8String(0, _place.guid);
NS_ENSURE_SUCCESS(rv, rv);
}
}
else {
nsAutoCString spec;
rv = stmt->GetUTF8String(0, spec);
NS_ENSURE_SUCCESS(rv, rv);
_place.spec = spec;
}
rv = stmt->GetInt64(1, &_place.placeId);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoString title;
rv = stmt->GetString(1, title);
NS_ENSURE_SUCCESS(rv, true);
rv = stmt->GetString(2, title);
NS_ENSURE_SUCCESS(rv, rv);
// If the title we were given was void, that means we did not bother to set
// it to anything. As a result, ignore the fact that we may have changed the
@ -2022,26 +2197,23 @@ History::FetchPageInfo(VisitData& _place)
// Any one visible transition makes this location visible. If database
// has location as visible, reflect that in our data structure.
int32_t hidden;
rv = stmt->GetInt32(2, &hidden);
rv = stmt->GetInt32(3, &hidden);
NS_ENSURE_SUCCESS(rv, rv);
_place.hidden = !!hidden;
NS_ENSURE_SUCCESS(rv, true);
}
if (!_place.typed) {
// If this transition wasn't typed, others might have been. If database
// has location as typed, reflect that in our data structure.
int32_t typed;
rv = stmt->GetInt32(3, &typed);
rv = stmt->GetInt32(4, &typed);
NS_ENSURE_SUCCESS(rv, rv);
_place.typed = !!typed;
NS_ENSURE_SUCCESS(rv, true);
}
if (_place.guid.IsVoid()) {
rv = stmt->GetUTF8String(4, _place.guid);
NS_ENSURE_SUCCESS(rv, true);
}
return true;
rv = stmt->GetInt32(5, &_place.frecency);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
/* static */ size_t
@ -2500,6 +2672,74 @@ History::RemoveAllDownloads()
////////////////////////////////////////////////////////////////////////////////
//// mozIAsyncHistory
NS_IMETHODIMP
History::GetPlacesInfo(const JS::Value& aPlaceIdentifiers,
mozIVisitInfoCallback* aCallback,
JSContext* aCtx) {
nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
NS_ABORT_IF_FALSE(navHistory, "Could not get nsNavHistory?!");
uint32_t placesIndentifiersLength;
JS::Rooted<JSObject*> placesIndentifiers(aCtx);
nsresult rv = GetJSArrayFromJSValue(aPlaceIdentifiers, aCtx,
placesIndentifiers.address(),
&placesIndentifiersLength);
NS_ENSURE_SUCCESS(rv, rv);
nsTArray<VisitData> placesInfo;
placesInfo.SetCapacity(placesIndentifiersLength);
for (uint32_t i = 0; i < placesIndentifiersLength; i++) {
JS::Value placeIdentifier;
JSBool rc = JS_GetElement(aCtx, placesIndentifiers, i, &placeIdentifier);
NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
// GUID
nsAutoString fatGUID;
GetJSValueAsString(aCtx, placeIdentifier, fatGUID);
if (!fatGUID.IsVoid()) {
NS_ConvertUTF16toUTF8 guid(fatGUID);
if (!IsValidGUID(guid))
return NS_ERROR_INVALID_ARG;
VisitData& placeInfo = *placesInfo.AppendElement(VisitData());
placeInfo.guid = guid;
}
else {
nsCOMPtr<nsIURI> uri = GetJSValueAsURI(aCtx, placeIdentifier);
if (!uri)
return NS_ERROR_INVALID_ARG; // neither a guid, nor a uri.
*placesInfo.AppendElement(VisitData(uri));
}
}
mozIStorageConnection* dbConn = GetDBConn();
NS_ENSURE_STATE(dbConn);
for (nsTArray<VisitData>::size_type i = 0; i < placesInfo.Length(); i++) {
nsresult rv = GetPlaceInfo::Start(dbConn, placesInfo.ElementAt(i), aCallback);
NS_ENSURE_SUCCESS(rv, rv);
}
// Be sure to notify that all of our operations are complete. This
// is dispatched to the background thread first and redirected to the
// main thread from there to make sure that all database notifications
// and all embed or canAddURI notifications have finished.
if (aCallback) {
// NotifyCompletion does not hold a strong reference to the callback,
// so we have to manage it by AddRefing now. NotifyCompletion will
// release it for us once it has dispatched the callback to the main
// thread.
NS_ADDREF(aCallback);
nsCOMPtr<nsIEventTarget> backgroundThread = do_GetInterface(dbConn);
NS_ENSURE_TRUE(backgroundThread, NS_ERROR_UNEXPECTED);
nsCOMPtr<nsIRunnable> event = new NotifyCompletion(aCallback);
return backgroundThread->Dispatch(event, NS_DISPATCH_NORMAL);
}
return NS_OK;
}
NS_IMETHODIMP
History::UpdatePlaces(const JS::Value& aPlaceInfos,
mozIVisitInfoCallback* aCallback,
@ -2508,22 +2748,10 @@ History::UpdatePlaces(const JS::Value& aPlaceInfos,
NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_UNEXPECTED);
NS_ENSURE_TRUE(!JSVAL_IS_PRIMITIVE(aPlaceInfos), NS_ERROR_INVALID_ARG);
uint32_t infosLength = 1;
uint32_t infosLength;
JS::Rooted<JSObject*> infos(aCtx);
if (JS_IsArrayObject(aCtx, aPlaceInfos.toObjectOrNull())) {
infos = aPlaceInfos.toObjectOrNull();
(void)JS_GetArrayLength(aCtx, infos, &infosLength);
NS_ENSURE_ARG(infosLength > 0);
}
else {
// Build a temporary array to store this one item so the code below can
// just loop.
infos = JS_NewArrayObject(aCtx, 0, NULL);
NS_ENSURE_TRUE(infos, NS_ERROR_OUT_OF_MEMORY);
JSBool rc = JS_DefineElement(aCtx, infos, 0, aPlaceInfos, NULL, NULL, 0);
NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
}
nsresult rv = GetJSArrayFromJSValue(aPlaceInfos, aCtx, infos.address(), &infosLength);
NS_ENSURE_SUCCESS(rv, rv);
nsTArray<VisitData> visitData;
for (uint32_t i = 0; i < infosLength; i++) {
@ -2643,7 +2871,7 @@ History::UpdatePlaces(const JS::Value& aPlaceInfos,
nsCOMPtr<nsIEventTarget> backgroundThread = do_GetInterface(dbConn);
NS_ENSURE_TRUE(backgroundThread, NS_ERROR_UNEXPECTED);
nsCOMPtr<nsIRunnable> event = new NotifyCompletion(aCallback);
(void)backgroundThread->Dispatch(event, NS_DISPATCH_NORMAL);
return backgroundThread->Dispatch(event, NS_DISPATCH_NORMAL);
}
return NS_OK;

View File

@ -73,9 +73,10 @@ public:
*
* @param _place
* The VisitData for the place we need to know information about.
* @return true if the page was recorded in moz_places, false otherwise.
* @param [out] _exists
* Whether or the page was recorded in moz_places, false otherwise.
*/
bool FetchPageInfo(VisitData& _place);
nsresult FetchPageInfo(VisitData& _place, bool* _exists);
/**
* Get the number of bytes of memory this History object is using,

View File

@ -15,6 +15,21 @@ namespace places {
////////////////////////////////////////////////////////////////////////////////
//// PlaceInfo
PlaceInfo::PlaceInfo(int64_t aId,
const nsCString& aGUID,
already_AddRefed<nsIURI> aURI,
const nsString& aTitle,
int64_t aFrecency)
: mId(aId)
, mGUID(aGUID)
, mURI(aURI)
, mTitle(aTitle)
, mFrecency(aFrecency)
, mVisitsAvailable(false)
{
NS_PRECONDITION(mURI, "Must provide a non-null uri!");
}
PlaceInfo::PlaceInfo(int64_t aId,
const nsCString& aGUID,
already_AddRefed<nsIURI> aURI,
@ -27,6 +42,7 @@ PlaceInfo::PlaceInfo(int64_t aId,
, mTitle(aTitle)
, mFrecency(aFrecency)
, mVisits(aVisits)
, mVisitsAvailable(true)
{
NS_PRECONDITION(mURI, "Must provide a non-null uri!");
}
@ -73,6 +89,14 @@ NS_IMETHODIMP
PlaceInfo::GetVisits(JSContext* aContext,
JS::Value* _visits)
{
// If the visits data was not provided, return null rather
// than an empty array to distinguish this case from the case
// of a place without any visit.
if (!mVisitsAvailable) {
*_visits = JSVAL_NULL;
return NS_OK;
}
// TODO bug 625913 when we use this in situations that have more than one
// visit here, we will likely want to make this cache the value.
JS::Rooted<JSObject*> visits(aContext, JS_NewArrayObject(aContext, 0, NULL));

View File

@ -26,6 +26,8 @@ public:
typedef nsTArray< nsCOMPtr<mozIVisitInfo> > VisitsArray;
PlaceInfo(int64_t aId, const nsCString& aGUID, already_AddRefed<nsIURI> aURI,
const nsString& aTitle, int64_t aFrecency);
PlaceInfo(int64_t aId, const nsCString& aGUID, already_AddRefed<nsIURI> aURI,
const nsString& aTitle, int64_t aFrecency,
const VisitsArray& aVisits);
@ -37,6 +39,7 @@ private:
const nsString mTitle;
const int64_t mFrecency;
const VisitsArray mVisits;
bool mVisitsAvailable;
};
} // namespace places

View File

@ -1754,6 +1754,56 @@ this.PlacesUtils = {
deferred.resolve(charset);
}, Ci.nsIThread.DISPATCH_NORMAL);
return deferred.promise;
},
/**
* Promised wrapper for mozIAsyncHistory::updatePlaces for a single place.
*
* @param aPlaces
* a single mozIPlaceInfo object
* @resolves {Promise}
*/
promiseUpdatePlace: function PU_promiseUpdatePlaces(aPlace) {
let deferred = Promise.defer();
PlacesUtils.asyncHistory.updatePlaces(aPlace, {
_placeInfo: null,
handleResult: function handleResult(aPlaceInfo) {
this._placeInfo = aPlaceInfo;
},
handleError: function handleError(aResultCode, aPlaceInfo) {
deferred.reject(new Components.Exception("Error", aResultCode));
},
handleCompletion: function() {
deferred.resolve(this._placeInfo);
}
});
return deferred.promise;
},
/**
* Promised wrapper for mozIAsyncHistory::getPlacesInfo for a single place.
*
* @param aPlaceIdentifier
* either an nsIURI or a GUID (@see getPlacesInfo)
* @resolves to the place info object handed to handleResult.
*/
promisePlaceInfo: function PU_promisePlaceInfo(aPlaceIdentifier) {
let deferred = Promise.defer();
PlacesUtils.asyncHistory.getPlacesInfo(aPlaceIdentifier, {
_placeInfo: null,
handleResult: function handleResult(aPlaceInfo) {
this._placeInfo = aPlaceInfo;
},
handleError: function handleError(aResultCode, aPlaceInfo) {
deferred.reject(new Components.Exception("Error", aResultCode));
},
handleCompletion: function() {
deferred.resolve(this._placeInfo);
}
});
return deferred.promise;
}
};

View File

@ -73,34 +73,34 @@ interface mozIPlaceInfo : nsISupports
readonly attribute jsval visits;
};
/**
* Shared Callback interface for mozIAsyncHistory methods. The semantics
* for each method are detailed in mozIAsyncHistory.
*/
[scriptable, uuid(1f266877-2859-418b-a11b-ec3ae4f4f93d)]
interface mozIVisitInfoCallback : nsISupports
{
/**
* Called when the given mozIPlaceInfo object could not be processed.
* Called when the given place could not be processed.
*
* @param aResultCode
* nsresult indicating the failure reason.
* @param aPlaceInfo
* The information that was being entered into the database.
* The information that was given to the caller for the place.
*/
void handleError(in nsresult aResultCode,
in mozIPlaceInfo aPlaceInfo);
/**
* Called for each visit added, title change, or guid change when passed to
* mozIAsyncHistory::updatePlaces. If more than one operation is done for
* a given visit, only one callback will be given (i.e. title change and
* add visit).
* Called for each place processed successfully.
*
* @param aPlaceInfo
* The information that was being entered into the database.
* The current info stored for the place.
*/
void handleResult(in mozIPlaceInfo aPlaceInfo);
/**
* Called when the mozIAsyncHistory::updatePlaces has finished processing
* all mozIPlaceInfo records.
* Called when all records were processed.
*/
void handleCompletion();
@ -121,13 +121,43 @@ interface mozIVisitedStatusCallback : nsISupports
in boolean aVisitedStatus);
};
[scriptable, uuid(b7edc16e-9f3c-4bf5-981b-4e8000b02d89)]
[scriptable, uuid(1643EFD2-A329-4733-A39D-17069C8D3B2D)]
interface mozIAsyncHistory : nsISupports
{
/**
* Gets the available information for the given array of places, each
* identified by either nsIURI or places GUID (string).
*
* The retrieved places info objects DO NOT include the visits data (the
* |visits| attribute is set to null).
*
* If a given place does not exist in the database, aCallback.handleError is
* called for it with NS_ERROR_NOT_AVAILABLE result code.
*
* @param aPlaceIdentifiers
* The place[s] for which to retrieve information, identified by either
* a single place GUID, a single URI, or a JS array of URIs and/or GUIDs.
* @param aCallback
* A mozIVisitInfoCallback object which consists of callbacks to be
* notified for successful or failed retrievals.
* If there's no information available for a given place, aCallback
* is called with a stub place info object, containing just the provided
* data (GUID or URI).
*
* @throws NS_ERROR_INVALID_ARG
* - Passing in NULL for aPlaceIdentifiers or aCallback.
* - Not providing at least one valid GUID or URI.
*/
[implicit_jscontext]
void getPlacesInfo(in jsval aPlaceIdentifiers,
in mozIVisitInfoCallback aCallback);
/**
* Adds a set of visits for one or more mozIPlaceInfo objects, and updates
* each mozIPlaceInfo's title or guid.
*
* aCallback.handleResult is called for each visit added.
*
* @param aPlaceInfo
* The mozIPlaceInfo object[s] containing the information to store or
* update. This can be a single object, or an array of objects.

View File

@ -1241,6 +1241,7 @@ interface nsINavHistoryService : nsISupports
/**
* Gets the original title of the page.
* @deprecated use mozIAsyncHistory.getPlacesInfo instead.
*/
AString getPageTitle(in nsIURI aURI);

View File

@ -2828,6 +2828,8 @@ nsNavHistory::GetCharsetForURI(nsIURI* aURI,
NS_IMETHODIMP
nsNavHistory::GetPageTitle(nsIURI* aURI, nsAString& aTitle)
{
PLACES_WARN_DEPRECATED();
NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
NS_ENSURE_ARG(aURI);

View File

@ -0,0 +1,116 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
function promiseGetPlacesInfo(aPlacesIdentifiers) {
let deferred = Promise.defer();
PlacesUtils.asyncHistory.getPlacesInfo(aPlacesIdentifiers, {
_results: [],
_errors: [],
handleResult: function handleResult(aPlaceInfo) {
this._results.push(aPlaceInfo);
},
handleError: function handleError(aResultCode, aPlaceInfo) {
this._errors.push({ resultCode: aResultCode, info: aPlaceInfo });
},
handleCompletion: function handleCompletion() {
deferred.resolve({ errors: this._errors, results: this._results });
}
});
return deferred.promise;
}
function ensurePlacesInfoObjectsAreEqual(a, b) {
do_check_true(a.uri.equals(b.uri));
do_check_eq(a.title, b.title);
do_check_eq(a.guid, b.guid);
do_check_eq(a.placeId, b.placeId);
}
function test_getPlacesInfoExistentPlace() {
let testURI = NetUtil.newURI("http://www.example.tld");
yield promiseAddVisits(testURI);
let getPlacesInfoResult = yield promiseGetPlacesInfo([testURI]);
do_check_eq(getPlacesInfoResult.results.length, 1);
do_check_eq(getPlacesInfoResult.errors.length, 0);
let placeInfo = getPlacesInfoResult.results[0];
do_check_true(placeInfo instanceof Ci.mozIPlaceInfo);
do_check_true(placeInfo.uri.equals(testURI));
do_check_eq(placeInfo.title, "test visit for " + testURI.spec);
do_check_true(placeInfo.guid.length > 0);
do_check_eq(placeInfo.visits, null);
}
add_task(test_getPlacesInfoExistentPlace);
function test_getPlacesInfoNonExistentPlace() {
let testURI = NetUtil.newURI("http://www.example_non_existent.tld");
let getPlacesInfoResult = yield promiseGetPlacesInfo(testURI);
do_check_eq(getPlacesInfoResult.results.length, 0);
do_check_eq(getPlacesInfoResult.errors.length, 1);
}
add_task(test_getPlacesInfoNonExistentPlace);
function test_promisedHelper() {
let (uri = NetUtil.newURI("http://www.helper_existent_example.tld")) {
yield promiseAddVisits(uri);
let placeInfo = yield PlacesUtils.promisePlaceInfo(uri);
do_check_true(placeInfo instanceof Ci.mozIPlaceInfo);
};
let (uri = NetUtil.newURI("http://www.helper_non_existent_example.tld")) {
try {
let placeInfo = yield PlacesUtils.promisePlaceInfo(uri);
do_throw("PlacesUtils.promisePlaceInfo should have rejected the promise");
}
catch(ex) { }
};
}
add_task(test_promisedHelper);
function test_infoByGUID() {
let testURI = NetUtil.newURI("http://www.guid_example.tld");
yield promiseAddVisits(testURI);
let placeInfoByURI = yield PlacesUtils.promisePlaceInfo(testURI);
let placeInfoByGUID = yield PlacesUtils.promisePlaceInfo(placeInfoByURI.guid);
ensurePlacesInfoObjectsAreEqual(placeInfoByURI, placeInfoByGUID);
}
add_task(test_infoByGUID);
function test_invalid_guid() {
try {
let placeInfoByGUID = yield PlacesUtils.promisePlaceInfo("###");
do_throw("getPlacesInfo should fail for invalid guids")
}
catch(ex) { }
}
add_task(test_invalid_guid);
function test_mixed_selection() {
let placeInfo1, placeInfo2;
let (uri = NetUtil.newURI("http://www.mixed_selection_test_1.tld")) {
yield promiseAddVisits(uri);
placeInfo1 = yield PlacesUtils.promisePlaceInfo(uri);
};
let (uri = NetUtil.newURI("http://www.mixed_selection_test_2.tld")) {
yield promiseAddVisits(uri);
placeInfo2 = yield PlacesUtils.promisePlaceInfo(uri);
};
let getPlacesInfoResult = yield promiseGetPlacesInfo([placeInfo1.uri, placeInfo2.guid]);
do_check_eq(getPlacesInfoResult.results.length, 2);
do_check_eq(getPlacesInfoResult.errors.length, 0);
do_check_eq(getPlacesInfoResult.results[0].uri.spec, placeInfo1.uri.spec);
do_check_eq(getPlacesInfoResult.results[1].guid, placeInfo2.guid);
}
add_task(test_mixed_selection);
function run_test() {
run_next_test();
}

View File

@ -123,3 +123,4 @@ skip-if = os == "android"
[test_PlacesUtils_lazyobservers.js]
[test_placesTxn.js]
[test_telemetry.js]
[test_getPlacesInfo.js]