mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 209275 - Links' hrefs should be updated when a <base>'s href changes. r=bzbarsky
This commit is contained in:
parent
ce54997bc6
commit
b3e255230a
@ -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.
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
@ -7363,6 +7398,8 @@ nsDocument::NotifyURIVisitednessChanged(nsIURI* aURI)
|
||||
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);
|
||||
@ -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)
|
||||
{
|
||||
|
@ -1039,6 +1039,12 @@ protected:
|
||||
// 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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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 \
|
||||
|
28
content/html/content/test/file_bug209275_1.html
Normal file
28
content/html/content/test/file_bug209275_1.html
Normal 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>
|
23
content/html/content/test/file_bug209275_2.html
Normal file
23
content/html/content/test/file_bug209275_2.html
Normal 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>
|
23
content/html/content/test/file_bug209275_3.html
Normal file
23
content/html/content/test/file_bug209275_3.html
Normal 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>
|
261
content/html/content/test/test_bug209275.xhtml
Normal file
261
content/html/content/test/test_bug209275.xhtml
Normal 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>
|
||||
|
Loading…
Reference in New Issue
Block a user