Bug 481335. Cache the href URI of <html:a> once we resolve it. r+sr=sicking

This commit is contained in:
Boris Zbarsky 2009-03-10 09:51:34 -04:00
parent a7fbf9a901
commit eb4ceff899
7 changed files with 300 additions and 69 deletions

View File

@ -51,10 +51,14 @@
#include "nsTPtrArray.h"
#include "nsContentUtils.h"
#include "nsReadableUtils.h"
#include "nsIURI.h"
#ifdef MOZ_SVG
#include "nsISVGValue.h"
#endif
#define MISC_STR_PTR(_cont) \
reinterpret_cast<void*>((_cont)->mStringBits & NS_ATTRVALUE_POINTERVALUE_MASK)
nsTPtrArray<const nsAttrValue::EnumTable>* nsAttrValue::sEnumTableArray = nsnull;
nsAttrValue::nsAttrValue()
@ -250,6 +254,16 @@ nsAttrValue::SetTo(const nsAttrValue& aOther)
break;
}
#endif
case eFloatValue:
{
cont->mFloatValue = otherCont->mFloatValue;
break;
}
case eLazyURIValue:
{
NS_IF_ADDREF(cont->mURI = otherCont->mURI);
break;
}
default:
{
NS_NOTREACHED("unknown type stored in MiscContainer");
@ -257,8 +271,7 @@ nsAttrValue::SetTo(const nsAttrValue& aOther)
}
}
void* otherPtr =
reinterpret_cast<void*>(otherCont->mStringBits & NS_ATTRVALUE_POINTERVALUE_MASK);
void* otherPtr = MISC_STR_PTR(otherCont);
if (otherPtr) {
if (static_cast<ValueBaseType>(otherCont->mStringBits & NS_ATTRVALUE_BASETYPE_MASK) ==
eStringBase) {
@ -326,8 +339,7 @@ nsAttrValue::ToString(nsAString& aResult) const
MiscContainer* cont = nsnull;
if (BaseType() == eOtherBase) {
cont = GetMiscContainer();
void* ptr =
reinterpret_cast<void*>(cont->mStringBits & NS_ATTRVALUE_POINTERVALUE_MASK);
void* ptr = MISC_STR_PTR(cont);
if (ptr) {
if (static_cast<ValueBaseType>(cont->mStringBits & NS_ATTRVALUE_BASETYPE_MASK) ==
eStringBase) {
@ -434,6 +446,16 @@ nsAttrValue::ToString(nsAString& aResult) const
aResult = str;
break;
}
// No need to do for eLazyURIValue, since that always stores the
// original string.
#ifdef DEBUG
case eLazyURIValue:
{
NS_NOTREACHED("Shouldn't get here");
aResult.Truncate();
break;
}
#endif
default:
{
aResult.Truncate();
@ -577,6 +599,22 @@ nsAttrValue::HashValue() const
return NS_PTR_TO_INT32(cont->mSVGValue);
}
#endif
case eFloatValue:
{
// XXX this is crappy, but oh well
return cont->mFloatValue;
}
case eLazyURIValue:
{
NS_ASSERTION(static_cast<ValueBaseType>(cont->mStringBits &
NS_ATTRVALUE_BASETYPE_MASK) ==
eStringBase,
"Unexpected type");
nsStringBuffer* str = static_cast<nsStringBuffer*>(MISC_STR_PTR(cont));
NS_ASSERTION(str, "How did that happen?");
PRUint32 len = str->StorageSize()/sizeof(PRUnichar) - 1;
return nsCRT::BufferHashCode(static_cast<PRUnichar*>(str->Data()), len);
}
default:
{
NS_NOTREACHED("unknown type stored in MiscContainer");
@ -672,6 +710,15 @@ nsAttrValue::Equals(const nsAttrValue& aOther) const
return thisCont->mSVGValue == otherCont->mSVGValue;
}
#endif
case eFloatValue:
{
return thisCont->mFloatValue == otherCont->mFloatValue;
}
case eLazyURIValue:
{
needsStringComparison = PR_TRUE;
break;
}
default:
{
NS_NOTREACHED("unknown type stored in MiscContainer");
@ -1117,6 +1164,58 @@ PRBool nsAttrValue::ParseFloatValue(const nsAString& aString)
return PR_FALSE;
}
PRBool nsAttrValue::ParseLazyURIValue(const nsAString& aString)
{
ResetIfSet();
if (EnsureEmptyMiscContainer()) {
MiscContainer* cont = GetMiscContainer();
cont->mURI = nsnull;
cont->mType = eLazyURIValue;
// Don't use SetMiscAtomOrString because atomizing URIs is not
// likely to do us much good.
nsStringBuffer* buf = GetStringBuffer(aString);
if (!buf) {
return PR_FALSE;
}
cont->mStringBits = reinterpret_cast<PtrBits>(buf) | eStringBase;
return PR_TRUE;
}
return PR_FALSE;
}
void
nsAttrValue::CacheURIValue(nsIURI* aURI)
{
NS_PRECONDITION(Type() == eLazyURIValue, "wrong type");
NS_PRECONDITION(!GetMiscContainer()->mURI, "Why are we being called?");
NS_IF_ADDREF(GetMiscContainer()->mURI = aURI);
}
void
nsAttrValue::DropCachedURI()
{
NS_PRECONDITION(Type() == eLazyURIValue, "wrong type");
NS_IF_RELEASE(GetMiscContainer()->mURI);
}
const nsCheapString
nsAttrValue::GetURIStringValue() const
{
NS_PRECONDITION(Type() == eLazyURIValue, "wrong type");
NS_PRECONDITION(static_cast<ValueBaseType>(GetMiscContainer()->mStringBits &
NS_ATTRVALUE_BASETYPE_MASK) ==
eStringBase,
"Unexpected type");
NS_PRECONDITION(MISC_STR_PTR(GetMiscContainer()),
"Should have a string buffer here!");
return nsCheapString(static_cast<nsStringBuffer*>
(MISC_STR_PTR(GetMiscContainer())));
}
void
nsAttrValue::SetMiscAtomOrString(const nsAString* aValue)
{
@ -1145,7 +1244,7 @@ void
nsAttrValue::ResetMiscAtomOrString()
{
MiscContainer* cont = GetMiscContainer();
void* ptr = reinterpret_cast<void*>(cont->mStringBits & NS_ATTRVALUE_POINTERVALUE_MASK);
void* ptr = MISC_STR_PTR(cont);
if (ptr) {
if (static_cast<ValueBaseType>(cont->mStringBits & NS_ATTRVALUE_BASETYPE_MASK) ==
eStringBase) {
@ -1182,6 +1281,11 @@ nsAttrValue::EnsureEmptyMiscContainer()
break;
}
#endif
case eLazyURIValue:
{
NS_IF_RELEASE(cont->mURI);
break;
}
default:
{
break;

View File

@ -54,6 +54,7 @@ typedef unsigned long PtrBits;
class nsAString;
class nsIAtom;
class nsICSSStyleRule;
class nsIURI;
class nsISVGValue;
class nsIDocument;
template<class E> class nsCOMArray;
@ -120,6 +121,7 @@ public:
,eSVGValue = 0x12
#endif
,eFloatValue = 0x13
,eLazyURIValue = 0x14
};
ValueType Type() const;
@ -153,6 +155,10 @@ public:
inline nsISVGValue* GetSVGValue() const;
#endif
inline float GetFloatValue() const;
inline nsIURI* GetURIValue() const;
const nsCheapString GetURIStringValue() const;
void CacheURIValue(nsIURI* aURI);
void DropCachedURI();
// Methods to get access to atoms we may have
// Returns the number of atoms we have; 0 if we have none. It's OK
@ -257,6 +263,12 @@ public:
*/
PRBool ParseFloatValue(const nsAString& aString);
/**
* Parse a lazy URI. This just sets up the storage for the URI; it
* doesn't actually allocate it.
*/
PRBool ParseLazyURIValue(const nsAString& aString);
private:
// These have to be the same as in ValueType
enum ValueBaseType {
@ -285,6 +297,7 @@ private:
nsISVGValue* mSVGValue;
#endif
float mFloatValue;
nsIURI* mURI;
};
};
@ -390,6 +403,13 @@ nsAttrValue::GetFloatValue() const
return GetMiscContainer()->mFloatValue;
}
inline nsIURI*
nsAttrValue::GetURIValue() const
{
NS_PRECONDITION(Type() == eLazyURIValue, "wrong type");
return GetMiscContainer()->mURI;
}
inline nsAttrValue::ValueBaseType
nsAttrValue::BaseType() const
{

View File

@ -1060,24 +1060,7 @@ nsGenericHTMLElement::GetHrefURIForAnchors(nsIURI** aURI) const
// Get href= attribute (relative URI).
// We use the nsAttrValue's copy of the URI string to avoid copying.
const nsAttrValue* attr = mAttrsAndChildren.GetAttr(nsGkAtoms::href);
if (attr) {
// Get base URI.
nsCOMPtr<nsIURI> baseURI = GetBaseURI();
// Get absolute URI.
nsresult rv = nsContentUtils::NewURIWithDocumentCharset(aURI,
attr->GetStringValue(),
GetOwnerDoc(),
baseURI);
if (NS_FAILED(rv)) {
*aURI = nsnull;
}
}
else {
// Absolute URI is null to say we have no HREF.
*aURI = nsnull;
}
GetURIAttr(nsGkAtoms::href, nsnull, aURI);
return NS_OK;
}
@ -2156,51 +2139,72 @@ nsGenericHTMLElement::SetFloatAttr(nsIAtom* aAttr, float aValue)
nsresult
nsGenericHTMLElement::GetURIAttr(nsIAtom* aAttr, nsIAtom* aBaseAttr, nsAString& aResult)
{
nsAutoString attrValue;
if (!GetAttr(kNameSpaceID_None, aAttr, attrValue)) {
nsCOMPtr<nsIURI> uri;
PRBool hadAttr = GetURIAttr(aAttr, aBaseAttr, getter_AddRefs(uri));
if (!hadAttr) {
aResult.Truncate();
return NS_OK;
}
if (!uri) {
// Just return the attr value
GetAttr(kNameSpaceID_None, aAttr, aResult);
return NS_OK;
}
nsCAutoString spec;
uri->GetSpec(spec);
CopyUTF8toUTF16(spec, aResult);
return NS_OK;
}
PRBool
nsGenericHTMLElement::GetURIAttr(nsIAtom* aAttr, nsIAtom* aBaseAttr,
nsIURI** aURI) const
{
*aURI = nsnull;
const nsAttrValue* attr = mAttrsAndChildren.GetAttr(aAttr);
if (!attr) {
return PR_FALSE;
}
PRBool isURIAttr = (attr->Type() == nsAttrValue::eLazyURIValue);
if (isURIAttr && (*aURI = attr->GetURIValue())) {
NS_ADDREF(*aURI);
return PR_TRUE;
}
nsCOMPtr<nsIURI> baseURI = GetBaseURI();
nsresult rv;
if (aBaseAttr) {
nsAutoString baseAttrValue;
if (GetAttr(kNameSpaceID_None, aBaseAttr, baseAttrValue)) {
nsCOMPtr<nsIURI> baseAttrURI;
rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(baseAttrURI),
baseAttrValue, GetOwnerDoc(),
baseURI);
nsresult rv =
nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(baseAttrURI),
baseAttrValue, GetOwnerDoc(),
baseURI);
if (NS_FAILED(rv)) {
// Just use the attr value as the result...
aResult = attrValue;
return NS_OK;
return PR_TRUE;
}
baseURI.swap(baseAttrURI);
}
}
nsCOMPtr<nsIURI> attrURI;
rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(attrURI),
attrValue, GetOwnerDoc(),
baseURI);
if (NS_FAILED(rv)) {
// Just use the attr value as the result...
aResult = attrValue;
// Don't care about return value. If it fails, we still want to
// return PR_TRUE, and *aURI will be null.
nsContentUtils::NewURIWithDocumentCharset(aURI,
isURIAttr ?
attr->GetURIStringValue() :
attr->GetStringValue(),
GetOwnerDoc(), baseURI);
return NS_OK;
if (isURIAttr) {
const_cast<nsAttrValue*>(attr)->CacheURIValue(*aURI);
}
NS_ASSERTION(attrURI,
"nsContentUtils::NewURIWithDocumentCharset return value lied");
nsCAutoString spec;
attrURI->GetSpec(spec);
CopyUTF8toUTF16(spec, aResult);
return NS_OK;
return PR_TRUE;
}
nsresult

View File

@ -687,6 +687,15 @@ protected:
*/
NS_HIDDEN_(nsresult) GetURIAttr(nsIAtom* aAttr, nsIAtom* aBaseAttr, nsAString& aResult);
/**
* Helper for GetURIAttr and GetHrefURIForAnchors which returns an
* nsIURI in the out param..
*
* @return PR_TRUE if we had the attr, PR_FALSE otherwise.
*/
NS_HIDDEN_(PRBool) GetURIAttr(nsIAtom* aAttr, nsIAtom* aBaseAttr,
nsIURI** aURI) const;
/**
* This method works like GetURIAttr, except that it supports multiple
* URIs separated by whitespace (one or more U+0020 SPACE characters).

View File

@ -131,10 +131,16 @@ public:
PRBool aNotify);
virtual nsresult UnsetAttr(PRInt32 aNameSpaceID, nsIAtom* aAttribute,
PRBool aNotify);
virtual PRBool ParseAttribute(PRInt32 aNamespaceID,
nsIAtom* aAttribute,
const nsAString& aValue,
nsAttrValue& aResult);
virtual nsresult Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const;
protected:
void ResetLinkCacheState();
// The cached visited state
nsLinkState mLinkState;
};
@ -223,13 +229,9 @@ nsHTMLAnchorElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
void
nsHTMLAnchorElement::UnbindFromTree(PRBool aDeep, PRBool aNullParent)
{
nsIDocument* doc = GetCurrentDoc();
if (doc) {
if (IsInDoc()) {
RegUnRegAccessKey(PR_FALSE);
doc->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;
ResetLinkCacheState();
}
nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
@ -464,14 +466,7 @@ 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);
ResetLinkCacheState();
}
}
@ -495,11 +490,7 @@ nsHTMLAnchorElement::UnsetAttr(PRInt32 aNameSpaceID, nsIAtom* aAttribute,
PRBool aNotify)
{
if (aAttribute == nsGkAtoms::href && kNameSpaceID_None == aNameSpaceID) {
nsIDocument* doc = GetCurrentDoc();
if (doc) {
doc->ForgetLink(this);
}
SetLinkState(eLinkState_Unknown);
ResetLinkCacheState();
}
if (aAttribute == nsGkAtoms::accesskey &&
@ -509,3 +500,34 @@ nsHTMLAnchorElement::UnsetAttr(PRInt32 aNameSpaceID, nsIAtom* aAttribute,
return nsGenericHTMLElement::UnsetAttr(aNameSpaceID, aAttribute, aNotify);
}
PRBool
nsHTMLAnchorElement::ParseAttribute(PRInt32 aNamespaceID,
nsIAtom* aAttribute,
const nsAString& aValue,
nsAttrValue& aResult)
{
if (aNamespaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::href) {
return aResult.ParseLazyURIValue(aValue);
}
return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
aResult);
}
void
nsHTMLAnchorElement::ResetLinkCacheState()
{
nsIDocument* doc = GetCurrentDoc();
if (doc) {
doc->ForgetLink(this);
}
mLinkState = eLinkState_Unknown;
// Clear our cached URI _after_ we ForgetLink(), since ForgetLink()
// wants that URI.
const nsAttrValue* attr = mAttrsAndChildren.GetAttr(nsGkAtoms::href);
if (attr && attr->Type() == nsAttrValue::eLazyURIValue) {
const_cast<nsAttrValue*>(attr)->DropCachedURI();
}
}

View File

@ -130,6 +130,7 @@ _TEST_FILES = test_bug589.html \
test_bug347174_xslp.html \
347174transformable.xml \
347174transform.xsl \
test_bug481335.xhtml \
$(NULL)
libs:: $(_TEST_FILES)

View File

@ -0,0 +1,71 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=481335
-->
<head>
<title>Test for Bug 481335</title>
<script type="application/javascript" src="/MochiKit/packed.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.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=481335">Mozilla Bug 481335</a>
<p id="display">
<a id="t">A link</a>
<iframe id="i"></iframe>
</p>
<p id="newparent" xml:base="http://www.example.com/"></p>
<div id="content" style="display: none"></div>
<pre id="test">
<script type="application/javascript">
<![CDATA[
/** Test for Bug 481335 **/
SimpleTest.waitForExplicitFinish();
var rand = Date.now() + "-" + Math.random();
is($("t").href, "",
"Unexpected href before set");
is($("t").href, "",
"Unexpected cached href before set");
$("t").setAttribute("href", rand);
is($("t").href,
window.location.href.replace(/test_bug481335.xhtml/, rand),
"Unexpected href after set");
is($("t").href,
window.location.href.replace(/test_bug481335.xhtml/, rand),
"Unexpected cached href after set");
var unvisitedColor = document.defaultView.getComputedStyle($("t"), "").color;
var visitedColor;
function afterSecondLoad() {
todo(document.defaultView.getComputedStyle($("t"), "").color == visitedColor,
"Should be visited now");
SimpleTest.finish();
}
function afterFirstLoad() {
visitedColor = document.defaultView.getComputedStyle($("t"), "").color;
todo(visitedColor != unvisitedColor, "Why are the colors the same?");
$("newparent").appendChild($("t"));
is($("t").href, "http://www.example.com/" + rand,
"Unexpected href after move");
is($("t").href, "http://www.example.com/" + rand,
"Unexpected cached href after move");
is(document.defaultView.getComputedStyle($("t"), "").color, unvisitedColor,
"Should be unvisited now");
$("i").onload = afterSecondLoad;
$("i").src = $("t").href;
}
addLoadEvent(function() {
$("i").onload = afterFirstLoad;
$("i").src = $("t").href;
});
]]>
</script>
</pre>
</body>
</html>