Bug 542943 - Get rid of bookmarksHash, r=dietrich

This commit is contained in:
Marco Bonardo 2010-03-10 13:40:52 +01:00
parent 1dc1d82e84
commit a91d58bd5e
7 changed files with 300 additions and 438 deletions

View File

@ -499,6 +499,9 @@ interface nsINavBookmarksService : nsISupports
* in this case "mozilla.org".
*
* If there is no bookmarked page found, it will return NULL.
*
* @note The function will only return bookmarks in the first 3 levels of
* redirection (1 -> 2 -> 3 -> aURI).
*/
nsIURI getBookmarkedURIFor(in nsIURI aURI);

View File

@ -286,52 +286,6 @@ nsNavBookmarks::GetStatement(const nsCOMPtr<mozIStorageStatement>& aStmt)
"WHERE content = ?1 "
"LIMIT 1"));
// input = page ID, time threshold; output = unique ID input has redirected to
RETURN_IF_STMT(mDBGetRedirectDestinations, NS_LITERAL_CSTRING(
"SELECT DISTINCT dest_v.place_id "
"FROM moz_historyvisits_temp source_v "
"JOIN moz_historyvisits_temp dest_v ON dest_v.from_visit = source_v.id "
"WHERE source_v.place_id = ?1 "
"AND source_v.visit_date >= ?2 "
"AND dest_v.visit_type IN (") +
nsPrintfCString("%d,%d",
nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT,
nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY) +
NS_LITERAL_CSTRING(") "
"UNION "
"SELECT DISTINCT dest_v.place_id "
"FROM moz_historyvisits_temp source_v "
"JOIN moz_historyvisits dest_v ON dest_v.from_visit = source_v.id "
"WHERE source_v.place_id = ?1 "
"AND source_v.visit_date >= ?2 "
"AND dest_v.visit_type IN (") +
nsPrintfCString("%d,%d",
nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT,
nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY) +
NS_LITERAL_CSTRING(") "
"UNION "
"SELECT DISTINCT dest_v.place_id "
"FROM moz_historyvisits source_v "
"JOIN moz_historyvisits_temp dest_v ON dest_v.from_visit = source_v.id "
"WHERE source_v.place_id = ?1 "
"AND source_v.visit_date >= ?2 "
"AND dest_v.visit_type IN (") +
nsPrintfCString("%d,%d",
nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT,
nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY) +
NS_LITERAL_CSTRING(") "
"UNION "
"SELECT DISTINCT dest_v.place_id "
"FROM moz_historyvisits source_v "
"JOIN moz_historyvisits dest_v ON dest_v.from_visit = source_v.id "
"WHERE source_v.place_id = ?1 "
"AND source_v.visit_date >= ?2 "
"AND dest_v.visit_type IN (") +
nsPrintfCString("%d,%d",
nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT,
nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY) +
NS_LITERAL_CSTRING(") "));
RETURN_IF_STMT(mDBInsertBookmark, NS_LITERAL_CSTRING(
"INSERT INTO moz_bookmarks "
"(id, fk, type, parent, position, title, folder_type, "
@ -341,9 +295,17 @@ nsNavBookmarks::GetStatement(const nsCOMPtr<mozIStorageStatement>& aStmt)
// Just select position since it's just an int32 and may be faster.
// We don't actually care about the data, just whether there is any.
RETURN_IF_STMT(mDBIsBookmarkedInDatabase, NS_LITERAL_CSTRING(
"SELECT position FROM moz_bookmarks WHERE fk = ?1 AND type = ?2"));
"SELECT 1 FROM moz_bookmarks WHERE fk = ?1"));
// Checks to make sure a place_id is a bookmark, and isn't a livemark.
RETURN_IF_STMT(mDBIsURIBookmarkedInDatabase, NS_LITERAL_CSTRING(
"SELECT 1 FROM moz_bookmarks WHERE fk = ("
"SELECT id FROM moz_places_temp WHERE url = ?1 "
"UNION ALL "
"SELECT id FROM moz_places WHERE url = ?1 "
"LIMIT 1"
")"));
// Checks to make sure a place id is a bookmark, and isn't a livemark.
RETURN_IF_STMT(mDBIsRealBookmark, NS_LITERAL_CSTRING(
"SELECT id "
"FROM moz_bookmarks "
@ -429,6 +391,85 @@ nsNavBookmarks::GetStatement(const nsCOMPtr<mozIStorageStatement>& aStmt)
RETURN_IF_STMT(mDBChangeBookmarkURI, NS_LITERAL_CSTRING(
"UPDATE moz_bookmarks SET fk = ?1, lastModified = ?2 WHERE id = ?3 "));
// The next query finds the bookmarked ancestors in a redirects chain.
// It won't go further than 3 levels of redirects (a->b->c->your_place_id).
// To make this path 100% correct (up to any level) we would need either:
// - A separate hash, build through recursive querying of the database.
// This solution was previously implemented, but it had a negative effect
// on startup since at each startup we have to recursively query the
// database to rebuild a hash that is always the same across sessions.
// It must be updated at each visit and bookmarks change too. The code to
// manage it is complex and prone to errors, sometimes causing incorrect
// data fetches (for example wrong favicon for a redirected bookmark).
// - A better way to track redirects for a visit.
// We would need a separate table to track redirects, in the table we would
// have visit_id, redirect_session. To get all sources for
// a visit then we could just join this table and get all visit_id that
// are in the same redirect_session as our visit. This has the drawback
// that we can't ensure data integrity in the downgrade -> upgrade path,
// since an old version would not update the table on new visits.
//
// For most cases these levels of redirects should be fine though, it's hard
// to hit a page that is 4 or 5 levels of redirects below a bookmarked page.
//
// Moreover this query does not mix-up all possible cases of disk and temp
// tables. This is because we expect a redirects chain to be completely on
// disk or completely in memory. We never bring back visits from disk to
// memory, we sync visits on a timer (the chained visits have narrow times),
// or on bookmarks changes. The likely possiblity that we break a chain in
// the middle is so much smaller than the perf and readability hit we would
// get making complete crossing joins.
//
// As a bonus the query also checks first if place_id is already a bookmark,
// so you don't have to check that apart.
#define COALESCE_PLACEID \
"COALESCE(greatgrandparent.place_id, grandparent.place_id, parent.place_id) "
nsCString redirectsFragment =
nsPrintfCString(3, "%d,%d",
nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT,
nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY);
RETURN_IF_STMT(mDBFindRedirectedBookmark, NS_LITERAL_CSTRING(
"SELECT IFNULL( "
"(SELECT url FROM moz_places_temp WHERE id = ?1), "
"(SELECT url FROM moz_places WHERE id = ?1) "
") "
"FROM moz_bookmarks b "
"WHERE b.fk = ?1 "
"UNION ALL " // Not directly bookmarked.
"SELECT IFNULL( "
"(SELECT url FROM moz_places_temp WHERE id = " COALESCE_PLACEID "), "
"(SELECT url FROM moz_places WHERE id = " COALESCE_PLACEID ") "
") "
"FROM moz_historyvisits_temp self "
"JOIN moz_bookmarks b ON b.fk = " COALESCE_PLACEID
"LEFT JOIN moz_historyvisits_temp parent ON parent.id = self.from_visit "
"LEFT JOIN moz_historyvisits_temp grandparent ON parent.from_visit = grandparent.id "
"AND parent.visit_type IN (") + redirectsFragment + NS_LITERAL_CSTRING(") "
"LEFT JOIN moz_historyvisits_temp greatgrandparent ON grandparent.from_visit = greatgrandparent.id "
"AND grandparent.visit_type IN (") + redirectsFragment + NS_LITERAL_CSTRING(") "
"WHERE self.visit_type IN (") + redirectsFragment + NS_LITERAL_CSTRING(") "
"AND self.place_id = ?1 "
"UNION ALL " // Not in the temp table.
"SELECT IFNULL( "
"(SELECT url FROM moz_places_temp WHERE id = " COALESCE_PLACEID "), "
"(SELECT url FROM moz_places WHERE id = " COALESCE_PLACEID ") "
") "
"FROM moz_historyvisits self "
"JOIN moz_bookmarks b ON b.fk = " COALESCE_PLACEID
"LEFT JOIN moz_historyvisits parent ON parent.id = self.from_visit "
"LEFT JOIN moz_historyvisits grandparent ON parent.from_visit = grandparent.id "
"AND parent.visit_type IN (") + redirectsFragment + NS_LITERAL_CSTRING(") "
"LEFT JOIN moz_historyvisits greatgrandparent ON grandparent.from_visit = greatgrandparent.id "
"AND grandparent.visit_type IN (") + redirectsFragment + NS_LITERAL_CSTRING(") "
"WHERE self.visit_type IN (") + redirectsFragment + NS_LITERAL_CSTRING(") "
"AND self.place_id = ?1 "
"LIMIT 1 " // Stop at the first result.
));
#undef COALESCE_PLACEID
return nsnull;
}
@ -444,7 +485,6 @@ nsNavBookmarks::FinalizeStatements() {
mDBGetChildAt,
mDBGetItemProperties,
mDBGetItemIdForGUID,
mDBGetRedirectDestinations,
mDBInsertBookmark,
mDBIsBookmarkedInDatabase,
mDBIsRealBookmark,
@ -461,6 +501,8 @@ nsNavBookmarks::FinalizeStatements() {
mDBMoveItem,
mDBSetItemTitle,
mDBChangeBookmarkURI,
mDBIsURIBookmarkedInDatabase,
mDBFindRedirectedBookmark,
};
for (PRUint32 i = 0; i < NS_ARRAY_LENGTH(stmts); i++) {
@ -678,271 +720,24 @@ nsNavBookmarks::CreateRoot(mozIStorageStatement* aGetRootStatement,
}
// nsNavBookmarks::GetBookmarksHash
//
// Getter and lazy initializer of the bookmarks redirect hash.
// See FillBookmarksHash for more information.
nsDataHashtable<nsTrimInt64HashKey, PRInt64>*
nsNavBookmarks::GetBookmarksHash()
{
if (!mBookmarksHash.IsInitialized()) {
nsresult rv = FillBookmarksHash();
NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv), "FillBookmarksHash() failed!");
}
return &mBookmarksHash;
}
// nsNavBookmarks::FillBookmarksHash
//
// This initializes the bookmarks hashtable that tells us which bookmark
// a given URI redirects to. This hashtable includes all URIs that
// redirect to bookmarks.
nsresult
nsNavBookmarks::FillBookmarksHash()
{
PRBool hasMore;
// first init the hashtable
NS_ENSURE_TRUE(mBookmarksHash.Init(1024), NS_ERROR_OUT_OF_MEMORY);
// first populate the hashtable with all bookmarks
nsCOMPtr<mozIStorageStatement> statement;
nsresult rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
"SELECT b.fk "
"FROM moz_bookmarks b "
"WHERE b.type = ?1 "
"AND b.fk NOTNULL"),
getter_AddRefs(statement));
NS_ENSURE_SUCCESS(rv, rv);
rv = statement->BindInt32Parameter(0, TYPE_BOOKMARK);
NS_ENSURE_SUCCESS(rv, rv);
while (NS_SUCCEEDED(statement->ExecuteStep(&hasMore)) && hasMore) {
PRInt64 pageID;
rv = statement->GetInt64(0, &pageID);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(mBookmarksHash.Put(pageID, pageID), NS_ERROR_OUT_OF_MEMORY);
}
// Find all pages h2 that have been redirected to from a bookmarked URI:
// bookmarked -> url (h1) url (h2)
// | ^
// . |
// visit (v1) -> destination visit (v2)
// This should catch most redirects, which are only one level. More levels of
// redirection will be handled separately.
rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
"SELECT v1.place_id, v2.place_id "
"FROM moz_bookmarks b "
"LEFT JOIN moz_historyvisits_temp v1 on b.fk = v1.place_id "
"LEFT JOIN moz_historyvisits v2 on v2.from_visit = v1.id "
"WHERE b.fk IS NOT NULL AND b.type = ?1 "
"AND v2.visit_type IN (") +
nsPrintfCString("%d,%d",
nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT,
nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY) +
NS_LITERAL_CSTRING(") GROUP BY v2.place_id "
"UNION "
"SELECT v1.place_id, v2.place_id "
"FROM moz_bookmarks b "
"LEFT JOIN moz_historyvisits v1 on b.fk = v1.place_id "
"LEFT JOIN moz_historyvisits_temp v2 on v2.from_visit = v1.id "
"WHERE b.fk IS NOT NULL AND b.type = ?1 "
"AND v2.visit_type IN (") +
nsPrintfCString("%d,%d",
nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT,
nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY) +
NS_LITERAL_CSTRING(") GROUP BY v2.place_id "
"UNION "
"SELECT v1.place_id, v2.place_id "
"FROM moz_bookmarks b "
"LEFT JOIN moz_historyvisits v1 on b.fk = v1.place_id "
"LEFT JOIN moz_historyvisits v2 on v2.from_visit = v1.id "
"WHERE b.fk IS NOT NULL AND b.type = ?1 "
"AND v2.visit_type IN (") +
nsPrintfCString("%d,%d",
nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT,
nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY) +
NS_LITERAL_CSTRING(") GROUP BY v2.place_id "
"UNION "
"SELECT v1.place_id, v2.place_id "
"FROM moz_bookmarks b "
"LEFT JOIN moz_historyvisits_temp v1 on b.fk = v1.place_id "
"LEFT JOIN moz_historyvisits_temp v2 on v2.from_visit = v1.id "
"WHERE b.fk IS NOT NULL AND b.type = ?1 "
"AND v2.visit_type IN (") +
nsPrintfCString("%d,%d",
nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT,
nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY) +
NS_LITERAL_CSTRING(") GROUP BY v2.place_id "),
getter_AddRefs(statement));
NS_ENSURE_SUCCESS(rv, rv);
rv = statement->BindInt32Parameter(0, TYPE_BOOKMARK);
NS_ENSURE_SUCCESS(rv, rv);
while (NS_SUCCEEDED(statement->ExecuteStep(&hasMore)) && hasMore) {
PRInt64 fromId, toId;
rv = statement->GetInt64(0, &fromId);
NS_ENSURE_SUCCESS(rv, rv);
rv = statement->GetInt64(1, &toId);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(mBookmarksHash.Put(toId, fromId), NS_ERROR_OUT_OF_MEMORY);
// handle redirects deeper than one level
rv = RecursiveAddBookmarkHash(fromId, toId, 0);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
// nsNavBookmarks::AddBookmarkToHash
//
// Given a bookmark that was potentially added, this goes through all
// redirects that this page may have resulted in and adds them to our hash.
// Note that this takes the ID of the URL in the history system, which we
// generally have when calling this function and which makes it faster.
//
// For better performance, this call should be in a DB transaction.
//
// @see RecursiveAddBookmarkHash
nsresult
nsNavBookmarks::AddBookmarkToHash(PRInt64 aPlaceId, PRTime aMinTime)
{
if (!GetBookmarksHash()->Put(aPlaceId, aPlaceId))
return NS_ERROR_OUT_OF_MEMORY;
nsresult rv = RecursiveAddBookmarkHash(aPlaceId, aPlaceId, aMinTime);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
// nsNavBookmkars::RecursiveAddBookmarkHash
//
// Used to add a new level of redirect information to the bookmark hash.
// Given a source bookmark 'aBookmark' and 'aCurrentSource' that has already
// been added to the hashtable, this will add all redirect destinations of
// 'aCurrentSource'. Will call itself recursively to walk down the chain.
//
// 'aMinTime' is the minimum time to consider visits from. Visits previous
// to this will not be considered. This allows the search to be much more
// efficient if you know something happened recently. Use 0 for the min time
// to search all history for redirects.
nsresult
nsNavBookmarks::RecursiveAddBookmarkHash(PRInt64 aPlaceID,
PRInt64 aCurrentSource,
PRTime aMinTime)
{
nsresult rv;
nsTArray<PRInt64> found;
// scope for the DB statement. The statement must be reset by the time we
// recursively call ourselves again, because our recursive call will use the
// same statement.
{
DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(stmt, mDBGetRedirectDestinations);
rv = stmt->BindInt64Parameter(0, aCurrentSource);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt64Parameter(1, aMinTime);
NS_ENSURE_SUCCESS(rv, rv);
PRBool hasMore;
while (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) {
// add this newly found redirect destination to the hashtable
PRInt64 curID;
rv = stmt->GetInt64(0, &curID);
NS_ENSURE_SUCCESS(rv, rv);
// It is very important we ignore anything already in our hashtable. It
// is actually pretty common to get loops of redirects. For example,
// a restricted page will redirect you to a login page, which will
// redirect you to the restricted page again with the proper cookie.
PRInt64 alreadyExistingOne;
if (GetBookmarksHash()->Get(curID, &alreadyExistingOne))
continue;
if (!GetBookmarksHash()->Put(curID, aPlaceID))
return NS_ERROR_OUT_OF_MEMORY;
// save for recursion later
found.AppendElement(curID);
}
}
// recurse on each found item now that we're done with the statement
for (PRUint32 i = 0; i < found.Length(); i ++) {
rv = RecursiveAddBookmarkHash(aPlaceID, found[i], aMinTime);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
// nsNavBookmarks::UpdateBookmarkHashOnRemove
//
// Call this when a bookmark is removed. It will see if the bookmark still
// exists anywhere in the system, and, if not, remove all references to it
// in the bookmark hashtable.
//
// The callback takes a pointer to what bookmark is being removed (as
// an Int64 history page ID) as the userArg and removes all redirect
// destinations that reference it.
static PLDHashOperator
RemoveBookmarkHashCallback(nsTrimInt64HashKey::KeyType aKey,
PRInt64& aPlaceId, void* aUserArg)
{
const PRInt64* removeThisOne = reinterpret_cast<const PRInt64*>(aUserArg);
if (aPlaceId == *removeThisOne)
return PL_DHASH_REMOVE;
return PL_DHASH_NEXT;
}
nsresult
nsNavBookmarks::UpdateBookmarkHashOnRemove(PRInt64 aPlaceId)
{
// note we have to use the DB version here since the hashtable may be
// out-of-date
PRBool inDB;
nsresult rv = IsBookmarkedInDatabase(aPlaceId, &inDB);
NS_ENSURE_SUCCESS(rv, rv);
if (!inDB) {
// Bookmark does not exist anymore, remove it from hashtable
GetBookmarksHash()->Enumerate(RemoveBookmarkHashCallback,
reinterpret_cast<void*>(&aPlaceId));
}
return NS_OK;
}
PRBool
nsNavBookmarks::IsRealBookmark(PRInt64 aPlaceId)
{
// Fast path is to check the hash table first. If it is in the hash table,
// then verify that it is a real bookmark.
PRInt64 bookmarkId;
PRBool isBookmark = GetBookmarksHash()->Get(aPlaceId, &bookmarkId);
if (isBookmark) {
DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(stmt, mDBIsRealBookmark);
nsresult rv = stmt->BindInt64Parameter(0, aPlaceId);
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Binding failed");
rv = stmt->BindInt32Parameter(1, TYPE_BOOKMARK);
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Binding failed");
rv = stmt->BindUTF8StringParameter(2, NS_LITERAL_CSTRING(LMANNO_FEEDURI));
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Binding failed");
DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(stmt, mDBIsRealBookmark);
nsresult rv = stmt->BindInt64Parameter(0, aPlaceId);
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Binding failed");
rv = stmt->BindInt32Parameter(1, TYPE_BOOKMARK);
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Binding failed");
rv = stmt->BindUTF8StringParameter(2, NS_LITERAL_CSTRING(LMANNO_FEEDURI));
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Binding failed");
// If we get any rows, then there exists at least one bookmark corresponding
// to aPlaceId that is not a livemark item.
rv = stmt->ExecuteStep(&isBookmark);
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "ExecuteStep failed");
if (NS_SUCCEEDED(rv))
return isBookmark;
}
// If we get any rows, then there exists at least one bookmark corresponding
// to aPlaceId that is not a livemark item.
PRBool isBookmark;
rv = stmt->ExecuteStep(&isBookmark);
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "ExecuteStep failed");
if (NS_SUCCEEDED(rv))
return isBookmark;
return PR_FALSE;
}
@ -951,7 +746,6 @@ nsNavBookmarks::IsRealBookmark(PRInt64 aPlaceId)
// nsNavBookmarks::IsBookmarkedInDatabase
//
// This checks to see if the specified place_id is actually bookmarked.
// This does not check for redirects in the hashtable.
nsresult
nsNavBookmarks::IsBookmarkedInDatabase(PRInt64 aPlaceId,
@ -960,8 +754,6 @@ nsNavBookmarks::IsBookmarkedInDatabase(PRInt64 aPlaceId,
DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(stmt, mDBIsBookmarkedInDatabase);
nsresult rv = stmt->BindInt64Parameter(0, aPlaceId);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt32Parameter(1, TYPE_BOOKMARK);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->ExecuteStep(aIsBookmarked);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
@ -1202,9 +994,6 @@ nsNavBookmarks::InsertBookmark(PRInt64 aFolder,
rv = transaction.Commit();
NS_ENSURE_SUCCESS(rv, rv);
rv = AddBookmarkToHash(childID, 0);
NS_ENSURE_SUCCESS(rv, rv);
NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
nsINavBookmarkObserver,
OnItemAdded(*aNewBookmarkId, aFolder, index, TYPE_BOOKMARK));
@ -1310,9 +1099,6 @@ nsNavBookmarks::RemoveItem(PRInt64 aItemId)
rv = transaction.Commit();
NS_ENSURE_SUCCESS(rv, rv);
rv = UpdateBookmarkHashOnRemove(placeId);
NS_ENSURE_SUCCESS(rv, rv);
if (itemType == TYPE_BOOKMARK) {
// UpdateFrecency needs to know whether placeId is still bookmarked.
// Although we removed aItemId, placeId may still be bookmarked elsewhere;
@ -1881,7 +1667,6 @@ nsNavBookmarks::RemoveFolderChildren(PRInt64 aFolderId)
folderChildrenInfo child = folderChildrenArray[i];
if (child.itemType == TYPE_BOOKMARK) {
PRInt64 placeId = child.placeId;
UpdateBookmarkHashOnRemove(placeId);
// UpdateFrecency needs to know whether placeId is still bookmarked.
// Although we removed a child of aFolderId that bookmarked it, it may
@ -2618,36 +2403,11 @@ nsNavBookmarks::IsBookmarked(nsIURI* aURI, PRBool* aBookmarked)
NS_ENSURE_ARG(aURI);
NS_ENSURE_ARG_POINTER(aBookmarked);
nsNavHistory* history = nsNavHistory::GetHistoryService();
NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
// convert the URL to an ID
PRInt64 urlID;
nsresult rv = history->GetUrlIdFor(aURI, &urlID, PR_FALSE);
DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(stmt, mDBIsURIBookmarkedInDatabase);
nsresult rv = BindStatementURI(stmt, 0, aURI);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->ExecuteStep(aBookmarked);
NS_ENSURE_SUCCESS(rv, rv);
if (!urlID) {
// never seen this before, not even in history
*aBookmarked = PR_FALSE;
return NS_OK;
}
PRInt64 bookmarkedID;
PRBool foundOne = GetBookmarksHash()->Get(urlID, &bookmarkedID);
// IsBookmarked only tests if this exact URI is bookmarked, so we need to
// check that the destination matches
if (foundOne)
*aBookmarked = (urlID == bookmarkedID);
else
*aBookmarked = PR_FALSE;
#ifdef DEBUG
// sanity check for the bookmark hashtable
PRBool realBookmarked;
rv = IsBookmarkedInDatabase(urlID, &realBookmarked);
NS_ASSERTION(realBookmarked == *aBookmarked,
"Bookmark hash table out-of-sync with the database");
#endif
return NS_OK;
}
@ -2663,33 +2423,32 @@ nsNavBookmarks::GetBookmarkedURIFor(nsIURI* aURI, nsIURI** _retval)
nsNavHistory* history = nsNavHistory::GetHistoryService();
NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
// convert the URL to an ID
PRInt64 urlID;
nsresult rv = history->GetUrlIdFor(aURI, &urlID, PR_FALSE);
PRInt64 placeId;
nsresult rv = history->GetUrlIdFor(aURI, &placeId, PR_TRUE);
NS_ENSURE_SUCCESS(rv, rv);
if (!urlID) {
// never seen this before, not even in history, leave result NULL
if (!placeId) {
// This URI is unknown, just return null.
return NS_OK;
}
PRInt64 bookmarkID;
if (GetBookmarksHash()->Get(urlID, &bookmarkID)) {
// found one, convert ID back to URL. This statement is NOT refcounted
mozIStorageStatement* statement = history->DBGetIdPageInfo();
NS_ENSURE_STATE(statement);
mozStorageStatementScoper scoper(statement);
rv = statement->BindInt64Parameter(0, bookmarkID);
// Check if a bookmark exists in the redirects chain for this URI.
// The query will also check if the page is directly bookmarked, and return
// the first found bookmark in case. The check is directly on moz_bookmarks
// without special filtering.
DECLARE_AND_ASSIGN_SCOPED_LAZY_STMT(stmt, mDBFindRedirectedBookmark);
rv = stmt->BindInt64Parameter(0, placeId);
NS_ENSURE_SUCCESS(rv, rv);
PRBool hasBookmarkedOrigin;
if (NS_SUCCEEDED(stmt->ExecuteStep(&hasBookmarkedOrigin)) &&
hasBookmarkedOrigin) {
nsCAutoString spec;
rv = stmt->GetUTF8String(0, spec);
NS_ENSURE_SUCCESS(rv, rv);
rv = NS_NewURI(_retval, spec);
NS_ENSURE_SUCCESS(rv, rv);
PRBool hasMore;
if (NS_SUCCEEDED(statement->ExecuteStep(&hasMore)) && hasMore) {
nsCAutoString spec;
statement->GetUTF8String(nsNavHistory::kGetInfoIndex_URL, spec);
return NS_NewURI(_retval, spec);
}
}
// If there is no bookmarked origin, we will just return null.
return NS_OK;
}
@ -2734,14 +2493,6 @@ nsNavBookmarks::ChangeBookmarkURI(PRInt64 aBookmarkId, nsIURI* aNewURI)
rv = transaction.Commit();
NS_ENSURE_SUCCESS(rv, rv);
// Add new URI to bookmark hash.
rv = AddBookmarkToHash(placeId, 0);
NS_ENSURE_SUCCESS(rv, rv);
// Remove old URI from bookmark hash.
rv = UpdateBookmarkHashOnRemove(oldPlaceId);
NS_ENSURE_SUCCESS(rv, rv);
// Upon changing the URI for a bookmark, update the frecency for the new place.
// UpdateFrecency needs to know whether placeId is bookmarked (as opposed
// to a livemark item). Bookmarking it is exactly what we did above.

View File

@ -45,7 +45,6 @@
#include "nsIAnnotationService.h"
#include "nsITransaction.h"
#include "nsNavHistory.h"
#include "nsNavHistoryResult.h" // need for Int64 hashtable
#include "nsToolkitCompsCID.h"
#include "nsCategoryCache.h"
@ -87,8 +86,6 @@ public:
return gBookmarksService;
}
nsresult AddBookmarkToHash(PRInt64 aBookmarkId, PRTime aMinTime);
nsresult ResultNodeForContainer(PRInt64 aID,
nsNavHistoryQueryOptions* aOptions,
nsNavHistoryResultNode** aNode);
@ -187,16 +184,6 @@ private:
// be committed when our batch level reaches 0 again.
PRBool mBatchHasTransaction;
// This stores a mapping from all pages reachable by redirects from bookmarked
// pages to the bookmarked page. Used by GetBookmarkedURIFor.
nsDataHashtable<nsTrimInt64HashKey, PRInt64> mBookmarksHash;
nsDataHashtable<nsTrimInt64HashKey, PRInt64>* GetBookmarksHash();
nsresult FillBookmarksHash();
nsresult RecursiveAddBookmarkHash(PRInt64 aBookmarkId,
PRInt64 aCurrentSource,
PRTime aMinTime);
nsresult UpdateBookmarkHashOnRemove(PRInt64 aPlaceId);
nsresult GetParentAndIndexOfFolder(PRInt64 aFolder,
PRInt64* aParent,
PRInt32* aIndex);
@ -281,6 +268,7 @@ private:
nsresult GetBookmarkIdsForURITArray(nsIURI* aURI,
nsTArray<PRInt64>& aResult);
PRInt64 RecursiveFindRedirectedBookmark(PRInt64 aPlaceId);
/**
* You should always use this getter and never use directly the nsCOMPtr.
@ -330,8 +318,8 @@ private:
nsCOMPtr<mozIStorageStatement> mDBGetItemIndex;
nsCOMPtr<mozIStorageStatement> mDBGetChildAt;
nsCOMPtr<mozIStorageStatement> mDBGetItemIdForGUID;
nsCOMPtr<mozIStorageStatement> mDBGetRedirectDestinations;
nsCOMPtr<mozIStorageStatement> mDBIsBookmarkedInDatabase;
nsCOMPtr<mozIStorageStatement> mDBIsURIBookmarkedInDatabase;
nsCOMPtr<mozIStorageStatement> mDBIsRealBookmark;
nsCOMPtr<mozIStorageStatement> mDBGetLastBookmarkID;
nsCOMPtr<mozIStorageStatement> mDBSetItemDateAdded;
@ -346,6 +334,7 @@ private:
nsCOMPtr<mozIStorageStatement> mDBMoveItem;
nsCOMPtr<mozIStorageStatement> mDBSetItemTitle;
nsCOMPtr<mozIStorageStatement> mDBChangeBookmarkURI;
nsCOMPtr<mozIStorageStatement> mDBFindRedirectedBookmark;
class RemoveFolderTransaction : public nsITransaction {
public:

View File

@ -91,10 +91,6 @@ using namespace mozilla::places;
// This is 15 minutes m s/m us/s
#define RECENT_EVENT_THRESHOLD ((PRInt64)15 * 60 * PR_USEC_PER_SEC)
// Microseconds ago to look for redirects when updating bookmarks. Used to
// compute the threshold for nsNavBookmarks::AddBookmarkToHash
#define BOOKMARK_REDIRECT_TIME_THRESHOLD ((PRInt64)2 * 60 * PR_USEC_PER_SEC)
// The maximum number of things that we will store in the recent events list
// before calling ExpireNonrecentEvents. This number should be big enough so it
// is very difficult to get that many unconsumed events (for example, typed but
@ -3858,7 +3854,7 @@ PlacesSQLQueryBuilder::OrderBy()
switch(mSortingMode)
{
case nsINavHistoryQueryOptions::SORT_BY_NONE:
// If this is an URI query the sorting could change based on the
// If this is a URI query the sorting could change based on the
// sync status of disk and temp tables, we must ensure sorting does not
// change between queries.
if (mResultType == nsINavHistoryQueryOptions::RESULTS_AS_URI) {
@ -5038,7 +5034,7 @@ nsNavHistory::MarkPageAsFollowedLink(nsIURI *aURI)
// nsNavHistory::SetCharsetForURI
//
// Sets the character-set for an URI.
// Sets the character-set for a URI.
// If aCharset is empty remove character-set annotation for aURI.
NS_IMETHODIMP
@ -5075,7 +5071,7 @@ nsNavHistory::SetCharsetForURI(nsIURI* aURI,
// nsNavHistory::GetCharsetForURI
//
// Get the last saved character-set for an URI.
// Get the last saved character-set for a URI.
NS_IMETHODIMP
nsNavHistory::GetCharsetForURI(nsIURI* aURI,
@ -5160,27 +5156,9 @@ nsNavHistory::AddURIInternal(nsIURI* aURI, PRTime aTime, PRBool aRedirect,
PRInt64 visitID = 0;
PRInt64 sessionID = 0;
nsresult rv = AddVisitChain(aURI, aTime, aToplevel, aRedirect, aReferrer,
&visitID, &sessionID, &redirectBookmark);
&visitID, &sessionID);
NS_ENSURE_SUCCESS(rv, rv);
// The bookmark cache of redirects may be out-of-date with this addition, so
// we need to update it. The issue here is if they bookmark "mozilla.org" by
// typing it in without ever having visited "www.mozilla.org". They will then
// get redirected to the latter, and we need to add mozilla.org ->
// www.mozilla.org to the bookmark hashtable.
//
// AddVisitChain will put the spec of a bookmarked URI if it encounters one
// into bookmarkURI. If this is non-empty, we know that something has happened
// with a bookmark and we should probably go update it.
if (redirectBookmark) {
nsNavBookmarks *bookmarkService = nsNavBookmarks::GetBookmarksService();
if (bookmarkService) {
PRTime now = GetNow();
bookmarkService->AddBookmarkToHash(redirectBookmark,
now - BOOKMARK_REDIRECT_TIME_THRESHOLD);
}
}
return transaction.Commit();
}
@ -5198,12 +5176,6 @@ nsNavHistory::AddURIInternal(nsIURI* aURI, PRTime aTime, PRBool aRedirect,
// save them in mRecentRedirects. This function will add all of them for a
// given destination page when that page is actually visited.)
// See GetRedirectFor for more information about how redirects work.
//
// aRedirectBookmark should be empty when this function is first called. If
// there are any redirects that are bookmarks the specs will be placed in
// this buffer. The caller can then determine if any bookmarked items were
// visited so it knows whether to update the bookmark service's redirect
// hashtable.
nsresult
nsNavHistory::AddVisitChain(nsIURI* aURI,
@ -5212,8 +5184,7 @@ nsNavHistory::AddVisitChain(nsIURI* aURI,
PRBool aIsRedirect,
nsIURI* aReferrerURI,
PRInt64* aVisitID,
PRInt64* aSessionID,
PRInt64* aRedirectBookmark)
PRInt64* aSessionID)
{
// This is the address that will be saved to from_visit column, will be
// overwritten later if needed.
@ -5247,15 +5218,6 @@ nsNavHistory::AddVisitChain(nsIURI* aURI,
redirectIsSame)
return NS_OK;
// remember if any redirect sources were bookmarked
nsNavBookmarks *bookmarkService = nsNavBookmarks::GetBookmarksService();
PRBool isBookmarked;
if (bookmarkService &&
NS_SUCCEEDED(bookmarkService->IsBookmarked(redirectSourceURI, &isBookmarked))
&& isBookmarked) {
GetUrlIdFor(redirectSourceURI, aRedirectBookmark, PR_FALSE);
}
// Recusively call addVisitChain to walk up the chain till the first
// not-redirected URI.
// Ensure that the sources have a visit time smaller than aTime, otherwise
@ -5266,8 +5228,7 @@ nsNavHistory::AddVisitChain(nsIURI* aURI,
PR_TRUE, // Is a redirect.
aReferrerURI, // This one is the originating source.
&sourceVisitId, // Get back the visit id of the source.
aSessionID,
aRedirectBookmark);
aSessionID);
NS_ENSURE_SUCCESS(rv, rv);
// All the visits for preceding pages in the redirects chain have been

View File

@ -488,7 +488,7 @@ protected:
nsresult AddVisitChain(nsIURI* aURI, PRTime aTime,
PRBool aToplevel, PRBool aRedirect,
nsIURI* aReferrer, PRInt64* aVisitID,
PRInt64* aSessionID, PRInt64* aRedirectBookmark);
PRInt64* aSessionID);
nsresult InternalAddNewPage(nsIURI* aURI, const nsAString& aTitle,
PRBool aHidden, PRBool aTyped,
PRInt32 aVisitCount, PRBool aCalculateFrecency,

View File

@ -155,6 +155,32 @@ function check_no_bookmarks() {
root.containerOpen = false;
}
/**
* Function gets current database connection, if the connection has been closed
* it will try to reconnect to the places.sqlite database.
*/
function DBConn()
{
let db = Cc["@mozilla.org/browser/nav-history-service;1"].
getService(Ci.nsPIPlacesDatabase).
DBConnection;
if (db.connectionReady)
return db;
// open a new connection if needed
let file = dirSvc.get('ProfD', Ci.nsIFile);
file.append("places.sqlite");
let storageService = Cc["@mozilla.org/storage/service;1"].
getService(Ci.mozIStorageService);
try {
return dbConn = storageService.openDatabase(file);
}
catch(ex) {}
return null;
}
/**
* Flushes any events in the event loop of the main thread.
*/

View File

@ -0,0 +1,132 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Places unit test code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Marco Bonardo <mak77bonardo.net> (Original Author)
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/**
* Test bookmarksService.getBookmarkedURIFor(aURI);
*/
let hs = Cc["@mozilla.org/browser/nav-history-service;1"].
getService(Ci.nsINavHistoryService);
let bs = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
getService(Ci.nsINavBookmarksService);
/**
* Adds a fake redirect between two visits.
*/
function addFakeRedirect(aSourceVisitId, aDestVisitId, aRedirectType) {
let dbConn = DBConn();
let stmt = dbConn.createStatement(
"UPDATE moz_historyvisits " +
"SET from_visit = :source, visit_type = :type " +
"WHERE id = :dest");
stmt.params.source = aSourceVisitId;
stmt.params.dest = aDestVisitId;
stmt.params.type = aRedirectType;
try {
stmt.executeStep();
}
finally {
stmt.finalize();
}
}
function run_test() {
let now = Date.now() * 1000;
const sourceURI = uri("http://test.mozilla.org/");
// Add a visit and a bookmark.
let sourceVisitId = hs.addVisit(sourceURI,
now,
null,
hs.TRANSITION_TYPED,
false,
0);
do_check_eq(bs.getBookmarkedURIFor(sourceURI), null);
let sourceItemId = bs.insertBookmark(bs.unfiledBookmarksFolder,
sourceURI,
bs.DEFAULT_INDEX,
"bookmark");
do_check_true(bs.getBookmarkedURIFor(sourceURI).equals(sourceURI));
// Add a redirected visit.
const permaURI = uri("http://perma.mozilla.org/");
hs.addVisit(permaURI,
now++,
sourceURI,
hs.TRANSITION_REDIRECT_PERMANENT,
true,
0);
do_check_true(bs.getBookmarkedURIFor(sourceURI).equals(sourceURI));
do_check_true(bs.getBookmarkedURIFor(permaURI).equals(sourceURI));
// Add a bookmark to the destination.
let permaItemId = bs.insertBookmark(bs.unfiledBookmarksFolder,
permaURI,
bs.DEFAULT_INDEX,
"bookmark");
do_check_true(bs.getBookmarkedURIFor(sourceURI).equals(sourceURI));
do_check_true(bs.getBookmarkedURIFor(permaURI).equals(permaURI));
// Now remove the bookmark on the destination.
bs.removeItem(permaItemId);
// We should see the source as bookmark.
do_check_true(bs.getBookmarkedURIFor(permaURI).equals(sourceURI));
// Add another redirected visit.
const tempURI = uri("http://perma.mozilla.org/");
hs.addVisit(tempURI,
now++,
permaURI,
hs.TRANSITION_REDIRECT_TEMPORARY,
true,
0);
do_check_true(bs.getBookmarkedURIFor(sourceURI).equals(sourceURI));
do_check_true(bs.getBookmarkedURIFor(tempURI).equals(sourceURI));
// Add a bookmark to the destination.
let tempItemId = bs.insertBookmark(bs.unfiledBookmarksFolder,
tempURI,
bs.DEFAULT_INDEX,
"bookmark");
do_check_true(bs.getBookmarkedURIFor(sourceURI).equals(sourceURI));
do_check_true(bs.getBookmarkedURIFor(tempURI).equals(tempURI));
// Now remove the bookmark on the destination.
bs.removeItem(tempItemId);
// We should see the source as bookmark.
do_check_true(bs.getBookmarkedURIFor(tempURI).equals(sourceURI));
// Remove the source bookmark as well.
bs.removeItem(sourceItemId);
do_check_eq(bs.getBookmarkedURIFor(tempURI), null);
}