Bug 209275 - Links' hrefs should be updated when a <base>'s href changes. r=bzbarsky

This commit is contained in:
Justin Lebar 2009-09-24 13:59:43 -04:00
parent ce54997bc6
commit b3e255230a
12 changed files with 725 additions and 18 deletions

View File

@ -70,10 +70,10 @@ enum nsLinkState {
};
// IID for the nsIContent interface
// b877753c-316a-422d-9aec-a1d0cf0928b0
// d510382f-f5eb-48bb-9ad9-b3dc4806faaf
#define NS_ICONTENT_IID \
{ 0xb877753c, 0x316a, 0x422d, \
{ 0x9a, 0xec, 0xa1, 0xd0, 0xcf, 0x09, 0x28, 0xb0 } }
{ 0xd510382f, 0xf5eb, 0x48bb, \
{ 0x9a, 0xd9, 0xb3, 0xdc, 0x48, 0x06, 0xfa, 0xaf } }
/**
* A node of content in a document's content model. This interface
@ -607,6 +607,17 @@ public:
*/
virtual PRBool IsLink(nsIURI** aURI) const = 0;
/**
* If the implementing element is a link, calling this method forces it to
* clear its cached href, if it has one.
*
* This function does not notify the document that it may need to restyle the
* link.
*/
virtual void DropCachedHref()
{
}
/**
* Get the cached state of the link. If the state is unknown,
* return eLinkState_Unknown.

View File

@ -105,8 +105,8 @@ class nsIBoxObject;
// IID for the nsIDocument interface
#define NS_IDOCUMENT_IID \
{ 0xe0ca6723, 0x1efa, 0x4819, \
{ 0x84, 0xbb, 0xfa, 0x48, 0x39, 0xe8, 0xef, 0x19 } }
{0x1666cc78, 0x54ad, 0x4672, \
{0x93, 0x79, 0x9b, 0x6a, 0x61, 0x78, 0x94, 0x1a } }
// Flag for AddStyleSheet().
#define NS_STYLESHEET_FROM_CATALOG (1 << 0)
@ -1165,6 +1165,24 @@ public:
*/
virtual PRBool IsDocumentRightToLeft() { return PR_FALSE; }
/**
* Gets the document's cached pointer to the first <base> element in this
* document which has an href attribute. If the document doesn't contain any
* <base> elements with an href, returns null.
*/
virtual nsIContent* GetFirstBaseNodeWithHref() = 0;
/**
* Sets the document's cached pointer to the first <base> element with an
* href attribute in this document and updates the document's base URI
* according to the element's href.
*
* If the given node is the same as the current first base node, this
* function still updates the document's base URI according to the node's
* href, if it changed.
*/
virtual nsresult SetFirstBaseNodeWithHref(nsIContent *node) = 0;
protected:
~nsIDocument()
{

View File

@ -1768,6 +1768,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsDocument)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mXPathEvaluatorTearoff)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mLayoutHistoryState)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnloadBlocker)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mFirstBaseNodeWithHref)
// An element will only be in the linkmap as long as it's in the
// document, so we'll traverse the table here instead of from the element.
@ -1819,6 +1820,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDocument)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mCachedRootContent)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mDisplayDocument)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mFirstBaseNodeWithHref)
NS_IMPL_CYCLE_COLLECTION_UNLINK_USERDATA
@ -1971,12 +1973,9 @@ nsDocument::ResetToURI(nsIURI *aURI, nsILoadGroup *aLoadGroup,
for (PRInt32 i = PRInt32(count) - 1; i >= 0; i--) {
nsCOMPtr<nsIContent> content = mChildren.ChildAt(i);
// XXXbz this is backwards from how ContentRemoved normally works. That
// is, usually it's dispatched after the content has been removed from
// the tree.
mChildren.RemoveChildAt(i);
nsNodeUtils::ContentRemoved(this, content, i);
content->UnbindFromTree();
mChildren.RemoveChildAt(i);
}
}
mCachedRootContent = nsnull;
@ -1994,7 +1993,9 @@ nsDocument::ResetToURI(nsIURI *aURI, nsILoadGroup *aLoadGroup,
mDOMStyleSheets = nsnull;
SetDocumentURI(aURI);
mDocumentBaseURI = mDocumentURI;
// If mDocumentBaseURI is null, nsIDocument::GetBaseURI() returns
// mDocumentURI.
mDocumentBaseURI = nsnull;
if (aLoadGroup) {
mDocumentLoadGroup = do_GetWeakReference(aLoadGroup);
@ -2249,7 +2250,23 @@ nsDocument::StopDocumentLoad()
void
nsDocument::SetDocumentURI(nsIURI* aURI)
{
nsCOMPtr<nsIURI> oldBase = nsIDocument::GetBaseURI();
mDocumentURI = NS_TryToMakeImmutable(aURI);
nsIURI* newBase = nsIDocument::GetBaseURI();
PRBool equalBases = PR_FALSE;
if (oldBase && newBase) {
oldBase->Equals(newBase, &equalBases);
}
else {
equalBases = !oldBase && !newBase;
}
// If changing the document's URI changed the base URI of the document, we
// need to refresh the hrefs of all the links on the page.
if (!equalBases) {
RefreshLinkHrefs();
}
}
NS_IMETHODIMP
@ -2789,6 +2806,7 @@ nsDocument::SetBaseURI(nsIURI* aURI)
{
nsresult rv = NS_OK;
nsCOMPtr<nsIURI> oldBase = nsIDocument::GetBaseURI();
if (aURI) {
rv = nsContentUtils::GetSecurityManager()->
CheckLoadURIWithPrincipal(NodePrincipal(), aURI,
@ -2800,6 +2818,21 @@ nsDocument::SetBaseURI(nsIURI* aURI)
mDocumentBaseURI = nsnull;
}
nsIURI* newBase = nsIDocument::GetBaseURI();
PRBool equalBases = PR_FALSE;
if (oldBase && newBase) {
oldBase->Equals(newBase, &equalBases);
}
else {
equalBases = !oldBase && !newBase;
}
// If the document's base URI has changed, we need to re-resolve all the
// cached link hrefs relative to the new base.
if (!equalBases) {
RefreshLinkHrefs();
}
return rv;
}
@ -5578,8 +5611,8 @@ NS_IMETHODIMP
nsDocument::GetBaseURI(nsAString &aURI)
{
nsCAutoString spec;
if (mDocumentBaseURI) {
mDocumentBaseURI->GetSpec(spec);
if (nsIDocument::GetBaseURI()) {
nsIDocument::GetBaseURI()->GetSpec(spec);
}
CopyUTF8toUTF16(spec, aURI);
@ -7342,7 +7375,9 @@ public:
return;
// Throw away the cached link state so it gets refetched by the style
// system
// system. We can't call ContentStatesChanged here, because that might
// modify the hashtable. Instead, we'll just insert into this array and
// leave it to our caller to call ContentStatesChanged.
aContent->SetLinkState(eLinkState_Unknown);
contentVisited.AppendObject(aContent);
}
@ -7359,10 +7394,12 @@ nsDocument::NotifyURIVisitednessChanged(nsIURI* aURI)
nsUint32ToContentHashEntry* entry = mLinkMap.GetEntry(GetURIHash(aURI));
if (!entry)
return;
URIVisitNotifier visitor;
aURI->GetSpec(visitor.matchURISpec);
entry->VisitContent(&visitor);
MOZ_AUTO_DOC_UPDATE(this, UPDATE_CONTENT_STATE, PR_TRUE);
for (PRUint32 count = visitor.contentVisited.Count(), i = 0; i < count; ++i) {
ContentStatesChanged(visitor.contentVisited[i],
nsnull, NS_EVENT_STATE_VISITED);
@ -7383,7 +7420,7 @@ nsDocument::UpdateLinkMap()
"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]);
@ -7391,6 +7428,113 @@ nsDocument::UpdateLinkMap()
mVisitednessChangedURIs.Clear();
}
class RefreshLinkStateVisitor : public nsUint32ToContentHashEntry::Visitor
{
public:
nsCOMArray<nsIContent> contentVisited;
virtual void Visit(nsIContent* aContent) {
// We can't call ContentStatesChanged here, because that may modify the link
// map. Instead, we just add to an array and call ContentStatesChanged
// later.
aContent->SetLinkState(eLinkState_Unknown);
contentVisited.AppendObject(aContent);
}
};
static PLDHashOperator
RefreshLinkStateTraverser(nsUint32ToContentHashEntry* aEntry,
void* userArg)
{
RefreshLinkStateVisitor *visitor =
static_cast<RefreshLinkStateVisitor*>(userArg);
aEntry->VisitContent(visitor);
return PL_DHASH_NEXT;
}
// Helper function for nsDocument::RefreshLinkHrefs
static void
DropCachedHrefsRecursive(nsIContent * const elem)
{
// Drop the element's cached href, if it has one. (If it doesn't have
// one, this call does nothing.) We could check first that elem is an <a>
// tag to avoid making a virtual call, but it turns out not to make a
// substantial perf difference either way. This doesn't restyle the link,
// but we do that later.
elem->DropCachedHref();
PRUint32 childCount;
nsIContent * const * child = elem->GetChildArray(&childCount);
nsIContent * const * end = child + childCount;
for ( ; child != end; ++child) {
DropCachedHrefsRecursive(*child);
}
}
void
nsDocument::RefreshLinkHrefs()
{
if (!GetRootContent())
return;
// First, walk the DOM and clear the cached hrefs of all the <a> tags.
DropCachedHrefsRecursive(GetRootContent());
// Now update the styles of everything in the linkmap.
RefreshLinkStateVisitor visitor;
mLinkMap.EnumerateEntries(RefreshLinkStateTraverser, &visitor);
MOZ_AUTO_DOC_UPDATE(this, UPDATE_CONTENT_STATE, PR_TRUE);
for (PRUint32 count = visitor.contentVisited.Count(), i = 0; i < count; i++) {
ContentStatesChanged(visitor.contentVisited[i],
nsnull, NS_EVENT_STATE_VISITED);
}
}
nsIContent*
nsDocument::GetFirstBaseNodeWithHref()
{
return mFirstBaseNodeWithHref;
}
nsresult
nsDocument::SetFirstBaseNodeWithHref(nsIContent *elem)
{
mFirstBaseNodeWithHref = elem;
if (!elem) {
SetBaseURI(nsnull);
return NS_OK;
}
NS_ASSERTION(elem->Tag() == nsGkAtoms::base,
"Setting base node to a non <base> element?");
NS_ASSERTION(elem->GetNameSpaceID() == kNameSpaceID_XHTML,
"Setting base node to a non XHTML element?");
nsIDocument* doc = elem->GetOwnerDoc();
nsIURI* currentURI = nsIDocument::GetDocumentURI();
// Resolve the <base> element's href relative to our current URI
nsAutoString href;
PRBool hasHref = elem->GetAttr(kNameSpaceID_None, nsGkAtoms::href, href);
NS_ASSERTION(hasHref,
"Setting first base node to a node with no href attr?");
nsCOMPtr<nsIURI> newBaseURI;
nsContentUtils::NewURIWithDocumentCharset(
getter_AddRefs(newBaseURI), href, doc, currentURI);
// Try to set our base URI. If that fails, try to set our base URI to null.
nsresult rv = SetBaseURI(newBaseURI);
if (NS_FAILED(rv)) {
return SetBaseURI(nsnull);
}
return rv;
}
NS_IMETHODIMP
nsDocument::GetScriptTypeID(PRUint32 *aScriptType)
{

View File

@ -1033,12 +1033,18 @@ protected:
static PRBool TryChannelCharset(nsIChannel *aChannel,
PRInt32& aCharsetSource,
nsACString& aCharset);
void UpdateLinkMap();
// Call this before the document does something that will unbind all content.
// That will stop us from resolving URIs for all links as they are removed.
void DestroyLinkMap();
// Refreshes the hrefs of all the links in the document.
void RefreshLinkHrefs();
nsIContent* GetFirstBaseNodeWithHref();
nsresult SetFirstBaseNodeWithHref(nsIContent *node);
// Get the root <html> element, or return null if there isn't one (e.g.
// if the root isn't <html>)
nsIContent* GetHtmlContent();
@ -1213,6 +1219,8 @@ protected:
// any. This can change during the lifetime of the document.
nsCOMPtr<nsIApplicationCache> mApplicationCache;
nsCOMPtr<nsIContent> mFirstBaseNodeWithHref;
private:
friend class nsUnblockOnloadEvent;

View File

@ -2160,7 +2160,11 @@ nsGenericHTMLElement::GetURIAttr(nsIAtom* aAttr, nsIAtom* aBaseAttr,
attr->GetStringValue(),
GetOwnerDoc(), baseURI);
if (isURIAttr) {
// We may have to re-resolve all our cached hrefs when the document's base
// URI changes. The base URI depends on the owner document, but it's the
// current document that keeps track of links. If the two documents don't
// match, we shouldn't cache.
if (isURIAttr && GetOwnerDoc() == GetCurrentDoc()) {
const_cast<nsAttrValue*>(attr)->CacheURIValue(*aURI);
}
return PR_TRUE;

View File

@ -127,6 +127,8 @@ public:
virtual nsresult Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const;
virtual void DropCachedHref();
protected:
void ResetLinkCacheState();
@ -486,6 +488,18 @@ nsHTMLAnchorElement::ParseAttribute(PRInt32 aNamespaceID,
aResult);
}
void
nsHTMLAnchorElement::DropCachedHref()
{
nsAttrValue* attr =
const_cast<nsAttrValue*>(mAttrsAndChildren.GetAttr(nsGkAtoms::href));
if (!attr || attr->Type() != nsAttrValue::eLazyURIValue)
return;
attr->DropCachedURI();
}
void
nsHTMLAnchorElement::ResetLinkCacheState()
{

View File

@ -47,6 +47,7 @@
#include "nsPresContext.h"
#include "nsRuleData.h"
#include "nsMappedAttributes.h"
#include "nsNetUtil.h"
// XXX nav4 has type= start= (same as OL/UL)
extern nsAttrValue::EnumTable kListTypeTable[];
@ -102,13 +103,31 @@ public:
nsIAtom* aAttribute,
const nsAString& aValue,
nsAttrValue& aResult);
nsresult SetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
const nsAString& aValue, PRBool aNotify)
{
return SetAttr(aNameSpaceID, aName, nsnull, aValue, aNotify);
}
virtual nsresult SetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
nsIAtom* aPrefix, const nsAString& aValue,
PRBool aNotify);
virtual nsresult UnsetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
PRBool aNotify);
virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
nsIContent* aBindingParent,
PRBool aCompileEventHandlers);
virtual void UnbindFromTree(PRBool aDeep = PR_TRUE,
PRBool aNullParent = PR_TRUE);
virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction() const;
NS_IMETHOD_(PRBool) IsAttributeMapped(const nsIAtom* aAttribute) const;
virtual nsresult Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const;
};
NS_IMPL_NS_NEW_HTML_ELEMENT(Shared)
@ -385,6 +404,156 @@ nsHTMLSharedElement::IsAttributeMapped(const nsIAtom* aAttribute) const
return nsGenericHTMLElement::IsAttributeMapped(aAttribute);
}
nsresult
nsHTMLSharedElement::SetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
nsIAtom* aPrefix, const nsAString& aValue,
PRBool aNotify)
{
nsresult rv = nsGenericHTMLElement::SetAttr(aNameSpaceID, aName, aPrefix,
aValue, aNotify);
// If the href attribute of a <base> tag is changing, we may need to update
// the document's base URI, which will cause all the links on the page to be
// re-resolved given the new base.
if (NS_SUCCEEDED(rv) &&
mNodeInfo->Equals(nsGkAtoms::base, kNameSpaceID_XHTML) &&
aName == nsGkAtoms::href &&
aNameSpaceID == kNameSpaceID_None &&
GetOwnerDoc() == GetCurrentDoc()) {
nsIDocument* doc = GetCurrentDoc();
NS_ENSURE_TRUE(doc, NS_OK);
// We become the first base node with an href if
// * there's no other base node with an href, or
// * we come before the first base node with an href (this would happen
// if we didn't have an href before this call to SetAttr).
// Additionally, we call doc->SetFirstBaseNodeWithHref if we're the first
// base node with an href so the document updates its base URI with our new
// href.
nsIContent* firstBase = doc->GetFirstBaseNodeWithHref();
if (!firstBase || this == firstBase ||
nsContentUtils::PositionIsBefore(this, firstBase)) {
return doc->SetFirstBaseNodeWithHref(this);
}
}
return rv;
}
// Helper function for nsHTMLSharedElement::UnbindFromTree. Finds and returns
// the first <base> tag with an href attribute which is a child of elem, if one
// exists.
static nsIContent*
FindBaseRecursive(nsINode * const elem)
{
// We can't use NS_GetContentList to get the list of <base> elements, because
// that flushes content notifications, and we need this function to work in
// UnbindFromTree. Once we land the HTML5 parser and get rid of content
// notifications, we should fix this up. (bug 515819)
PRUint32 childCount;
nsIContent * const * child = elem->GetChildArray(&childCount);
nsIContent * const * end = child + childCount;
for ( ; child != end; child++) {
nsIContent *childElem = *child;
if (childElem->NodeInfo()->Equals(nsGkAtoms::base, kNameSpaceID_XHTML) &&
childElem->HasAttr(kNameSpaceID_None, nsGkAtoms::href))
return childElem;
nsIContent* base = FindBaseRecursive(childElem);
if (base)
return base;
}
return nsnull;
}
nsresult
nsHTMLSharedElement::UnsetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
PRBool aNotify)
{
nsresult rv = nsGenericHTMLElement::UnsetAttr(aNameSpaceID, aName, aNotify);
// If we're the first <base> with an href and our href attribute is being
// unset, then we're no longer the first <base> with an href, and we need to
// find the new one.
if (NS_SUCCEEDED(rv) &&
mNodeInfo->Equals(nsGkAtoms::base, kNameSpaceID_XHTML) &&
aName == nsGkAtoms::href &&
aNameSpaceID == kNameSpaceID_None &&
GetOwnerDoc() == GetCurrentDoc()) {
nsIDocument* doc = GetCurrentDoc();
NS_ENSURE_TRUE(doc, NS_OK);
// If we're not the first <base> in the document, then unsetting our href
// doesn't affect the document's base URI.
if (this != doc->GetFirstBaseNodeWithHref())
return NS_OK;
// We're the first base, but we don't have an href; find the first base
// which does have an href, and set the document's first base to that.
nsIContent* newBaseNode = FindBaseRecursive(doc);
return doc->SetFirstBaseNodeWithHref(newBaseNode);
}
return rv;
}
nsresult
nsHTMLSharedElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
nsIContent* aBindingParent,
PRBool aCompileEventHandlers)
{
nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent,
aBindingParent,
aCompileEventHandlers);
// The document stores a pointer to its first <base> element, which we may
// need to update here.
if (NS_SUCCEEDED(rv) &&
mNodeInfo->Equals(nsGkAtoms::base, kNameSpaceID_XHTML) &&
HasAttr(kNameSpaceID_None, nsGkAtoms::href) &&
aDocument) {
// If there's no <base> in the document, or if this comes before the one
// that's currently there, set the document's first <base> to this.
nsINode* curBaseNode = aDocument->GetFirstBaseNodeWithHref();
if (!curBaseNode ||
nsContentUtils::PositionIsBefore(this, curBaseNode)) {
aDocument->SetFirstBaseNodeWithHref(this);
}
}
return rv;
}
void
nsHTMLSharedElement::UnbindFromTree(PRBool aDeep, PRBool aNullParent)
{
nsCOMPtr<nsIDocument> doc = GetCurrentDoc();
nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
// If we're removing a <base> from a document, we may need to update the
// document's record of the first base node.
if (doc && mNodeInfo->Equals(nsGkAtoms::base, kNameSpaceID_XHTML)) {
// If we're not the first base node, then we don't need to do anything.
if (this != doc->GetFirstBaseNodeWithHref())
return;
// If we were the first base node, we need to find the new first base.
nsIContent* newBaseNode = FindBaseRecursive(doc);
doc->SetFirstBaseNodeWithHref(newBaseNode);
}
}
nsMapRuleToAttributesFunc
nsHTMLSharedElement::GetAttributeMappingFunction() const
{

View File

@ -71,6 +71,10 @@ _TEST_FILES = test_bug589.html \
bug277890_load.html \
test_bug277890.html \
test_bug287465.html \
test_bug209275.xhtml \
file_bug209275_1.html \
file_bug209275_2.html \
file_bug209275_3.html \
test_bug295561.html \
test_bug300691-1.html \
test_bug300691-2.html \

View File

@ -0,0 +1,28 @@
<!DOCTYPE HTML>
<html>
<head>
<base href="http://example.org" />
</head>
<body onload="load();">
Initial state
<script>
function load() {
// Nuke and rebuild the page.
document.removeChild(document.documentElement);
var html = document.createElement("html");
var body = document.createElement("body");
html.appendChild(body);
var link = document.createElement("a");
link.href = "#";
link.id = "link";
body.appendChild(link);
document.appendChild(html);
// Tell our parent to have a look at us.
parent.gGen.next();
}
</script>
</body>
</html>

View File

@ -0,0 +1,23 @@
<!DOCTYPE HTML>
<html>
<head>
<base href="http://example.com" />
</head>
<body onload="load();">
Page 2 initial state
<script>
function load() {
// Nuke and rebuild the page.
document.removeChild(document.documentElement);
html = document.createElement("html");
html.innerHTML = "<body><a href='/' id='link'>B</a></body>"
document.appendChild(html);
// Tell our parent to have a look at us
parent.gGen.next();
}
</script>
</body>
</html>

View File

@ -0,0 +1,23 @@
<!DOCTYPE HTML>
<html>
<head>
<base href="http://example.org" />
</head>
<body onload="load();">
Initial state
<script>
function load() {
// Nuke and rebuild the page. If document.open() clears the <base> properly,
// our new <base> will take precedence and the test will pass.
document.open();
document.write("<html><body><a id='link' href='/'>A</a>"
"<base href='http://localhost:8888' /></body></html>");
// Tell our parent to have a look at us.
parent.gGen.next();
}
</script>
</body>
</html>

View File

@ -0,0 +1,261 @@
<!DOCTYPE html [
<!ATTLIST foo:base
id ID #IMPLIED
>
]>
<html xmlns:foo="http://foo.com" xmlns="http://www.w3.org/1999/xhtml">
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=209275
-->
<head>
<title>Test for Bug 209275</title>
<script type="text/javascript" src="/MochiKit/packed.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<style>
@namespace svg url("http://www.w3.org/2000/svg");
svg|a { fill:blue; }
svg|a:visited { fill:purple; }
</style>
<!--
base0 should be ignored because it's not in the XHTML namespace
-->
<foo:base id="base0" href="http://www.foo.com" />
<!--
baseEmpty should be ignored because it has no href and never gets one.
-->
<base id="baseEmpty" />
<!--
baseWrongAttrNS should be ignored because its href attribute isn't in the empty
namespace.
-->
<base id="baseWrongAttrNS" foo:href="http://foo.com" />
<base id="base1" />
<base id="base2" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=209275">Mozilla Bug 209275</a>
<p id="display">
</p>
<div id="content">
<a href="/" id="link1">link1</a>
<div style="display:none">
<a href="/" id="link2">link2</a>
</div>
<a href="/" id="link3" style="display:none">link3</a>
<a href="#" id="link4">link4</a>
<a href="" id="colorlink">colorlink</a>
<a href="#" id="link5">link5</a>
<iframe id="iframe"></iframe>
<svg width="5cm" height="3cm" viewBox="0 0 5 3" version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<a xlink:href="http://www.w3.org" id="ellipselink">
<ellipse cx="2.5" cy="1.5" rx="2" ry="1" id="ellipse" />
</a>
</svg>
</div>
<pre id="test">
<script type="text/javascript;version=1.7">
<![CDATA[
/** Test for Bug 209275 **/
SimpleTest.waitForExplicitFinish();
function link123HrefIs(href, testNum) {
is($('link1').href, href, "link1 test " + testNum);
is($('link2').href, href, "link2 test " + testNum);
is($('link3').href, href, "link3 test " + testNum);
}
var gGen;
function getColor(elem) {
return document.defaultView.getComputedStyle(elem, "").color;
}
function getFill(elem) {
return document.defaultView
.getComputedStyle(elem, "")
.getPropertyValue("fill");
}
function setXlinkHref(elem, href) {
elem.setAttributeNS("http://www.w3.org/1999/xlink", "href", href);
}
var iframe = document.getElementById("iframe");
var iframeCw = iframe.contentWindow;
function run() {
var iframe = document.getElementById("iframe");
var iframeCw = iframe.contentWindow;
// First, get the visited/unvisited link/ellipse colors.
var unvisitedColor;
var visitedColor;
var unvisitedFill;
var visitedFill;
var rand = Date.now() + "-" + Math.random();
// Set colorlink and ellipselink's hrefs
$("colorlink").href = rand;
setXlinkHref($("ellipselink"), rand);
unvisitedColor = getColor($("colorlink"));
unvisitedFill = getFill($("ellipse"));
// Set the link's href to our current location so we can get the visited link
// color.
$("colorlink").href = document.location;
setXlinkHref($("ellipselink"), document.location);
visitedColor = getColor($("colorlink"));
visitedFill = getFill($("ellipse"));
isnot(visitedColor, unvisitedColor,
"visited/unvisited link colors are the same?");
isnot(visitedFill, unvisitedFill,
"visited/unvisited ellipse fill colors are the same?");
// Now we can start the tests in earnest.
var loc = location;
// everything from the location up to and including the final forward slash
var path = /(.*\/)[^\/]*/.exec(location)[1];
// Set colorlink's href so we can check that it changes colors after we
// change the base href.
$('colorlink').href = "http://example.com/" + rand;
setXlinkHref($("ellipse"), "http://example.com/" + rand);
// Load http://example.com/${rand} into an iframe so we can test that changing
// the document's base changes the visitedness of our links.
iframe.onload = function() { gGen.next(); };
iframeCw.location = "http://example.com/" + rand;
yield; // wait for onload to fire.
// Make sure things are what as we expect them at the beginning.
link123HrefIs("http://localhost:8888/", 1);
is($('link4').href, loc + "#", "link 4 test 1");
is($('link5').href, loc + "#", "link 5 test 1");
// Remove link5 from the document. We're going to test that its href changes
// properly when we change our base.
var link5 = $('link5');
link5.parentNode.removeChild(link5);
$('base1').href = "http://example.com";
// Were the links' hrefs updated after the base change?
link123HrefIs("http://example.com/", 2);
is($('link4').href, "http://example.com/#", "link 4 test 2");
is(link5.href, "http://example.com/#", "link 5 test 2");
// Were colorlink's color and ellipse's fill updated appropriately?
is(getColor($('colorlink')), visitedColor,
"Wrong link color after base change.");
is(getFill($('ellipse')), visitedFill,
"Wrong ellipse fill after base change.");
$('base1').href = "foo/";
// Should be interpreted relative to current URI (not the current base), so
// base should now be http://localhost:8888/foo/
link123HrefIs("http://localhost:8888/", 3);
is($('link4').href, path + "foo/#", "link 4 test 3");
// Changing base2 shouldn't affect anything, because it's not the first base
// tag.
$('base2').href = "http://example.org/bar/";
link123HrefIs("http://localhost:8888/", 4);
is($('link4').href, path + "foo/#", "link 4 test 4");
// If we unset base1's href attribute, the document's base should come from
// base2, whose href is http://example.org/bar/.
$('base1').removeAttribute("href");
link123HrefIs("http://example.org/", 5);
is($('link4').href, "http://example.org/bar/#", "link 4 test 5");
// If we remove base1, base2 should become the first base tag, and the hrefs
// of all the links should change accordingly.
$('base1').parentNode.removeChild($('base1'));
link123HrefIs("http://example.org/", 6);
is($('link4').href, "http://example.org/bar/#", "link 4 test 6");
// If we add a new base after base2, nothing should change.
var base3 = document.createElement("base");
base3.href = "http://base3.example.org/";
$('base2').parentNode.insertBefore(base3, $('base2').nextSibling);
link123HrefIs("http://example.org/", 7);
is($('link4').href, "http://example.org/bar/#", "link 4 test 7");
// But now if we add a new base before base 2, it should become the primary
// base.
var base4 = document.createElement("base");
base4.href = "http://base4.example.org/";
$('base2').parentNode.insertBefore(base4, $('base2'));
link123HrefIs("http://base4.example.org/", 8);
is($('link4').href, "http://base4.example.org/#", "link 4 test 8");
// Now if we remove all the base tags, the base should become the page's URI
// again.
$('base2').parentNode.removeChild($('base2'));
base3.parentNode.removeChild(base3);
base4.parentNode.removeChild(base4);
link123HrefIs("http://localhost:8888/", 9);
is($('link4').href, loc + "#", "link 4 test 9");
// Setting the href of base0 shouldn't do anything because it's not in the
// XHTML namespace.
$('base0').href = "http://bar.com";
link123HrefIs("http://localhost:8888/", 10);
is($('link4').href, loc + "#", "link 4 test 10");
// We load into an iframe a document with a <base href="...">, then remove
// the document element. Then we add an <html>, <body>, and <a>, and make
// sure that the <a> is resolved relative to the page's location, not its
// original base. We do this twice, rebuilding the document in a different
// way each time.
iframe.onload = null;
iframeCw.location = "file_bug209275_1.html";
yield; // wait for our child to call us back.
is(iframeCw.document.getElementById("link").href,
path + "file_bug209275_1.html#",
"Wrong href after nuking document.");
iframeCw.location = "file_bug209275_2.html";
yield; // wait for callback from child
is(iframeCw.document.getElementById("link").href,
"http://localhost:8888/",
"Wrong href after nuking document second time.");
// Make sure that document.open() makes the document forget about any <base>
// tags it has.
iframeCw.location = "file_bug209275_3.html";
yield; // wait for callback from child
is(iframeCw.document.getElementById("link").href,
"http://localhost:8888/",
"Wrong href after document.open().");
SimpleTest.finish();
yield;
}
window.addEventListener("load", function() {
gGen = run();
gGen.next();
}, false);
]]>
</script>
</pre>
</body>
</html>