Bug 787134 - Make links not in a document or with an invalid href respond to :link selector. r=bzbarsky

This commit is contained in:
Benedict Singer 2012-12-07 09:35:14 -05:00
parent dab388309c
commit 8b16d029d1
12 changed files with 122 additions and 67 deletions

View File

@ -27,10 +27,9 @@ struct IMEState;
} // namespace mozilla
enum nsLinkState {
eLinkState_Unknown = 0,
eLinkState_Unvisited = 1,
eLinkState_Visited = 2,
eLinkState_NotLink = 3
eLinkState_NotLink = 3
};
// IID for the nsIContent interface

View File

@ -24,7 +24,8 @@ namespace dom {
Link::Link(Element *aElement)
: mElement(aElement)
, mHistory(services::GetHistoryService())
, mLinkState(defaultState)
, mLinkState(eLinkState_NotLink)
, mNeedsRegistration(false)
, mRegistered(false)
{
NS_ABORT_IF_FALSE(mElement, "Must have an element");
@ -35,13 +36,18 @@ Link::~Link()
UnregisterFromHistory();
}
bool
Link::ElementHasHref() const
{
return ((!mElement->IsSVG() && mElement->HasAttr(kNameSpaceID_None, nsGkAtoms::href))
|| (!mElement->IsHTML() && mElement->HasAttr(kNameSpaceID_XLink, nsGkAtoms::href)));
}
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 nsLinkState(mLinkState);
}
@ -74,36 +80,28 @@ Link::LinkState() const
// track that state. Cast away that constness!
Link *self = const_cast<Link *>(this);
// If we are not in the document, default to not visited.
Element *element = self->mElement;
if (!element->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).
// If we have not yet registered for notifications and need to,
// due to our href changing, register now!
if (!mRegistered && mNeedsRegistration && element->IsInDoc()) {
// Only try and register once.
self->mNeedsRegistration = false;
nsCOMPtr<nsIURI> hrefURI(GetURI());
if (!hrefURI) {
self->mLinkState = eLinkState_NotLink;
return nsEventStates();
}
// Assume that we are not visited until we are told otherwise.
self->mLinkState = eLinkState_Unvisited;
// We have a good href, so register with History.
if (mHistory) {
// Make sure the href attribute has a valid link (bug 23209).
// If we have a good href, register with History if available.
if (mHistory && hrefURI) {
nsresult rv = mHistory->RegisterVisitedCallback(hrefURI, self);
if (NS_SUCCEEDED(rv)) {
self->mRegistered = true;
// And make sure we are in the document's link map.
nsIDocument *doc = element->GetCurrentDoc();
if (doc) {
doc->AddStyleRelevantLink(self);
}
doc->GetCurrentDoc()->AddStyleRelevantLink(self);
}
}
}
@ -135,8 +133,8 @@ Link::GetURI() const
Element *element = self->mElement;
uri = element->GetHrefURI();
// We want to cache the URI if the node is in the document.
if (uri && element->IsInDoc()) {
// We want to cache the URI if we have it
if (uri) {
mCachedURI = uri;
}
@ -424,39 +422,56 @@ Link::GetHash(nsAString &_hash)
}
void
Link::ResetLinkState(bool aNotify)
Link::ResetLinkState(bool aNotify, bool aHasHref)
{
// If we are in our default state, bail early.
if (mLinkState == defaultState) {
return;
nsLinkState defaultState;
// The default state for links with an href is unvisited.
if (aHasHref) {
defaultState = eLinkState_Unvisited;
} else {
defaultState = eLinkState_NotLink;
}
Element *element = mElement;
// If !mNeedsRegstration, then either we've never registered, or we're
// currently registered; in either case, we should remove ourself
// from the doc and the history.
if (!mNeedsRegistration && mLinkState != eLinkState_NotLink) {
nsIDocument *doc = mElement->GetCurrentDoc();
if (doc && (mRegistered || mLinkState == eLinkState_Visited)) {
// Tell the document to forget about this link if we've registered
// with it before.
doc->ForgetLink(this);
}
// Tell the document to forget about this link if we were a link before.
nsIDocument *doc = element->GetCurrentDoc();
if (doc && mLinkState != eLinkState_NotLink) {
doc->ForgetLink(this);
UnregisterFromHistory();
}
UnregisterFromHistory();
// If we have an href, we should register with the history.
mNeedsRegistration = aHasHref;
// If we've cached the URI, reset always invalidates it.
mCachedURI = nullptr;
// Update our state back to the default.
mLinkState = defaultState;
// Get rid of our cached URI.
mCachedURI = nullptr;
// We have to be very careful here: if aNotify is false we do NOT
// want to call UpdateState, because that will call into LinkState()
// and try to start off loads, etc. But ResetLinkState is called
// with aNotify false when things are in inconsistent states, so
// we'll get confused in that situation. Instead, just silently
// update the link state on mElement.
// update the link state on mElement. Since we might have set the
// link state to unvisited, make sure to update with that state if
// required.
if (aNotify) {
mElement->UpdateState(aNotify);
} else {
mElement->UpdateLinkState(nsEventStates());
if (mLinkState == eLinkState_Unvisited) {
mElement->UpdateLinkState(NS_EVENT_STATE_UNVISITED);
} else {
mElement->UpdateLinkState(nsEventStates());
}
}
}

View File

@ -28,8 +28,6 @@ class Link : public nsISupports
public:
NS_DECLARE_STATIC_IID_ACCESSOR(MOZILLA_DOM_LINK_IMPLEMENTATION_IID)
static const nsLinkState defaultState = eLinkState_Unknown;
/**
* aElement is the element pointer corresponding to this link.
*/
@ -77,7 +75,7 @@ public:
* true if ResetLinkState should notify the owning document about style
* changes or false if it should not.
*/
void ResetLinkState(bool aNotify);
void ResetLinkState(bool aNotify, bool aHasHref);
// This method nevers returns a null element.
Element* GetElement() const { return mElement; }
@ -103,6 +101,8 @@ public:
virtual size_t
SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) const;
bool ElementHasHref() const;
protected:
virtual ~Link();
@ -141,6 +141,8 @@ private:
uint16_t mLinkState;
bool mNeedsRegistration;
bool mRegistered;
};

View File

@ -7269,7 +7269,7 @@ nsDocument::RefreshLinkHrefs()
// Reset all of our styled links.
nsAutoScriptBlocker scriptBlocker;
for (LinkArray::size_type i = 0; i < linksToNotify.Length(); i++) {
linksToNotify[i]->ResetLinkState(true);
linksToNotify[i]->ResetLinkState(true, linksToNotify[i]->ElementHasHref());
}
}

View File

@ -219,7 +219,7 @@ nsHTMLAnchorElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
nsIContent* aBindingParent,
bool aCompileEventHandlers)
{
Link::ResetLinkState(false);
Link::ResetLinkState(false, Link::ElementHasHref());
nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent,
aBindingParent,
@ -257,7 +257,7 @@ nsHTMLAnchorElement::UnbindFromTree(bool aDeep, bool aNullParent)
// If this link is ever reinserted into a document, it might
// be under a different xml:base, so forget the cached state now.
Link::ResetLinkState(false);
Link::ResetLinkState(false, Link::ElementHasHref());
nsIDocument* doc = GetCurrentDoc();
if (doc) {
@ -466,7 +466,7 @@ nsHTMLAnchorElement::SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
// that content states have changed will call IntrinsicState, which will try
// to get updated information about the visitedness from Link.
if (reset) {
Link::ResetLinkState(!!aNotify);
Link::ResetLinkState(!!aNotify, true);
}
return rv;
@ -485,7 +485,7 @@ nsHTMLAnchorElement::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
// that content states have changed will call IntrinsicState, which will try
// to get updated information about the visitedness from Link.
if (aAttribute == nsGkAtoms::href && kNameSpaceID_None == aNameSpaceID) {
Link::ResetLinkState(!!aNotify);
Link::ResetLinkState(!!aNotify, false);
}
return rv;

View File

@ -187,7 +187,7 @@ nsHTMLAreaElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
nsIContent* aBindingParent,
bool aCompileEventHandlers)
{
Link::ResetLinkState(false);
Link::ResetLinkState(false, Link::ElementHasHref());
if (aDocument) {
aDocument->RegisterPendingLinkUpdate(this);
}
@ -202,7 +202,7 @@ nsHTMLAreaElement::UnbindFromTree(bool aDeep, bool aNullParent)
{
// If this link is ever reinserted into a document, it might
// be under a different xml:base, so forget the cached state now.
Link::ResetLinkState(false);
Link::ResetLinkState(false, Link::ElementHasHref());
nsIDocument* doc = GetCurrentDoc();
if (doc) {
@ -226,7 +226,7 @@ nsHTMLAreaElement::SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
// that content states have changed will call IntrinsicState, which will try
// to get updated information about the visitedness from Link.
if (aName == nsGkAtoms::href && aNameSpaceID == kNameSpaceID_None) {
Link::ResetLinkState(!!aNotify);
Link::ResetLinkState(!!aNotify, true);
}
return rv;
@ -245,7 +245,7 @@ nsHTMLAreaElement::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
// that content states have changed will call IntrinsicState, which will try
// to get updated information about the visitedness from Link.
if (aAttribute == nsGkAtoms::href && kNameSpaceID_None == aNameSpaceID) {
Link::ResetLinkState(!!aNotify);
Link::ResetLinkState(!!aNotify, false);
}
return rv;

View File

@ -212,7 +212,7 @@ nsHTMLLinkElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
nsIContent* aBindingParent,
bool aCompileEventHandlers)
{
Link::ResetLinkState(false);
Link::ResetLinkState(false, Link::ElementHasHref());
nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent,
aBindingParent,
@ -250,7 +250,7 @@ nsHTMLLinkElement::UnbindFromTree(bool aDeep, bool aNullParent)
{
// If this link is ever reinserted into a document, it might
// be under a different xml:base, so forget the cached state now.
Link::ResetLinkState(false);
Link::ResetLinkState(false, Link::ElementHasHref());
// Once we have XPCOMGC we shouldn't need to call UnbindFromTree during Unlink
// and so this messy event dispatch can go away.
@ -322,7 +322,7 @@ nsHTMLLinkElement::SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
// that content states have changed will call IntrinsicState, which will try
// to get updated information about the visitedness from Link.
if (aName == nsGkAtoms::href && kNameSpaceID_None == aNameSpaceID) {
Link::ResetLinkState(!!aNotify);
Link::ResetLinkState(!!aNotify, true);
}
if (NS_SUCCEEDED(rv) && aNameSpaceID == kNameSpaceID_None &&
@ -370,7 +370,7 @@ nsHTMLLinkElement::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
// that content states have changed will call IntrinsicState, which will try
// to get updated information about the visitedness from Link.
if (aAttribute == nsGkAtoms::href && kNameSpaceID_None == aNameSpaceID) {
Link::ResetLinkState(!!aNotify);
Link::ResetLinkState(!!aNotify, false);
}
return rv;

View File

@ -269,6 +269,7 @@ MOCHITEST_FILES = \
test_bug780993.html \
test_bug786564.html \
test_bug797113.html \
test_bug787134.html \
test_iframe_sandbox_inheritance.html \
file_iframe_sandbox_a_if1.html \
file_iframe_sandbox_a_if2.html \

View File

@ -0,0 +1,29 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=787134
-->
<head>
<title>Test for Bug 787134</title>
<script type="application/javascript" src="/MochiKit/packed.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="reflect.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=787134">Mozilla Bug 787134</a>
<p id="display"></p>
<p><a id="link-test1" href="example link">example link</a></p>
<pre id="test">
<script>
var div = document.createElement('div');
div.innerHTML = '<a href=#></a>';
var a = div.firstChild;
ok(a.mozMatchesSelector(':link'), "Should match a link not in a document");
is(div.querySelector(':link'), a, "Should find a link not in a document");
a = document.querySelector('#link-test1');
ok(a.mozMatchesSelector(':link'), "Should match a link in a document with an invalid URL");
</script>
</pre>
</body>
</html>

View File

@ -48,7 +48,7 @@ nsMathMLElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
{
static const char kMathMLStyleSheetURI[] = "resource://gre-resources/mathml.css";
Link::ResetLinkState(false);
Link::ResetLinkState(false, Link::ElementHasHref());
nsresult rv = nsMathMLElementBase::BindToTree(aDocument, aParent,
aBindingParent,
@ -83,7 +83,7 @@ nsMathMLElement::UnbindFromTree(bool aDeep, bool aNullParent)
{
// If this link is ever reinserted into a document, it might
// be under a different xml:base, so forget the cached state now.
Link::ResetLinkState(false);
Link::ResetLinkState(false, Link::ElementHasHref());
nsIDocument* doc = GetCurrentDoc();
if (doc) {
@ -793,7 +793,7 @@ nsMathMLElement::SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
if (aName == nsGkAtoms::href &&
(aNameSpaceID == kNameSpaceID_None ||
aNameSpaceID == kNameSpaceID_XLink)) {
Link::ResetLinkState(!!aNotify);
Link::ResetLinkState(!!aNotify, true);
}
return rv;
@ -813,7 +813,9 @@ nsMathMLElement::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttr,
if (aAttr == nsGkAtoms::href &&
(aNameSpaceID == kNameSpaceID_None ||
aNameSpaceID == kNameSpaceID_XLink)) {
Link::ResetLinkState(!!aNotify);
// Note: just because we removed a single href attr doesn't mean there's no href,
// since there are 2 possible namespaces.
Link::ResetLinkState(!!aNotify, Link::ElementHasHref());
}
return rv;

View File

@ -110,7 +110,7 @@ nsSVGAElement::BindToTree(nsIDocument *aDocument, nsIContent *aParent,
nsIContent *aBindingParent,
bool aCompileEventHandlers)
{
Link::ResetLinkState(false);
Link::ResetLinkState(false, Link::ElementHasHref());
nsresult rv = nsSVGAElementBase::BindToTree(aDocument, aParent,
aBindingParent,
@ -129,7 +129,7 @@ nsSVGAElement::UnbindFromTree(bool aDeep, bool aNullParent)
{
// If this link is ever reinserted into a document, it might
// be under a different xml:base, so forget the cached state now.
Link::ResetLinkState(false);
Link::ResetLinkState(false, Link::ElementHasHref());
nsIDocument* doc = GetCurrentDoc();
if (doc) {
@ -282,7 +282,7 @@ nsSVGAElement::SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
// that content states have changed will call IntrinsicState, which will try
// to get updated information about the visitedness from Link.
if (aName == nsGkAtoms::href && aNameSpaceID == kNameSpaceID_XLink) {
Link::ResetLinkState(!!aNotify);
Link::ResetLinkState(!!aNotify, true);
}
return rv;
@ -300,7 +300,7 @@ nsSVGAElement::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttr,
// that content states have changed will call IntrinsicState, which will try
// to get updated information about the visitedness from Link.
if (aAttr == nsGkAtoms::href && aNameSpaceID == kNameSpaceID_XLink) {
Link::ResetLinkState(!!aNotify);
Link::ResetLinkState(!!aNotify, false);
}
return rv;

View File

@ -72,7 +72,7 @@ namespace dom {
Link::Link(Element* aElement)
: mElement(aElement)
, mLinkState(mozilla::dom::Link::defaultState)
, mLinkState(eLinkState_NotLink)
, mRegistered(false)
{
}
@ -81,11 +81,18 @@ Link::~Link()
{
}
bool
Link::ElementHasHref() const
{
NS_NOTREACHED("Unexpected call to Link::ElementHasHref");
return false; // suppress compiler warning
}
nsLinkState
Link::GetLinkState() const
{
NS_NOTREACHED("Unexpected call to Link::GetLinkState");
return eLinkState_Unknown; // suppress compiler warning
return eLinkState_NotLink; // suppress compiler warning
}
void
@ -95,7 +102,7 @@ Link::SetLinkState(nsLinkState aState)
}
void
Link::ResetLinkState(bool aNotify)
Link::ResetLinkState(bool aNotify, bool aHasHref)
{
NS_NOTREACHED("Unexpected call to Link::ResetLinkState");
}