Bug 629200 part 9 - Update attribute setting for SVGAnimatedLengthList; r=jwatt

This commit is contained in:
Brian Birtles 2012-02-16 08:40:45 +09:00
parent 9fca7d4aa4
commit 82e52ab216
9 changed files with 186 additions and 26 deletions

View File

@ -52,6 +52,7 @@
#include "nsReadableUtils.h"
#include "prprf.h"
#include "nsSVGLength2.h"
#include "SVGLengthList.h"
namespace css = mozilla::css;
@ -270,6 +271,11 @@ nsAttrValue::SetTo(const nsAttrValue& aOther)
cont->mSVGLength = otherCont->mSVGLength;
break;
}
case eSVGLengthList:
{
cont->mSVGLengthList = otherCont->mSVGLengthList;
break;
}
default:
{
NS_NOTREACHED("unknown type stored in MiscContainer");
@ -364,6 +370,18 @@ nsAttrValue::SetTo(const nsSVGLength2& aValue, const nsAString* aSerialized)
}
}
void
nsAttrValue::SetTo(const mozilla::SVGLengthList& aValue,
const nsAString* aSerialized)
{
if (EnsureEmptyMiscContainer()) {
MiscContainer* cont = GetMiscContainer();
cont->mSVGLengthList = &aValue;
cont->mType = eSVGLengthList;
SetMiscAtomOrString(aSerialized);
}
}
void
nsAttrValue::SwapValueWith(nsAttrValue& aOther)
{
@ -466,6 +484,11 @@ nsAttrValue::ToString(nsAString& aResult) const
GetMiscContainer()->mSVGLength->GetBaseValueString(aResult);
break;
}
case eSVGLengthList:
{
GetMiscContainer()->mSVGLengthList->GetValueAsString(aResult);
break;
}
default:
{
aResult.Truncate();
@ -654,6 +677,10 @@ nsAttrValue::HashValue() const
{
return NS_PTR_TO_INT32(cont->mSVGLength);
}
case eSVGLengthList:
{
return NS_PTR_TO_INT32(cont->mSVGLengthList);
}
default:
{
NS_NOTREACHED("unknown type stored in MiscContainer");
@ -750,6 +777,10 @@ nsAttrValue::Equals(const nsAttrValue& aOther) const
{
return thisCont->mSVGLength == otherCont->mSVGLength;
}
case eSVGLengthList:
{
return thisCont->mSVGLengthList == otherCont->mSVGLengthList;
}
default:
{
NS_NOTREACHED("unknown type stored in MiscContainer");

View File

@ -64,6 +64,7 @@ namespace mozilla {
namespace css {
class StyleRule;
}
class SVGLengthList;
}
#define NS_ATTRVALUE_MAX_STRINGLENGTH_ATOM 12
@ -129,6 +130,7 @@ public:
,eDoubleValue = 0x12
,eIntMarginValue = 0x13
,eSVGLength = 0x14
,eSVGLengthList = 0x15
};
ValueType Type() const;
@ -142,6 +144,8 @@ public:
void SetTo(mozilla::css::StyleRule* aValue, const nsAString* aSerialized);
void SetTo(const nsIntMargin& aValue);
void SetTo(const nsSVGLength2& aValue, const nsAString* aSerialized);
void SetTo(const mozilla::SVGLengthList& aValue,
const nsAString* aSerialized);
/**
* Sets this object with the string or atom representation of aValue.
@ -374,6 +378,7 @@ private:
double mDoubleValue;
nsIntMargin* mIntMargin;
const nsSVGLength2* mSVGLength;
const mozilla::SVGLengthList* mSVGLengthList;
};
};

View File

@ -156,8 +156,14 @@ DOMSVGLength::SetValue(float aUserUnitValue)
// unit as it is.
if (HasOwner()) {
if (InternalItem().SetFromUserUnitValue(aUserUnitValue, Element(), Axis())) {
Element()->DidChangeLengthList(mAttrEnum, true);
if (InternalItem().GetValueInUserUnits(Element(), Axis()) ==
aUserUnitValue) {
return NS_OK;
}
nsAttrValue emptyOrOldValue = Element()->WillChangeLengthList(mAttrEnum);
if (InternalItem().SetFromUserUnitValue(aUserUnitValue, Element(), Axis()))
{
Element()->DidChangeLengthList(mAttrEnum, emptyOrOldValue);
if (mList->mAList->IsAnimating()) {
Element()->AnimationNeedsResample();
}
@ -195,8 +201,12 @@ DOMSVGLength::SetValueInSpecifiedUnits(float aValue)
}
if (HasOwner()) {
if (InternalItem().GetValueInCurrentUnits() == aValue) {
return NS_OK;
}
nsAttrValue emptyOrOldValue = Element()->WillChangeLengthList(mAttrEnum);
InternalItem().SetValueInCurrentUnits(aValue);
Element()->DidChangeLengthList(mAttrEnum, true);
Element()->DidChangeLengthList(mAttrEnum, emptyOrOldValue);
if (mList->mAList->IsAnimating()) {
Element()->AnimationNeedsResample();
}
@ -218,8 +228,12 @@ DOMSVGLength::SetValueAsString(const nsAString& aValue)
return NS_ERROR_DOM_SYNTAX_ERR;
}
if (HasOwner()) {
if (InternalItem() == value) {
return NS_OK;
}
nsAttrValue emptyOrOldValue = Element()->WillChangeLengthList(mAttrEnum);
InternalItem() = value;
Element()->DidChangeLengthList(mAttrEnum, true);
Element()->DidChangeLengthList(mAttrEnum, emptyOrOldValue);
if (mList->mAList->IsAnimating()) {
Element()->AnimationNeedsResample();
}
@ -259,8 +273,13 @@ DOMSVGLength::NewValueSpecifiedUnits(PRUint16 aUnit, float aValue)
return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
}
if (HasOwner()) {
if (InternalItem().GetUnit() == aUnit &&
InternalItem().GetValueInCurrentUnits() == aValue) {
return NS_OK;
}
nsAttrValue emptyOrOldValue = Element()->WillChangeLengthList(mAttrEnum);
InternalItem().SetValueAndUnit(aValue, PRUint8(aUnit));
Element()->DidChangeLengthList(mAttrEnum, true);
Element()->DidChangeLengthList(mAttrEnum, emptyOrOldValue);
if (mList->mAList->IsAnimating()) {
Element()->AnimationNeedsResample();
}
@ -282,7 +301,12 @@ DOMSVGLength::ConvertToSpecifiedUnits(PRUint16 aUnit)
return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
}
if (HasOwner()) {
if (InternalItem().GetUnit() == aUnit) {
return NS_OK;
}
nsAttrValue emptyOrOldValue = Element()->WillChangeLengthList(mAttrEnum);
if (InternalItem().ConvertToUnit(PRUint8(aUnit), Element(), Axis())) {
Element()->DidChangeLengthList(mAttrEnum, emptyOrOldValue);
return NS_OK;
}
} else {

View File

@ -171,6 +171,7 @@ DOMSVGLengthList::Clear()
}
if (Length() > 0) {
nsAttrValue emptyOrOldValue = Element()->WillChangeLengthList(AttrEnum());
// Notify any existing DOM items of removal *before* truncating the lists
// so that they can find their SVGLength internal counterparts and copy
// their values. This also notifies the animVal list:
@ -178,7 +179,7 @@ DOMSVGLengthList::Clear()
mItems.Clear();
InternalList().Clear();
Element()->DidChangeLengthList(AttrEnum(), true);
Element()->DidChangeLengthList(AttrEnum(), emptyOrOldValue);
if (mAList->IsAnimating()) {
Element()->AnimationNeedsResample();
}
@ -256,6 +257,7 @@ DOMSVGLengthList::InsertItemBefore(nsIDOMSVGLength *newItem,
return NS_ERROR_OUT_OF_MEMORY;
}
nsAttrValue emptyOrOldValue = Element()->WillChangeLengthList(AttrEnum());
// Now that we know we're inserting, keep animVal list in sync as necessary.
MaybeInsertNullInAnimValListAt(index);
@ -269,7 +271,7 @@ DOMSVGLengthList::InsertItemBefore(nsIDOMSVGLength *newItem,
UpdateListIndicesFromIndex(mItems, index + 1);
Element()->DidChangeLengthList(AttrEnum(), true);
Element()->DidChangeLengthList(AttrEnum(), emptyOrOldValue);
if (mAList->IsAnimating()) {
Element()->AnimationNeedsResample();
}
@ -298,6 +300,7 @@ DOMSVGLengthList::ReplaceItem(nsIDOMSVGLength *newItem,
domItem = domItem->Copy(); // must do this before changing anything!
}
nsAttrValue emptyOrOldValue = Element()->WillChangeLengthList(AttrEnum());
if (mItems[index]) {
// Notify any existing DOM item of removal *before* modifying the lists so
// that the DOM item can copy the *old* value at its index:
@ -311,7 +314,7 @@ DOMSVGLengthList::ReplaceItem(nsIDOMSVGLength *newItem,
// would end up reading bad data from InternalList()!
domItem->InsertingIntoList(this, AttrEnum(), index, IsAnimValList());
Element()->DidChangeLengthList(AttrEnum(), true);
Element()->DidChangeLengthList(AttrEnum(), emptyOrOldValue);
if (mAList->IsAnimating()) {
Element()->AnimationNeedsResample();
}
@ -332,6 +335,7 @@ DOMSVGLengthList::RemoveItem(PRUint32 index,
return NS_ERROR_DOM_INDEX_SIZE_ERR;
}
nsAttrValue emptyOrOldValue = Element()->WillChangeLengthList(AttrEnum());
// Now that we know we're removing, keep animVal list in sync as necessary.
// Do this *before* touching InternalList() so the removed item can get its
// internal value.
@ -350,7 +354,7 @@ DOMSVGLengthList::RemoveItem(PRUint32 index,
UpdateListIndicesFromIndex(mItems, index);
Element()->DidChangeLengthList(AttrEnum(), true);
Element()->DidChangeLengthList(AttrEnum(), emptyOrOldValue);
if (mAList->IsAnimating()) {
Element()->AnimationNeedsResample();
}

View File

@ -171,6 +171,8 @@ EXPORTS = \
nsSVGFeatures.h \
nsSVGLength2.h \
nsSVGRect.h \
SVGLength.h \
SVGLengthList.h \
$(NULL)
include $(topsrcdir)/config/rules.mk

View File

@ -330,6 +330,10 @@ nsSVGElement::ParseAttribute(PRInt32 aNamespaceID,
rv = lengthListInfo.mLengthLists[i].SetBaseValueString(aValue);
if (NS_FAILED(rv)) {
lengthListInfo.Reset(i);
} else {
aResult.SetTo(lengthListInfo.mLengthLists[i].GetBaseValue(),
&aValue);
didSetResult = true;
}
foundMatch = true;
break;
@ -616,8 +620,8 @@ nsSVGElement::UnsetAttrInternal(PRInt32 aNamespaceID, nsIAtom* aName,
for (PRUint32 i = 0; i < lengthListInfo.mLengthListCount; i++) {
if (aName == *lengthListInfo.mLengthListInfo[i].mName) {
MaybeSerializeAttrBeforeRemoval(aName, aNotify);
lengthListInfo.Reset(i);
DidChangeLengthList(i, false);
return;
}
}
@ -1577,24 +1581,27 @@ nsSVGElement::LengthListAttributesInfo::Reset(PRUint8 aAttrEnum)
// caller notifies
}
void
nsSVGElement::DidChangeLengthList(PRUint8 aAttrEnum, bool aDoSetAttr)
nsAttrValue
nsSVGElement::WillChangeLengthList(PRUint8 aAttrEnum)
{
if (!aDoSetAttr)
return;
return WillChangeValue(*GetLengthListInfo().mLengthListInfo[aAttrEnum].mName);
}
void
nsSVGElement::DidChangeLengthList(PRUint8 aAttrEnum,
const nsAttrValue& aEmptyOrOldValue)
{
LengthListAttributesInfo info = GetLengthListInfo();
NS_ASSERTION(info.mLengthListCount > 0,
"DidChangeLengthList on element with no length list attribs");
NS_ASSERTION(aAttrEnum < info.mLengthListCount, "aAttrEnum out of range");
nsAutoString serializedValue;
info.mLengthLists[aAttrEnum].GetBaseValue().GetValueAsString(serializedValue);
nsAttrValue newValue;
newValue.SetTo(info.mLengthLists[aAttrEnum].GetBaseValue(), nsnull);
nsAttrValue attrValue(serializedValue);
SetParsedAttr(kNameSpaceID_None, *info.mLengthListInfo[aAttrEnum].mName,
nsnull, attrValue, true);
DidChangeValue(*info.mLengthListInfo[aAttrEnum].mName, aEmptyOrOldValue,
newValue);
}
void

View File

@ -171,6 +171,7 @@ public:
void SetLength(nsIAtom* aName, const nsSVGLength2 &aLength);
nsAttrValue WillChangeLength(PRUint8 aAttrEnum);
nsAttrValue WillChangeLengthList(PRUint8 aAttrEnum);
void DidChangeLength(PRUint8 aAttrEnum, const nsAttrValue& aEmptyOrOldValue);
virtual void DidChangeNumber(PRUint8 aAttrEnum, bool aDoSetAttr);
@ -183,7 +184,8 @@ public:
virtual void DidChangeViewBox(bool aDoSetAttr);
virtual void DidChangePreserveAspectRatio(bool aDoSetAttr);
virtual void DidChangeNumberList(PRUint8 aAttrEnum, bool aDoSetAttr);
virtual void DidChangeLengthList(PRUint8 aAttrEnum, bool aDoSetAttr);
void DidChangeLengthList(PRUint8 aAttrEnum,
const nsAttrValue& aEmptyOrOldValue);
virtual void DidChangePointList(bool aDoSetAttr);
virtual void DidChangePathSegList(bool aDoSetAttr);
virtual void DidChangeTransformList(bool aDoSetAttr);

View File

@ -5,6 +5,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=515116
<head>
<title>Tests specific to SVGLengthList</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="MutationEventChecker.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
@ -36,11 +37,31 @@ function run_tests()
is(lengths.numberOfItems, 0, 'Checking numberOfItems');
/*
is(lengths.numberOfItems, 2, 'Checking numberOfItems');
is(lengths.getItem(1).valueInSpecifiedUnits == 20, 'Checking the value of the second length');
is(lengths.getItem(1).unitType == SVGLength.SVG_LENGTHTYPE_CM, 'Checking the unit of the second length');
*/
// Test mutation events
// --- Initialization
eventChecker = new MutationEventChecker;
eventChecker.watchAttr(text, "x");
eventChecker.expect("modify");
text.textContent = "abc";
text.setAttribute("x", "10 20 30");
is(lengths.numberOfItems, 3, 'Checking numberOfItems');
// -- Actual changes
eventChecker.expect("modify modify modify modify modify");
lengths[0].value = 8;
lengths[0].valueInSpecifiedUnits = 9;
lengths[0].valueAsString = "10";
lengths[0].convertToSpecifiedUnits(SVGLength.SVG_LENGTHTYPE_CM);
lengths[0].newValueSpecifiedUnits(SVGLength.SVG_LENGTHTYPE_MM, 11);
// -- Redundant changes
eventChecker.expect("modify");
lengths[0].valueAsString = "10";
eventChecker.expect("");
lengths[0].value = 10;
lengths[0].valueInSpecifiedUnits = 10;
lengths[0].valueAsString = "10";
lengths[0].convertToSpecifiedUnits(SVGLength.SVG_LENGTHTYPE_NUMBER);
lengths[0].newValueSpecifiedUnits(SVGLength.SVG_LENGTHTYPE_NUMBER, 10);
eventChecker.finish();
SimpleTest.finish();
}

View File

@ -6,6 +6,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=515116
<title>Generic tests for SVG animated length lists</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="matrixUtils.js"></script>
<script type="text/javascript" src="MutationEventChecker.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
@ -452,6 +453,7 @@ function get_array_of_list_items(list)
function run_baseVal_API_tests()
{
var res, threw, items;
var eventChecker = new MutationEventChecker;
for each (var t in tests) {
@ -462,6 +464,8 @@ function run_baseVal_API_tests()
is(t.baseVal.numberOfItems, 4,
'The '+t.list_type+' object should contain four list items.');
eventChecker.watchAttr(t.element, t.attr_name);
eventChecker.expect("modify");
res = t.baseVal.clear();
is(t.baseVal.numberOfItems, 0,
@ -469,13 +473,48 @@ function run_baseVal_API_tests()
' object.');
is(res, undefined,
'The method '+t.list_type+'.clear() should not return a value.');
ok(t.element.hasAttribute(t.attr_name),
'The method '+t.list_type+'.clear() should not remove the attribute.');
ok(t.element.getAttribute(t.attr_name) === "",
'Cleared '+t.attr_name+' ('+t.list_type+') but did not get an '+
'empty string back.');
eventChecker.expect("");
t.baseVal.clear();
eventChecker.ignoreEvents();
// Test empty strings
t.element.setAttribute(t.attr_name, "");
ok(t.element.getAttribute(t.attr_name) === "",
'Set an empty attribute value for '+t.attr_name+' ('+t.list_type+
') but did not get an empty string back.');
// Test removed attributes
t.element.removeAttribute(t.attr_name);
ok(t.element.getAttribute(t.attr_name) === null,
'Removed attribute value for '+t.attr_name+' ('+t.list_type+
') but did not get null back.');
ok(!t.element.hasAttribute(t.attr_name),
'Removed attribute value for '+t.attr_name+' ('+t.list_type+
') but hasAttribute still returns true.');
// Test .initialize():
t.element.setAttribute(t.attr_name, t.attr_val_4);
var item = t.item_constructor();
// Our current implementation of 'initialize' for most list types performs
// a 'clear' followed by an 'insertItemBefore'. This results in two
// modification events being dispatched. SVGStringList however avoids the
// additional clear.
var expectedModEvents =
t.item_type == "DOMString" ? "modify" : "modify modify";
eventChecker.expect(expectedModEvents);
var res = t.baseVal.initialize(item);
eventChecker.ignoreEvents();
is(t.baseVal.numberOfItems, 1,
'The '+t.list_type+' object should contain one list item.');
@ -515,6 +554,7 @@ function run_baseVal_API_tests()
'is passed in, even if that object is the only item in that list.');
// [SVGWG issue] not what the spec currently says
eventChecker.expect("");
threw = false;
try {
t.baseVal.initialize({});
@ -524,6 +564,7 @@ function run_baseVal_API_tests()
ok(threw,
'The method '+t.list_type+'.initialize() should throw if passed an '+
'object of the wrong type.');
eventChecker.ignoreEvents();
}
// Test .insertItemBefore():
@ -532,7 +573,9 @@ function run_baseVal_API_tests()
old_items = get_array_of_list_items(t.baseVal);
item = t.item_constructor();
eventChecker.expect("modify");
res = t.baseVal.insertItemBefore(item, 2);
eventChecker.ignoreEvents();
is(t.baseVal.numberOfItems, 5,
'The '+t.list_type+' object should contain five list items.');
@ -587,6 +630,7 @@ function run_baseVal_API_tests()
'the list at the index specified.');
// [SVGWG issue] not what the spec currently says
eventChecker.expect("");
threw = false;
try {
t.baseVal.insertItemBefore({}, 2);
@ -596,6 +640,7 @@ function run_baseVal_API_tests()
ok(threw,
'The method '+t.list_type+'.insertItemBefore() should throw if passed '+
'an object of the wrong type.');
eventChecker.ignoreEvents();
}
// Test .replaceItem():
@ -604,7 +649,9 @@ function run_baseVal_API_tests()
old_items = get_array_of_list_items(t.baseVal);
item = t.item_constructor();
eventChecker.expect("modify");
res = t.baseVal.replaceItem(item, 2);
eventChecker.ignoreEvents();
is(t.baseVal.numberOfItems, 4,
'The '+t.list_type+' object should contain four list items.');
@ -627,6 +674,7 @@ function run_baseVal_API_tests()
item = t.item_constructor();
eventChecker.expect("");
threw = false;
try {
t.baseVal.replaceItem(item, 100);
@ -636,6 +684,7 @@ function run_baseVal_API_tests()
ok(threw,
'The method '+t.list_type+'.replaceItem() should throw if passed '+
'an index that is out of bounds.');
eventChecker.ignoreEvents();
old_items = get_array_of_list_items(t.baseVal);
item = t.baseVal.getItem(3);
@ -683,7 +732,9 @@ function run_baseVal_API_tests()
old_items = get_array_of_list_items(t.baseVal);
item = t.baseVal.getItem(2);
eventChecker.expect("modify");
res = t.baseVal.removeItem(2);
eventChecker.ignoreEvents();
is(t.baseVal.numberOfItems, 3,
'The '+t.list_type+' object should contain three list items.');
@ -701,6 +752,7 @@ function run_baseVal_API_tests()
'the item at index 2 was removed using the '+t.list_type+
'.replaceItem() method.');
eventChecker.expect("");
threw = false;
try {
t.baseVal.removeItem(100);
@ -710,6 +762,7 @@ function run_baseVal_API_tests()
ok(threw,
'The method '+t.list_type+'.removeItem() should throw if passed '+
'an index that is out of bounds.');
eventChecker.ignoreEvents();
// Test .appendItem():
@ -717,7 +770,9 @@ function run_baseVal_API_tests()
old_items = get_array_of_list_items(t.baseVal);
item = t.item_constructor();
eventChecker.expect("modify");
res = t.baseVal.appendItem(item);
eventChecker.ignoreEvents();
is(t.baseVal.numberOfItems, 5,
'The '+t.list_type+' object should contain five list items.');
@ -763,6 +818,7 @@ function run_baseVal_API_tests()
'that list.');
// [SVGWG issue] not what the spec currently says
eventChecker.expect("");
threw = false;
try {
t.baseVal.appendItem({});
@ -772,7 +828,15 @@ function run_baseVal_API_tests()
ok(threw,
'The method '+t.list_type+'.appendItem() should throw if passed '+
'an object of the wrong type.');
eventChecker.ignoreEvents();
}
// Test removal and addition events
eventChecker.expect("remove add");
t.element.removeAttribute(t.attr_name);
res = t.baseVal.appendItem(item);
eventChecker.finish();
}
}
@ -880,7 +944,7 @@ function run_animVal_API_tests()
/**
* This function runs some basic tests to check the effect of setAttribute()
* calls on object identidy, without taking SMIL animation into consideration.
* calls on object identity, without taking SMIL animation into consideration.
*/
function run_basic_setAttribute_tests()
{