Bug 629200 part 9 - Update attribute setting for SVGAnimatedLengthList; r=jwatt
authorBrian Birtles <birtles@gmail.com>
Thu, 16 Feb 2012 08:40:45 +0900
changeset 89821 298a762dc5524ad3c0554b3fc5cc2b293a5de1e3
parent 89820 ca1719020069f7c20893ca9e81f39e33cd71d40d
child 89822 92bf6c6fc409821fa7d484222b4301ccd77e9e2a
push id783
push userlsblakk@mozilla.com
push dateTue, 24 Apr 2012 17:33:42 +0000
treeherdermozilla-beta@11faed19f136 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjwatt
bugs629200
milestone13.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 629200 part 9 - Update attribute setting for SVGAnimatedLengthList; r=jwatt
content/base/src/nsAttrValue.cpp
content/base/src/nsAttrValue.h
content/svg/content/src/DOMSVGLength.cpp
content/svg/content/src/DOMSVGLengthList.cpp
content/svg/content/src/Makefile.in
content/svg/content/src/nsSVGElement.cpp
content/svg/content/src/nsSVGElement.h
content/svg/content/test/test_SVGLengthList.xhtml
content/svg/content/test/test_SVGxxxList.xhtml
--- a/content/base/src/nsAttrValue.cpp
+++ b/content/base/src/nsAttrValue.cpp
@@ -47,16 +47,17 @@
 #include "mozilla/css/StyleRule.h"
 #include "mozilla/css/Declaration.h"
 #include "nsIHTMLDocument.h"
 #include "nsIDocument.h"
 #include "nsContentUtils.h"
 #include "nsReadableUtils.h"
 #include "prprf.h"
 #include "nsSVGLength2.h"
+#include "SVGLengthList.h"
 
 namespace css = mozilla::css;
 
 #define MISC_STR_PTR(_cont) \
   reinterpret_cast<void*>((_cont)->mStringBits & NS_ATTRVALUE_POINTERVALUE_MASK)
 
 nsTArray<const nsAttrValue::EnumTable*>* nsAttrValue::sEnumTableArray = nsnull;
 
@@ -265,16 +266,21 @@ nsAttrValue::SetTo(const nsAttrValue& aO
         cont->mIntMargin = new nsIntMargin(*otherCont->mIntMargin);
       break;
     }
     case eSVGLength:
     {
       cont->mSVGLength = otherCont->mSVGLength;
       break;
     }
+    case eSVGLengthList:
+    {
+      cont->mSVGLengthList = otherCont->mSVGLengthList;
+      break;
+    }
     default:
     {
       NS_NOTREACHED("unknown type stored in MiscContainer");
       break;
     }
   }
 
   void* otherPtr = MISC_STR_PTR(otherCont);
@@ -360,16 +366,28 @@ nsAttrValue::SetTo(const nsSVGLength2& a
     MiscContainer* cont = GetMiscContainer();
     cont->mSVGLength = &aValue;
     cont->mType = eSVGLength;
     SetMiscAtomOrString(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)
 {
   PtrBits tmp = aOther.mBits;
   aOther.mBits = mBits;
   mBits = tmp;
 }
 
 void
@@ -461,16 +479,21 @@ nsAttrValue::ToString(nsAString& aResult
       aResult.AppendFloat(GetDoubleValue());
       break;
     }
     case eSVGLength:
     {
       GetMiscContainer()->mSVGLength->GetBaseValueString(aResult);
       break;
     }
+    case eSVGLengthList:
+    {
+      GetMiscContainer()->mSVGLengthList->GetValueAsString(aResult);
+      break;
+    }
     default:
     {
       aResult.Truncate();
       break;
     }
   }
 }
 
@@ -649,16 +672,20 @@ nsAttrValue::HashValue() const
     case eIntMarginValue:
     {
       return NS_PTR_TO_INT32(cont->mIntMargin);
     }
     case eSVGLength:
     {
       return NS_PTR_TO_INT32(cont->mSVGLength);
     }
+    case eSVGLengthList:
+    {
+      return NS_PTR_TO_INT32(cont->mSVGLengthList);
+    }
     default:
     {
       NS_NOTREACHED("unknown type stored in MiscContainer");
       return 0;
     }
   }
 }
 
@@ -745,16 +772,20 @@ nsAttrValue::Equals(const nsAttrValue& a
     case eIntMarginValue:
     {
       return thisCont->mIntMargin == otherCont->mIntMargin;
     }
     case eSVGLength:
     {
       return thisCont->mSVGLength == otherCont->mSVGLength;
     }
+    case eSVGLengthList:
+    {
+      return thisCont->mSVGLengthList == otherCont->mSVGLengthList;
+    }
     default:
     {
       NS_NOTREACHED("unknown type stored in MiscContainer");
       return false;
     }
   }
   if (needsStringComparison) {
     if (thisCont->mStringBits == otherCont->mStringBits) {
--- a/content/base/src/nsAttrValue.h
+++ b/content/base/src/nsAttrValue.h
@@ -59,16 +59,17 @@ class nsIDocument;
 template<class E, class A> class nsTArray;
 struct nsTArrayDefaultAllocator;
 class nsSVGLength2;
 
 namespace mozilla {
 namespace css {
 class StyleRule;
 }
+class SVGLengthList;
 }
 
 #define NS_ATTRVALUE_MAX_STRINGLENGTH_ATOM 12
 
 #define NS_ATTRVALUE_BASETYPE_MASK (PtrBits(3))
 #define NS_ATTRVALUE_POINTERVALUE_MASK (~NS_ATTRVALUE_BASETYPE_MASK)
 
 #define NS_ATTRVALUE_INTEGERTYPE_BITS 4
@@ -124,29 +125,32 @@ public:
     ePercent =      0x0F, // 1111
     // Values below here won't matter, they'll be always stored in the 'misc'
     // struct.
     eCSSStyleRule =    0x10
     ,eAtomArray =      0x11
     ,eDoubleValue  =   0x12
     ,eIntMarginValue = 0x13
     ,eSVGLength =      0x14
+    ,eSVGLengthList =  0x15
   };
 
   ValueType Type() const;
 
   void Reset();
 
   void SetTo(const nsAttrValue& aOther);
   void SetTo(const nsAString& aValue);
   void SetTo(nsIAtom* aValue);
   void SetTo(PRInt16 aInt);
   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.
    *
    * After calling this method, this object will have type eString unless the
    * type of aValue is eAtom, in which case this object will also have type
    * eAtom.
    */
@@ -369,16 +373,17 @@ private:
       nscolor mColor;
       PRUint32 mEnumValue;
       PRInt32 mPercent;
       mozilla::css::StyleRule* mCSSStyleRule;
       AtomArray* mAtomArray;
       double mDoubleValue;
       nsIntMargin* mIntMargin;
       const nsSVGLength2* mSVGLength;
+      const mozilla::SVGLengthList* mSVGLengthList;
     };
   };
 
   inline ValueBaseType BaseType() const;
 
   /**
    * Get the index of an EnumTable in the sEnumTableArray.
    * If the EnumTable is not in the sEnumTableArray, it is added.
--- a/content/svg/content/src/DOMSVGLength.cpp
+++ b/content/svg/content/src/DOMSVGLength.cpp
@@ -151,18 +151,24 @@ DOMSVGLength::SetValue(float aUserUnitVa
   }
 
   // Although the value passed in is in user units, this method does not turn
   // this length into a user unit length. Instead it converts the user unit
   // value to this length's current unit and sets that, leaving this length's
   // 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();
       }
       return NS_OK;
     }
   } else if (mUnit == nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER ||
              mUnit == nsIDOMSVGLength::SVG_LENGTHTYPE_PX) {
     mValue = aUserUnitValue;
@@ -190,18 +196,22 @@ DOMSVGLength::SetValueInSpecifiedUnits(f
     return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
   }
 
   if (!NS_finite(aValue)) {
     return NS_ERROR_ILLEGAL_VALUE;
   }
 
   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();
     }
     return NS_OK;
   }
   mValue = aValue;
   return NS_OK;
 }
@@ -213,18 +223,22 @@ DOMSVGLength::SetValueAsString(const nsA
     return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
   }
 
   SVGLength value;
   if (!value.SetValueFromString(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();
     }
     return NS_OK;
   }
   mValue = value.GetValueInCurrentUnits();
   mUnit = value.GetUnit();
   return NS_OK;
@@ -254,18 +268,23 @@ DOMSVGLength::NewValueSpecifiedUnits(PRU
   if (!NS_finite(aValue)) {
     return NS_ERROR_ILLEGAL_VALUE;
   }
 
   if (!SVGLength::IsValidUnitType(aUnit)) {
     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();
     }
     return NS_OK;
   }
   mUnit = PRUint8(aUnit);
   mValue = aValue;
   return NS_OK;
@@ -277,17 +296,22 @@ DOMSVGLength::ConvertToSpecifiedUnits(PR
   if (mIsAnimValItem) {
     return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
   }
 
   if (!SVGLength::IsValidUnitType(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 {
     SVGLength len(mValue, mUnit);
     if (len.ConvertToUnit(PRUint8(aUnit), nsnull, 0)) {
       mValue = len.GetValueInCurrentUnits();
       mUnit = aUnit;
       return NS_OK;
--- a/content/svg/content/src/DOMSVGLengthList.cpp
+++ b/content/svg/content/src/DOMSVGLengthList.cpp
@@ -166,24 +166,25 @@ DOMSVGLengthList::GetNumberOfItems(PRUin
 NS_IMETHODIMP
 DOMSVGLengthList::Clear()
 {
   if (IsAnimValList()) {
     return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
   }
 
   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:
     mAList->InternalBaseValListWillChangeTo(SVGLengthList());
 
     mItems.Clear();
     InternalList().Clear();
-    Element()->DidChangeLengthList(AttrEnum(), true);
+    Element()->DidChangeLengthList(AttrEnum(), emptyOrOldValue);
     if (mAList->IsAnimating()) {
       Element()->AnimationNeedsResample();
     }
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -251,30 +252,31 @@ DOMSVGLengthList::InsertItemBefore(nsIDO
   }
 
   // Ensure we have enough memory so we can avoid complex error handling below:
   if (!mItems.SetCapacity(mItems.Length() + 1) ||
       !InternalList().SetCapacity(InternalList().Length() + 1)) {
     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);
 
   InternalList().InsertItem(index, domItem->ToSVGLength());
   mItems.InsertElementAt(index, domItem.get());
 
   // This MUST come after the insertion into InternalList(), or else under the
   // insertion into InternalList() the values read from domItem would be bad
   // data from InternalList() itself!:
   domItem->InsertingIntoList(this, AttrEnum(), index, IsAnimValList());
 
   UpdateListIndicesFromIndex(mItems, index + 1);
 
-  Element()->DidChangeLengthList(AttrEnum(), true);
+  Element()->DidChangeLengthList(AttrEnum(), emptyOrOldValue);
   if (mAList->IsAnimating()) {
     Element()->AnimationNeedsResample();
   }
   *_retval = domItem.forget().get();
   return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -293,30 +295,31 @@ DOMSVGLengthList::ReplaceItem(nsIDOMSVGL
   }
   if (index >= Length()) {
     return NS_ERROR_DOM_INDEX_SIZE_ERR;
   }
   if (domItem->HasOwner()) {
     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:
     mItems[index]->RemovingFromList();
   }
 
   InternalList()[index] = domItem->ToSVGLength();
   mItems[index] = domItem;
 
   // This MUST come after the ToSVGPoint() call, otherwise that call
   // 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();
   }
   NS_ADDREF(*_retval = domItem.get());
   return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -327,16 +330,17 @@ DOMSVGLengthList::RemoveItem(PRUint32 in
   if (IsAnimValList()) {
     return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
   }
 
   if (index >= Length()) {
     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.
   MaybeRemoveItemFromAnimValListAt(index);
 
   // We have to return the removed item, so make sure it exists:
   EnsureItemAt(index);
 
@@ -345,17 +349,17 @@ DOMSVGLengthList::RemoveItem(PRUint32 in
   mItems[index]->RemovingFromList();
   NS_ADDREF(*_retval = mItems[index]);
 
   InternalList().RemoveItem(index);
   mItems.RemoveElementAt(index);
 
   UpdateListIndicesFromIndex(mItems, index);
 
-  Element()->DidChangeLengthList(AttrEnum(), true);
+  Element()->DidChangeLengthList(AttrEnum(), emptyOrOldValue);
   if (mAList->IsAnimating()) {
     Element()->AnimationNeedsResample();
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 DOMSVGLengthList::AppendItem(nsIDOMSVGLength *newItem,
--- a/content/svg/content/src/Makefile.in
+++ b/content/svg/content/src/Makefile.in
@@ -166,16 +166,18 @@ include $(topsrcdir)/config/config.mk
 # we don't want the shared lib, but we want to force the creation of a static lib.
 FORCE_STATIC_LIB = 1
 
 EXPORTS =  			\
 	nsSVGElement.h             \
 	nsSVGFeatures.h            \
 	nsSVGLength2.h             \
 	nsSVGRect.h                \
+	SVGLength.h                \
+	SVGLengthList.h            \
 	$(NULL)
 
 include $(topsrcdir)/config/rules.mk
 
 INCLUDES += 	\
 		-I$(srcdir)/../../../xml/content/src \
 		-I$(srcdir)/../../../../dom \
 		-I$(srcdir)/../../../base/src \
--- a/content/svg/content/src/nsSVGElement.cpp
+++ b/content/svg/content/src/nsSVGElement.cpp
@@ -325,16 +325,20 @@ nsSVGElement::ParseAttribute(PRInt32 aNa
     if (!foundMatch) {
       // Check for SVGAnimatedLengthList attribute
       LengthListAttributesInfo lengthListInfo = GetLengthListInfo();
       for (i = 0; i < lengthListInfo.mLengthListCount; i++) {
         if (aAttribute == *lengthListInfo.mLengthListInfo[i].mName) {
           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;
         }
       }
     }
 
     if (!foundMatch) {
@@ -611,18 +615,18 @@ nsSVGElement::UnsetAttrInternal(PRInt32 
       }
     }
 
     // Check if this is a length list attribute going away
     LengthListAttributesInfo lengthListInfo = GetLengthListInfo();
 
     for (PRUint32 i = 0; i < lengthListInfo.mLengthListCount; i++) {
       if (aName == *lengthListInfo.mLengthListInfo[i].mName) {
+        MaybeSerializeAttrBeforeRemoval(aName, aNotify);
         lengthListInfo.Reset(i);
-        DidChangeLengthList(i, false);
         return;
       }
     }
 
     // Check if this is a number list attribute going away
     NumberListAttributesInfo numberListInfo = GetNumberListInfo();
 
     for (PRUint32 i = 0; i < numberListInfo.mNumberListCount; i++) {
@@ -1572,34 +1576,37 @@ nsSVGElement::GetLengthListInfo()
 
 void
 nsSVGElement::LengthListAttributesInfo::Reset(PRUint8 aAttrEnum)
 {
   mLengthLists[aAttrEnum].ClearBaseValue(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
 nsSVGElement::DidAnimateLengthList(PRUint8 aAttrEnum)
 {
   nsIFrame* frame = GetPrimaryFrame();
 
   if (frame) {
--- a/content/svg/content/src/nsSVGElement.h
+++ b/content/svg/content/src/nsSVGElement.h
@@ -166,29 +166,31 @@ public:
     return GetStringInfo().mStringInfo[aAttrEnum].mIsAnimatable;
   }
   bool NumberAttrAllowsPercentage(PRUint8 aAttrEnum) {
     return GetNumberInfo().mNumberInfo[aAttrEnum].mPercentagesAllowed;
   }
   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);
   virtual void DidChangeNumberPair(PRUint8 aAttrEnum, bool aDoSetAttr);
   virtual void DidChangeInteger(PRUint8 aAttrEnum, bool aDoSetAttr);
   virtual void DidChangeIntegerPair(PRUint8 aAttrEnum, bool aDoSetAttr);
   virtual void DidChangeAngle(PRUint8 aAttrEnum, bool aDoSetAttr);
   virtual void DidChangeBoolean(PRUint8 aAttrEnum, bool aDoSetAttr);
   void DidChangeEnum(PRUint8 aAttrEnum);
   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);
   virtual void DidChangeString(PRUint8 aAttrEnum) {}
   void DidChangeStringList(bool aIsConditionalProcessingAttribute,
                            PRUint8 aAttrEnum);
 
   virtual void DidAnimateLength(PRUint8 aAttrEnum);
--- a/content/svg/content/test/test_SVGLengthList.xhtml
+++ b/content/svg/content/test/test_SVGLengthList.xhtml
@@ -1,15 +1,16 @@
 <html xmlns="http://www.w3.org/1999/xhtml">
 <!--
 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>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=515116">Mozilla Bug 515116</a>
 <p id="display"></p>
 <div id="content" style="display:none;">
 <svg id="svg" xmlns="http://www.w3.org/2000/svg" width="100" height="100">
   <text id="text" x="10cm 20cm 30mc"/>
@@ -31,21 +32,41 @@ function run_tests()
 {
   document.getElementById('svg').pauseAnimations();
 
   var text = document.getElementById("text");
   var lengths = text.x.baseVal;
 
   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();
 }
 
 window.addEventListener("load", run_tests, false);
 
 ]]>
 </script>
--- a/content/svg/content/test/test_SVGxxxList.xhtml
+++ b/content/svg/content/test/test_SVGxxxList.xhtml
@@ -1,16 +1,17 @@
 <html xmlns="http://www.w3.org/1999/xhtml">
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=515116
 -->
 <head>
   <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>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=515116">Mozilla Bug 515116</a>
 <p id="display"></p>
 <div id="content" style="display:none;">
 <svg id="svg" xmlns="http://www.w3.org/2000/svg" width="100" height="100"
      onload="this.pauseAnimations();">
@@ -447,40 +448,78 @@ function get_array_of_list_items(list)
  *   SVGLength removeItem(in unsigned long index)
  *   SVGLength appendItem(in SVGLength newItem)
  *
  * @param t A test from the 'tests' array.
  */
 function run_baseVal_API_tests()
 {
   var res, threw, items;
+  var eventChecker = new MutationEventChecker;
 
   for each (var t in tests) {
 
     // Test .clear():
 
     t.element.setAttribute(t.attr_name, t.attr_val_4);
 
     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,
        'The method '+t.list_type+'.clear() should clear the '+t.list_type+
        ' 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.');
     ok(res === item,
        'The list item returned by '+t.list_type+'.initialize() should be the '+
        'exact same object as the item that was passed to that method, since '+
        'the item that was passed to that method did not already belong to a '+
        'list.');
@@ -510,34 +549,38 @@ function run_baseVal_API_tests()
       res = t.baseVal.initialize(item);
 
       ok(res !== item &&
          t.baseVal.getItem(0) !== item,
          'The method '+t.list_type+'.initialize() should clone the object that '+
          '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({});
       } catch(e) {
         threw = true;
       }
       ok(threw,
          'The method '+t.list_type+'.initialize() should throw if passed an '+
          'object of the wrong type.');
+      eventChecker.ignoreEvents();
     }
 
     // Test .insertItemBefore():
 
     t.element.setAttribute(t.attr_name, t.attr_val_4);
 
     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.');
     ok(res === item,
        'The list item returned by '+t.list_type+'.insertItemBefore() should '+
        'be the exact same object as the item that was passed to that method, '+
        'since the item that was passed to that method did not already belong '+
        'to a list.');
@@ -582,34 +625,38 @@ function run_baseVal_API_tests()
     if (t.item_type != "DOMString") {
       ok(res !== item &&
          t.baseVal.getItem(2) !== item,
          'The method '+t.list_type+'.insertItemBefore() should clone the '+
          'object that is passed in, even if that object is the item in '+
          'the list at the index specified.');
       // [SVGWG issue] not what the spec currently says
 
+      eventChecker.expect("");
       threw = false;
       try {
         t.baseVal.insertItemBefore({}, 2);
       } catch(e) {
         threw = true;
       }
       ok(threw,
          'The method '+t.list_type+'.insertItemBefore() should throw if passed '+
          'an object of the wrong type.');
+      eventChecker.ignoreEvents();
     }
 
     // Test .replaceItem():
 
     t.element.setAttribute(t.attr_name, t.attr_val_4);
 
     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.');
     if (t.item_type != "DOMString") {
       ok(res === item,
          'The list item returned by '+t.list_type+'.replaceItem() should be '+
          'the exact same object as the item that was passed to that method, '+
          'since the item that was passed to that method did not already belong '+
@@ -622,25 +669,27 @@ function run_baseVal_API_tests()
        'to a list.');
     ok(t.baseVal.getItem(3) === old_items[3],
        'The list item that was at index 3 should still be at index 3 after '+
        'the item at index 2 was replaced using the '+t.list_type+
        '.replaceItem() method.');
 
     item = t.item_constructor();
 
+    eventChecker.expect("");
     threw = false;
     try {
       t.baseVal.replaceItem(item, 100);
     } catch(e) {
       threw = true;
     }
     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);
     res = t.baseVal.replaceItem(item, 1);
 
     is(t.baseVal.numberOfItems, 4,
        'The '+t.list_type+' object should contain four list items.');
     if (t.item_type != "DOMString") {
@@ -678,17 +727,19 @@ function run_baseVal_API_tests()
     }
 
     // Test .removeItem():
 
     t.element.setAttribute(t.attr_name, t.attr_val_4);
 
     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.');
     if (t.item_type != "DOMString") {
       ok(res === item,
          'The list item returned by '+t.list_type+'.removeItem() should be the '+
          'exact same object as the item that was at the specified index.');
     }
@@ -696,33 +747,37 @@ function run_baseVal_API_tests()
        'The list item that was at index 1 should still be at index 1 after '+
        'the item at index 2 was removed using the '+t.list_type+
        '.replaceItem() method.');
     ok(t.baseVal.getItem(2) === old_items[3],
        'The list item that was at index 3 should still be at index 2 after '+
        'the item at index 2 was removed using the '+t.list_type+
        '.replaceItem() method.');
 
+    eventChecker.expect("");
     threw = false;
     try {
       t.baseVal.removeItem(100);
     } catch(e) {
       threw = true;
     }
     ok(threw,
        'The method '+t.list_type+'.removeItem() should throw if passed '+
        'an index that is out of bounds.');
+    eventChecker.ignoreEvents();
 
     // Test .appendItem():
 
     t.element.setAttribute(t.attr_name, t.attr_val_4);
 
     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.');
     ok(res === item,
        'The list item returned by '+t.list_type+'.appendItem() should be the '+
        'exact same object as the item that was passed to that method, since '+
        'the item that was passed to that method did not already belong '+
        'to a list.');
@@ -758,26 +813,35 @@ function run_baseVal_API_tests()
     if (t.item_type != "DOMString") {
       ok(res !== item &&
          t.baseVal.getItem(6) !== item,
          'The method '+t.list_type+'.appendItem() should clone the object '+
          'that is passed in, if that object is already the last item in '+
          'that list.');
       // [SVGWG issue] not what the spec currently says
 
+      eventChecker.expect("");
       threw = false;
       try {
         t.baseVal.appendItem({});
       } catch(e) {
         threw = true;
       }
       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();
   }
 }
 
 
 /**
  * This function tests the SVGXxxList API for the anim val list (see also the
  * comment for test_baseVal_API).
  */
@@ -875,17 +939,17 @@ function run_animVal_API_tests()
        'The method '+t.list_type+'.appendItem() should throw when called '+
        'on an anim val list, since anim val lists should be readonly.');
   }
 }
 
 
 /**
  * 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()
 {
   for each (var t in tests) {
 
     // Since the t.prop, t.baseVal and t.animVal objects should never ever
     // change, we leave testing of them to our caller so that it can check
     // them after all the other mutations such as SMIL changes.