Bug 461199 (Part 2) - Create an API for asynchronous isVisited checks that content/layout can use
authorShawn Wilsher <sdwilsh@shawnwilsher.com>
Thu, 20 Aug 2009 11:56:10 -0700
changeset 38166 ed47ac6b707e16596b564e0ddb4a7799905de975
parent 38165 4eac87192a32188e3349072be0ad27787cc7d971
child 38167 1622d8dab8c37368d23050089da325ed1ef0c2ad
push id11651
push usersdwilsh@shawnwilsher.com
push dateWed, 17 Feb 2010 22:38:03 +0000
treeherdermozilla-central@550a4379468f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs461199
milestone1.9.3a2pre
Bug 461199 (Part 2) - Create an API for asynchronous isVisited checks that content/layout can use Create a new API (IHistory) to check for the visitedness of a URI. r=mak r=bz sr=bsmedberg
content/base/src/Makefile.in
docshell/base/IHistory.h
docshell/base/Makefile.in
docshell/build/nsDocShellCID.h
toolkit/components/places/src/History.cpp
toolkit/components/places/src/History.h
toolkit/components/places/src/Makefile.in
toolkit/components/places/src/nsNavHistory.cpp
toolkit/components/places/src/nsNavHistory.h
toolkit/components/places/src/nsPlacesModule.cpp
toolkit/components/places/tests/Makefile.in
toolkit/components/places/tests/cpp/Makefile.in
toolkit/components/places/tests/cpp/mock_Link.h
toolkit/components/places/tests/cpp/places_test_harness.h
toolkit/components/places/tests/cpp/places_test_harness_tail.h
toolkit/components/places/tests/cpp/test_IHistory.cpp
--- a/content/base/src/Makefile.in
+++ b/content/base/src/Makefile.in
@@ -59,16 +59,22 @@ EXPORTS		= \
 		nsScriptLoader.h \
 		nsStubDocumentObserver.h \
 		nsStubImageDecoderObserver.h \
 		nsStubMutationObserver.h \
 		nsTextFragment.h \
 		mozAutoDocUpdate.h \
 		$(NULL)
 
+EXPORTS_NAMESPACES = mozilla/dom
+
+EXPORTS_mozilla/dom = \
+  Link.h \
+  $(NULL)
+
 CPPSRCS		= \
 		mozSanitizingSerializer.cpp \
 		nsAtomListUtils.cpp \
 		nsAttrAndChildArray.cpp \
 		nsAttrValue.cpp \
 		nsCCUncollectableMarker.cpp \
 		nsCommentNode.cpp \
 		nsContentAreaDragDrop.cpp \
new file mode 100644
--- /dev/null
+++ b/docshell/base/IHistory.h
@@ -0,0 +1,111 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=4 sw=4 et tw=80:
+ * ***** 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 the mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Shawn Wilsher <me@shawnwilsher.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of 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 ***** */
+
+#ifndef mozilla_IHistory_h_
+#define mozilla_IHistory_h_
+
+#include "nsISupports.h"
+
+class nsIURI;
+
+namespace mozilla {
+
+    namespace dom {
+        class Link;
+    }
+
+#define IHISTORY_IID \
+  {0xaf27265d, 0x5672, 0x4d23, {0xa0, 0x75, 0x34, 0x8e, 0xb9, 0x73, 0x5a, 0x9a}}
+
+class IHistory : public nsISupports
+{
+public:
+    NS_DECLARE_STATIC_IID_ACCESSOR(IHISTORY_IID)
+
+    /**
+     * Registers the Link for notifications about the visited-ness of aURI.
+     * Consumers should assume that the URI is unvisited after calling this, and
+     * they will be notified if that state (unvisited) changes by having
+     * SetLinkState called on themselves.  This function is guaranteed to run to
+     * completion before aLink is notified.  After the node is notified, it will
+     * be unregistered.
+     *
+     * @note SetLinkState must not call RegisterVisitedCallback or
+     *       UnregisterVisitedCallback.
+     *
+     * @pre aURI must not be null.
+     * @pre aLink must not be null.
+     *
+     * @param aURI
+     *        The URI to check.
+     * @param aLink
+     *        The link to update whenever the history status changes.  The
+     *        implementation will only hold onto a raw pointer, so if this
+     *        object should be destroyed, be sure to call
+     *        UnregisterVistedCallback first.
+     */
+    NS_IMETHOD RegisterVisitedCallback(nsIURI *aURI, dom::Link *aLink) = 0;
+
+    /**
+     * Unregisters a previously registered Link object.  This must be called
+     * before destroying the registered object.
+     *
+     * @pre aURI must not be null.
+     * @pre aLink must not be null.
+     *
+     * @param aURI
+     *        The URI that aLink was registered for.
+     * @param aLink
+     *        The link object to unregister for aURI.
+     */
+    NS_IMETHOD UnregisterVisitedCallback(nsIURI *aURI, dom::Link *aLink) = 0;
+
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(IHistory, IHISTORY_IID)
+
+#define NS_DECL_IHISTORY \
+    NS_IMETHOD RegisterVisitedCallback(nsIURI *aURI, \
+                                       mozilla::dom::Link *aContent); \
+    NS_IMETHOD UnregisterVisitedCallback(nsIURI *aURI, \
+                                         mozilla::dom::Link *aContent);
+
+} // namespace mozilla
+
+#endif // mozilla_IHistory_h_
--- a/docshell/base/Makefile.in
+++ b/docshell/base/Makefile.in
@@ -77,16 +77,22 @@ XPIDLSRCS	= \
 		nsIURIClassifier.idl		\
 		nsIChannelClassifier.idl	\
 		nsIDownloadHistory.idl          \
 		nsILoadContext.idl              \
 		$(NULL)
 
 EXPORTS		= nsDocShellLoadTypes.h
 
+EXPORTS_NAMESPACES = mozilla
+
+EXPORTS_mozilla = \
+  IHistory.h \
+  $(NULL)
+
 CPPSRCS		= \
 		nsDocShell.cpp		\
 		nsDocShellLoadInfo.cpp		\
 		nsDocShellEditorData.cpp	\
 		nsDocShellTransferableHooks.cpp \
 		nsDocShellEnumerator.cpp  \
 		nsDSURIContentListener.cpp		\
 		nsDefaultURIFixup.cpp		\
--- a/docshell/build/nsDocShellCID.h
+++ b/docshell/build/nsDocShellCID.h
@@ -81,17 +81,21 @@
  * exact ones keep changing; if they stabilize somewhat that will get
  * documented.
  */
 #define NS_DOCSHELL_CID                                             \
     { 0xf1eac762, 0x87e9, 0x11d3,                                   \
       { 0xaf, 0x80, 0x00, 0xa0, 0x24, 0xff, 0xc0, 0x8c } }
 #define NS_DOCSHELL_CONTRACTID "@mozilla.org/docshell/html;1"
 
-
+/**
+ * Contract ID to obtain the IHistory interface.  This is a non-scriptable
+ * interface used to interact with history in an asynchronous manner.
+ */
+#define NS_IHISTORY_CONTRACTID "@mozilla.org/browser/history;1"
 
 /**
  * An observer service topic that can be listened to to catch creation
  * of content browsing areas (both toplevel ones and subframes).  The
  * subject of the notification will be the nsIWebNavigation being
  * created.  At this time the additional data wstring is not defined
  * to be anything in particular.
  */
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/src/History.cpp
@@ -0,0 +1,286 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * ***** 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 code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Shawn Wilsher <me@shawnwilsher.com> (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 "History.h"
+#include "nsNavHistory.h"
+
+#include "mozilla/storage.h"
+#include "mozilla/dom/Link.h"
+#include "nsDocShellCID.h"
+#include "nsIEventStateManager.h"
+
+using namespace mozilla::dom;
+
+namespace mozilla {
+namespace places {
+
+////////////////////////////////////////////////////////////////////////////////
+//// Anonymous Helpers
+
+namespace {
+
+class VisitedQuery : public mozIStorageStatementCallback
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  static nsresult Start(nsIURI* aURI)
+  {
+    NS_ASSERTION(aURI, "Don't pass a null URI!");
+
+    nsNavHistory* navHist = nsNavHistory::GetHistoryService();
+    NS_ENSURE_TRUE(navHist, NS_ERROR_FAILURE);
+    mozIStorageStatement* stmt = navHist->DBGetIsVisited();
+    NS_ENSURE_STATE(stmt);
+
+    // Be sure to reset our statement!
+    mozStorageStatementScoper scoper(stmt);
+    nsCString spec;
+    nsresult rv = aURI->GetSpec(spec);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = stmt->BindUTF8StringParameter(0, spec);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsRefPtr<VisitedQuery> callback = new VisitedQuery(aURI);
+    NS_ENSURE_TRUE(callback, NS_ERROR_OUT_OF_MEMORY);
+
+    nsCOMPtr<mozIStoragePendingStatement> handle;
+    return stmt->ExecuteAsync(callback, getter_AddRefs(handle));
+  }
+
+  NS_IMETHOD HandleResult(mozIStorageResultSet* aResults)
+  {
+    // If this method is called, we've gotten results, which means we have a
+    // visit.
+    mIsVisited = true;
+    return NS_OK;
+  }
+
+  NS_IMETHOD HandleError(mozIStorageError* aError)
+  {
+    // mIsVisited is already set to false, and that's the assumption we will
+    // make if an error occurred.
+    return NS_OK;
+  }
+
+  NS_IMETHOD HandleCompletion(PRUint16 aReason)
+  {
+    if (mIsVisited) {
+      History::GetService()->NotifyVisited(mURI);
+    }
+    return NS_OK;
+  }
+private:
+  VisitedQuery(nsIURI* aURI)
+  : mURI(aURI)
+  , mIsVisited(false)
+  {
+  }
+
+  nsCOMPtr<nsIURI> mURI;
+  bool mIsVisited;
+};
+NS_IMPL_ISUPPORTS1(
+  VisitedQuery,
+  mozIStorageStatementCallback
+)
+
+} // anonymous namespace
+
+////////////////////////////////////////////////////////////////////////////////
+//// History
+
+History* History::gService = NULL;
+
+History::History()
+{
+  NS_ASSERTION(!gService, "Ruh-roh!  This service has already been created!");
+  gService = this;
+}
+
+History::~History()
+{
+  gService = NULL;
+}
+
+void
+History::NotifyVisited(nsIURI* aURI)
+{
+  NS_ASSERTION(aURI, "Ruh-roh!  A NULL URI was passed to us!");
+
+  // If the hash table has not been initialized, then we have nothing to notify
+  // about.
+  if (!mObservers.IsInitialized()) {
+    return;
+  }
+
+  // Additionally, if we have no observers for this URI, we have nothing to
+  // notify about.
+  KeyClass* key = mObservers.GetEntry(aURI);
+  if (!key) {
+    return;
+  }
+
+  // Walk through the array, and update each Link node.
+  const ObserverArray& observers = key->array;
+  ObserverArray::index_type len = observers.Length();
+  for (ObserverArray::index_type i = 0; i < len; i++) {
+    Link* link = observers[i];
+    link->SetLinkState(eLinkState_Visited);
+    NS_ASSERTION(len == observers.Length(),
+                 "Calling SetLinkState added or removed an observer!");
+  }
+
+  // All the registered nodes can now be removed for this URI.
+  mObservers.RemoveEntry(aURI);
+}
+
+/* static */
+History*
+History::GetService()
+{
+  if (gService) {
+    return gService;
+  }
+
+  nsCOMPtr<IHistory> service(do_GetService(NS_IHISTORY_CONTRACTID));
+  NS_ABORT_IF_FALSE(service, "Cannot obtain IHistory service!");
+  NS_ASSERTION(gService, "Our constructor was not run?!");
+
+  return gService;
+}
+
+/* static */
+History*
+History::GetSingleton()
+{
+  if (!gService) {
+    gService = new History();
+    NS_ENSURE_TRUE(gService, nsnull);
+  }
+
+  NS_ADDREF(gService);
+  return gService;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// IHistory
+
+NS_IMETHODIMP
+History::RegisterVisitedCallback(nsIURI* aURI,
+                                 Link* aLink)
+{
+  NS_ASSERTION(aURI, "Must pass a non-null URI!");
+  NS_ASSERTION(aLink, "Must pass a non-null Link object!");
+
+  // First, ensure that our hash table is setup.
+  if (!mObservers.IsInitialized()) {
+    NS_ENSURE_TRUE(mObservers.Init(), NS_ERROR_OUT_OF_MEMORY);
+  }
+
+  // Obtain our array of observers for this URI.
+  KeyClass* key = mObservers.PutEntry(aURI);
+  NS_ENSURE_TRUE(key, NS_ERROR_OUT_OF_MEMORY);
+  ObserverArray& observers = key->array;
+
+  if (observers.IsEmpty()) {
+    // We are the first Link node to ask about this URI, or there are no pending
+    // Links wanting to know about this URI.  Therefore, we should query the
+    // database now.
+    nsresult rv = VisitedQuery::Start(aURI);
+    if (NS_FAILED(rv)) {
+      // Curses - unregister and return failure.
+      (void)UnregisterVisitedCallback(aURI, aLink);
+      return rv;
+    }
+  }
+
+  // Sanity check that Links are not registered more than once for a given URI.
+  // This will not catch a case where it is registered for two different URIs.
+  NS_ASSERTION(!observers.Contains(aLink),
+               "Already tracking this Link object!");
+
+  // Start tracking our Link.
+  if (!observers.AppendElement(aLink)) {
+    // Curses - unregister and return failure.
+    (void)UnregisterVisitedCallback(aURI, aLink);
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+History::UnregisterVisitedCallback(nsIURI* aURI,
+                                   Link* aLink)
+{
+  NS_ASSERTION(aURI, "Must pass a non-null URI!");
+  NS_ASSERTION(aLink, "Must pass a non-null Link object!");
+
+  // Get the array, and remove the item from it.
+  KeyClass* key = mObservers.GetEntry(aURI);
+  if (!key) {
+    NS_ERROR("Trying to unregister for a URI that wasn't registered!");
+    return NS_ERROR_UNEXPECTED;
+  }
+  ObserverArray& observers = key->array;
+  if (!observers.RemoveElement(aLink)) {
+    NS_ERROR("Trying to unregister a node that wasn't registered!");
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  // If the array is now empty, we should remove it from the hashtable.
+  if (observers.IsEmpty()) {
+    mObservers.RemoveEntry(aURI);
+  }
+
+  return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsISupports
+
+NS_IMPL_ISUPPORTS1(
+  History,
+  IHistory
+)
+
+} // namespace places
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/src/History.h
@@ -0,0 +1,111 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * ***** 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 code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Shawn Wilsher <me@shawnwilsher.com> (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 ***** */
+
+#ifndef mozilla_places_History_h_
+#define mozilla_places_History_h_
+
+#include "mozilla/IHistory.h"
+#include "mozilla/dom/Link.h"
+#include "nsTHashtable.h"
+#include "nsString.h"
+#include "nsURIHashKey.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace places {
+
+#define NS_HISTORYSERVICE_CID \
+  {0x9fc91e65, 0x1475, 0x4353, {0x9b, 0x9a, 0x93, 0xd7, 0x6f, 0x5b, 0xd9, 0xb7}}
+
+class History : public IHistory
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_IHISTORY
+
+  History();
+
+  /**
+   * Notifies about the visited status of a given URI.
+   *
+   * @param aURI
+   *        The URI to notify about.
+   */
+  void NotifyVisited(nsIURI *aURI);
+
+  /**
+   * Obtains a pointer to this service.
+   */
+  static History *GetService();
+
+  /**
+   * Obtains a pointer that has had AddRef called on it.  Used by the service
+   * manager only.
+   */
+  static History *GetSingleton();
+
+private:
+  ~History();
+
+  static History *gService;
+
+  typedef nsTArray<mozilla::dom::Link *> ObserverArray;
+
+  class KeyClass : public nsURIHashKey
+  {
+  public:
+    KeyClass(const nsIURI *aURI)
+    : nsURIHashKey(aURI)
+    {
+    }
+    KeyClass(const KeyClass &aOther)
+    : nsURIHashKey(aOther)
+    {
+      NS_NOTREACHED("Do not call me!");
+    }
+    ObserverArray array;
+  };
+
+  nsTHashtable<KeyClass> mObservers;
+};
+
+} // namespace places
+} // namespace mozilla
+
+#endif // mozilla_places_History_h_
--- a/toolkit/components/places/src/Makefile.in
+++ b/toolkit/components/places/src/Makefile.in
@@ -59,16 +59,17 @@ CPPSRCS = \
           nsNavHistoryQuery.cpp \
           nsNavHistoryResult.cpp \
           nsNavBookmarks.cpp \
           nsMaybeWeakPtr.cpp \
           nsMorkHistoryImporter.cpp \
           nsPlacesModule.cpp \
           SQLFunctions.cpp \
           Helpers.cpp \
+          History.cpp \
           $(NULL)
 
 EXTRA_DSO_LDOPTS += \
 	$(DEPTH)/db/morkreader/$(LIB_PREFIX)morkreader_s.$(LIB_SUFFIX) \
 	$(MOZ_UNICHARUTIL_LIBS) \
 	$(MOZ_COMPONENT_LIBS) \
 	$(NULL)
 
--- a/toolkit/components/places/src/nsNavHistory.cpp
+++ b/toolkit/components/places/src/nsNavHistory.cpp
@@ -72,16 +72,17 @@
 #include "nsFaviconService.h"
 
 #include "nsPlacesTables.h"
 #include "nsPlacesIndexes.h"
 #include "nsPlacesTriggers.h"
 #include "nsPlacesMacros.h"
 #include "SQLFunctions.h"
 #include "Helpers.h"
+#include "History.h"
 
 #ifdef MOZ_XUL
 #include "nsIAutoCompleteInput.h"
 #include "nsIAutoCompletePopup.h"
 #endif
 
 using namespace mozilla::places;
 
@@ -2795,16 +2796,19 @@ nsNavHistory::AddVisit(nsIURI* aURI, PRT
   // send it ourselves.
   if (newItem && (aIsRedirect || aTransitionType == TRANSITION_DOWNLOAD)) {
     nsCOMPtr<nsIObserverService> obsService =
       do_GetService(NS_OBSERVERSERVICE_CONTRACTID);
     if (obsService)
       obsService->NotifyObservers(aURI, NS_LINK_VISITED_EVENT_TOPIC, nsnull);
   }
 
+  // Because we implement IHistory, we always have to notify about the visit.
+  History::GetService()->NotifyVisited(aURI);
+
   return NS_OK;
 }
 
 
 // nsNavHistory::GetNewQuery
 
 NS_IMETHODIMP
 nsNavHistory::GetNewQuery(nsINavHistoryQuery **_retval)
--- a/toolkit/components/places/src/nsNavHistory.h
+++ b/toolkit/components/places/src/nsNavHistory.h
@@ -231,16 +231,18 @@ public:
   static const PRInt32 kGetInfoIndex_ItemParentId;
 
   // select a history row by id
   mozIStorageStatement *DBGetIdPageInfo() { return mDBGetIdPageInfo; }
 
   mozIStorageStatement *DBGetTags() { return mDBGetTags; }
   PRInt64 GetTagsFolder();
 
+  mozIStorageStatement *DBGetIsVisited() { return mDBIsPageVisited; }
+
   // Constants for the columns returned by the above statement
   // (in addition to the ones above).
   static const PRInt32 kGetInfoIndex_VisitDate;
   static const PRInt32 kGetInfoIndex_FaviconURL;
 
   // used in execute queries to get session ID info (only for visits)
   static const PRInt32 kGetInfoIndex_SessionId;
 
--- a/toolkit/components/places/src/nsPlacesModule.cpp
+++ b/toolkit/components/places/src/nsPlacesModule.cpp
@@ -1,18 +1,21 @@
 #include "nsIGenericFactory.h"
 #include "nsIClassInfoImpl.h"
 
 #include "nsAnnoProtocolHandler.h"
 #include "nsAnnotationService.h"
 #include "nsNavHistory.h"
 #include "nsNavBookmarks.h"
 #include "nsFaviconService.h"
+#include "History.h"
 #include "nsDocShellCID.h"
 
+using namespace mozilla::places;
+
 #define NS_NAVHISTORY_CLASSINFO \
   nsnull, nsnull, nsnull, \
   NS_CI_INTERFACE_GETTER_NAME(nsNavHistory), \
   nsnull, \
   &NS_CLASSINFO_NAME(nsNavHistory), \
   nsIClassInfo::SINGLETON
 
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsNavHistory,
@@ -20,16 +23,17 @@ NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR
 NS_DECL_CLASSINFO(nsNavHistory)
 
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsAnnotationService,
                                          nsAnnotationService::GetSingleton)
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsNavBookmarks,
                                          nsNavBookmarks::GetSingleton)
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsFaviconService,
                                          nsFaviconService::GetSingleton)
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(History, History::GetSingleton)
 
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsAnnoProtocolHandler)
 
 static const nsModuleComponentInfo components[] =
 {
   { "Browser Navigation History",
     NS_NAVHISTORYSERVICE_CID,
     NS_NAVHISTORYSERVICE_CONTRACTID,
@@ -69,11 +73,16 @@ static const nsModuleComponentInfo compo
     nsFaviconServiceConstructor },
 
   { "Browser History Charset Resolver",
     NS_NAVHISTORYSERVICE_CID,
     "@mozilla.org/embeddor.implemented/bookmark-charset-resolver;1",
     nsNavHistoryConstructor,
     NS_NAVHISTORY_CLASSINFO },
 
+  { "Browser History",
+    NS_HISTORYSERVICE_CID,
+    NS_IHISTORY_CONTRACTID,
+    HistoryConstructor },
+
 };
 
 NS_IMPL_NSGETMODULE(nsPlacesModule, components)
--- a/toolkit/components/places/tests/Makefile.in
+++ b/toolkit/components/places/tests/Makefile.in
@@ -63,12 +63,16 @@ MOCHI_TESTS = \
 
 DIRS = \
 	chrome \
 	mochitest/bug_411966 \
 	mochitest/bug_461710 \
 	browser \
 	$(NULL)
 
+TOOL_DIRS = \
+  cpp \
+  $(NULL)
+
 include $(topsrcdir)/config/rules.mk
 
 libs:: $(MOCHI_TESTS)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/cpp/Makefile.in
@@ -0,0 +1,53 @@
+# ***** 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 cpp unit test code.
+#
+# The Initial Developer of the Original Code is
+# Mozilla Foundation.
+# Portions created by the Initial Developer are Copyright (C) 2009
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Shawn Wilsher <me@shawnwilsher.com> (Original author)
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either of 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 *****
+
+DEPTH     = ../../../../..
+topsrcdir = @top_srcdir@
+srcdir    = @srcdir@
+VPATH     = @srcdir@
+
+relativesrcdir = toolkit/components/places/tests/cpp
+
+include $(DEPTH)/config/autoconf.mk
+
+MODULE = test_places
+
+CPP_UNIT_TESTS = \
+  test_IHistory.cpp \
+  $(NULL)
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/cpp/mock_Link.h
@@ -0,0 +1,119 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * ***** 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 test code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Shawn Wilsher <me@shawnwilsher.com> (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 ***** */
+
+/**
+ * This is a mock Link object which can be used in tests.
+ */
+
+#ifndef mock_Link_h__
+#define mock_Link_h__
+
+#include "mozilla/dom/Link.h"
+
+class mock_Link : public mozilla::dom::Link
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  mock_Link(void (*aHandlerFunction)(nsLinkState),
+            bool aRunNextTest = true)
+  : mozilla::dom::Link()
+  , mHandler(aHandlerFunction)
+  , mRunNextTest(aRunNextTest)
+  {
+  }
+
+  virtual void SetLinkState(nsLinkState aState)
+  {
+    // Notify our callback function.
+    mHandler(aState);
+
+    // Run the next test if we are supposed to.
+    if (mRunNextTest) {
+      run_next_test();
+    }
+
+    // Finally, we must manually release ourselves.
+    NS_RELEASE_THIS();
+  }
+
+private:
+  void (*mHandler)(nsLinkState);
+  bool mRunNextTest;
+};
+
+NS_IMPL_ISUPPORTS1(
+  mock_Link,
+  mozilla::dom::Link
+)
+
+////////////////////////////////////////////////////////////////////////////////
+//// Needed Link Methods
+
+namespace mozilla {
+namespace dom {
+
+Link::Link()
+: mLinkState(mozilla::dom::Link::defaultState)
+{
+}
+
+nsLinkState
+Link::GetLinkState() const
+{
+  NS_NOTREACHED("Unexpected call to Link::GetLinkState");
+  return eLinkState_Unknown; // suppress compiler warning
+}
+
+void
+Link::SetLinkState(nsLinkState aState)
+{
+  NS_NOTREACHED("Unexpected call to Link::SetLinkState");
+}
+
+void
+Link::ResetLinkState()
+{
+  NS_NOTREACHED("Unexpected call to Link::ResetLinkState");
+}
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mock_Link_h__
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/cpp/places_test_harness.h
@@ -0,0 +1,123 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * ***** 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 test code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Shawn Wilsher <me@shawnwilsher.com> (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 "TestHarness.h"
+#include "nsMemory.h"
+#include "nsThreadUtils.h"
+#include "nsNetUtil.h"
+#include "nsDocShellCID.h"
+
+#include "nsToolkitCompsCID.h"
+#include "nsINavHistoryService.h"
+#include "nsIObserverService.h"
+#include "mozilla/IHistory.h"
+
+using namespace mozilla;
+
+static size_t gTotalTests = 0;
+static size_t gPassedTests = 0;
+
+#define do_check_true(aCondition) \
+  PR_BEGIN_MACRO \
+    gTotalTests++; \
+    if (aCondition) { \
+      gPassedTests++; \
+    } else { \
+      fail("Expected true, got false at %s:%d!", __FILE__, __LINE__); \
+    } \
+  PR_END_MACRO
+
+#define do_check_false(aCondition) \
+  PR_BEGIN_MACRO \
+    gTotalTests++; \
+    if (!aCondition) { \
+      gPassedTests++; \
+    } else { \
+      fail("Expected false, got true at %s:%d!", __FILE__, __LINE__); \
+    } \
+  PR_END_MACRO
+
+#define do_check_success(aResult) \
+  do_check_true(NS_SUCCEEDED(aResult))
+
+struct Test
+{
+  void (*func)(void);
+  const char* const name;
+};
+#define TEST(aName) \
+  {aName, #aName}
+
+/**
+ * Runs the next text.
+ */
+void run_next_test();
+
+/**
+ * To be used around asynchronous work.
+ */
+void do_test_pending();
+void do_test_finished();
+
+/**
+ * Adds a URI to the database.
+ *
+ * @param aURI
+ *        The URI to add to the database.
+ */
+void
+addURI(nsIURI* aURI)
+{
+  nsCOMPtr<nsINavHistoryService> hist =
+    do_GetService(NS_NAVHISTORYSERVICE_CONTRACTID);
+
+  PRInt64 id;
+  nsresult rv = hist->AddVisit(aURI, PR_Now(), nsnull,
+                               nsINavHistoryService::TRANSITION_LINK, PR_FALSE,
+                               0, &id);
+  do_check_success(rv);
+}
+
+already_AddRefed<IHistory>
+do_get_IHistory()
+{
+  nsCOMPtr<IHistory> history = do_GetService(NS_IHISTORY_CONTRACTID);
+  do_check_true(history);
+  return history.forget();
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/cpp/places_test_harness_tail.h
@@ -0,0 +1,127 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * ***** 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 test code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Shawn Wilsher <me@shawnwilsher.com> (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 ***** */
+
+#ifndef TEST_NAME
+#error "Must #define TEST_NAME before including places_test_harness_tail.h"
+#endif
+
+#ifndef TEST_FILE
+#error "Must #define TEST_FILE before include places_test_harness_tail.h"
+#endif
+
+size_t gTestsIndex = 0;
+
+#ifdef XP_WIN
+#define SIZE_T "%Iu"
+#else
+#define SIZE_T "%zu"
+#endif
+
+#define TEST_INFO_STR "TEST-INFO | (%s) | "
+
+class RunNextTest : public nsRunnable
+{
+public:
+  NS_IMETHOD Run()
+  {
+    NS_ASSERTION(NS_IsMainThread(), "Not running on the main thread?");
+    if (gTestsIndex < NS_ARRAY_LENGTH(gTests)) {
+      do_test_pending();
+      Test &test = gTests[gTestsIndex++];
+      (void)fprintf(stderr, TEST_INFO_STR "Running %s.\n", TEST_FILE,
+                    test.name);
+      test.func();
+    }
+
+    do_test_finished();
+    return NS_OK;
+  }
+};
+
+void
+run_next_test()
+{
+  nsCOMPtr<nsIRunnable> event = new RunNextTest();
+  do_check_success(NS_DispatchToCurrentThread(event));
+}
+
+size_t gPendingTests = 0;
+
+void
+do_test_pending()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Not running on the main thread?");
+  gPendingTests++;
+}
+
+void
+do_test_finished()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Not running on the main thread?");
+  NS_ASSERTION(gPendingTests > 0, "Invalid pending test count!");
+  gPendingTests--;
+}
+
+int
+main(int aArgc,
+     char** aArgv)
+{
+  ScopedXPCOM xpcom(TEST_NAME);
+
+  do_test_pending();
+  run_next_test();
+
+  // Spin the event loop until we've run out of tests to run.
+  while (gPendingTests) {
+    (void)NS_ProcessNextEvent();
+  }
+
+  // And let any other events finish before we quit.
+  (void)NS_ProcessPendingEvents(nsnull);
+
+  // Check that we have passed all of our tests, and output accordingly.
+  if (gPassedTests == gTotalTests) {
+    passed(TEST_FILE);
+  }
+
+  (void)fprintf(stderr, TEST_INFO_STR SIZE_T " of " SIZE_T " tests passed\n",
+                TEST_FILE, gPassedTests, gTotalTests);
+
+  return gPassedTests == gTotalTests ? 0 : -1;
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/cpp/test_IHistory.cpp
@@ -0,0 +1,270 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * ***** 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 test code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Shawn Wilsher <me@shawnwilsher.com> (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 "places_test_harness.h"
+
+#include "mock_Link.h"
+using namespace mozilla::dom;
+
+/**
+ * This file tests the IHistory interface.
+ */
+
+////////////////////////////////////////////////////////////////////////////////
+//// Helper Methods
+
+void
+expect_visit(nsLinkState aState)
+{
+  do_check_true(aState == eLinkState_Visited);
+}
+
+void
+expect_no_visit(nsLinkState aState)
+{
+  do_check_true(aState == eLinkState_Unvisited);
+}
+
+already_AddRefed<nsIURI>
+new_test_uri()
+{
+  // Create a unique spec.
+  static PRInt32 specNumber = 0;
+  nsCAutoString spec = NS_LITERAL_CSTRING("http://mozilla.org/");
+  spec.AppendInt(specNumber++);
+
+  // Create the URI for the spec.
+  nsCOMPtr<nsIURI> testURI;
+  nsresult rv = NS_NewURI(getter_AddRefs(testURI), spec);
+  do_check_success(rv);
+  return testURI.forget();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// Test Functions
+
+// These variables are shared between part 1 and part 2 of the test.  Part 2
+// sets the nsCOMPtr's to nsnull, freeing the reference.
+namespace test_unvisited_does_not_notify {
+  nsCOMPtr<nsIURI> testURI;
+  nsCOMPtr<Link> link;
+}
+void
+test_unvisted_does_not_notify_part1()
+{
+  using namespace test_unvisited_does_not_notify;
+
+  // This test is done in two parts.  The first part registers for a URI that
+  // should not be visited.  We then run another test that will also do a
+  // lookup and will be notified.  Since requests are answered in the order they
+  // are requested (at least as long as the same URI isn't asked for later), we
+  // will know that the Link was not notified.
+
+  // First, we need a test URI.
+  testURI = new_test_uri();
+
+  // Create our test Link.
+  link = new mock_Link(expect_no_visit);
+
+  // Now, register our Link to be notified.
+  nsCOMPtr<IHistory> history(do_get_IHistory());
+  nsresult rv = history->RegisterVisitedCallback(testURI, link);
+  do_check_success(rv);
+
+  // Run the next test.
+  run_next_test();
+}
+
+void
+test_visited_notifies()
+{
+  // First, we add our test URI to history.
+  nsCOMPtr<nsIURI> testURI(new_test_uri());
+  addURI(testURI);
+
+  // Create our test Link.  The callback function will release the reference we
+  // have on the Link.
+  Link* link = new mock_Link(expect_visit);
+  NS_ADDREF(link);
+
+  // Now, register our Link to be notified.
+  nsCOMPtr<IHistory> history(do_get_IHistory());
+  nsresult rv = history->RegisterVisitedCallback(testURI, link);
+  do_check_success(rv);
+  // Note: test will continue upon notification.
+}
+
+void
+test_unvisted_does_not_notify_part2()
+{
+  using namespace test_unvisited_does_not_notify;
+
+  // We would have had a failure at this point had the content node been told it
+  // was visited.  Therefore, it is safe to unregister our content node.
+  nsCOMPtr<IHistory> history(do_get_IHistory());
+  nsresult rv = history->UnregisterVisitedCallback(testURI, link);
+  do_check_success(rv);
+
+  // Clear the stored variables now.
+  testURI = nsnull;
+  link = nsnull;
+
+  // Run the next test.
+  run_next_test();
+}
+
+void
+test_same_uri_notifies_both()
+{
+  // First, we add our test URI to history.
+  nsCOMPtr<nsIURI> testURI(new_test_uri());
+  addURI(testURI);
+
+  // Create our two test Links.  The callback function will release the
+  // reference we have on the Links.  Only the second Link should run the next
+  // test!
+  Link* link1 = new mock_Link(expect_visit, false);
+  NS_ADDREF(link1);
+  Link* link2 = new mock_Link(expect_visit);
+  NS_ADDREF(link2);
+
+  // Now, register our Link to be notified.
+  nsCOMPtr<IHistory> history(do_get_IHistory());
+  nsresult rv = history->RegisterVisitedCallback(testURI, link1);
+  do_check_success(rv);
+  rv = history->RegisterVisitedCallback(testURI, link2);
+  do_check_success(rv);
+
+  // Note: test will continue upon notification.
+}
+
+void
+test_unregistered_visited_does_not_notify()
+{
+  // This test must have a test that has a successful notification after it.
+  // The Link would have been notified by now if we were buggy and notified
+  // unregistered Links (due to request serialization).
+
+  nsCOMPtr<nsIURI> testURI(new_test_uri());
+  nsCOMPtr<Link> link(new mock_Link(expect_no_visit));
+
+  // Now, register our Link to be notified.
+  nsCOMPtr<IHistory> history(do_get_IHistory());
+  nsresult rv = history->RegisterVisitedCallback(testURI, link);
+  do_check_success(rv);
+
+  // Unregister the Link.
+  rv = history->UnregisterVisitedCallback(testURI, link);
+  do_check_success(rv);
+
+  // And finally add a visit for the URI.
+  addURI(testURI);
+
+  // If history tries to notify us, we'll either crash because the Link will
+  // have been deleted (we are the only thing holding a reference to it), or our
+  // expect_no_visit call back will produce a failure.  Either way, the test
+  // will be reported as a failure.
+
+  // Run the next test.
+  run_next_test();
+}
+
+void
+test_new_visit_notifies_waiting_Link()
+{
+  // Create our test Link.  The callback function will release the reference we
+  // have on the link.
+  Link* link = new mock_Link(expect_visit);
+  NS_ADDREF(link);
+
+  // Now, register our content node to be notified.
+  nsCOMPtr<nsIURI> testURI(new_test_uri());
+  nsCOMPtr<IHistory> history(do_get_IHistory());
+  nsresult rv = history->RegisterVisitedCallback(testURI, link);
+  do_check_success(rv);
+
+  // Add ourselves to history.
+  addURI(testURI);
+
+  // Note: test will continue upon notification.
+}
+
+void
+test_RegisterVisitedCallback_returns_before_notifying()
+{
+  // Add a URI so that it's already in history.
+  nsCOMPtr<nsIURI> testURI(new_test_uri());
+  addURI(testURI);
+
+  // Create our test Link.
+  nsCOMPtr<Link> link(new mock_Link(expect_no_visit));
+
+  // Now, register our content node to be notified.  It should not be notified.
+  nsCOMPtr<IHistory> history(do_get_IHistory());
+  nsresult rv = history->RegisterVisitedCallback(testURI, link);
+  do_check_success(rv);
+
+  // Remove ourselves as an observer.  We would have failed if we had been
+  // notified.
+  rv = history->UnregisterVisitedCallback(testURI, link);
+  do_check_success(rv);
+
+  run_next_test();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// Test Harness
+
+/**
+ * Note: for tests marked "Order Important!", please see the test for details.
+ */
+Test gTests[] = {
+  TEST(test_unvisted_does_not_notify_part1), // Order Important!
+  TEST(test_visited_notifies),
+  TEST(test_unvisted_does_not_notify_part2), // Order Important!
+  TEST(test_same_uri_notifies_both),
+  TEST(test_unregistered_visited_does_not_notify), // Order Important!
+  TEST(test_new_visit_notifies_waiting_Link),
+  TEST(test_RegisterVisitedCallback_returns_before_notifying),
+};
+
+const char* file = __FILE__;
+#define TEST_NAME "IHistory"
+#define TEST_FILE file
+#include "places_test_harness_tail.h"