зеркало из https://github.com/mozilla/pjs.git
Bug 606966 - Need an async history visit API exposed to JS
Part 21 - Track and notify about title changes. r=mak a=blocking
This commit is contained in:
Родитель
423633f81f
Коммит
cd82bca5d7
|
@ -91,6 +91,7 @@ struct VisitData {
|
|||
, typed(false)
|
||||
, transitionType(PR_UINT32_MAX)
|
||||
, visitTime(0)
|
||||
, titleChanged(false)
|
||||
{
|
||||
guid.SetIsVoid(PR_TRUE);
|
||||
title.SetIsVoid(PR_TRUE);
|
||||
|
@ -105,6 +106,7 @@ struct VisitData {
|
|||
, typed(false)
|
||||
, transitionType(PR_UINT32_MAX)
|
||||
, visitTime(0)
|
||||
, titleChanged(false)
|
||||
{
|
||||
(void)aURI->GetSpec(spec);
|
||||
(void)GetReversedHostname(aURI, revHost);
|
||||
|
@ -165,6 +167,9 @@ struct VisitData {
|
|||
PRTime visitTime;
|
||||
nsString title;
|
||||
nsCString referrerSpec;
|
||||
|
||||
// TODO bug 626836 hook up hidden and typed change tracking too!
|
||||
bool titleChanged;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -225,10 +230,16 @@ GetStringFromJSObject(JSContext* aCtx,
|
|||
{
|
||||
jsval val;
|
||||
JSBool rc = JS_GetProperty(aCtx, aObject, aProperty, &val);
|
||||
if (!rc || JSVAL_IS_VOID(val) || !JSVAL_IS_STRING(val)) {
|
||||
if (!rc || JSVAL_IS_VOID(val) ||
|
||||
!(JSVAL_IS_NULL(val) || JSVAL_IS_STRING(val))) {
|
||||
_string.SetIsVoid(PR_TRUE);
|
||||
return;
|
||||
}
|
||||
// |null| in JS maps to the empty string.
|
||||
if (JSVAL_IS_NULL(val)) {
|
||||
_string.Truncate();
|
||||
return;
|
||||
}
|
||||
size_t length;
|
||||
const jschar* chars =
|
||||
JS_GetStringCharsZAndLength(aCtx, JSVAL_TO_STRING(val), &length);
|
||||
|
@ -468,6 +479,45 @@ private:
|
|||
VisitData mReferrer;
|
||||
};
|
||||
|
||||
/**
|
||||
* Notifies observers about a pages title changing.
|
||||
*/
|
||||
class NotifyTitleObservers : public nsRunnable
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Notifies observers on the main thread.
|
||||
*
|
||||
* @param aSpec
|
||||
* The spec of the URI to notify about.
|
||||
* @param aTitle
|
||||
* The new title to notify about.
|
||||
*/
|
||||
NotifyTitleObservers(const nsCString& aSpec,
|
||||
const nsString& aTitle)
|
||||
: mSpec(aSpec)
|
||||
, mTitle(aTitle)
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMETHOD Run()
|
||||
{
|
||||
NS_PRECONDITION(NS_IsMainThread(),
|
||||
"This should be called on the main thread");
|
||||
|
||||
nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
|
||||
NS_ENSURE_TRUE(navHistory, NS_ERROR_OUT_OF_MEMORY);
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
(void)NS_NewURI(getter_AddRefs(uri), mSpec);
|
||||
navHistory->NotifyTitleChange(uri, mTitle);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
private:
|
||||
const nsCString mSpec;
|
||||
const nsString mTitle;
|
||||
};
|
||||
|
||||
/**
|
||||
* Notifies a callback object about completion.
|
||||
*/
|
||||
|
@ -644,6 +694,13 @@ public:
|
|||
rv = NS_DispatchToMainThread(event);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Notify about title change if needed.
|
||||
if ((!known && !place.title.IsVoid()) || place.titleChanged) {
|
||||
event = new NotifyTitleObservers(place.spec, place.title);
|
||||
rv = NS_DispatchToMainThread(event);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
lastPlace = &mPlaces.ElementAt(i);
|
||||
}
|
||||
|
||||
|
@ -1009,48 +1066,6 @@ private:
|
|||
nsRefPtr<History> mHistory;
|
||||
};
|
||||
|
||||
/**
|
||||
* Notifies observers about a pages title changing.
|
||||
*/
|
||||
class NotifyTitleObservers : public nsRunnable
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Notifies observers on the main thread.
|
||||
*
|
||||
* @param aSpec
|
||||
* The spec of the URI to notify about.
|
||||
* @param aTitle
|
||||
* The new title to notify about.
|
||||
*/
|
||||
NotifyTitleObservers(const nsCString& aSpec,
|
||||
const nsString& aTitle)
|
||||
: mSpec(aSpec)
|
||||
, mTitle(aTitle)
|
||||
{
|
||||
NS_PRECONDITION(!NS_IsMainThread(),
|
||||
"This should not be called on the main thread");
|
||||
}
|
||||
|
||||
NS_IMETHOD Run()
|
||||
{
|
||||
NS_PRECONDITION(NS_IsMainThread(),
|
||||
"This should be called on the main thread");
|
||||
|
||||
nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
|
||||
NS_ENSURE_TRUE(navHistory, NS_ERROR_OUT_OF_MEMORY);
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
(void)NS_NewURI(getter_AddRefs(uri), mSpec);
|
||||
navHistory->NotifyTitleChange(uri, mTitle);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
private:
|
||||
const nsCString mSpec;
|
||||
const nsString mTitle;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the page title for a page in moz_places (if necessary).
|
||||
*/
|
||||
|
@ -1069,7 +1084,7 @@ public:
|
|||
*/
|
||||
static nsresult Start(mozIStorageConnection* aConnection,
|
||||
nsIURI* aURI,
|
||||
const nsString& aTitle)
|
||||
const nsAString& aTitle)
|
||||
{
|
||||
NS_PRECONDITION(NS_IsMainThread(),
|
||||
"This should be called on the main thread");
|
||||
|
@ -1097,22 +1112,15 @@ public:
|
|||
|
||||
// First, see if the page exists in the database (we'll need its id later).
|
||||
bool exists = mHistory->FetchPageInfo(mPlace);
|
||||
if (!exists) {
|
||||
// We have no record of this page, so there is no need to do any further
|
||||
// work.
|
||||
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.
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_ASSERTION(mPlace.placeId > 0,
|
||||
"We somehow have an invalid place id here!");
|
||||
|
||||
// Also, if we have the same title, there is no reason to do another write
|
||||
// or notify our observers, so bail early.
|
||||
if (mTitle.Equals(mPlace.title) ||
|
||||
(mTitle.IsVoid() && mPlace.title.IsVoid())) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Now we can update our database record.
|
||||
nsCOMPtr<mozIStorageStatement> stmt =
|
||||
mHistory->syncStatements.GetCachedStatement(
|
||||
|
@ -1127,19 +1135,22 @@ public:
|
|||
nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"),
|
||||
mPlace.placeId);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (mTitle.IsVoid()) {
|
||||
// Empty strings should clear the title, just like
|
||||
// nsNavHistory::SetPageTitle.
|
||||
if (mPlace.title.IsEmpty()) {
|
||||
rv = stmt->BindNullByName(NS_LITERAL_CSTRING("page_title"));
|
||||
}
|
||||
else {
|
||||
rv = stmt->BindStringByName(NS_LITERAL_CSTRING("page_title"),
|
||||
StringHead(mTitle, TITLE_LENGTH_MAX));
|
||||
StringHead(mPlace.title, TITLE_LENGTH_MAX));
|
||||
}
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = stmt->Execute();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIRunnable> event = new NotifyTitleObservers(mPlace.spec, mTitle);
|
||||
nsCOMPtr<nsIRunnable> event =
|
||||
new NotifyTitleObservers(mPlace.spec, mPlace.title);
|
||||
nsresult rv = NS_DispatchToMainThread(event);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
|
@ -1148,15 +1159,14 @@ public:
|
|||
|
||||
private:
|
||||
SetPageTitle(const nsCString& aSpec,
|
||||
const nsString& aTitle)
|
||||
: mTitle(aTitle)
|
||||
, mHistory(History::GetService())
|
||||
const nsAString& aTitle)
|
||||
: mHistory(History::GetService())
|
||||
{
|
||||
mPlace.spec = aSpec;
|
||||
mPlace.title = aTitle;
|
||||
}
|
||||
|
||||
VisitData mPlace;
|
||||
const nsString mTitle;
|
||||
|
||||
/**
|
||||
* Strong reference to the History object because we do not want it to
|
||||
|
@ -1331,7 +1341,8 @@ 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);
|
||||
if (aPlace.title.IsVoid()) {
|
||||
// Empty strings should have no title, just like nsNavHistory::SetPageTitle.
|
||||
if (aPlace.title.IsEmpty()) {
|
||||
rv = stmt->BindNullByName(NS_LITERAL_CSTRING("title"));
|
||||
}
|
||||
else {
|
||||
|
@ -1375,11 +1386,15 @@ History::UpdatePlace(const VisitData& aPlace)
|
|||
mozStorageStatementScoper scoper(stmt);
|
||||
|
||||
nsresult rv;
|
||||
if (!aPlace.title.IsVoid()) {
|
||||
// Empty strings should clear the title, just like nsNavHistory::SetPageTitle.
|
||||
if (aPlace.title.IsEmpty()) {
|
||||
rv = stmt->BindNullByName(NS_LITERAL_CSTRING("title"));
|
||||
}
|
||||
else {
|
||||
rv = stmt->BindStringByName(NS_LITERAL_CSTRING("title"),
|
||||
StringHead(aPlace.title, TITLE_LENGTH_MAX));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("typed"), aPlace.typed);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("hidden"), aPlace.hidden);
|
||||
|
@ -1423,9 +1438,16 @@ History::FetchPageInfo(VisitData& _place)
|
|||
rv = stmt->GetInt64(0, &_place.placeId);
|
||||
NS_ENSURE_SUCCESS(rv, false);
|
||||
|
||||
nsAutoString title;
|
||||
rv = stmt->GetString(1, title);
|
||||
NS_ENSURE_SUCCESS(rv, true);
|
||||
|
||||
// We track if we change the title, but will add the current title to _place
|
||||
// if we do not have one set.
|
||||
_place.titleChanged = !(_place.title.Equals(title) ||
|
||||
(_place.title.IsVoid() && title.IsVoid()));
|
||||
if (_place.title.IsVoid()) {
|
||||
rv = stmt->GetString(1, _place.title);
|
||||
NS_ENSURE_SUCCESS(rv, true);
|
||||
_place.title = title;
|
||||
}
|
||||
|
||||
if (_place.hidden) {
|
||||
|
@ -1776,18 +1798,10 @@ History::SetURITitle(nsIURI* aURI, const nsAString& aTitle)
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
nsAutoString title;
|
||||
if (aTitle.IsEmpty()) {
|
||||
title.SetIsVoid(PR_TRUE);
|
||||
}
|
||||
else {
|
||||
title.Assign(aTitle);
|
||||
}
|
||||
|
||||
mozIStorageConnection* dbConn = GetDBConn();
|
||||
NS_ENSURE_STATE(dbConn);
|
||||
|
||||
rv = SetPageTitle::Start(dbConn, aURI, title);
|
||||
rv = SetPageTitle::Start(dbConn, aURI, aTitle);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return NS_OK;
|
||||
|
|
|
@ -39,6 +39,62 @@ function VisitInfo(aTransitionType,
|
|||
this.visitDate = aVisitTime || Date.now() * 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic nsINavHistoryObserver that doesn't implement anything, but provides
|
||||
* dummy methods to prevent errors about an object not having a certain method.
|
||||
*/
|
||||
function NavHistoryObserver()
|
||||
{
|
||||
}
|
||||
NavHistoryObserver.prototype =
|
||||
{
|
||||
onBeginUpdateBatch: function() { },
|
||||
onEndUpdateBatch: function() { },
|
||||
onVisit: function() { },
|
||||
onTitleChanged: function() { },
|
||||
onBeforeDeleteURI: function() { },
|
||||
onDeleteURI: function() { },
|
||||
onClearHistory: function() { },
|
||||
onPageChanged: function() { },
|
||||
onDeleteVisits: function() { },
|
||||
QueryInterface: XPCOMUtils.generateQI([
|
||||
Ci.nsINavHistoryObserver,
|
||||
]),
|
||||
};
|
||||
|
||||
/**
|
||||
* Listens for a title change notification, and calls aCallback when it gets it.
|
||||
*
|
||||
* @param aURI
|
||||
* The URI of the page we expect a notification for.
|
||||
* @param aExpectedTitle
|
||||
* The expected title of the URI we expect a notification for.
|
||||
* @param aCallback
|
||||
* The method to call when we have gotten the proper notification about
|
||||
* the title changing.
|
||||
*/
|
||||
function TitleChangedObserver(aURI,
|
||||
aExpectedTitle,
|
||||
aCallback)
|
||||
{
|
||||
this.uri = aURI;
|
||||
this.expectedTitle = aExpectedTitle;
|
||||
this.callback = aCallback;
|
||||
}
|
||||
TitleChangedObserver.prototype = {
|
||||
__proto__: NavHistoryObserver.prototype,
|
||||
onTitleChanged: function(aURI,
|
||||
aTitle)
|
||||
{
|
||||
do_log_info("onTitleChanged(" + aURI.spec + ", " + aTitle + ")");
|
||||
if (!this.uri.equals(aURI)) {
|
||||
return;
|
||||
}
|
||||
do_check_eq(aTitle, this.expectedTitle);
|
||||
this.callback();
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Tests that a title was set properly in the database.
|
||||
*
|
||||
|
@ -720,6 +776,7 @@ function test_title_change_saved()
|
|||
// First, add a visit for it.
|
||||
let place = {
|
||||
uri: NetUtil.newURI(TEST_DOMAIN + "test_title_change_saved"),
|
||||
title: "original title",
|
||||
visits: [
|
||||
new VisitInfo(),
|
||||
],
|
||||
|
@ -729,18 +786,107 @@ function test_title_change_saved()
|
|||
gHistory.updatePlaces(place, function(aResultCode, aPlaceInfo) {
|
||||
do_check_true(Components.isSuccessCode(aResultCode));
|
||||
|
||||
// Then, change the title with visits.
|
||||
place.title = "title change";
|
||||
// Now, make sure the empty string clears the title.
|
||||
place.title = "";
|
||||
place.visits = [new VisitInfo()];
|
||||
gHistory.updatePlaces(place, function(aResultCode, aPlaceInfo) {
|
||||
do_check_true(Components.isSuccessCode(aResultCode));
|
||||
do_check_title_for_uri(place.uri, place.title);
|
||||
do_check_title_for_uri(place.uri, null);
|
||||
|
||||
// Then, change the title with visits.
|
||||
place.title = "title change";
|
||||
place.visits = [new VisitInfo()];
|
||||
gHistory.updatePlaces(place, function(aResultCode, aPlaceInfo) {
|
||||
do_check_true(Components.isSuccessCode(aResultCode));
|
||||
do_check_title_for_uri(place.uri, place.title);
|
||||
|
||||
// Lastly, check that the title is cleared if we set it to null.
|
||||
place.title = null;
|
||||
place.visits = [new VisitInfo()];
|
||||
gHistory.updatePlaces(place, function(aResultCode, aPlaceInfo) {
|
||||
do_check_true(Components.isSuccessCode(aResultCode));
|
||||
do_check_title_for_uri(place.uri, place.title);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function test_no_title_does_not_clear_title()
|
||||
{
|
||||
const TITLE = "test title";
|
||||
// First, add a visit for it.
|
||||
let place = {
|
||||
uri: NetUtil.newURI(TEST_DOMAIN + "test_no_title_does_not_clear_title"),
|
||||
title: TITLE,
|
||||
visits: [
|
||||
new VisitInfo(),
|
||||
],
|
||||
};
|
||||
do_check_false(gGlobalHistory.isVisited(place.uri));
|
||||
|
||||
gHistory.updatePlaces(place, function(aResultCode, aPlaceInfo) {
|
||||
do_check_true(Components.isSuccessCode(aResultCode));
|
||||
|
||||
// Now, make sure that not specifying a title does not clear it.
|
||||
delete place.title;
|
||||
place.visits = [new VisitInfo()];
|
||||
gHistory.updatePlaces(place, function(aResultCode, aPlaceInfo) {
|
||||
do_check_true(Components.isSuccessCode(aResultCode));
|
||||
do_check_title_for_uri(place.uri, TITLE);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function test_title_change_notifies()
|
||||
{
|
||||
// There are three cases to test. The first case is to make sure we do not
|
||||
// get notified if we do not specify a title.
|
||||
let place = {
|
||||
uri: NetUtil.newURI(TEST_DOMAIN + "test_title_change_notifies"),
|
||||
visits: [
|
||||
new VisitInfo(),
|
||||
],
|
||||
};
|
||||
do_check_false(gGlobalHistory.isVisited(place.uri));
|
||||
|
||||
let silentObserver =
|
||||
new TitleChangedObserver(place.uri, "DO NOT WANT", function() {
|
||||
do_throw("unexpected callback!");
|
||||
});
|
||||
|
||||
PlacesUtils.history.addObserver(silentObserver, false);
|
||||
gHistory.updatePlaces(place);
|
||||
|
||||
// The second case to test is that we get the notification when we add
|
||||
// it for the first time. The first case will fail before our callback if it
|
||||
// is busted, so we can do this now.
|
||||
place.uri = NetUtil.newURI(place.uri.spec + "/new-visit-with-title");
|
||||
place.title = "title 1";
|
||||
let callbackCount = 0;
|
||||
let observer = new TitleChangedObserver(place.uri, place.title, function() {
|
||||
switch (++callbackCount) {
|
||||
case 1:
|
||||
// The third case to test is to make sure we get a notification when we
|
||||
// change an existing place.
|
||||
observer.expectedTitle = place.title = "title 2";
|
||||
place.visits = [new VisitInfo()];
|
||||
gHistory.updatePlaces(place);
|
||||
break;
|
||||
case 2:
|
||||
PlacesUtils.history.removeObserver(silentObserver);
|
||||
PlacesUtils.history.removeObserver(observer);
|
||||
run_next_test();
|
||||
};
|
||||
});
|
||||
PlacesUtils.history.addObserver(observer, false);
|
||||
gHistory.updatePlaces(place);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// Test Runner
|
||||
|
||||
|
@ -763,6 +909,8 @@ let gTests = [
|
|||
test_sessionId_saved,
|
||||
test_guid_change_saved,
|
||||
test_title_change_saved,
|
||||
test_no_title_does_not_clear_title,
|
||||
test_title_change_notifies,
|
||||
];
|
||||
|
||||
function run_test()
|
||||
|
|
Загрузка…
Ссылка в новой задаче