diff --git a/mozilla/browser/base/content/browser.js b/mozilla/browser/base/content/browser.js index c2c72820aaa..7e53d402796 100644 --- a/mozilla/browser/base/content/browser.js +++ b/mozilla/browser/base/content/browser.js @@ -740,7 +740,7 @@ function delayedStartup() gPrefService = Components.classes["@mozilla.org/preferences-service;1"] .getService(Components.interfaces.nsIPrefBranch); BrowserOffline.init(); - + if (gURLBar && document.documentElement.getAttribute("chromehidden").indexOf("toolbar") != -1) { gURLBar.setAttribute("readonly", "true"); gURLBar.setAttribute("enablehistory", "false"); @@ -4828,8 +4828,8 @@ function asyncOpenWebPanel(event) // A Web panel's links should target the main content area. Do this // if no modifier keys are down and if there's no target or the target equals // _main (the IE convention) or _content (the Mozilla convention). - // The only reason we field _main and _content here is for the markLinkVisited - // hack. + // XXX Now that markLinkVisited is gone, we may not need to field _main and + // _content here. target = wrapper.getAttribute("target"); var docWrapper = wrapper.ownerDocument; var locWrapper = docWrapper.location; @@ -4855,7 +4855,6 @@ function asyncOpenWebPanel(event) var url = getShortcutOrURI(wrapper.href, postData); if (!url) return true; - markLinkVisited(wrapper.href, linkNode); loadURI(url, null, postData.value); event.preventDefault(); return false; diff --git a/mozilla/camino/src/history/nsSimpleGlobalHistory.cpp b/mozilla/camino/src/history/nsSimpleGlobalHistory.cpp index 03c7b65eb79..a230157c639 100644 --- a/mozilla/camino/src/history/nsSimpleGlobalHistory.cpp +++ b/mozilla/camino/src/history/nsSimpleGlobalHistory.cpp @@ -681,8 +681,10 @@ nsSimpleGlobalHistory::AddExistingPageToDatabase(nsIMdbRow *row, // if the page was typed, unhide it now because it's // known to be valid - if (HasCell(mEnv, row, kToken_TypedColumn)) + if (HasCell(mEnv, row, kToken_TypedColumn)) { + mTypedHiddenURIs.Remove(URISpec); row->CutColumn(mEnv, kToken_HiddenColumn); + } // Update last visit date. // First get the old date so we can update observers... @@ -1413,9 +1415,18 @@ nsSimpleGlobalHistory::IsVisited(nsIURI* aURI, PRBool *_retval) rv = aURI->GetSpec(URISpec); NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr row; - rv = FindRow(kToken_URLColumn, URISpec.get(), getter_AddRefs(row)); - + rv = FindRow(kToken_URLColumn, URISpec.get(), nsnull); + *_retval = NS_SUCCEEDED(rv); + + // Hidden, typed URIs haven't really been visited yet. They've only + // been typed in and the actual load hasn't happened yet. We maintain + // the list of hidden+typed URIs in memory in mTypedHiddenURIs because + // the list will usually be small and checking the actual Mork row + // would require several dynamic memory allocations. + if (*_retval && mTypedHiddenURIs.Contains(URISpec)) + *_retval = PR_FALSE; + } + *_retval = NS_SUCCEEDED(rv); return NS_OK; @@ -1582,6 +1593,7 @@ nsSimpleGlobalHistory::MarkPageAsTyped(nsIURI *aURI) // We don't know if this is a valid URI yet. Hide it until it finishes // loading. SetRowValue(row, kToken_HiddenColumn, 1); + mTypedHiddenURIs.Put(spec); } rv = SetRowValue(row, kToken_TypedColumn, 1); @@ -1635,7 +1647,9 @@ nsSimpleGlobalHistory::Init() observerService->AddObserver(this, "profile-before-change", PR_TRUE); observerService->AddObserver(this, "profile-do-change", PR_TRUE); } - + + mTypedHiddenURIs.Init(3); + return NS_OK; } diff --git a/mozilla/camino/src/history/nsSimpleGlobalHistory.h b/mozilla/camino/src/history/nsSimpleGlobalHistory.h index d9618d76595..9c4bd0dc332 100644 --- a/mozilla/camino/src/history/nsSimpleGlobalHistory.h +++ b/mozilla/camino/src/history/nsSimpleGlobalHistory.h @@ -57,6 +57,7 @@ #include "nsString.h" #include "nsVoidArray.h" #include "nsSupportsArray.h" +#include "nsHashSets.h" struct MatchHostData; struct SearchQueryData; @@ -307,6 +308,7 @@ protected: mdb_column kToken_LastPageVisited; mdb_column kToken_ByteOrder; + nsCStringHashSet mTypedHiddenURIs; }; diff --git a/mozilla/content/base/public/nsIDocument.h b/mozilla/content/base/public/nsIDocument.h index 34dd8c85478..629858d9a31 100644 --- a/mozilla/content/base/public/nsIDocument.h +++ b/mozilla/content/base/public/nsIDocument.h @@ -52,6 +52,8 @@ #include "nsCRT.h" #include "mozFlushType.h" #include "nsPropertyTable.h" +#include "nsHashSets.h" +#include "nsAutoPtr.h" class nsIAtom; class nsIContent; @@ -90,8 +92,8 @@ class nsILayoutHistoryState; // IID for the nsIDocument interface #define NS_IDOCUMENT_IID \ -{ 0x9339ff1e, 0xdab0, 0x4264, \ - { 0x8a, 0x8c, 0xcb, 0x84, 0xeb, 0x4e, 0x6b, 0x92 } } +{ 0xd7c47f55, 0x480b, 0x4a60, \ + { 0x9a, 0xdf, 0xca, 0x49, 0x87, 0x3c, 0x71, 0xe2 } } // The base value for the content ID counter. // This counter is used by the document to @@ -103,6 +105,7 @@ class nsILayoutHistoryState; // Flag for AddStyleSheet(). #define NS_STYLESHEET_FROM_CATALOG (1 << 0) +#define NS_LINK_VISITED_EVENT_TOPIC "link-visited" //---------------------------------------------------------------------- @@ -751,6 +754,28 @@ public: * |aPersisted| parameter. */ virtual void OnPageHide(PRBool aPersisted) = 0; + + /* + * We record the set of links in the document that are relevant to + * style. + */ + /** + * Notification that an element is a link with a given URI that is + * relevant to style. + */ + virtual void AddStyleRelevantLink(nsIContent* aContent, nsIURI* aURI) = 0; + /** + * Notification that an element is a link and its URI might have been + * changed or the element removed. If the element is still a link relevant + * to style, then someone must ensure that AddStyleRelevantLink is + * (eventually) called on it again. + */ + virtual void ForgetLink(nsIContent* aContent) = 0; + /** + * Notification that the visitedness state of a URI has been changed + * and style related to elements linking to that URI should be updated. + */ + virtual void NotifyURIVisitednessChanged(nsIURI* aURI) = 0; protected: ~nsIDocument() diff --git a/mozilla/content/base/src/nsDocument.cpp b/mozilla/content/base/src/nsDocument.cpp index 39348bf49c6..8191c90c3f2 100644 --- a/mozilla/content/base/src/nsDocument.cpp +++ b/mozilla/content/base/src/nsDocument.cpp @@ -140,6 +140,115 @@ static NS_DEFINE_CID(kDOMEventGroupCID, NS_DOMEVENTGROUP_CID); static NS_DEFINE_CID(kCharsetAliasCID, NS_CHARSETALIAS_CID); static NS_DEFINE_CID(kDateTimeFormatCID, NS_DATETIMEFORMAT_CID); +void +nsUint32ToContentHashEntry::Destroy() +{ + HashSet* set = GetHashSet(); + if (set) { + delete set; + } else { + nsIContent* content = GetContent(); + NS_IF_RELEASE(content); + } +} + +nsresult +nsUint32ToContentHashEntry::PutContent(nsIContent* aVal) +{ + // Add the value to the hash if it is there + HashSet* set = GetHashSet(); + if (set) { + nsISupportsHashKey* entry = set->PutEntry(aVal); + return entry ? NS_OK : NS_ERROR_OUT_OF_MEMORY; + } + + // If an element is already there, create a hashtable and both of these to it + if (GetContent()) { + nsIContent* oldVal = GetContent(); + nsresult rv = InitHashSet(&set); + NS_ENSURE_SUCCESS(rv, rv); + + nsISupportsHashKey* entry = set->PutEntry(oldVal); + if (!entry) + return NS_ERROR_OUT_OF_MEMORY; + // The hashset adds its own reference, so release the one we had + NS_RELEASE(oldVal); + + entry = set->PutEntry(aVal); + return entry ? NS_OK : NS_ERROR_OUT_OF_MEMORY; + } + + // Nothing exists in the hash right now, so just set the single pointer + return SetContent(aVal); +} + +void +nsUint32ToContentHashEntry::RemoveContent(nsIContent* aVal) +{ + // Remove from the hash if the hash is there + HashSet* set = GetHashSet(); + if (set) { + set->RemoveEntry(aVal); + if (set->Count() == 0) { + delete set; + mValOrHash = nsnull; + } + return; + } + + // Remove the ptr if there is just a ptr + nsIContent* v = GetContent(); + if (v == aVal) { + NS_IF_RELEASE(v); + mValOrHash = nsnull; + } +} + +nsresult +nsUint32ToContentHashEntry::InitHashSet(HashSet** aSet) +{ + HashSet* newSet = new HashSet(); + if (!newSet) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsresult rv = newSet->Init(); + NS_ENSURE_SUCCESS(rv, rv); + + mValOrHash = newSet; + *aSet = newSet; + return NS_OK; +} + +static PLDHashOperator PR_CALLBACK +nsUint32ToContentHashEntryVisitorCallback(nsISupportsHashKey* aEntry, + void* aClosure) +{ + nsUint32ToContentHashEntry::Visitor* visitor = + NS_STATIC_CAST(nsUint32ToContentHashEntry::Visitor*, aClosure); + visitor->Visit(NS_STATIC_CAST(nsIContent*, aEntry->GetKey())); + return PL_DHASH_NEXT; +} + +void +nsUint32ToContentHashEntry::VisitContent(Visitor* aVisitor) +{ + HashSet* set = GetHashSet(); + if (set) { + set->EnumerateEntries(nsUint32ToContentHashEntryVisitorCallback, aVisitor); + if (set->Count() == 0) { + delete set; + mValOrHash = nsnull; + } + return; + } + + nsIContent* v = GetContent(); + if (v) { + aVisitor->Visit(v); + } +} + // Helper structs for the content->subdoc map class SubDocMapEntry : public PLDHashEntryHdr @@ -749,6 +858,8 @@ nsDocument::Init() return NS_ERROR_ALREADY_INITIALIZED; } + mLinkMap.Init(); + // Force initialization. nsBindingManager *bindingManager = new nsBindingManager(); NS_ENSURE_TRUE(bindingManager, NS_ERROR_OUT_OF_MEMORY); @@ -4963,6 +5074,9 @@ nsDocument::UnblockOnload() void nsDocument::OnPageShow(PRBool aPersisted) { + mVisible = PR_TRUE; + UpdateLinkMap(); + if (aPersisted) { // Send out notifications that our elements are attached. nsRefPtr links = NS_GetContentList(this, @@ -5028,4 +5142,104 @@ nsDocument::OnPageHide(PRBool aPersisted) NS_EVENT_FLAG_INIT, &status); } } + + mVisible = PR_FALSE; +} + +static PRUint32 GetURIHash(nsIURI* aURI) +{ + nsCAutoString str; + aURI->GetSpec(str); + return HashString(str); +} + +void +nsDocument::AddStyleRelevantLink(nsIContent* aContent, nsIURI* aURI) +{ + nsUint32ToContentHashEntry* entry = mLinkMap.PutEntry(GetURIHash(aURI)); + if (!entry) // out of memory? + return; + entry->PutContent(aContent); +} + +void +nsDocument::ForgetLink(nsIContent* aContent) +{ + nsCOMPtr uri = nsContentUtils::GetLinkURI(aContent); + if (!uri) + return; + PRUint32 hash = GetURIHash(uri); + nsUint32ToContentHashEntry* entry = mLinkMap.GetEntry(hash); + if (!entry) + return; + + entry->RemoveContent(aContent); + if (entry->IsEmpty()) { + // Remove the entry and allow the table to resize, in case + // a lot of links are being removed from the document or modified + mLinkMap.RemoveEntry(hash); + } +} + +class URIVisitNotifier : public nsUint32ToContentHashEntry::Visitor +{ +public: + nsCAutoString matchURISpec; + nsIDocument* document; + + virtual void Visit(nsIContent* aContent) { + // Ensure that the URIs really match before we try to do anything + nsCOMPtr uri = nsContentUtils::GetLinkURI(aContent); + if (!uri) { + NS_ERROR("Should have found a URI for content in the link map"); + return; + } + nsCAutoString spec; + uri->GetSpec(spec); + // We use nsCString::Equals here instead of nsIURI::Equals because + // history matching is all based on spec equality + if (!spec.Equals(matchURISpec)) + return; + + // Throw away the cached link state so it gets refetched by the style + // system + nsCOMPtr link = do_QueryInterface(aContent); + if (link) { + link->SetLinkState(eLinkState_Unknown); + } + document->ContentStatesChanged(aContent, nsnull, NS_EVENT_STATE_VISITED); + } +}; + +void +nsDocument::NotifyURIVisitednessChanged(nsIURI* aURI) +{ + if (!mVisible) { + mVisitednessChangedURIs.AppendObject(aURI); + return; + } + + nsUint32ToContentHashEntry* entry = mLinkMap.GetEntry(GetURIHash(aURI)); + if (!entry) + return; + + URIVisitNotifier visitor; + visitor.document = this; + aURI->GetSpec(visitor.matchURISpec); + entry->VisitContent(&visitor); +} + +void +nsDocument::UpdateLinkMap() +{ + NS_ASSERTION(mVisible, + "Should only be updating the link map in visible documents"); + if (!mVisible) + return; + + PRInt32 count = mVisitednessChangedURIs.Count(); + for (PRInt32 i = 0; i < count; ++i) { + NotifyURIVisitednessChanged(mVisitednessChangedURIs[i]); + } + mVisitednessChangedURIs.Clear(); } diff --git a/mozilla/content/base/src/nsDocument.h b/mozilla/content/base/src/nsDocument.h index acacfb7c9ec..edebf76e592 100644 --- a/mozilla/content/base/src/nsDocument.h +++ b/mozilla/content/base/src/nsDocument.h @@ -44,6 +44,7 @@ #include "nsWeakReference.h" #include "nsWeakPtr.h" #include "nsVoidArray.h" +#include "nsHashSets.h" #include "nsIDOMXMLDocument.h" #include "nsIDOM3Document.h" #include "nsIDOMDocumentView.h" @@ -110,6 +111,85 @@ class nsIFormControl; struct nsRadioGroupStruct; class nsOnloadBlocker; +/** + * Hashentry using a PRUint32 key and a cheap set of nsIContent* owning + * pointers for the value. + * + * @see nsTHashtable::EntryType for specification + */ +class nsUint32ToContentHashEntry : public PLDHashEntryHdr +{ + public: + typedef const PRUint32& KeyType; + typedef const PRUint32* KeyTypePointer; + + nsUint32ToContentHashEntry(const KeyTypePointer key) : + mValue(*key), mValOrHash(nsnull) { } + nsUint32ToContentHashEntry(const nsUint32ToContentHashEntry& toCopy) : + mValue(toCopy.mValue), mValOrHash(toCopy.mValOrHash) + { + // Pathetic attempt to not die: clear out the other mValOrHash so we're + // effectively stealing it. If toCopy is destroyed right after this, + // we'll be OK. + NS_CONST_CAST(nsUint32ToContentHashEntry&, toCopy).mValOrHash = nsnull; + NS_ERROR("Copying not supported. Fasten your seat belt."); + } + ~nsUint32ToContentHashEntry() { Destroy(); } + + KeyType GetKey() const { return mValue; } + KeyTypePointer GetKeyPointer() const { return &mValue; } + + PRBool KeyEquals(KeyTypePointer aKey) const { return mValue == *aKey; } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) { return *aKey; } + enum { ALLOW_MEMMOVE = PR_TRUE }; + + // Content set methods + nsresult PutContent(nsIContent* aContent); + + void RemoveContent(nsIContent* aContent); + + struct Visitor { + virtual void Visit(nsIContent* aContent) = 0; + }; + void VisitContent(Visitor* aVisitor); + + PRBool IsEmpty() { return mValOrHash == nsnull; } + + private: + typedef unsigned long PtrBits; + typedef nsTHashtable HashSet; + /** Get the hash pointer (or null if we're not a hash) */ + HashSet* GetHashSet() + { + return (PtrBits(mValOrHash) & 0x1) ? nsnull : (HashSet*)mValOrHash; + } + /** Find out whether it is an nsIContent (returns weak) */ + nsIContent* GetContent() + { + return (PtrBits(mValOrHash) & 0x1) + ? (nsIContent*)(PtrBits(mValOrHash) & ~0x1) + : nsnull; + } + /** Set the single element, adding a reference */ + nsresult SetContent(nsIContent* aVal) + { + NS_IF_ADDREF(aVal); + mValOrHash = (void*)(PtrBits(aVal) | 0x1); + return NS_OK; + } + /** Initialize the hash */ + nsresult InitHashSet(HashSet** aSet); + + void Destroy(); + + private: + const PRUint32 mValue; + /** A hash or nsIContent ptr, depending on the lower bit (0=hash, 1=ptr) */ + void* mValOrHash; +}; + class nsDocHeaderData { @@ -562,6 +642,10 @@ public: virtual NS_HIDDEN_(void) BlockOnload(); virtual NS_HIDDEN_(void) UnblockOnload(); + virtual NS_HIDDEN_(void) AddStyleRelevantLink(nsIContent* aContent, nsIURI* aURI); + virtual NS_HIDDEN_(void) ForgetLink(nsIContent* aContent); + virtual NS_HIDDEN_(void) NotifyURIVisitednessChanged(nsIURI* aURI); + protected: void DispatchContentLoadedEvents(); @@ -572,6 +656,8 @@ protected: PRInt32& aCharsetSource, nsACString& aCharset); + void UpdateLinkMap(); + nsresult doCreateShell(nsPresContext* aContext, nsIViewManager* aViewManager, nsStyleSet* aStyleSet, nsCompatibility aCompatMode, @@ -592,7 +678,7 @@ protected: return kNameSpaceID_None; }; - nsDocument() : nsIDocument() {} + nsDocument() : nsIDocument(), mVisible(PR_TRUE) {} virtual ~nsDocument(); nsCString mReferrer; @@ -635,10 +721,11 @@ protected: nsHashtable mRadioGroups; // True if the document has been detached from its content viewer. - PRPackedBool mIsGoingAway; - + PRPackedBool mIsGoingAway:1; // True if the document is being destroyed. - PRPackedBool mInDestructor; + PRPackedBool mInDestructor:1; + // True if the document "page" is not hidden + PRPackedBool mVisible:1; PRUint8 mXMLDeclarationBits; @@ -673,8 +760,12 @@ private: PRUint32 mOnloadBlockCount; nsCOMPtr mOnloadBlocker; + + // A map from unvisited URI hashes to content elements + nsTHashtable mLinkMap; + // URIs whose visitedness has changed while we were hidden + nsCOMArray mVisitednessChangedURIs; }; - #endif /* nsDocument_h___ */ diff --git a/mozilla/content/base/src/nsGenericElement.cpp b/mozilla/content/base/src/nsGenericElement.cpp index 8ceb0c535e3..3c852fcf46b 100644 --- a/mozilla/content/base/src/nsGenericElement.cpp +++ b/mozilla/content/base/src/nsGenericElement.cpp @@ -1897,6 +1897,10 @@ nsGenericElement::UnbindFromTree(PRBool aDeep, PRBool aNullParent) // anonymous content that the document is changing. document->BindingManager()->ChangeDocumentFor(this, document, nsnull); + if (HasAttr(kNameSpaceID_XLink, nsHTMLAtoms::href)) { + document->ForgetLink(this); + } + nsCOMPtr domElement = do_QueryInterface(this); if (domElement) { @@ -3488,6 +3492,20 @@ nsGenericElement::SetAttr(PRInt32 aNamespaceID, nsIAtom* aName, NS_ASSERTION(aNamespaceID != kNameSpaceID_Unknown, "Don't call SetAttr with unknown namespace"); + if (kNameSpaceID_XLink == aNamespaceID && nsHTMLAtoms::href == aName) { + // XLink URI(s) might be changing. Drop the link from the map. If it + // is still style relevant it will be re-added by + // nsStyleUtil::IsSimpleXlink. Make sure to keep the style system + // consistent so this remains true! In particular if the style system + // were to get smarter and not restyling an XLink element if the href + // doesn't change in a "significant" way, we'd need to do the same + // significance check here. + nsIDocument* doc = GetCurrentDoc(); + if (doc) { + doc->ForgetLink(this); + } + } + PRBool modification = PR_FALSE; nsAutoString oldValue; @@ -3642,6 +3660,11 @@ nsGenericElement::UnsetAttr(PRInt32 aNameSpaceID, nsIAtom* aName, nsIDocument *document = GetCurrentDoc(); mozAutoDocUpdate updateBatch(document, UPDATE_CONTENT_MODEL, aNotify); if (document) { + if (kNameSpaceID_XLink == aNameSpaceID && nsHTMLAtoms::href == aName) { + // XLink URI might be changing. + document->ForgetLink(this); + } + if (aNotify) { document->AttributeWillChange(this, aNameSpaceID, aName); } diff --git a/mozilla/content/events/public/nsIEventStateManager.h b/mozilla/content/events/public/nsIEventStateManager.h index 54e5fe7d71c..dd37ca0135c 100644 --- a/mozilla/content/events/public/nsIEventStateManager.h +++ b/mozilla/content/events/public/nsIEventStateManager.h @@ -151,5 +151,6 @@ public: // CSS 3 UI #define NS_EVENT_STATE_REQUIRED 0x00000040 #define NS_EVENT_STATE_OPTIONAL 0x00000080 +#define NS_EVENT_STATE_VISITED 0x00000100 #endif // nsIEventStateManager_h__ diff --git a/mozilla/content/html/content/src/nsHTMLAnchorElement.cpp b/mozilla/content/html/content/src/nsHTMLAnchorElement.cpp index a32be762a72..0767a66e281 100644 --- a/mozilla/content/html/content/src/nsHTMLAnchorElement.cpp +++ b/mozilla/content/html/content/src/nsHTMLAnchorElement.cpp @@ -195,8 +195,12 @@ nsHTMLAnchorElement::UnbindFromTree(PRBool aDeep, PRBool aNullParent) { if (IsInDoc()) { RegUnRegAccessKey(PR_FALSE); + GetCurrentDoc()->ForgetLink(this); + // If this link is ever reinserted into a document, it might + // be under a different xml:base, so forget the cached state now + mLinkState = eLinkState_Unknown; } - + nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent); } @@ -577,6 +581,13 @@ nsHTMLAnchorElement::SetAttr(PRInt32 aNameSpaceID, nsIAtom* aName, nsAutoString val; GetHref(val); if (!val.Equals(aValue)) { + nsIDocument* doc = GetCurrentDoc(); + if (doc) { + doc->ForgetLink(this); + // The change to 'href' will cause style reresolution which will + // eventually recompute the link state and re-add this element + // to the link map if necessary. + } SetLinkState(eLinkState_Unknown); } } diff --git a/mozilla/content/html/content/src/nsHTMLAreaElement.cpp b/mozilla/content/html/content/src/nsHTMLAreaElement.cpp index 8a3589a2291..4ef0c9fbfe7 100644 --- a/mozilla/content/html/content/src/nsHTMLAreaElement.cpp +++ b/mozilla/content/html/content/src/nsHTMLAreaElement.cpp @@ -228,6 +228,10 @@ nsHTMLAreaElement::UnbindFromTree(PRBool aDeep, PRBool aNullParent) { if (IsInDoc()) { RegUnRegAccessKey(PR_FALSE); + GetCurrentDoc()->ForgetLink(this); + // If this link is ever reinserted into a document, it might + // be under a different xml:base, so forget the cached state now + mLinkState = eLinkState_Unknown; } nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent); @@ -243,6 +247,13 @@ nsHTMLAreaElement::SetAttr(PRInt32 aNameSpaceID, nsIAtom* aName, } if (aName == nsHTMLAtoms::href && aNameSpaceID == kNameSpaceID_None) { + nsIDocument* doc = GetCurrentDoc(); + if (doc) { + doc->ForgetLink(this); + // The change to 'href' will cause style reresolution which will + // eventually recompute the link state and re-add this element + // to the link map if necessary. + } SetLinkState(eLinkState_Unknown); } diff --git a/mozilla/content/html/content/src/nsHTMLLinkElement.cpp b/mozilla/content/html/content/src/nsHTMLLinkElement.cpp index f155f94741c..438e2221d9d 100644 --- a/mozilla/content/html/content/src/nsHTMLLinkElement.cpp +++ b/mozilla/content/html/content/src/nsHTMLLinkElement.cpp @@ -233,6 +233,12 @@ void nsHTMLLinkElement::UnbindFromTree(PRBool aDeep, PRBool aNullParent) { nsCOMPtr oldDoc = GetCurrentDoc(); + if (oldDoc) { + GetCurrentDoc()->ForgetLink(this); + // If this link is ever reinserted into a document, it might + // be under a different xml:base, so forget the cached state now + mLinkState = eLinkState_Unknown; + } // XXXbz we really shouldn't fire the event until after we've finished with // the outermost UnbindFromTree... In particular, this can effectively cause @@ -289,6 +295,13 @@ nsHTMLLinkElement::SetAttr(PRInt32 aNameSpaceID, nsIAtom* aName, PRBool aNotify) { if (aName == nsHTMLAtoms::href && kNameSpaceID_None == aNameSpaceID) { + nsIDocument* doc = GetCurrentDoc(); + if (doc) { + doc->ForgetLink(this); + // The change to 'href' will cause style reresolution which will + // eventually recompute the link state and re-add this element + // to the link map if necessary. + } SetLinkState(eLinkState_Unknown); } diff --git a/mozilla/docshell/base/nsDocShell.cpp b/mozilla/docshell/base/nsDocShell.cpp index d8bf45554e1..1120377cab6 100644 --- a/mozilla/docshell/base/nsDocShell.cpp +++ b/mozilla/docshell/base/nsDocShell.cpp @@ -66,6 +66,7 @@ #include "nsIWebBrowserChrome.h" #include "nsPoint.h" #include "nsGfxCIID.h" +#include "nsIObserverService.h" #include "nsIPrompt.h" #include "nsIAuthPrompt.h" #include "nsTextFormatter.h" @@ -7831,13 +7832,27 @@ NS_IMETHODIMP nsDocShell::MakeEditable(PRBool inWaitForUriLoad) nsresult nsDocShell::AddToGlobalHistory(nsIURI * aURI, PRBool aRedirect, nsIURI * aReferrer) { - if (mItemType != typeContent) + if (mItemType != typeContent || !mGlobalHistory) return NS_OK; - if (!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; - return mGlobalHistory->AddURI(aURI, aRedirect, !IsFrame(), aReferrer); + if (!visited) { + nsCOMPtr obsService = + do_GetService("@mozilla.org/observer-service;1"); + if (obsService) { + obsService->NotifyObservers(aURI, NS_LINK_VISITED_EVENT_TOPIC, nsnull); + } + } + + return NS_OK; } //***************************************************************************** diff --git a/mozilla/layout/base/nsPresShell.cpp b/mozilla/layout/base/nsPresShell.cpp index 47c78086de8..39e9ba2d958 100644 --- a/mozilla/layout/base/nsPresShell.cpp +++ b/mozilla/layout/base/nsPresShell.cpp @@ -1846,14 +1846,16 @@ PresShell::Init(nsIDocument* aDocument, PR_TRUE); } -#ifdef MOZ_XUL { nsCOMPtr os = do_GetService("@mozilla.org/observer-service;1", &result); - if (os) + if (os) { + os->AddObserver(this, NS_LINK_VISITED_EVENT_TOPIC, PR_FALSE); +#ifdef MOZ_XUL os->AddObserver(this, "chrome-flush-skin-caches", PR_FALSE); - } #endif + } + } // cache the drag service so we can check it during reflows mDragService = do_GetService("@mozilla.org/widget/dragservice;1"); @@ -7218,6 +7220,14 @@ PresShell::Observe(nsISupports* aSubject, } #endif + if (!nsCRT::strcmp(aTopic, NS_LINK_VISITED_EVENT_TOPIC)) { + nsCOMPtr uri = do_QueryInterface(aSubject); + if (uri && mDocument) { + mDocument->NotifyURIVisitednessChanged(uri); + } + return NS_OK; + } + NS_WARNING("unrecognized topic in PresShell::Observe"); return NS_ERROR_FAILURE; } diff --git a/mozilla/layout/style/nsCSSStyleSheet.cpp b/mozilla/layout/style/nsCSSStyleSheet.cpp index 1ca46ac4bd6..195d14e65a0 100644 --- a/mozilla/layout/style/nsCSSStyleSheet.cpp +++ b/mozilla/layout/style/nsCSSStyleSheet.cpp @@ -3108,10 +3108,12 @@ static PRBool SelectorMatches(RuleProcessorData &data, result = localTrue; } else if (nsCSSPseudoClasses::link == pseudoClass->mAtom) { - result = localTrue == (eLinkState_Unvisited == data.mLinkState); + result = (aStateMask & NS_EVENT_STATE_VISITED) || + localTrue == (eLinkState_Unvisited == data.mLinkState); } else if (nsCSSPseudoClasses::visited == pseudoClass->mAtom) { - result = localTrue == (eLinkState_Visited == data.mLinkState); + result = (aStateMask & NS_EVENT_STATE_VISITED) || + localTrue == (eLinkState_Visited == data.mLinkState); } } else { @@ -3644,6 +3646,8 @@ PRBool IsStateSelector(nsCSSSelector& aSelector) (pseudoClass->mAtom == nsCSSPseudoClasses::focus) || (pseudoClass->mAtom == nsCSSPseudoClasses::hover) || (pseudoClass->mAtom == nsCSSPseudoClasses::target) || + (pseudoClass->mAtom == nsCSSPseudoClasses::link) || + (pseudoClass->mAtom == nsCSSPseudoClasses::visited) || (pseudoClass->mAtom == nsCSSPseudoClasses::required) || (pseudoClass->mAtom == nsCSSPseudoClasses::optional)) { return PR_TRUE; diff --git a/mozilla/layout/style/nsHTMLStyleSheet.cpp b/mozilla/layout/style/nsHTMLStyleSheet.cpp index 894ba44bd78..c825944dcb0 100644 --- a/mozilla/layout/style/nsHTMLStyleSheet.cpp +++ b/mozilla/layout/style/nsHTMLStyleSheet.cpp @@ -480,13 +480,15 @@ NS_IMETHODIMP nsHTMLStyleSheet::HasStateDependentStyle(StateRuleProcessorData* aData, nsReStyleHint* aResult) { - if (mActiveRule && - (aData->mStateMask & NS_EVENT_STATE_ACTIVE) && - aData->mStyledContent && + if (aData->mStyledContent && aData->mIsHTMLContent && aData->mIsHTMLLink && - aData->mContentTag == nsHTMLAtoms::a) + aData->mContentTag == nsHTMLAtoms::a && + ((mActiveRule && (aData->mStateMask & NS_EVENT_STATE_ACTIVE)) || + (mLinkRule && (aData->mStateMask & NS_EVENT_STATE_VISITED)) || + (mVisitedRule && (aData->mStateMask & NS_EVENT_STATE_VISITED)))) { *aResult = eReStyle_Self; + } else *aResult = nsReStyleHint(0); diff --git a/mozilla/layout/style/nsStyleUtil.cpp b/mozilla/layout/style/nsStyleUtil.cpp index 0a447bbe369..fcefb9eccf6 100644 --- a/mozilla/layout/style/nsStyleUtil.cpp +++ b/mozilla/layout/style/nsStyleUtil.cpp @@ -458,6 +458,12 @@ PRBool nsStyleUtil::IsHTMLLink(nsIContent *aContent, nsIAtom *aTag, nsPresContex } else { linkState = eLinkState_NotLink; } + if (linkState != eLinkState_NotLink) { + nsIDocument* doc = aPresContext->GetDocument(); + if (doc) { + doc->AddStyleRelevantLink(aContent, hrefURI); + } + } link->SetLinkState(linkState); } if (linkState != eLinkState_NotLink) { @@ -495,6 +501,10 @@ PRBool nsStyleUtil::IsSimpleXlink(nsIContent *aContent, nsPresContext *aPresCont // no link handler? then all links are unvisited *aState = eLinkState_Unvisited; } + nsIDocument* doc = aPresContext->GetDocument(); + if (doc) { + doc->AddStyleRelevantLink(aContent, absURI); + } rv = PR_TRUE; } diff --git a/mozilla/toolkit/components/history/src/nsGlobalHistory.cpp b/mozilla/toolkit/components/history/src/nsGlobalHistory.cpp index a7e1a69ec6e..14890287f04 100644 --- a/mozilla/toolkit/components/history/src/nsGlobalHistory.cpp +++ b/mozilla/toolkit/components/history/src/nsGlobalHistory.cpp @@ -485,6 +485,8 @@ nsGlobalHistory::nsGlobalHistory() mIgnoreSchemes.AppendString(NS_LITERAL_STRING("ftp://")); mIgnoreHostnames.AppendString(NS_LITERAL_STRING("www.")); mIgnoreHostnames.AppendString(NS_LITERAL_STRING("ftp.")); + + mTypedHiddenURIs.Init(3); } nsGlobalHistory::~nsGlobalHistory() @@ -695,8 +697,10 @@ nsGlobalHistory::AddExistingPageToDatabase(nsIMdbRow *row, // if the page was typed, unhide it now because it's // known to be valid - if (HasCell(mEnv, row, kToken_TypedColumn)) + if (HasCell(mEnv, row, kToken_TypedColumn)) { + mTypedHiddenURIs.Remove(URISpec); row->CutColumn(mEnv, kToken_HiddenColumn); + } // Update last visit date. // First get the old date so we can update observers... @@ -1333,6 +1337,16 @@ nsGlobalHistory::IsVisited(nsIURI* aURI, PRBool *_retval) rv = FindRow(kToken_URLColumn, URISpec.get(), nsnull); *_retval = NS_SUCCEEDED(rv); + + // Hidden, typed URIs haven't really been visited yet. They've only + // been typed in and the actual load hasn't happened yet. We maintain + // the list of hidden+typed URIs in memory in mTypedHiddenURIs because + // the list will usually be small and checking the actual Mork row + // would require several dynamic memory allocations. + if (*_retval && mTypedHiddenURIs.Contains(URISpec)) + { + *_retval = PR_FALSE; + } return NS_OK; } @@ -1443,6 +1457,7 @@ nsGlobalHistory::MarkPageAsTyped(nsIURI *aURI) // We don't know if this is a valid URI yet. Hide it until it finishes // loading. SetRowValue(row, kToken_HiddenColumn, 1); + mTypedHiddenURIs.Put(spec); } return SetRowValue(row, kToken_TypedColumn, 1); diff --git a/mozilla/toolkit/components/history/src/nsGlobalHistory.h b/mozilla/toolkit/components/history/src/nsGlobalHistory.h index 17160030e39..d03ec36dc51 100644 --- a/mozilla/toolkit/components/history/src/nsGlobalHistory.h +++ b/mozilla/toolkit/components/history/src/nsGlobalHistory.h @@ -60,6 +60,7 @@ #include "nsIAutoCompleteSearch.h" #include "nsIAutoCompleteResult.h" #include "nsIAutoCompleteResultTypes.h" +#include "nsHashSets.h" //---------------------------------------------------------------------- // @@ -299,6 +300,9 @@ protected: // meta-data tokens mdb_column kToken_LastPageVisited; + // A set of the page URI specs that have been typed but not yet loaded + nsCStringHashSet mTypedHiddenURIs; + // // AddPage-oriented stuff // diff --git a/mozilla/toolkit/content/contentAreaUtils.js b/mozilla/toolkit/content/contentAreaUtils.js index 0f53163b92d..c2360af368d 100644 --- a/mozilla/toolkit/content/contentAreaUtils.js +++ b/mozilla/toolkit/content/contentAreaUtils.js @@ -42,7 +42,8 @@ * Determine whether or not a given focused DOMWindow is in the content * area. **/ - + +// linkNode is not used anymore function openNewTabWith(href, linkNode, event, securityCheck, postData, sendReferrer) { if (securityCheck) @@ -77,11 +78,9 @@ function openNewTabWith(href, linkNode, event, securityCheck, postData, sendRefe var referrer = (sendReferrer == false) ? null : getReferrer(document); browser.loadOneTab(href, referrer, originCharset, postData, loadInBackground); - - if (linkNode) - markLinkVisited(href, linkNode); } +// linkNode is not used anymore function openNewWindowWith(href, linkNode, securityCheck, postData, sendReferrer) { if (securityCheck) @@ -99,31 +98,6 @@ function openNewWindowWith(href, linkNode, securityCheck, postData, sendReferrer var referrer = (sendReferrer == false) ? null : getReferrer(document); window.openDialog(getBrowserURL(), "_blank", "chrome,all,dialog=no", href, charsetArg, referrer, postData); - - if (linkNode) - markLinkVisited(href, linkNode); -} - -function markLinkVisited(href, linkNode) -{ - var globalHistory = Components.classes["@mozilla.org/browser/global-history;2"] - .getService(Components.interfaces.nsIGlobalHistory2); - - var uri = makeURI(href); - if (!globalHistory.isVisited(uri)) { - globalHistory.addURI(uri, false, true, null); - var oldHref = linkNode.getAttribute("href"); - if (typeof oldHref == "string") { - // Use setAttribute instead of direct assignment. - // (bug 217195, bug 187195) - linkNode.setAttribute("href", ""); - linkNode.setAttribute("href", oldHref); - } - else { - // Converting to string implicitly would be a - // minor security hole (similar to bug 202994). - } - } } function urlSecurityCheck(url, doc) diff --git a/mozilla/xpfe/components/history/src/nsGlobalHistory.cpp b/mozilla/xpfe/components/history/src/nsGlobalHistory.cpp index 3574f4f7934..019f5e59b41 100644 --- a/mozilla/xpfe/components/history/src/nsGlobalHistory.cpp +++ b/mozilla/xpfe/components/history/src/nsGlobalHistory.cpp @@ -372,6 +372,8 @@ nsMdbTableEnumerator::Init(nsIMdbEnv* aEnv, mdb_err err; err = mTable->GetTableRowCursor(mEnv, -1, &mCursor); if (err != 0) return NS_ERROR_FAILURE; + + mTypedHiddenURIs.Init(3); return NS_OK; } @@ -706,8 +708,14 @@ nsGlobalHistory::AddExistingPageToDatabase(nsIMdbRow *row, // if the page was typed, unhide it now because it's // known to be valid - if (HasCell(mEnv, row, kToken_TypedColumn)) + if (HasCell(mEnv, row, kToken_TypedColumn)) { + nsCAutoString URISpec; + rv = GetRowValue(row, kToken_URLColumn, URISpec); + NS_ENSURE_SUCCESS(rv, rv); + + mTypedHiddenURIs.Remove(URISpec); row->CutColumn(mEnv, kToken_HiddenColumn); + } // Update last visit date. // First get the old date so we can update observers... @@ -1252,7 +1260,17 @@ nsGlobalHistory::IsVisited(nsIURI* aURI, PRBool *_retval) rv = FindRow(kToken_URLColumn, URISpec.get(), nsnull); *_retval = NS_SUCCEEDED(rv); - + + // Hidden, typed URIs haven't really been visited yet. They've only + // been typed in and the actual load hasn't happened yet. We maintain + // the list of hidden+typed URIs in memory in mTypedHiddenURIs because + // the list will usually be small and checking the actual Mork row + // would require several dynamic memory allocations. + if (*_retval && mTypedHiddenURIs.Contains(URISpec)) + { + *_retval = PR_FALSE; + } + return NS_OK; } @@ -1357,6 +1375,7 @@ nsGlobalHistory::MarkPageAsTyped(nsIURI *aURI) // We don't know if this is a valid URI yet. Hide it until it finishes // loading. SetRowValue(row, kToken_HiddenColumn, 1); + mTypedHiddenURIs.Put(spec); } return SetRowValue(row, kToken_TypedColumn, 1); diff --git a/mozilla/xpfe/components/history/src/nsGlobalHistory.h b/mozilla/xpfe/components/history/src/nsGlobalHistory.h index 660015f7b4c..620056ee48a 100644 --- a/mozilla/xpfe/components/history/src/nsGlobalHistory.h +++ b/mozilla/xpfe/components/history/src/nsGlobalHistory.h @@ -57,6 +57,7 @@ #include "nsString.h" #include "nsITimer.h" #include "nsIAutoCompleteSession.h" +#include "nsHashSets.h" //---------------------------------------------------------------------- // @@ -299,6 +300,8 @@ protected: // meta-data tokens mdb_column kToken_LastPageVisited; mdb_column kToken_ByteOrder; + + nsCStringHashSet mTypedHiddenURIs; // // AddPage-oriented stuff