--- 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