diff --git a/content/base/src/Link.cpp b/content/base/src/Link.cpp index 198f0b4722e..8acd27b796b 100644 --- a/content/base/src/Link.cpp +++ b/content/base/src/Link.cpp @@ -42,33 +42,79 @@ #include "nsIEventStateManager.h" #include "nsIURL.h" +#include "nsContentUtils.h" #include "nsEscape.h" #include "nsGkAtoms.h" #include "nsString.h" +#include "mozilla/IHistory.h" + namespace mozilla { namespace dom { Link::Link() : mLinkState(defaultState) + , mRegistered(false) { } nsLinkState Link::GetLinkState() const { + NS_ASSERTION(mRegistered, + "Getting the link state of an unregistered Link!"); + NS_ASSERTION(mLinkState != eLinkState_Unknown, + "Getting the link state with an unknown value!"); return mLinkState; } void Link::SetLinkState(nsLinkState aState) { + NS_ASSERTION(mRegistered, + "Setting the link state of an unregistered Link!"); mLinkState = aState; + + // Per IHistory interface documentation, we are no longer registered. + mRegistered = false; } PRInt32 Link::LinkState() const { + // We are a constant method, but we are just lazily doing things and have to + // track that state. Cast away that constness! + Link *self = const_cast(this); + + // If we are not in the document, default to not visited. + nsCOMPtr content(do_QueryInterface(self)); + NS_ASSERTION(content, "Why isn't this an nsIContent node?!"); + if (!content->IsInDoc()) { + self->mLinkState = eLinkState_Unvisited; + } + + // If we have not yet registered for notifications and are in an unknown + // state, register now! + if (!mRegistered && mLinkState == eLinkState_Unknown) { + // First, make sure the href attribute has a valid link (bug 23209). + nsCOMPtr hrefURI(GetURI()); + if (!hrefURI) { + self->mLinkState = eLinkState_NotLink; + return 0; + } + + // We have a good href, so register with History. + IHistory *history = nsContentUtils::GetHistory(); + nsresult rv = history->RegisterVisitedCallback(hrefURI, self); + if (NS_SUCCEEDED(rv)) { + self->mRegistered = true; + + // Assume that we are not visited until we are told otherwise. + self->mLinkState = eLinkState_Unvisited; + } + } + + // Otherwise, return our known state. if (mLinkState == eLinkState_Visited) { return NS_EVENT_STATE_VISITED; } @@ -398,6 +444,8 @@ Link::ResetLinkState() doc->ForgetLink(content); } + UnregisterFromHistory(); + // Update our state back to the default. mLinkState = defaultState; @@ -405,6 +453,27 @@ Link::ResetLinkState() mCachedURI = nsnull; } +void +Link::UnregisterFromHistory() +{ + // If we are not registered, we have nothing to do. + if (!mRegistered) { + return; + } + + // Obtain the URI that we registered with. + nsCOMPtr hrefURI(GetURI()); + NS_ASSERTION(hrefURI, "mRegistered is true, but we have no URI?!"); + + // And tell History to stop tracking us. + IHistory *history = nsContentUtils::GetHistory(); + nsresult rv = history->UnregisterVisitedCallback(hrefURI, this); + NS_ASSERTION(NS_SUCCEEDED(rv), "This should only fail if we misuse the API!"); + if (NS_SUCCEEDED(rv)) { + mRegistered = false; + } +} + already_AddRefed Link::GetURIToMutate() { diff --git a/content/base/src/Link.h b/content/base/src/Link.h index 4940ed87794..c8d80fea0b2 100644 --- a/content/base/src/Link.h +++ b/content/base/src/Link.h @@ -93,13 +93,21 @@ protected: */ virtual void ResetLinkState(); - nsLinkState mLinkState; - private: + /** + * Unregisters from History so this node no longer gets notifications about + * changes to visitedness. + */ + void UnregisterFromHistory(); + already_AddRefed GetURIToMutate(); void SetHrefAttribute(nsIURI *aURI); + nsLinkState mLinkState; + mutable nsCOMPtr mCachedURI; + + bool mRegistered; }; } // namespace dom