diff --git a/browser/components/places/tests/unit/head_bookmarks.js b/browser/components/places/tests/unit/head_bookmarks.js index 5d0b12e96991..8fc132e49601 100644 --- a/browser/components/places/tests/unit/head_bookmarks.js +++ b/browser/components/places/tests/unit/head_bookmarks.js @@ -330,3 +330,13 @@ function dump_table(aName) stmt.finalize(); stmt = null; } + +/** + * Flushes any events in the event loop of the main thread. + */ +function flush_main_thread_events() +{ + let tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager); + while (tm.mainThread.hasPendingEvents()) + tm.mainThread.processNextEvent(false); +} diff --git a/browser/components/places/tests/unit/tail_bookmarks.js b/browser/components/places/tests/unit/tail_bookmarks.js index aedd64b3f6a7..6207283cccff 100644 --- a/browser/components/places/tests/unit/tail_bookmarks.js +++ b/browser/components/places/tests/unit/tail_bookmarks.js @@ -41,9 +41,7 @@ // event loop long before code like this would run. // Not doing so could cause us to close the connection before all tasks have // been completed, and that would crash badly. -let tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager); -while (tm.mainThread.hasPendingEvents()) - tm.mainThread.processNextEvent(false); +flush_main_thread_events(); // XPCShell doesn't dispatch quit-application, to ensure cleanup we have to // dispatch it after each test run. @@ -52,6 +50,9 @@ var os = Cc['@mozilla.org/observer-service;1']. os.notifyObservers(null, "quit-application-granted", null); os.notifyObservers(null, "quit-application", null); +// Run the event loop, since we enqueue some statement finalization. +flush_main_thread_events(); + // try to close the connection so we can remove places.sqlite var pip = Cc["@mozilla.org/browser/nav-history-service;1"]. getService(Ci.nsINavHistoryService). diff --git a/toolkit/components/places/src/nsNavBookmarks.cpp b/toolkit/components/places/src/nsNavBookmarks.cpp index e9a59465b787..9db718100797 100644 --- a/toolkit/components/places/src/nsNavBookmarks.cpp +++ b/toolkit/components/places/src/nsNavBookmarks.cpp @@ -54,6 +54,7 @@ #include "nsPlacesTriggers.h" #include "nsPlacesTables.h" #include "nsPlacesIndexes.h" +#include "nsPlacesMacros.h" const PRInt32 nsNavBookmarks::kFindBookmarksIndex_ID = 0; const PRInt32 nsNavBookmarks::kFindBookmarksIndex_Type = 1; @@ -88,7 +89,7 @@ nsNavBookmarks* nsNavBookmarks::sInstance = nsnull; nsNavBookmarks::nsNavBookmarks() : mItemCount(0), mRoot(0), mBookmarksRoot(0), mTagRoot(0), mToolbarFolder(0), mBatchLevel(0), - mBatchHasTransaction(PR_FALSE) + mBatchHasTransaction(PR_FALSE), mCanNotify(false), mCacheObservers("bookmark-observers") { NS_ASSERTION(!sInstance, "Multiple nsNavBookmarks instances!"); sInstance = this; @@ -125,6 +126,8 @@ nsNavBookmarks::Init() rv = transaction.Commit(); NS_ENSURE_SUCCESS(rv, rv); + mCanNotify = true; + // Add observers nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService(); NS_ENSURE_TRUE(annosvc, NS_ERROR_OUT_OF_MEMORY); @@ -1106,7 +1109,7 @@ nsNavBookmarks::InsertBookmark(PRInt64 aFolder, nsIURI *aItem, PRInt32 aIndex, AddBookmarkToHash(childID, 0); - ENUMERATE_WEAKARRAY(mObservers, nsINavBookmarkObserver, + ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemAdded(rowId, aFolder, index)) // If the bookmark has been added to a tag container, notify all @@ -1124,7 +1127,7 @@ nsNavBookmarks::InsertBookmark(PRInt64 aFolder, nsIURI *aItem, PRInt32 aIndex, if (bookmarks.Length()) { for (PRUint32 i = 0; i < bookmarks.Length(); i++) { - ENUMERATE_WEAKARRAY(mObservers, nsINavBookmarkObserver, + ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemChanged(bookmarks[i], NS_LITERAL_CSTRING("tags"), PR_FALSE, EmptyCString())) } @@ -1172,7 +1175,7 @@ nsNavBookmarks::RemoveItem(PRInt64 aItemId) return NS_OK; } - ENUMERATE_WEAKARRAY(mObservers, nsINavBookmarkObserver, + ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnBeforeItemRemoved(aItemId)) mozStorageTransaction transaction(mDBConn, PR_FALSE); @@ -1211,7 +1214,7 @@ nsNavBookmarks::RemoveItem(PRInt64 aItemId) NS_ENSURE_SUCCESS(rv, rv); } - ENUMERATE_WEAKARRAY(mObservers, nsINavBookmarkObserver, + ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemRemoved(aItemId, folderId, childIndex)) if (itemType == TYPE_BOOKMARK) { @@ -1232,7 +1235,7 @@ nsNavBookmarks::RemoveItem(PRInt64 aItemId) if (bookmarks.Length()) { for (PRUint32 i = 0; i < bookmarks.Length(); i++) { - ENUMERATE_WEAKARRAY(mObservers, nsINavBookmarkObserver, + ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemChanged(bookmarks[i], NS_LITERAL_CSTRING("tags"), PR_FALSE, EmptyCString())) } @@ -1381,7 +1384,7 @@ nsNavBookmarks::CreateContainerWithID(PRInt64 aItemId, PRInt64 aParent, rv = transaction.Commit(); NS_ENSURE_SUCCESS(rv, rv); - ENUMERATE_WEAKARRAY(mObservers, nsINavBookmarkObserver, + ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemAdded(id, aParent, index)) *aIndex = index; @@ -1446,7 +1449,7 @@ nsNavBookmarks::InsertSeparator(PRInt64 aParent, PRInt32 aIndex, rv = transaction.Commit(); NS_ENSURE_SUCCESS(rv, rv); - ENUMERATE_WEAKARRAY(mObservers, nsINavBookmarkObserver, + ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemAdded(rowId, aParent, index)) return NS_OK; @@ -1583,7 +1586,7 @@ nsNavBookmarks::RemoveFolder(PRInt64 aFolderId) { NS_ENSURE_TRUE(aFolderId != mRoot, NS_ERROR_INVALID_ARG); - ENUMERATE_WEAKARRAY(mObservers, nsINavBookmarkObserver, + ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnBeforeItemRemoved(aFolderId)) mozStorageTransaction transaction(mDBConn, PR_FALSE); @@ -1657,7 +1660,7 @@ nsNavBookmarks::RemoveFolder(PRInt64 aFolderId) mToolbarFolder = 0; } - ENUMERATE_WEAKARRAY(mObservers, nsINavBookmarkObserver, + ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemRemoved(aFolderId, parent, index)) return NS_OK; @@ -1775,7 +1778,7 @@ nsNavBookmarks::RemoveFolderChildren(PRInt64 aFolderId) folderChildrenInfo child = folderChildrenArray[i]; // Notify observers that we are about to remove this child. - ENUMERATE_WEAKARRAY(mObservers, nsINavBookmarkObserver, + ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnBeforeItemRemoved(child.itemId)) if (child.itemType == TYPE_FOLDER) { @@ -1848,7 +1851,7 @@ nsNavBookmarks::RemoveFolderChildren(PRInt64 aFolderId) for (PRInt32 i = folderChildrenArray.Length() - 1; i >= 0 ; i--) { folderChildrenInfo child = folderChildrenArray[i]; - ENUMERATE_WEAKARRAY(mObservers, nsINavBookmarkObserver, + ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemRemoved(child.itemId, child.parentId, child.index)); @@ -1869,7 +1872,7 @@ nsNavBookmarks::RemoveFolderChildren(PRInt64 aFolderId) if (bookmarks.Length()) { for (PRUint32 i = 0; i < bookmarks.Length(); i++) { - ENUMERATE_WEAKARRAY(mObservers, nsINavBookmarkObserver, + ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemChanged(bookmarks[i], NS_LITERAL_CSTRING("tags"), PR_FALSE, EmptyCString())) } @@ -2026,7 +2029,7 @@ nsNavBookmarks::MoveItem(PRInt64 aItemId, PRInt64 aNewParent, PRInt32 aIndex) NS_ENSURE_SUCCESS(rv, rv); // notify bookmark observers - ENUMERATE_WEAKARRAY(mObservers, nsINavBookmarkObserver, + ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemMoved(aItemId, oldParent, oldIndex, aNewParent, newIndex)) @@ -2101,7 +2104,7 @@ nsNavBookmarks::SetItemDateAdded(PRInt64 aItemId, PRTime aDateAdded) nsresult rv = SetItemDateInternal(mDBSetItemDateAdded, aItemId, aDateAdded); NS_ENSURE_SUCCESS(rv, rv); - ENUMERATE_WEAKARRAY(mObservers, nsINavBookmarkObserver, + ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemChanged(aItemId, NS_LITERAL_CSTRING("dateAdded"), PR_FALSE, nsPrintfCString(16, "%lld", aDateAdded))); return NS_OK; @@ -2132,7 +2135,7 @@ nsNavBookmarks::SetItemLastModified(PRInt64 aItemId, PRTime aLastModified) nsresult rv = SetItemDateInternal(mDBSetItemLastModified, aItemId, aLastModified); NS_ENSURE_SUCCESS(rv, rv); - ENUMERATE_WEAKARRAY(mObservers, nsINavBookmarkObserver, + ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemChanged(aItemId, NS_LITERAL_CSTRING("lastModified"), PR_FALSE, nsPrintfCString(16, "%lld", aLastModified))); return NS_OK; @@ -2255,7 +2258,7 @@ nsNavBookmarks::SetItemTitle(PRInt64 aItemId, const nsACString &aTitle) rv = statement->Execute(); NS_ENSURE_SUCCESS(rv, rv); - ENUMERATE_WEAKARRAY(mObservers, nsINavBookmarkObserver, + ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemChanged(aItemId, NS_LITERAL_CSTRING("title"), PR_FALSE, aTitle)); return NS_OK; @@ -2633,7 +2636,7 @@ nsNavBookmarks::ChangeBookmarkURI(PRInt64 aBookmarkId, nsIURI *aNewURI) NS_ENSURE_SUCCESS(rv, rv); // Pass the new URI to OnItemChanged. - ENUMERATE_WEAKARRAY(mObservers, nsINavBookmarkObserver, + ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemChanged(aBookmarkId, NS_LITERAL_CSTRING("uri"), PR_FALSE, spec)) return NS_OK; @@ -2760,11 +2763,11 @@ nsNavBookmarks::SetItemIndex(PRInt64 aItemId, PRInt32 aNewIndex) // XXX (bug 484096) this is really inefficient and we should look into using // onItemChanged here! - ENUMERATE_WEAKARRAY(mObservers, nsINavBookmarkObserver, + ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnBeforeItemRemoved(aItemId)) - ENUMERATE_WEAKARRAY(mObservers, nsINavBookmarkObserver, + ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemRemoved(aItemId, parent, oldIndex)) - ENUMERATE_WEAKARRAY(mObservers, nsINavBookmarkObserver, + ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemAdded(aItemId, parent, aNewIndex)) return NS_OK; @@ -2850,7 +2853,7 @@ nsNavBookmarks::SetKeywordForBookmark(PRInt64 aBookmarkId, const nsAString& aKey transaction.Commit(); // Pass the new keyword to OnItemChanged. - ENUMERATE_WEAKARRAY(mObservers, nsINavBookmarkObserver, + ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemChanged(aBookmarkId, NS_LITERAL_CSTRING("keyword"), PR_FALSE, NS_ConvertUTF16toUTF8(aKeyword))) @@ -2936,7 +2939,7 @@ nsNavBookmarks::BeginUpdateBatch() if (mBatchHasTransaction) conn->BeginTransaction(); - ENUMERATE_WEAKARRAY(mObservers, nsINavBookmarkObserver, + ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnBeginUpdateBatch()) } return NS_OK; @@ -2949,7 +2952,7 @@ nsNavBookmarks::EndUpdateBatch() if (mBatchHasTransaction) mDBConn->CommitTransaction(); mBatchHasTransaction = PR_FALSE; - ENUMERATE_WEAKARRAY(mObservers, nsINavBookmarkObserver, + ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnEndUpdateBatch()) } return NS_OK; @@ -3022,7 +3025,7 @@ nsNavBookmarks::OnVisit(nsIURI *aURI, PRInt64 aVisitID, PRTime aTime, if (bookmarks.Length()) { for (PRUint32 i = 0; i < bookmarks.Length(); i++) - ENUMERATE_WEAKARRAY(mObservers, nsINavBookmarkObserver, + ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemVisited(bookmarks[i], aVisitID, aTime)) } } @@ -3050,7 +3053,7 @@ nsNavBookmarks::OnDeleteURI(nsIURI *aURI) if (bookmarks.Length()) { for (PRUint32 i = 0; i < bookmarks.Length(); i ++) - ENUMERATE_WEAKARRAY(mObservers, nsINavBookmarkObserver, + ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemChanged(bookmarks[i], NS_LITERAL_CSTRING("cleartime"), PR_FALSE, EmptyCString())) } @@ -3099,7 +3102,7 @@ nsNavBookmarks::OnPageChanged(nsIURI *aURI, PRUint32 aWhat, NS_ENSURE_STATE(queries.Count() == 1); NS_ENSURE_STATE(queries[0]->Folders().Length() == 1); - ENUMERATE_WEAKARRAY(mObservers, nsINavBookmarkObserver, + ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemChanged(queries[0]->Folders()[0], NS_LITERAL_CSTRING("favicon"), PR_FALSE, NS_ConvertUTF16toUTF8(aValue))); } @@ -3111,7 +3114,7 @@ nsNavBookmarks::OnPageChanged(nsIURI *aURI, PRUint32 aWhat, if (bookmarks.Length()) { for (PRUint32 i = 0; i < bookmarks.Length(); i ++) - ENUMERATE_WEAKARRAY(mObservers, nsINavBookmarkObserver, + ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemChanged(bookmarks[i], NS_LITERAL_CSTRING("favicon"), PR_FALSE, NS_ConvertUTF16toUTF8(aValue))); } @@ -3142,7 +3145,7 @@ nsNavBookmarks::OnItemAnnotationSet(PRInt64 aItemId, const nsACString& aName) nsresult rv = SetItemDateInternal(mDBSetItemLastModified, aItemId, PR_Now()); NS_ENSURE_SUCCESS(rv, rv); - ENUMERATE_WEAKARRAY(mObservers, nsINavBookmarkObserver, + ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemChanged(aItemId, aName, PR_TRUE, EmptyCString())); return NS_OK; @@ -3161,7 +3164,7 @@ nsNavBookmarks::OnItemAnnotationRemoved(PRInt64 aItemId, const nsACString& aName nsresult rv = SetItemDateInternal(mDBSetItemLastModified, aItemId, PR_Now()); NS_ENSURE_SUCCESS(rv, rv); - ENUMERATE_WEAKARRAY(mObservers, nsINavBookmarkObserver, + ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver, OnItemChanged(aItemId, aName, PR_TRUE, EmptyCString())); return NS_OK; diff --git a/toolkit/components/places/src/nsNavBookmarks.h b/toolkit/components/places/src/nsNavBookmarks.h index 86eb42827550..d79d92ca9197 100644 --- a/toolkit/components/places/src/nsNavBookmarks.h +++ b/toolkit/components/places/src/nsNavBookmarks.h @@ -45,6 +45,7 @@ #include "nsNavHistory.h" #include "nsNavHistoryResult.h" // need for Int64 hashtable #include "nsToolkitCompsCID.h" +#include "nsCategoryCache.h" class nsIOutputStream; @@ -296,6 +297,10 @@ private: nsString mType; PRInt32 mIndex; }; + + // Used to enable and disable the observer notifications. + bool mCanNotify; + nsCategoryCache mCacheObservers; }; struct nsBookmarksUpdateBatcher diff --git a/toolkit/components/places/src/nsNavHistory.cpp b/toolkit/components/places/src/nsNavHistory.cpp index eaebf7d6f612..f4b1fb0bb2e7 100644 --- a/toolkit/components/places/src/nsNavHistory.cpp +++ b/toolkit/components/places/src/nsNavHistory.cpp @@ -53,6 +53,7 @@ #include "nsPlacesTables.h" #include "nsPlacesIndexes.h" #include "nsPlacesTriggers.h" +#include "nsPlacesMacros.h" #include "nsIArray.h" #include "nsTArray.h" @@ -444,7 +445,9 @@ nsNavHistory::nsNavHistory() : mBatchLevel(0), mNumVisitsForFrecency(10), mTagsFolder(-1), mInPrivateBrowsing(PRIVATEBROWSING_NOTINITED), - mDatabaseStatus(DATABASE_STATUS_OK) + mDatabaseStatus(DATABASE_STATUS_OK), + mCanNotify(true), + mCacheObservers("history-observers") { #ifdef LAZY_ADD mLazyTimerSet = PR_TRUE; @@ -2883,7 +2886,7 @@ nsNavHistory::AddVisit(nsIURI* aURI, PRTime aTime, nsIURI* aReferringURI, PRUint32 added = 0; if (!hidden && aTransitionType != TRANSITION_EMBED && aTransitionType != TRANSITION_DOWNLOAD) { - ENUMERATE_WEAKARRAY(mObservers, nsINavHistoryObserver, + ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavHistoryObserver, OnVisit(aURI, *aVisitID, aTime, aSessionID, referringVisitID, aTransitionType, &added)); } @@ -4261,7 +4264,7 @@ nsNavHistory::BeginUpdateBatch() if (mBatchHasTransaction) mDBConn->BeginTransaction(); - ENUMERATE_WEAKARRAY(mObservers, nsINavHistoryObserver, + ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavHistoryObserver, OnBeginUpdateBatch()) } return NS_OK; @@ -4275,7 +4278,8 @@ nsNavHistory::EndUpdateBatch() if (mBatchHasTransaction) mDBConn->CommitTransaction(); mBatchHasTransaction = PR_FALSE; - ENUMERATE_WEAKARRAY(mObservers, nsINavHistoryObserver, OnEndUpdateBatch()) + ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavHistoryObserver, + OnEndUpdateBatch()) } return NS_OK; } @@ -4582,15 +4586,16 @@ nsNavHistory::RemovePage(nsIURI *aURI) NS_ENSURE_ARG(aURI); // Before we remove, we have to notify our observers! - ENUMERATE_WEAKARRAY(mObservers, nsINavHistoryObserver, - OnBeforeDeleteURI(aURI)) + ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, + nsINavHistoryObserver, OnBeforeDeleteURI(aURI)) nsIURI** URIs = &aURI; nsresult rv = RemovePages(URIs, 1, PR_FALSE); NS_ENSURE_SUCCESS(rv, rv); // Notify our observers that the URI has been removed. - ENUMERATE_WEAKARRAY(mObservers, nsINavHistoryObserver, OnDeleteURI(aURI)) + ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, + nsINavHistoryObserver, OnDeleteURI(aURI)) return NS_OK; } @@ -4950,7 +4955,7 @@ nsNavHistory::HidePage(nsIURI *aURI) // notify observers, finish transaction first transaction.Commit(); - ENUMERATE_WEAKARRAY(mObservers, nsINavHistoryObserver, + ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavHistoryObserver, OnPageChanged(aURI, nsINavHistoryObserver::ATTRIBUTE_HIDDEN, EmptyString())) @@ -5609,7 +5614,7 @@ NS_IMETHODIMP nsNavHistory::NotifyOnPageExpired(nsIURI *aURI, PRTime aVisitTime, PRBool aWholeEntry) { - ENUMERATE_WEAKARRAY(mObservers, nsINavHistoryObserver, + ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavHistoryObserver, OnPageExpired(aURI, aVisitTime, aWholeEntry)); return NS_OK; } @@ -6858,6 +6863,14 @@ nsNavHistory::BookmarkIdToResultNode(PRInt64 aBookmarkId, nsNavHistoryQueryOptio return RowToResult(stmt, aOptions, aResult); } +void +nsNavHistory::SendPageChangedNotification(nsIURI* aURI, PRUint32 aWhat, + const nsAString& aValue) +{ + ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavHistoryObserver, + OnPageChanged(aURI, aWhat, aValue)); +} + // nsNavHistory::TitleForDomain // // This computes the title for a given domain. Normally, this is just the @@ -6987,11 +7000,10 @@ nsNavHistory::SetPageTitleInternal(nsIURI* aURI, const nsAString& aTitle) NS_ENSURE_SUCCESS(rv, rv); // observers (have to check first if it's bookmarked) - ENUMERATE_WEAKARRAY(mObservers, nsINavHistoryObserver, + ENUMERATE_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavHistoryObserver, OnTitleChanged(aURI, aTitle)) return NS_OK; - } nsresult diff --git a/toolkit/components/places/src/nsNavHistory.h b/toolkit/components/places/src/nsNavHistory.h index 7bffb3683c82..25874a0a3602 100644 --- a/toolkit/components/places/src/nsNavHistory.h +++ b/toolkit/components/places/src/nsNavHistory.h @@ -82,6 +82,7 @@ #include "nsTArray.h" #include "nsINavBookmarksService.h" #include "nsMaybeWeakPtr.h" +#include "nsCategoryCache.h" #include "nsNavHistoryExpire.h" #include "nsNavHistoryResult.h" @@ -303,11 +304,7 @@ public: // used by other places components to send history notifications (for example, // when the favicon has changed) void SendPageChangedNotification(nsIURI* aURI, PRUint32 aWhat, - const nsAString& aValue) - { - ENUMERATE_WEAKARRAY(mObservers, nsINavHistoryObserver, - OnPageChanged(aURI, aWhat, aValue)); - } + const nsAString& aValue); // current time optimization PRTime GetNow(); @@ -397,6 +394,12 @@ public: return NS_OK; } + /** + * Indicates if it is OK to notify history observers or not. + * + * @returns true if it is OK to notify, false otherwise. + */ + bool canNotify() { return mCanNotify; } private: ~nsNavHistory(); @@ -817,6 +820,10 @@ protected: PRBool mInPrivateBrowsing; PRUint16 mDatabaseStatus; + + // Used to enable and disable the observer notifications + bool mCanNotify; + nsCategoryCache mCacheObservers; }; /** diff --git a/toolkit/components/places/src/nsNavHistoryExpire.cpp b/toolkit/components/places/src/nsNavHistoryExpire.cpp index c9a70bf26111..c48b066664dc 100644 --- a/toolkit/components/places/src/nsNavHistoryExpire.cpp +++ b/toolkit/components/places/src/nsNavHistoryExpire.cpp @@ -47,6 +47,7 @@ #include "nsNetUtil.h" #include "nsIAnnotationService.h" #include "nsPrintfCString.h" +#include "nsPlacesMacros.h" struct nsNavHistoryExpireRecord { nsNavHistoryExpireRecord(mozIStorageStatement* statement); @@ -279,7 +280,8 @@ nsNavHistoryExpire::ClearHistory() // forcibly call the "on idle" timer here to do a little work // but the rest will happen on idle. - ENUMERATE_WEAKARRAY(mHistory->mObservers, nsINavHistoryObserver, + ENUMERATE_OBSERVERS(mHistory->canNotify(), mHistory->mCacheObservers, + mHistory->mObservers, nsINavHistoryObserver, OnClearHistory()) return NS_OK; @@ -384,7 +386,8 @@ nsNavHistoryExpire::ExpireItems(PRUint32 aNumToExpire, PRBool* aKeepGoing) // FIXME bug 325241 provide a way to observe hidden elements if (expiredVisits[i].hidden) continue; - ENUMERATE_WEAKARRAY(mHistory->mObservers, nsINavHistoryObserver, + ENUMERATE_OBSERVERS(mHistory->canNotify(), mHistory->mCacheObservers, + mHistory->mObservers, nsINavHistoryObserver, OnPageExpired(uri, expiredVisits[i].visitDate, expiredVisits[i].erased)); } diff --git a/toolkit/components/places/src/nsPlacesDBFlush.js b/toolkit/components/places/src/nsPlacesDBFlush.js index 809c2764167a..f32e4131a80b 100644 --- a/toolkit/components/places/src/nsPlacesDBFlush.js +++ b/toolkit/components/places/src/nsPlacesDBFlush.js @@ -103,10 +103,6 @@ function nsPlacesDBFlush() } // Register observers - this._bs = Cc["@mozilla.org/browser/nav-bookmarks-service;1"]. - getService(Ci.nsINavBookmarksService); - this._bs.addObserver(this, false); - this._os = Cc["@mozilla.org/observer-service;1"]. getService(Ci.nsIObserverService); this._os.addObserver(this, kQuitApplication, false); @@ -140,6 +136,12 @@ function nsPlacesDBFlush() return this._hsn = Cc["@mozilla.org/browser/nav-history-service;1"]. getService(Ci.nsPIPlacesHistoryListenersNotifier); }); + + this.__defineGetter__("_bs", function() { + delete this._bs; + return this._bs = Cc["@mozilla.org/browser/nav-bookmarks-service;1"]. + getService(Ci.nsINavBookmarksService); + }); } nsPlacesDBFlush.prototype = { @@ -149,7 +151,6 @@ nsPlacesDBFlush.prototype = { observe: function DBFlush_observe(aSubject, aTopic, aData) { if (aTopic == kQuitApplication) { - this._bs.removeObserver(this); this._os.removeObserver(this, kQuitApplication); let (pb2 = this._prefs.QueryInterface(Ci.nsIPrefBranch2)) { pb2.removeObserver(kSyncPrefName, this); @@ -245,6 +246,24 @@ nsPlacesDBFlush.prototype = { onItemVisited: function() { }, onItemMoved: function() { }, + ////////////////////////////////////////////////////////////////////////////// + //// nsINavHistoryObserver + + // We currently only use the history observer to know when the history service + // is activated. At that point, we actually get initialized, and our timer + // to sync history is added. + + // These methods share the name of the ones on nsINavBookmarkObserver, so + // the implementations can be found above. + //onBeginUpdateBatch: function() { }, + //onEndUpdateBatch: function() { }, + onVisit: function(aURI, aVisitID, aTime, aSessionID, aReferringID, aTransitionType) { }, + onTitleChanged: function(aURI, aPageTitle) { }, + onDeleteURI: function(aURI) { }, + onClearHistory: function() { }, + onPageChanged: function(aURI, aWhat, aValue) { }, + onPageExpired: function(aURI, aVisitTime, aWholeEntry) { }, + ////////////////////////////////////////////////////////////////////////////// //// nsITimerCallback @@ -458,13 +477,18 @@ nsPlacesDBFlush.prototype = { classDescription: "Used to synchronize the temporary and permanent tables of Places", classID: Components.ID("c1751cfc-e8f1-4ade-b0bb-f74edfb8ef6a"), contractID: "@mozilla.org/places/sync;1", - _xpcom_categories: [{ - category: "profile-after-change", - }], + + // Registering in these categories makes us get initialized when either of + // those listeners would be notified. + _xpcom_categories: [ + { category: "bookmark-observers" }, + { category: "history-observers" }, + ], QueryInterface: XPCOMUtils.generateQI([ Ci.nsIObserver, Ci.nsINavBookmarkObserver, + Ci.nsINavHistoryObserver, Ci.nsITimerCallback, Ci.mozIStorageStatementCallback, ]) diff --git a/toolkit/components/places/src/nsPlacesMacros.h b/toolkit/components/places/src/nsPlacesMacros.h new file mode 100644 index 000000000000..aa487e1754df --- /dev/null +++ b/toolkit/components/places/src/nsPlacesMacros.h @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** 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. + * + * The Initial Developer of the Original Code is Mozilla Corporation. + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Mark Finkle (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 ***** */ + +#include "prtypes.h" +// Call a method on each observer in a category cache, then call the same +// method on the observer array. + +#define ENUMERATE_OBSERVERS(canFire, cache, array, type, method) \ + PR_BEGIN_MACRO \ + if (canFire) { \ + const nsCOMArray &entries = cache.GetEntries(); \ + for (PRInt32 idx = 0; idx < entries.Count(); ++idx) \ + entries[idx]->method; \ + ENUMERATE_WEAKARRAY(array, type, method) \ + } \ + PR_END_MACRO; diff --git a/toolkit/components/places/tests/autocomplete/head_000.js b/toolkit/components/places/tests/autocomplete/head_000.js index 0423d71eaa95..33a8aea4ec38 100644 --- a/toolkit/components/places/tests/autocomplete/head_000.js +++ b/toolkit/components/places/tests/autocomplete/head_000.js @@ -139,3 +139,13 @@ function dump_table(aName) stmt.finalize(); stmt = null; } + +/** + * Flushes any events in the event loop of the main thread. + */ +function flush_main_thread_events() +{ + let tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager); + while (tm.mainThread.hasPendingEvents()) + tm.mainThread.processNextEvent(false); +} diff --git a/toolkit/components/places/tests/autocomplete/tail_autocomplete.js b/toolkit/components/places/tests/autocomplete/tail_autocomplete.js new file mode 100644 index 000000000000..820caf6a79bb --- /dev/null +++ b/toolkit/components/places/tests/autocomplete/tail_autocomplete.js @@ -0,0 +1,65 @@ +/* ***** 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. + * + * The Initial Developer of the Original Code is + * Mozilla.org + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Marco Bonardo + * + * 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 ***** */ + +// put cleanup of the bookmarks test here. + +// Run the event loop to be more like the browser, which normally runs the +// event loop long before code like this would run. +// Not doing so could cause us to close the connection before all tasks have +// been completed, and that would crash badly. +flush_main_thread_events(); + +// XPCShell doesn't dispatch quit-application, to ensure cleanup we have to +// dispatch it after each test run. +var os = Cc['@mozilla.org/observer-service;1']. + getService(Ci.nsIObserverService); +os.notifyObservers(null, "quit-application-granted", null); +os.notifyObservers(null, "quit-application", null); + +// Run the event loop, since we enqueue some statement finalization. +flush_main_thread_events(); + +// try to close the connection so we can remove places.sqlite +var pip = Cc["@mozilla.org/browser/nav-history-service;1"]. + getService(Ci.nsINavHistoryService). + QueryInterface(Ci.nsPIPlacesDatabase); +if (pip.DBConnection.connectionReady) { + pip.commitPendingChanges(); + pip.finalizeInternalStatements(); + pip.DBConnection.close(); + do_check_false(pip.DBConnection.connectionReady); +} diff --git a/toolkit/components/places/tests/bookmarks/head_bookmarks.js b/toolkit/components/places/tests/bookmarks/head_bookmarks.js index 8e694569bfe7..8ed221ebfd5e 100644 --- a/toolkit/components/places/tests/bookmarks/head_bookmarks.js +++ b/toolkit/components/places/tests/bookmarks/head_bookmarks.js @@ -94,6 +94,59 @@ function clearDB() { } clearDB(); +/** + * Dumps the rows of a table out to the console. + * + * @param aName + * The name of the table or view to output. + */ +function dump_table(aName) +{ + let db = Cc["@mozilla.org/browser/nav-history-service;1"]. + getService(Ci.nsPIPlacesDatabase). + DBConnection; + let stmt = db.createStatement("SELECT * FROM " + aName); + + dump("\n*** Printing data from " + aName + ":\n"); + let count = 0; + while (stmt.executeStep()) { + let columns = stmt.numEntries; + + if (count == 0) { + // print the column names + for (let i = 0; i < columns; i++) + dump(stmt.getColumnName(i) + "\t"); + dump("\n"); + } + + // print the row + for (let i = 0; i < columns; i++) { + switch (stmt.getTypeOfIndex(i)) { + case Ci.mozIStorageValueArray.VALUE_TYPE_NULL: + dump("NULL\t"); + break; + case Ci.mozIStorageValueArray.VALUE_TYPE_INTEGER: + dump(stmt.getInt64(i) + "\t"); + break; + case Ci.mozIStorageValueArray.VALUE_TYPE_FLOAT: + dump(stmt.getDouble(i) + "\t"); + break; + case Ci.mozIStorageValueArray.VALUE_TYPE_TEXT: + dump(stmt.getString(i) + "\t"); + break; + } + } + dump("\n"); + + count++; + } + dump("*** There were a total of " + count + " rows of data.\n\n"); + + stmt.reset(); + stmt.finalize(); + stmt = null; +} + /* * Removes all bookmarks and checks for correct cleanup */ @@ -126,3 +179,13 @@ function check_no_bookmarks() { do_check_eq(root.childCount, 0); root.containerOpen = false; } + +/** + * Flushes any events in the event loop of the main thread. + */ +function flush_main_thread_events() +{ + let tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager); + while (tm.mainThread.hasPendingEvents()) + tm.mainThread.processNextEvent(false); +} diff --git a/toolkit/components/places/tests/bookmarks/tail_bookmarks.js b/toolkit/components/places/tests/bookmarks/tail_bookmarks.js index 5165a840b1d3..116ba5054cb3 100644 --- a/toolkit/components/places/tests/bookmarks/tail_bookmarks.js +++ b/toolkit/components/places/tests/bookmarks/tail_bookmarks.js @@ -20,6 +20,7 @@ * * Contributor(s): * Dietrich Ayala + * Marco Bonardo * * 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 @@ -37,5 +38,29 @@ // put cleanup of the bookmarks test here. -// remove bookmarks file -//clearDB(); +// Run the event loop to be more like the browser, which normally runs the +// event loop long before code like this would run. +// Not doing so could cause us to close the connection before all tasks have +// been completed, and that would crash badly. +flush_main_thread_events(); + +// XPCShell doesn't dispatch quit-application, to ensure cleanup we have to +// dispatch it after each test run. +var os = Cc['@mozilla.org/observer-service;1']. + getService(Ci.nsIObserverService); +os.notifyObservers(null, "quit-application-granted", null); +os.notifyObservers(null, "quit-application", null); + +// Run the event loop, since we enqueue some statement finalization. +flush_main_thread_events(); + +// try to close the connection so we can remove places.sqlite +var pip = Cc["@mozilla.org/browser/nav-history-service;1"]. + getService(Ci.nsINavHistoryService). + QueryInterface(Ci.nsPIPlacesDatabase); +if (pip.DBConnection.connectionReady) { + pip.commitPendingChanges(); + pip.finalizeInternalStatements(); + pip.DBConnection.close(); + do_check_false(pip.DBConnection.connectionReady); +} diff --git a/toolkit/components/places/tests/bookmarks/test_384228.js b/toolkit/components/places/tests/bookmarks/test_384228.js index 30816cdf0765..480e56a46308 100644 --- a/toolkit/components/places/tests/bookmarks/test_384228.js +++ b/toolkit/components/places/tests/bookmarks/test_384228.js @@ -59,24 +59,34 @@ function run_test() { // test querying for bookmarks in multiple folders var testFolder1 = bmsvc.createFolder(root, "bug 384228 test folder 1", bmsvc.DEFAULT_INDEX); + do_check_eq(bmsvc.getItemIndex(testFolder1), 0); var testFolder2 = bmsvc.createFolder(root, "bug 384228 test folder 2", bmsvc.DEFAULT_INDEX); + do_check_eq(bmsvc.getItemIndex(testFolder2), 1); var testFolder3 = bmsvc.createFolder(root, "bug 384228 test folder 3", bmsvc.DEFAULT_INDEX); + do_check_eq(bmsvc.getItemIndex(testFolder3), 2); var b1 = bmsvc.insertBookmark(testFolder1, uri("http://foo.tld/"), bmsvc.DEFAULT_INDEX, "title b1 (folder 1)"); + do_check_eq(bmsvc.getItemIndex(b1), 0); var b2 = bmsvc.insertBookmark(testFolder1, uri("http://foo.tld/"), bmsvc.DEFAULT_INDEX, "title b2 (folder 1)"); + do_check_eq(bmsvc.getItemIndex(b2), 1); var b3 = bmsvc.insertBookmark(testFolder2, uri("http://foo.tld/"), bmsvc.DEFAULT_INDEX, "title b3 (folder 2)"); + do_check_eq(bmsvc.getItemIndex(b3), 0); var b4 = bmsvc.insertBookmark(testFolder3, uri("http://foo.tld/"), bmsvc.DEFAULT_INDEX, "title b4 (folder 3)"); + do_check_eq(bmsvc.getItemIndex(b4), 0); // also test recursive search var testFolder1_1 = bmsvc.createFolder(testFolder1, "bug 384228 test folder 1.1", bmsvc.DEFAULT_INDEX); + do_check_eq(bmsvc.getItemIndex(testFolder1_1), 2); var b5 = bmsvc.insertBookmark(testFolder1_1, uri("http://a1.com/"), bmsvc.DEFAULT_INDEX, "title b5 (folder 1.1)"); + do_check_eq(bmsvc.getItemIndex(b5), 0); + var options = histsvc.getNewQueryOptions(); var query = histsvc.getNewQuery(); query.searchTerms = "title"; @@ -94,4 +104,6 @@ function run_test() { do_check_eq(rootNode.getChild(1).itemId, b2); do_check_eq(rootNode.getChild(2).itemId, b3); do_check_eq(rootNode.getChild(3).itemId, b5); + + rootNode.containerOpen = false; } diff --git a/toolkit/components/places/tests/bookmarks/test_448584.js b/toolkit/components/places/tests/bookmarks/test_448584.js index e9359931535a..afa229977088 100644 --- a/toolkit/components/places/tests/bookmarks/test_448584.js +++ b/toolkit/components/places/tests/bookmarks/test_448584.js @@ -121,7 +121,11 @@ function run_test() { var sql = "UPDATE moz_bookmarks SET fk = 1337 WHERE id = ?1"; var stmt = mDBConn.createStatement(sql); stmt.bindUTF8StringParameter(0, aTest._itemId); - stmt.execute(); + try { + stmt.execute(); + } finally { + stmt.finalize(); + } }); // export json to file diff --git a/toolkit/components/places/tests/queries/head_queries.js b/toolkit/components/places/tests/queries/head_queries.js index 318e54f248f8..d9767cd0ed45 100644 --- a/toolkit/components/places/tests/queries/head_queries.js +++ b/toolkit/components/places/tests/queries/head_queries.js @@ -533,3 +533,13 @@ function dump_table(aName) stmt.finalize(); stmt = null; } + +/** + * Flushes any events in the event loop of the main thread. + */ +function flush_main_thread_events() +{ + let tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager); + while (tm.mainThread.hasPendingEvents()) + tm.mainThread.processNextEvent(false); +} diff --git a/toolkit/components/places/tests/queries/tail_queries.js b/toolkit/components/places/tests/queries/tail_queries.js index f4488b62c215..bc8f7042da13 100644 --- a/toolkit/components/places/tests/queries/tail_queries.js +++ b/toolkit/components/places/tests/queries/tail_queries.js @@ -1 +1,62 @@ -clearDB(); +/* ***** 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. + * + * The Initial Developer of the Original Code is + * Mozilla.org + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Marco Bonardo + * + * 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 ***** */ + +// put cleanup of the bookmarks test here. + +// XPCShell doesn't dispatch quit-application, to ensure cleanup we have to +// dispatch it after each test run. +var os = Cc['@mozilla.org/observer-service;1']. + getService(Ci.nsIObserverService); +os.notifyObservers(null, "quit-application-granted", null); +os.notifyObservers(null, "quit-application", null); + +// Run the event loop to be more like the browser, which normally runs the +// event loop long before code like this would run. +// Not doing so could cause us to close the connection before all tasks have +// been completed, and that would crash badly. +flush_main_thread_events(); + +// try to close the connection so we can remove places.sqlite +var pip = Cc["@mozilla.org/browser/nav-history-service;1"]. + getService(Ci.nsINavHistoryService). + QueryInterface(Ci.nsPIPlacesDatabase); +if (pip.DBConnection.connectionReady) { + pip.commitPendingChanges(); + pip.finalizeInternalStatements(); + pip.DBConnection.close(); + do_check_false(pip.DBConnection.connectionReady); +} diff --git a/toolkit/components/places/tests/sync/head_sync.js b/toolkit/components/places/tests/sync/head_sync.js index b00967730465..9d0ba3f7ee38 100644 --- a/toolkit/components/places/tests/sync/head_sync.js +++ b/toolkit/components/places/tests/sync/head_sync.js @@ -272,6 +272,12 @@ function DBConn() return dbConn; } -// profile-after-change doesn't create components in xpcshell, so we have to do -// it ourselves -Cc["@mozilla.org/places/sync;1"].getService(Ci.nsISupports); +/** + * Flushes any events in the event loop of the main thread. + */ +function flush_main_thread_events() +{ + let tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager); + while (tm.mainThread.hasPendingEvents()) + tm.mainThread.processNextEvent(false); +} diff --git a/toolkit/components/places/tests/sync/tail_sync.js b/toolkit/components/places/tests/sync/tail_sync.js new file mode 100644 index 000000000000..820caf6a79bb --- /dev/null +++ b/toolkit/components/places/tests/sync/tail_sync.js @@ -0,0 +1,65 @@ +/* ***** 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. + * + * The Initial Developer of the Original Code is + * Mozilla.org + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Marco Bonardo + * + * 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 ***** */ + +// put cleanup of the bookmarks test here. + +// Run the event loop to be more like the browser, which normally runs the +// event loop long before code like this would run. +// Not doing so could cause us to close the connection before all tasks have +// been completed, and that would crash badly. +flush_main_thread_events(); + +// XPCShell doesn't dispatch quit-application, to ensure cleanup we have to +// dispatch it after each test run. +var os = Cc['@mozilla.org/observer-service;1']. + getService(Ci.nsIObserverService); +os.notifyObservers(null, "quit-application-granted", null); +os.notifyObservers(null, "quit-application", null); + +// Run the event loop, since we enqueue some statement finalization. +flush_main_thread_events(); + +// try to close the connection so we can remove places.sqlite +var pip = Cc["@mozilla.org/browser/nav-history-service;1"]. + getService(Ci.nsINavHistoryService). + QueryInterface(Ci.nsPIPlacesDatabase); +if (pip.DBConnection.connectionReady) { + pip.commitPendingChanges(); + pip.finalizeInternalStatements(); + pip.DBConnection.close(); + do_check_false(pip.DBConnection.connectionReady); +} diff --git a/toolkit/components/places/tests/unit/head_bookmarks.js b/toolkit/components/places/tests/unit/head_bookmarks.js index 8733cad40cd2..4d22fb9d153b 100644 --- a/toolkit/components/places/tests/unit/head_bookmarks.js +++ b/toolkit/components/places/tests/unit/head_bookmarks.js @@ -206,3 +206,13 @@ function finish_test() os.notifyObservers(null, "quit-application", null); do_test_finished(); } + +/** + * Flushes any events in the event loop of the main thread. + */ +function flush_main_thread_events() +{ + let tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager); + while (tm.mainThread.hasPendingEvents()) + tm.mainThread.processNextEvent(false); +} diff --git a/toolkit/components/places/tests/unit/nsDummyObserver.js b/toolkit/components/places/tests/unit/nsDummyObserver.js new file mode 100644 index 000000000000..22678df3f9cb --- /dev/null +++ b/toolkit/components/places/tests/unit/nsDummyObserver.js @@ -0,0 +1,97 @@ +/* ***** 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 Corporation. + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Mark Finkle (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 ***** */ + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +const Cc = Components.classes; +const Ci = Components.interfaces; + +// Dummy boomark/history observer +function DummyObserver() { + let os = Cc["@mozilla.org/observer-service;1"]. + getService(Ci.nsIObserverService); + os.notifyObservers(null, "dummy-observer-created", null); +} + +DummyObserver.prototype = { + // history observer + onBeginUpdateBatch: function() {}, + onEndUpdateBatch: function() {}, + onVisit: function(aURI, aVisitID, aTime, aSessionID, aReferringID, aTransitionType) { + let os = Cc["@mozilla.org/observer-service;1"]. + getService(Ci.nsIObserverService); + os.notifyObservers(null, "dummy-observer-visited", null); + }, + onTitleChanged: function(aURI, aPageTitle) {}, + onDeleteURI: function(aURI) {}, + onClearHistory: function() {}, + onPageChanged: function(aURI, aWhat, aValue) {}, + onPageExpired: function(aURI, aVisitTime, aWholeEntry) {}, + + // bookmark observer + //onBeginUpdateBatch: function() {}, + //onEndUpdateBatch: function() {}, + onItemAdded: function(aItemId, aParentId, aIndex) { + let os = Cc["@mozilla.org/observer-service;1"]. + getService(Ci.nsIObserverService); + os.notifyObservers(null, "dummy-observer-item-added", null); + }, + onItemChanged: function (aItemId, aProperty, aIsAnnotationProperty, aValue) {}, + onBeforeItemRemoved: function() {}, + onItemRemoved: function() {}, + onItemVisited: function() {}, + onItemMoved: function() {}, + + classDescription: "Dummy observer used to test category observers", + classID: Components.ID("62e221d3-68c3-4e1a-8943-a27beb5005fe"), + contractID: "@mozilla.org/places/test/dummy-observer;1", + + // Registering in these categories makes us get initialized when either of + // those listeners would be notified. + _xpcom_categories: [ + { category: "bookmark-observers" }, + { category: "history-observers" } + ], + + QueryInterface: XPCOMUtils.generateQI([ + Ci.nsINavBookmarkObserver, + Ci.nsINavHistoryObserver, + ]) +}; + +function NSGetModule(compMgr, fileSpec) { + return XPCOMUtils.generateModule([DummyObserver]); +} diff --git a/toolkit/components/places/tests/unit/tail_bookmarks.js b/toolkit/components/places/tests/unit/tail_bookmarks.js index db9548ae64c4..06ebacd727d9 100644 --- a/toolkit/components/places/tests/unit/tail_bookmarks.js +++ b/toolkit/components/places/tests/unit/tail_bookmarks.js @@ -20,6 +20,7 @@ * * Contributor(s): * Dietrich Ayala + * Marco Bonardo * * 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 @@ -37,5 +38,30 @@ // put cleanup of the bookmarks test here. -// remove bookmarks file -clearDB(); +// Run the event loop to be more like the browser, which normally runs the +// event loop long before code like this would run. +// Not doing so could cause us to close the connection before all tasks have +// been completed, and that would crash badly. +flush_main_thread_events(); + +// XPCShell doesn't dispatch quit-application, to ensure cleanup we have to +// dispatch it after each test run. +var os = Cc['@mozilla.org/observer-service;1']. + getService(Ci.nsIObserverService); +os.notifyObservers(null, "quit-application-granted", null); +os.notifyObservers(null, "quit-application", null); + +// Run the event loop, since we enqueue some statement finalization. +flush_main_thread_events(); + +// try to close the connection so we can remove places.sqlite +var pip = Cc["@mozilla.org/browser/nav-history-service;1"]. + getService(Ci.nsINavHistoryService). + QueryInterface(Ci.nsPIPlacesDatabase); +if (pip.DBConnection.connectionReady) { + pip.commitPendingChanges(); + pip.finalizeInternalStatements(); + pip.DBConnection.close(); + do_check_false(pip.DBConnection.connectionReady); +} + diff --git a/toolkit/components/places/tests/unit/test_000_frecency.js b/toolkit/components/places/tests/unit/test_000_frecency.js index 8ac447943aae..984dcf4fd274 100644 --- a/toolkit/components/places/tests/unit/test_000_frecency.js +++ b/toolkit/components/places/tests/unit/test_000_frecency.js @@ -283,6 +283,7 @@ function run_test() { // undefined), so check if frecency matches. This is okay because we // can still ensure the correct number of expected frecencies. let getFrecency = function(aURL) aURL.match(/frecency:(-?\d+)$/)[1]; + print("### searchURL: '"+searchURL+"', expectedURL: '"+expectedURL+"'"); do_check_eq(getFrecency(searchURL), getFrecency(expectURL)); } } diff --git a/toolkit/components/places/tests/unit/test_421180.js b/toolkit/components/places/tests/unit/test_421180.js index 22b06d5752cd..e8309da02979 100644 --- a/toolkit/components/places/tests/unit/test_421180.js +++ b/toolkit/components/places/tests/unit/test_421180.js @@ -79,7 +79,7 @@ function run_test() { var stmt = mDBConn.createStatement(sql); stmt.bindUTF8StringParameter(0, keyword); do_check_false(stmt.executeStep()); - + stmt.finalize(); // TEST 2 // 1. add 2 bookmarks @@ -106,5 +106,5 @@ function run_test() { var stmt = mDBConn.createStatement(sql); stmt.bindUTF8StringParameter(0, keyword); do_check_true(stmt.executeStep()); - + stmt.finalize(); } diff --git a/toolkit/components/places/tests/unit/test_454977.js b/toolkit/components/places/tests/unit/test_454977.js index 68c63c96bcc4..57c0b028c60b 100644 --- a/toolkit/components/places/tests/unit/test_454977.js +++ b/toolkit/components/places/tests/unit/test_454977.js @@ -65,6 +65,7 @@ function add_visit(aURI, aVisitDate, aVisitType) { stmt.bindInt64Parameter(0, visitId); do_check_true(stmt.executeStep()); var placeId = stmt.getInt64(0); + stmt.finalize(); do_check_true(placeId > 0); return placeId; } diff --git a/toolkit/components/places/tests/unit/test_bookmark_catobs.js b/toolkit/components/places/tests/unit/test_bookmark_catobs.js new file mode 100644 index 000000000000..10cdd2349052 --- /dev/null +++ b/toolkit/components/places/tests/unit/test_bookmark_catobs.js @@ -0,0 +1,81 @@ +/* ***** 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 Corporation. + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Mark Finkle (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 ***** */ + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +// Get services. +let bs = Cc["@mozilla.org/browser/nav-bookmarks-service;1"]. + getService(Ci.nsINavBookmarksService); +let os = Cc["@mozilla.org/observer-service;1"]. + getService(Ci.nsIObserverService); + +let gDummyCreated = false; +let gDummyAdded = false; + +let observer = { + observe: function(subject, topic, data) { + if (topic == "dummy-observer-created") + gDummyCreated = true; + else if (topic == "dummy-observer-item-added") + gDummyAdded = true; + }, + + QueryInterface: XPCOMUtils.generateQI([ + Ci.nsIObserver, + Ci.nsISupportsWeakReference, + ]) +}; + +function verify() { + do_check_true(gDummyCreated); + do_check_true(gDummyAdded); + do_test_finished(); +} + +// main +function run_test() { + do_load_module("nsDummyObserver.js"); + + os.addObserver(observer, "dummy-observer-created", true); + os.addObserver(observer, "dummy-observer-item-added", true); + + // Add a bookmark + bs.insertBookmark(bs.unfiledBookmarksFolder, uri("http://typed.mozilla.org"), + bs.DEFAULT_INDEX, "bookmark"); + + do_test_pending(); + do_timeout(1000, "verify();"); +} diff --git a/toolkit/components/places/tests/unit/test_history.js b/toolkit/components/places/tests/unit/test_history.js index c0c6e53a54da..a2486561d2a0 100644 --- a/toolkit/components/places/tests/unit/test_history.js +++ b/toolkit/components/places/tests/unit/test_history.js @@ -198,11 +198,15 @@ function run_test() { // get direct db connection var db = histsvc.QueryInterface(Ci.nsPIPlacesDatabase).DBConnection; var q = "SELECT id FROM moz_bookmarks"; + var statement; try { - var statement = db.createStatement(q); + statement = db.createStatement(q); } catch(ex) { do_throw("bookmarks table does not have id field, schema is too old!"); } + finally { + statement.finalize(); + } // bug 394741 - regressed history text searches add_visit(uri("http://mozilla.com")); diff --git a/toolkit/components/places/tests/unit/test_history_catobs.js b/toolkit/components/places/tests/unit/test_history_catobs.js new file mode 100644 index 000000000000..a158b0cebba2 --- /dev/null +++ b/toolkit/components/places/tests/unit/test_history_catobs.js @@ -0,0 +1,81 @@ +/* ***** 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 Corporation. + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Mark Finkle (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 ***** */ + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +// Get services. +let hs = Cc["@mozilla.org/browser/nav-history-service;1"]. + getService(Ci.nsINavHistoryService); +let os = Cc["@mozilla.org/observer-service;1"]. + getService(Ci.nsIObserverService); + +let gDummyCreated = false; +let gDummyVisited = false; + +let observer = { + observe: function(subject, topic, data) { + if (topic == "dummy-observer-created") + gDummyCreated = true; + else if (topic == "dummy-observer-visited") + gDummyVisited = true; + }, + + QueryInterface: XPCOMUtils.generateQI([ + Ci.nsIObserver, + Ci.nsISupportsWeakReference, + ]) +}; + +function verify() { + do_check_true(gDummyCreated); + do_check_true(gDummyVisited); + do_test_finished(); +} + +// main +function run_test() { + do_load_module("nsDummyObserver.js"); + + os.addObserver(observer, "dummy-observer-created", true); + os.addObserver(observer, "dummy-observer-visited", true); + + // Add a visit + hs.addVisit(uri("http://typed.mozilla.org"), Date.now(), null, + hs.TRANSITION_TYPED, false, 0); + + do_test_pending(); + do_timeout(1000, "verify();"); +} diff --git a/toolkit/components/places/tests/unit/test_migrateFrecency.js b/toolkit/components/places/tests/unit/test_migrateFrecency.js index 1358916e45e8..4dd087401221 100644 --- a/toolkit/components/places/tests/unit/test_migrateFrecency.js +++ b/toolkit/components/places/tests/unit/test_migrateFrecency.js @@ -71,9 +71,9 @@ function run_test() { deleteMork(); _("Now that places has migrated, check that it calculated frecencies"); - places.DBConnection.createStatement( - "SELECT COUNT(*) FROM moz_places_view WHERE frecency < 0"). - executeAsync({ + var stmt = places.DBConnection.createStatement( + "SELECT COUNT(*) FROM moz_places_view WHERE frecency < 0"); + stmt.executeAsync({ handleResult: function(results) { _("Should always get a result from COUNT(*)"); let row = results.getNextRow(); @@ -85,6 +85,7 @@ function run_test() { handleCompletion: do_test_finished, handleError: do_throw, }); + stmt.finalize(); break; } },