Bug 556400 - Make adding visits asynchronous. r=sdwilsh r=mak sr=bz
☠☠ backed out by 920385221b56 ☠ ☠
authorBenjamin Stover <bstover@mozilla.com>
Wed, 30 Jun 2010 16:08:28 -0700
changeset 47125 f9a700607b86514c86ac85387f7d64206d7e23fa
parent 47124 6c1fcdfdc2c878a0b0786b8709d9df251b490ae1
child 47126 07a9dcc28c5ca62106336545c017b80cf8122b57
push id14241
push usermak77@bonardo.net
push dateFri, 02 Jul 2010 13:48:24 +0000
treeherdermozilla-central@f666976446db [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssdwilsh, mak, bz
bugs556400
milestone2.0b2pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 556400 - Make adding visits asynchronous. r=sdwilsh r=mak sr=bz
docshell/base/IHistory.h
docshell/base/nsDocShell.cpp
docshell/base/nsDocShell.h
toolkit/components/places/src/Helpers.cpp
toolkit/components/places/src/Helpers.h
toolkit/components/places/src/History.cpp
toolkit/components/places/src/History.h
toolkit/components/places/src/nsNavHistory.cpp
toolkit/components/places/src/nsNavHistory.h
toolkit/components/places/tests/browser/Makefile.in
toolkit/components/places/tests/cpp/places_test_harness.h
toolkit/components/places/tests/cpp/test_IHistory.cpp
xpcom/build/Makefile.in
xpcom/build/ServiceList.h
xpcom/build/Services.cpp
xpcom/build/Services.h
--- a/docshell/base/IHistory.h
+++ b/docshell/base/IHistory.h
@@ -46,17 +46,17 @@ class nsIURI;
 
 namespace mozilla {
 
     namespace dom {
         class Link;
     }
 
 #define IHISTORY_IID \
-  {0xaf27265d, 0x5672, 0x4d23, {0xa0, 0x75, 0x34, 0x8e, 0xb9, 0x73, 0x5a, 0x9a}}
+  {0x6f736049, 0x6370, 0x4376, {0xb7, 0x17, 0xfa, 0xfc, 0x0b, 0x4f, 0xd0, 0xf1}}
 
 class IHistory : public nsISupports
 {
 public:
     NS_DECLARE_STATIC_IID_ACCESSOR(IHISTORY_IID)
 
     /**
      * Registers the Link for notifications about the visited-ness of aURI.
@@ -91,21 +91,56 @@ public:
      *
      * @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;
 
+    enum VisitFlags {
+        /**
+         * Indicates whether the URI was loaded in a top-level window.
+         */
+        TOP_LEVEL = 1 << 0,
+        /**
+         * Indicates whether the URI was loaded as part of a permanent redirect.
+         */
+        REDIRECT_PERMANENT = 1 << 1,
+        /**
+         * Indicates whether the URI was loaded as part of a temporary redirect.
+         */
+        REDIRECT_TEMPORARY = 1 << 2
+    };
+
+    /**
+     * Adds a history visit for the URI.
+     *
+     * @pre aURI must not be null.
+     *
+     * @param aURI
+     *        The URI of the page being visited.
+     * @param aLastVisitedURI
+     *        The URI of the last visit in the chain.
+     * @param aFlags
+     *        The VisitFlags describing this visit.
+     */
+    NS_IMETHOD VisitURI(
+        nsIURI *aURI,
+        nsIURI *aLastVisitedURI,
+        PRUint32 aFlags
+    ) = 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);
+                                         mozilla::dom::Link *aContent); \
+    NS_IMETHOD VisitURI(nsIURI *aURI, \
+                        nsIURI *aLastVisitedURI, \
+                        PRUint32 aFlags);
 
 } // namespace mozilla
 
 #endif // mozilla_IHistory_h_
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -107,16 +107,18 @@
 #include "nsDOMJSUtils.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIView.h"
 #include "nsIViewManager.h"
 #include "nsIScriptChannel.h"
 #include "nsIOfflineCacheUpdate.h"
 #include "nsCPrefetchService.h"
 #include "nsJSON.h"
+#include "IHistory.h"
+#include "mozilla/Services.h"
 
 // we want to explore making the document own the load group
 // so we can associate the document URI with the load group.
 // until this point, we have an evil hack:
 #include "nsIHttpChannelInternal.h"  
 
 
 // Local Includes
@@ -5667,42 +5669,63 @@ nsDocShell::OnRedirectStateChange(nsICha
                                   PRUint32 aRedirectFlags,
                                   PRUint32 aStateFlags)
 {
     NS_ASSERTION(aStateFlags & STATE_REDIRECTING,
                  "Calling OnRedirectStateChange when there is no redirect");
     if (!(aStateFlags & STATE_IS_DOCUMENT))
         return; // not a toplevel document
 
-    nsCOMPtr<nsIGlobalHistory3> history3(do_QueryInterface(mGlobalHistory));
-    nsresult result = NS_ERROR_NOT_IMPLEMENTED;
-    if (history3) {
-        // notify global history of this redirect
-        result = history3->AddDocumentRedirect(aOldChannel, aNewChannel,
-                                               aRedirectFlags, !IsFrame());
-    }
-
-    if (result == NS_ERROR_NOT_IMPLEMENTED) {
-        // when there is no GlobalHistory3, or it doesn't implement
-        // AddToplevelRedirect, we fall back to GlobalHistory2.  Just notify
-        // that the redirecting page was a rePdirect so it will be link colored
-        // but not visible.
-        nsCOMPtr<nsIURI> oldURI;
-        aOldChannel->GetURI(getter_AddRefs(oldURI));
-        if (! oldURI)
-            return; // nothing to tell anybody about
-        AddToGlobalHistory(oldURI, PR_TRUE, aOldChannel);
+    nsCOMPtr<nsIURI> oldURI, newURI;
+    aOldChannel->GetURI(getter_AddRefs(oldURI));
+    aNewChannel->GetURI(getter_AddRefs(newURI));
+    if (!oldURI || !newURI) {
+        return;
+    }
+
+    // Below a URI visit is saved (see AddURIVisit method doc).
+    // The visit chain looks something like:
+    //   ...
+    //   Site N - 1
+    //                =>  Site N
+    //   (redirect to =>) Site N + 1 (we are here!)
+
+    // Get N - 1 and transition type
+    nsCOMPtr<nsIURI> previousURI;
+    PRUint32 previousFlags = 0;
+    ExtractLastVisit(aOldChannel, getter_AddRefs(previousURI), &previousFlags);
+
+    if (aRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL ||
+        ChannelIsPost(aOldChannel)) {
+        // 1. Internal redirects are ignored because they are specific to the
+        //    channel implementation.
+        // 2. POSTs are not saved by global history.
+        //
+        // Regardless, we need to propagate the previous visit to the new
+        // channel.
+        SaveLastVisit(aNewChannel, previousURI, previousFlags);
+    }
+    else {
+        nsCOMPtr<nsIURI> referrer;
+        // Treat referrer as null if there is an error getting it.
+        (void)NS_GetReferrerFromChannel(aOldChannel,
+                                        getter_AddRefs(referrer));
+
+        // Add visit N -1 => N
+        AddURIVisit(oldURI, referrer, previousURI, previousFlags);
+
+        // Since N + 1 could be the final destination, we will not save N => N + 1
+        // here.  OnNewURI will do that, so we will cache it.
+        SaveLastVisit(aNewChannel, oldURI, aRedirectFlags);
     }
 
     // check if the new load should go through the application cache.
     nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel =
         do_QueryInterface(aNewChannel);
     if (appCacheChannel) {
-        nsCOMPtr<nsIURI> newURI;
-        aNewChannel->GetURI(getter_AddRefs(newURI));
         appCacheChannel->SetChooseApplicationCache(ShouldCheckAppCache(newURI));
     }
 
     if (!(aRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL) && 
         mLoadType & (LOAD_CMD_RELOAD | LOAD_CMD_HISTORY)) {
         mLoadType = LOAD_NORMAL_REPLACE;
         SetHistoryEntry(&mLSHE, nsnull);
     }
@@ -8932,17 +8955,17 @@ nsDocShell::OnNewURI(nsIURI * aURI, nsIC
     PRBool updateHistory = PR_TRUE;
     PRBool equalUri = PR_FALSE;
     PRBool shAvailable = PR_TRUE;  
 
     // Get the post data from the channel
     nsCOMPtr<nsIInputStream> inputStream;
     if (aChannel) {
         nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel));
-        
+
         // Check if the HTTPChannel is hiding under a multiPartChannel
         if (!httpChannel)  {
             GetHttpChannel(aChannel, getter_AddRefs(httpChannel));
         }
 
         if (httpChannel) {
             nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(httpChannel));
             if (uploadChannel) {
@@ -9022,18 +9045,18 @@ nsDocShell::OnNewURI(nsIURI * aURI, nsIC
         (aLoadType == LOAD_RELOAD_BYPASS_CACHE ||
          aLoadType == LOAD_RELOAD_BYPASS_PROXY ||
          aLoadType == LOAD_RELOAD_BYPASS_PROXY_AND_CACHE)) {
         NS_ASSERTION(!updateHistory,
                      "We shouldn't be updating history for forced reloads!");
         
         nsCOMPtr<nsICachingChannel> cacheChannel(do_QueryInterface(aChannel));
         nsCOMPtr<nsISupports>  cacheKey;
-        // Get the Cache Key  and store it in SH.         
-        if (cacheChannel) 
+        // Get the Cache Key and store it in SH.
+        if (cacheChannel)
             cacheChannel->GetCacheKey(getter_AddRefs(cacheKey));
         // If we already have a loading history entry, store the new cache key
         // in it.  Otherwise, since we're doing a reload and won't be updating
         // our history entry, store the cache key in our current history entry.
         if (mLSHE)
             mLSHE->SetCacheKey(cacheKey);
         else if (mOSHE)
             mOSHE->SetCacheKey(cacheKey);
@@ -9045,20 +9068,32 @@ nsDocShell::OnNewURI(nsIURI * aURI, nsIC
             /* This is  a fresh page getting loaded for the first time
              *.Create a Entry for it and add it to SH, if this is the
              * rootDocShell
              */
             (void) AddToSessionHistory(aURI, aChannel, aOwner,
                                        getter_AddRefs(mLSHE));
         }
 
-        // Update Global history
         if (aAddToGlobalHistory) {
-            // Get the referrer uri from the channel
-            AddToGlobalHistory(aURI, PR_FALSE, aChannel);
+            // If this is a POST request, we do not want to include this in global
+            // history.
+            if (!ChannelIsPost(aChannel)) {
+                nsCOMPtr<nsIURI> previousURI;
+                PRUint32 previousFlags = 0;
+                ExtractLastVisit(aChannel, getter_AddRefs(previousURI),
+                                 &previousFlags);
+
+                nsCOMPtr<nsIURI> referrer;
+                // Treat referrer as null if there is an error getting it.
+                (void)NS_GetReferrerFromChannel(aChannel,
+                                                getter_AddRefs(referrer));
+
+                AddURIVisit(aURI, referrer, previousURI, previousFlags);
+            }
         }
     }
 
     // If this was a history load, update the index in 
     // SH. 
     if (rootSH && (mLoadType & LOAD_CMD_HISTORY)) {
         nsCOMPtr<nsISHistoryInternal> shInternal(do_QueryInterface(rootSH));
         if (shInternal) {
@@ -9367,17 +9402,17 @@ nsDocShell::AddState(nsIVariant *aData, 
     // We need to call FireOnLocationChange so that the browser's address bar
     // gets updated and the back button is enabled, but we only need to
     // explicitly call FireOnLocationChange if we're not calling SetCurrentURI,
     // since SetCurrentURI will call FireOnLocationChange for us.
     if (!equalURIs) {
         SetCurrentURI(newURI, nsnull, PR_TRUE);
         document->SetDocumentURI(newURI);
 
-        AddToGlobalHistory(newURI, PR_FALSE, oldURI);
+        AddURIVisit(newURI, oldURI, oldURI, 0);
     }
     else {
         FireOnLocationChange(this, nsnull, mCurrentURI);
     }
 
     // Try to set the title of the current history element
     if (mOSHE)
         mOSHE->SetTitle(aTitle);
@@ -10063,63 +10098,119 @@ NS_IMETHODIMP nsDocShell::GetHasEditingS
 NS_IMETHODIMP nsDocShell::MakeEditable(PRBool inWaitForUriLoad)
 {
   nsresult rv = EnsureEditorData();
   if (NS_FAILED(rv)) return rv;
 
   return mEditorData->MakeEditable(inWaitForUriLoad);
 }
 
-nsresult
-nsDocShell::AddToGlobalHistory(nsIURI * aURI, PRBool aRedirect,
-                               nsIChannel * aChannel)
-{
-    // If this is a POST request, we do not want to include this in global
-    // history, so return early.
-    nsCOMPtr<nsIHttpChannel> hchan(do_QueryInterface(aChannel));
-    if (hchan) {
-        nsCAutoString type;
-        nsresult rv = hchan->GetRequestMethod(type);
-        if (NS_SUCCEEDED(rv) && type.EqualsLiteral("POST"))
-            return NS_OK;
-    }
-
-    nsCOMPtr<nsIURI> referrer;
-    if (aChannel)
-        NS_GetReferrerFromChannel(aChannel, getter_AddRefs(referrer));
-
-    return AddToGlobalHistory(aURI, aRedirect, referrer);
-}
-
-nsresult
-nsDocShell::AddToGlobalHistory(nsIURI * aURI, PRBool aRedirect,
-                               nsIURI * aReferrer)
-{
-    if (mItemType != typeContent || !mGlobalHistory)
-        return NS_OK;
-
-    PRBool visited;
-    nsresult rv = mGlobalHistory->IsVisited(aURI, &visited);
-    if (NS_FAILED(rv))
-        return rv;
-
-    rv = mGlobalHistory->AddURI(aURI, aRedirect, !IsFrame(), aReferrer);
-    if (NS_FAILED(rv))
-        return rv;
-
-    if (!visited) {
-        nsCOMPtr<nsIObserverService> obsService =
-            mozilla::services::GetObserverService();
-        if (obsService) {
-            obsService->NotifyObservers(aURI, NS_LINK_VISITED_EVENT_TOPIC, nsnull);
-        }
-    }
-
-    return NS_OK;
-
+bool
+nsDocShell::ChannelIsPost(nsIChannel* aChannel)
+{
+    nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel));
+    if (!httpChannel) {
+        return false;
+    }
+
+    nsCAutoString method;
+    httpChannel->GetRequestMethod(method);
+    return method.Equals("POST");
+}
+
+void
+nsDocShell::ExtractLastVisit(nsIChannel* aChannel,
+                             nsIURI** aURI,
+                             PRUint32* aChannelRedirectFlags)
+{
+    nsCOMPtr<nsIPropertyBag2> props(do_QueryInterface(aChannel));
+    if (!props) {
+        return;
+    }
+
+    nsresult rv = props->GetPropertyAsInterface(
+        NS_LITERAL_STRING("docshell.previousURI"),
+        NS_GET_IID(nsIURI),
+        reinterpret_cast<void**>(aURI)
+    );
+
+    if (NS_FAILED(rv)) {
+        // There is no last visit for this channel, so this must be the first
+        // link.  Link the visit to the referrer of this request, if any.
+        // Treat referrer as null if there is an error getting it.
+        (void)NS_GetReferrerFromChannel(aChannel, aURI);
+    }
+    else {
+      rv = props->GetPropertyAsUint32(
+          NS_LITERAL_STRING("docshell.previousFlags"),
+          aChannelRedirectFlags
+      );
+
+      NS_WARN_IF_FALSE(
+          NS_FAILED(rv),
+          "Could not fetch previous flags, URI will be treated like referrer"
+      );
+    }
+}
+
+void
+nsDocShell::SaveLastVisit(nsIChannel* aChannel,
+                          nsIURI* aURI,
+                          PRUint32 aChannelRedirectFlags)
+{
+    nsCOMPtr<nsIWritablePropertyBag2> props(do_QueryInterface(aChannel));
+    if (!props || !aURI) {
+        return;
+    }
+
+    props->SetPropertyAsInterface(NS_LITERAL_STRING("docshell.previousURI"),
+                                  aURI);
+    props->SetPropertyAsUint32(NS_LITERAL_STRING("docshell.previousFlags"),
+                               aChannelRedirectFlags);
+}
+
+void
+nsDocShell::AddURIVisit(nsIURI* aURI,
+                        nsIURI* aReferrerURI,
+                        nsIURI* aPreviousURI,
+                        PRUint32 aChannelRedirectFlags)
+{
+    NS_ASSERTION(aURI, "Visited URI is null!");
+
+    // Only content-type docshells save URI visits.
+    if (mItemType != typeContent) {
+        return;
+    }
+
+    nsCOMPtr<IHistory> history = services::GetHistoryService();
+
+    if (history) {
+        PRUint32 visitURIFlags = 0;
+
+        if (!IsFrame()) {
+            visitURIFlags |= IHistory::TOP_LEVEL;
+        }
+
+        if (aChannelRedirectFlags & nsIChannelEventSink::REDIRECT_TEMPORARY) {
+            visitURIFlags |= IHistory::REDIRECT_TEMPORARY;
+        }
+        else if (aChannelRedirectFlags &
+                 nsIChannelEventSink::REDIRECT_PERMANENT) {
+            visitURIFlags |= IHistory::REDIRECT_PERMANENT;
+        }
+
+        (void)history->VisitURI(aURI, aPreviousURI, visitURIFlags);
+    }
+    else if (mGlobalHistory) {
+        // Falls back to sync global history interface.
+        (void)mGlobalHistory->AddURI(aURI,
+                                     !!aChannelRedirectFlags,
+                                     !IsFrame(),
+                                     aReferrerURI);
+    }
 }
 
 //*****************************************************************************
 // nsDocShell: Helper Routines
 //*****************************************************************************
 
 NS_IMETHODIMP
 nsDocShell::SetLoadType(PRUint32 aLoadType)
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -428,22 +428,87 @@ protected:
 
     // overridden from nsDocLoader, this provides more information than the
     // normal OnStateChange with flags STATE_REDIRECTING
     virtual void OnRedirectStateChange(nsIChannel* aOldChannel,
                                        nsIChannel* aNewChannel,
                                        PRUint32 aRedirectFlags,
                                        PRUint32 aStateFlags);
 
-    // Global History
+    /**
+     * Helper function that determines if channel is an HTTP POST.
+     *
+     * @param aChannel
+     *        The channel to test
+     *
+     * @return True iff channel is an HTTP post.
+     */
+    bool ChannelIsPost(nsIChannel* aChannel);
+
+    /**
+     * Helper function that finds the last URI and its transition flags for a
+     * channel.
+     *
+     * This method first checks the channel's property bag to see if previous
+     * info has been saved.  If not, it gives back the referrer of the channel.
+     *
+     * @param aChannel
+     *        The channel we are transitioning to
+     * @param aURI
+     *        Output parameter with the previous URI, not addref'd
+     * @param aChannelRedirectFlags
+     *        If a redirect, output parameter with the previous redirect flags
+     *        from nsIChannelEventSink
+     */
+    void ExtractLastVisit(nsIChannel* aChannel,
+                          nsIURI** aURI,
+                          PRUint32* aChannelRedirectFlags);
 
-    nsresult AddToGlobalHistory(nsIURI * aURI, PRBool aRedirect,
-                                nsIChannel * aChannel);
-    nsresult AddToGlobalHistory(nsIURI * aURI, PRBool aRedirect,
-                                nsIURI * aReferrer);
+    /**
+     * Helper function that caches a URI and a transition for saving later.
+     *
+     * @param aChannel
+     *        Channel that will have these properties saved
+     * @param aURI
+     *        The URI to save for later
+     * @param aChannelRedirectFlags
+     *        The nsIChannelEventSink redirect flags to save for later
+     */
+    void SaveLastVisit(nsIChannel* aChannel,
+                       nsIURI* aURI,
+                       PRUint32 aChannelRedirectFlags);
+
+    /**
+     * Helper function for adding a URI visit using IHistory.  If IHistory is
+     * not available, the method tries nsIGlobalHistory2.
+     *
+     * The IHistory API maintains chains of visits, tracking both HTTP referrers
+     * and redirects for a user session. VisitURI requires the current URI and
+     * the previous URI in the chain.
+     *
+     * Visits can be saved either during a redirect or when the request has
+     * reached its final destination.  The previous URI in the visit may be
+     * from another redirect or it may be the referrer.
+     *
+     * @pre aURI is not null.
+     *
+     * @param aURI
+     *        The URI that was just visited
+     * @param aReferrerURI
+     *        The referrer URI of this request
+     * @param aPreviousURI
+     *        The previous URI of this visit (may be the same as aReferrerURI)
+     * @param aChannelRedirectFlags
+     *        For redirects, the redirect flags from nsIChannelEventSink
+     *        (0 otherwise)
+     */
+    void AddURIVisit(nsIURI* aURI,
+                     nsIURI* aReferrerURI,
+                     nsIURI* aPreviousURI,
+                     PRUint32 aChannelRedirectFlags);
 
     // Helper Routines
     nsresult   ConfirmRepost(PRBool * aRepost);
     NS_IMETHOD GetPromptAndStringBundle(nsIPrompt ** aPrompt,
         nsIStringBundle ** aStringBundle);
     NS_IMETHOD GetChildOffset(nsIDOMNode * aChild, nsIDOMNode * aParent,
         PRInt32 * aOffset);
     nsIScrollableFrame* GetRootScrollFrame();
@@ -695,16 +760,19 @@ protected:
     // Try not to use it, we should get rid of it.
     PRUint32                   mChildOffset;
     PRUint32                   mBusyFlags;
     PRUint32                   mAppType;
     PRUint32                   mLoadType;
 
     PRInt32                    mMarginWidth;
     PRInt32                    mMarginHeight;
+
+    // This can either be a content docshell or a chrome docshell.  After
+    // Create() is called, the type is not expected to change.
     PRInt32                    mItemType;
 
     // Index into the SHTransaction list, indicating the previous and current
     // transaction at the time that this DocShell begins to load
     PRInt32                    mPreviousTransIndex;
     PRInt32                    mLoadedTransIndex;
 
     PRPackedBool               mAllowSubframes;
--- a/toolkit/components/places/src/Helpers.cpp
+++ b/toolkit/components/places/src/Helpers.cpp
@@ -55,17 +55,17 @@ AsyncStatementCallback::HandleError(mozI
   nsresult rv = aError->GetResult(&result);
   NS_ENSURE_SUCCESS(rv, rv);
   nsCAutoString message;
   rv = aError->GetMessage(message);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCAutoString warnMsg;
   warnMsg.Append("An error occurred while executing an async statement: ");
-  warnMsg.Append(result);
+  warnMsg.AppendInt(result);
   warnMsg.Append(" ");
   warnMsg.Append(message);
   NS_WARNING(warnMsg.get());
 #endif
 
   return NS_OK;
 }
 
@@ -175,11 +175,38 @@ URIBinder::Bind(mozIStorageBindingParams
     aName, StringHead(aURLString, URI_LENGTH_MAX)
   );
   NS_ENSURE_SUCCESS(rv, rv);
   return NS_OK;
 }
 
 #undef URI_TO_URLCSTRING
 
+nsresult
+GetReversedHostname(nsIURI* aURI, nsString& aRevHost)
+{
+  nsCAutoString forward8;
+  nsresult rv = aURI->GetHost(forward8);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // can't do reversing in UTF8, better use 16-bit chars
+  GetReversedHostname(NS_ConvertUTF8toUTF16(forward8), aRevHost);
+  return NS_OK;
+}
+
+void
+GetReversedHostname(const nsString& aForward, nsString& aRevHost)
+{
+  ReverseString(aForward, aRevHost);
+  aRevHost.Append(PRUnichar('.'));
+}
+
+void
+ReverseString(const nsString& aInput, nsString& aReversed)
+{
+  aReversed.Truncate(0);
+  for (PRInt32 i = aInput.Length() - 1; i >= 0; i--) {
+    aReversed.Append(aInput[i]);
+  }
+}
 
 } // namespace places
 } // namespace mozilla
--- a/toolkit/components/places/src/Helpers.h
+++ b/toolkit/components/places/src/Helpers.h
@@ -131,13 +131,51 @@ public:
                        const nsACString& aName,
                        nsIURI* aURI);
   // Bind URLCString to params by name.
   static nsresult Bind(mozIStorageBindingParams* aParams,
                        const nsACString& aName,
                        const nsACString& aURLString);
 };
 
+/**
+ * This extracts the hostname from the URI and reverses it in the
+ * form that we use (always ending with a "."). So
+ * "http://microsoft.com/" becomes "moc.tfosorcim."
+ * 
+ * The idea behind this is that we can create an index over the items in
+ * the reversed host name column, and then query for as much or as little
+ * of the host name as we feel like.
+ * 
+ * For example, the query "host >= 'gro.allizom.' AND host < 'gro.allizom/'
+ * Matches all host names ending in '.mozilla.org', including
+ * 'developer.mozilla.org' and just 'mozilla.org' (since we define all
+ * reversed host names to end in a period, even 'mozilla.org' matches).
+ * The important thing is that this operation uses the index. Any substring
+ * calls in a select statement (even if it's for the beginning of a string)
+ * will bypass any indices and will be slow).
+ *
+ * @param aURI
+ *        URI that contains spec to reverse
+ * @param aRevHost
+ *        Out parameter
+ */
+nsresult GetReversedHostname(nsIURI* aURI, nsString& aRevHost);
+
+/**
+ * Similar method to GetReversedHostName but for strings
+ */
+void GetReversedHostname(const nsString& aForward, nsString& aRevHost);
+
+/**
+ * Reverses a string.
+ *
+ * @param aInput
+ *        The string to be reversed
+ * @param aReversed
+ *        Ouput parameter will contain the reversed string
+ */
+void ReverseString(const nsString& aInput, nsString& aReversed);
 
 } // namespace places
 } // namespace mozilla
 
 #endif // mozilla_places_Helpers_h_
--- a/toolkit/components/places/src/History.cpp
+++ b/toolkit/components/places/src/History.cpp
@@ -34,16 +34,17 @@
  * 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 "nsNavBookmarks.h"
 #include "Helpers.h"
 
 #include "mozilla/storage.h"
 #include "mozilla/dom/Link.h"
 #include "nsDocShellCID.h"
 #include "nsIEventStateManager.h"
 #include "mozilla/Services.h"
 
@@ -53,30 +54,111 @@ namespace mozilla {
 namespace places {
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Global Defines
 
 #define URI_VISITED "visited"
 #define URI_NOT_VISITED "not visited"
 #define URI_VISITED_RESOLUTION_TOPIC "visited-status-resolution"
+// Observer event fired after a visit has been registered in the DB.
+#define URI_VISIT_SAVED "uri-visit-saved"
+
+////////////////////////////////////////////////////////////////////////////////
+//// Step
+
+class Step : public AsyncStatementCallback
+{
+public:
+  /**
+   * Executes statement asynchronously using this as a callback.
+   * 
+   * @param aStmt
+   *        Statement to execute asynchronously
+   */
+  NS_IMETHOD ExecuteAsync(mozIStorageStatement* aStmt);
+
+  /**
+   * Called once after query is completed.  If your query has more than one
+   * result set to process, you will want to override HandleResult to process
+   * each one.
+   *
+   * @param aResultSet
+   *        Results from ExecuteAsync
+   *        Unlike HandleResult, this *can be NULL* if there were no results.
+   */
+  NS_IMETHOD Callback(mozIStorageResultSet* aResultSet);
+
+  /**
+   * By default, stores the last result set received in mResultSet.
+   * For queries with only one result set, you don't need to override.
+   *
+   * @param aResultSet
+   *        Results from ExecuteAsync
+   */
+  NS_IMETHOD HandleResult(mozIStorageResultSet* aResultSet);
+
+  /**
+   * By default, this calls Callback with any saved results from HandleResult.
+   * For queries with only one result set, you don't need to override.
+   *
+   * @param aReason
+   *        SQL status code
+   */
+  NS_IMETHOD HandleCompletion(PRUint16 aReason);
+
+private:
+  // Used by HandleResult to cache results until HandleCompletion is called.
+  nsCOMPtr<mozIStorageResultSet> mResultSet;
+};
+
+NS_IMETHODIMP
+Step::ExecuteAsync(mozIStorageStatement* aStmt)
+{
+  nsCOMPtr<mozIStoragePendingStatement> handle;
+  nsresult rv = aStmt->ExecuteAsync(this, getter_AddRefs(handle));
+  NS_ENSURE_SUCCESS(rv, rv);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+Step::Callback(mozIStorageResultSet* aResultSet)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+Step::HandleResult(mozIStorageResultSet* aResultSet)
+{
+  mResultSet = aResultSet;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+Step::HandleCompletion(PRUint16 aReason)
+{
+  nsCOMPtr<mozIStorageResultSet> resultSet = mResultSet;
+  mResultSet = NULL;
+  Callback(resultSet);
+  return NS_OK;
+}
 
 ////////////////////////////////////////////////////////////////////////////////
 //// 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!");
+    NS_PRECONDITION(aURI, "Null URI");
 
     nsNavHistory* navHist = nsNavHistory::GetHistoryService();
     NS_ENSURE_TRUE(navHist, NS_ERROR_FAILURE);
     mozIStorageStatement* stmt = navHist->GetStatementById(DB_IS_PAGE_VISITED);
     NS_ENSURE_STATE(stmt);
 
     // Bind by index for performance.
     nsresult rv = URIBinder::Bind(stmt, 0, aURI);
@@ -139,41 +221,555 @@ private:
   nsCOMPtr<nsIURI> mURI;
   bool mIsVisited;
 };
 NS_IMPL_ISUPPORTS1(
   VisitedQuery,
   mozIStorageStatementCallback
 )
 
+/**
+ * Fail-safe mechanism for ensuring that your task completes, no matter what.
+ * Pass this around as an nsAutoPtr in your steps to guarantee that when all
+ * your steps are finished, your task is finished.
+ */
+class FailSafeFinishTask
+{
+public:
+  ~FailSafeFinishTask() {
+    History::GetService()->CurrentTaskFinished();
+  }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// Steps for VisitURI
+
+struct VisitURIData : public FailSafeFinishTask
+{
+  PRInt64 placeId;
+  PRInt32 hidden;
+  PRInt32 typed;
+  nsCOMPtr<nsIURI> uri;
+
+  // Url of last added visit in chain.
+  nsCString lastSpec;
+  PRInt64 lastVisitId;
+  PRInt32 transitionType;
+  PRInt64 sessionId;
+  PRTime dateTime;
+};
+
+/**
+ * Step 6: Update frecency of URI and notify observers.
+ */
+class UpdateFrecencyAndNotifyStep : public Step
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  UpdateFrecencyAndNotifyStep(nsAutoPtr<VisitURIData> aData)
+  : mData(aData)
+  {
+  }
+
+  NS_IMETHOD Callback(mozIStorageResultSet* aResultSet)
+  {
+    // Result set contains new visit created in earlier step
+    NS_ENSURE_STATE(aResultSet);
+
+    nsCOMPtr<mozIStorageRow> row;
+    nsresult rv = aResultSet->GetNextRow(getter_AddRefs(row));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    PRInt64 visitId;
+    rv = row->GetInt64(0, &visitId);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    // TODO need to figure out story for not synchronous frecency updating
+    // (bug 556631)
+
+    // Swallow errors here, since if we've gotten this far, it's more
+    // important to notify the observers below.
+    nsNavHistory* history = nsNavHistory::GetHistoryService();
+    NS_WARN_IF_FALSE(history, "Could not get history service");
+    nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
+    NS_WARN_IF_FALSE(bookmarks, "Could not get bookmarks service");
+    if (history && bookmarks) {
+      // Update frecency *after* the visit info is in the db
+      nsresult rv = history->UpdateFrecency(
+        mData->placeId,
+        bookmarks->IsRealBookmark(mData->placeId)
+      );
+      NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Could not update frecency");
+
+      // Notify nsNavHistory observers of visit, but only for certain types of
+      // visits to maintain consistency with nsNavHistory::GetQueryResults.
+      if (!mData->hidden &&
+          mData->transitionType != nsINavHistoryService::TRANSITION_EMBED &&
+          mData->transitionType != nsINavHistoryService::TRANSITION_FRAMED_LINK) {
+        history->FireOnVisit(mData->uri, visitId, mData->dateTime,
+                             mData->sessionId, mData->lastVisitId,
+                             mData->transitionType);
+      }
+    }
+
+    nsCOMPtr<nsIObserverService> obsService =
+      mozilla::services::GetObserverService();
+    if (obsService) {
+      nsresult rv = obsService->NotifyObservers(mData->uri, URI_VISIT_SAVED, nsnull);
+      NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Could not notify observers");
+    }
+
+    History::GetService()->NotifyVisited(mData->uri);
+
+    return NS_OK;
+  }
+
+protected:
+  nsAutoPtr<VisitURIData> mData;
+};
+NS_IMPL_ISUPPORTS1(
+  UpdateFrecencyAndNotifyStep
+, mozIStorageStatementCallback
+)
+
+/**
+ * Step 5: Get newly created visit ID from moz_history_visits table.
+ */
+class GetVisitIDStep : public Step
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  GetVisitIDStep(nsAutoPtr<VisitURIData> aData)
+  : mData(aData)
+  {
+  }
+
+  NS_IMETHOD Callback(mozIStorageResultSet* aResultSet)
+  {
+    // Find visit ID, needed for notifying observers in next step.
+    nsNavHistory* history = nsNavHistory::GetHistoryService();
+    NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
+    nsCOMPtr<mozIStorageStatement> stmt =
+      history->GetStatementById(DB_RECENT_VISIT_OF_URL);
+    NS_ENSURE_STATE(stmt);
+
+    nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), mData->uri);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsCOMPtr<Step> step = new UpdateFrecencyAndNotifyStep(mData);
+    rv = step->ExecuteAsync(stmt);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    return NS_OK;
+  }
+
+protected:
+  nsAutoPtr<VisitURIData> mData;
+};
+NS_IMPL_ISUPPORTS1(
+  GetVisitIDStep
+, mozIStorageStatementCallback
+)
+
+/**
+ * Step 4: Add visit to moz_history_visits table.
+ */
+class AddVisitStep : public Step
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  AddVisitStep(nsAutoPtr<VisitURIData> aData)
+  : mData(aData)
+  {
+  }
+
+  NS_IMETHOD Callback(mozIStorageResultSet* aResultSet)
+  {
+    nsresult rv;
+
+    nsNavHistory* history = nsNavHistory::GetHistoryService();
+    NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
+
+    // TODO need to figure out story for new session IDs that isn't synchronous
+    // (bug 561450)
+
+    if (aResultSet) {
+      // Result set contains last visit information for this session
+      nsCOMPtr<mozIStorageRow> row;
+      rv = aResultSet->GetNextRow(getter_AddRefs(row));
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      PRInt64 possibleSessionId;
+      PRTime lastVisitOfSession;
+
+      rv = row->GetInt64(0, &mData->lastVisitId);
+      NS_ENSURE_SUCCESS(rv, rv);
+      rv = row->GetInt64(1, &possibleSessionId);
+      NS_ENSURE_SUCCESS(rv, rv);
+      rv = row->GetInt64(2, &lastVisitOfSession);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      if (mData->dateTime - lastVisitOfSession <= RECENT_EVENT_THRESHOLD) {
+        mData->sessionId = possibleSessionId;
+      }
+      else {
+        // Session is too old. Start a new one.
+        mData->sessionId = history->GetNewSessionID();
+        mData->lastVisitId = 0;
+      }
+    }
+    else {
+      // No previous saved visit entry could be found, so start a new session.
+      mData->sessionId = history->GetNewSessionID();
+      mData->lastVisitId = 0;
+    }
+
+    nsCOMPtr<mozIStorageStatement> stmt =
+      history->GetStatementById(DB_INSERT_VISIT);
+    NS_ENSURE_STATE(stmt);
+
+    rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("from_visit"),
+                               mData->lastVisitId);
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"),
+                               mData->placeId);
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("visit_date"),
+                               mData->dateTime);
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("visit_type"),
+                               mData->transitionType);
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("session"),
+                               mData->sessionId);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsCOMPtr<Step> step = new GetVisitIDStep(mData);
+    rv = step->ExecuteAsync(stmt);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    return NS_OK;
+  }
+
+protected:
+  nsAutoPtr<VisitURIData> mData;
+};
+NS_IMPL_ISUPPORTS1(
+  AddVisitStep
+, mozIStorageStatementCallback
+)
+
+/**
+ * Step 3: Callback for inserting or updating a moz_places entry.
+ *         This step checks database for the last visit in session.
+ */
+class CheckLastVisitStep : public Step
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  CheckLastVisitStep(nsAutoPtr<VisitURIData> aData)
+  : mData(aData)
+  {
+  }
+
+  NS_IMETHOD Callback(mozIStorageResultSet* aResultSet)
+  {
+    nsresult rv;
+
+    if (aResultSet) {
+      // Last step inserted a new URL. This query contains the id.
+      nsCOMPtr<mozIStorageRow> row;
+      rv = aResultSet->GetNextRow(getter_AddRefs(row));
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      rv = row->GetInt64(0, &mData->placeId);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+
+    if (!mData->lastSpec.IsEmpty()) {
+      // Find last visit ID and session ID using lastSpec so we can add them
+      // to a browsing session if the visit was recent.
+      nsNavHistory* history = nsNavHistory::GetHistoryService();
+      NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
+      nsCOMPtr<mozIStorageStatement> stmt =
+        history->GetStatementById(DB_RECENT_VISIT_OF_URL);
+      NS_ENSURE_STATE(stmt);
+
+      rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), mData->lastSpec);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      nsCOMPtr<Step> step = new AddVisitStep(mData);
+      rv = step->ExecuteAsync(stmt);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+    else {
+      // Empty lastSpec.
+      // Not part of a session.  Just run next step's callback with no results.
+      nsCOMPtr<Step> step = new AddVisitStep(mData);
+      rv = step->Callback(NULL);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+
+    return NS_OK;
+  }
+
+protected:
+  nsAutoPtr<VisitURIData> mData;
+};
+NS_IMPL_ISUPPORTS1(
+  CheckLastVisitStep
+, mozIStorageStatementCallback
+)
+
+/**
+ * Step 2a: Called only when a new entry is put into moz_places.
+ *          Finds the ID of a recently inserted place.
+ */
+class FindNewIdStep : public Step
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  FindNewIdStep(nsAutoPtr<VisitURIData> aData)
+  : mData(aData)
+  {
+  }
+
+  NS_IMETHOD Callback(mozIStorageResultSet* aResultSet)
+  {
+    nsNavHistory* history = nsNavHistory::GetHistoryService();
+    NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
+    nsCOMPtr<mozIStorageStatement> stmt =
+      history->GetStatementById(DB_GET_PAGE_VISIT_STATS);
+    NS_ENSURE_STATE(stmt);
+
+    nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), mData->uri);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsCOMPtr<Step> step = new CheckLastVisitStep(mData);
+    rv = step->ExecuteAsync(stmt);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    return NS_OK;
+  }
+
+protected:
+  nsAutoPtr<VisitURIData> mData;
+};
+NS_IMPL_ISUPPORTS1(
+  FindNewIdStep
+, mozIStorageStatementCallback
+)
+
+/**
+ * Step 2: Callback for checking for an existing URI in moz_places.
+ *         This step inserts or updates the URI accordingly.
+ */
+class CheckExistingStep : public Step
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  CheckExistingStep(nsAutoPtr<VisitURIData> aData)
+  : mData(aData)
+  {
+  }
+
+  NS_IMETHOD Callback(mozIStorageResultSet* aResultSet)
+  {
+    nsresult rv;
+    nsCOMPtr<mozIStorageStatement> stmt;
+
+    nsNavHistory* history = nsNavHistory::GetHistoryService();
+    NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
+
+    if (aResultSet) {
+      nsCOMPtr<mozIStorageRow> row;
+      rv = aResultSet->GetNextRow(getter_AddRefs(row));
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      rv = row->GetInt64(0, &mData->placeId);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      if (!mData->typed) {
+        // If this transition wasn't typed, others might have been. If database
+        // has location as typed, reflect that in our data structure.
+        rv = row->GetInt32(2, &mData->typed);
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+      if (mData->hidden) {
+        // If this transition was hidden, it is possible that others were not.
+        // Any one visible transition makes this location visible. If database
+        // has location as visible, reflect that in our data structure.
+        rv = row->GetInt32(3, &mData->hidden);
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+
+      // Note: trigger will update visit_count.
+      stmt = history->GetStatementById(DB_UPDATE_PAGE_VISIT_STATS);
+      NS_ENSURE_STATE(stmt);
+
+      rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("typed"), mData->typed);
+      NS_ENSURE_SUCCESS(rv, rv);
+      rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("hidden"), mData->hidden);
+      NS_ENSURE_SUCCESS(rv, rv);
+      rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), mData->placeId);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      nsCOMPtr<Step> step = new CheckLastVisitStep(mData);
+      rv = step->ExecuteAsync(stmt);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+    else {
+      // No entry exists, so create one.
+      stmt = history->GetStatementById(DB_ADD_NEW_PAGE);
+      NS_ENSURE_STATE(stmt);
+
+      nsAutoString revHost;
+      rv = GetReversedHostname(mData->uri, revHost);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), mData->uri);
+      NS_ENSURE_SUCCESS(rv, rv);
+      rv = stmt->BindStringByName(NS_LITERAL_CSTRING("rev_host"), revHost);
+      NS_ENSURE_SUCCESS(rv, rv);
+      rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("typed"), mData->typed);
+      NS_ENSURE_SUCCESS(rv, rv);
+      rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("hidden"), mData->hidden);
+      NS_ENSURE_SUCCESS(rv, rv);
+      rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("frecency"), -1);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      nsCOMPtr<Step> step = new FindNewIdStep(mData);
+      rv = step->ExecuteAsync(stmt);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+
+    return NS_OK;
+  }
+
+protected:
+  nsAutoPtr<VisitURIData> mData;
+};
+NS_IMPL_ISUPPORTS1(
+  CheckExistingStep
+, mozIStorageStatementCallback
+)
+
+/**
+ * Step 1: See if there is an existing URI.
+ */
+class StartVisitURIStep : public Step
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  StartVisitURIStep(nsAutoPtr<VisitURIData> aData)
+  : mData(aData)
+  {
+  }
+
+  NS_IMETHOD Callback(mozIStorageResultSet* aResultSet)
+  {
+    nsNavHistory* history = nsNavHistory::GetHistoryService();
+
+    // Find existing entry in moz_places table, if any.
+    nsCOMPtr<mozIStorageStatement> stmt =
+      history->GetStatementById(DB_GET_PAGE_VISIT_STATS);
+    NS_ENSURE_STATE(stmt);
+
+    nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), mData->uri);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsCOMPtr<Step> step = new CheckExistingStep(mData);
+    rv = step->ExecuteAsync(stmt);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    return NS_OK;
+  }
+
+protected:
+  nsAutoPtr<VisitURIData> mData;
+};
+NS_IMPL_ISUPPORTS1(
+  StartVisitURIStep
+, Step
+)
+
 } // anonymous namespace
 
 ////////////////////////////////////////////////////////////////////////////////
 //// History
 
 History* History::gService = NULL;
 
 History::History()
+: mShuttingDown(false)
 {
   NS_ASSERTION(!gService, "Ruh-roh!  This service has already been created!");
   gService = this;
+
+  nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+  NS_WARN_IF_FALSE(os, "Observer service was not found!");
+  if (os) {
+    (void)os->AddObserver(this, TOPIC_PLACES_SHUTDOWN, PR_FALSE);
+  }
 }
 
 History::~History()
 {
   gService = NULL;
+
 #ifdef DEBUG
   if (mObservers.IsInitialized()) {
     NS_ASSERTION(mObservers.Count() == 0,
                  "Not all Links were removed before we disappear!");
   }
+
+  NS_ASSERTION(mShuttingDown, "Did not receive Places shutdown event");
 #endif
 }
 
 void
+History::AppendTask(Step* aTask)
+{
+  NS_PRECONDITION(aTask, "Got NULL task.");
+
+  if (mShuttingDown) {
+    return;
+  }
+
+  NS_ADDREF(aTask);
+  mPendingVisits.Push(aTask);
+
+  if (mPendingVisits.GetSize() == 1) {
+    // There are no other pending tasks.
+    StartNextTask();
+  }
+}
+
+void
+History::CurrentTaskFinished()
+{
+  if (mShuttingDown) {
+    return;
+  }
+
+  NS_ASSERTION(mPendingVisits.PeekFront(), "Tried to finish task not on the queue");
+
+  nsCOMPtr<Step> deadTaskWalking =
+    dont_AddRef(static_cast<Step*>(mPendingVisits.PopFront()));
+  StartNextTask();
+}
+
+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;
@@ -223,20 +819,125 @@ History::GetSingleton()
     gService = new History();
     NS_ENSURE_TRUE(gService, nsnull);
   }
 
   NS_ADDREF(gService);
   return gService;
 }
 
+void
+History::StartNextTask()
+{
+  if (mShuttingDown) {
+    return;
+  }
+
+  nsCOMPtr<Step> nextTask =
+    static_cast<Step*>(mPendingVisits.PeekFront());
+  if (!nextTask) {
+    // No more pending visits left to process.
+    return;
+  }
+  nsresult rv = nextTask->Callback(NULL);
+  NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Beginning a task failed.");
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 //// IHistory
 
 NS_IMETHODIMP
+History::VisitURI(nsIURI* aURI,
+                  nsIURI* aLastVisitedURI,
+                  PRUint32 aFlags)
+{
+  NS_PRECONDITION(aURI, "URI should not be NULL.");
+  if (mShuttingDown) {
+    return NS_OK;
+  }
+
+  nsNavHistory* history = nsNavHistory::GetHistoryService();
+  NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
+
+  // Silently return if URI is something we shouldn't add to DB.
+  PRBool canAdd;
+  nsresult rv = history->CanAddURI(aURI, &canAdd);
+  NS_ENSURE_SUCCESS(rv, rv);
+  if (!canAdd) {
+    return NS_OK;
+  }
+
+  // Populate data structure that will be used in our async SQL steps.
+  nsAutoPtr<VisitURIData> data(new VisitURIData());
+
+  nsCAutoString spec;
+  rv = aURI->GetSpec(spec);
+  NS_ENSURE_SUCCESS(rv, rv);
+  if (aLastVisitedURI) {
+    rv = aLastVisitedURI->GetSpec(data->lastSpec);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  if (spec.Equals(data->lastSpec)) {
+    // Do not save refresh-page visits.
+    return NS_OK;
+  }
+
+  // Assigns a type to the edge in the visit linked list. Each type will be
+  // considered differently when weighting the frecency of a location.
+  PRUint32 recentFlags = history->GetRecentFlags(aURI);
+  bool redirected = false;
+  if (aFlags & IHistory::REDIRECT_TEMPORARY) {
+    data->transitionType = nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY;
+    redirected = true;
+  }
+  else if (aFlags & IHistory::REDIRECT_PERMANENT) {
+    data->transitionType = nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT;
+    redirected = true;
+  }
+  else if (recentFlags & nsNavHistory::RECENT_TYPED) {
+    data->transitionType = nsINavHistoryService::TRANSITION_TYPED;
+  }
+  else if (recentFlags & nsNavHistory::RECENT_BOOKMARKED) {
+    data->transitionType = nsINavHistoryService::TRANSITION_BOOKMARK;
+  }
+  else if (aFlags & IHistory::TOP_LEVEL) {
+    // User was redirected or link was clicked in the main window.
+    data->transitionType = nsINavHistoryService::TRANSITION_LINK;
+  }
+  else if (recentFlags & nsNavHistory::RECENT_ACTIVATED) {
+    // User activated a link in a frame.
+    data->transitionType = nsINavHistoryService::TRANSITION_FRAMED_LINK;
+  }
+  else {
+    // A frame redirected to a new site without user interaction.
+    data->transitionType = nsINavHistoryService::TRANSITION_EMBED;
+  }
+
+  data->typed = (data->transitionType == nsINavHistoryService::TRANSITION_TYPED) ? 1 : 0;
+  data->hidden = 
+    (data->transitionType == nsINavHistoryService::TRANSITION_FRAMED_LINK ||
+     data->transitionType == nsINavHistoryService::TRANSITION_EMBED ||
+     redirected) ? 1 : 0;
+  data->dateTime = PR_Now();
+  data->uri = aURI;
+
+  nsCOMPtr<Step> task(new StartVisitURIStep(data));
+  AppendTask(task);
+
+  nsCOMPtr<nsIObserverService> obsService =
+    mozilla::services::GetObserverService();
+  if (obsService) {
+    obsService->NotifyObservers(aURI, NS_LINK_VISITED_EVENT_TOPIC, nsnull);
+  }
+
+  return NS_OK;
+}
+
+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()) {
@@ -304,17 +1005,43 @@ History::UnregisterVisitedCallback(nsIUR
   if (observers.IsEmpty()) {
     mObservers.RemoveEntry(aURI);
   }
 
   return NS_OK;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
+//// nsIObserver
+
+NS_IMETHODIMP
+History::Observe(nsISupports* aSubject, const char* aTopic,
+                 const PRUnichar* aData)
+{
+  if (strcmp(aTopic, TOPIC_PLACES_SHUTDOWN) == 0) {
+    mShuttingDown = true;
+
+    // History is going away, so abandon tasks.
+    while (mPendingVisits.PeekFront()) {
+      nsCOMPtr<Step> deadTaskWalking =
+        dont_AddRef(static_cast<Step*>(mPendingVisits.PopFront()));
+    }
+
+    nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+    if (os) {
+      (void)os->RemoveObserver(this, TOPIC_PLACES_SHUTDOWN);
+    }
+  }
+
+  return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
 //// nsISupports
 
-NS_IMPL_ISUPPORTS1(
-  History,
-  IHistory
+NS_IMPL_ISUPPORTS2(
+  History
+, IHistory
+, nsIObserver
 )
 
 } // namespace places
 } // namespace mozilla
--- a/toolkit/components/places/src/History.h
+++ b/toolkit/components/places/src/History.h
@@ -41,55 +41,102 @@
 #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"
+#include "nsDeque.h"
+#include "nsIObserver.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 nsIObserver
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_IHISTORY
+  NS_DECL_NSIOBSERVER
 
   History();
 
   /**
    * Notifies about the visited status of a given URI.
    *
    * @param aURI
    *        The URI to notify about.
    */
   void NotifyVisited(nsIURI *aURI);
 
   /**
+   * Append a task to the queue for SQL queries that need to happen
+   * atomically.
+   *
+   * @pre aTask is not null
+   *
+   * @param aTask
+   *        Task that needs to be completed atomically
+   */
+  void AppendTask(class Step* aTask);
+
+  /**
+   * Call when all steps of the current running task are finished.  Each task
+   * should be responsible for calling this when it is finished (even if there
+   * are errors).
+   *
+   * Do not call this twice for the same visit.
+   */
+  void CurrentTaskFinished();
+
+  /**
    * 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();
 
+  /**
+   * Since visits rapidly fire at once, it's very likely to have race
+   * conditions for SQL queries.  We often need to see if a row exists
+   * or peek at values, and by the time we have retrieved them they could
+   * be different.
+   *
+   * We guarantee an ordering of our SQL statements so that a set of
+   * callbacks for one visit are guaranteed to be atomic.  Each visit consists
+   * of a data structure that sits in this queue.
+   *
+   * The front of the queue always has the current visit we are processing.
+   */
+  nsDeque mPendingVisits;
+
+  /**
+   * Begins next task at the front of the queue.  The task remains in the queue
+   * until it is done and calls CurrentTaskFinished.
+   */
+  void StartNextTask();
+
   static History *gService;
 
+  // Ensures new tasks aren't started on destruction.
+  bool mShuttingDown;
+
   typedef nsTArray<mozilla::dom::Link *> ObserverArray;
 
   class KeyClass : public nsURIHashKey
   {
   public:
     KeyClass(const nsIURI *aURI)
     : nsURIHashKey(aURI)
     {
--- a/toolkit/components/places/src/nsNavHistory.cpp
+++ b/toolkit/components/places/src/nsNavHistory.cpp
@@ -83,21 +83,16 @@
 
 #ifdef MOZ_XUL
 #include "nsIAutoCompleteInput.h"
 #include "nsIAutoCompletePopup.h"
 #endif
 
 using namespace mozilla::places;
 
-// Microsecond timeout for "recent" events such as typed and bookmark following.
-// If you typed it more than this time ago, it's not recent.
-// This is 15 minutes           m    s/m  us/s
-#define RECENT_EVENT_THRESHOLD PRTime((PRInt64)15 * 60 * PR_USEC_PER_SEC)
-
 // The maximum number of things that we will store in the recent events list
 // before calling ExpireNonrecentEvents. This number should be big enough so it
 // is very difficult to get that many unconsumed events (for example, typed but
 // never visited) in the RECENT_EVENT_THRESHOLD. Otherwise, we'll start
 // checking each one for every page visit, which will be somewhat slower.
 #define RECENT_EVENT_QUEUE_MAX_LENGTH 128
 
 // preference ID strings
@@ -227,31 +222,22 @@ NS_IMPL_CI_INTERFACE_GETTER5(
 , nsIGlobalHistory3
 , nsIGlobalHistory2
 , nsIDownloadHistory
 , nsIBrowserHistory
 )
 
 namespace {
 
-static nsresult GetReversedHostname(nsIURI* aURI, nsAString& host);
-static void GetReversedHostname(const nsString& aForward, nsAString& aReversed);
 static PRInt64 GetSimpleBookmarksQueryFolder(
     const nsCOMArray<nsNavHistoryQuery>& aQueries,
     nsNavHistoryQueryOptions* aOptions);
 static void ParseSearchTermsFromQueries(const nsCOMArray<nsNavHistoryQuery>& aQueries,
                                         nsTArray<nsTArray<nsString>*>* aTerms);
 
-inline void ReverseString(const nsString& aInput, nsAString& aReversed)
-{
-  aReversed.Truncate(0);
-  for (PRInt32 i = aInput.Length() - 1; i >= 0; i --)
-    aReversed.Append(aInput[i]);
-}
-
 } // anonymous namespace
 
 namespace mozilla {
   namespace places {
 
     bool hasRecentCorruptDB()
     {
       nsCOMPtr<nsIFile> profDir;
@@ -886,16 +872,37 @@ nsNavHistory::GetDatabaseStatus(PRUint16
  */
 nsresult
 nsNavHistory::UpdateSchemaVersion()
 {
   return mDBConn->SetSchemaVersion(DATABASE_SCHEMA_VERSION);
 }
 
 
+PRUint32
+nsNavHistory::GetRecentFlags(nsIURI *aURI)
+{
+  PRUint32 result = 0;
+  nsCAutoString spec;
+  nsresult rv = aURI->GetSpec(spec);
+  NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Unable to get aURI's spec");
+
+  if (NS_SUCCEEDED(rv)) {
+    if (CheckIsRecentEvent(&mRecentTyped, spec))
+      result |= RECENT_TYPED;
+    if (CheckIsRecentEvent(&mRecentLink, spec))
+      result |= RECENT_ACTIVATED;
+    if (CheckIsRecentEvent(&mRecentBookmark, spec))
+      result |= RECENT_BOOKMARKED;
+  }
+
+  return result;
+}
+
+
 /**
  * Called after InitDB, this creates our own functions
  */
 class mozStorageFunctionGetUnreversedHost: public mozIStorageFunction
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_MOZISTORAGEFUNCTION
@@ -1842,16 +1849,19 @@ nsNavHistory::GetUrlIdFor(nsIURI* aURI, 
 
 
 // nsNavHistory::InternalAddNewPage
 //
 //    Adds a new page to the DB.
 //    THIS SHOULD BE THE ONLY PLACE NEW moz_places ROWS ARE
 //    CREATED. This allows us to maintain better consistency.
 //
+//    XXX this functionality is being moved to History.cpp, so
+//    in fact there *are* two places where new pages are added.
+//
 //    If non-null, the new page ID will be placed into aPageID.
 
 nsresult
 nsNavHistory::InternalAddNewPage(nsIURI* aURI,
                                  const nsAString& aTitle,
                                  PRBool aHidden,
                                  PRBool aTyped,
                                  PRInt32 aVisitCount,
@@ -2139,16 +2149,32 @@ nsNavHistory::GetNewSessionID()
     mLastSessionID = selectSession->AsInt64(0) + 1;
   else
     mLastSessionID = 1;
 
   return mLastSessionID;
 }
 
 
+void
+nsNavHistory::FireOnVisit(nsIURI* aURI,
+                          PRInt64 aVisitID,
+                          PRTime aTime,
+                          PRInt64 aSessionID,
+                          PRInt64 referringVisitID,
+                          PRInt32 aTransitionType)
+{
+  PRUint32 added = 0;
+  NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
+                   nsINavHistoryObserver,
+                   OnVisit(aURI, aVisitID, aTime, aSessionID,
+                           referringVisitID, aTransitionType, &added));
+}
+
+
 PRInt32
 nsNavHistory::GetDaysOfHistory() {
   PRInt32 daysOfHistory = 0;
   nsCOMPtr<mozIStorageStatement> statement;
   nsresult rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
       "SELECT ROUND(( "
         "strftime('%s','now','localtime','utc') - "
         "( "
@@ -2843,22 +2869,19 @@ nsNavHistory::AddVisit(nsIURI* aURI, PRT
   // important to notify the observers below.
   nsNavBookmarks *bs = nsNavBookmarks::GetBookmarksService();
   NS_ENSURE_TRUE(bs, NS_ERROR_OUT_OF_MEMORY);
   (void)UpdateFrecency(pageID, bs->IsRealBookmark(pageID));
 
   // Notify observers: The hidden detection code must match that in
   // GetQueryResults to maintain consistency.
   // FIXME bug 325241: make a way to observe hidden URLs
-  PRUint32 added = 0;
   if (!hidden) {
-    NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
-                     nsINavHistoryObserver,
-                     OnVisit(aURI, *aVisitID, aTime, aSessionID,
-                             referringVisitID, aTransitionType, &added));
+    FireOnVisit(aURI, *aVisitID, aTime, aSessionID, referringVisitID,
+                aTransitionType);
   }
 
   // Normally docshell sends the link visited observer notification for us (this
   // will tell all the documents to update their visited link coloring).
   // However, for redirects (since we implement nsIGlobalHistory3) and downloads
   // (since we implement nsIDownloadHistory) this will not happen and we need to
   // send it ourselves.
   if (newItem && (aIsRedirect || aTransitionType == TRANSITION_DOWNLOAD)) {
@@ -7524,62 +7547,16 @@ nsNavHistory::RemoveDuplicateURIs()
   rv = transaction.Commit();
   NS_ENSURE_SUCCESS(rv, rv);
   return NS_OK;
 }
 
 
 namespace {
 
-// GetReversedHostname
-//
-//    This extracts the hostname from the URI and reverses it in the
-//    form that we use (always ending with a "."). So
-//    "http://microsoft.com/" becomes "moc.tfosorcim."
-//
-//    The idea behind this is that we can create an index over the items in
-//    the reversed host name column, and then query for as much or as little
-//    of the host name as we feel like.
-//
-//    For example, the query "host >= 'gro.allizom.' AND host < 'gro.allizom/'
-//    Matches all host names ending in '.mozilla.org', including
-//    'developer.mozilla.org' and just 'mozilla.org' (since we define all
-//    reversed host names to end in a period, even 'mozilla.org' matches).
-//    The important thing is that this operation uses the index. Any substring
-//    calls in a select statement (even if it's for the beginning of a string)
-//    will bypass any indices and will be slow).
-
-nsresult
-GetReversedHostname(nsIURI* aURI, nsAString& aRevHost)
-{
-  nsCString forward8;
-  nsresult rv = aURI->GetHost(forward8);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-
-  // can't do reversing in UTF8, better use 16-bit chars
-  NS_ConvertUTF8toUTF16 forward(forward8);
-  GetReversedHostname(forward, aRevHost);
-  return NS_OK;
-}
-
-
-// GetReversedHostname
-//
-//    Same as previous but for strings
-
-void
-GetReversedHostname(const nsString& aForward, nsAString& aRevHost)
-{
-  ReverseString(aForward, aRevHost);
-  aRevHost.Append(PRUnichar('.'));
-}
-
-
 // GetSimpleBookmarksQueryFolder
 //
 //    Determines if this set of queries is a simple bookmarks query for a
 //    folder with no other constraints. In these common cases, we can more
 //    efficiently compute the results.
 //
 //    A simple bookmarks query will result in a hierarchical tree of
 //    bookmark items, folders and separators.
--- a/toolkit/components/places/src/nsNavHistory.h
+++ b/toolkit/components/places/src/nsNavHistory.h
@@ -83,16 +83,20 @@
 // mInPrivateBrowsing member
 #define PRIVATEBROWSING_NOTINITED (PRBool(0xffffffff))
 
 // Clamp title and URL to generously large, but not too large, length.
 // See bug 319004 for details.
 #define URI_LENGTH_MAX 65536
 #define TITLE_LENGTH_MAX 4096
 
+// Microsecond timeout for "recent" events such as typed and bookmark following.
+// If you typed it more than this time ago, it's not recent.
+#define RECENT_EVENT_THRESHOLD PRTime((PRInt64)15 * 60 * PR_USEC_PER_SEC)
+
 #ifdef MOZ_XUL
 // Fired after autocomplete feedback has been updated.
 #define TOPIC_AUTOCOMPLETE_FEEDBACK_UPDATED "places-autocomplete-feedback-updated"
 #endif
 // Fired when Places is shutting down.
 #define TOPIC_PLACES_SHUTDOWN "places-shutdown"
 // Internal notification, called after places-shutdown.
 // If you need to listen for Places shutdown, you should really use
@@ -107,16 +111,21 @@
 
 namespace mozilla {
 namespace places {
 
   enum HistoryStatementId {
     DB_GET_PAGE_INFO_BY_URL = 0
   , DB_GET_TAGS = 1
   , DB_IS_PAGE_VISITED = 2
+  , DB_INSERT_VISIT = 3
+  , DB_RECENT_VISIT_OF_URL = 4
+  , DB_GET_PAGE_VISIT_STATS = 5
+  , DB_UPDATE_PAGE_VISIT_STATS = 6
+  , DB_ADD_NEW_PAGE = 7
   };
 
 } // namespace places
 } // namespace mozilla
 
 
 class mozIAnnotationService;
 class nsNavHistory;
@@ -389,32 +398,67 @@ public:
 
   /**
    * 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; }
 
+  enum RecentEventFlags {
+    RECENT_TYPED      = 1 << 0,    // User typed in URL recently
+    RECENT_ACTIVATED  = 1 << 1,    // User tapped URL link recently
+    RECENT_BOOKMARKED = 1 << 2     // User bookmarked URL recently
+  };
+
+  /**
+   * Returns any recent activity done with a URL.
+   * @return Any recent events associated with this URI.  Each bit is set
+   *         according to RecentEventFlags enum values.
+   */
+  PRUint32 GetRecentFlags(nsIURI *aURI);
+
   mozIStorageStatement* GetStatementById(
     enum mozilla::places::HistoryStatementId aStatementId
   )
   {
     using namespace mozilla::places;
     switch(aStatementId) {
       case DB_GET_PAGE_INFO_BY_URL:
         return mDBGetURLPageInfo;
       case DB_GET_TAGS:
         return mDBGetTags;
       case DB_IS_PAGE_VISITED:
         return mDBIsPageVisited;
+      case DB_INSERT_VISIT:
+        return mDBInsertVisit;
+      case DB_RECENT_VISIT_OF_URL:
+        return mDBRecentVisitOfURL;
+      case DB_GET_PAGE_VISIT_STATS:
+        return mDBGetPageVisitStats;
+      case DB_UPDATE_PAGE_VISIT_STATS:
+        return mDBUpdatePageVisitStats;
+      case DB_ADD_NEW_PAGE:
+        return mDBAddNewPage;
     }
     return nsnull;
   }
 
+  PRInt64 GetNewSessionID();
+
+  /**
+   * Fires onVisit event to nsINavHistoryService observers
+   */
+  void FireOnVisit(nsIURI* aURI,
+                   PRInt64 aVisitID,
+                   PRTime aTime,
+                   PRInt64 aSessionID,
+                   PRInt64 referringVisitID,
+                   PRInt32 aTransitionType);
+
 private:
   ~nsNavHistory();
 
   // used by GetHistoryService
   static nsNavHistory *gHistoryService;
 
 protected:
 
@@ -682,17 +726,16 @@ protected:
   RedirectHash mRecentRedirects;
   static PLDHashOperator ExpireNonrecentRedirects(
       nsCStringHashKey::KeyType aKey, RedirectInfo& aData, void* aUserArg);
   PRBool GetRedirectFor(const nsACString& aDestination, nsACString& aSource,
                         PRTime* aTime, PRUint32* aRedirectType);
 
   // Sessions tracking.
   PRInt64 mLastSessionID;
-  PRInt64 GetNewSessionID();
 
 #ifdef MOZ_XUL
   // AutoComplete stuff
   mozIStorageStatement *GetDBFeedbackIncrease();
   nsCOMPtr<mozIStorageStatement> mDBFeedbackIncrease;
 
   nsresult AutoCompleteFeedback(PRInt32 aIndex,
                                 nsIAutoCompleteController *aController);
--- a/toolkit/components/places/tests/browser/Makefile.in
+++ b/toolkit/components/places/tests/browser/Makefile.in
@@ -42,26 +42,31 @@ srcdir		= @srcdir@
 VPATH		= @srcdir@
 relativesrcdir  = toolkit/components/places/tests/browser
 
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _BROWSER_FILES = \
 	browser_bug399606.js \
+	browser_visituri.js \
 	$(NULL)
 
 # These are files that need to be loaded via the HTTP proxy server
 # Access them through http://example.com/
 _HTTP_FILES = \
 	bug_399606/399606-httprefresh.html \
 	bug_399606/399606-location.reload.html \
 	bug_399606/399606-location.replace.html \
 	bug_399606/399606-window.location.href.html \
 	bug_399606/399606-window.location.html \
 	bug_399606/399606-history.go-0.html \
+	visituri/begin.html \
+	visituri/redirect_twice.sjs \
+	visituri/redirect_once.sjs \
+	visituri/final.html \
 	$(NULL)
 
 libs:: $(_BROWSER_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
 
 libs:: $(_HTTP_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
--- a/toolkit/components/places/tests/cpp/places_test_harness.h
+++ b/toolkit/components/places/tests/cpp/places_test_harness.h
@@ -42,16 +42,19 @@
 #include "nsThreadUtils.h"
 #include "nsNetUtil.h"
 #include "nsDocShellCID.h"
 
 #include "nsToolkitCompsCID.h"
 #include "nsINavHistoryService.h"
 #include "nsIObserverService.h"
 #include "mozilla/IHistory.h"
+#include "mozIStorageConnection.h"
+#include "mozIStorageStatement.h"
+#include "nsPIPlacesDatabase.h"
 
 using namespace mozilla;
 
 static size_t gTotalTests = 0;
 static size_t gPassedTests = 0;
 
 #define do_check_true(aCondition) \
   PR_BEGIN_MACRO \
@@ -112,15 +115,134 @@ addURI(nsIURI* aURI)
 
   PRInt64 id;
   nsresult rv = hist->AddVisit(aURI, PR_Now(), nsnull,
                                nsINavHistoryService::TRANSITION_LINK, PR_FALSE,
                                0, &id);
   do_check_success(rv);
 }
 
+struct PlaceRecord
+{
+  PRInt64 id;
+  PRInt32 hidden;
+  PRInt32 typed;
+  PRInt32 visitCount;
+};
+
+struct VisitRecord
+{
+  PRInt64 id;
+  PRInt64 lastVisitId;
+  PRInt32 transitionType;
+};
+
 already_AddRefed<IHistory>
 do_get_IHistory()
 {
   nsCOMPtr<IHistory> history = do_GetService(NS_IHISTORY_CONTRACTID);
   do_check_true(history);
   return history.forget();
 }
+
+already_AddRefed<nsINavHistoryService>
+do_get_NavHistory()
+{
+  nsCOMPtr<nsINavHistoryService> serv =
+    do_GetService(NS_NAVHISTORYSERVICE_CONTRACTID);
+  do_check_true(serv);
+  return serv.forget();
+}
+
+already_AddRefed<mozIStorageConnection>
+do_get_db()
+{
+  nsCOMPtr<nsINavHistoryService> history = do_get_NavHistory();
+  nsCOMPtr<nsPIPlacesDatabase> database = do_QueryInterface(history);
+  do_check_true(database);
+
+  mozIStorageConnection* dbConn;
+  nsresult rv = database->GetDBConnection(&dbConn);
+  do_check_success(rv);
+  return dbConn;
+}
+
+/**
+ * Get the place record from the database.
+ *
+ * @param aURI The unique URI of the place we are looking up
+ * @param result Out parameter where the result is stored
+ */
+void
+do_get_place(nsIURI* aURI, PlaceRecord& result)
+{
+  nsCOMPtr<mozIStorageConnection> dbConn = do_get_db();
+  nsCOMPtr<mozIStorageStatement> stmt;
+
+  nsCString spec;
+  nsresult rv = aURI->GetSpec(spec);
+  do_check_success(rv);
+
+  rv = dbConn->CreateStatement(NS_LITERAL_CSTRING(
+    "SELECT id, hidden, typed, visit_count FROM moz_places_temp "
+    "WHERE url=?1 "
+    "UNION ALL "
+    "SELECT id, hidden, typed, visit_count FROM moz_places "
+    "WHERE url=?1 "
+    "LIMIT 1"
+  ), getter_AddRefs(stmt));
+  do_check_success(rv);
+
+  rv = stmt->BindUTF8StringParameter(0, spec);
+  do_check_success(rv);
+
+  PRBool hasResults;
+  rv = stmt->ExecuteStep(&hasResults);
+  do_check_true(hasResults);
+  do_check_success(rv);
+
+  rv = stmt->GetInt64(0, &result.id);
+  do_check_success(rv);
+  rv = stmt->GetInt32(1, &result.hidden);
+  do_check_success(rv);
+  rv = stmt->GetInt32(2, &result.typed);
+  do_check_success(rv);
+  rv = stmt->GetInt32(3, &result.visitCount);
+  do_check_success(rv);
+}
+
+/**
+ * Gets the most recent visit to a place.
+ *
+ * @param placeID ID from the moz_places table
+ * @param result Out parameter where visit is stored
+ */
+void
+do_get_lastVisit(PRInt64 placeId, VisitRecord& result)
+{
+  nsCOMPtr<mozIStorageConnection> dbConn = do_get_db();
+  nsCOMPtr<mozIStorageStatement> stmt;
+
+  nsresult rv = dbConn->CreateStatement(NS_LITERAL_CSTRING(
+    "SELECT id, from_visit, visit_type FROM moz_historyvisits_temp "
+    "WHERE place_id=?1 "
+    "UNION ALL "
+    "SELECT id, from_visit, visit_type FROM moz_historyvisits "
+    "WHERE place_id=?1 "
+    "LIMIT 1"
+  ), getter_AddRefs(stmt));
+  do_check_success(rv);
+
+  rv = stmt->BindInt64Parameter(0, placeId);
+  do_check_success(rv);
+
+  PRBool hasResults;
+  rv = stmt->ExecuteStep(&hasResults);
+  do_check_true(hasResults);
+  do_check_success(rv);
+
+  rv = stmt->GetInt64(0, &result.id);
+  do_check_success(rv);
+  rv = stmt->GetInt64(1, &result.lastVisitId);
+  do_check_success(rv);
+  rv = stmt->GetInt32(2, &result.transitionType);
+  do_check_success(rv);
+}
--- a/toolkit/components/places/tests/cpp/test_IHistory.cpp
+++ b/toolkit/components/places/tests/cpp/test_IHistory.cpp
@@ -33,16 +33,17 @@
  * 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 "nsIBrowserHistory.h"
 
 #include "mock_Link.h"
 using namespace mozilla::dom;
 
 /**
  * This file tests the IHistory interface.
  */
 
@@ -71,16 +72,63 @@ new_test_uri()
 
   // Create the URI for the spec.
   nsCOMPtr<nsIURI> testURI;
   nsresult rv = NS_NewURI(getter_AddRefs(testURI), spec);
   do_check_success(rv);
   return testURI.forget();
 }
 
+class VisitURIObserver : public nsIObserver
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  VisitURIObserver(int aExpectedVisits = 1) :
+    mVisits(0),
+    mExpectedVisits(aExpectedVisits)
+  {
+    nsCOMPtr<nsIObserverService> observerService =
+      do_GetService(NS_OBSERVERSERVICE_CONTRACTID);
+    do_check_true(observerService);
+    (void)observerService->AddObserver(this,
+                                       "uri-visit-saved",
+                                       PR_FALSE);
+  }
+
+  void WaitForNotification()
+  {
+    while (mVisits < mExpectedVisits) {
+      (void)NS_ProcessNextEvent();
+    }
+  }
+
+  NS_IMETHOD Observe(nsISupports* aSubject,
+                     const char* aTopic,
+                     const PRUnichar* aData)
+  {
+    mVisits++;
+
+    if (mVisits == mExpectedVisits) {
+      nsCOMPtr<nsIObserverService> observerService =
+        do_GetService(NS_OBSERVERSERVICE_CONTRACTID);
+      (void)observerService->RemoveObserver(this, "uri-visit-saved");
+    }
+
+    return NS_OK;
+  }
+private:
+  int mVisits;
+  int mExpectedVisits;
+};
+NS_IMPL_ISUPPORTS1(
+  VisitURIObserver,
+  nsIObserver
+)
+
 ////////////////////////////////////////////////////////////////////////////////
 //// 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> testLink;
@@ -358,29 +406,174 @@ test_observer_topic_dispatched()
 
   // Unregister our observer that would not have been released.
   rv = history->UnregisterVisitedCallback(notVisitedURI, notVisitedLink);
   do_check_success(rv);
 
   run_next_test();
 }
 
+void
+test_visituri_inserts()
+{
+  nsCOMPtr<IHistory> history(do_get_IHistory());
+  nsCOMPtr<nsIURI> lastURI(new_test_uri());
+  nsCOMPtr<nsIURI> visitedURI(new_test_uri());
+
+  history->VisitURI(visitedURI, lastURI, mozilla::IHistory::TOP_LEVEL);
+
+  nsCOMPtr<VisitURIObserver> finisher = new VisitURIObserver();
+  finisher->WaitForNotification();
+
+  PlaceRecord place;
+  do_get_place(visitedURI, place);
+
+  do_check_true(place.id > 0);
+  do_check_false(place.hidden);
+  do_check_false(place.typed);
+  do_check_true(place.visitCount == 1);
+
+  run_next_test();
+}
+
+void
+test_visituri_updates()
+{
+  nsCOMPtr<IHistory> history(do_get_IHistory());
+  nsCOMPtr<nsIURI> lastURI(new_test_uri());
+  nsCOMPtr<nsIURI> visitedURI(new_test_uri());
+  nsCOMPtr<VisitURIObserver> finisher;
+
+  history->VisitURI(visitedURI, lastURI, mozilla::IHistory::TOP_LEVEL);
+  finisher = new VisitURIObserver();
+  finisher->WaitForNotification();
+
+  history->VisitURI(visitedURI, lastURI, mozilla::IHistory::TOP_LEVEL);
+  finisher = new VisitURIObserver();
+  finisher->WaitForNotification();
+
+  PlaceRecord place;
+  do_get_place(visitedURI, place);
+
+  do_check_true(place.visitCount == 2);
+
+  run_next_test();
+}
+
+void
+test_visituri_preserves_shown_and_typed()
+{
+  nsCOMPtr<IHistory> history(do_get_IHistory());
+  nsCOMPtr<nsIURI> lastURI(new_test_uri());
+  nsCOMPtr<nsIURI> visitedURI(new_test_uri());
+
+  history->VisitURI(visitedURI, lastURI, mozilla::IHistory::TOP_LEVEL);
+  // this simulates the uri visit happening in a frame.  Normally frame
+  // transitions would be hidden unless it was previously loaded top-level
+  history->VisitURI(visitedURI, lastURI, 0);
+
+  nsCOMPtr<VisitURIObserver> finisher = new VisitURIObserver(2);
+  finisher->WaitForNotification();
+
+  PlaceRecord place;
+  do_get_place(visitedURI, place);
+  do_check_false(place.hidden);
+
+  run_next_test();
+}
+
+void
+test_visituri_creates_visit()
+{
+  nsCOMPtr<IHistory> history(do_get_IHistory());
+  nsCOMPtr<nsIURI> lastURI(new_test_uri());
+  nsCOMPtr<nsIURI> visitedURI(new_test_uri());
+
+  history->VisitURI(visitedURI, lastURI, mozilla::IHistory::TOP_LEVEL);
+  nsCOMPtr<VisitURIObserver> finisher = new VisitURIObserver();
+  finisher->WaitForNotification();
+
+  PlaceRecord place;
+  VisitRecord visit;
+  do_get_place(visitedURI, place);
+  do_get_lastVisit(place.id, visit);
+
+  do_check_true(visit.id > 0);
+  do_check_true(visit.lastVisitId == 0);
+  do_check_true(visit.transitionType == nsINavHistoryService::TRANSITION_LINK);
+
+  run_next_test();
+}
+
+void
+test_visituri_transition_typed()
+{
+  nsCOMPtr<nsINavHistoryService> navHistory = do_get_NavHistory();
+  nsCOMPtr<nsIBrowserHistory> browserHistory = do_QueryInterface(navHistory);
+  nsCOMPtr<IHistory> history(do_get_IHistory());
+  nsCOMPtr<nsIURI> lastURI(new_test_uri());
+  nsCOMPtr<nsIURI> visitedURI(new_test_uri());
+
+  browserHistory->MarkPageAsTyped(visitedURI);
+  history->VisitURI(visitedURI, lastURI, mozilla::IHistory::TOP_LEVEL);
+  nsCOMPtr<VisitURIObserver> finisher = new VisitURIObserver();
+  finisher->WaitForNotification();
+
+  PlaceRecord place;
+  VisitRecord visit;
+  do_get_place(visitedURI, place);
+  do_get_lastVisit(place.id, visit);
+
+  do_check_true(visit.transitionType == nsINavHistoryService::TRANSITION_TYPED);
+
+  run_next_test();
+}
+
+void
+test_visituri_transition_embed()
+{
+  nsCOMPtr<nsINavHistoryService> navHistory = do_get_NavHistory();
+  nsCOMPtr<nsIBrowserHistory> browserHistory = do_QueryInterface(navHistory);
+  nsCOMPtr<IHistory> history(do_get_IHistory());
+  nsCOMPtr<nsIURI> lastURI(new_test_uri());
+  nsCOMPtr<nsIURI> visitedURI(new_test_uri());
+
+  history->VisitURI(visitedURI, lastURI, 0);
+  nsCOMPtr<VisitURIObserver> finisher = new VisitURIObserver();
+  finisher->WaitForNotification();
+
+  PlaceRecord place;
+  VisitRecord visit;
+  do_get_place(visitedURI, place);
+  do_get_lastVisit(place.id, visit);
+
+  do_check_true(visit.transitionType == nsINavHistoryService::TRANSITION_EMBED);
+
+  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),
   TEST(test_observer_topic_dispatched),
+  TEST(test_visituri_inserts),
+  TEST(test_visituri_updates),
+  TEST(test_visituri_preserves_shown_and_typed),
+  TEST(test_visituri_creates_visit),
+  TEST(test_visituri_transition_typed),
+  TEST(test_visituri_transition_embed),
 };
 
 const char* file = __FILE__;
 #define TEST_NAME "IHistory"
 #define TEST_FILE file
 #include "places_test_harness_tail.h"
--- a/xpcom/build/Makefile.in
+++ b/xpcom/build/Makefile.in
@@ -110,16 +110,17 @@ LOCAL_INCLUDES	= \
 		-I$(srcdir)/../ds \
 		-I$(srcdir)/../io \
 		-I$(srcdir)/../components \
 		-I$(srcdir)/../threads \
 		-I$(srcdir)/../threads/_xpidlgen \
 		-I$(srcdir)/../proxy/src \
 		-I$(srcdir)/../reflect/xptinfo/src \
 		-I$(topsrcdir)/chrome/src \
+		-I$(srcdir)/../../docshell/base \
 		$(NULL)
 
 EXPORTS_NAMESPACES = mozilla
 
 SDK_HEADERS =  \
   nsXPCOM.h       \
   nsXPCOMCID.h    \
   $(NULL)
--- a/xpcom/build/ServiceList.h
+++ b/xpcom/build/ServiceList.h
@@ -1,7 +1,18 @@
 MOZ_SERVICE(ChromeRegistryService, nsIChromeRegistry, "@mozilla.org/chrome/chrome-registry;1")
 MOZ_SERVICE(ToolkitChromeRegistryService, nsIToolkitChromeRegistry, "@mozilla.org/chrome/chrome-registry;1")
 MOZ_SERVICE(XULChromeRegistryService, nsIXULChromeRegistry, "@mozilla.org/chrome/chrome-registry;1")
 MOZ_SERVICE(XULOverlayProviderService, nsIXULOverlayProvider, "@mozilla.org/chrome/chrome-registry;1")
 MOZ_SERVICE(IOService, nsIIOService, "@mozilla.org/network/io-service;1")
 MOZ_SERVICE(ObserverService, nsIObserverService, "@mozilla.org/observer-service;1")
 MOZ_SERVICE(StringBundleService, nsIStringBundleService, "@mozilla.org/intl/stringbundle;1")
+
+#ifdef MOZ_USE_NAMESPACE
+namespace mozilla
+{
+#endif
+
+MOZ_SERVICE(HistoryService, IHistory, "@mozilla.org/browser/history;1")
+
+#ifdef MOZ_USE_NAMESPACE
+}
+#endif
--- a/xpcom/build/Services.cpp
+++ b/xpcom/build/Services.cpp
@@ -43,17 +43,19 @@
 #include "nsIChromeRegistry.h"
 #include "nsIObserverService.h"
 #include "nsNetCID.h"
 #include "nsObserverService.h"
 #include "nsXPCOMPrivate.h"
 #include "nsIStringBundle.h"
 #include "nsIToolkitChromeRegistry.h"
 #include "nsIXULOverlayProvider.h"
+#include "IHistory.h"
 
+using namespace mozilla;
 using namespace mozilla::services;
 
 /*
  * Define a global variable and a getter for every service in ServiceList.
  * eg. gIOService and GetIOService()
  */
 #define MOZ_SERVICE(NAME, TYPE, CONTRACT_ID)                            \
   static TYPE* g##NAME = nsnull;                                        \
--- a/xpcom/build/Services.h
+++ b/xpcom/build/Services.h
@@ -37,19 +37,22 @@
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef mozilla_Services_h
 #define mozilla_Services_h
 
 #include "nscore.h"
 #include "nsCOMPtr.h"
 
+#define MOZ_USE_NAMESPACE
 #define MOZ_SERVICE(NAME, TYPE, SERVICE_CID) class TYPE;
+
 #include "ServiceList.h"
 #undef MOZ_SERVICE
+#undef MOZ_USE_NAMESPACE
 
 namespace mozilla {
 namespace services {
 
 #define MOZ_SERVICE(NAME, TYPE, SERVICE_CID) NS_COM already_AddRefed<TYPE> Get##NAME();
 #include "ServiceList.h"
 #undef MOZ_SERVICE