Bug 629200 part 19 - Remove unnecessary serialisation from setting nsSVGNumberList; r=jwatt
authorBrian Birtles <birtles@gmail.com>
Thu, 16 Feb 2012 08:40:46 +0900
changeset 86941 e6b4f0dbd601a0c04fde82090855237aa14b1418
parent 86940 b397e9d603348257f499d2daed842146fd37911e
child 86942 1348090e5c2716e0881e319556946bd698cd8c7d
push id6122
push userbbirtles@mozilla.com
push dateWed, 15 Feb 2012 23:41:18 +0000
treeherdermozilla-inbound@03ff5cb466da [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 19 - Remove unnecessary serialisation from setting nsSVGNumberList; r=jwatt
content/base/src/nsAttrValue.cpp
content/base/src/nsAttrValue.h
content/svg/content/src/DOMSVGNumber.cpp
content/svg/content/src/DOMSVGNumberList.cpp
content/svg/content/src/Makefile.in
content/svg/content/src/nsSVGElement.cpp
content/svg/content/src/nsSVGElement.h
content/svg/content/test/Makefile.in
content/svg/content/test/test_SVGNumberList.xhtml
--- a/content/base/src/nsAttrValue.cpp
+++ b/content/base/src/nsAttrValue.cpp
@@ -53,16 +53,17 @@
 #include "prprf.h"
 #include "nsSVGAngle.h"
 #include "nsSVGIntegerPair.h"
 #include "nsSVGLength2.h"
 #include "nsSVGNumberPair.h"
 #include "nsSVGViewBox.h"
 #include "SVGAnimatedPreserveAspectRatio.h"
 #include "SVGLengthList.h"
+#include "SVGNumberList.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;
 
@@ -286,16 +287,21 @@ nsAttrValue::SetTo(const nsAttrValue& aO
       cont->mSVGLength = otherCont->mSVGLength;
       break;
     }
     case eSVGLengthList:
     {
       cont->mSVGLengthList = otherCont->mSVGLengthList;
       break;
     }
+    case eSVGNumberList:
+    {
+      cont->mSVGNumberList = otherCont->mSVGNumberList;
+      break;
+    }
     case eSVGNumberPair:
     {
       cont->mSVGNumberPair = otherCont->mSVGNumberPair;
       break;
     }
     case eSVGPreserveAspectRatio:
     {
       cont->mSVGPreserveAspectRatio = otherCont->mSVGPreserveAspectRatio;
@@ -448,16 +454,28 @@ nsAttrValue::SetTo(const mozilla::SVGLen
     MiscContainer* cont = GetMiscContainer();
     cont->mSVGLengthList = &aValue;
     cont->mType = eSVGLengthList;
     SetMiscAtomOrString(aSerialized);
   }
 }
 
 void
+nsAttrValue::SetTo(const mozilla::SVGNumberList& aValue,
+                   const nsAString* aSerialized)
+{
+  if (EnsureEmptyMiscContainer()) {
+    MiscContainer* cont = GetMiscContainer();
+    cont->mSVGNumberList = &aValue;
+    cont->mType = eSVGNumberList;
+    SetMiscAtomOrString(aSerialized);
+  }
+}
+
+void
 nsAttrValue::SetTo(const nsSVGNumberPair& aValue, const nsAString* aSerialized)
 {
   if (EnsureEmptyMiscContainer()) {
     MiscContainer* cont = GetMiscContainer();
     cont->mSVGNumberPair = &aValue;
     cont->mType = eSVGNumberPair;
     SetMiscAtomOrString(aSerialized);
   }
@@ -598,16 +616,21 @@ nsAttrValue::ToString(nsAString& aResult
       GetMiscContainer()->mSVGLength->GetBaseValueString(aResult);
       break;
     }
     case eSVGLengthList:
     {
       GetMiscContainer()->mSVGLengthList->GetValueAsString(aResult);
       break;
     }
+    case eSVGNumberList:
+    {
+      GetMiscContainer()->mSVGNumberList->GetValueAsString(aResult);
+      break;
+    }
     case eSVGNumberPair:
     {
       GetMiscContainer()->mSVGNumberPair->GetBaseValueString(aResult);
       break;
     }
     case eSVGPreserveAspectRatio:
     {
       GetMiscContainer()->mSVGPreserveAspectRatio->GetBaseValueString(aResult);
@@ -813,16 +836,20 @@ nsAttrValue::HashValue() const
     case eSVGLength:
     {
       return NS_PTR_TO_INT32(cont->mSVGLength);
     }
     case eSVGLengthList:
     {
       return NS_PTR_TO_INT32(cont->mSVGLengthList);
     }
+    case eSVGNumberList:
+    {
+      return NS_PTR_TO_INT32(cont->mSVGNumberList);
+    }
     case eSVGNumberPair:
     {
       return NS_PTR_TO_INT32(cont->mSVGNumberPair);
     }
     case eSVGPreserveAspectRatio:
     {
       return NS_PTR_TO_INT32(cont->mSVGPreserveAspectRatio);
     }
@@ -933,16 +960,20 @@ nsAttrValue::Equals(const nsAttrValue& a
     case eSVGLength:
     {
       return thisCont->mSVGLength == otherCont->mSVGLength;
     }
     case eSVGLengthList:
     {
       return thisCont->mSVGLengthList == otherCont->mSVGLengthList;
     }
+    case eSVGNumberList:
+    {
+      return thisCont->mSVGNumberList == otherCont->mSVGNumberList;
+    }
     case eSVGNumberPair:
     {
       return thisCont->mSVGNumberPair == otherCont->mSVGNumberPair;
     }
     case eSVGPreserveAspectRatio:
     {
       return thisCont->mSVGPreserveAspectRatio ==
         otherCont->mSVGPreserveAspectRatio;
--- a/content/base/src/nsAttrValue.h
+++ b/content/base/src/nsAttrValue.h
@@ -65,16 +65,17 @@ class nsSVGNumberPair;
 class nsSVGViewBox;
 
 namespace mozilla {
 namespace css {
 class StyleRule;
 }
 class SVGAnimatedPreserveAspectRatio;
 class SVGLengthList;
+class SVGNumberList;
 }
 
 #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
@@ -133,19 +134,20 @@ public:
     eCSSStyleRule =    0x10
     ,eAtomArray =      0x11
     ,eDoubleValue  =   0x12
     ,eIntMarginValue = 0x13
     ,eSVGAngle =       0x14
     ,eSVGIntegerPair = 0x15
     ,eSVGLength =      0x16
     ,eSVGLengthList =  0x17
-    ,eSVGNumberPair =  0x18
-    ,eSVGPreserveAspectRatio = 0x19
-    ,eSVGViewBox =     0x20
+    ,eSVGNumberList =  0x18
+    ,eSVGNumberPair =  0x19
+    ,eSVGPreserveAspectRatio = 0x20
+    ,eSVGViewBox =     0x21
   };
 
   ValueType Type() const;
 
   void Reset();
 
   void SetTo(const nsAttrValue& aOther);
   void SetTo(const nsAString& aValue);
@@ -155,16 +157,18 @@ public:
   void SetTo(double aValue, const nsAString* aSerialized);
   void SetTo(mozilla::css::StyleRule* aValue, const nsAString* aSerialized);
   void SetTo(const nsIntMargin& aValue);
   void SetTo(const nsSVGAngle& aValue, const nsAString* aSerialized);
   void SetTo(const nsSVGIntegerPair& aValue, const nsAString* aSerialized);
   void SetTo(const nsSVGLength2& aValue, const nsAString* aSerialized);
   void SetTo(const mozilla::SVGLengthList& aValue,
              const nsAString* aSerialized);
+  void SetTo(const mozilla::SVGNumberList& aValue,
+             const nsAString* aSerialized);
   void SetTo(const nsSVGNumberPair& aValue, const nsAString* aSerialized);
   void SetTo(const mozilla::SVGAnimatedPreserveAspectRatio& aValue,
              const nsAString* aSerialized);
   void SetTo(const nsSVGViewBox& aValue, const nsAString* aSerialized);
 
   /**
    * Sets this object with the string or atom representation of aValue.
    *
@@ -394,16 +398,17 @@ private:
       mozilla::css::StyleRule* mCSSStyleRule;
       AtomArray* mAtomArray;
       double mDoubleValue;
       nsIntMargin* mIntMargin;
       const nsSVGAngle* mSVGAngle;
       const nsSVGIntegerPair* mSVGIntegerPair;
       const nsSVGLength2* mSVGLength;
       const mozilla::SVGLengthList* mSVGLengthList;
+      const mozilla::SVGNumberList* mSVGNumberList;
       const nsSVGNumberPair* mSVGNumberPair;
       const mozilla::SVGAnimatedPreserveAspectRatio* mSVGPreserveAspectRatio;
       const nsSVGViewBox* mSVGViewBox;
     };
   };
 
   inline ValueBaseType BaseType() const;
 
--- a/content/svg/content/src/DOMSVGNumber.cpp
+++ b/content/svg/content/src/DOMSVGNumber.cpp
@@ -118,18 +118,22 @@ DOMSVGNumber::SetValue(float aValue)
 {
   if (mIsAnimValItem) {
     return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
   }
 
   NS_ENSURE_FINITE(aValue, NS_ERROR_ILLEGAL_VALUE);
 
   if (HasOwner()) {
+    if (InternalItem() == aValue) {
+      return NS_OK;
+    }
+    nsAttrValue emptyOrOldValue = Element()->WillChangeNumberList(mAttrEnum);
     InternalItem() = aValue;
-    Element()->DidChangeNumberList(mAttrEnum, true);
+    Element()->DidChangeNumberList(mAttrEnum, emptyOrOldValue);
     if (mList->mAList->IsAnimating()) {
       Element()->AnimationNeedsResample();
     }
     return NS_OK;
   }
   mValue = aValue;
   return NS_OK;
 }
--- a/content/svg/content/src/DOMSVGNumberList.cpp
+++ b/content/svg/content/src/DOMSVGNumberList.cpp
@@ -166,24 +166,25 @@ DOMSVGNumberList::GetNumberOfItems(PRUin
 NS_IMETHODIMP
 DOMSVGNumberList::Clear()
 {
   if (IsAnimValList()) {
     return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
   }
 
   if (Length() > 0) {
+    nsAttrValue emptyOrOldValue = Element()->WillChangeNumberList(AttrEnum());
     // Notify any existing DOM items of removal *before* truncating the lists
     // so that they can find their SVGNumber internal counterparts and copy
     // their values. This also notifies the animVal list:
     mAList->InternalBaseValListWillChangeTo(SVGNumberList());
 
     mItems.Clear();
     InternalList().Clear();
-    Element()->DidChangeNumberList(AttrEnum(), true);
+    Element()->DidChangeNumberList(AttrEnum(), emptyOrOldValue);
     if (mAList->IsAnimating()) {
       Element()->AnimationNeedsResample();
     }
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -251,30 +252,31 @@ DOMSVGNumberList::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()->WillChangeNumberList(AttrEnum());
   // Now that we know we're inserting, keep animVal list in sync as necessary.
   MaybeInsertNullInAnimValListAt(index);
 
   InternalList().InsertItem(index, domItem->ToSVGNumber());
   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()->DidChangeNumberList(AttrEnum(), true);
+  Element()->DidChangeNumberList(AttrEnum(), emptyOrOldValue);
   if (mAList->IsAnimating()) {
     Element()->AnimationNeedsResample();
   }
   *_retval = domItem.forget().get();
   return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -293,30 +295,31 @@ DOMSVGNumberList::ReplaceItem(nsIDOMSVGN
   }
   if (index >= Length()) {
     return NS_ERROR_DOM_INDEX_SIZE_ERR;
   }
   if (domItem->HasOwner()) {
     domItem = domItem->Clone(); // must do this before changing anything!
   }
 
+  nsAttrValue emptyOrOldValue = Element()->WillChangeNumberList(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->ToSVGNumber();
   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()->DidChangeNumberList(AttrEnum(), true);
+  Element()->DidChangeNumberList(AttrEnum(), emptyOrOldValue);
   if (mAList->IsAnimating()) {
     Element()->AnimationNeedsResample();
   }
   NS_ADDREF(*_retval = domItem.get());
   return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -335,27 +338,28 @@ DOMSVGNumberList::RemoveItem(PRUint32 in
   // 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);
 
+  nsAttrValue emptyOrOldValue = Element()->WillChangeNumberList(AttrEnum());
   // Notify the DOM item of removal *before* modifying the lists so that the
   // DOM item can copy its *old* value:
   mItems[index]->RemovingFromList();
   NS_ADDREF(*_retval = mItems[index]);
 
   InternalList().RemoveItem(index);
   mItems.RemoveElementAt(index);
 
   UpdateListIndicesFromIndex(mItems, index);
 
-  Element()->DidChangeNumberList(AttrEnum(), true);
+  Element()->DidChangeNumberList(AttrEnum(), emptyOrOldValue);
   if (mAList->IsAnimating()) {
     Element()->AnimationNeedsResample();
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 DOMSVGNumberList::AppendItem(nsIDOMSVGNumber *newItem,
--- a/content/svg/content/src/Makefile.in
+++ b/content/svg/content/src/Makefile.in
@@ -173,16 +173,17 @@ EXPORTS =  			\
 	nsSVGIntegerPair.h         \
 	nsSVGLength2.h             \
 	nsSVGNumberPair.h          \
 	nsSVGRect.h                \
 	nsSVGViewBox.h             \
 	SVGAnimatedPreserveAspectRatio.h \
 	SVGLength.h                \
 	SVGLengthList.h            \
+	SVGNumberList.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
@@ -344,16 +344,20 @@ nsSVGElement::ParseAttribute(PRInt32 aNa
     if (!foundMatch) {
       // Check for SVGAnimatedNumberList attribute
       NumberListAttributesInfo numberListInfo = GetNumberListInfo();
       for (i = 0; i < numberListInfo.mNumberListCount; i++) {
         if (aAttribute == *numberListInfo.mNumberListInfo[i].mName) {
           rv = numberListInfo.mNumberLists[i].SetBaseValueString(aValue);
           if (NS_FAILED(rv)) {
             numberListInfo.Reset(i);
+          } else {
+            aResult.SetTo(numberListInfo.mNumberLists[i].GetBaseValue(),
+                          &aValue);
+            didSetResult = true;
           }
           foundMatch = true;
           break;
         }
       }
     }
 
     if (!foundMatch) {
@@ -652,18 +656,18 @@ nsSVGElement::UnsetAttrInternal(PRInt32 
       }
     }
 
     // Check if this is a number list attribute going away
     NumberListAttributesInfo numberListInfo = GetNumberListInfo();
 
     for (PRUint32 i = 0; i < numberListInfo.mNumberListCount; i++) {
       if (aName == *numberListInfo.mNumberListInfo[i].mName) {
+        MaybeSerializeAttrBeforeRemoval(aName, aNotify);
         numberListInfo.Reset(i);
-        DidChangeNumberList(i, false);
         return;
       }
     }
 
     // Check if this is a point list attribute going away
     if (GetPointListAttrName() == aName) {
       SVGAnimatedPointList *pointList = GetAnimatedPointList();
       if (pointList) {
@@ -1682,34 +1686,38 @@ nsSVGElement::GetNumberListInfo()
 void
 nsSVGElement::NumberListAttributesInfo::Reset(PRUint8 aAttrEnum)
 {
   NS_ABORT_IF_FALSE(aAttrEnum < mNumberListCount, "Bad attr enum");
   mNumberLists[aAttrEnum].ClearBaseValue(aAttrEnum);
   // caller notifies
 }
 
-void
-nsSVGElement::DidChangeNumberList(PRUint8 aAttrEnum, bool aDoSetAttr)
+nsAttrValue
+nsSVGElement::WillChangeNumberList(PRUint8 aAttrEnum)
 {
-  if (!aDoSetAttr)
-    return;
+  return WillChangeValue(*GetNumberListInfo().mNumberListInfo[aAttrEnum].mName);
+}
 
+void
+nsSVGElement::DidChangeNumberList(PRUint8 aAttrEnum,
+                                  const nsAttrValue& aEmptyOrOldValue)
+{
   NumberListAttributesInfo info = GetNumberListInfo();
 
   NS_ABORT_IF_FALSE(info.mNumberListCount > 0,
-                    "DidChangeNumberList on element with no number list attribs");
-  NS_ABORT_IF_FALSE(aAttrEnum < info.mNumberListCount, "aAttrEnum out of range");
+    "DidChangeNumberList on element with no number list attribs");
+  NS_ABORT_IF_FALSE(aAttrEnum < info.mNumberListCount,
+    "aAttrEnum out of range");
 
-  nsAutoString serializedValue;
-  info.mNumberLists[aAttrEnum].GetBaseValue().GetValueAsString(serializedValue);
+  nsAttrValue newValue;
+  newValue.SetTo(info.mNumberLists[aAttrEnum].GetBaseValue(), nsnull);
 
-  nsAttrValue attrValue(serializedValue);
-  SetParsedAttr(kNameSpaceID_None, *info.mNumberListInfo[aAttrEnum].mName,
-                nsnull, attrValue, true);
+  DidChangeValue(*info.mNumberListInfo[aAttrEnum].mName, aEmptyOrOldValue,
+                 newValue);
 }
 
 void
 nsSVGElement::DidAnimateNumberList(PRUint8 aAttrEnum)
 {
   nsIFrame* frame = GetPrimaryFrame();
 
   if (frame) {
--- a/content/svg/content/src/nsSVGElement.h
+++ b/content/svg/content/src/nsSVGElement.h
@@ -171,31 +171,33 @@ public:
   void SetLength(nsIAtom* aName, const nsSVGLength2 &aLength);
 
   nsAttrValue WillChangeLength(PRUint8 aAttrEnum);
   nsAttrValue WillChangeNumberPair(PRUint8 aAttrEnum);
   nsAttrValue WillChangeIntegerPair(PRUint8 aAttrEnum);
   nsAttrValue WillChangeAngle(PRUint8 aAttrEnum);
   nsAttrValue WillChangeViewBox();
   nsAttrValue WillChangePreserveAspectRatio();
+  nsAttrValue WillChangeNumberList(PRUint8 aAttrEnum);
   nsAttrValue WillChangeLengthList(PRUint8 aAttrEnum);
 
   void DidChangeLength(PRUint8 aAttrEnum, const nsAttrValue& aEmptyOrOldValue);
   void DidChangeNumber(PRUint8 aAttrEnum);
   void DidChangeNumberPair(PRUint8 aAttrEnum,
                            const nsAttrValue& aEmptyOrOldValue);
   void DidChangeInteger(PRUint8 aAttrEnum);
   void DidChangeIntegerPair(PRUint8 aAttrEnum,
                             const nsAttrValue& aEmptyOrOldValue);
   void DidChangeAngle(PRUint8 aAttrEnum, const nsAttrValue& aEmptyOrOldValue);
   void DidChangeBoolean(PRUint8 aAttrEnum);
   void DidChangeEnum(PRUint8 aAttrEnum);
   void DidChangeViewBox(const nsAttrValue& aEmptyOrOldValue);
   void DidChangePreserveAspectRatio(const nsAttrValue& aEmptyOrOldValue);
-  virtual void DidChangeNumberList(PRUint8 aAttrEnum, bool aDoSetAttr);
+  void DidChangeNumberList(PRUint8 aAttrEnum,
+                           const nsAttrValue& aEmptyOrOldValue);
   void DidChangeLengthList(PRUint8 aAttrEnum,
                            const nsAttrValue& aEmptyOrOldValue);
   virtual void DidChangePointList(bool aDoSetAttr);
   virtual void DidChangePathSegList(bool aDoSetAttr);
   virtual void DidChangeTransformList(bool aDoSetAttr);
   void DidChangeString(PRUint8 aAttrEnum) {}
   void DidChangeStringList(bool aIsConditionalProcessingAttribute,
                            PRUint8 aAttrEnum);
--- a/content/svg/content/test/Makefile.in
+++ b/content/svg/content/test/Makefile.in
@@ -85,16 +85,17 @@ include $(topsrcdir)/config/rules.mk
 		test_SVGAnimatedImageSMILDisabled.html \
 		animated-svg-image-helper.html \
 		animated-svg-image-helper.svg \
 		test_stroke-linecap-hit-testing.xhtml \
 		test_SVG_namespace_ids.html \
 		test_SVGLengthList.xhtml \
 		test_SVGLengthList-2.xhtml \
 		test_SVGMatrix.xhtml \
+		test_SVGNumberList.xhtml \
 		test_SVGPathSegList.xhtml \
 		test_SVGStyleElement.xhtml \
 		test_SVGStringList.xhtml \
 		test_SVGTransformList.xhtml \
 		test_SVGTransformListAddition.xhtml \
 		test_SVGxxxList.xhtml \
 		test_SVGxxxListIndexing.xhtml \
 		test_switch.xhtml \
new file mode 100644
--- /dev/null
+++ b/content/svg/content/test/test_SVGNumberList.xhtml
@@ -0,0 +1,64 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=629200
+-->
+<head>
+  <title>Tests specific to SVGNumberList</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=629200">Mozilla Bug 629200</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" rotate="10 20 30">abc</text>
+</svg>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+<![CDATA[
+
+SimpleTest.waitForExplicitFinish();
+
+/*
+This file runs a series of SVGNumberList specific tests. Generic SVGXxxList
+tests can be found in test_SVGxxxList.xhtml. Anything that can be generalized
+to other list types belongs there.
+*/
+
+function run_tests()
+{
+  document.getElementById('svg').pauseAnimations();
+
+  var text = document.getElementById("text");
+  var numbers = text.rotate.baseVal;
+
+  is(numbers.numberOfItems, 3, 'Checking numberOfItems');
+
+  // Test mutation events
+  // --- Initialization
+  eventChecker = new MutationEventChecker;
+  eventChecker.watchAttr(text, "rotate");
+  // -- Actual changes
+  eventChecker.expect("modify modify");
+  numbers[0].value = 15;
+  text.setAttribute("rotate", "17 20 30");
+  // -- Redundant changes
+  eventChecker.expect("");
+  numbers[0].value = 17;
+  numbers[1].value = 20;
+  text.setAttribute("rotate", "17 20 30");
+  eventChecker.finish();
+
+  SimpleTest.finish();
+}
+
+window.addEventListener("load", run_tests, false);
+
+]]>
+</script>
+</pre>
+</body>
+</html>