Merge from mozilla-central.
authorDavid Anderson <danderson@mozilla.com>
Thu, 16 Feb 2012 15:06:30 -0800
changeset 112142 7d6904f186bcf0149c2391267b4d7529c7f326a8
parent 112141 8f0f05d821e514704e49e3ee5cfa482d8cc3768e (current diff)
parent 89853 78fde7e54d9275bacdae0d20536adf573d26e6a6 (diff)
child 112143 8aaf5f626c54aa48ac363ce4c32ad109f2125803
push id1708
push userakeybl@mozilla.com
push dateMon, 19 Nov 2012 21:10:21 +0000
treeherdermozilla-beta@27b14fe50103 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
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
Merge from mozilla-central.
accessible/src/html/nsHyperTextAccessible.cpp
b2g/installer/package-manifest.in
browser/app/profile/firefox.js
browser/base/content/browser.js
browser/base/content/tabbrowser.xml
browser/locales/en-US/searchplugins/google-params.inc
build/mobile/robocop/Actions.java.in
config/autoconf.mk.in
configure.in
content/base/public/nsIMessageWakeupService.idl
content/base/src/nsAttrAndChildArray.h
content/base/src/nsAttrValue.cpp
content/base/src/nsAttrValue.h
content/base/src/nsEventSource.cpp
content/base/src/nsEventSource.h
content/base/src/nsWebSocket.cpp
content/base/src/nsXMLHttpRequest.cpp
content/base/src/nsXMLHttpRequest.h
content/canvas/src/WebGLContext.h
content/html/content/src/nsDOMValidityState.h
content/html/content/src/nsHTMLInputElement.cpp
content/html/content/src/nsHTMLInputElement.h
content/html/content/src/nsHTMLMediaElement.cpp
content/media/nsBuiltinDecoder.cpp
content/svg/content/src/DOMSVGLength.cpp
content/svg/content/src/DOMSVGNumber.cpp
content/svg/content/src/DOMSVGPathSeg.cpp
content/svg/content/src/DOMSVGPathSegList.cpp
content/svg/content/src/DOMSVGPoint.cpp
content/svg/content/src/DOMSVGPointList.cpp
content/svg/content/src/DOMSVGTransform.cpp
content/svg/content/src/DOMSVGTransform.h
content/svg/content/src/Makefile.in
content/svg/content/src/SVGAnimatedPreserveAspectRatio.cpp
content/svg/content/src/SVGAnimatedPreserveAspectRatio.h
content/svg/content/src/SVGTransform.h
content/svg/content/src/nsSVGAngle.cpp
content/svg/content/src/nsSVGAngle.h
content/svg/content/src/nsSVGBoolean.cpp
content/svg/content/src/nsSVGBoolean.h
content/svg/content/src/nsSVGElement.cpp
content/svg/content/src/nsSVGElement.h
content/svg/content/src/nsSVGEnum.cpp
content/svg/content/src/nsSVGEnum.h
content/svg/content/src/nsSVGInteger.cpp
content/svg/content/src/nsSVGIntegerPair.cpp
content/svg/content/src/nsSVGIntegerPair.h
content/svg/content/src/nsSVGLength2.cpp
content/svg/content/src/nsSVGLength2.h
content/svg/content/src/nsSVGMarkerElement.cpp
content/svg/content/src/nsSVGNumber2.cpp
content/svg/content/src/nsSVGNumberPair.cpp
content/svg/content/src/nsSVGNumberPair.h
content/svg/content/src/nsSVGViewBox.cpp
docshell/base/nsDocShell.cpp
dom/Makefile.in
dom/base/nsDOMClassInfo.cpp
dom/base/nsDOMClassInfoClasses.h
dom/dom-config.mk
dom/plugins/base/nsNPAPIPluginInstance.cpp
dom/plugins/base/nsNPAPIPluginInstance.h
dom/plugins/base/nsPluginHost.cpp
dom/plugins/base/nsPluginInstanceOwner.cpp
dom/plugins/base/nsPluginInstanceOwner.h
gfx/layers/basic/BasicImages.cpp
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jsfriendapi.cpp
js/src/jsgc.cpp
js/src/jsgcmark.cpp
js/src/jsinterp.cpp
js/src/jsinterpinlines.h
js/src/jsopcode.cpp
js/src/jsopcode.h
js/src/methodjit/StubCalls.cpp
js/src/shell/js.cpp
js/xpconnect/src/nsXPConnect.cpp
js/xpconnect/src/xpcprivate.h
layout/base/FrameLayerBuilder.cpp
layout/build/Makefile.in
layout/reftests/bugs/reftest.list
layout/style/nsCSSParser.cpp
layout/style/nsComputedDOMStyle.cpp
layout/style/nsComputedDOMStyle.h
layout/style/test/Makefile.in
layout/svg/base/src/nsSVGForeignObjectFrame.cpp
layout/svg/base/src/nsSVGImageFrame.cpp
mobile/android/base/GeckoApp.java
mobile/android/base/Makefile.in
mobile/android/base/resources/drawable-hdpi/abouthome_topsite_placeholder.png
mobile/android/base/resources/drawable-hdpi/abouthome_topsite_shadow.9.png
mobile/android/base/resources/drawable-xhdpi-v11/abouthome_topsite_placeholder.png
mobile/android/base/resources/drawable-xhdpi-v11/abouthome_topsite_shadow.9.png
mobile/android/base/resources/drawable/abouthome_topsite_placeholder.png
mobile/android/base/resources/drawable/abouthome_topsite_shadow.9.png
mobile/xul/installer/package-manifest.in
netwerk/protocol/http/SpdySession.cpp
netwerk/protocol/http/nsAHttpTransaction.h
netwerk/protocol/http/nsHttpChannelAuthProvider.cpp
netwerk/protocol/http/nsHttpConnection.cpp
netwerk/protocol/http/nsHttpConnection.h
netwerk/protocol/http/nsHttpPipeline.cpp
netwerk/protocol/http/nsHttpTransaction.cpp
testing/testsuite-targets.mk
toolkit/components/places/tests/head_common.js
toolkit/content/widgets/videocontrols.css
toolkit/mozapps/installer/packager.mk
widget/android/nsAppShell.cpp
widget/cocoa/nsMenuItemX.h
widget/cocoa/nsMenuItemX.mm
widget/windows/nsNativeThemeWin.cpp
widget/windows/nsUXThemeData.cpp
widget/windows/nsUXThemeData.h
widget/windows/nsWindow.cpp
xpcom/glue/nsCycleCollectionParticipant.h
--- a/accessible/src/html/nsHyperTextAccessible.cpp
+++ b/accessible/src/html/nsHyperTextAccessible.cpp
@@ -1626,16 +1626,19 @@ nsHyperTextAccessible::SetCaretOffset(PR
  * Gets the offset position of the caret (cursor).
  */
 NS_IMETHODIMP
 nsHyperTextAccessible::GetCaretOffset(PRInt32 *aCaretOffset)
 {
   NS_ENSURE_ARG_POINTER(aCaretOffset);
   *aCaretOffset = -1;
 
+  if (IsDefunct())
+    return NS_ERROR_FAILURE;
+
   // Not focused focusable accessible except document accessible doesn't have
   // a caret.
   if (!IsDoc() && !FocusMgr()->IsFocused(this) &&
       (State() & states::FOCUSABLE)) {
     return NS_OK;
   }
 
   // No caret if the focused node is not inside this DOM node and this DOM node
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -146,16 +146,19 @@
 @BINPATH@/components/dom.xpt
 @BINPATH@/components/dom_base.xpt
 #ifdef MOZ_B2G_RIL
 @BINPATH@/components/dom_telephony.xpt
 @BINPATH@/components/dom_wifi.xpt
 @BINPATH@/components/dom_system_b2g.xpt
 #endif
 @BINPATH@/components/dom_battery.xpt
+#ifdef MOZ_B2G_BT
+@BINPATH@/components/dom_bluetooth.xpt
+#endif
 @BINPATH@/components/dom_canvas.xpt
 @BINPATH@/components/dom_core.xpt
 @BINPATH@/components/dom_css.xpt
 @BINPATH@/components/dom_events.xpt
 @BINPATH@/components/dom_geolocation.xpt
 @BINPATH@/components/dom_network.xpt
 @BINPATH@/components/dom_notification.xpt
 @BINPATH@/components/dom_html.xpt
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -352,16 +352,19 @@ pref("browser.search.log", false);
 // Ordering of Search Engines in the Engine list. 
 pref("browser.search.order.1",                "chrome://browser-region/locale/region.properties");
 pref("browser.search.order.2",                "chrome://browser-region/locale/region.properties");
 pref("browser.search.order.3",                "chrome://browser-region/locale/region.properties");
 
 // search bar results always open in a new tab
 pref("browser.search.openintab", false);
 
+// context menu searches open in the foreground
+pref("browser.search.context.loadInBackground", false);
+
 // send ping to the server to update
 pref("browser.search.update", true);
 
 // disable logging for the search service update system by default
 pref("browser.search.update.log", false);
 
 // Check whether we need to perform engine updates every 6 hours
 pref("browser.search.update.interval", 21600);
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -3550,20 +3550,21 @@ const BrowserSearch = {
 
     // getSubmission can return null if the engine doesn't have a URL
     // with a text/html response type.  This is unlikely (since
     // SearchService._addEngineToStore() should fail for such an engine),
     // but let's be on the safe side.
     if (!submission)
       return;
 
+    let inBackground = Services.prefs.getBoolPref("browser.search.context.loadInBackground");
     openLinkIn(submission.uri.spec,
                useNewTab ? "tab" : "current",
                { postData: submission.postData,
-                 inBackground: false,
+                 inBackground: inBackground,
                  relatedToCurrent: true });
   },
 
   /**
    * Returns the search bar element if it is present in the toolbar, null otherwise.
    */
   get searchBar() {
     return document.getElementById("searchbar");
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -531,17 +531,18 @@
                 // cancelled a pending load which would have cleared
                 // its anchor scroll detection temporary increment.
                 if (aWebProgress.DOMWindow == this.mBrowser.contentWindow)
                   this.mBrowser.userTypedClear += 2;
 
                 if (!this.mBlank) {
                   if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING)) {
                     this.mTab.setAttribute("busy", "true");
-                    this.mTabBrowser.setTabTitleLoading(this.mTab);
+                    if (!(this.mBrowser.docShell.loadType & Ci.nsIDocShell.LOAD_CMD_RELOAD))
+                      this.mTabBrowser.setTabTitleLoading(this.mTab);
                   }
 
                   if (this.mTab.selected)
                     this.mTabBrowser.mIsBusy = true;
                 }
               }
               else if (aStateFlags & nsIWebProgressListener.STATE_STOP &&
                        aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
@@ -3560,18 +3561,20 @@
             } catch(ex) {
               // Just ignore invalid urls
             }
           }
         }
 
         // these offsets are only used in dragend, but we need to free them here
         // as well
-        delete draggedTab._dragOffsetX;
-        delete draggedTab._dragOffsetY;
+        if (draggedTab) {
+          delete draggedTab._dragOffsetX;
+          delete draggedTab._dragOffsetY;
+        }
       ]]></handler>
 
       <handler event="dragend"><![CDATA[
         // Note: while this case is correctly handled here, this event
         // isn't dispatched when the tab is moved within the tabstrip,
         // see bug 460801.
 
         // * mozUserCancelled = the user pressed ESC to cancel the drag
@@ -3976,34 +3979,28 @@
                    xbl:inherits="value=label,crop,mirror"
                    flex="1"
                    crop="end"/>
       </xul:hbox>
     </content>
 
     <implementation implements="nsIDOMEventListener">
       <constructor><![CDATA[
-        window.addEventListener("findbaropen", this, false);
         window.addEventListener("resize", this, false);
       ]]></constructor>
 
       <destructor><![CDATA[
-        window.removeEventListener("findbaropen", this, false);
         window.removeEventListener("resize", this, false);
         MousePosTracker.removeListener(this);
       ]]></destructor>
 
       <property name="label">
         <setter><![CDATA[
-          if (!this.label) {
-            if (window.gFindBarInitialized && !window.gFindBar.hidden)
-              this.setAttribute("mirror", "true");
-            else
-              this.removeAttribute("mirror");
-          }
+          if (!this.label)
+            this.removeAttribute("mirror");
 
           this.style.minWidth = this.getAttribute("type") == "status" &&
                                 this.getAttribute("previoustype") == "status"
                                   ? getComputedStyle(this).width : "";
 
           if (val) {
             this.setAttribute("label", val);
             this.removeAttribute("inactive");
@@ -4041,30 +4038,26 @@
 
       <method name="handleEvent">
         <parameter name="event"/>
         <body><![CDATA[
           if (!this.label)
             return;
 
           switch (event.type) {
-            case "findbaropen":
-              this.setAttribute("mirror", "true");
-              this._calcMouseTargetRect();
-              break;
             case "resize":
               this._calcMouseTargetRect();
               break;
           }
         ]]></body>
       </method>
 
       <method name="_calcMouseTargetRect">
         <body><![CDATA[
-          let alignRight = (window.gFindBarInitialized && !window.gFindBar.hidden);
+          let alignRight = false;
 
           if (getComputedStyle(document.documentElement).direction == "rtl")
             alighRight = !alignRight;
 
           let rect = this.getBoundingClientRect();
           this._mouseTargetRect = {
             top:    rect.top,
             bottom: rect.bottom,
--- a/browser/base/content/test/authenticate.sjs
+++ b/browser/base/content/test/authenticate.sjs
@@ -10,34 +10,36 @@ function handleRequest(request, response
 
 
 function reallyHandleRequest(request, response) {
   var match;
   var requestAuth = true, requestProxyAuth = true;
 
   // Allow the caller to drive how authentication is processed via the query.
   // Eg, http://localhost:8888/authenticate.sjs?user=foo&realm=bar
-  var query = request.queryString;
+  // The extra ? allows the user/pass/realm checks to succeed if the name is
+  // at the beginning of the query string.
+  var query = "?" + request.queryString;
 
   var expected_user = "", expected_pass = "", realm = "mochitest";
   var proxy_expected_user = "", proxy_expected_pass = "", proxy_realm = "mochi-proxy";
-  var huge = false, plugin = false;
+  var huge = false, plugin = false, anonymous = false;
   var authHeaderCount = 1;
   // user=xxx
-  match = /user=([^&]*)/.exec(query);
+  match = /[^_]user=([^&]*)/.exec(query);
   if (match)
     expected_user = match[1];
 
   // pass=xxx
-  match = /pass=([^&]*)/.exec(query);
+  match = /[^_]pass=([^&]*)/.exec(query);
   if (match)
     expected_pass = match[1];
 
   // realm=xxx
-  match = /realm=([^&]*)/.exec(query);
+  match = /[^_]realm=([^&]*)/.exec(query);
   if (match)
     realm = match[1];
 
   // proxy_user=xxx
   match = /proxy_user=([^&]*)/.exec(query);
   if (match)
     proxy_expected_user = match[1];
 
@@ -61,26 +63,31 @@ function reallyHandleRequest(request, re
   if (match)
     plugin = true;
 
   // multiple=1
   match = /multiple=([^&]*)/.exec(query);
   if (match)
     authHeaderCount = match[1]+0;
 
+  // anonymous=1
+  match = /anonymous=1/.exec(query);
+  if (match)
+    anonymous = true;
 
   // Look for an authentication header, if any, in the request.
   //
   // EG: Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
   // 
   // This test only supports Basic auth. The value sent by the client is
   // "username:password", obscured with base64 encoding.
 
-  var actual_user = "", actual_pass = "", authHeader;
+  var actual_user = "", actual_pass = "", authHeader, authPresent = false;
   if (request.hasHeader("Authorization")) {
+    authPresent = true;
     authHeader = request.getHeader("Authorization");
     match = /Basic (.+)/.exec(authHeader);
     if (match.length != 2)
         throw "Couldn't parse auth header: " + authHeader;
 
     var userpass = base64ToString(match[1]); // no atob() :-(
     match = /(.*):(.*)/.exec(userpass);
     if (match.length != 3)
@@ -110,26 +117,34 @@ function reallyHandleRequest(request, re
     expected_pass == actual_pass) {
     requestAuth = false;
   }
   if (proxy_expected_user == proxy_actual_user &&
     proxy_expected_pass == proxy_actual_pass) {
     requestProxyAuth = false;
   }
 
-  if (requestProxyAuth) {
-    response.setStatusLine("1.0", 407, "Proxy authentication required");
-    for (i = 0; i < authHeaderCount; ++i)
-      response.setHeader("Proxy-Authenticate", "basic realm=\"" + proxy_realm + "\"", true);
-  } else if (requestAuth) {
-    response.setStatusLine("1.0", 401, "Authentication required");
-    for (i = 0; i < authHeaderCount; ++i)
-      response.setHeader("WWW-Authenticate", "basic realm=\"" + realm + "\"", true);
+  if (anonymous) {
+    if (authPresent) {
+      response.setStatusLine("1.0", 400, "Unexpected authorization header found");
+    } else {
+      response.setStatusLine("1.0", 200, "Authorization header not found");
+    }
   } else {
-    response.setStatusLine("1.0", 200, "OK");
+    if (requestProxyAuth) {
+      response.setStatusLine("1.0", 407, "Proxy authentication required");
+      for (i = 0; i < authHeaderCount; ++i)
+        response.setHeader("Proxy-Authenticate", "basic realm=\"" + proxy_realm + "\"", true);
+    } else if (requestAuth) {
+      response.setStatusLine("1.0", 401, "Authentication required");
+      for (i = 0; i < authHeaderCount; ++i)
+        response.setHeader("WWW-Authenticate", "basic realm=\"" + realm + "\"", true);
+    } else {
+      response.setStatusLine("1.0", 200, "OK");
+    }
   }
 
   response.setHeader("Content-Type", "application/xhtml+xml", false);
   response.write("<html xmlns='http://www.w3.org/1999/xhtml'>");
   response.write("<p>Login: <span id='ok'>" + (requestAuth ? "FAIL" : "PASS") + "</span></p>\n");
   response.write("<p>Proxy: <span id='proxy'>" + (requestProxyAuth ? "FAIL" : "PASS") + "</span></p>\n");
   response.write("<p>Auth: <span id='auth'>" + authHeader + "</span></p>\n");
   response.write("<p>User: <span id='user'>" + actual_user + "</span></p>\n");
--- a/browser/base/content/test/browser_homeDrop.js
+++ b/browser/base/content/test/browser_homeDrop.js
@@ -23,24 +23,28 @@ function test() {
     ok(true, "dialog appeared in response to home button drop");
     domwindow.document.documentElement.cancelDialog();
     Services.wm.removeListener(dialogListener);
 
     // Now trigger the invalid URI test
     executeSoon(function () {
       let consoleListener = {
         observe: function (m) {
+          info("m: " + m + "\n");
+          info("m.message: " + m.message + "\n");
           if (m.message.indexOf("NS_ERROR_DOM_BAD_URI") > -1) {
-            Services.console.unregisterListener(consoleListener);
             ok(true, "drop was blocked");
             executeSoon(finish);
           }
         }
       }
       Services.console.registerListener(consoleListener);
+      registerCleanupFunction(function () {
+        Services.console.unregisterListener(consoleListener);
+      });
 
       // The drop handler throws an exception when dragging URIs that inherit
       // principal, e.g. javascript:
       expectUncaughtException();
       chromeUtils.synthesizeDrop(homeButton, homeButton, [[{type: "text/plain", data: "javascript:8888"}]], "copy", window, EventUtils);
     })
   });
 
--- a/browser/devtools/debugger/test/browser_dbg_select-line.js
+++ b/browser/devtools/debugger/test/browser_dbg_select-line.js
@@ -38,44 +38,48 @@ function testSelectLine() {
       is(gDebugger.StackFrames.activeThread.state, "paused",
         "Should only be getting stack frames while paused.");
 
       is(gScripts.itemCount, 2, "Found the expected number of scripts.");
 
       ok(gDebugger.editor.getText().search(/debugger/) != -1,
         "The correct script was loaded initially.");
 
-      // getCaretPosition is 0-based.
-      is(gDebugger.editor.getCaretPosition().line, 5,
-         "The correct line is selected.");
+      // Yield control back to the event loop so that the debugger has a
+      // chance to highlight the proper line.
+      executeSoon(function(){
+        // getCaretPosition is 0-based.
+        is(gDebugger.editor.getCaretPosition().line, 5,
+           "The correct line is selected.");
 
-      gDebugger.editor.addEventListener(SourceEditor.EVENTS.TEXT_CHANGED,
-                                        function onChange() {
-        gDebugger.editor.removeEventListener(SourceEditor.EVENTS.TEXT_CHANGED,
-                                             onChange);
-        ok(gDebugger.editor.getText().search(/debugger/) == -1,
-          "The second script is no longer displayed.");
+        gDebugger.editor.addEventListener(SourceEditor.EVENTS.TEXT_CHANGED,
+                                          function onChange() {
+          gDebugger.editor.removeEventListener(SourceEditor.EVENTS.TEXT_CHANGED,
+                                               onChange);
+          ok(gDebugger.editor.getText().search(/debugger/) == -1,
+            "The second script is no longer displayed.");
 
-        ok(gDebugger.editor.getText().search(/firstCall/) != -1,
-          "The first script is displayed.");
+          ok(gDebugger.editor.getText().search(/firstCall/) != -1,
+            "The first script is displayed.");
 
-        // Yield control back to the event loop so that the debugger has a
-        // chance to highlight the proper line.
-        executeSoon(function(){
-          // getCaretPosition is 0-based.
-          is(gDebugger.editor.getCaretPosition().line, 4,
-             "The correct line is selected.");
+          // Yield control back to the event loop so that the debugger has a
+          // chance to highlight the proper line.
+          executeSoon(function(){
+            // getCaretPosition is 0-based.
+            is(gDebugger.editor.getCaretPosition().line, 4,
+               "The correct line is selected.");
 
-          gDebugger.StackFrames.activeThread.resume(function() {
-            removeTab(gTab);
-            finish();
+            gDebugger.StackFrames.activeThread.resume(function() {
+              removeTab(gTab);
+              finish();
+            });
           });
         });
+
+        // Click the oldest stack frame.
+        let element = gDebugger.document.getElementById("stackframe-3");
+        EventUtils.synthesizeMouseAtCenter(element, {}, gDebugger);
       });
-
-      // Click the oldest stack frame.
-      let element = gDebugger.document.getElementById("stackframe-3");
-      EventUtils.synthesizeMouseAtCenter(element, {}, gDebugger);
     }}, 0);
   });
 
   gDebuggee.firstCall();
 }
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -145,16 +145,19 @@
 @BINPATH@/components/dom.xpt
 @BINPATH@/components/dom_base.xpt
 #ifdef MOZ_B2G_RIL
 @BINPATH@/components/dom_telephony.xpt
 @BINPATH@/components/dom_wifi.xpt
 @BINPATH@/components/dom_system_b2g.xpt
 #endif
 @BINPATH@/components/dom_battery.xpt
+#ifdef MOZ_B2G_BT
+@BINPATH@/components/dom_bluetooth.xpt
+#endif
 @BINPATH@/components/dom_canvas.xpt
 @BINPATH@/components/dom_core.xpt
 @BINPATH@/components/dom_css.xpt
 @BINPATH@/components/dom_events.xpt
 @BINPATH@/components/dom_geolocation.xpt
 @BINPATH@/components/dom_network.xpt
 @BINPATH@/components/dom_notification.xpt
 @BINPATH@/components/dom_html.xpt
@@ -393,16 +396,18 @@
 @BINPATH@/components/nsPrompter.manifest
 @BINPATH@/components/nsPrompter.js
 #ifdef MOZ_SERVICES_SYNC
 @BINPATH@/components/SyncComponents.manifest
 @BINPATH@/components/Weave.js
 #endif
 @BINPATH@/components/TelemetryPing.js
 @BINPATH@/components/TelemetryPing.manifest
+@BINPATH@/components/messageWakeupService.js
+@BINPATH@/components/messageWakeupService.manifest
 
 ; Modules
 @BINPATH@/modules/*
 
 ; Safe Browsing
 @BINPATH@/components/nsSafebrowsingApplication.manifest
 @BINPATH@/components/nsSafebrowsingApplication.js
 @BINPATH@/components/nsURLClassifier.manifest
deleted file mode 100644
--- a/browser/locales/en-US/searchplugins/google-params.inc
+++ /dev/null
@@ -1,15 +0,0 @@
-  <Param name="q" value="{searchTerms}"/>
-  <Param name="ie" value="utf-8"/>
-  <Param name="oe" value="utf-8"/>
-  <Param name="aq" value="t"/>
-  <!-- Dynamic parameters -->
-  <Param name="rls" value="{moz:distributionID}:{moz:locale}:{moz:official}"/>
-#if MOZ_UPDATE_CHANNEL == beta
-  <MozParam name="client" condition="defaultEngine" trueValue="firefox-beta" falseValue="firefox"/>
-#elif MOZ_UPDATE_CHANNEL == aurora
-  <MozParam name="client" condition="defaultEngine" trueValue="firefox-aurora" falseValue="firefox"/>
-#elif MOZ_UPDATE_CHANNEL == nightly
-  <MozParam name="client" condition="defaultEngine" trueValue="firefox-nightly" falseValue="firefox"/>
-#else
-  <MozParam name="client" condition="defaultEngine" trueValue="firefox-a" falseValue="firefox"/>
-#endif
--- a/browser/locales/en-US/searchplugins/google.xml
+++ b/browser/locales/en-US/searchplugins/google.xml
@@ -1,21 +1,34 @@
+#define GOOGLE_PARAMS <Param name="q" value="{searchTerms}"/><Param name="ie" value="utf-8"/><Param name="oe" value="utf-8"/><Param name="aq" value="t"/><Param name="rls" value="{moz:distributionID}:{moz:locale}:{moz:official}"/>
+#if MOZ_UPDATE_CHANNEL == beta
+#define GOOGLE_CLIENT_PARAM <MozParam name="client" condition="defaultEngine" trueValue="firefox-beta" falseValue="firefox"/>
+#elif MOZ_UPDATE_CHANNEL == aurora
+#define GOOGLE_CLIENT_PARAM <MozParam name="client" condition="defaultEngine" trueValue="firefox-aurora" falseValue="firefox"/>
+#elif MOZ_UPDATE_CHANNEL == nightly
+#define GOOGLE_CLIENT_PARAM <MozParam name="client" condition="defaultEngine" trueValue="firefox-nightly" falseValue="firefox"/>
+#else
+#define GOOGLE_CLIENT_PARAM <MozParam name="client" condition="defaultEngine" trueValue="firefox-a" falseValue="firefox"/>
+#endif
 <SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
 <ShortName>Google</ShortName>
 <Description>Google Search</Description>
 <InputEncoding>UTF-8</InputEncoding>
 <Image width="16" height="16">data:image/png;base64,AAABAAEAEBAAAAEAGABoAwAAFgAAACgAAAAQAAAAIAAAAAEAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADs9Pt8xetPtu9FsfFNtu%2BTzvb2%2B%2Fne4dFJeBw0egA%2FfAJAfAA8ewBBegAAAAD%2B%2FPtft98Mp%2BwWsfAVsvEbs%2FQeqvF8xO7%2F%2F%2F63yqkxdgM7gwE%2FggM%2BfQA%2BegBDeQDe7PIbotgQufcMufEPtfIPsvAbs%2FQvq%2Bfz%2Bf%2F%2B%2B%2FZKhR05hgBBhQI8hgBAgAI9ewD0%2B%2Fg3pswAtO8Cxf4Kw%2FsJvvYAqupKsNv%2B%2Fv7%2F%2FP5VkSU0iQA7jQA9hgBDgQU%2BfQH%2F%2Ff%2FQ6fM4sM4KsN8AteMCruIqqdbZ7PH8%2Fv%2Fg6Nc%2Fhg05kAA8jAM9iQI%2BhQA%2BgQDQu6b97uv%2F%2F%2F7V8Pqw3eiWz97q8%2Ff%2F%2F%2F%2F7%2FPptpkkqjQE4kwA7kAA5iwI8iAA8hQCOSSKdXjiyflbAkG7u2s%2F%2B%2F%2F39%2F%2F7r8utrqEYtjQE8lgA7kwA7kwA9jwA9igA9hACiWSekVRyeSgiYSBHx6N%2F%2B%2Fv7k7OFRmiYtlAA5lwI7lwI4lAA7kgI9jwE9iwI4iQCoVhWcTxCmb0K%2BooT8%2Fv%2F7%2F%2F%2FJ2r8fdwI1mwA3mQA3mgA8lAE8lAE4jwA9iwE%2BhwGfXifWvqz%2B%2Ff%2F58u%2Fev6Dt4tr%2B%2F%2F2ZuIUsggA7mgM6mAM3lgA5lgA6kQE%2FkwBChwHt4dv%2F%2F%2F728ei1bCi7VAC5XQ7kz7n%2F%2F%2F6bsZkgcB03lQA9lgM7kwA2iQktZToPK4r9%2F%2F%2F9%2F%2F%2FSqYK5UwDKZAS9WALIkFn%2B%2F%2F3%2F%2BP8oKccGGcIRJrERILYFEMwAAuEAAdX%2F%2Ff7%2F%2FP%2B%2BfDvGXQLIZgLEWgLOjlf7%2F%2F%2F%2F%2F%2F9QU90EAPQAAf8DAP0AAfMAAOUDAtr%2F%2F%2F%2F7%2B%2Fu2bCTIYwDPZgDBWQDSr4P%2F%2Fv%2F%2F%2FP5GRuABAPkAA%2FwBAfkDAPAAAesAAN%2F%2F%2B%2Fz%2F%2F%2F64g1C5VwDMYwK8Yg7y5tz8%2Fv%2FV1PYKDOcAAP0DAf4AAf0AAfYEAOwAAuAAAAD%2F%2FPvi28ymXyChTATRrIb8%2F%2F3v8fk6P8MAAdUCAvoAAP0CAP0AAfYAAO4AAACAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQAA</Image>
 <Url type="application/x-suggestions+json" method="GET" template="http://suggestqueries.google.com/complete/search?output=firefox&amp;client=firefox&amp;hl={moz:locale}&amp;q={searchTerms}"/>
 <Url type="text/html" method="GET" template="http://www.google.com/search">
-#include google-params.inc
+#expand   __GOOGLE_PARAMS__
+#expand   __GOOGLE_CLIENT_PARAM__
 </Url>
 <!-- Keyword search URL is the same as the default, but with an additional parameter -->
 <Url type="application/x-moz-keywordsearch" method="GET" template="http://www.google.com/search">
-#include google-params.inc
+#expand   __GOOGLE_PARAMS__
+#expand   __GOOGLE_CLIENT_PARAM__
   <Param name="channel" value="fflb"/>
 </Url>
 <!-- Context/Right-click search URL is the same as the default, but with an additional parameter -->
 <Url type="application/x-moz-contextsearch" method="GET" template="http://www.google.com/search">
-#include google-params.inc
+#expand   __GOOGLE_PARAMS__
+#expand   __GOOGLE_CLIENT_PARAM__
   <Param name="channel" value="rcs"/>
 </Url>
 <SearchForm>http://www.google.com/</SearchForm>
 </SearchPlugin>
--- a/build/mobile/robocop/Actions.java.in
+++ b/build/mobile/robocop/Actions.java.in
@@ -36,48 +36,60 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 package @ANDROID_PACKAGE_NAME@;
 import java.util.List;
 
 public interface Actions {
-  public enum SpecialKey {
-    DOWN, UP, LEFT, RIGHT, ENTER, MENU, BACK
-  }
+
+    /** Special keys supported by sendSpecialKey() */
+    public enum SpecialKey {
+        DOWN, UP, LEFT, RIGHT, ENTER, MENU, BACK
+    }
 
-  public interface EventExpecter {
-    /** Blocks until the event has been received. Subsequent calls will return immediately. */
-    public void blockForEvent();
-    /** Polls to see if the event has been received. Once this returns true, subsequent calls will also return true. */
-    public boolean eventReceived();
-  }
+    public interface EventExpecter {
+        /** Blocks until the event has been received. Subsequent calls will return immediately. */
+        public void blockForEvent();
 
-  public interface RepeatedEventExpecter extends EventExpecter {
-    /** Blocks until at least one event has been received, and no events have been received in the last <code>millis</code> milliseconds. */
-    public void blockUntilClear(long millis);
-  }
+        /** Polls to see if the event has been received. Once this returns true, subsequent calls will also return true. */
+        public boolean eventReceived();
+    }
+
+    public interface RepeatedEventExpecter extends EventExpecter {
+        /** Blocks until at least one event has been received, and no events have been received in the last <code>millis</code> milliseconds. */
+        public void blockUntilClear(long millis);
+    }
 
-  /**
-   * Listens for a gecko event to be sent from the Gecko instance.
-   * The returned object can be used to test if the event has been
-   * received. Note that only one event is listened for.
-   * 
-   * @param geckoEvent The geckoEvent JSONObject's type
-   */
-  EventExpecter expectGeckoEvent(String geckoEvent);
+    /**
+     * Listens for a gecko event to be sent from the Gecko instance.
+     * The returned object can be used to test if the event has been
+     * received. Note that only one event is listened for.
+     * 
+     * @param geckoEvent The geckoEvent JSONObject's type
+     */
+    EventExpecter expectGeckoEvent(String geckoEvent);
+
+    /**
+     * Listens for a paint event. Note that calling expectPaint() will
+     * invalidate the event expecters returned from any previous calls
+     * to expectPaint(); calling any methods on those invalidated objects
+     * will result in undefined behaviour.
+     */
+    RepeatedEventExpecter expectPaint();
 
-  /**
-   * Listens for a paint event. Note that calling expectPaint() will
-   * invalidate the event expecters returned from any previous calls
-   * to expectPaint(); calling any methods on those invalidated objects
-   * will result in undefined behaviour.
-   */
-  RepeatedEventExpecter expectPaint();
+    /** 
+     * Send a string to the application 
+     * 
+     * @param keysToSend The string to send
+     */
+    void sendKeys(String keysToSend);
 
-  // Send the string kewsToSend to the application 
-  void sendKeys(String keysToSend);
-  //Send any of the above keys to the element
-  void sendSpecialKey(SpecialKey button);
+    /**
+     * Send a special keycode to the element
+     *
+     * @param key The special key to send
+     */
+    void sendSpecialKey(SpecialKey key);
 
-  void drag(int startingX, int endingX, int startingY, int endingY);
+    void drag(int startingX, int endingX, int startingY, int endingY);
 }
--- a/build/mobile/robocop/Assert.java.in
+++ b/build/mobile/robocop/Assert.java.in
@@ -35,24 +35,24 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 package @ANDROID_PACKAGE_NAME@;
 
 public interface Assert {
-  void dumpLog(String message);
-  void setLogFile(String filename);
-  void setTestName(String testName);
+    void dumpLog(String message);
+    void setLogFile(String filename);
+    void setTestName(String testName);
 
-  void finalize();
-  void ok(boolean condition, String name, String diag);
-  void is(Object a, Object b, String name);
-  void isnot(Object a, Object b, String name);
-  void todo(boolean condition, String name, String diag);
-  void todo_is(Object a, Object b, String name);
-  void todo_isnot(Object a, Object b, String name);
-  void info(String name, String message);
+    void finalize();
+    void ok(boolean condition, String name, String diag);
+    void is(Object a, Object b, String name);
+    void isnot(Object a, Object b, String name);
+    void todo(boolean condition, String name, String diag);
+    void todo_is(Object a, Object b, String name);
+    void todo_isnot(Object a, Object b, String name);
+    void info(String name, String message);
 
-  // robocop-specific asserts
-  void ispixel(int actual, int r, int g, int b, String name);
+    // robocop-specific asserts
+    void ispixel(int actual, int r, int g, int b, String name);
 }
--- a/build/mobile/robocop/Driver.java.in
+++ b/build/mobile/robocop/Driver.java.in
@@ -38,44 +38,44 @@
  * ***** END LICENSE BLOCK ***** */
 
 package @ANDROID_PACKAGE_NAME@;
 
 import java.util.List;
 import android.app.Activity;
 
 public interface Driver {
-  /**
-   * Find the first Element using the given method.
-   * 
-   * @param activity The activity the element belongs to
-   * @param name The name of the element
-   * @return The first matching element on the current context
-   * @throws RoboCopException If no matching elements are found
-   */
-  Element findElement(Activity activity, String name);
+    /**
+     * Find the first Element using the given method.
+     * 
+     * @param activity The activity the element belongs to
+     * @param name The name of the element
+     * @return The first matching element on the current context
+     * @throws RoboCopException If no matching elements are found
+     */
+    Element findElement(Activity activity, String name);
 
-  /**
-   * Sets up scroll handling so that data is received from the extension.
-   */
-  void setupScrollHandling();
+    /**
+     * Sets up scroll handling so that data is received from the extension.
+     */
+    void setupScrollHandling();
 
-  int getPageHeight();
-  int getScrollHeight();
-  int getHeight();
-  int getGeckoTop();
-  int getGeckoLeft();
-  int getGeckoWidth();
-  int getGeckoHeight();
+    int getPageHeight();
+    int getScrollHeight();
+    int getHeight();
+    int getGeckoTop();
+    int getGeckoLeft();
+    int getGeckoWidth();
+    int getGeckoHeight();
 
-  void startFrameRecording();
-  int stopFrameRecording();
+    void startFrameRecording();
+    int stopFrameRecording();
 
-  void startCheckerboardRecording();
-  float stopCheckerboardRecording();
+    void startCheckerboardRecording();
+    float stopCheckerboardRecording();
 
-  /**
-   * Get a copy of the painted content region.
-   * @return A 2-D array of pixels (indexed by y, then x). The pixels
-   * are in ARGB-8888 format.
-   */
-  int[][] getPaintedSurface();
+    /**
+     * Get a copy of the painted content region.
+     * @return A 2-D array of pixels (indexed by y, then x). The pixels
+     * are in ARGB-8888 format.
+     */
+    int[][] getPaintedSurface();
 }
--- a/build/mobile/robocop/Element.java.in
+++ b/build/mobile/robocop/Element.java.in
@@ -34,18 +34,26 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 package @ANDROID_PACKAGE_NAME@;
 
+/** 
+ *  Element provides access to a specific UI view (android.view.View). 
+ *  See also Driver.findElement().
+ */
 public interface Element {
-  //Click on the element
-  void click();
-  //Returns true if the element is currently displayed
-  boolean isDisplayed();
-  //Returns the text currently displayed on the element.
-  String getText();
-  //Returns view ID.
-  Integer getId();
+
+    /** Click on the element */
+    void click();
+
+    /** Returns true if the element is currently displayed */
+    boolean isDisplayed();
+
+    /** Returns the text currently displayed on the element */
+    String getText();
+
+    /** Returns the view ID */
+    Integer getId();
 }
--- a/build/mobile/robocop/FennecMochitestAssert.java.in
+++ b/build/mobile/robocop/FennecMochitestAssert.java.in
@@ -33,207 +33,199 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 package @ANDROID_PACKAGE_NAME@;
 
 import java.util.LinkedList;
-import java.util.List;
-import java.util.Date;
+import android.os.SystemClock;
 
 public class FennecMochitestAssert implements Assert {
-  // Objects for reflexive access of fennec classes.
-
-  private LinkedList<testInfo> testList = new LinkedList<testInfo>();
+    private LinkedList<testInfo> mTestList = new LinkedList<testInfo>();
 
-  // Internal state variables to make logging match up with existing mochitests
-  private int lineNumber = 0;
-  private int passed = 0;
-  private int failed = 0;
-  private int todo = 0;
-  
-  // Used to write the first line of the test file
-  private boolean logStarted = false;
-
-  // Used to write the test-start/test-end log lines
-  private String logTestName = "";
-
-  // Measure the time it takes to run test case
-  private long startTime = 0;
+    // Internal state variables to make logging match up with existing mochitests
+    private int mLineNumber = 0;
+    private int mPassed = 0;
+    private int mFailed = 0;
+    private int mTodo = 0;
+    
+    // Used to write the first line of the test file
+    private boolean mLogStarted = false;
 
-  public FennecMochitestAssert() {
-  }
+    // Used to write the test-start/test-end log lines
+    private String mLogTestName = "";
 
-  // Write information to a logfile and logcat
-  public void dumpLog(String message)
-  {
-    FennecNativeDriver.log(FennecNativeDriver.LogLevel.LOG_LEVEL_INFO, message);
-  }
+    // Measure the time it takes to run test case
+    private long mStartTime = 0;
 
-  // Set the filename used for dumpLog.
-  public void setLogFile(String filename)
-  {
-    FennecNativeDriver.setLogFile(filename);
+    public FennecMochitestAssert() {
+    }
 
-    String message;
-    if (!logStarted) {
-      dumpLog(Integer.toString(lineNumber++) + " INFO SimpleTest START");
-      logStarted = true;
+    /** Write information to a logfile and logcat */
+    public void dumpLog(String message) {
+        FennecNativeDriver.log(FennecNativeDriver.LogLevel.LOG_LEVEL_INFO, message);
     }
 
-    if (logTestName != "") {
-      long diff = (new Date().getTime()) - startTime;
-      message = Integer.toString(lineNumber++) + " INFO TEST-END | " + logTestName;
-      message += " | finished in " + diff + "ms";
-      dumpLog(message);
-      logTestName = "";
+    /** Set the filename used for dumpLog. */
+    public void setLogFile(String filename) {
+        FennecNativeDriver.setLogFile(filename);
+
+        String message;
+        if (!mLogStarted) {
+            dumpLog(Integer.toString(mLineNumber++) + " INFO SimpleTest START");
+            mLogStarted = true;
+        }
+
+        if (mLogTestName != "") {
+            long diff = SystemClock.uptimeMillis() - mStartTime;
+            message = Integer.toString(mLineNumber++) + " INFO TEST-END | " + mLogTestName;
+            message += " | finished in " + diff + "ms";
+            dumpLog(message);
+            mLogTestName = "";
+        }
     }
-  }
 
-  public void setTestName(String testName)
-  {
-    String[] nameParts = testName.split("\\.");
-    logTestName = nameParts[nameParts.length - 1];
-    startTime = new Date().getTime();
+    public void setTestName(String testName) {
+        String[] nameParts = testName.split("\\.");
+        mLogTestName = nameParts[nameParts.length - 1];
+        mStartTime = SystemClock.uptimeMillis();
 
-    dumpLog(Integer.toString(lineNumber++) + " INFO TEST-START | " + logTestName);
-  }
+        dumpLog(Integer.toString(mLineNumber++) + " INFO TEST-START | " + mLogTestName);
+    }
 
-  class testInfo {
-    public boolean result;
-    public String name;
-    public String diag;
-    public boolean todo;
-    public testInfo(boolean r, String n, String d, boolean t) {
-      result = r;
-      name = n;
-      diag = d;
-      todo = t;
+    class testInfo {
+        public boolean mResult;
+        public String mName;
+        public String mDiag;
+        public boolean mTodo;
+        public testInfo(boolean r, String n, String d, boolean t) {
+            mResult = r;
+            mName = n;
+            mDiag = d;
+            mTodo = t;
+        }
+
     }
 
-  }
+    private void _logMochitestResult(testInfo test, String passString, String failString) {
+        boolean isError = true;
+        String resultString = failString;
+        if (test.mResult || test.mTodo) {
+            isError = false;
+        }
+        if (test.mResult)
+        {
+            resultString = passString;
+        }
+        String diag = test.mName;
+        if (test.mDiag != null) diag += " - " + test.mDiag;
 
-  private void _logMochitestResult(testInfo test, String passString, String failString)
-  {
-    boolean isError = true;
-    String resultString = failString;
-    if (test.result || test.todo) {
-      isError = false;
+        String message = Integer.toString(mLineNumber++) + " INFO " + resultString + " | " + mLogTestName + " | " + diag;
+        dumpLog(message);
+
+        if (test.mTodo) {
+            mTodo++;
+        } else if (isError) {
+            mFailed++;
+        } else {
+            mPassed++;
+        }
+        if (isError) {
+            junit.framework.Assert.fail(message);
+        }
     }
-    if (test.result)
-    {
-      resultString = passString;
-    }
-    String diag = test.name;
-    if (test.diag != null) diag += " - " + test.diag;
-
-    String message = Integer.toString(lineNumber++) + " INFO " + resultString + " | " + logTestName + " | " + diag;
-    dumpLog(message);
 
-    if (test.todo) {
-      todo++;
-    } else if (isError) {
-      failed++;
-    } else {
-      passed++;
-    }
-    if (isError) {
-      junit.framework.Assert.fail(message);
-    }
-  }
+    public void finalize() {
+        // It appears that we call finalize during cleanup, this might be an invalid assertion.
+        String message;
+
+        if (mLogTestName != "") {
+            long diff = SystemClock.uptimeMillis() - mStartTime;
+            message = Integer.toString(mLineNumber++) + " INFO TEST-END | " + mLogTestName;
+            message += " | finished in " + diff + "ms";
+            dumpLog(message);
+            mLogTestName = "";
+        }
 
-  public void finalize()
-  {
-    // It appears that we call finalize during cleanup, this might be an invalid assertion.
-    String message;
+        message = Integer.toString(mLineNumber++) + " INFO TEST-START | Shutdown";
+        dumpLog(message);
+        message = Integer.toString(mLineNumber++) + " INFO Passed: " + Integer.toString(mPassed);
+        dumpLog(message);
+        message = Integer.toString(mLineNumber++) + " INFO Failed: " + Integer.toString(mFailed);
+        dumpLog(message);
+        message = Integer.toString(mLineNumber++) + " INFO Todo: " + Integer.toString(mTodo);
+        dumpLog(message);
+        message = Integer.toString(mLineNumber++) + " INFO SimpleTest FINISHED";
+        dumpLog(message);
+    }
 
-    if (logTestName != "") {
-      long diff = (new Date().getTime()) - startTime;
-      message = Integer.toString(lineNumber++) + " INFO TEST-END | " + logTestName;
-      message += " | finished in " + diff + "ms";
-      dumpLog(message);
-      logTestName = "";
+    public void ok(boolean condition, String name, String diag) {
+        testInfo test = new testInfo(condition, name, diag, false);
+        _logMochitestResult(test, "TEST-PASS", "TEST-UNEXPECTED-FAIL");
+        mTestList.add(test);
     }
 
-    message = Integer.toString(lineNumber++) + " INFO TEST-START | Shutdown";
-    dumpLog(message);
-    message = Integer.toString(lineNumber++) + " INFO Passed: " + Integer.toString(passed);
-    dumpLog(message);
-    message = Integer.toString(lineNumber++) + " INFO Failed: " + Integer.toString(failed);
-    dumpLog(message);
-    message = Integer.toString(lineNumber++) + " INFO Todo: " + Integer.toString(todo);
-    dumpLog(message);
-    message = Integer.toString(lineNumber++) + " INFO SimpleTest FINISHED";
-    dumpLog(message);
-  }
-
-  public void ok(boolean condition, String name, String diag) {
-    testInfo test = new testInfo(condition, name, diag, false);
-    _logMochitestResult(test, "TEST-PASS", "TEST-UNEXPECTED-FAIL");
-    testList.add(test);
-  }
-
-  public void is(Object a, Object b, String name) {
-    boolean pass = a.equals(b);
-    String diag = "got " + a.toString() + ", expected " + b.toString();
-    if(pass) {
-      diag = a.toString() + " should equal " + b.toString();
+    public void is(Object a, Object b, String name) {
+        boolean pass = a.equals(b);
+        String diag = "got " + a.toString() + ", expected " + b.toString();
+        if (pass) {
+            diag = a.toString() + " should equal " + b.toString();
+        }
+        ok(pass, name, diag);
     }
-    ok(pass, name, diag);
-  }
-  
-  public void isnot(Object a, Object b, String name) {
-    boolean pass = !a.equals(b);
-    String diag = "didn't expect " + a.toString() + ", but got it";
-    if(pass) {
-      diag = a.toString() + " should not equal " + b.toString();
+    
+    public void isnot(Object a, Object b, String name) {
+        boolean pass = !a.equals(b);
+        String diag = "didn't expect " + a.toString() + ", but got it";
+        if (pass) {
+            diag = a.toString() + " should not equal " + b.toString();
+        }
+        ok(pass, name, diag);
     }
-    ok(pass, name, diag);
-  }
 
-  public void ispixel(int actual, int r, int g, int b, String name) {
-    // When we read GL pixels the GPU has already processed them and they
-    // are usually off by a little bit. For example a CSS-color pixel of color #64FFF5
-    // was turned into #63FFF7 when it came out of glReadPixels. So in order to compare
-    // against the expected value, we use a little fuzz factor. For the alpha we just
-    // make sure it is always 0xFF.
-    int aAlpha = ((actual >> 24) & 0xFF);
-    int aR = ((actual >> 16) & 0xFF);
-    int aG = ((actual >> 8) & 0xFF);
-    int aB = (actual & 0xFF);
-    boolean pass = (aAlpha == 0xFF) /* alpha */
-                && (Math.abs(aR - r) < 8) /* red */
-                && (Math.abs(aG - g) < 8) /* green */
-                && (Math.abs(aB - b) < 8); /* blue */
-    ok(pass, name, "Color rgba(" + aR + "," + aG + "," + aB + "," + aAlpha + ")" + (pass ? " " : " not") + " close enough to expected rgb(" + r + "," + g + "," + b + ")");
-  }
+    public void ispixel(int actual, int r, int g, int b, String name) {
+        // When we read GL pixels the GPU has already processed them and they
+        // are usually off by a little bit. For example a CSS-color pixel of color #64FFF5
+        // was turned into #63FFF7 when it came out of glReadPixels. So in order to compare
+        // against the expected value, we use a little fuzz factor. For the alpha we just
+        // make sure it is always 0xFF.
+        int aAlpha = ((actual >> 24) & 0xFF);
+        int aR = ((actual >> 16) & 0xFF);
+        int aG = ((actual >> 8) & 0xFF);
+        int aB = (actual & 0xFF);
+        boolean pass = (aAlpha == 0xFF) /* alpha */
+                                && (Math.abs(aR - r) < 8) /* red */
+                                && (Math.abs(aG - g) < 8) /* green */
+                                && (Math.abs(aB - b) < 8); /* blue */
+        ok(pass, name, "Color rgba(" + aR + "," + aG + "," + aB + "," + aAlpha + ")" + (pass ? " " : " not") + " close enough to expected rgb(" + r + "," + g + "," + b + ")");
+    }
 
-  public void todo(boolean condition, String name, String diag) {
-    testInfo test = new testInfo(condition, name, diag, true);
-    _logMochitestResult(test, "TEST-UNEXPECTED-PASS", "TEST-KNOWN-FAIL");
-    testList.add(test);
-  }
+    public void todo(boolean condition, String name, String diag) {
+        testInfo test = new testInfo(condition, name, diag, true);
+        _logMochitestResult(test, "TEST-UNEXPECTED-PASS", "TEST-KNOWN-FAIL");
+        mTestList.add(test);
+    }
 
-  public void todo_is(Object a, Object b, String name) {
-    boolean pass = a.equals(b);
-    String diag = "got " + a.toString() + ", expected " + b.toString();
-    if(pass) {
-      diag = a.toString() + " should equal " + b.toString();
+    public void todo_is(Object a, Object b, String name) {
+        boolean pass = a.equals(b);
+        String diag = "got " + a.toString() + ", expected " + b.toString();
+        if (pass) {
+            diag = a.toString() + " should equal " + b.toString();
+        }
+        todo(pass, name, diag);
     }
-    todo(pass, name, diag);
-  }
-  
-  public void todo_isnot(Object a, Object b, String name) {
-    boolean pass = !a.equals(b);
-    String diag = "didn't expect " + a.toString() + ", but got it";
-    if(pass) {
-      diag = a.toString() + " should not equal " + b.toString();
+    
+    public void todo_isnot(Object a, Object b, String name) {
+        boolean pass = !a.equals(b);
+        String diag = "didn't expect " + a.toString() + ", but got it";
+        if (pass) {
+            diag = a.toString() + " should not equal " + b.toString();
+        }
+        todo(pass, name, diag);
     }
-    todo(pass, name, diag);
-  }
 
-  public void info(String name, String message) {
-    testInfo test = new testInfo(true, name, message, false);
-    _logMochitestResult(test, "TEST-INFO", "INFO FAILED?");
-  }
+    public void info(String name, String message) {
+        testInfo test = new testInfo(true, name, message, false);
+        _logMochitestResult(test, "TEST-INFO", "INFO FAILED?");
+    }
 }
--- a/build/mobile/robocop/FennecNativeActions.java.in
+++ b/build/mobile/robocop/FennecNativeActions.java.in
@@ -54,300 +54,299 @@ import android.view.KeyEvent;
 
 import java.util.concurrent.SynchronousQueue;
 
 import org.json.*;
 
 import com.jayway.android.robotium.solo.Solo;
 
 public class FennecNativeActions implements Actions {
-  private Solo solo;
-  private Instrumentation instr;
-  private Activity geckoApp;
+    private Solo mSolo;
+    private Instrumentation mInstr;
+    private Activity mGeckoApp;
 
-  // Objects for reflexive access of fennec classes.
-  private ClassLoader classLoader;
-  private Class gel;
-  private Class ge;
-  private Class gas;
-  private Class drawListener;
-  private Method registerGEL;
-  private Method unregisterGEL;
-  private Method sendGE;
-  private Method getLayerClient;
-  private Method setDrawListener;
+    // Objects for reflexive access of fennec classes.
+    private ClassLoader mClassLoader;
+    private Class mGel;
+    private Class mGe;
+    private Class mGas;
+    private Class mDrawListener;
+    private Method mRegisterGEL;
+    private Method mUnregisterGEL;
+    private Method mSendGE;
+    private Method mGetLayerClient;
+    private Method mSetDrawListener;
 
-  public FennecNativeActions(Activity activity, Solo robocop, Instrumentation instrumentation){
-    this.solo = robocop;
-    this.instr = instrumentation;
-    this.geckoApp = activity;
-    // Set up reflexive access of java classes and methods.
-    try {
-      classLoader = activity.getClassLoader();
-      gel = classLoader.loadClass("org.mozilla.gecko.GeckoEventListener");
-      ge = classLoader.loadClass("org.mozilla.gecko.GeckoEvent");
-      gas = classLoader.loadClass("org.mozilla.gecko.GeckoAppShell");
-      Class [] parameters = new Class[2];
-      parameters[0] = String.class;
-      parameters[1] = gel;
-      registerGEL = gas.getMethod("registerGeckoEventListener", parameters);
-      unregisterGEL = gas.getMethod("unregisterGeckoEventListener", parameters);
-      parameters = new Class[1];
-      parameters[0] = ge;
-      sendGE = gas.getMethod("sendEventToGecko", parameters);
+    public FennecNativeActions(Activity activity, Solo robocop, Instrumentation instrumentation) {
+        mSolo = robocop;
+        mInstr = instrumentation;
+        mGeckoApp = activity;
+        // Set up reflexive access of java classes and methods.
+        try {
+            mClassLoader = activity.getClassLoader();
+            mGel = mClassLoader.loadClass("org.mozilla.gecko.GeckoEventListener");
+            mGe = mClassLoader.loadClass("org.mozilla.gecko.GeckoEvent");
+            mGas = mClassLoader.loadClass("org.mozilla.gecko.GeckoAppShell");
+            Class [] parameters = new Class[2];
+            parameters[0] = String.class;
+            parameters[1] = mGel;
+            mRegisterGEL = mGas.getMethod("registerGeckoEventListener", parameters);
+            mUnregisterGEL = mGas.getMethod("unregisterGeckoEventListener", parameters);
+            parameters = new Class[1];
+            parameters[0] = mGe;
+            mSendGE = mGas.getMethod("sendEventToGecko", parameters);
 
-      getLayerClient = activity.getClass().getMethod("getSoftwareLayerClient");
-      Class gslc = classLoader.loadClass("org.mozilla.gecko.gfx.GeckoSoftwareLayerClient");
-      drawListener = classLoader.loadClass("org.mozilla.gecko.gfx.GeckoSoftwareLayerClient$DrawListener");
-      setDrawListener = gslc.getDeclaredMethod("setDrawListener", drawListener);
-     } catch (ClassNotFoundException e) {
-       e.printStackTrace();
-     } catch (SecurityException e) {
-       e.printStackTrace();
-     } catch (NoSuchMethodException e) {
-       e.printStackTrace();
-     } catch (IllegalArgumentException e) {
-       e.printStackTrace();
-     }
-  }
-
-  class wakeInvocationHandler implements InvocationHandler {
-    private final GeckoEventExpecter mEventExpecter;
-
-    public wakeInvocationHandler(GeckoEventExpecter expecter) {
-      mEventExpecter = expecter;
+            mGetLayerClient = activity.getClass().getMethod("getSoftwareLayerClient");
+            Class gslc = mClassLoader.loadClass("org.mozilla.gecko.gfx.GeckoSoftwareLayerClient");
+            mDrawListener = mClassLoader.loadClass("org.mozilla.gecko.gfx.GeckoSoftwareLayerClient$DrawListener");
+            mSetDrawListener = gslc.getDeclaredMethod("setDrawListener", mDrawListener);
+         } catch (ClassNotFoundException e) {
+             e.printStackTrace();
+         } catch (SecurityException e) {
+             e.printStackTrace();
+         } catch (NoSuchMethodException e) {
+             e.printStackTrace();
+         } catch (IllegalArgumentException e) {
+             e.printStackTrace();
+         }
     }
 
-    public Object invoke(Object proxy, Method method, Object[] args) {
-      String methodName = method.getName();
-      //Depending on the method, return a completely different type.
-      if(methodName.equals("toString")) {
-        return "wakeInvocationHandler";
-      }
-      if(methodName.equals("equals")) {
-        return this == args[0];
-      }
-      if(methodName.equals("clone")) {
-        return this;
-      }
-      if(methodName.equals("hashCode")) {
-        return 314;
-      }
-      FennecNativeDriver.log(FennecNativeDriver.LogLevel.LOG_LEVEL_DEBUG, 
-        "Waking up on "+methodName);
-      mEventExpecter.notifyOfEvent();
-      return null;
-    }
-  }
+    class wakeInvocationHandler implements InvocationHandler {
+        private final GeckoEventExpecter mEventExpecter;
+
+        public wakeInvocationHandler(GeckoEventExpecter expecter) {
+            mEventExpecter = expecter;
+        }
 
-  class GeckoEventExpecter implements EventExpecter {
-    private final String mGeckoEvent;
-    private final Object[] mRegistrationParams;
-    private boolean mEventReceived;
-
-    GeckoEventExpecter(String geckoEvent, Object[] registrationParams) {
-      mGeckoEvent = geckoEvent;
-      mRegistrationParams = registrationParams;
-    }
-
-    public synchronized void blockForEvent() {
-      while (! mEventReceived) {
-        try {
-          this.wait();
-        } catch (InterruptedException ie) {
-          ie.printStackTrace();
-          break;
+        public Object invoke(Object proxy, Method method, Object[] args) {
+            String methodName = method.getName();
+            //Depending on the method, return a completely different type.
+            if(methodName.equals("toString")) {
+                return "wakeInvocationHandler";
+            }
+            if(methodName.equals("equals")) {
+                return this == args[0];
+            }
+            if(methodName.equals("clone")) {
+                return this;
+            }
+            if(methodName.equals("hashCode")) {
+                return 314;
+            }
+            FennecNativeDriver.log(FennecNativeDriver.LogLevel.LOG_LEVEL_DEBUG, 
+                "Waking up on "+methodName);
+            mEventExpecter.notifyOfEvent();
+            return null;
         }
-      }
-      FennecNativeDriver.log(FennecNativeDriver.LogLevel.LOG_LEVEL_DEBUG,
-        "unblocked on expecter for " + mGeckoEvent);
-    }
-
-    public synchronized boolean eventReceived() {
-      return mEventReceived;
     }
 
-    void notifyOfEvent() {
-      try {
-        unregisterGEL.invoke(null, mRegistrationParams);
-      } catch (IllegalAccessException e) {
-        e.printStackTrace();
-      } catch (InvocationTargetException e) {
-        e.printStackTrace();
-      }
-      FennecNativeDriver.log(FennecNativeDriver.LogLevel.LOG_LEVEL_DEBUG,
-        "received event " + mGeckoEvent);
-      synchronized (this) {
-        mEventReceived = true;
-        this.notifyAll();
-      }
+    class GeckoEventExpecter implements EventExpecter {
+        private final String mGeckoEvent;
+        private final Object[] mRegistrationParams;
+        private boolean mEventReceived;
+
+        GeckoEventExpecter(String geckoEvent, Object[] registrationParams) {
+            mGeckoEvent = geckoEvent;
+            mRegistrationParams = registrationParams;
+        }
+
+        public synchronized void blockForEvent() {
+            while (! mEventReceived) {
+                try {
+                    this.wait();
+                } catch (InterruptedException ie) {
+                    ie.printStackTrace();
+                    break;
+                }
+            }
+            FennecNativeDriver.log(FennecNativeDriver.LogLevel.LOG_LEVEL_DEBUG,
+                "unblocked on expecter for " + mGeckoEvent);
+        }
+
+        public synchronized boolean eventReceived() {
+            return mEventReceived;
+        }
+
+        void notifyOfEvent() {
+            try {
+                mUnregisterGEL.invoke(null, mRegistrationParams);
+            } catch (IllegalAccessException e) {
+                e.printStackTrace();
+            } catch (InvocationTargetException e) {
+                e.printStackTrace();
+            }
+            FennecNativeDriver.log(FennecNativeDriver.LogLevel.LOG_LEVEL_DEBUG,
+                "received event " + mGeckoEvent);
+            synchronized (this) {
+                mEventReceived = true;
+                this.notifyAll();
+            }
+        }
     }
-  }
-  
-  public EventExpecter expectGeckoEvent(String geckoEvent) {
-    FennecNativeDriver.log(FennecNativeDriver.LogLevel.LOG_LEVEL_DEBUG,
-      "waiting for "+geckoEvent);
-    try {
-      Class [] interfaces = new Class[1];
-      interfaces[0] = gel;
-      Object[] finalParams = new Object[2];
-      finalParams[0] = geckoEvent;
-     
-      GeckoEventExpecter expecter = new GeckoEventExpecter(geckoEvent, finalParams);
-      wakeInvocationHandler wIH = new wakeInvocationHandler(expecter);
-      Object proxy = Proxy.newProxyInstance(classLoader, interfaces, wIH);
-      finalParams[1] = proxy;
-      registerGEL.invoke(null, finalParams);
-      
-      return expecter;
-    } catch (IllegalAccessException e) {
-      e.printStackTrace();
-    } catch (InvocationTargetException e) {
-      e.printStackTrace();
-    }
-    return null;
-  }
-
-  class DrawListenerProxy implements InvocationHandler {
-    private final PaintExpecter mPaintExpecter;
-
-    DrawListenerProxy(PaintExpecter paintExpecter) {
-      mPaintExpecter = paintExpecter;
+    
+    public EventExpecter expectGeckoEvent(String geckoEvent) {
+        FennecNativeDriver.log(FennecNativeDriver.LogLevel.LOG_LEVEL_DEBUG,
+            "waiting for "+geckoEvent);
+        try {
+            Class [] interfaces = new Class[1];
+            interfaces[0] = mGel;
+            Object[] finalParams = new Object[2];
+            finalParams[0] = geckoEvent;
+         
+            GeckoEventExpecter expecter = new GeckoEventExpecter(geckoEvent, finalParams);
+            wakeInvocationHandler wIH = new wakeInvocationHandler(expecter);
+            Object proxy = Proxy.newProxyInstance(mClassLoader, interfaces, wIH);
+            finalParams[1] = proxy;
+            mRegisterGEL.invoke(null, finalParams);
+            
+            return expecter;
+        } catch (IllegalAccessException e) {
+            e.printStackTrace();
+        } catch (InvocationTargetException e) {
+            e.printStackTrace();
+        }
+        return null;
     }
 
-    public Object invoke(Object proxy, Method method, Object[] args) {
-      String methodName = method.getName();
-      if ("drawFinished".equals(methodName)) {
-        FennecNativeDriver.log(FennecNativeDriver.LogLevel.LOG_LEVEL_DEBUG,
-          "Received drawFinished notification");
-        mPaintExpecter.notifyOfEvent();
-      } else if ("toString".equals(methodName)) {
-        return "DrawListenerProxy";
-      } else if ("equals".equals(methodName)) {
-        return false;
-      } else if ("hashCode".equals(methodName)) {
-        return 0;
-      }
-      return null;
-    }
-  }
+    class DrawListenerProxy implements InvocationHandler {
+        private final PaintExpecter mPaintExpecter;
+
+        DrawListenerProxy(PaintExpecter paintExpecter) {
+            mPaintExpecter = paintExpecter;
+        }
 
-  class PaintExpecter implements RepeatedEventExpecter {
-    private Object mLayerClient;
-    private boolean mPaintDone;
-
-    PaintExpecter() throws IllegalAccessException, InvocationTargetException {
-      mLayerClient = getLayerClient.invoke(geckoApp);
-      setDrawListener.invoke(mLayerClient, Proxy.newProxyInstance(classLoader, new Class[] { drawListener }, new DrawListenerProxy(this)));
-    }
-
-    void notifyOfEvent() {
-      synchronized (this) {
-        mPaintDone = true;
-        this.notifyAll();
-      }
+        public Object invoke(Object proxy, Method method, Object[] args) {
+            String methodName = method.getName();
+            if ("drawFinished".equals(methodName)) {
+                FennecNativeDriver.log(FennecNativeDriver.LogLevel.LOG_LEVEL_DEBUG,
+                    "Received drawFinished notification");
+                mPaintExpecter.notifyOfEvent();
+            } else if ("toString".equals(methodName)) {
+                return "DrawListenerProxy";
+            } else if ("equals".equals(methodName)) {
+                return false;
+            } else if ("hashCode".equals(methodName)) {
+                return 0;
+            }
+            return null;
+        }
     }
 
-    public synchronized void blockForEvent() {
-      while (! mPaintDone) {
-        try {
-          this.wait();
-        } catch (InterruptedException ie) {
-          ie.printStackTrace();
-          break;
+    class PaintExpecter implements RepeatedEventExpecter {
+        private Object mLayerClient;
+        private boolean mPaintDone;
+
+        PaintExpecter() throws IllegalAccessException, InvocationTargetException {
+            mLayerClient = mGetLayerClient.invoke(mGeckoApp);
+            mSetDrawListener.invoke(mLayerClient, Proxy.newProxyInstance(mClassLoader, new Class[] { mDrawListener }, new DrawListenerProxy(this)));
+        }
+
+        void notifyOfEvent() {
+            synchronized (this) {
+                mPaintDone = true;
+                this.notifyAll();
+            }
+        }
+
+        public synchronized void blockForEvent() {
+            while (!mPaintDone) {
+                try {
+                    this.wait();
+                } catch (InterruptedException ie) {
+                    ie.printStackTrace();
+                    break;
+                }
+            }
+            try {
+                mSetDrawListener.invoke(mLayerClient, (Object)null);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+
+        public synchronized boolean eventReceived() {
+            return mPaintDone;
         }
-      }
-      try {
-        setDrawListener.invoke(mLayerClient, (Object)null);
-      } catch (Exception e) {
-        e.printStackTrace();
-      }
-    }
 
-    public synchronized boolean eventReceived() {
-      return mPaintDone;
+        public synchronized void blockUntilClear(long millis) {
+            if (millis <= 0) {
+                throw new IllegalArgumentException("millis must be > 0");
+            }
+            // wait for at least one event
+            while (!mPaintDone) {
+                try {
+                    this.wait();
+                } catch (InterruptedException ie) {
+                    ie.printStackTrace();
+                    break;
+                }
+            }
+            // now wait for a period of millis where we don't get an event
+            long startTime = SystemClock.uptimeMillis();
+            while (true) {
+                try {
+                    this.wait(millis);
+                } catch (InterruptedException ie) {
+                    ie.printStackTrace();
+                    break;
+                }
+                long endTime = SystemClock.uptimeMillis();
+                if (endTime - startTime >= millis) {
+                    // success
+                    break;
+                }
+                // we got a notify() before we could wait long enough, so we need to start over
+                startTime = endTime;
+            }
+            try {
+                mSetDrawListener.invoke(mLayerClient, (Object)null);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
     }
 
-    public synchronized void blockUntilClear(long millis) {
-      if (millis <= 0) {
-        throw new IllegalArgumentException("millis must be > 0");
-      }
-      // wait for at least one event
-      while (! mPaintDone) {
-        try {
-          this.wait();
-        } catch (InterruptedException ie) {
-          ie.printStackTrace();
-          break;
-        }
-      }
-      // now wait for a period of millis where we don't get an event
-      long startTime = SystemClock.uptimeMillis();
-      while (true) {
+    public RepeatedEventExpecter expectPaint() {
         try {
-          this.wait(millis);
-        } catch (InterruptedException ie) {
-          ie.printStackTrace();
-          break;
-        }
-        long endTime = SystemClock.uptimeMillis();
-        if (endTime - startTime >= millis) {
-          // success
-          break;
+            return new PaintExpecter();
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
         }
-        // we got a notify() before we could wait long enough, so we need to start over
-        startTime = endTime;
-      }
-      try {
-        setDrawListener.invoke(mLayerClient, (Object)null);
-      } catch (Exception e) {
-        e.printStackTrace();
-      }
     }
-  }
 
-  public RepeatedEventExpecter expectPaint() {
-    try {
-      return new PaintExpecter();
-    } catch (Exception e) {
-      e.printStackTrace();
-      return null;
+    public void sendSpecialKey(SpecialKey button) {
+        switch(button) {
+            case DOWN:
+                mInstr.sendCharacterSync(KeyEvent.KEYCODE_DPAD_DOWN);
+                break;
+            case UP:
+                mInstr.sendCharacterSync(KeyEvent.KEYCODE_DPAD_UP);
+                break;
+            case LEFT:
+                mInstr.sendCharacterSync(KeyEvent.KEYCODE_DPAD_LEFT);
+                break;
+            case RIGHT:
+                mInstr.sendCharacterSync(KeyEvent.KEYCODE_DPAD_RIGHT);
+                break;
+            case ENTER:
+                mInstr.sendCharacterSync(KeyEvent.KEYCODE_ENTER);
+                break;
+            case MENU:
+                mInstr.sendCharacterSync(KeyEvent.KEYCODE_MENU);
+                break;
+            case BACK:
+                mInstr.sendCharacterSync(KeyEvent.KEYCODE_BACK);
+                break;
+            default:
+                break;
+        }
     }
-  }
 
-  public void sendSpecialKey(SpecialKey button) {
-    switch( button) {
-      case DOWN:
-        instr.sendCharacterSync(KeyEvent.KEYCODE_DPAD_DOWN);
-        break;
-      case UP:
-        instr.sendCharacterSync(KeyEvent.KEYCODE_DPAD_UP);
-        break;
-      case LEFT:
-        instr.sendCharacterSync(KeyEvent.KEYCODE_DPAD_LEFT);
-        break;
-      case RIGHT:
-        instr.sendCharacterSync(KeyEvent.KEYCODE_DPAD_RIGHT);
-        break;
-      case ENTER:
-        instr.sendCharacterSync(KeyEvent.KEYCODE_ENTER);
-        break;
-      case MENU:
-        instr.sendCharacterSync(KeyEvent.KEYCODE_MENU);
-        break;
-      case BACK:
-        instr.sendCharacterSync(KeyEvent.KEYCODE_BACK);
-        break;
-      default:
-        break;
+    @Override
+    public void sendKeys(String input) {
+        mInstr.sendStringSync(input);
     }
-  }
 
-  @Override
-  public void sendKeys(String input) {
-    instr.sendStringSync(input);
-  }
-
-
-  public void drag(int startingX, int endingX, int startingY, int endingY) {
-    solo.drag(startingX, endingX, startingY, endingY, 10);
-  }
+    public void drag(int startingX, int endingX, int startingY, int endingY) {
+        mSolo.drag(startingX, endingX, startingY, endingY, 10);
+    }
 }
--- a/build/mobile/robocop/FennecNativeDriver.java.in
+++ b/build/mobile/robocop/FennecNativeDriver.java.in
@@ -41,18 +41,16 @@ package @ANDROID_PACKAGE_NAME@;
 
 import java.io.BufferedReader;
 import java.io.BufferedWriter;
 import java.io.File;
 import java.io.FileReader;
 import java.io.FileWriter;
 import java.io.IOException;
 import java.nio.IntBuffer;
-import java.util.ArrayList;
-import java.util.LinkedList;
 import java.util.HashMap;
 import java.util.List;
 
 import java.lang.Class;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
 import java.lang.reflect.InvocationHandler;
@@ -63,401 +61,410 @@ import android.opengl.GLSurfaceView;
 import android.view.View;
 import android.util.Log;
 
 import org.json.*;
 
 import com.jayway.android.robotium.solo.Solo;
 
 public class FennecNativeDriver implements Driver {
-  // Map of IDs to element names.
-  private HashMap locators = null;
-  private Activity activity;
-  private Solo solo;
+    // Map of IDs to element names.
+    private HashMap mLocators = null;
+    private Activity mActivity;
+    private Solo mSolo;
+
+    private static String mLogFile = null;
+    private static LogLevel mLogLevel = LogLevel.LOG_LEVEL_INFO;
 
-  private static String mLogFile = null;
-  private static LogLevel mLogLevel = LogLevel.LOG_LEVEL_INFO;
+    // Objects for reflexive access of fennec classes.
+    private ClassLoader mClassLoader;
+    private Class mGel;
+    private Class mGe;
+    private Class mGas;
+    private Method mRegisterGEL;
+    private Method mUnregisterGEL;
+    private Method mSendGE;
+    private Method _startFrameRecording;
+    private Method _stopFrameRecording;
+    private Method _startCheckerboardRecording;
+    private Method _stopCheckerboardRecording;
+    private Method _getPixels;
 
-  // Objects for reflexive access of fennec classes.
-  private ClassLoader classLoader;
-  private Class gel;
-  private Class ge;
-  private Class gas;
-  private Method registerGEL;
-  private Method unregisterGEL;
-  private Method sendGE;
-  private Method _startFrameRecording;
-  private Method _stopFrameRecording;
-  private Method _startCheckerboardRecording;
-  private Method _stopCheckerboardRecording;
-  private Method _getPixels;
+    public enum LogLevel {
+        LOG_LEVEL_DEBUG(1), 
+        LOG_LEVEL_INFO(2),    
+        LOG_LEVEL_WARN(3), 
+        LOG_LEVEL_ERROR(4);
 
-  public enum LogLevel {
-    LOG_LEVEL_DEBUG(1), 
-    LOG_LEVEL_INFO(2),  
-    LOG_LEVEL_WARN(3), 
-    LOG_LEVEL_ERROR(4);
+        private int mValue;
+        LogLevel(int value) {
+            mValue = value;
+        }
+        public boolean isEnabled(LogLevel configuredLevel) {
+            return mValue >= configuredLevel.getValue();
+        }
+        private int getValue() {
+            return mValue;
+        }
+    }
 
-    private int mValue;
-    LogLevel(int value) {
-      mValue = value;
-    }
-    public boolean isEnabled(LogLevel configuredLevel) {
-      return mValue >= configuredLevel.getValue();
-    }
-    private int getValue() {
-      return mValue;
-    }
-  }
+    public FennecNativeDriver(Activity activity, Solo robocop) {
+        mActivity = activity;
+        mSolo = robocop;
 
-  public FennecNativeDriver(Activity activity, Solo robocop){
-    this.activity = activity;
-    this.solo = robocop;
+        // Set up table of fennec_ids.
+        mLocators = convertTextToTable(getFile("/mnt/sdcard/fennec_ids.txt"));
 
-    // Set up table of fennec_ids.
-    locators = convertTextToTable(getFile("/mnt/sdcard/fennec_ids.txt"));
+        // Set up reflexive access of java classes and methods.
+        try {
+            mClassLoader = activity.getClassLoader();
+            mGel = mClassLoader.loadClass("org.mozilla.gecko.GeckoEventListener");
+            mGe = mClassLoader.loadClass("org.mozilla.gecko.GeckoEvent");
+            mGas = mClassLoader.loadClass("org.mozilla.gecko.GeckoAppShell");
+            Class [] parameters = new Class[2];
+            parameters[0] = String.class;
+            parameters[1] = mGel;
+            mRegisterGEL = mGas.getMethod("registerGeckoEventListener", parameters);
+            mUnregisterGEL = mGas.getMethod("unregisterGeckoEventListener", parameters);
+            parameters = new Class[1];
+            parameters[0] = mGe;
+            mSendGE = mGas.getMethod("sendEventToGecko", parameters);
 
-    // Set up reflexive access of java classes and methods.
-    try {
-      classLoader = activity.getClassLoader();
-      gel = classLoader.loadClass("org.mozilla.gecko.GeckoEventListener");
-      ge = classLoader.loadClass("org.mozilla.gecko.GeckoEvent");
-      gas = classLoader.loadClass("org.mozilla.gecko.GeckoAppShell");
-      Class [] parameters = new Class[2];
-      parameters[0] = String.class;
-      parameters[1] = gel;
-      registerGEL = gas.getMethod("registerGeckoEventListener", parameters);
-      unregisterGEL = gas.getMethod("unregisterGeckoEventListener", parameters);
-      parameters = new Class[1];
-      parameters[0] = ge;
-      sendGE = gas.getMethod("sendEventToGecko", parameters);
+            Class gfx = mClassLoader.loadClass("org.mozilla.gecko.gfx.PanningPerfAPI");
+            _startFrameRecording = gfx.getDeclaredMethod("startFrameTimeRecording");
+            _stopFrameRecording = gfx.getDeclaredMethod("stopFrameTimeRecording");
+            _startCheckerboardRecording = gfx.getDeclaredMethod("startCheckerboardRecording");
+            _stopCheckerboardRecording = gfx.getDeclaredMethod("stopCheckerboardRecording");
 
-      Class gfx = classLoader.loadClass("org.mozilla.gecko.gfx.PanningPerfAPI");
-      _startFrameRecording = gfx.getDeclaredMethod("startFrameTimeRecording");
-      _stopFrameRecording = gfx.getDeclaredMethod("stopFrameTimeRecording");
-      _startCheckerboardRecording = gfx.getDeclaredMethod("startCheckerboardRecording");
-      _stopCheckerboardRecording = gfx.getDeclaredMethod("stopCheckerboardRecording");
+            Class layerView = mClassLoader.loadClass("org.mozilla.gecko.gfx.LayerView");
+            _getPixels = layerView.getDeclaredMethod("getPixels");
+         } catch (ClassNotFoundException e) {
+             e.printStackTrace();
+         } catch (SecurityException e) {
+             e.printStackTrace();
+         } catch (NoSuchMethodException e) {
+             e.printStackTrace();
+         } catch (IllegalArgumentException e) {
+             e.printStackTrace();
+         }
+    }
 
-      Class layerView = classLoader.loadClass("org.mozilla.gecko.gfx.LayerView");
-      _getPixels = layerView.getDeclaredMethod("getPixels");
-     } catch (ClassNotFoundException e) {
-       e.printStackTrace();
-     } catch (SecurityException e) {
-       e.printStackTrace();
-     } catch (NoSuchMethodException e) {
-       e.printStackTrace();
-     } catch (IllegalArgumentException e) {
-       e.printStackTrace();
-     }
-  }
+    //Information on the location of the Gecko Frame.
+    private boolean mGeckoInfo = false;
+    private int mGeckoTop = 100;
+    private int mGeckoLeft = 0;
+    private int mGeckoHeight= 700;
+    private int mGeckoWidth = 1024;
 
-  //Information on the location of the Gecko Frame.
-  private boolean geckoInfo = false;
-  private int geckoTop = 100;
-  private int geckoLeft = 0;
-  private int geckoHeight= 700;
-  private int geckoWidth = 1024;
-
-  private void getGeckoInfo() {
-    View geckoLayout = activity.findViewById(Integer.decode((String)locators.get("gecko_layout")));
-    if (geckoLayout != null) {
-      int[] pos = new int[2];
-      geckoLayout.getLocationOnScreen(pos);
-      geckoTop = pos[1];
-      geckoLeft = pos[0];
-      geckoWidth = geckoLayout.getWidth();
-      geckoHeight = geckoLayout.getHeight();
-      geckoInfo = true;
-    } else {
-      throw new RoboCopException("Unable to find view gecko_layout");
+    private void getGeckoInfo() {
+        View geckoLayout = mActivity.findViewById(Integer.decode((String)mLocators.get("gecko_layout")));
+        if (geckoLayout != null) {
+            int[] pos = new int[2];
+            geckoLayout.getLocationOnScreen(pos);
+            mGeckoTop = pos[1];
+            mGeckoLeft = pos[0];
+            mGeckoWidth = geckoLayout.getWidth();
+            mGeckoHeight = geckoLayout.getHeight();
+            mGeckoInfo = true;
+        } else {
+            throw new RoboCopException("Unable to find view gecko_layout");
+        }
     }
-  }
 
-  public int getGeckoTop() {
-    if(!geckoInfo) {
-      getGeckoInfo();
+    public int getGeckoTop() {
+        if (!mGeckoInfo) {
+            getGeckoInfo();
+        }
+        return mGeckoTop;
     }
-    return geckoTop;
-  }
 
-  public int getGeckoLeft() {
-    if(!geckoInfo) {
-      getGeckoInfo();
+    public int getGeckoLeft() {
+        if (!mGeckoInfo) {
+            getGeckoInfo();
+        }
+        return mGeckoLeft;
     }
-    return geckoLeft;
-  }
 
-  public int getGeckoHeight() {
-    if(!geckoInfo) {
-      getGeckoInfo();
+    public int getGeckoHeight() {
+        if (!mGeckoInfo) {
+            getGeckoInfo();
+        }
+        return mGeckoHeight;
     }
-    return geckoHeight;
-  }
-  public int getGeckoWidth() {
-    if(!geckoInfo) {
-      getGeckoInfo();
+
+    public int getGeckoWidth() {
+        if (!mGeckoInfo) {
+            getGeckoInfo();
+        }
+        return mGeckoWidth;
     }
-    return geckoWidth;
-  }
 
-  public Element findElement(Activity activity, String name) {
-    if (name == null)
-      throw new IllegalArgumentException("Can not findElements when passed a null");
-    if (locators.containsKey(name)){
-      return new FennecNativeElement(Integer.decode((String)locators.get(name)), activity, solo);
+    public Element findElement(Activity activity, String name) {
+        if (name == null) {
+            throw new IllegalArgumentException("Can not findElements when passed a null");
+        }
+        if (mLocators.containsKey(name)) {
+            return new FennecNativeElement(Integer.decode((String)mLocators.get(name)), activity, mSolo);
+        }
+        throw new RoboCopException("Element does not exist in the list");
     }
-    throw new RoboCopException("Element does not exist in the list");
-  }
 
-  public void startFrameRecording() {
-    try {
-      Object [] params = null;
-      _startFrameRecording.invoke(null, params);
-    } catch (IllegalAccessException e) {
-      e.printStackTrace();
-    } catch (InvocationTargetException e) {
-      e.printStackTrace();
+    public void startFrameRecording() {
+        try {
+            Object [] params = null;
+            _startFrameRecording.invoke(null, params);
+        } catch (IllegalAccessException e) {
+            e.printStackTrace();
+        } catch (InvocationTargetException e) {
+            e.printStackTrace();
+        }
     }
-  }
+
+    public int stopFrameRecording() {
+        Class [] parameters = new Class[1];
+        parameters[0] = null;
+        List frames;
 
-  public int stopFrameRecording() {
-    Class [] parameters = new Class[1];
-    parameters[0] = null;
-    List frames;
+        try {
+            Object [] params = null;
+            frames = (List)_stopFrameRecording.invoke(null, params);
+            Object [] framearray = frames.toArray();
+            Long last = new Long(0);
+            Long threshold = new Long(17);
+            int numDelays = 0;
+            for (int i=0; i < framearray.length; i++) {
+                Long val = (Long)framearray[i];
+                if ((val - last) > threshold) {
+                    numDelays++;
+                }
+                last = val;
+            }
+            return numDelays;
+        } catch (IllegalAccessException e) {
+            e.printStackTrace();
+        } catch (InvocationTargetException e) {
+            e.printStackTrace();
+        }
 
-    try {
-      Object [] params = null;
-      frames = (List)_stopFrameRecording.invoke(null, params);
-      Object [] framearray = frames.toArray();
-      Long last = new Long(0);
-      Long threshold = new Long(17);
-      int numDelays = 0;
-      for (int i=0; i < framearray.length; i++) {
-        Long val = (Long)framearray[i];
-        if ((val - last) > threshold) {
-          numDelays++;
+        return 0;
+    }
+
+    public void startCheckerboardRecording() {
+        try {
+            Object [] params = null;
+            _startCheckerboardRecording.invoke(null, params);
+        } catch (IllegalAccessException e) {
+            e.printStackTrace();
+        } catch (InvocationTargetException e) {
+            e.printStackTrace();
         }
-        last = val;
-      }
-      return numDelays;
-    } catch (IllegalAccessException e) {
-      e.printStackTrace();
-    } catch (InvocationTargetException e) {
-      e.printStackTrace();
     }
 
-    return 0;
-  }
-
-  public void startCheckerboardRecording() {
-    try {
-      Object [] params = null;
-      _startCheckerboardRecording.invoke(null, params);
-    } catch (IllegalAccessException e) {
-      e.printStackTrace();
-    } catch (InvocationTargetException e) {
-      e.printStackTrace();
-    }
-  }
+    public float stopCheckerboardRecording() {
+        Class [] parameters = new Class[1];
+        parameters[0] = null;
+        List checkerboard;
 
-  public float stopCheckerboardRecording() {
-    Class [] parameters = new Class[1];
-    parameters[0] = null;
-    List checkerboard;
+        try {
+            Object [] params = null;
+            checkerboard = (List)_stopCheckerboardRecording.invoke(null, params);
+            Object [] amountarray = checkerboard.toArray();
+            double completeness = 0;
+            for (Object obj : amountarray) {
+                float val = (Float)obj;
+                completeness += (1.0 - (double)val) / (double)amountarray.length;
+            }
+            return (float)completeness;
+        } catch (IllegalAccessException e) {
+            e.printStackTrace();
+        } catch (InvocationTargetException e) {
+            e.printStackTrace();
+        }
 
-    try {
-      Object [] params = null;
-      checkerboard = (List)_stopCheckerboardRecording.invoke(null, params);
-      Object [] amountarray = checkerboard.toArray();
-      double completeness = 0;
-      for (Object obj : amountarray) {
-        float val = (Float)obj;
-        completeness += (1.0 - (double)val) / (double)amountarray.length;
-      }
-      return (float)completeness;
-    } catch (IllegalAccessException e) {
-      e.printStackTrace();
-    } catch (InvocationTargetException e) {
-      e.printStackTrace();
+        return 0.0f;
     }
 
-    return 0.0f;
-  }
-
-  private GLSurfaceView getSurfaceView() {
-    for (View v : solo.getCurrentViews()) {
-      if (v instanceof GLSurfaceView) {
-        return (GLSurfaceView)v;
-      }
-    }
-    return null;
-  }
-
-  public int[][] getPaintedSurface() {
-    GLSurfaceView view = getSurfaceView();
-    if (view == null) {
-      return null;
-    }
-    IntBuffer pixelBuffer;
-    try {
-      pixelBuffer = (IntBuffer)_getPixels.invoke(view);
-    } catch (Exception e) {
-      e.printStackTrace();
-      return null;
+    private GLSurfaceView getSurfaceView() {
+        for (View v : mSolo.getCurrentViews()) {
+            if (v instanceof GLSurfaceView) {
+                return (GLSurfaceView)v;
+            }
+        }
+        return null;
     }
 
-    // now we need to (1) flip the image, because GL likes to do things up-side-down,
-    // and (2) rearrange the bits from AGBR-8888 to ARGB-8888.
-    int w = view.getWidth();
-    int h = view.getHeight();
-    pixelBuffer.position(0);
-    int[][] pixels = new int[h][w];
-    for (int y = h - 1; y >= 0; y--) {
-      for (int x = 0; x < w; x++) {
-        int agbr = pixelBuffer.get();
-        pixels[y][x] = (agbr & 0xFF00FF00) | ((agbr >> 16) & 0x000000FF) | ((agbr << 16) & 0x00FF0000);
-      }
-    }
-    return pixels;
-  }
-
-  class scrollHandler implements InvocationHandler {
-    public scrollHandler(){};
-    public Object invoke(Object proxy, Method method, Object[] args) {
-      try{
-        //Disect the JSON object into the appropriate variables 
-        JSONObject jo = ((JSONObject)args[1]);
-        scrollHeight = jo.getInt("y");
-        height = jo.getInt("cheight");
-        //We don't want a height of 0. That means it's a bad response.
-        if( height > 0) {
-          pageHeight = jo.getInt("height");
+    public int[][] getPaintedSurface() {
+        GLSurfaceView view = getSurfaceView();
+        if (view == null) {
+            return null;
+        }
+        IntBuffer pixelBuffer;
+        try {
+            pixelBuffer = (IntBuffer)_getPixels.invoke(view);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
         }
 
-      } catch( Throwable e) {
-        FennecNativeDriver.log(FennecNativeDriver.LogLevel.LOG_LEVEL_WARN, 
-          "WARNING: ScrollReceived, but read wrong!");
-      }
-      return null;
+        // now we need to (1) flip the image, because GL likes to do things up-side-down,
+        // and (2) rearrange the bits from AGBR-8888 to ARGB-8888.
+        int w = view.getWidth();
+        int h = view.getHeight();
+        pixelBuffer.position(0);
+        int[][] pixels = new int[h][w];
+        for (int y = h - 1; y >= 0; y--) {
+            for (int x = 0; x < w; x++) {
+                int agbr = pixelBuffer.get();
+                pixels[y][x] = (agbr & 0xFF00FF00) | ((agbr >> 16) & 0x000000FF) | ((agbr << 16) & 0x00FF0000);
+            }
+        }
+        return pixels;
     }
-  }
-  public int getScrollHeight() {
-    return scrollHeight;
-  }
-  public int getPageHeight() {
-    return pageHeight;
-  }
-  public int getHeight() {
-    return height;
-  }
+
+    public int mHeight=0;
+    public int mScrollHeight=0;
+    public int mPageHeight=10;
+
+    class scrollHandler implements InvocationHandler {
+        public scrollHandler(){};
+        public Object invoke(Object proxy, Method method, Object[] args) {
+            try {
+                // Disect the JSON object into the appropriate variables 
+                JSONObject jo = ((JSONObject)args[1]);
+                mScrollHeight = jo.getInt("y");
+                mHeight = jo.getInt("cheight");
+                // We don't want a height of 0. That means it's a bad response.
+                if (mHeight > 0) {
+                    mPageHeight = jo.getInt("height");
+                }
 
-  public int height=0;
-  public int scrollHeight=0;
-  public int pageHeight=10;
-  public void setupScrollHandling() {
-    //Setup scrollHandler to catch "robocop:scroll" events. 
-    try {
-      Class [] interfaces = new Class[1];
-      interfaces[0] = gel;
-      Object[] finalParams = new Object[2];
-      finalParams[0] = "robocop:scroll";
-      finalParams[1] = Proxy.newProxyInstance(classLoader, interfaces, new scrollHandler());
-      registerGEL.invoke(null, finalParams);
-    } catch (IllegalAccessException e) {
-      e.printStackTrace();
-    } catch (InvocationTargetException e) {
-      e.printStackTrace();
+            } catch( Throwable e) {
+                FennecNativeDriver.log(FennecNativeDriver.LogLevel.LOG_LEVEL_WARN, 
+                    "WARNING: ScrollReceived, but read wrong!");
+            }
+            return null;
+        }
+    }
+
+    public int getScrollHeight() {
+        return mScrollHeight;
+    }
+    public int getPageHeight() {
+        return mPageHeight;
+    }
+    public int getHeight() {
+        return mHeight;
+    }
+
+    public void setupScrollHandling() {
+        //Setup scrollHandler to catch "robocop:scroll" events. 
+        try {
+            Class [] interfaces = new Class[1];
+            interfaces[0] = mGel;
+            Object[] finalParams = new Object[2];
+            finalParams[0] = "robocop:scroll";
+            finalParams[1] = Proxy.newProxyInstance(mClassLoader, interfaces, new scrollHandler());
+            mRegisterGEL.invoke(null, finalParams);
+        } catch (IllegalAccessException e) {
+            e.printStackTrace();
+        } catch (InvocationTargetException e) {
+            e.printStackTrace();
+        }
+
     }
 
-  }
-
-  //Takes a filename, loads the file, 
-  //  and returns a string version of the entire file.
-  public static String getFile(String filename)
-  {
-    StringBuilder text = new StringBuilder();
-
-    BufferedReader br = null;
-    try {
-      br = new BufferedReader(new FileReader(filename));
-      String line;
+    /**
+     *  Takes a filename, loads the file, and returns a string version of the entire file.
+     */
+    public static String getFile(String filename)
+    {
+        StringBuilder text = new StringBuilder();
 
-      while ((line = br.readLine()) != null) {
-        text.append(line);
-        text.append('\n');
-      }
-    } catch(IOException e) {
-      e.printStackTrace();
-    } finally {
-      try {
-        br.close();
-      } catch (IOException e) {
-      }
-    }
-    return text.toString();  
-  }
+        BufferedReader br = null;
+        try {
+            br = new BufferedReader(new FileReader(filename));
+            String line;
 
-  // Takes a string of "key=value" pairs split by \n and creates a hash table.
-  public static HashMap convertTextToTable(String data)
-  {
-    HashMap retVal = new HashMap();
-
-    String[] lines = data.split("\n");
-    for (int i = 0; i < lines.length; i++) {
-      String[] parts = lines[i].split("=");
-      retVal.put(parts[0].trim(), parts[1].trim());
+            while ((line = br.readLine()) != null) {
+                text.append(line);
+                text.append('\n');
+            }
+        } catch(IOException e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                br.close();
+            } catch (IOException e) {
+            }
+        }
+        return text.toString();    
     }
-    return retVal;
-  }
 
-  // Set the filename used for logging. If the file already exists, delete it
-  // as a safe-guard against accidentally appending to an old log file.
-  public static void setLogFile(String filename) {
-    mLogFile = filename;
-    File file = new File(mLogFile);
-    if (file.exists()) {
-      file.delete();
-    }
-  }
+    /**
+     *  Takes a string of "key=value" pairs split by \n and creates a hash table.
+     */
+    public static HashMap convertTextToTable(String data)
+    {
+        HashMap retVal = new HashMap();
 
-  public static void setLogLevel(LogLevel level) {
-    mLogLevel = level;
-  }
-
-  public static void log(LogLevel level, String message) {
-    if (mLogFile == null) {
-      assert(false);
+        String[] lines = data.split("\n");
+        for (int i = 0; i < lines.length; i++) {
+            String[] parts = lines[i].split("=");
+            retVal.put(parts[0].trim(), parts[1].trim());
+        }
+        return retVal;
     }
 
-    if (level.isEnabled(mLogLevel)) {
-      File file = new File(mLogFile);
-      BufferedWriter bw = null;
+    /** 
+     *  Set the filename used for logging. If the file already exists, delete it
+     *  as a safe-guard against accidentally appending to an old log file.
+     */
+    public static void setLogFile(String filename) {
+        mLogFile = filename;
+        File file = new File(mLogFile);
+        if (file.exists()) {
+            file.delete();
+        }
+    }
 
-      try {
-        bw = new BufferedWriter(new FileWriter(mLogFile, true));
-        bw.write(message);
-        bw.newLine();
-      } catch(IOException e) {
-        Log.e("Robocop", "exception with file writer on: " + mLogFile);
-      } finally {
-        try {
-          if (bw != null) {
-            bw.flush();
-            bw.close();
-          }
-        } catch (IOException ex) {
-          ex.printStackTrace();
-        }
-      }
+    public static void setLogLevel(LogLevel level) {
+        mLogLevel = level;
     }
 
-    if (level == LogLevel.LOG_LEVEL_INFO) {
-      Log.i("Robocop", message);
-    } else if (level == LogLevel.LOG_LEVEL_DEBUG) {
-      Log.d("Robocop", message);
-    } else if (level == LogLevel.LOG_LEVEL_WARN) {
-      Log.w("Robocop", message);
-    } else if (level == LogLevel.LOG_LEVEL_ERROR) {
-      Log.e("Robocop", message);
+    public static void log(LogLevel level, String message) {
+        if (mLogFile == null) {
+            assert(false);
+        }
+
+        if (level.isEnabled(mLogLevel)) {
+            File file = new File(mLogFile);
+            BufferedWriter bw = null;
+
+            try {
+                bw = new BufferedWriter(new FileWriter(mLogFile, true));
+                bw.write(message);
+                bw.newLine();
+            } catch(IOException e) {
+                Log.e("Robocop", "exception with file writer on: " + mLogFile);
+            } finally {
+                try {
+                    if (bw != null) {
+                        bw.flush();
+                        bw.close();
+                    }
+                } catch (IOException ex) {
+                    ex.printStackTrace();
+                }
+            }
+        }
+
+        if (level == LogLevel.LOG_LEVEL_INFO) {
+            Log.i("Robocop", message);
+        } else if (level == LogLevel.LOG_LEVEL_DEBUG) {
+            Log.d("Robocop", message);
+        } else if (level == LogLevel.LOG_LEVEL_WARN) {
+            Log.w("Robocop", message);
+        } else if (level == LogLevel.LOG_LEVEL_ERROR) {
+            Log.e("Robocop", message);
+        }
     }
-  }
 
 }
--- a/build/mobile/robocop/FennecNativeElement.java.in
+++ b/build/mobile/robocop/FennecNativeElement.java.in
@@ -37,128 +37,127 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 package @ANDROID_PACKAGE_NAME@;
 
 import java.util.List;
 
 import android.app.Activity;
-import android.view.KeyEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.Button;
 import android.widget.EditText;
 import android.widget.ListView;
 import android.widget.TextView;
 import android.widget.TextSwitcher;
 import android.app.Instrumentation;
 import com.jayway.android.robotium.solo.Solo;
 import java.util.concurrent.SynchronousQueue;
 
 public class FennecNativeElement implements Element {
-  private final Activity mActivity;
-  private Integer id;
-  private Solo robocop;
+    private final Activity mActivity;
+    private Integer mId;
+    private Solo mSolo;
 
-  public FennecNativeElement(Integer id, Activity activity, Solo solo){
-    this.id = id;
-    mActivity = activity;
-    robocop = solo;
-  }
+    public FennecNativeElement(Integer id, Activity activity, Solo solo) {
+        mId = id;
+        mActivity = activity;
+        mSolo = solo;
+    }
 
-  public Integer getId() {
-    return id;
-  }
+    public Integer getId() {
+        return mId;
+    }
 
-  public void click() {
-    final SynchronousQueue syncQueue = new SynchronousQueue();
-    mActivity.runOnUiThread(
-        new Runnable() {
-          public void run() {
-            View view = (View)mActivity.findViewById(id);
-            if(view != null) {
-              if (!view.performClick()) {
-                FennecNativeDriver.log(FennecNativeDriver.LogLevel.LOG_LEVEL_WARN,
-                  "Robocop called click on an element with no listener");
-              }
-            } else {
-              throw new RoboCopException("click: unable to find view "+id); 
-            }
-            syncQueue.offer(new Object());
-          }
-        });
-    try {
-      syncQueue.take();
-    } catch (InterruptedException e) {
-      e.printStackTrace();
+    public void click() {
+        final SynchronousQueue syncQueue = new SynchronousQueue();
+        mActivity.runOnUiThread(
+            new Runnable() {
+                public void run() {
+                    View view = (View)mActivity.findViewById(mId);
+                    if(view != null) {
+                        if (!view.performClick()) {
+                            FennecNativeDriver.log(FennecNativeDriver.LogLevel.LOG_LEVEL_WARN,
+                                "Robocop called click on an element with no listener");
+                        }
+                    } else {
+                        throw new RoboCopException("click: unable to find view "+mId); 
+                    }
+                    syncQueue.offer(new Object());
+                }
+            });
+        try {
+            syncQueue.take();
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
     }
-  }
 
-  private Object text;
+    private Object mText;
 
-  public String getText() {
-    final SynchronousQueue syncQueue = new SynchronousQueue();
-    mActivity.runOnUiThread(
-        new Runnable() {
-          public void run() {
-            View v = mActivity.findViewById(id);
-            if(v instanceof EditText) {
-              EditText et = (EditText)v;
-              text = et.getEditableText();
-            }else if(v instanceof TextSwitcher) {
-              TextSwitcher ts = (TextSwitcher)v;
-              ts.getNextView();
-              text = ((TextView)ts.getCurrentView()).getText();
-            }else if(v instanceof ViewGroup) {
-              ViewGroup vg = (ViewGroup)v;
-              for(int i = 0; i < vg.getChildCount(); i++) {
-                if(vg.getChildAt(i) instanceof TextView) {
-                  text = ((TextView)vg.getChildAt(i)).getText();
+    public String getText() {
+        final SynchronousQueue syncQueue = new SynchronousQueue();
+        mActivity.runOnUiThread(
+            new Runnable() {
+                public void run() {
+                    View v = mActivity.findViewById(mId);
+                    if (v instanceof EditText) {
+                        EditText et = (EditText)v;
+                        mText = et.getEditableText();
+                    } else if (v instanceof TextSwitcher) {
+                        TextSwitcher ts = (TextSwitcher)v;
+                        ts.getNextView();
+                        mText = ((TextView)ts.getCurrentView()).getText();
+                    } else if (v instanceof ViewGroup) {
+                        ViewGroup vg = (ViewGroup)v;
+                        for (int i = 0; i < vg.getChildCount(); i++) {
+                            if (vg.getChildAt(i) instanceof TextView) {
+                                mText = ((TextView)vg.getChildAt(i)).getText();
+                            }
+                        }
+                    } else if (v instanceof TextView) {
+                        mText = ((TextView)v).getText(); 
+                    } else if (v == null) {
+                        throw new RoboCopException("getText: unable to find view "+mId); 
+                    } else {
+                        throw new RoboCopException("getText: unhandled type for view "+mId); 
+                    }
+                    syncQueue.offer(new Object());
+                } // end of run() method definition
+            } // end of anonymous Runnable object instantiation
+        );
+        try {     
+            // Wait for the UiThread code to finish running
+            syncQueue.take();
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+        if (mText == null) {
+            throw new RoboCopException("getText: Text is null for view "+mId);
+        }
+        return mText.toString();
+    }
+
+    private boolean mDisplayed;
+
+    public boolean isDisplayed() {
+        final SynchronousQueue syncQueue = new SynchronousQueue();
+        mDisplayed = false;
+        mActivity.runOnUiThread(
+            new Runnable() {
+                public void run() {
+                    View view = (View)mActivity.findViewById(mId);
+                    if (view != null) {
+                        mDisplayed = true;
+                    }
+                    syncQueue.offer(new Object());
                 }
-              } //end of for
-            } else if(v instanceof TextView) {
-              text = ((TextView)v).getText(); 
-            } else if(v == null) {
-              throw new RoboCopException("getText: unable to find view "+id); 
-            } else {
-              throw new RoboCopException("getText: unhandled type for view "+id); 
-            }
-            syncQueue.offer(new Object());
-          } // end of run() method definition
-        } // end of anonymous Runnable object instantiation
-    );
-    try {   
-      //Wait for the UiThread code to finish running
-      syncQueue.take();
-    } catch (InterruptedException e) {
-      e.printStackTrace();
-    }
-    if(text == null) {
-      throw new RoboCopException("getText: Text is null for view "+id);
+            });
+        try {
+            syncQueue.take();
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+        return mDisplayed;
     }
-    return text.toString();
-  }
-
-  private boolean displayed;
-
-  public boolean isDisplayed() {
-    final SynchronousQueue syncQueue = new SynchronousQueue();
-    displayed = false;
-    mActivity.runOnUiThread(
-        new Runnable() {
-          public void run() {
-            View view = (View)mActivity.findViewById(id);
-            if(view != null) {
-              displayed = true;
-            }
-            syncQueue.offer(new Object());
-          }
-        });
-    try {
-      syncQueue.take();
-    } catch (InterruptedException e) {
-      e.printStackTrace();
-    }
-    return displayed;
-  }
 
 }
--- a/build/mobile/robocop/FennecTalosAssert.java.in
+++ b/build/mobile/robocop/FennecTalosAssert.java.in
@@ -34,57 +34,45 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 package @ANDROID_PACKAGE_NAME@;
 
 
 public class FennecTalosAssert implements Assert {
-  
-  public FennecTalosAssert() {
-  }
-
-  // Write information to a logfile and logcat
-  public void dumpLog(String message)
-  {
-    FennecNativeDriver.log(FennecNativeDriver.LogLevel.LOG_LEVEL_INFO, message);
-  }
+    
+    public FennecTalosAssert() { }
 
-  // Set the filename used for dumpLog.
-  public void setLogFile(String filename)
-  {
-    FennecNativeDriver.setLogFile(filename);
-  }
+    /**
+     *  Write information to a logfile and logcat
+     */
+    public void dumpLog(String message) {
+        FennecNativeDriver.log(FennecNativeDriver.LogLevel.LOG_LEVEL_INFO, message);
+    }
 
-  public void setTestName(String testName)
-  {
-  }
+    /**
+     *  Set the filename used for dumpLog.
+     */
+    public void setLogFile(String filename) {
+        FennecNativeDriver.setLogFile(filename);
+    }
 
-
-  public void finalize()
-  {
-  }
+    public void setTestName(String testName) { }
 
-  public void ok(boolean condition, String name, String diag) {
-  }
+    public void finalize() { }
+
+    public void ok(boolean condition, String name, String diag) { }
 
-  public void is(Object a, Object b, String name) {
-  }
-  
-  public void isnot(Object a, Object b, String name) {
-  }
-
-  public void ispixel(int actual, int r, int g, int b, String name) {
-  }
+    public void is(Object a, Object b, String name) { }
+    
+    public void isnot(Object a, Object b, String name) { }
 
-  public void todo(boolean condition, String name, String diag) {
-  }
+    public void ispixel(int actual, int r, int g, int b, String name) { }
+
+    public void todo(boolean condition, String name, String diag) { }
 
-  public void todo_is(Object a, Object b, String name) {
-  }
-  
-  public void todo_isnot(Object a, Object b, String name) {
-  }
+    public void todo_is(Object a, Object b, String name) { }
+    
+    public void todo_isnot(Object a, Object b, String name) { }
 
-  public void info(String name, String message) {
-  }
+    public void info(String name, String message) { }
 }
--- a/build/mobile/robocop/RoboCopException.java.in
+++ b/build/mobile/robocop/RoboCopException.java.in
@@ -35,25 +35,25 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 package @ANDROID_PACKAGE_NAME@;
 
 public class RoboCopException extends RuntimeException {
-  
-  public RoboCopException(){
-    super();
-  }
-  
-  public RoboCopException(String message){
-    super(message);
-  }
-  
-  public RoboCopException(Throwable cause){
-    super(cause);
-  }
-  
-  public RoboCopException(String message, Throwable cause){
-    super(message, cause);
-  }
+    
+    public RoboCopException() {
+        super();
+    }
+    
+    public RoboCopException(String message) {
+        super(message);
+    }
+    
+    public RoboCopException(Throwable cause) {
+        super(cause);
+    }
+    
+    public RoboCopException(String message, Throwable cause) {
+        super(message, cause);
+    }
 }
--- a/config/autoconf.mk.in
+++ b/config/autoconf.mk.in
@@ -288,16 +288,17 @@ MOZ_ENABLE_GNOME_COMPONENT = @MOZ_ENABLE
 MOZ_ENABLE_GIO = @MOZ_ENABLE_GIO@
 MOZ_GIO_CFLAGS = @MOZ_GIO_CFLAGS@
 MOZ_GIO_LIBS = @MOZ_GIO_LIBS@
 
 MOZ_NATIVE_NSPR = @MOZ_NATIVE_NSPR@
 MOZ_NATIVE_NSS = @MOZ_NATIVE_NSS@
 
 MOZ_B2G_RIL = @MOZ_B2G_RIL@
+MOZ_B2G_BT = @MOZ_B2G_BT@
 
 BUILD_CTYPES = @BUILD_CTYPES@
 
 COMPILE_ENVIRONMENT = @COMPILE_ENVIRONMENT@
 CROSS_COMPILE   = @CROSS_COMPILE@
 
 WCHAR_CFLAGS	= @WCHAR_CFLAGS@
 
--- a/configure.in
+++ b/configure.in
@@ -220,17 +220,24 @@ AC_SUBST(COMPILE_ENVIRONMENT)
 
 MOZ_ARG_WITH_STRING(l10n-base,
 [  --with-l10n-base=DIR    path to l10n repositories],
     L10NBASEDIR=$withval)
 if test -n "$L10NBASEDIR"; then
     if test "$L10NBASEDIR" = "yes" -o "$L10NBASEDIR" = "no"; then
         AC_MSG_ERROR([--with-l10n-base must specify a path])
     elif test -d "$L10NBASEDIR"; then
-        L10NBASEDIR=`cd "$L10NBASEDIR" && pwd`
+        case "$host_os" in
+        mingw*)
+            L10NBASEDIR=`cd "$L10NBASEDIR" && pwd -W`
+            ;;
+        *)
+            L10NBASEDIR=`cd "$L10NBASEDIR" && pwd`
+            ;;
+        esac
     else
         AC_MSG_ERROR([Invalid value --with-l10n-base, $L10NBASEDIR doesn't exist])
     fi
 fi
 AC_SUBST(L10NBASEDIR)
 
 dnl Check for Perl first -- needed for win32 SDK checks
 MOZ_PATH_PROGS(PERL, $PERL perl5 perl )
@@ -4915,16 +4922,17 @@ cairo-gonk)
     AC_DEFINE(MOZ_TOUCH)
     MOZ_WIDGET_TOOLKIT=gonk
     TK_CFLAGS='$(MOZ_CAIRO_CFLAGS)'
     TK_LIBS='$(MOZ_CAIRO_LIBS)'
     MOZ_WEBGL=1
     MOZ_PDF_PRINTING=1
     MOZ_B2G_RIL=1
     MOZ_TOUCH=1
+    MOZ_B2G_BT=1
     ;;
 
 esac
 
 AC_SUBST(MOZ_OLD_LINKER)
 AC_SUBST(MOZ_PDF_PRINTING)
 if test "$MOZ_PDF_PRINTING"; then
    PDF_SURFACE_FEATURE="#define CAIRO_HAS_PDF_SURFACE 1"
@@ -7586,16 +7594,28 @@ MOZ_ARG_ENABLE_BOOL(b2g-ril,
     MOZ_B2G_RIL=1,
     MOZ_B2G_RIL= )
 if test -n "$MOZ_B2G_RIL"; then
    AC_DEFINE(MOZ_B2G_RIL)
 fi
 AC_SUBST(MOZ_B2G_RIL)
 
 dnl ========================================================
+dnl = Enable Bluetooth Interface for B2G (Gonk usually)
+dnl ========================================================
+MOZ_ARG_ENABLE_BOOL(b2g-bt,
+[  --enable-b2g-bt      Set compile flags necessary for compiling Bluetooth API for B2G ],
+    MOZ_B2G_BT=1,
+    MOZ_B2G_BT= )
+if test -n "$MOZ_B2G_BT"; then
+   AC_DEFINE(MOZ_B2G_BT)
+fi
+AC_SUBST(MOZ_B2G_BT)
+
+dnl ========================================================
 dnl = Support for demangling undefined symbols
 dnl ========================================================
 if test -z "$SKIP_LIBRARY_CHECKS"; then
     AC_LANG_SAVE
     AC_LANG_CPLUSPLUS
     AC_CHECK_FUNCS(__cxa_demangle, HAVE_DEMANGLE=1, HAVE_DEMANGLE=)
     AC_LANG_RESTORE
 fi
deleted file mode 100644
--- a/content/base/public/nsIMessageWakeupService.idl
+++ /dev/null
@@ -1,74 +0,0 @@
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is mozilla.org code.
- *
- * The Initial Developer of the Original Code is the Mozilla Corporation.
- * Portions created by the Initial Developer are Copyright (C) 2010
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *   Alon Zakai <azakai@mozilla.com>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-#include "nsISupports.idl"
-
-/**
- * This service lets other components be woken up when particular
- * messageManager messages arrive. By using this wakeup service,
- * those components do not need to be started until they are
- * needed.
- *
- * The parentprocessmessagemanager is used for this, so messages
- * send from childprocessmessagemanagers will be heard.
- *
- * Components can request wakeups using the .manifest files, or
- * by someone else calling requestWakeup. For .manifest files,
- * the line should look something like
- *
- *   category wakeup-request nsComponent @mozilla.org/myservice;1,
- *            nsIMyInterface,getService,myMessage1,myMessage2[,..]
- *
- * Currently we require services to expose wrappedJSObject, but
- * that will be cleaned up in bug 593407, at which point the
- * service that will be woken up must implement
- * nsIFrameMessageListener.
- */
-[scriptable, uuid(968e31b6-b859-42f3-8140-014378fe1783)]
-interface nsIMessageWakeupService : nsISupports
-{
-  /**
-   * Requests that the wakeup service wake us up when a particular
-   * message arrives. At that time the service will be woken up
-   * and subscribed to receive further messages of that name as
-   * well.
-   */
-  boolean requestWakeup(in AString aMessageName,
-                        in AString aCid,
-                        in AString aIid,
-                        in AString aMethod);
-};
-
--- a/content/base/src/messageWakeupService.js
+++ b/content/base/src/messageWakeupService.js
@@ -41,17 +41,17 @@ const Ci = Components.interfaces;
 
 const CATEGORY_WAKEUP_REQUEST = "wakeup-request";
 
 function MessageWakeupService() { };
 
 MessageWakeupService.prototype =
 {
   classID:          Components.ID("{f9798742-4f7b-4188-86ba-48b116412b29}"),
-  QueryInterface:   XPCOMUtils.generateQI([Ci.nsIMessageWakeupService, Ci.nsISupports, Ci.nsIObserver]),
+  QueryInterface:   XPCOMUtils.generateQI([Ci.nsIObserver]),
 
   messagesData: [],
 
   get messageManager() {
     if (!this._messageManager)
       this._messageManager = Cc["@mozilla.org/parentprocessmessagemanager;1"].
                              getService(Ci.nsIFrameMessageManager);
     return this._messageManager;
--- a/content/base/src/nsAttrAndChildArray.h
+++ b/content/base/src/nsAttrAndChildArray.h
@@ -131,16 +131,20 @@ public:
 
   bool CanFitMoreAttrs() const
   {
     return AttrSlotCount() < ATTRCHILD_ARRAY_MAX_ATTR_COUNT ||
            !AttrSlotIsTaken(ATTRCHILD_ARRAY_MAX_ATTR_COUNT - 1);
   }
 
   PRInt64 SizeOf() const;
+  bool HasMappedAttrs() const
+  {
+    return MappedAttrCount();
+  }
 
 private:
   nsAttrAndChildArray(const nsAttrAndChildArray& aOther) MOZ_DELETE;
   nsAttrAndChildArray& operator=(const nsAttrAndChildArray& aOther) MOZ_DELETE;
 
   void Clear();
 
   PRUint32 NonMappedAttrCount() const;
--- a/content/base/src/nsAttrValue.cpp
+++ b/content/base/src/nsAttrValue.cpp
@@ -49,16 +49,18 @@
 #include "nsIHTMLDocument.h"
 #include "nsIDocument.h"
 #include "nsContentUtils.h"
 #include "nsReadableUtils.h"
 #include "prprf.h"
 
 namespace css = mozilla::css;
 
+using mozilla::SVGAttrValueWrapper;
+
 #define MISC_STR_PTR(_cont) \
   reinterpret_cast<void*>((_cont)->mStringBits & NS_ATTRVALUE_POINTERVALUE_MASK)
 
 nsTArray<const nsAttrValue::EnumTable*>* nsAttrValue::sEnumTableArray = nsnull;
 
 nsAttrValue::nsAttrValue()
     : mBits(0)
 {
@@ -71,16 +73,22 @@ nsAttrValue::nsAttrValue(const nsAttrVal
 }
 
 nsAttrValue::nsAttrValue(const nsAString& aValue)
     : mBits(0)
 {
   SetTo(aValue);
 }
 
+nsAttrValue::nsAttrValue(nsIAtom* aValue)
+    : mBits(0)
+{
+  SetTo(aValue);
+}
+
 nsAttrValue::nsAttrValue(css::StyleRule* aValue, const nsAString* aSerialized)
     : mBits(0)
 {
   SetTo(aValue, aSerialized);
 }
 
 nsAttrValue::nsAttrValue(const nsIntMargin& aValue)
     : mBits(0)
@@ -255,17 +263,23 @@ nsAttrValue::SetTo(const nsAttrValue& aO
     case eIntMarginValue:
     {
       if (otherCont->mIntMargin)
         cont->mIntMargin = new nsIntMargin(*otherCont->mIntMargin);
       break;
     }
     default:
     {
-      NS_NOTREACHED("unknown type stored in MiscContainer");
+      if (IsSVGType(otherCont->mType)) {
+        // All SVG types are just pointers to classes and will therefore have
+        // the same size so it doesn't really matter which one we assign
+        cont->mSVGAngle = otherCont->mSVGAngle;
+      } else {
+        NS_NOTREACHED("unknown type stored in MiscContainer");
+      }
       break;
     }
   }
 
   void* otherPtr = MISC_STR_PTR(otherCont);
   if (otherPtr) {
     if (static_cast<ValueBaseType>(otherCont->mStringBits & NS_ATTRVALUE_BASETYPE_MASK) ==
         eStringBase) {
@@ -286,23 +300,51 @@ nsAttrValue::SetTo(const nsAString& aVal
   ResetIfSet();
   nsStringBuffer* buf = GetStringBuffer(aValue);
   if (buf) {
     SetPtrValueAndType(buf, eStringBase);
   }
 }
 
 void
+nsAttrValue::SetTo(nsIAtom* aValue)
+{
+  ResetIfSet();
+  if (aValue) {
+    NS_ADDREF(aValue);
+    SetPtrValueAndType(aValue, eAtomBase);
+  }
+}
+
+void
 nsAttrValue::SetTo(PRInt16 aInt)
 {
   ResetIfSet();
   SetIntValueAndType(aInt, eInteger, nsnull);
 }
 
 void
+nsAttrValue::SetTo(PRInt32 aInt, const nsAString* aSerialized)
+{
+  ResetIfSet();
+  SetIntValueAndType(aInt, eInteger, aSerialized);
+}
+
+void
+nsAttrValue::SetTo(double aValue, const nsAString* aSerialized)
+{
+  if (EnsureEmptyMiscContainer()) {
+    MiscContainer* cont = GetMiscContainer();
+    cont->mDoubleValue = aValue;
+    cont->mType = eDoubleValue;
+    SetMiscAtomOrString(aSerialized);
+  }
+}
+
+void
 nsAttrValue::SetTo(css::StyleRule* aValue, const nsAString* aSerialized)
 {
   if (EnsureEmptyMiscContainer()) {
     MiscContainer* cont = GetMiscContainer();
     NS_ADDREF(cont->mCSSStyleRule = aValue);
     cont->mType = eCSSStyleRule;
     SetMiscAtomOrString(aSerialized);
   }
@@ -327,16 +369,125 @@ nsAttrValue::SetToSerialized(const nsAtt
     aOther.ToString(val);
     SetTo(val);
   } else {
     SetTo(aOther);
   }
 }
 
 void
+nsAttrValue::SetTo(const nsSVGAngle& aValue, const nsAString* aSerialized)
+{
+  SetSVGType(eSVGAngle, &aValue, aSerialized);
+}
+
+void
+nsAttrValue::SetTo(const nsSVGIntegerPair& aValue, const nsAString* aSerialized)
+{
+  SetSVGType(eSVGIntegerPair, &aValue, aSerialized);
+}
+
+void
+nsAttrValue::SetTo(const nsSVGLength2& aValue, const nsAString* aSerialized)
+{
+  SetSVGType(eSVGLength, &aValue, aSerialized);
+}
+
+void
+nsAttrValue::SetTo(const mozilla::SVGLengthList& aValue,
+                   const nsAString* aSerialized)
+{
+  // While an empty string will parse as a length list, there's no need to store
+  // it (and SetMiscAtomOrString will assert if we try)
+  if (aSerialized && aSerialized->IsEmpty()) {
+    aSerialized = nsnull;
+  }
+  SetSVGType(eSVGLengthList, &aValue, aSerialized);
+}
+
+void
+nsAttrValue::SetTo(const mozilla::SVGNumberList& aValue,
+                   const nsAString* aSerialized)
+{
+  // While an empty string will parse as a number list, there's no need to store
+  // it (and SetMiscAtomOrString will assert if we try)
+  if (aSerialized && aSerialized->IsEmpty()) {
+    aSerialized = nsnull;
+  }
+  SetSVGType(eSVGNumberList, &aValue, aSerialized);
+}
+
+void
+nsAttrValue::SetTo(const nsSVGNumberPair& aValue, const nsAString* aSerialized)
+{
+  SetSVGType(eSVGNumberPair, &aValue, aSerialized);
+}
+
+void
+nsAttrValue::SetTo(const mozilla::SVGPathData& aValue,
+                   const nsAString* aSerialized)
+{
+  // While an empty string will parse as path data, there's no need to store it
+  // (and SetMiscAtomOrString will assert if we try)
+  if (aSerialized && aSerialized->IsEmpty()) {
+    aSerialized = nsnull;
+  }
+  SetSVGType(eSVGPathData, &aValue, aSerialized);
+}
+
+void
+nsAttrValue::SetTo(const mozilla::SVGPointList& aValue,
+                   const nsAString* aSerialized)
+{
+  // While an empty string will parse as a point list, there's no need to store
+  // it (and SetMiscAtomOrString will assert if we try)
+  if (aSerialized && aSerialized->IsEmpty()) {
+    aSerialized = nsnull;
+  }
+  SetSVGType(eSVGPointList, &aValue, aSerialized);
+}
+
+void
+nsAttrValue::SetTo(const mozilla::SVGAnimatedPreserveAspectRatio& aValue,
+                   const nsAString* aSerialized)
+{
+  SetSVGType(eSVGPreserveAspectRatio, &aValue, aSerialized);
+}
+
+void
+nsAttrValue::SetTo(const mozilla::SVGStringList& aValue,
+                   const nsAString* aSerialized)
+{
+  // While an empty string will parse as a string list, there's no need to store
+  // it (and SetMiscAtomOrString will assert if we try)
+  if (aSerialized && aSerialized->IsEmpty()) {
+    aSerialized = nsnull;
+  }
+  SetSVGType(eSVGStringList, &aValue, aSerialized);
+}
+
+void
+nsAttrValue::SetTo(const mozilla::SVGTransformList& aValue,
+                   const nsAString* aSerialized)
+{
+  // While an empty string will parse as a transform list, there's no need to
+  // store it (and SetMiscAtomOrString will assert if we try)
+  if (aSerialized && aSerialized->IsEmpty()) {
+    aSerialized = nsnull;
+  }
+  SetSVGType(eSVGTransformList, &aValue, aSerialized);
+}
+
+void
+nsAttrValue::SetTo(const nsSVGViewBox& aValue, const nsAString* aSerialized)
+{
+  SetSVGType(eSVGViewBox, &aValue, aSerialized);
+}
+
+void
 nsAttrValue::SwapValueWith(nsAttrValue& aOther)
 {
   PtrBits tmp = aOther.mBits;
   aOther.mBits = mBits;
   mBits = tmp;
 }
 
 void
@@ -423,16 +574,83 @@ nsAttrValue::ToString(nsAString& aResult
       break;
     }
     case eDoubleValue:
     {
       aResult.Truncate();
       aResult.AppendFloat(GetDoubleValue());
       break;
     }
+    case eSVGAngle:
+    {
+      SVGAttrValueWrapper::ToString(GetMiscContainer()->mSVGAngle, aResult);
+      break;
+    }
+    case eSVGIntegerPair:
+    {
+      SVGAttrValueWrapper::ToString(GetMiscContainer()->mSVGIntegerPair,
+                                    aResult);
+      break;
+    }
+    case eSVGLength:
+    {
+      SVGAttrValueWrapper::ToString(GetMiscContainer()->mSVGLength, aResult);
+      break;
+    }
+    case eSVGLengthList:
+    {
+      SVGAttrValueWrapper::ToString(GetMiscContainer()->mSVGLengthList,
+                                    aResult);
+      break;
+    }
+    case eSVGNumberList:
+    {
+      SVGAttrValueWrapper::ToString(GetMiscContainer()->mSVGNumberList,
+                                    aResult);
+      break;
+    }
+    case eSVGNumberPair:
+    {
+      SVGAttrValueWrapper::ToString(GetMiscContainer()->mSVGNumberPair,
+                                    aResult);
+      break;
+    }
+    case eSVGPathData:
+    {
+      SVGAttrValueWrapper::ToString(GetMiscContainer()->mSVGPathData, aResult);
+      break;
+    }
+    case eSVGPointList:
+    {
+      SVGAttrValueWrapper::ToString(GetMiscContainer()->mSVGPointList, aResult);
+      break;
+    }
+    case eSVGPreserveAspectRatio:
+    {
+      SVGAttrValueWrapper::ToString(GetMiscContainer()->mSVGPreserveAspectRatio,
+                                    aResult);
+      break;
+    }
+    case eSVGStringList:
+    {
+      SVGAttrValueWrapper::ToString(GetMiscContainer()->mSVGStringList,
+                                    aResult);
+      break;
+    }
+    case eSVGTransformList:
+    {
+      SVGAttrValueWrapper::ToString(GetMiscContainer()->mSVGTransformList,
+                                    aResult);
+      break;
+    }
+    case eSVGViewBox:
+    {
+      SVGAttrValueWrapper::ToString(GetMiscContainer()->mSVGViewBox, aResult);
+      break;
+    }
     default:
     {
       aResult.Truncate();
       break;
     }
   }
 }
 
@@ -609,16 +827,20 @@ nsAttrValue::HashValue() const
       return cont->mDoubleValue;
     }
     case eIntMarginValue:
     {
       return NS_PTR_TO_INT32(cont->mIntMargin);
     }
     default:
     {
+      if (IsSVGType(cont->mType)) {
+        // All SVG types are just pointers to classes so we can treat them alike
+        return NS_PTR_TO_INT32(cont->mSVGAngle);
+      }
       NS_NOTREACHED("unknown type stored in MiscContainer");
       return 0;
     }
   }
 }
 
 bool
 nsAttrValue::Equals(const nsAttrValue& aOther) const
@@ -701,16 +923,26 @@ nsAttrValue::Equals(const nsAttrValue& a
       return thisCont->mDoubleValue == otherCont->mDoubleValue;
     }
     case eIntMarginValue:
     {
       return thisCont->mIntMargin == otherCont->mIntMargin;
     }
     default:
     {
+      if (IsSVGType(thisCont->mType)) {
+        // Currently this method is never called for nsAttrValue objects that
+        // point to SVG data types.
+        // If that changes then we probably want to add methods to the
+        // corresponding SVG types to compare their base values.
+        // As a shortcut, however, we can begin by comparing the pointers.
+        NS_ABORT_IF_FALSE(false, "Comparing nsAttrValues that point to SVG "
+          "data");
+        return false;
+      }
       NS_NOTREACHED("unknown type stored in MiscContainer");
       return false;
     }
   }
   if (needsStringComparison) {
     if (thisCont->mStringBits == otherCont->mStringBits) {
       return true;
     }
@@ -1346,16 +1578,32 @@ nsAttrValue::ResetMiscAtomOrString()
       static_cast<nsStringBuffer*>(ptr)->Release();
     } else {
       static_cast<nsIAtom*>(ptr)->Release();
     }
     cont->mStringBits = 0;
   }
 }
 
+void
+nsAttrValue::SetSVGType(ValueType aType, const void* aValue,
+                        const nsAString* aSerialized) {
+  NS_ABORT_IF_FALSE(IsSVGType(aType), "Not an SVG type");
+  if (EnsureEmptyMiscContainer()) {
+    MiscContainer* cont = GetMiscContainer();
+    // All SVG types are just pointers to classes so just setting any of them
+    // will do. We'll lose type-safety but the signature of the calling
+    // function should ensure we don't get anything unexpected, and once we
+    // stick aValue in a union we lose type information anyway.
+    cont->mSVGAngle = static_cast<const nsSVGAngle*>(aValue);
+    cont->mType = aType;
+    SetMiscAtomOrString(aSerialized);
+  }
+}
+
 bool
 nsAttrValue::EnsureEmptyMiscContainer()
 {
   MiscContainer* cont;
   if (BaseType() == eOtherBase) {
     ResetMiscAtomOrString();
     cont = GetMiscContainer();
     switch (cont->mType) {
--- a/content/base/src/nsAttrValue.h
+++ b/content/base/src/nsAttrValue.h
@@ -46,16 +46,17 @@
 
 #include "nscore.h"
 #include "nsString.h"
 #include "nsStringBuffer.h"
 #include "nsColor.h"
 #include "nsCaseTreatment.h"
 #include "nsMargin.h"
 #include "nsCOMPtr.h"
+#include "SVGAttrValueWrapper.h"
 
 typedef PRUptrdiff PtrBits;
 class nsAString;
 class nsIAtom;
 class nsIDocument;
 template<class E, class A> class nsTArray;
 struct nsTArrayDefaultAllocator;
 
@@ -97,16 +98,17 @@ public:
 
 class nsAttrValue {
 public:
   typedef nsTArray< nsCOMPtr<nsIAtom> > AtomArray;
 
   nsAttrValue();
   nsAttrValue(const nsAttrValue& aOther);
   explicit nsAttrValue(const nsAString& aValue);
+  explicit nsAttrValue(nsIAtom* aValue);
   nsAttrValue(mozilla::css::StyleRule* aValue, const nsAString* aSerialized);
   explicit nsAttrValue(const nsIntMargin& aValue);
   ~nsAttrValue();
 
   inline const nsAttrValue& operator=(const nsAttrValue& aOther);
 
   static nsresult Init();
   static void Shutdown();
@@ -118,30 +120,64 @@ public:
     eAtom =         0x02, //   10
     eInteger =      0x03, // 0011
     eColor =        0x07, // 0111
     eEnum =         0x0B, // 1011  This should eventually die
     ePercent =      0x0F, // 1111
     // Values below here won't matter, they'll be always stored in the 'misc'
     // struct.
     eCSSStyleRule =    0x10
-    ,eAtomArray =      0x11 
+    ,eAtomArray =      0x11
     ,eDoubleValue  =   0x12
     ,eIntMarginValue = 0x13
+    ,eSVGTypesBegin =  0x14
+    ,eSVGAngle =       eSVGTypesBegin
+    ,eSVGIntegerPair = 0x15
+    ,eSVGLength =      0x16
+    ,eSVGLengthList =  0x17
+    ,eSVGNumberList =  0x18
+    ,eSVGNumberPair =  0x19
+    ,eSVGPathData   =  0x20
+    ,eSVGPointList  =  0x21
+    ,eSVGPreserveAspectRatio = 0x22
+    ,eSVGStringList =  0x23
+    ,eSVGTransformList = 0x24
+    ,eSVGViewBox =     0x25
+    ,eSVGTypesEnd =    0x34
   };
 
   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(PRInt32 aInt, const nsAString* aSerialized);
+  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::SVGPathData& aValue, const nsAString* aSerialized);
+  void SetTo(const mozilla::SVGPointList& aValue, const nsAString* aSerialized);
+  void SetTo(const mozilla::SVGAnimatedPreserveAspectRatio& aValue,
+             const nsAString* aSerialized);
+  void SetTo(const mozilla::SVGStringList& aValue,
+             const nsAString* aSerialized);
+  void SetTo(const mozilla::SVGTransformList& aValue,
+             const nsAString* aSerialized);
+  void SetTo(const nsSVGViewBox& 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.
    */
@@ -363,36 +399,51 @@ private:
       PRInt32 mInteger;
       nscolor mColor;
       PRUint32 mEnumValue;
       PRInt32 mPercent;
       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::SVGPathData* mSVGPathData;
+      const mozilla::SVGPointList* mSVGPointList;
+      const mozilla::SVGAnimatedPreserveAspectRatio* mSVGPreserveAspectRatio;
+      const mozilla::SVGStringList* mSVGStringList;
+      const mozilla::SVGTransformList* mSVGTransformList;
+      const nsSVGViewBox* mSVGViewBox;
     };
   };
 
   inline ValueBaseType BaseType() const;
+  inline bool IsSVGType(ValueType aType) const;
 
   /**
    * Get the index of an EnumTable in the sEnumTableArray.
    * If the EnumTable is not in the sEnumTableArray, it is added.
    *
    * @param aTable   the EnumTable to get the index of.
    * @return         the index of the EnumTable.
    */
   PRInt16  GetEnumTableIndex(const EnumTable* aTable);
 
   inline void SetPtrValueAndType(void* aValue, ValueBaseType aType);
   void SetIntValueAndType(PRInt32 aValue, ValueType aType,
                           const nsAString* aStringValue);
   void SetColorValue(nscolor aColor, const nsAString& aString);
   void SetMiscAtomOrString(const nsAString* aValue);
   void ResetMiscAtomOrString();
+  void SetSVGType(ValueType aType, const void* aValue,
+                  const nsAString* aSerialized);
   inline void ResetIfSet();
 
   inline void* GetPtr() const;
   inline MiscContainer* GetMiscContainer() const;
   inline PRInt32 GetIntInternal() const;
 
   bool EnsureEmptyMiscContainer();
   bool EnsureEmptyAtomArray();
@@ -497,16 +548,22 @@ nsAttrValue::GetIntMarginValue(nsIntMarg
 }
 
 inline nsAttrValue::ValueBaseType
 nsAttrValue::BaseType() const
 {
   return static_cast<ValueBaseType>(mBits & NS_ATTRVALUE_BASETYPE_MASK);
 }
 
+inline bool
+nsAttrValue::IsSVGType(ValueType aType) const
+{
+  return aType >= eSVGTypesBegin && aType <= eSVGTypesEnd;
+}
+
 inline void
 nsAttrValue::SetPtrValueAndType(void* aValue, ValueBaseType aType)
 {
   NS_ASSERTION(!(NS_PTR_TO_INT32(aValue) & ~NS_ATTRVALUE_POINTERVALUE_MASK),
                "pointer not properly aligned, this will crash");
   mBits = reinterpret_cast<PtrBits>(aValue) | aType;
 }
 
--- a/content/base/src/nsEventSource.cpp
+++ b/content/base/src/nsEventSource.cpp
@@ -84,16 +84,17 @@ using namespace mozilla;
 #define MAX_RECONNECTION_TIME_VALUE       PR_IntervalToMilliseconds(DELAY_INTERVAL_LIMIT)
 
 nsEventSource::nsEventSource() :
   mStatus(PARSE_STATE_OFF),
   mFrozen(false),
   mErrorLoadOnRedirect(false),
   mGoingToDispatchAllMessages(false),
   mWithCredentials(false),
+  mWaitingForOnStopRequest(false),
   mLastConvertionResult(NS_OK),
   mReadyState(nsIEventSource::CONNECTING),
   mScriptLine(0),
   mInnerWindowID(0)
 {
 }
 
 nsEventSource::~nsEventSource()
@@ -103,23 +104,29 @@ nsEventSource::~nsEventSource()
 
 //-----------------------------------------------------------------------------
 // nsEventSource::nsISupports
 //-----------------------------------------------------------------------------
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsEventSource)
 
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsEventSource)
-  if (tmp->IsBlack()) {
+  bool isBlack = tmp->IsBlack();
+  if (isBlack || tmp->mWaitingForOnStopRequest) {
     if (tmp->mListenerManager) {
       tmp->mListenerManager->UnmarkGrayJSListeners();
       NS_UNMARK_LISTENER_WRAPPER(Open)
       NS_UNMARK_LISTENER_WRAPPER(Message)
       NS_UNMARK_LISTENER_WRAPPER(Error)
     }
+    if (!isBlack) {
+      xpc_UnmarkGrayObject(tmp->PreservingWrapper() ? 
+                           tmp->GetWrapperPreserveColor() :
+                           tmp->GetExpandoObjectPreserveColor());
+    }
     return true;
   }
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
 
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsEventSource)
   return tmp->IsBlack();
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
 
@@ -594,16 +601,18 @@ nsEventSource::OnDataAvailable(nsIReques
                                     aCount, &totalRead);
 }
 
 NS_IMETHODIMP
 nsEventSource::OnStopRequest(nsIRequest *aRequest,
                              nsISupports *aContext,
                              nsresult aStatusCode)
 {
+  mWaitingForOnStopRequest = false;
+
   if (mReadyState == nsIEventSource::CLOSED) {
     return NS_ERROR_ABORT;
   }
 
   if (NS_FAILED(aStatusCode)) {
     DispatchFailConnection();
     return aStatusCode;
   }
@@ -944,17 +953,21 @@ nsEventSource::InitChannelAndRequestEven
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIStreamListener> listener =
     new nsCORSListenerProxy(this, mPrincipal, mHttpChannel,
                             mWithCredentials, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Start reading from the channel
-  return mHttpChannel->AsyncOpen(listener, nsnull);
+  rv = mHttpChannel->AsyncOpen(listener, nsnull);
+  if (NS_SUCCEEDED(rv)) {
+    mWaitingForOnStopRequest = true;
+  }
+  return rv;
 }
 
 void
 nsEventSource::AnnounceConnection()
 {
   if (mReadyState == nsIEventSource::CLOSED) {
     return;
   }
--- a/content/base/src/nsEventSource.h
+++ b/content/base/src/nsEventSource.h
@@ -211,16 +211,17 @@ protected:
     PARSE_STATE_BEGIN_OF_LINE
   };
   ParserStatus mStatus;
 
   bool mFrozen;
   bool mErrorLoadOnRedirect;
   bool mGoingToDispatchAllMessages;
   bool mWithCredentials;
+  bool mWaitingForOnStopRequest;
 
   // used while reading the input streams
   nsCOMPtr<nsIUnicodeDecoder> mUnicodeDecoder;
   nsresult mLastConvertionResult;
   nsString mLastFieldName;
   nsString mLastFieldValue;
 
   nsRefPtr<nsDOMEventListenerWrapper> mOnOpenListener;
--- a/content/base/src/nsWebSocket.cpp
+++ b/content/base/src/nsWebSocket.cpp
@@ -434,24 +434,30 @@ nsWebSocket::~nsWebSocket()
 
   Disconnect();
   nsLayoutStatics::Release();
 }
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsWebSocket)
 
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsWebSocket)
-  if (tmp->IsBlack()) {
+  bool isBlack = tmp->IsBlack();
+  if (isBlack|| tmp->mKeepingAlive) {
     if (tmp->mListenerManager) {
       tmp->mListenerManager->UnmarkGrayJSListeners();
       NS_UNMARK_LISTENER_WRAPPER(Open)
       NS_UNMARK_LISTENER_WRAPPER(Error)
       NS_UNMARK_LISTENER_WRAPPER(Message)
       NS_UNMARK_LISTENER_WRAPPER(Close)
     }
+    if (!isBlack) {
+      xpc_UnmarkGrayObject(tmp->PreservingWrapper() ? 
+                           tmp->GetWrapperPreserveColor() :
+                           tmp->GetExpandoObjectPreserveColor());
+    }
     return true;
   }
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
 
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsWebSocket)
   return tmp->IsBlack();
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
 
--- a/content/base/src/nsXMLHttpRequest.cpp
+++ b/content/base/src/nsXMLHttpRequest.cpp
@@ -440,18 +440,18 @@ NS_IMPL_RELEASE_INHERITED(nsXMLHttpReque
 nsXMLHttpRequest::nsXMLHttpRequest()
   : mResponseBodyDecodedPos(0),
     mResponseType(XML_HTTP_RESPONSE_TYPE_DEFAULT),
     mRequestObserver(nsnull), mState(XML_HTTP_REQUEST_UNSENT),
     mUploadTransferred(0), mUploadTotal(0), mUploadComplete(true),
     mProgressSinceLastProgressEvent(false),
     mUploadProgress(0), mUploadProgressMax(0),
     mRequestSentTime(0), mTimeoutMilliseconds(0),
-    mErrorLoad(false), mProgressTimerIsActive(false),
-    mProgressEventWasDelayed(false),
+    mErrorLoad(false), mWaitingForOnStopRequest(false),
+    mProgressTimerIsActive(false), mProgressEventWasDelayed(false),
     mIsHtml(false),
     mWarnAboutMultipartHtml(false),
     mWarnAboutSyncHtml(false),
     mLoadLengthComputable(false), mLoadTotal(0),
     mFirstStartRequestSeen(false),
     mInLoadProgressEvent(false),
     mResultJSON(JSVAL_VOID),
     mResultArrayBuffer(nsnull)
@@ -597,28 +597,34 @@ void
 nsXMLHttpRequest::SetRequestObserver(nsIRequestObserver* aObserver)
 {
   mRequestObserver = aObserver;
 }
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsXMLHttpRequest)
 
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsXMLHttpRequest)
-  if (tmp->IsBlack()) {
+  bool isBlack = tmp->IsBlack();
+  if (isBlack || tmp->mWaitingForOnStopRequest) {
     if (tmp->mListenerManager) {
       tmp->mListenerManager->UnmarkGrayJSListeners();
       NS_UNMARK_LISTENER_WRAPPER(Load)
       NS_UNMARK_LISTENER_WRAPPER(Error)
       NS_UNMARK_LISTENER_WRAPPER(Abort)
       NS_UNMARK_LISTENER_WRAPPER(LoadStart)
       NS_UNMARK_LISTENER_WRAPPER(Progress)
       NS_UNMARK_LISTENER_WRAPPER(Loadend)
       NS_UNMARK_LISTENER_WRAPPER(UploadProgress)
       NS_UNMARK_LISTENER_WRAPPER(Readystatechange)
     }
+    if (!isBlack) {
+      xpc_UnmarkGrayObject(tmp->PreservingWrapper() ? 
+                           tmp->GetWrapperPreserveColor() :
+                           tmp->GetExpandoObjectPreserveColor());
+    }
     return true;
   }
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
 
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsXMLHttpRequest)
   return tmp->IsBlack();
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
 
@@ -2135,16 +2141,18 @@ nsXMLHttpRequest::OnStartRequest(nsIRequ
 NS_IMETHODIMP
 nsXMLHttpRequest::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult status)
 {
   SAMPLE_LABEL("content", "nsXMLHttpRequest::OnStopRequest");
   if (!IsSameOrBaseChannel(request, mChannel)) {
     return NS_OK;
   }
 
+  mWaitingForOnStopRequest = false;
+
   nsresult rv = NS_OK;
 
   // If we're loading a multipart stream of XML documents, we'll get
   // an OnStopRequest() for the last part in the stream, and then
   // another one for the end of the initiating
   // "multipart/x-mixed-replace" stream too. So we must check that we
   // still have an xml parser stream listener before accessing it
   // here.
@@ -2769,16 +2777,19 @@ nsXMLHttpRequest::Send(nsIVariant *aBody
 
   if (NS_FAILED(rv)) {
     // Drop our ref to the channel to avoid cycles
     mChannel = nsnull;
     mCORSPreflightChannel = nsnull;
     return rv;
   }
 
+  // Either AsyncOpen was called, or CORS will open the channel later.
+  mWaitingForOnStopRequest = true;
+
   // If we're synchronous, spin an event loop here and wait
   if (!(mState & XML_HTTP_REQUEST_ASYNC)) {
     mState |= XML_HTTP_REQUEST_SYNCLOOPING;
 
     nsCOMPtr<nsIDocument> suspendedDoc;
     nsCOMPtr<nsIRunnable> resumeTimeoutRunnable;
     if (mOwner) {
       nsCOMPtr<nsIDOMWindow> topWindow;
--- a/content/base/src/nsXMLHttpRequest.h
+++ b/content/base/src/nsXMLHttpRequest.h
@@ -360,17 +360,17 @@ protected:
   // Timeout support
   PRTime mRequestSentTime;
   PRUint32 mTimeoutMilliseconds;
   nsCOMPtr<nsITimer> mTimeoutTimer;
   void StartTimeoutTimer();
   void HandleTimeoutCallback();
 
   bool mErrorLoad;
-
+  bool mWaitingForOnStopRequest;
   bool mProgressTimerIsActive;
   bool mProgressEventWasDelayed;
   bool mIsHtml;
   bool mWarnAboutMultipartHtml;
   bool mWarnAboutSyncHtml;
   bool mLoadLengthComputable;
   PRUint64 mLoadTotal; // 0 if not known.
   PRUint64 mLoadTransferred;
--- a/content/base/test/test_bug338679.html
+++ b/content/base/test/test_bug338679.html
@@ -44,17 +44,17 @@ function attr_modified(ev) {
   is(ev.newValue, e_new,
      phase.name + (recursive ? " recursive" : "") + ": newValue");
   is(ev.prevValue, e_prev,
      phase.name + (recursive ? " recursive" : "") + ": prevValue");
 
   e_prev = e_new;
   if (!recursive) {
     recursive = true;
-    e_new = "width: 0pt;";
+    e_new = "width: 0px;";
     testDiv.style.width = "0";
   } else {
     recursive = false;
     setTimeout(nextTest, 0);
   }
 }
 
 /* tests */
--- a/content/canvas/src/WebGLContext.h
+++ b/content/canvas/src/WebGLContext.h
@@ -2383,16 +2383,17 @@ class WebGLExtension
 {
 public:
     WebGLExtension(WebGLContext *baseContext)
         : WebGLContextBoundObject(baseContext)
     {}
 
     NS_DECL_ISUPPORTS
     NS_DECL_NSIWEBGLEXTENSION
+    virtual ~WebGLExtension() {}
 };
 
 inline const WebGLRectangleObject *WebGLContext::FramebufferRectangleObject() const {
     return mBoundFramebuffer ? mBoundFramebuffer->RectangleObject()
                              : static_cast<const WebGLRectangleObject*>(this);
 }
 
 /**
--- a/content/html/content/src/nsDOMValidityState.h
+++ b/content/html/content/src/nsDOMValidityState.h
@@ -37,17 +37,17 @@
 
 #ifndef nsDOMValidityState_h__
 #define nsDOMValidityState_h__
 
 #include "nsIDOMValidityState.h"
 #include "nsIConstraintValidation.h"
 
 
-class nsDOMValidityState : public nsIDOMValidityState
+class nsDOMValidityState MOZ_FINAL : public nsIDOMValidityState
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIDOMVALIDITYSTATE
 
   friend class nsIConstraintValidation;
 
 protected:
--- a/content/html/content/src/nsHTMLInputElement.cpp
+++ b/content/html/content/src/nsHTMLInputElement.cpp
@@ -180,17 +180,17 @@ static const nsAttrValue::EnumTable* kIn
 #define NS_INPUT_ELEMENT_STATE_IID                 \
 { /* dc3b3d14-23e2-4479-b513-7b369343e3a0 */       \
   0xdc3b3d14,                                      \
   0x23e2,                                          \
   0x4479,                                          \
   {0xb5, 0x13, 0x7b, 0x36, 0x93, 0x43, 0xe3, 0xa0} \
 }
 
-class nsHTMLInputElementState : public nsISupports
+class nsHTMLInputElementState MOZ_FINAL : public nsISupports
 {
   public:
     NS_DECLARE_STATIC_IID_ACCESSOR(NS_INPUT_ELEMENT_STATE_IID)
     NS_DECL_ISUPPORTS
 
     bool IsCheckedSet() {
       return mCheckedSet;
     }
--- a/content/html/content/src/nsHTMLInputElement.h
+++ b/content/html/content/src/nsHTMLInputElement.h
@@ -52,17 +52,17 @@
 #include "nsHTMLFormElement.h" // for ShouldShowInvalidUI()
 #include "nsIFile.h"
 
 class nsDOMFileList;
 class nsIRadioGroupContainer;
 class nsIRadioGroupVisitor;
 class nsIRadioVisitor;
 
-class UploadLastDir : public nsIObserver, public nsSupportsWeakReference {
+class UploadLastDir MOZ_FINAL : public nsIObserver, public nsSupportsWeakReference {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
 
   /**
    * Fetch the last used directory for this location from the content
    * pref service, if it is available.
    *
--- a/content/html/content/src/nsHTMLMediaElement.cpp
+++ b/content/html/content/src/nsHTMLMediaElement.cpp
@@ -238,20 +238,20 @@ public:
 };
 
 /**
  * There is a reference cycle involving this class: MediaLoadListener
  * holds a reference to the nsHTMLMediaElement, which holds a reference
  * to an nsIChannel, which holds a reference to this listener.
  * We break the reference cycle in OnStartRequest by clearing mElement.
  */
-class nsHTMLMediaElement::MediaLoadListener : public nsIStreamListener,
-                                              public nsIChannelEventSink,
-                                              public nsIInterfaceRequestor,
-                                              public nsIObserver
+class nsHTMLMediaElement::MediaLoadListener MOZ_FINAL : public nsIStreamListener,
+                                                        public nsIChannelEventSink,
+                                                        public nsIInterfaceRequestor,
+                                                        public nsIObserver
 {
   NS_DECL_ISUPPORTS
   NS_DECL_NSIREQUESTOBSERVER
   NS_DECL_NSISTREAMLISTENER
   NS_DECL_NSICHANNELEVENTSINK
   NS_DECL_NSIOBSERVER
   NS_DECL_NSIINTERFACEREQUESTOR
 
--- a/content/html/content/src/nsMediaError.h
+++ b/content/html/content/src/nsMediaError.h
@@ -32,18 +32,19 @@
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 #include "nsIDOMMediaError.h"
 #include "nsISupports.h"
+#include "mozilla/Attributes.h"
 
-class nsMediaError : public nsIDOMMediaError
+class nsMediaError MOZ_FINAL : public nsIDOMMediaError
 {
 public:
   nsMediaError(PRUint16 aCode);
 
   // nsISupports
   NS_DECL_ISUPPORTS
 
   // nsIDOMMediaError
--- a/content/html/content/src/nsTimeRanges.h
+++ b/content/html/content/src/nsTimeRanges.h
@@ -40,17 +40,17 @@
 #define nsTimeRanges_h__
 
 #include "nsIDOMTimeRanges.h"
 #include "nsISupports.h"
 #include "nsTArray.h"
 
 // Implements media TimeRanges:
 // http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html#timeranges
-class nsTimeRanges : public nsIDOMTimeRanges {
+class nsTimeRanges MOZ_FINAL : public nsIDOMTimeRanges {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIDOMTIMERANGES
 
   nsTimeRanges();
   ~nsTimeRanges();
 
   void Add(double aStart, double aEnd);
--- a/content/media/nsBuiltinDecoder.cpp
+++ b/content/media/nsBuiltinDecoder.cpp
@@ -211,16 +211,17 @@ nsresult nsBuiltinDecoder::Load(MediaRes
                                            cloneDonor->mDecoderStateMachine : nsnull))) {
     LOG(PR_LOG_DEBUG, ("%p Failed to init state machine!", this));
     return NS_ERROR_FAILURE;
   }
   {
     ReentrantMonitorAutoEnter mon(mReentrantMonitor);
     mDecoderStateMachine->SetSeekable(mSeekable);
     mDecoderStateMachine->SetDuration(mDuration);
+    mDecoderStateMachine->SetVolume(mInitialVolume);
     
     if (mFrameBufferLength > 0) {
       // The valid mFrameBufferLength value was specified earlier
       mDecoderStateMachine->SetFrameBufferLength(mFrameBufferLength);
     }
   }
 
   ChangeState(PLAY_STATE_LOADING);
--- 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/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/DOMSVGPathSeg.cpp
+++ b/content/svg/content/src/DOMSVGPathSeg.cpp
@@ -240,19 +240,23 @@ DOMSVGPathSeg::IndexIsValid()
   NS_IMETHODIMP                                                               \
   DOMSVGPathSeg##segName::Set##propName(type a##propName)                     \
   {                                                                           \
     if (mIsAnimValItem) {                                                     \
       return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;                        \
     }                                                                         \
     NS_ENSURE_FINITE(float(a##propName), NS_ERROR_ILLEGAL_VALUE);             \
     if (HasOwner()) {                                                         \
+      if (InternalItem()[1+index] == float(a##propName)) {                    \
+        return NS_OK;                                                         \
+      }                                                                       \
+      NS_ABORT_IF_FALSE(IsInList(), "Will/DidChangePathSegList() is wrong");  \
+      nsAttrValue emptyOrOldValue = Element()->WillChangePathSegList();       \
       InternalItem()[1+index] = float(a##propName);                           \
-      NS_ABORT_IF_FALSE(IsInList(), "DidChangePathSegList() is wrong");       \
-      Element()->DidChangePathSegList(true);                               \
+      Element()->DidChangePathSegList(emptyOrOldValue);                       \
       if (mList->AttrIsAnimating()) {                                         \
         Element()->AnimationNeedsResample();                                  \
       }                                                                       \
     } else {                                                                  \
       mArgs[index] = float(a##propName);                                      \
     }                                                                         \
     return NS_OK;                                                             \
   }
--- a/content/svg/content/src/DOMSVGPathSegList.cpp
+++ b/content/svg/content/src/DOMSVGPathSegList.cpp
@@ -264,33 +264,34 @@ DOMSVGPathSegList::GetNumberOfItems(PRUi
 NS_IMETHODIMP
 DOMSVGPathSegList::Clear()
 {
   if (IsAnimValList()) {
     return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
   }
 
   if (Length() > 0) {
+    nsAttrValue emptyOrOldValue = Element()->WillChangePathSegList();
     // DOM list items that are to be removed must be removed before we change
     // the internal list, otherwise they wouldn't be able to copy their
     // internal counterparts' values!
 
     InternalListWillChangeTo(SVGPathData()); // clears mItems
 
     if (!AttrIsAnimating()) {
       // The anim val list is in sync with the base val list
       DOMSVGPathSegList *animList =
         GetDOMWrapperIfExists(InternalAList().GetAnimValKey());
       if (animList) {
         animList->InternalListWillChangeTo(SVGPathData()); // clears its mItems
       }
     }
 
     InternalList().Clear();
-    Element()->DidChangePathSegList(true);
+    Element()->DidChangePathSegList(emptyOrOldValue);
     if (AttrIsAnimating()) {
       Element()->AnimationNeedsResample();
     }
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -366,33 +367,34 @@ DOMSVGPathSegList::InsertItemBefore(nsID
   PRUint32 argCount = SVGPathSegUtils::ArgCountForType(domItem->Type());
 
   // Ensure we have enough memory so we can avoid complex error handling below:
   if (!mItems.SetCapacity(mItems.Length() + 1) ||
       !InternalList().mData.SetCapacity(InternalList().mData.Length() + 1 + argCount)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
+  nsAttrValue emptyOrOldValue = Element()->WillChangePathSegList();
   // Now that we know we're inserting, keep animVal list in sync as necessary.
   MaybeInsertNullInAnimValListAt(aIndex, internalIndex, argCount);
 
   float segAsRaw[1 + NS_SVG_PATH_SEG_MAX_ARGS];
   domItem->ToSVGPathSegEncodedData(segAsRaw);
 
   InternalList().mData.InsertElementsAt(internalIndex, segAsRaw, 1 + argCount);
   mItems.InsertElementAt(aIndex, ItemProxy(domItem.get(), internalIndex));
 
   // 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, aIndex, IsAnimValList());
 
   UpdateListIndicesFromIndex(aIndex + 1, argCount + 1);
 
-  Element()->DidChangePathSegList(true);
+  Element()->DidChangePathSegList(emptyOrOldValue);
   if (AttrIsAnimating()) {
     Element()->AnimationNeedsResample();
   }
   *_retval = domItem.forget().get();
   return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -411,16 +413,17 @@ DOMSVGPathSegList::ReplaceItem(nsIDOMSVG
   }
   if (aIndex >= Length()) {
     return NS_ERROR_DOM_INDEX_SIZE_ERR;
   }
   if (domItem->HasOwner()) {
     domItem = domItem->Clone(); // must do this before changing anything!
   }
 
+  nsAttrValue emptyOrOldValue = Element()->WillChangePathSegList();
   if (ItemAt(aIndex)) {
     // Notify any existing DOM item of removal *before* modifying the lists so
     // that the DOM item can copy the *old* value at its index:
     ItemAt(aIndex)->RemovingFromList();
   }
 
   PRUint32 internalIndex = mItems[aIndex].mInternalDataIndex;
   // We use InternalList() to get oldArgCount since we may not have a DOM
@@ -446,17 +449,17 @@ DOMSVGPathSegList::ReplaceItem(nsIDOMSVG
 
   PRUint32 delta = newArgCount - oldArgCount;
   if (delta != 0) {
     for (PRUint32 i = aIndex + 1; i < Length(); ++i) {
       mItems[i].mInternalDataIndex += delta;
     }
   }
 
-  Element()->DidChangePathSegList(true);
+  Element()->DidChangePathSegList(emptyOrOldValue);
   if (AttrIsAnimating()) {
     Element()->AnimationNeedsResample();
   }
   NS_ADDREF(*_retval = domItem.get());
   return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -469,16 +472,17 @@ DOMSVGPathSegList::RemoveItem(PRUint32 a
   }
 
   if (aIndex >= Length()) {
     return NS_ERROR_DOM_INDEX_SIZE_ERR;
   }
   // We have to return the removed item, so make sure it exists:
   EnsureItemAt(aIndex);
 
+  nsAttrValue emptyOrOldValue = Element()->WillChangePathSegList();
   // Notify the DOM item of removal *before* modifying the lists so that the
   // DOM item can copy its *old* value:
   ItemAt(aIndex)->RemovingFromList();
   NS_ADDREF(*_retval = ItemAt(aIndex));
 
   PRUint32 internalIndex = mItems[aIndex].mInternalDataIndex;
   PRUint32 segType = SVGPathSegUtils::DecodeType(InternalList().mData[internalIndex]);
   PRUint32 argCount = SVGPathSegUtils::ArgCountForType(segType);
@@ -488,17 +492,17 @@ DOMSVGPathSegList::RemoveItem(PRUint32 a
   // internal value.
   MaybeRemoveItemFromAnimValListAt(aIndex, argCount);
 
   InternalList().mData.RemoveElementsAt(internalIndex, 1 + argCount);
   mItems.RemoveElementAt(aIndex);
 
   UpdateListIndicesFromIndex(aIndex, -(argCount + 1));
 
-  Element()->DidChangePathSegList(true);
+  Element()->DidChangePathSegList(emptyOrOldValue);
   if (AttrIsAnimating()) {
     Element()->AnimationNeedsResample();
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 DOMSVGPathSegList::AppendItem(nsIDOMSVGPathSeg *aNewItem,
--- a/content/svg/content/src/DOMSVGPoint.cpp
+++ b/content/svg/content/src/DOMSVGPoint.cpp
@@ -93,18 +93,22 @@ DOMSVGPoint::SetX(float aX)
 {
   if (mIsAnimValItem || mIsReadonly) {
     return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
   }
 
   NS_ENSURE_FINITE(aX, NS_ERROR_ILLEGAL_VALUE);
 
   if (HasOwner()) {
+    if (InternalItem().mX == aX) {
+      return NS_OK;
+    }
+    nsAttrValue emptyOrOldValue = Element()->WillChangePointList();
     InternalItem().mX = aX;
-    Element()->DidChangePointList(true);
+    Element()->DidChangePointList(emptyOrOldValue);
     if (mList->AttrIsAnimating()) {
       Element()->AnimationNeedsResample();
     }
     return NS_OK;
   }
   mPt.mX = aX;
   return NS_OK;
 }
@@ -124,18 +128,22 @@ DOMSVGPoint::SetY(float aY)
 {
   if (mIsAnimValItem || mIsReadonly) {
     return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
   }
 
   NS_ENSURE_FINITE(aY, NS_ERROR_ILLEGAL_VALUE);
 
   if (HasOwner()) {
+    if (InternalItem().mY == aY) {
+      return NS_OK;
+    }
+    nsAttrValue emptyOrOldValue = Element()->WillChangePointList();
     InternalItem().mY = aY;
-    Element()->DidChangePointList(true);
+    Element()->DidChangePointList(emptyOrOldValue);
     if (mList->AttrIsAnimating()) {
       Element()->AnimationNeedsResample();
     }
     return NS_OK;
   }
   mPt.mY = aY;
   return NS_OK;
 }
--- a/content/svg/content/src/DOMSVGPointList.cpp
+++ b/content/svg/content/src/DOMSVGPointList.cpp
@@ -208,33 +208,34 @@ DOMSVGPointList::GetNumberOfItems(PRUint
 NS_IMETHODIMP
 DOMSVGPointList::Clear()
 {
   if (IsAnimValList()) {
     return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
   }
 
   if (Length() > 0) {
+    nsAttrValue emptyOrOldValue = Element()->WillChangePointList();
     // DOM list items that are to be removed must be removed before we change
     // the internal list, otherwise they wouldn't be able to copy their
     // internal counterparts' values!
 
     InternalListWillChangeTo(SVGPointList()); // clears mItems
 
     if (!AttrIsAnimating()) {
       // The anim val list is in sync with the base val list
       DOMSVGPointList *animList =
         GetDOMWrapperIfExists(InternalAList().GetAnimValKey());
       if (animList) {
         animList->InternalListWillChangeTo(SVGPointList()); // clears its mItems
       }
     }
 
     InternalList().Clear();
-    Element()->DidChangePointList(true);
+    Element()->DidChangePointList(emptyOrOldValue);
     if (AttrIsAnimating()) {
       Element()->AnimationNeedsResample();
     }
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -302,30 +303,31 @@ DOMSVGPointList::InsertItemBefore(nsIDOM
   }
 
   // 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()->WillChangePointList();
   // Now that we know we're inserting, keep animVal list in sync as necessary.
   MaybeInsertNullInAnimValListAt(aIndex);
 
   InternalList().InsertItem(aIndex, domItem->ToSVGPoint());
   mItems.InsertElementAt(aIndex, 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, aIndex, IsAnimValList());
 
   UpdateListIndicesFromIndex(mItems, aIndex + 1);
 
-  Element()->DidChangePointList(true);
+  Element()->DidChangePointList(emptyOrOldValue);
   if (AttrIsAnimating()) {
     Element()->AnimationNeedsResample();
   }
   *_retval = domItem.forget().get();
   return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -344,30 +346,31 @@ DOMSVGPointList::ReplaceItem(nsIDOMSVGPo
   }
   if (aIndex >= Length()) {
     return NS_ERROR_DOM_INDEX_SIZE_ERR;
   }
   if (domItem->HasOwner() || domItem->IsReadonly()) {
     domItem = domItem->Clone(); // must do this before changing anything!
   }
 
+  nsAttrValue emptyOrOldValue = Element()->WillChangePointList();
   if (mItems[aIndex]) {
     // 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[aIndex]->RemovingFromList();
   }
 
   InternalList()[aIndex] = domItem->ToSVGPoint();
   mItems[aIndex] = domItem;
 
   // This MUST come after the ToSVGPoint() call, otherwise that call
   // would end up reading bad data from InternalList()!
   domItem->InsertingIntoList(this, aIndex, IsAnimValList());
 
-  Element()->DidChangePointList(true);
+  Element()->DidChangePointList(emptyOrOldValue);
   if (AttrIsAnimating()) {
     Element()->AnimationNeedsResample();
   }
   NS_ADDREF(*_retval = domItem.get());
   return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -378,16 +381,17 @@ DOMSVGPointList::RemoveItem(PRUint32 aIn
   if (IsAnimValList()) {
     return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
   }
 
   if (aIndex >= Length()) {
     return NS_ERROR_DOM_INDEX_SIZE_ERR;
   }
 
+  nsAttrValue emptyOrOldValue = Element()->WillChangePointList();
   // 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(aIndex);
 
   // We have to return the removed item, so make sure it exists:
   EnsureItemAt(aIndex);
 
@@ -396,17 +400,17 @@ DOMSVGPointList::RemoveItem(PRUint32 aIn
   mItems[aIndex]->RemovingFromList();
   NS_ADDREF(*_retval = mItems[aIndex]);
 
   InternalList().RemoveItem(aIndex);
   mItems.RemoveElementAt(aIndex);
 
   UpdateListIndicesFromIndex(mItems, aIndex);
 
-  Element()->DidChangePointList(true);
+  Element()->DidChangePointList(emptyOrOldValue);
   if (AttrIsAnimating()) {
     Element()->AnimationNeedsResample();
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 DOMSVGPointList::AppendItem(nsIDOMSVGPoint *aNewItem,
--- a/content/svg/content/src/DOMSVGStringList.cpp
+++ b/content/svg/content/src/DOMSVGStringList.cpp
@@ -104,19 +104,22 @@ DOMSVGStringList::GetLength(PRUint32 *aL
 {
   return GetNumberOfItems(aLength);
 }
 
 NS_IMETHODIMP
 DOMSVGStringList::Clear()
 {
   if (InternalList().IsExplicitlySet()) {
+    nsAttrValue emptyOrOldValue =
+      mElement->WillChangeStringList(mIsConditionalProcessingAttribute,
+                                     mAttrEnum);
     InternalList().Clear();
     mElement->DidChangeStringList(mIsConditionalProcessingAttribute,
-                                  mAttrEnum);
+                                  mAttrEnum, emptyOrOldValue);
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 DOMSVGStringList::Initialize(const nsAString & newItem, nsAString & _retval)
 {
   if (InternalList().IsExplicitlySet()) {
@@ -146,19 +149,23 @@ DOMSVGStringList::InsertItemBefore(const
   }
   index = NS_MIN(index, InternalList().Length());
 
   // Ensure we have enough memory so we can avoid complex error handling below:
   if (!InternalList().SetCapacity(InternalList().Length() + 1)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
+  nsAttrValue emptyOrOldValue =
+    mElement->WillChangeStringList(mIsConditionalProcessingAttribute,
+                                   mAttrEnum);
   InternalList().InsertItem(index, newItem);
 
-  mElement->DidChangeStringList(mIsConditionalProcessingAttribute, mAttrEnum);
+  mElement->DidChangeStringList(mIsConditionalProcessingAttribute, mAttrEnum,
+                                emptyOrOldValue);
   _retval = newItem;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 DOMSVGStringList::ReplaceItem(const nsAString & newItem,
                               PRUint32 index,
                               nsAString & _retval)
@@ -166,33 +173,41 @@ DOMSVGStringList::ReplaceItem(const nsAS
   if (newItem.IsEmpty()) { // takes care of DOMStringIsNull too
     return NS_ERROR_DOM_SVG_INVALID_VALUE_ERR;
   }
   if (index >= InternalList().Length()) {
     return NS_ERROR_DOM_INDEX_SIZE_ERR;
   }
 
   _retval = InternalList()[index];
+  nsAttrValue emptyOrOldValue =
+    mElement->WillChangeStringList(mIsConditionalProcessingAttribute,
+                                   mAttrEnum);
   InternalList().ReplaceItem(index, newItem);
 
-  mElement->DidChangeStringList(mIsConditionalProcessingAttribute, mAttrEnum);
+  mElement->DidChangeStringList(mIsConditionalProcessingAttribute, mAttrEnum,
+                                emptyOrOldValue);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 DOMSVGStringList::RemoveItem(PRUint32 index,
                              nsAString & _retval)
 {
   if (index >= InternalList().Length()) {
     return NS_ERROR_DOM_INDEX_SIZE_ERR;
   }
 
+  nsAttrValue emptyOrOldValue =
+    mElement->WillChangeStringList(mIsConditionalProcessingAttribute,
+                                   mAttrEnum);
   InternalList().RemoveItem(index);
 
-  mElement->DidChangeStringList(mIsConditionalProcessingAttribute, mAttrEnum);
+  mElement->DidChangeStringList(mIsConditionalProcessingAttribute, mAttrEnum,
+                                emptyOrOldValue);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 DOMSVGStringList::AppendItem(const nsAString & newItem,
                              nsAString & _retval)
 {
   return InsertItemBefore(newItem, InternalList().Length(), _retval);
--- a/content/svg/content/src/DOMSVGStringList.h
+++ b/content/svg/content/src/DOMSVGStringList.h
@@ -106,18 +106,16 @@ private:
                    bool aIsConditionalProcessingAttribute, PRUint8 aAttrEnum)
     : mElement(aElement)
     , mAttrEnum(aAttrEnum)
     , mIsConditionalProcessingAttribute(aIsConditionalProcessingAttribute)
   {}
 
   ~DOMSVGStringList();
 
-  void DidChangeStringList(PRUint8 aAttrEnum, bool aDoSetAttr);
-
   SVGStringList &InternalList();
 
   // Strong ref to our element to keep it alive.
   nsRefPtr<nsSVGElement> mElement;
 
   PRUint8 mAttrEnum;
 
   bool    mIsConditionalProcessingAttribute;
--- a/content/svg/content/src/DOMSVGTests.cpp
+++ b/content/svg/content/src/DOMSVGTests.cpp
@@ -43,23 +43,28 @@
 #include "nsStyleUtil.h"
 #include "nsSVGUtils.h"
 #include "mozilla/Preferences.h"
 
 using namespace mozilla;
 
 NS_IMPL_ISUPPORTS1(DOMSVGTests, nsIDOMSVGTests)
 
-DOMSVGTests::StringListInfo DOMSVGTests::sStringListInfo[3] =
+nsIAtom** DOMSVGTests::sStringListNames[3] =
 {
-  { &nsGkAtoms::requiredFeatures, false },
-  { &nsGkAtoms::requiredExtensions, false },
-  { &nsGkAtoms::systemLanguage, true }
+  &nsGkAtoms::requiredFeatures,
+  &nsGkAtoms::requiredExtensions,
+  &nsGkAtoms::systemLanguage,
 };
 
+DOMSVGTests::DOMSVGTests()
+{
+  mStringListAttributes[LANGUAGE].SetIsCommaSeparated(true);
+}
+
 /* readonly attribute nsIDOMSVGStringList requiredFeatures; */
 NS_IMETHODIMP
 DOMSVGTests::GetRequiredFeatures(nsIDOMSVGStringList * *aRequiredFeatures)
 {
   nsCOMPtr<nsSVGElement> element = do_QueryInterface(this);
   *aRequiredFeatures = DOMSVGStringList::GetDOMWrapper(
                          &mStringListAttributes[FEATURES], element, true, FEATURES).get();
   return NS_OK;
@@ -91,18 +96,18 @@ DOMSVGTests::HasExtension(const nsAStrin
 {
   *_retval = nsSVGFeatures::HasExtension(extension);
   return NS_OK;
 }
 
 bool
 DOMSVGTests::IsConditionalProcessingAttribute(const nsIAtom* aAttribute) const
 {
-  for (PRUint32 i = 0; i < ArrayLength(sStringListInfo); i++) {
-    if (aAttribute == *sStringListInfo[i].mName) {
+  for (PRUint32 i = 0; i < ArrayLength(sStringListNames); i++) {
+    if (aAttribute == *sStringListNames[i]) {
       return true;
     }
   }
   return false;
 }
 
 PRInt32
 DOMSVGTests::GetBestLanguagePreferenceRank(const nsSubstring& aAcceptLangs) const
@@ -217,73 +222,59 @@ DOMSVGTests::PassesConditionalProcessing
   return true;
 }
 
 bool
 DOMSVGTests::ParseConditionalProcessingAttribute(nsIAtom* aAttribute,
                                                  const nsAString& aValue,
                                                  nsAttrValue& aResult)
 {
-  for (PRUint32 i = 0; i < ArrayLength(sStringListInfo); i++) {
-    if (aAttribute == *sStringListInfo[i].mName) {
-      nsresult rv = mStringListAttributes[i].SetValue(
-                      aValue, sStringListInfo[i].mIsCommaSeparated);
+  for (PRUint32 i = 0; i < ArrayLength(sStringListNames); i++) {
+    if (aAttribute == *sStringListNames[i]) {
+      nsresult rv = mStringListAttributes[i].SetValue(aValue);
       if (NS_FAILED(rv)) {
         mStringListAttributes[i].Clear();
       }
       MaybeInvalidate();
       return true;
     }
   }
   return false;
 }
 
 void
-DOMSVGTests::GetValue(PRUint8 aAttrEnum, nsAString& aValue) const
-{
-  NS_ABORT_IF_FALSE(aAttrEnum >= 0 && aAttrEnum < ArrayLength(sStringListInfo),
-                    "aAttrEnum out of range");
-  mStringListAttributes[aAttrEnum].GetValue(
-    aValue, sStringListInfo[aAttrEnum].mIsCommaSeparated);
-}
-
-void
 DOMSVGTests::UnsetAttr(const nsIAtom* aAttribute)
 {
-  for (PRUint32 i = 0; i < ArrayLength(sStringListInfo); i++) {
-    if (aAttribute == *sStringListInfo[i].mName) {
+  for (PRUint32 i = 0; i < ArrayLength(sStringListNames); i++) {
+    if (aAttribute == *sStringListNames[i]) {
       mStringListAttributes[i].Clear();
       MaybeInvalidate();
       return;
     }
   }
 }
 
-void
-DOMSVGTests::DidChangeStringList(PRUint8 aAttrEnum)
+nsIAtom*
+DOMSVGTests::GetAttrName(PRUint8 aAttrEnum) const
 {
-  NS_ASSERTION(aAttrEnum < ArrayLength(sStringListInfo), "aAttrEnum out of range");
-
-  nsCOMPtr<nsSVGElement> element = do_QueryInterface(this);
+  return *sStringListNames[aAttrEnum];
+}
 
-  nsAutoString serializedValue;
-  GetValue(aAttrEnum, serializedValue);
-
-  nsAttrValue attrValue(serializedValue);
-  element->SetParsedAttr(kNameSpaceID_None,
-                         *sStringListInfo[aAttrEnum].mName,
-                         nsnull, attrValue, true);
-
-  MaybeInvalidate();
+void
+DOMSVGTests::GetAttrValue(PRUint8 aAttrEnum, nsAttrValue& aValue) const
+{
+  NS_ABORT_IF_FALSE(aAttrEnum >= 0 && aAttrEnum < ArrayLength(sStringListNames),
+                    "aAttrEnum out of range");
+  aValue.SetTo(mStringListAttributes[aAttrEnum], nsnull);
 }
 
 void
 DOMSVGTests::MaybeInvalidate()
 {
   nsCOMPtr<nsSVGElement> element = do_QueryInterface(this);
 
   nsIContent* parent = element->GetFlattenedTreeParent();
-  
+
   if (parent &&
       parent->NodeInfo()->Equals(nsGkAtoms::svgSwitch, kNameSpaceID_SVG)) {
     static_cast<nsSVGSwitchElement*>(parent)->MaybeInvalidate();
   }
 }
--- a/content/svg/content/src/DOMSVGTests.h
+++ b/content/svg/content/src/DOMSVGTests.h
@@ -54,16 +54,18 @@ class DOMSVGTests : public nsIDOMSVGTest
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIDOMSVGTESTS
 
   friend class mozilla::DOMSVGStringList;
   typedef mozilla::SVGStringList SVGStringList;
 
+  DOMSVGTests();
+
   /**
    * Compare the language name(s) in a systemLanguage attribute to the
    * user's language preferences, as defined in
    * http://www.w3.org/TR/SVG11/struct.html#SystemLanguageAttribute
    * We have a match if a language name in the users language preferences
    * exactly equals one of the language names or exactly equals a prefix of
    * one of the language names in the systemLanguage attribute.
    * @returns 2 * the lowest index in the aAcceptLangs that matches + 1
@@ -99,34 +101,24 @@ public:
   bool IsConditionalProcessingAttribute(const nsIAtom* aAttribute) const;
 
   bool ParseConditionalProcessingAttribute(
          nsIAtom* aAttribute,
          const nsAString& aValue,
          nsAttrValue& aResult);
 
   /**
-   * Serialises the conditional processing attribute.
-   */
-  void GetValue(PRUint8 aAttrEnum, nsAString& aValue) const;
-
-  /**
    * Unsets a conditional processing attribute.
    */
   void UnsetAttr(const nsIAtom* aAttribute);
 
-  void DidChangeStringList(PRUint8 aAttrEnum);
+  nsIAtom* GetAttrName(PRUint8 aAttrEnum) const;
+  void GetAttrValue(PRUint8 aAttrEnum, nsAttrValue &aValue) const;
 
   void MaybeInvalidate();
 
 private:
-
-  struct StringListInfo {
-    nsIAtom** mName;
-    bool      mIsCommaSeparated;
-  };
-
   enum { FEATURES, EXTENSIONS, LANGUAGE };
   SVGStringList mStringListAttributes[3];
-  static StringListInfo sStringListInfo[3];
+  static nsIAtom** sStringListNames[3];
 };
 
 #endif // MOZILLA_DOMSVGTESTS_H__
--- a/content/svg/content/src/DOMSVGTransform.cpp
+++ b/content/svg/content/src/DOMSVGTransform.cpp
@@ -168,97 +168,128 @@ DOMSVGTransform::SetMatrix(nsIDOMSVGMatr
 {
   if (mIsAnimValItem)
     return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
 
   nsCOMPtr<DOMSVGMatrix> domMatrix = do_QueryInterface(matrix);
   if (!domMatrix)
     return NS_ERROR_DOM_SVG_WRONG_TYPE_ERR;
 
-  Transform().SetMatrix(domMatrix->Matrix());
-  NotifyElementOfChange();
-
+  SetMatrix(domMatrix->Matrix());
   return NS_OK;
 }
 
 /* void setTranslate (in float tx, in float ty); */
 NS_IMETHODIMP
 DOMSVGTransform::SetTranslate(float tx, float ty)
 {
   if (mIsAnimValItem) {
     return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
   }
   NS_ENSURE_FINITE2(tx, ty, NS_ERROR_ILLEGAL_VALUE);
 
+  if (Transform().Type() == nsIDOMSVGTransform::SVG_TRANSFORM_TRANSLATE &&
+      Matrix().x0 == tx && Matrix().y0 == ty) {
+    return NS_OK;
+  }
+
+  nsAttrValue emptyOrOldValue = NotifyElementWillChange();
   Transform().SetTranslate(tx, ty);
-  NotifyElementOfChange();
+  NotifyElementDidChange(emptyOrOldValue);
 
   return NS_OK;
 }
 
 /* void setScale (in float sx, in float sy); */
 NS_IMETHODIMP
 DOMSVGTransform::SetScale(float sx, float sy)
 {
   if (mIsAnimValItem) {
     return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
   }
   NS_ENSURE_FINITE2(sx, sy, NS_ERROR_ILLEGAL_VALUE);
 
+  if (Transform().Type() == nsIDOMSVGTransform::SVG_TRANSFORM_SCALE &&
+      Matrix().xx == sx && Matrix().yy == sy) {
+    return NS_OK;
+  }
+
+  nsAttrValue emptyOrOldValue = NotifyElementWillChange();
   Transform().SetScale(sx, sy);
-  NotifyElementOfChange();
+  NotifyElementDidChange(emptyOrOldValue);
 
   return NS_OK;
 }
 
 /* void setRotate (in float angle, in float cx, in float cy); */
 NS_IMETHODIMP
 DOMSVGTransform::SetRotate(float angle, float cx, float cy)
 {
   if (mIsAnimValItem) {
     return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
   }
   NS_ENSURE_FINITE3(angle, cx, cy, NS_ERROR_ILLEGAL_VALUE);
 
+  if (Transform().Type() == nsIDOMSVGTransform::SVG_TRANSFORM_ROTATE) {
+    float currentCx, currentCy;
+    Transform().GetRotationOrigin(currentCx, currentCy);
+    if (Transform().Angle() == angle && currentCx == cx && currentCy == cy) {
+      return NS_OK;
+    }
+  }
+
+  nsAttrValue emptyOrOldValue = NotifyElementWillChange();
   Transform().SetRotate(angle, cx, cy);
-  NotifyElementOfChange();
+  NotifyElementDidChange(emptyOrOldValue);
 
   return NS_OK;
 }
 
 /* void setSkewX (in float angle); */
 NS_IMETHODIMP
 DOMSVGTransform::SetSkewX(float angle)
 {
   if (mIsAnimValItem) {
     return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
   }
   NS_ENSURE_FINITE(angle, NS_ERROR_ILLEGAL_VALUE);
 
+  if (Transform().Type() == nsIDOMSVGTransform::SVG_TRANSFORM_SKEWX &&
+      Transform().Angle() == angle) {
+    return NS_OK;
+  }
+
+  nsAttrValue emptyOrOldValue = NotifyElementWillChange();
   nsresult rv = Transform().SetSkewX(angle);
   if (NS_FAILED(rv))
     return rv;
-  NotifyElementOfChange();
+  NotifyElementDidChange(emptyOrOldValue);
 
   return NS_OK;
 }
 
 /* void setSkewY (in float angle); */
 NS_IMETHODIMP
 DOMSVGTransform::SetSkewY(float angle)
 {
   if (mIsAnimValItem) {
     return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
   }
   NS_ENSURE_FINITE(angle, NS_ERROR_ILLEGAL_VALUE);
 
+  if (Transform().Type() == nsIDOMSVGTransform::SVG_TRANSFORM_SKEWY &&
+      Transform().Angle() == angle) {
+    return NS_OK;
+  }
+
+  nsAttrValue emptyOrOldValue = NotifyElementWillChange();
   nsresult rv = Transform().SetSkewY(angle);
   if (NS_FAILED(rv))
     return rv;
-  NotifyElementOfChange();
+  NotifyElementDidChange(emptyOrOldValue);
 
   return NS_OK;
 }
 
 
 //----------------------------------------------------------------------
 // List management methods:
 
@@ -319,36 +350,43 @@ DOMSVGTransform::IndexIsValid()
 //----------------------------------------------------------------------
 // Interface for DOMSVGMatrix's use
 
 void
 DOMSVGTransform::SetMatrix(const gfxMatrix& aMatrix)
 {
   NS_ABORT_IF_FALSE(!mIsAnimValItem,
       "Attempting to modify read-only transform");
+
+  if (Transform().Type() == nsIDOMSVGTransform::SVG_TRANSFORM_MATRIX &&
+      SVGTransform::MatricesEqual(Matrix(), aMatrix)) {
+    return;
+  }
+
+  nsAttrValue emptyOrOldValue = NotifyElementWillChange();
   Transform().SetMatrix(aMatrix);
-  NotifyElementOfChange();
+  NotifyElementDidChange(emptyOrOldValue);
 }
 
 void
 DOMSVGTransform::ClearMatrixTearoff(DOMSVGMatrix* aMatrix)
 {
   NS_ABORT_IF_FALSE(mMatrixTearoff == aMatrix,
       "Unexpected matrix pointer to be cleared");
   mMatrixTearoff = nsnull;
 }
 
 
 //----------------------------------------------------------------------
 // Implementation helpers
 
 void
-DOMSVGTransform::NotifyElementOfChange()
+DOMSVGTransform::NotifyElementDidChange(const nsAttrValue& aEmptyOrOldValue)
 {
   if (HasOwner()) {
-    Element()->DidChangeTransformList(true);
+    Element()->DidChangeTransformList(aEmptyOrOldValue);
     if (mList->mAList->IsAnimating()) {
       Element()->AnimationNeedsResample();
     }
   }
 }
 
 } // namespace mozilla
--- a/content/svg/content/src/DOMSVGTransform.h
+++ b/content/svg/content/src/DOMSVGTransform.h
@@ -201,17 +201,18 @@ private:
 #endif
 
   const SVGTransform& Transform() const {
     return HasOwner() ? InternalItem() : *mTransform;
   }
   SVGTransform& Transform() {
     return HasOwner() ? InternalItem() : *mTransform;
   }
-  void NotifyElementOfChange();
+  inline nsAttrValue NotifyElementWillChange();
+  void NotifyElementDidChange(const nsAttrValue& aEmptyOrOldValue);
 
   nsRefPtr<DOMSVGTransformList> mList;
 
   // Bounds for the following are checked in the ctor, so be sure to update
   // that if you change the capacity of any of the following.
 
   PRUint32 mListIndex:MOZ_SVG_LIST_INDEX_BIT_COUNT;
   bool mIsAnimValItem:1;
@@ -230,13 +231,23 @@ private:
   // If this extra pointer member proves undesirable, it can be replaced with
   // a hashmap (nsSVGAttrTearoffTable) to map from DOMSVGTransform to
   // DOMSVGMatrix.
   DOMSVGMatrix* mMatrixTearoff;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(DOMSVGTransform, MOZILLA_DOMSVGTRANSFORM_IID)
 
+nsAttrValue
+DOMSVGTransform::NotifyElementWillChange()
+{
+  nsAttrValue result;
+  if (HasOwner()) {
+    result = Element()->WillChangeTransformList();
+  }
+  return result;
+}
+
 } // namespace mozilla
 
 #undef MOZ_SVG_LIST_INDEX_BIT_COUNT
 
 #endif // MOZILLA_DOMSVGTRANSFORM_H__
--- a/content/svg/content/src/DOMSVGTransformList.cpp
+++ b/content/svg/content/src/DOMSVGTransformList.cpp
@@ -179,24 +179,25 @@ DOMSVGTransformList::GetLength(PRUint32 
 NS_IMETHODIMP
 DOMSVGTransformList::Clear()
 {
   if (IsAnimValList()) {
     return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
   }
 
   if (Length() > 0) {
+    nsAttrValue emptyOrOldValue = Element()->WillChangeTransformList();
     // Notify any existing DOM items of removal *before* truncating the lists
     // so that they can find their SVGTransform internal counterparts and copy
     // their values. This also notifies the animVal list:
     mAList->InternalBaseValListWillChangeLengthTo(0);
 
     mItems.Clear();
     InternalList().Clear();
-    Element()->DidChangeTransformList(true);
+    Element()->DidChangeTransformList(emptyOrOldValue);
     if (mAList->IsAnimating()) {
       Element()->AnimationNeedsResample();
     }
   }
   return NS_OK;
 }
 
 /* nsIDOMSVGTransform initialize (in nsIDOMSVGTransform newItem); */
@@ -267,30 +268,31 @@ DOMSVGTransformList::InsertItemBefore(ns
   }
 
   // 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()->WillChangeTransformList();
   // Now that we know we're inserting, keep animVal list in sync as necessary.
   MaybeInsertNullInAnimValListAt(index);
 
   InternalList().InsertItem(index, domItem->ToSVGTransform());
   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, index, IsAnimValList());
 
   UpdateListIndicesFromIndex(mItems, index + 1);
 
-  Element()->DidChangeTransformList(true);
+  Element()->DidChangeTransformList(emptyOrOldValue);
   if (mAList->IsAnimating()) {
     Element()->AnimationNeedsResample();
   }
   *_retval = domItem.forget().get();
   return NS_OK;
 }
 
 /* nsIDOMSVGTransform replaceItem (in nsIDOMSVGTransform newItem,
@@ -311,30 +313,31 @@ DOMSVGTransformList::ReplaceItem(nsIDOMS
   }
   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()->WillChangeTransformList();
   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->ToSVGTransform();
   mItems[index] = domItem;
 
   // This MUST come after the ToSVGPoint() call, otherwise that call
   // would end up reading bad data from InternalList()!
   domItem->InsertingIntoList(this, index, IsAnimValList());
 
-  Element()->DidChangeTransformList(true);
+  Element()->DidChangeTransformList(emptyOrOldValue);
   if (mAList->IsAnimating()) {
     Element()->AnimationNeedsResample();
   }
   NS_ADDREF(*_retval = domItem.get());
   return NS_OK;
 }
 
 /* nsIDOMSVGTransform removeItem (in unsigned long index); */
@@ -345,16 +348,17 @@ DOMSVGTransformList::RemoveItem(PRUint32
   if (IsAnimValList()) {
     return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
   }
 
   if (index >= Length()) {
     return NS_ERROR_DOM_INDEX_SIZE_ERR;
   }
 
+  nsAttrValue emptyOrOldValue = Element()->WillChangeTransformList();
   // 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);
 
@@ -363,17 +367,17 @@ DOMSVGTransformList::RemoveItem(PRUint32
   mItems[index]->RemovingFromList();
   NS_ADDREF(*_retval = mItems[index]);
 
   InternalList().RemoveItem(index);
   mItems.RemoveElementAt(index);
 
   UpdateListIndicesFromIndex(mItems, index);
 
-  Element()->DidChangeTransformList(true);
+  Element()->DidChangeTransformList(emptyOrOldValue);
   if (mAList->IsAnimating()) {
     Element()->AnimationNeedsResample();
   }
   return NS_OK;
 }
 
 /* nsIDOMSVGTransform appendItem (in nsIDOMSVGTransform newItem); */
 NS_IMETHODIMP
--- a/content/svg/content/src/Makefile.in
+++ b/content/svg/content/src/Makefile.in
@@ -141,16 +141,17 @@ CPPSRCS		= \
 		SVGTransformList.cpp \
 		SVGTransformListParser.cpp \
 		nsSVGAnimateElement.cpp \
 		nsSVGAnimateTransformElement.cpp \
 		nsSVGAnimateMotionElement.cpp \
 		nsSVGAnimationElement.cpp \
 		nsSVGMpathElement.cpp \
 		nsSVGSetElement.cpp \
+		SVGAttrValueWrapper.cpp \
 		SVGIntegerPairSMILType.cpp \
 		SVGLengthListSMILType.cpp \
 		SVGMotionSMILType.cpp \
 		SVGMotionSMILAttr.cpp \
 		SVGMotionSMILAnimationFunction.cpp \
 		SVGMotionSMILPathUtils.cpp \
 		SVGNumberListSMILType.cpp \
 		SVGNumberPairSMILType.cpp \
@@ -164,16 +165,17 @@ CPPSRCS		= \
 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 =  			\
 	nsSVGFeatures.h            \
 	nsSVGRect.h                \
+	SVGAttrValueWrapper.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/SVGAnimatedPreserveAspectRatio.cpp
+++ b/content/svg/content/src/SVGAnimatedPreserveAspectRatio.cpp
@@ -245,17 +245,18 @@ SVGAnimatedPreserveAspectRatio::SetBaseV
 
   // We don't need to call DidChange* here - we're only called by
   // nsSVGElement::ParseAttribute under nsGenericElement::SetAttr,
   // which takes care of notifying.
   return NS_OK;
 }
 
 void
-SVGAnimatedPreserveAspectRatio::GetBaseValueString(nsAString & aValueAsString)
+SVGAnimatedPreserveAspectRatio::GetBaseValueString(
+  nsAString& aValueAsString) const
 {
   nsAutoString tmpString;
 
   aValueAsString.Truncate();
 
   if (mBaseVal.mDefer) {
     aValueAsString.AppendLiteral("defer ");
   }
@@ -271,39 +272,49 @@ SVGAnimatedPreserveAspectRatio::GetBaseV
     aValueAsString.Append(tmpString);
   }
 }
 
 nsresult
 SVGAnimatedPreserveAspectRatio::SetBaseAlign(PRUint16 aAlign,
                                              nsSVGElement *aSVGElement)
 {
+  if (mIsBaseSet && mBaseVal.GetAlign() == aAlign) {
+    return NS_OK;
+  }
+
+  nsAttrValue emptyOrOldValue = aSVGElement->WillChangePreserveAspectRatio();
   nsresult rv = mBaseVal.SetAlign(aAlign);
   NS_ENSURE_SUCCESS(rv, rv);
   mIsBaseSet = true;
 
   mAnimVal.mAlign = mBaseVal.mAlign;
-  aSVGElement->DidChangePreserveAspectRatio(true);
+  aSVGElement->DidChangePreserveAspectRatio(emptyOrOldValue);
   if (mIsAnimated) {
     aSVGElement->AnimationNeedsResample();
   }
   
   return NS_OK;
 }
 
 nsresult
 SVGAnimatedPreserveAspectRatio::SetBaseMeetOrSlice(PRUint16 aMeetOrSlice,
                                                    nsSVGElement *aSVGElement)
 {
+  if (mIsBaseSet && mBaseVal.GetMeetOrSlice() == aMeetOrSlice) {
+    return NS_OK;
+  }
+
+  nsAttrValue emptyOrOldValue = aSVGElement->WillChangePreserveAspectRatio();
   nsresult rv = mBaseVal.SetMeetOrSlice(aMeetOrSlice);
   NS_ENSURE_SUCCESS(rv, rv);
   mIsBaseSet = true;
 
   mAnimVal.mMeetOrSlice = mBaseVal.mMeetOrSlice;
-  aSVGElement->DidChangePreserveAspectRatio(true);
+  aSVGElement->DidChangePreserveAspectRatio(emptyOrOldValue);
   if (mIsAnimated) {
     aSVGElement->AnimationNeedsResample();
   }
   
   return NS_OK;
 }
 
 void
--- a/content/svg/content/src/SVGAnimatedPreserveAspectRatio.h
+++ b/content/svg/content/src/SVGAnimatedPreserveAspectRatio.h
@@ -113,17 +113,17 @@ public:
     mBaseVal.mDefer = false;
     mAnimVal = mBaseVal;
     mIsAnimated = false;
     mIsBaseSet = false;
   }
 
   nsresult SetBaseValueString(const nsAString& aValue,
                               nsSVGElement *aSVGElement);
-  void GetBaseValueString(nsAString& aValue);
+  void GetBaseValueString(nsAString& aValue) const;
 
   nsresult SetBaseAlign(PRUint16 aAlign, nsSVGElement *aSVGElement);
   nsresult SetBaseMeetOrSlice(PRUint16 aMeetOrSlice, nsSVGElement *aSVGElement);
   void SetAnimValue(PRUint64 aPackedValue, nsSVGElement *aSVGElement);
 
   const SVGPreserveAspectRatio &GetBaseValue() const
     { return mBaseVal; }
   const SVGPreserveAspectRatio &GetAnimValue() const
new file mode 100644
--- /dev/null
+++ b/content/svg/content/src/SVGAttrValueWrapper.cpp
@@ -0,0 +1,133 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Mozilla SVG project.
+ *
+ * The Initial Developer of the Original Code is Mozilla Japan.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "SVGAttrValueWrapper.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"
+#include "SVGPathData.h"
+#include "SVGPointList.h"
+#include "SVGStringList.h"
+#include "SVGTransformList.h"
+
+using namespace mozilla;
+
+/*static*/ void
+SVGAttrValueWrapper::ToString(const nsSVGAngle* aAngle, nsAString& aResult)
+{
+  aAngle->GetBaseValueString(aResult);
+}
+
+/*static*/ void
+SVGAttrValueWrapper::ToString(const nsSVGIntegerPair* aIntegerPair,
+                              nsAString& aResult)
+{
+  aIntegerPair->GetBaseValueString(aResult);
+}
+
+/*static*/ void
+SVGAttrValueWrapper::ToString(const nsSVGLength2* aLength, nsAString& aResult)
+{
+  aLength->GetBaseValueString(aResult);
+}
+
+/*static*/ void
+SVGAttrValueWrapper::ToString(const SVGLengthList* aLengthList,
+                              nsAString& aResult)
+{
+  aLengthList->GetValueAsString(aResult);
+}
+
+/*static*/ void
+SVGAttrValueWrapper::ToString(const SVGNumberList* aNumberList,
+                              nsAString& aResult)
+{
+  aNumberList->GetValueAsString(aResult);
+}
+
+/*static*/ void
+SVGAttrValueWrapper::ToString(const nsSVGNumberPair* aNumberPair,
+                              nsAString& aResult)
+{
+  aNumberPair->GetBaseValueString(aResult);
+}
+
+/*static*/ void
+SVGAttrValueWrapper::ToString(const SVGPathData* aPathData, nsAString& aResult)
+{
+  aPathData->GetValueAsString(aResult);
+}
+
+/*static*/ void
+SVGAttrValueWrapper::ToString(const SVGPointList* aPointList,
+                              nsAString& aResult)
+{
+  aPointList->GetValueAsString(aResult);
+}
+
+/*static*/ void
+SVGAttrValueWrapper::ToString(
+  const SVGAnimatedPreserveAspectRatio* aPreserveAspectRatio,
+  nsAString& aResult)
+{
+  aPreserveAspectRatio->GetBaseValueString(aResult);
+}
+
+/*static*/ void
+SVGAttrValueWrapper::ToString(const SVGStringList* aStringList,
+                              nsAString& aResult)
+{
+  aStringList->GetValue(aResult);
+}
+
+/*static*/ void
+SVGAttrValueWrapper::ToString(const SVGTransformList* aTransformList,
+                              nsAString& aResult)
+{
+  aTransformList->GetValueAsString(aResult);
+}
+
+/*static*/ void
+SVGAttrValueWrapper::ToString(const nsSVGViewBox* aViewBox, nsAString& aResult)
+{
+  aViewBox->GetBaseValueString(aResult);
+}
new file mode 100644
--- /dev/null
+++ b/content/svg/content/src/SVGAttrValueWrapper.h
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Mozilla SVG project.
+ *
+ * The Initial Developer of the Original Code is Mozilla Japan.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef MOZILLA_SVGATTRVALUEWRAPPER_H__
+#define MOZILLA_SVGATTRVALUEWRAPPER_H__
+
+/**
+ * Utility wrapper for handling SVG types used inside nsAttrValue so that these
+ * types don't need to be exported outside the SVG module.
+ */
+
+#include "nsString.h"
+
+class nsSVGAngle;
+class nsSVGIntegerPair;
+class nsSVGLength2;
+class nsSVGNumberPair;
+class nsSVGViewBox;
+
+namespace mozilla {
+class SVGLengthList;
+class SVGNumberList;
+class SVGPathData;
+class SVGPointList;
+class SVGAnimatedPreserveAspectRatio;
+class SVGStringList;
+class SVGTransformList;
+}
+
+namespace mozilla {
+
+class SVGAttrValueWrapper
+{
+public:
+  static void ToString(const nsSVGAngle* aAngle, nsAString& aResult);
+  static void ToString(const nsSVGIntegerPair* aIntegerPair,
+                       nsAString& aResult);
+  static void ToString(const nsSVGLength2* aLength, nsAString& aResult);
+  static void ToString(const mozilla::SVGLengthList* aLengthList,
+                       nsAString& aResult);
+  static void ToString(const mozilla::SVGNumberList* aNumberList,
+                       nsAString& aResult);
+  static void ToString(const nsSVGNumberPair* aNumberPair, nsAString& aResult);
+  static void ToString(const mozilla::SVGPathData* aPathData,
+                       nsAString& aResult);
+  static void ToString(const mozilla::SVGPointList* aPointList,
+                       nsAString& aResult);
+  static void ToString(
+    const mozilla::SVGAnimatedPreserveAspectRatio* aPreserveAspectRatio,
+    nsAString& aResult);
+  static void ToString(const mozilla::SVGStringList* aStringList,
+                       nsAString& aResult);
+  static void ToString(const mozilla::SVGTransformList* aTransformList,
+                       nsAString& aResult);
+  static void ToString(const nsSVGViewBox* aViewBox, nsAString& aResult);
+};
+
+} /* namespace mozilla */
+
+#endif // MOZILLA_SVGATTRVALUEWRAPPER_H__
--- a/content/svg/content/src/SVGStringList.cpp
+++ b/content/svg/content/src/SVGStringList.cpp
@@ -57,37 +57,37 @@ SVGStringList::CopyFrom(const SVGStringL
     return NS_ERROR_OUT_OF_MEMORY;
   }
   mStrings = rhs.mStrings;
   mIsSet = true;
   return NS_OK;
 }
 
 void
-SVGStringList::GetValue(nsAString& aValue, bool aIsCommaSeparated) const
+SVGStringList::GetValue(nsAString& aValue) const
 {
   aValue.Truncate();
   PRUint32 last = mStrings.Length() - 1;
   for (PRUint32 i = 0; i < mStrings.Length(); ++i) {
     aValue.Append(mStrings[i]);
     if (i != last) {
-      if (aIsCommaSeparated) {
+      if (mIsCommaSeparated) {
         aValue.Append(',');
       }
       aValue.Append(' ');
     }
   }
 }
 
 nsresult
-SVGStringList::SetValue(const nsAString& aValue, bool aIsCommaSeparated)
+SVGStringList::SetValue(const nsAString& aValue)
 {
   SVGStringList temp;
 
-  if (aIsCommaSeparated) {
+  if (mIsCommaSeparated) {
     nsCharSeparatedTokenizerTemplate<IsSVGWhitespace>
       tokenizer(aValue, ',');
 
     while (tokenizer.hasMoreTokens()) {
       if (!temp.AppendItem(tokenizer.nextToken())) {
         return NS_ERROR_OUT_OF_MEMORY;
       }
     }
--- a/content/svg/content/src/SVGStringList.h
+++ b/content/svg/content/src/SVGStringList.h
@@ -49,28 +49,31 @@ namespace mozilla {
  * The DOM wrapper class for this class is DOMSVGStringList.
  */
 class SVGStringList
 {
   friend class DOMSVGStringList;
 
 public:
 
-  SVGStringList() : mIsSet(false) {}
+  SVGStringList() : mIsSet(false), mIsCommaSeparated(false) {}
   ~SVGStringList(){}
 
-  nsresult SetValue(const nsAString& aValue, bool aIsCommaSeparated);
+  void SetIsCommaSeparated(bool aIsCommaSeparated) {
+    mIsCommaSeparated = aIsCommaSeparated;
+  }
+  nsresult SetValue(const nsAString& aValue);
 
   void Clear() {
     mStrings.Clear();
     mIsSet = false;
   }
 
   /// This may return an incomplete string on OOM, but that's acceptable.
-  void GetValue(nsAString& aValue, bool aIsCommaSeparated) const;
+  void GetValue(nsAString& aValue) const;
 
   bool IsEmpty() const {
     return mStrings.IsEmpty();
   }
 
   PRUint32 Length() const {
     return mStrings.Length();
   }
@@ -86,19 +89,18 @@ public:
   bool SetCapacity(PRUint32 size) {
     return mStrings.SetCapacity(size);
   }
 
   void Compact() {
     mStrings.Compact();
   }
 
-  // Returns true if the animated value of this stringlist has been explicitly
-  // set by taking on the base value which has been explicitly set by markup
-  // or a DOM call, false otherwise.
+  // Returns true if the value of this stringlist has been explicitly
+  // set by markup or a DOM call, false otherwise.
   bool IsExplicitlySet() const
     { return mIsSet; }
 
   // Access to methods that can modify objects of this type is deliberately
   // limited. This is to reduce the chances of someone modifying objects of
   // this type without taking the necessary steps to keep DOM wrappers in sync.
   // If you need wider access to these methods, consider adding a method to
   // SVGAnimatedStringList and having that class act as an intermediary so it
@@ -163,13 +165,14 @@ private:
 
 protected:
 
   /* See SVGLengthList for the rationale for using nsTArray<float> instead
    * of nsTArray<float, 1>.
    */
   nsTArray<nsString> mStrings;
   bool mIsSet;
+  bool mIsCommaSeparated;
 };
 
 } // namespace mozilla
 
 #endif // MOZILLA_SVGSTRINGLIST_H__
--- a/content/svg/content/src/SVGTransform.h
+++ b/content/svg/content/src/SVGTransform.h
@@ -92,27 +92,27 @@ public:
   const gfxMatrix& Matrix() const { return mMatrix; }
   void SetMatrix(const gfxMatrix& aMatrix);
   void SetTranslate(float aTx, float aTy);
   void SetScale(float aSx, float aSy);
   void SetRotate(float aAngle, float aCx, float aCy);
   nsresult SetSkewX(float aAngle);
   nsresult SetSkewY(float aAngle);
 
-protected:
   static bool MatricesEqual(const gfxMatrix& a, const gfxMatrix& b)
   {
     return a.xx == b.xx &&
            a.yx == b.yx &&
            a.xy == b.xy &&
            a.yy == b.yy &&
            a.x0 == b.x0 &&
            a.y0 == b.y0;
   }
 
+protected:
   gfxMatrix mMatrix;
   float mAngle, mOriginX, mOriginY;
   PRUint16 mType;
 };
 
 /*
  * A slightly more light-weight version of SVGTransform for SMIL animation.
  *
--- a/content/svg/content/src/nsSVGAngle.cpp
+++ b/content/svg/content/src/nsSVGAngle.cpp
@@ -67,17 +67,17 @@ public:
   NS_IMETHOD GetUnitType(PRUint16* aResult)
     { *aResult = mVal.mBaseValUnit; return NS_OK; }
 
   NS_IMETHOD GetValue(float* aResult)
     { *aResult = mVal.GetBaseValue(); return NS_OK; }
   NS_IMETHOD SetValue(float aValue)
     {
       NS_ENSURE_FINITE(aValue, NS_ERROR_ILLEGAL_VALUE);
-      mVal.SetBaseValue(aValue, nsnull);
+      mVal.SetBaseValue(aValue, nsnull, true);
       return NS_OK;
     }
 
   NS_IMETHOD GetValueInSpecifiedUnits(float* aResult)
     { *aResult = mVal.mBaseVal; return NS_OK; }
   NS_IMETHOD SetValueInSpecifiedUnits(float aValue)
     {
       NS_ENSURE_FINITE(aValue, NS_ERROR_ILLEGAL_VALUE);
@@ -253,60 +253,82 @@ nsSVGAngle::GetDegreesPerUnit(PRUint8 aU
     return 0;
   }
 }
 
 void
 nsSVGAngle::SetBaseValueInSpecifiedUnits(float aValue,
                                          nsSVGElement *aSVGElement)
 {
+  if (mBaseVal == aValue) {
+    return;
+  }
+
+  nsAttrValue emptyOrOldValue = aSVGElement->WillChangeAngle(mAttrEnum);
   mBaseVal = aValue;
   if (!mIsAnimated) {
     mAnimVal = mBaseVal;
   }
   else {
     aSVGElement->AnimationNeedsResample();
   }
-  aSVGElement->DidChangeAngle(mAttrEnum, true);
+  aSVGElement->DidChangeAngle(mAttrEnum, emptyOrOldValue);
 }
 
 nsresult
 nsSVGAngle::ConvertToSpecifiedUnits(PRUint16 unitType,
                                     nsSVGElement *aSVGElement)
 {
   if (!IsValidUnitType(unitType))
     return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
 
+  if (mBaseValUnit == PRUint8(unitType))
+    return NS_OK;
+
+  nsAttrValue emptyOrOldValue = aSVGElement->WillChangeAngle(mAttrEnum);
+
   float valueInUserUnits = mBaseVal * GetDegreesPerUnit(mBaseValUnit);
   mBaseValUnit = PRUint8(unitType);
-  SetBaseValue(valueInUserUnits, aSVGElement);
+  // Setting aDoSetAttr to false here will ensure we don't call
+  // Will/DidChangeAngle a second time (and dispatch duplicate notifications).
+  SetBaseValue(valueInUserUnits, aSVGElement, false);
+
+  aSVGElement->DidChangeAngle(mAttrEnum, emptyOrOldValue);
+
   return NS_OK;
 }
 
 nsresult
 nsSVGAngle::NewValueSpecifiedUnits(PRUint16 unitType,
                                    float valueInSpecifiedUnits,
                                    nsSVGElement *aSVGElement)
 {
   NS_ENSURE_FINITE(valueInSpecifiedUnits, NS_ERROR_ILLEGAL_VALUE);
 
   if (!IsValidUnitType(unitType))
     return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
 
+  if (mBaseVal == valueInSpecifiedUnits && mBaseValUnit == PRUint8(unitType))
+    return NS_OK;
+
+  nsAttrValue emptyOrOldValue;
+  if (aSVGElement) {
+    emptyOrOldValue = aSVGElement->WillChangeAngle(mAttrEnum);
+  }
   mBaseVal = valueInSpecifiedUnits;
   mBaseValUnit = PRUint8(unitType);
   if (!mIsAnimated) {
     mAnimVal = mBaseVal;
     mAnimValUnit = mBaseValUnit;
   }
   else {
     aSVGElement->AnimationNeedsResample();
   }
   if (aSVGElement) {
-    aSVGElement->DidChangeAngle(mAttrEnum, true);
+    aSVGElement->DidChangeAngle(mAttrEnum, emptyOrOldValue);
   }
   return NS_OK;
 }
 
 nsresult
 nsSVGAngle::ToDOMBaseVal(nsIDOMSVGAngle **aResult, nsSVGElement *aSVGElement)
 {
   *aResult = new DOMBaseVal(this, aSVGElement);
@@ -337,57 +359,73 @@ nsSVGAngle::SetBaseValueString(const nsA
 {
   float value = 0;
   PRUint16 unitType = 0;
   
   nsresult rv = GetValueFromString(aValueAsString, &value, &unitType);
   if (NS_FAILED(rv)) {
     return rv;
   }
+  if (mBaseVal == value && mBaseValUnit == PRUint8(unitType)) {
+    return NS_OK;
+  }
 
+  nsAttrValue emptyOrOldValue;
+  if (aDoSetAttr) {
+    emptyOrOldValue = aSVGElement->WillChangeAngle(mAttrEnum);
+  }
   mBaseVal = value;
   mBaseValUnit = PRUint8(unitType);
   if (!mIsAnimated) {
     mAnimVal = mBaseVal;
     mAnimValUnit = mBaseValUnit;
   }
   else {
     aSVGElement->AnimationNeedsResample();
   }
 
-  // We don't need to call DidChange* here - we're only called by
-  // nsSVGElement::ParseAttribute under nsGenericElement::SetAttr,
-  // which takes care of notifying.
+  if (aDoSetAttr) {
+    aSVGElement->DidChangeAngle(mAttrEnum, emptyOrOldValue);
+  }
   return NS_OK;
 }
 
 void
-nsSVGAngle::GetBaseValueString(nsAString & aValueAsString)
+nsSVGAngle::GetBaseValueString(nsAString & aValueAsString) const
 {
   GetValueString(aValueAsString, mBaseVal, mBaseValUnit);
 }
 
 void
-nsSVGAngle::GetAnimValueString(nsAString & aValueAsString)
+nsSVGAngle::GetAnimValueString(nsAString & aValueAsString) const
 {
   GetValueString(aValueAsString, mAnimVal, mAnimValUnit);
 }
 
 void
-nsSVGAngle::SetBaseValue(float aValue, nsSVGElement *aSVGElement)
+nsSVGAngle::SetBaseValue(float aValue, nsSVGElement *aSVGElement,
+                         bool aDoSetAttr)
 {
+  if (mBaseVal == aValue * GetDegreesPerUnit(mBaseValUnit)) {
+    return;
+  }
+  nsAttrValue emptyOrOldValue;
+  if (aSVGElement && aDoSetAttr) {
+    emptyOrOldValue = aSVGElement->WillChangeAngle(mAttrEnum);
+  }
+
   mBaseVal = aValue / GetDegreesPerUnit(mBaseValUnit);
   if (!mIsAnimated) {
     mAnimVal = mBaseVal;
   }
   else {
     aSVGElement->AnimationNeedsResample();
   }
-  if (aSVGElement) {
-    aSVGElement->DidChangeAngle(mAttrEnum, true);
+  if (aSVGElement && aDoSetAttr) {
+    aSVGElement->DidChangeAngle(mAttrEnum, emptyOrOldValue);
   }
 }
 
 void
 nsSVGAngle::SetAnimValue(float aValue, PRUint8 aUnit, nsSVGElement *aSVGElement)
 {
   mAnimVal = aValue;
   mAnimValUnit = aUnit;
--- a/content/svg/content/src/nsSVGAngle.h
+++ b/content/svg/content/src/nsSVGAngle.h
@@ -62,25 +62,25 @@ public:
     mAnimValUnit = mBaseValUnit = aUnitType;
     mAttrEnum = aAttrEnum;
     mIsAnimated = false;
   }
 
   nsresult SetBaseValueString(const nsAString& aValue,
                               nsSVGElement *aSVGElement,
                               bool aDoSetAttr);
-  void GetBaseValueString(nsAString& aValue);
-  void GetAnimValueString(nsAString& aValue);
+  void GetBaseValueString(nsAString& aValue) const;
+  void GetAnimValueString(nsAString& aValue) const;
 
   float GetBaseValue() const
     { return mBaseVal * GetDegreesPerUnit(mBaseValUnit); }
   float GetAnimValue() const
     { return mAnimVal * GetDegreesPerUnit(mAnimValUnit); }
 
-  void SetBaseValue(float aValue, nsSVGElement *aSVGElement);
+  void SetBaseValue(float aValue, nsSVGElement *aSVGElement, bool aDoSetAttr);
   void SetAnimValue(float aValue, PRUint8 aUnit, nsSVGElement *aSVGElement);
 
   PRUint8 GetBaseValueUnit() const { return mBaseValUnit; }
   PRUint8 GetAnimValueUnit() const { return mAnimValUnit; }
   float GetBaseValInSpecifiedUnits() const { return mBaseVal; }
   float GetAnimValInSpecifiedUnits() const { return mAnimVal; }
 
   static nsresult ToDOMSVGAngle(nsIDOMSVGAngle **aResult);
@@ -120,17 +120,17 @@ public:
     nsRefPtr<nsSVGElement> mSVGElement;
     
     NS_IMETHOD GetUnitType(PRUint16* aResult)
       { *aResult = mVal->mBaseValUnit; return NS_OK; }
 
     NS_IMETHOD GetValue(float* aResult)
       { *aResult = mVal->GetBaseValue(); return NS_OK; }
     NS_IMETHOD SetValue(float aValue)
-      { mVal->SetBaseValue(aValue, mSVGElement); return NS_OK; }
+      { mVal->SetBaseValue(aValue, mSVGElement, true); return NS_OK; }
 
     NS_IMETHOD GetValueInSpecifiedUnits(float* aResult)
       { *aResult = mVal->mBaseVal; return NS_OK; }
     NS_IMETHOD SetValueInSpecifiedUnits(float aValue)
       { mVal->SetBaseValueInSpecifiedUnits(aValue, mSVGElement);
         return NS_OK; }
 
     NS_IMETHOD SetValueAsString(const nsAString& aValue)
--- a/content/svg/content/src/nsSVGBoolean.cpp
+++ b/content/svg/content/src/nsSVGBoolean.cpp
@@ -65,23 +65,36 @@ GetValueFromString(const nsAString &aVal
   }
   if (aValueAsString.EqualsLiteral("false")) {
     *aValue = false;
     return NS_OK;
   }
   return NS_ERROR_DOM_SYNTAX_ERR;
 }
 
+static nsresult
+GetValueFromAtom(const nsIAtom* aValueAsAtom, bool *aValue)
+{
+  if (aValueAsAtom == nsGkAtoms::_true) {
+    *aValue = true;
+    return NS_OK;
+  }
+  if (aValueAsAtom == nsGkAtoms::_false) {
+    *aValue = false;
+    return NS_OK;
+  }
+  return NS_ERROR_DOM_SYNTAX_ERR;
+}
+
 nsresult
-nsSVGBoolean::SetBaseValueString(const nsAString &aValueAsString,
-                                 nsSVGElement *aSVGElement)
+nsSVGBoolean::SetBaseValueAtom(const nsIAtom* aValue, nsSVGElement *aSVGElement)
 {
   bool val;
 
-  nsresult rv = GetValueFromString(aValueAsString, &val);
+  nsresult rv = GetValueFromAtom(aValue, &val);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   mBaseVal = val;
   if (!mIsAnimated) {
     mAnimVal = mBaseVal;
   }
@@ -90,40 +103,38 @@ nsSVGBoolean::SetBaseValueString(const n
   }
 
   // We don't need to call DidChange* here - we're only called by
   // nsSVGElement::ParseAttribute under nsGenericElement::SetAttr,
   // which takes care of notifying.
   return NS_OK;
 }
 
-void
-nsSVGBoolean::GetBaseValueString(nsAString & aValueAsString)
+nsIAtom*
+nsSVGBoolean::GetBaseValueAtom() const
 {
-  aValueAsString.Assign(mBaseVal
-                        ? NS_LITERAL_STRING("true")
-                        : NS_LITERAL_STRING("false"));
+  return mBaseVal ? nsGkAtoms::_true : nsGkAtoms::_false;
 }
 
 void
-nsSVGBoolean::SetBaseValue(bool aValue,
-                           nsSVGElement *aSVGElement)
+nsSVGBoolean::SetBaseValue(bool aValue, nsSVGElement *aSVGElement)
 {
   NS_PRECONDITION(aValue == true || aValue == false, "Boolean out of range");
 
-  if (aValue != mBaseVal) {
-    mBaseVal = aValue;
-    if (!mIsAnimated) {
-      mAnimVal = mBaseVal;
-    }
-    else {
-      aSVGElement->AnimationNeedsResample();
-    }
-    aSVGElement->DidChangeBoolean(mAttrEnum, true);
+  if (aValue == mBaseVal) {
+    return;
   }
+
+  mBaseVal = aValue;
+  if (!mIsAnimated) {
+    mAnimVal = mBaseVal;
+  } else {
+    aSVGElement->AnimationNeedsResample();
+  }
+  aSVGElement->DidChangeBoolean(mAttrEnum);
 }
 
 void
 nsSVGBoolean::SetAnimValue(bool aValue, nsSVGElement *aSVGElement)
 {
   mAnimVal = aValue;
   mIsAnimated = true;
   aSVGElement->DidAnimateBoolean(mAttrEnum);
--- a/content/svg/content/src/nsSVGBoolean.h
+++ b/content/svg/content/src/nsSVGBoolean.h
@@ -53,19 +53,18 @@ class nsSVGBoolean
 
 public:
   void Init(PRUint8 aAttrEnum = 0xff, bool aValue = false) {
     mAnimVal = mBaseVal = aValue;
     mAttrEnum = aAttrEnum;
     mIsAnimated = false;
   }
 
-  nsresult SetBaseValueString(const nsAString& aValue,
-                              nsSVGElement *aSVGElement);
-  void GetBaseValueString(nsAString& aValue);
+  nsresult SetBaseValueAtom(const nsIAtom* aValue, nsSVGElement *aSVGElement);
+  nsIAtom* GetBaseValueAtom() const;
 
   void SetBaseValue(bool aValue, nsSVGElement *aSVGElement);
   bool GetBaseValue() const
     { return mBaseVal; }
 
   void SetAnimValue(bool aValue, nsSVGElement *aSVGElement);
   bool GetAnimValue() const
     { return mAnimVal; }
--- a/content/svg/content/src/nsSVGElement.cpp
+++ b/content/svg/content/src/nsSVGElement.cpp
@@ -84,16 +84,17 @@
 #include "DOMSVGTests.h"
 #include "nsIDOMSVGUnitTypes.h"
 #include "nsSVGRect.h"
 #include "nsIFrame.h"
 #include "prdtoa.h"
 #include <stdarg.h>
 #include "nsSMILMappedAttribute.h"
 #include "SVGMotionSMILAttr.h"
+#include "nsAttrValueOrString.h"
 
 using namespace mozilla;
 
 // This is needed to ensure correct handling of calls to the
 // vararg-list methods in this file:
 //   nsSVGElement::GetAnimated{Length,Number,Integer}Values
 // See bug 547964 for details:
 PR_STATIC_ASSERT(sizeof(void*) == sizeof(nsnull));
@@ -257,16 +258,25 @@ nsSVGElement::BindToTree(nsIDocument* aD
 
   return NS_OK;
 }
 
 nsresult
 nsSVGElement::AfterSetAttr(PRInt32 aNamespaceID, nsIAtom* aName,
                            const nsAttrValue* aValue, bool aNotify)
 {
+  // We don't currently use nsMappedAttributes within SVG. If this changes, we
+  // need to be very careful because some nsAttrValues used by SVG point to
+  // member data of SVG elements and if an nsAttrValue outlives the SVG element
+  // whose data it points to (by virtue of being stored in
+  // mAttrsAndChildren->mMappedAttributes, meaning it's shared between
+  // elements), the pointer will dangle. See bug 724680.
+  NS_ABORT_IF_FALSE(!mAttrsAndChildren.HasMappedAttrs(),
+    "Unexpected use of nsMappedAttributes within SVG");
+
   // If this is an svg presentation attribute we need to map it into
   // the content stylerule.
   // XXX For some reason incremental mapping doesn't work, so for now
   // just delete the style rule and lazily reconstruct it in
   // GetContentStyleRule()
   if (aNamespaceID == kNameSpaceID_None && IsAttributeMapped(aName)) {
     mContentStyleRule = nsnull;
   }
@@ -285,191 +295,230 @@ nsSVGElement::AfterSetAttr(PRInt32 aName
 bool
 nsSVGElement::ParseAttribute(PRInt32 aNamespaceID,
                              nsIAtom* aAttribute,
                              const nsAString& aValue,
                              nsAttrValue& aResult)
 {
   nsresult rv = NS_OK;
   bool foundMatch = false;
+  bool didSetResult = false;
+
   if (aNamespaceID == kNameSpaceID_None) {
 
     // Check for nsSVGLength2 attribute
     LengthAttributesInfo lengthInfo = GetLengthInfo();
 
     PRUint32 i;
     for (i = 0; i < lengthInfo.mLengthCount; i++) {
       if (aAttribute == *lengthInfo.mLengthInfo[i].mName) {
         rv = lengthInfo.mLengths[i].SetBaseValueString(aValue, this, false);
         if (NS_FAILED(rv)) {
           lengthInfo.Reset(i);
+        } else {
+          aResult.SetTo(lengthInfo.mLengths[i], &aValue);
+          didSetResult = true;
         }
         foundMatch = true;
         break;
       }
     }
 
     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) {
       // 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) {
       // Check for SVGAnimatedPointList attribute
       if (GetPointListAttrName() == aAttribute) {
         SVGAnimatedPointList* pointList = GetAnimatedPointList();
         if (pointList) {
-          rv = pointList->SetBaseValueString(aValue);
-          if (NS_FAILED(rv)) {
-            // The spec says we parse everything up to the failure, so we don't
-            // call pointList->ClearBaseValue()
-          }
+          pointList->SetBaseValueString(aValue);
+          // The spec says we parse everything up to the failure, so we DON'T
+          // need to check the result of SetBaseValueString or call
+          // pointList->ClearBaseValue() if it fails
+          aResult.SetTo(pointList->GetBaseValue(), &aValue);
+          didSetResult = true;
           foundMatch = true;
         }
       }
     }
 
     if (!foundMatch) {
       // Check for SVGAnimatedPathSegList attribute
       if (GetPathDataAttrName() == aAttribute) {
         SVGAnimatedPathSegList* segList = GetAnimPathSegList();
         if (segList) {
-          rv = segList->SetBaseValueString(aValue);
-          if (NS_FAILED(rv)) {
-            // The spec says we parse everything up to the failure, so we don't
-            // call segList->ClearBaseValue()
-          }
+          segList->SetBaseValueString(aValue);
+          // The spec says we parse everything up to the failure, so we DON'T
+          // need to check the result of SetBaseValueString or call
+          // segList->ClearBaseValue() if it fails
+          aResult.SetTo(segList->GetBaseValue(), &aValue);
+          didSetResult = true;
           foundMatch = true;
         }
       }
     }
 
     if (!foundMatch) {
       // Check for nsSVGNumber2 attribute
       NumberAttributesInfo numberInfo = GetNumberInfo();
       for (i = 0; i < numberInfo.mNumberCount; i++) {
         if (aAttribute == *numberInfo.mNumberInfo[i].mName) {
           rv = numberInfo.mNumbers[i].SetBaseValueString(aValue, this);
           if (NS_FAILED(rv)) {
             numberInfo.Reset(i);
+          } else {
+            aResult.SetTo(numberInfo.mNumbers[i].GetBaseValue(), &aValue);
+            didSetResult = true;
           }
           foundMatch = true;
           break;
         }
       }
     }
 
     if (!foundMatch) {
       // Check for nsSVGNumberPair attribute
       NumberPairAttributesInfo numberPairInfo = GetNumberPairInfo();
       for (i = 0; i < numberPairInfo.mNumberPairCount; i++) {
         if (aAttribute == *numberPairInfo.mNumberPairInfo[i].mName) {
           rv = numberPairInfo.mNumberPairs[i].SetBaseValueString(aValue, this);
           if (NS_FAILED(rv)) {
             numberPairInfo.Reset(i);
+          } else {
+            aResult.SetTo(numberPairInfo.mNumberPairs[i], &aValue);
+            didSetResult = true;
           }
           foundMatch = true;
           break;
         }
       }
     }
 
     if (!foundMatch) {
       // Check for nsSVGInteger attribute
       IntegerAttributesInfo integerInfo = GetIntegerInfo();
       for (i = 0; i < integerInfo.mIntegerCount; i++) {
         if (aAttribute == *integerInfo.mIntegerInfo[i].mName) {
           rv = integerInfo.mIntegers[i].SetBaseValueString(aValue, this);
           if (NS_FAILED(rv)) {
             integerInfo.Reset(i);
+          } else {
+            aResult.SetTo(integerInfo.mIntegers[i].GetBaseValue(), &aValue);
+            didSetResult = true;
           }
           foundMatch = true;
           break;
         }
       }
     }
 
     if (!foundMatch) {
       // Check for nsSVGIntegerPair attribute
       IntegerPairAttributesInfo integerPairInfo = GetIntegerPairInfo();
       for (i = 0; i < integerPairInfo.mIntegerPairCount; i++) {
         if (aAttribute == *integerPairInfo.mIntegerPairInfo[i].mName) {
-          rv = integerPairInfo.mIntegerPairs[i].SetBaseValueString(aValue, this);
+          rv =
+            integerPairInfo.mIntegerPairs[i].SetBaseValueString(aValue, this);
           if (NS_FAILED(rv)) {
             integerPairInfo.Reset(i);
+          } else {
+            aResult.SetTo(integerPairInfo.mIntegerPairs[i], &aValue);
+            didSetResult = true;
           }
           foundMatch = true;
           break;
         }
       }
     }
 
     if (!foundMatch) {
       // Check for nsSVGAngle attribute
       AngleAttributesInfo angleInfo = GetAngleInfo();
       for (i = 0; i < angleInfo.mAngleCount; i++) {
         if (aAttribute == *angleInfo.mAngleInfo[i].mName) {
           rv = angleInfo.mAngles[i].SetBaseValueString(aValue, this, false);
           if (NS_FAILED(rv)) {
             angleInfo.Reset(i);
+          } else {
+            aResult.SetTo(angleInfo.mAngles[i], &aValue);
+            didSetResult = true;
           }
           foundMatch = true;
           break;
         }
       }
     }
 
     if (!foundMatch) {
       // Check for nsSVGBoolean attribute
       BooleanAttributesInfo booleanInfo = GetBooleanInfo();
       for (i = 0; i < booleanInfo.mBooleanCount; i++) {
         if (aAttribute == *booleanInfo.mBooleanInfo[i].mName) {
-          rv = booleanInfo.mBooleans[i].SetBaseValueString(aValue, this);
+          nsCOMPtr<nsIAtom> valAtom = do_GetAtom(aValue);
+          rv = booleanInfo.mBooleans[i].SetBaseValueAtom(valAtom, this);
           if (NS_FAILED(rv)) {
             booleanInfo.Reset(i);
+          } else {
+            aResult.SetTo(valAtom);
+            didSetResult = true;
           }
           foundMatch = true;
           break;
         }
       }
     }
 
     if (!foundMatch) {
       // Check for nsSVGEnum attribute
       EnumAttributesInfo enumInfo = GetEnumInfo();
       for (i = 0; i < enumInfo.mEnumCount; i++) {
         if (aAttribute == *enumInfo.mEnumInfo[i].mName) {
-          rv = enumInfo.mEnums[i].SetBaseValueString(aValue, this);
+          nsCOMPtr<nsIAtom> valAtom = do_GetAtom(aValue);
+          rv = enumInfo.mEnums[i].SetBaseValueAtom(valAtom, this);
           if (NS_FAILED(rv)) {
             enumInfo.Reset(i);
+          } else {
+            aResult.SetTo(valAtom);
+            didSetResult = true;
           }
           foundMatch = true;
           break;
         }
       }
     }
 
     if (!foundMatch) {
@@ -481,55 +530,67 @@ nsSVGElement::ParseAttribute(PRInt32 aNa
       }
     }
 
     if (!foundMatch) {
       // Check for StringList attribute
       StringListAttributesInfo stringListInfo = GetStringListInfo();
       for (i = 0; i < stringListInfo.mStringListCount; i++) {
         if (aAttribute == *stringListInfo.mStringListInfo[i].mName) {
-          rv = stringListInfo.mStringLists[i].SetValue(aValue, false);
+          rv = stringListInfo.mStringLists[i].SetValue(aValue);
           if (NS_FAILED(rv)) {
             stringListInfo.Reset(i);
+          } else {
+            aResult.SetTo(stringListInfo.mStringLists[i], &aValue);
+            didSetResult = true;
           }
           foundMatch = true;
           break;
         }
       }
     }
 
     if (!foundMatch) {
       // Check for nsSVGViewBox attribute
       if (aAttribute == nsGkAtoms::viewBox) {
         nsSVGViewBox* viewBox = GetViewBox();
         if (viewBox) {
           rv = viewBox->SetBaseValueString(aValue, this);
           if (NS_FAILED(rv)) {
             viewBox->Init();
+          } else {
+            aResult.SetTo(*viewBox, &aValue);
+            didSetResult = true;
           }
           foundMatch = true;
         }
       // Check for SVGAnimatedPreserveAspectRatio attribute
       } else if (aAttribute == nsGkAtoms::preserveAspectRatio) {
         SVGAnimatedPreserveAspectRatio *preserveAspectRatio =
           GetPreserveAspectRatio();
         if (preserveAspectRatio) {
           rv = preserveAspectRatio->SetBaseValueString(aValue, this);
           if (NS_FAILED(rv)) {
             preserveAspectRatio->Init();
+          } else {
+            aResult.SetTo(*preserveAspectRatio, &aValue);
+            didSetResult = true;
           }
           foundMatch = true;
         }
       // Check for SVGAnimatedTransformList attribute
       } else if (GetTransformListAttrName() == aAttribute) {
         SVGAnimatedTransformList *transformList = GetAnimatedTransformList();
         if (transformList) {
           rv = transformList->SetBaseValueString(aValue);
           if (NS_FAILED(rv)) {
             transformList->ClearBaseValue();
+          } else {
+            aResult.SetTo(transformList->GetBaseValue(), &aValue);
+            didSetResult = true;
           }
           foundMatch = true;
         }
       }
     }
   }
 
   if (!foundMatch) {
@@ -545,17 +606,19 @@ nsSVGElement::ParseAttribute(PRInt32 aNa
     }
   }
 
   if (foundMatch) {
     if (NS_FAILED(rv)) {
       ReportAttributeParseFailure(OwnerDoc(), aAttribute, aValue);
       return false;
     }
-    aResult.SetTo(aValue);
+    if (!didSetResult) {
+      aResult.SetTo(aValue);
+    }
     return true;
   }
 
   return nsSVGElementBase::ParseAttribute(aNamespaceID, aAttribute, aValue,
                                           aResult);
 }
 
 void
@@ -579,198 +642,195 @@ nsSVGElement::UnsetAttrInternal(PRInt32 
       return;
     }
     
     // Check if this is a length attribute going away
     LengthAttributesInfo lenInfo = GetLengthInfo();
 
     for (PRUint32 i = 0; i < lenInfo.mLengthCount; i++) {
       if (aName == *lenInfo.mLengthInfo[i].mName) {
+        MaybeSerializeAttrBeforeRemoval(aName, aNotify);
         lenInfo.Reset(i);
-        DidChangeLength(i, false);
         return;
       }
     }
 
     // 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++) {
       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) {
+        MaybeSerializeAttrBeforeRemoval(aName, aNotify);
         pointList->ClearBaseValue();
         return;
       }
     }
 
     // Check if this is a path segment list attribute going away
     if (GetPathDataAttrName() == aName) {
       SVGAnimatedPathSegList *segList = GetAnimPathSegList();
       if (segList) {
+        MaybeSerializeAttrBeforeRemoval(aName, aNotify);
         segList->ClearBaseValue();
-        DidChangePathSegList(false);
         return;
       }
     }
 
     // Check if this is a number attribute going away
     NumberAttributesInfo numInfo = GetNumberInfo();
 
     for (PRUint32 i = 0; i < numInfo.mNumberCount; i++) {
       if (aName == *numInfo.mNumberInfo[i].mName) {
         numInfo.Reset(i);
-        DidChangeNumber(i, false);
         return;
       }
     }
 
     // Check if this is a number pair attribute going away
     NumberPairAttributesInfo numPairInfo = GetNumberPairInfo();
 
     for (PRUint32 i = 0; i < numPairInfo.mNumberPairCount; i++) {
       if (aName == *numPairInfo.mNumberPairInfo[i].mName) {
+        MaybeSerializeAttrBeforeRemoval(aName, aNotify);
         numPairInfo.Reset(i);
-        DidChangeNumberPair(i, false);
         return;
       }
     }
 
     // Check if this is an integer attribute going away
     IntegerAttributesInfo intInfo = GetIntegerInfo();
 
     for (PRUint32 i = 0; i < intInfo.mIntegerCount; i++) {
       if (aName == *intInfo.mIntegerInfo[i].mName) {
         intInfo.Reset(i);
-        DidChangeInteger(i, false);
         return;
       }
     }
 
     // Check if this is an integer pair attribute going away
     IntegerPairAttributesInfo intPairInfo = GetIntegerPairInfo();
 
     for (PRUint32 i = 0; i < intPairInfo.mIntegerPairCount; i++) {
       if (aName == *intPairInfo.mIntegerPairInfo[i].mName) {
+        MaybeSerializeAttrBeforeRemoval(aName, aNotify);
         intPairInfo.Reset(i);
-        DidChangeIntegerPair(i, false);
         return;
       }
     }
 
     // Check if this is an angle attribute going away
     AngleAttributesInfo angleInfo = GetAngleInfo();
 
     for (PRUint32 i = 0; i < angleInfo.mAngleCount; i++) {
       if (aName == *angleInfo.mAngleInfo[i].mName) {
+        MaybeSerializeAttrBeforeRemoval(aName, aNotify);
         angleInfo.Reset(i);
-        DidChangeAngle(i, false);
         return;
       }
     }
 
     // Check if this is a boolean attribute going away
     BooleanAttributesInfo boolInfo = GetBooleanInfo();
 
     for (PRUint32 i = 0; i < boolInfo.mBooleanCount; i++) {
       if (aName == *boolInfo.mBooleanInfo[i].mName) {
         boolInfo.Reset(i);
-        DidChangeBoolean(i, false);
         return;
       }
     }
 
     // Check if this is an enum attribute going away
     EnumAttributesInfo enumInfo = GetEnumInfo();
 
     for (PRUint32 i = 0; i < enumInfo.mEnumCount; i++) {
       if (aName == *enumInfo.mEnumInfo[i].mName) {
         enumInfo.Reset(i);
-        DidChangeEnum(i, false);
         return;
       }
     }
 
     // Check if this is a nsViewBox attribute going away
     if (aName == nsGkAtoms::viewBox) {
       nsSVGViewBox* viewBox = GetViewBox();
       if (viewBox) {
+        MaybeSerializeAttrBeforeRemoval(aName, aNotify);
         viewBox->Init();
-        DidChangeViewBox(false);
         return;
       }
     }
 
     // Check if this is a preserveAspectRatio attribute going away
     if (aName == nsGkAtoms::preserveAspectRatio) {
       SVGAnimatedPreserveAspectRatio *preserveAspectRatio =
         GetPreserveAspectRatio();
-
       if (preserveAspectRatio) {
+        MaybeSerializeAttrBeforeRemoval(aName, aNotify);
         preserveAspectRatio->Init();
-        DidChangePreserveAspectRatio(false);
         return;
       }
     }
 
     // Check if this is a transform list attribute going away
     if (GetTransformListAttrName() == aName) {
       SVGAnimatedTransformList *transformList = GetAnimatedTransformList();
       if (transformList) {
+        MaybeSerializeAttrBeforeRemoval(aName, aNotify);
         transformList->ClearBaseValue();
-        DidChangeTransformList(false);
         return;
       }
     }
 
     // Check for conditional processing attributes
     nsCOMPtr<DOMSVGTests> tests(do_QueryInterface(this));
     if (tests && tests->IsConditionalProcessingAttribute(aName)) {
+      MaybeSerializeAttrBeforeRemoval(aName, aNotify);
       tests->UnsetAttr(aName);
       return;
     }
 
     // Check if this is a string list attribute going away
     StringListAttributesInfo stringListInfo = GetStringListInfo();
 
     for (PRUint32 i = 0; i < stringListInfo.mStringListCount; i++) {
       if (aName == *stringListInfo.mStringListInfo[i].mName) {
+        MaybeSerializeAttrBeforeRemoval(aName, aNotify);
         stringListInfo.Reset(i);
         return;
       }
     }
   }
 
   // Check if this is a string attribute going away
   StringAttributesInfo stringInfo = GetStringInfo();
 
   for (PRUint32 i = 0; i < stringInfo.mStringCount; i++) {
     if (aNamespaceID == stringInfo.mStringInfo[i].mNamespaceID &&
         aName == *stringInfo.mStringInfo[i].mName) {
       stringInfo.Reset(i);
-      DidChangeString(i);
       return;
     }
   }
 }
 
 nsresult
 nsSVGElement::UnsetAttr(PRInt32 aNamespaceID, nsIAtom* aName,
                         bool aNotify)
@@ -1242,16 +1302,147 @@ css::StyleRule*
 nsSVGElement::GetAnimatedContentStyleRule()
 {
   return
     static_cast<css::StyleRule*>(GetProperty(SMIL_MAPPED_ATTR_ANIMVAL,
                                              SMIL_MAPPED_ATTR_STYLERULE_ATOM,
                                              nsnull));
 }
 
+/**
+ * Helper methods for the type-specific WillChangeXXX methods.
+ *
+ * This method sends out appropriate pre-change notifications so that selector
+ * restyles (e.g. due to changes that cause |elem[attr="val"]| to start/stop
+ * matching) work, and it returns an nsAttrValue that _may_ contain the
+ * attribute's pre-change value.
+ *
+ * The nsAttrValue returned by this method depends on whether there are
+ * mutation event listeners listening for changes to this element's attributes.
+ * If not, then the object returned is empty. If there are, then the
+ * nsAttrValue returned contains a serialized copy of the attribute's value
+ * prior to the change, and this object should be passed to the corresponding
+ * DidChangeXXX method call (assuming a WillChangeXXX call is required for the
+ * SVG type - see comment below). This is necessary so that the 'prevValue'
+ * property of the mutation event that is dispatched will correctly contain the
+ * old value.
+ *
+ * The reason we need to serialize the old value if there are mutation
+ * event listeners is because the underlying nsAttrValue for the attribute
+ * points directly to a parsed representation of the attribute (e.g. an
+ * SVGAnimatedLengthList*) that is a member of the SVG element. That object
+ * will have changed by the time DidChangeXXX has been called, so without the
+ * serialization of the old attribute value that we provide, DidChangeXXX
+ * would have no way to get the old value to pass to SetAttrAndNotify.
+ *
+ * We only return the old value when there are mutation event listeners because
+ * it's not needed otherwise, and because it's expensive to serialize the old
+ * value. This is especially true for list type attributes, which may be built
+ * up via the SVG DOM resulting in a large number of Will/DidModifyXXX calls
+ * before the script finally finishes setting the attribute.
+ *
+ * Note that unlike using SetParsedAttr, using Will/DidChangeXXX does NOT check
+ * and filter out redundant changes. Before calling WillChangeXXX, the caller
+ * should check whether the new and old values are actually the same, and skip
+ * calling Will/DidChangeXXX if they are.
+ *
+ * Also note that not all SVG types use this scheme. For types that can be
+ * represented by an nsAttrValue without pointing back to an SVG object (e.g.
+ * enums, booleans, integers) we can simply use SetParsedAttr which will do all
+ * of the above for us. For such types there is no matching WillChangeXXX
+ * method, only DidChangeXXX which calls SetParsedAttr.
+ */
+nsAttrValue
+nsSVGElement::WillChangeValue(nsIAtom* aName)
+{
+  // We need an empty attr value:
+  //   a) to pass to BeforeSetAttr when GetParsedAttr returns nsnull
+  //   b) to store the old value in the case we have mutation listeners
+  // We can use the same value for both purposes since (a) happens before (b).
+  // Also, we should be careful to always return this value to benefit from
+  // return value optimization.
+  nsAttrValue emptyOrOldAttrValue;
+  const nsAttrValue* attrValue = GetParsedAttr(aName);
+
+  // This is not strictly correct--the attribute value parameter for
+  // BeforeSetAttr should reflect the value that *will* be set but that implies
+  // allocating, e.g. an extra nsSVGLength2, and isn't necessary at the moment
+  // since no SVG elements overload BeforeSetAttr. For now we just pass the
+  // current value.
+  nsAttrValueOrString attrStringOrValue(attrValue ? *attrValue
+                                                  : emptyOrOldAttrValue);
+  DebugOnly<nsresult> rv =
+    BeforeSetAttr(kNameSpaceID_None, aName, &attrStringOrValue,
+                  kNotifyDocumentObservers);
+  // SVG elements aren't expected to overload BeforeSetAttr in such a way that
+  // it may fail. So long as this is the case we don't need to check and pass on
+  // the return value which simplifies the calling code significantly.
+  NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv), "Unexpected failure from BeforeSetAttr");
+
+  // We only need to set the old value if we have listeners since otherwise it
+  // isn't used.
+  if (attrValue &&
+      nsContentUtils::HasMutationListeners(this,
+                                           NS_EVENT_BITS_MUTATION_ATTRMODIFIED,
+                                           this)) {
+    emptyOrOldAttrValue.SetToSerialized(*attrValue);
+  }
+
+  PRUint8 modType = attrValue
+                  ? static_cast<PRUint8>(nsIDOMMutationEvent::MODIFICATION)
+                  : static_cast<PRUint8>(nsIDOMMutationEvent::ADDITION);
+  nsNodeUtils::AttributeWillChange(this, kNameSpaceID_None, aName, modType);
+
+  return emptyOrOldAttrValue;
+}
+
+/**
+ * Helper methods for the type-specific DidChangeXXX methods.
+ *
+ * aEmptyOrOldValue will normally be the object returned from the corresponding
+ * WillChangeXXX call. This is because:
+ * a) WillChangeXXX will ensure the object is set when we have mutation
+ *    listeners, and
+ * b) WillChangeXXX will ensure the object represents a serialized version of
+ *    the old attribute value so that the value doesn't change when the
+ *    underlying SVG type is updated.
+ */
+void
+nsSVGElement::DidChangeValue(nsIAtom* aName,
+                             const nsAttrValue& aEmptyOrOldValue,
+                             nsAttrValue& aNewValue)
+{
+  bool hasListeners =
+    nsContentUtils::HasMutationListeners(this,
+                                         NS_EVENT_BITS_MUTATION_ATTRMODIFIED,
+                                         this);
+  PRUint8 modType = HasAttr(kNameSpaceID_None, aName)
+                  ? static_cast<PRUint8>(nsIDOMMutationEvent::MODIFICATION)
+                  : static_cast<PRUint8>(nsIDOMMutationEvent::ADDITION);
+  SetAttrAndNotify(kNameSpaceID_None, aName, nsnull, aEmptyOrOldValue,
+                   aNewValue, modType, hasListeners, kNotifyDocumentObservers,
+                   kCallAfterSetAttr);
+}
+
+void
+nsSVGElement::MaybeSerializeAttrBeforeRemoval(nsIAtom* aName, bool aNotify)
+{
+  if (!aNotify ||
+      !nsContentUtils::HasMutationListeners(this,
+                                            NS_EVENT_BITS_MUTATION_ATTRMODIFIED,
+                                            this)) {
+    return;
+  }
+
+  nsAutoString serializedValue;
+  mAttrsAndChildren.GetAttr(aName)->ToString(serializedValue);
+  nsAttrValue attrValue(serializedValue);
+  mAttrsAndChildren.SetAndTakeAttr(aName, attrValue);
+}
+
 /* static */
 nsIAtom* nsSVGElement::GetEventNameForAttr(nsIAtom* aAttr)
 {
   if (aAttr == nsGkAtoms::onload)
     return nsGkAtoms::onSVGLoad;
   if (aAttr == nsGkAtoms::onunload)
     return nsGkAtoms::onSVGUnload;
   if (aAttr == nsGkAtoms::onabort)
@@ -1324,35 +1515,37 @@ nsSVGElement::SetLength(nsIAtom* aName, 
       lengthInfo.mLengths[i] = aLength;
       DidAnimateLength(i);
       return;
     }
   }
   NS_ABORT_IF_FALSE(false, "no length found to set");
 }
 
-void
-nsSVGElement::DidChangeLength(PRUint8 aAttrEnum, bool aDoSetAttr)
+nsAttrValue
+nsSVGElement::WillChangeLength(PRUint8 aAttrEnum)
 {
-  if (!aDoSetAttr)
-    return;
+  return WillChangeValue(*GetLengthInfo().mLengthInfo[aAttrEnum].mName);
+}
 
+void
+nsSVGElement::DidChangeLength(PRUint8 aAttrEnum,
+                              const nsAttrValue& aEmptyOrOldValue)
+{
   LengthAttributesInfo info = GetLengthInfo();
 
   NS_ASSERTION(info.mLengthCount > 0,
                "DidChangeLength on element with no length attribs");
-
   NS_ASSERTION(aAttrEnum < info.mLengthCount, "aAttrEnum out of range");
 
-  nsAutoString serializedValue;
-  info.mLengths[aAttrEnum].GetBaseValueString(serializedValue);
+  nsAttrValue newValue;
+  newValue.SetTo(info.mLengths[aAttrEnum], nsnull);
 
-  nsAttrValue attrValue(serializedValue);
-  SetParsedAttr(kNameSpaceID_None, *info.mLengthInfo[aAttrEnum].mName, nsnull,
-                attrValue, true);
+  DidChangeValue(*info.mLengthInfo[aAttrEnum].mName, aEmptyOrOldValue,
+                 newValue);
 }
 
 void
 nsSVGElement::DidAnimateLength(PRUint8 aAttrEnum)
 {
   nsIFrame* frame = GetPrimaryFrame();
 
   if (frame) {
@@ -1419,34 +1612,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) {
@@ -1501,34 +1697,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) {
@@ -1560,30 +1760,34 @@ nsSVGElement::GetAnimatedNumberList(nsIA
     if (aAttrName == *info.mNumberListInfo[i].mName) {
       return &info.mNumberLists[i];
     }
   }
   NS_ABORT_IF_FALSE(false, "Bad caller");
   return nsnull;
 }
 
-void
-nsSVGElement::DidChangePointList(bool aDoSetAttr)
+nsAttrValue
+nsSVGElement::WillChangePointList()
 {
-  NS_ABORT_IF_FALSE(GetPointListAttrName(), "Changing non-existent point list?");
+  NS_ABORT_IF_FALSE(GetPointListAttrName(),
+                    "Changing non-existent point list?");
+  return WillChangeValue(GetPointListAttrName());
+}
 
-  if (!aDoSetAttr)
-    return;
+void
+nsSVGElement::DidChangePointList(const nsAttrValue& aEmptyOrOldValue)
+{
+  NS_ABORT_IF_FALSE(GetPointListAttrName(),
+                    "Changing non-existent point list?");
 
-  nsAutoString serializedValue;
-  GetAnimatedPointList()->GetBaseValue().GetValueAsString(serializedValue);
+  nsAttrValue newValue;
+  newValue.SetTo(GetAnimatedPointList()->GetBaseValue(), nsnull);
 
-  nsAttrValue attrValue(serializedValue);
-  SetParsedAttr(kNameSpaceID_None, GetPointListAttrName(), nsnull,
-                attrValue, true);
+  DidChangeValue(GetPointListAttrName(), aEmptyOrOldValue, newValue);
 }
 
 void
 nsSVGElement::DidAnimatePointList()
 {
   NS_ABORT_IF_FALSE(GetPointListAttrName(),
                     "Animating non-existent path data?");
 
@@ -1591,28 +1795,34 @@ nsSVGElement::DidAnimatePointList()
 
   if (frame) {
     frame->AttributeChanged(kNameSpaceID_None,
                             GetPointListAttrName(),
                             nsIDOMMutationEvent::MODIFICATION);
   }
 }
 
-void
-nsSVGElement::DidChangePathSegList(bool aDoSetAttr)
+nsAttrValue
+nsSVGElement::WillChangePathSegList()
 {
-  if (!aDoSetAttr)
-    return;
+  NS_ABORT_IF_FALSE(GetPathDataAttrName(),
+                    "Changing non-existent path seg list?");
+  return WillChangeValue(GetPathDataAttrName());
+}
 
-  nsAutoString serializedValue;
-  GetAnimPathSegList()->GetBaseValue().GetValueAsString(serializedValue);
+void
+nsSVGElement::DidChangePathSegList(const nsAttrValue& aEmptyOrOldValue)
+{
+  NS_ABORT_IF_FALSE(GetPathDataAttrName(),
+                    "Changing non-existent path seg list?");
 
-  nsAttrValue attrValue(serializedValue);
-  SetParsedAttr(kNameSpaceID_None, GetPathDataAttrName(), nsnull,
-                attrValue, true);
+  nsAttrValue newValue;
+  newValue.SetTo(GetAnimPathSegList()->GetBaseValue(), nsnull);
+
+  DidChangeValue(GetPathDataAttrName(), aEmptyOrOldValue, newValue);
 }
 
 void
 nsSVGElement::DidAnimatePathSegList()
 {
   NS_ABORT_IF_FALSE(GetPathDataAttrName(),
                     "Animating non-existent path data?");
 
@@ -1633,32 +1843,27 @@ nsSVGElement::GetNumberInfo()
 
 void nsSVGElement::NumberAttributesInfo::Reset(PRUint8 aAttrEnum)
 {
   mNumbers[aAttrEnum].Init(aAttrEnum,
                            mNumberInfo[aAttrEnum].mDefaultValue);
 }
 
 void
-nsSVGElement::DidChangeNumber(PRUint8 aAttrEnum, bool aDoSetAttr)
+nsSVGElement::DidChangeNumber(PRUint8 aAttrEnum)
 {
-  if (!aDoSetAttr)
-    return;
-
   NumberAttributesInfo info = GetNumberInfo();
 
   NS_ASSERTION(info.mNumberCount > 0,
                "DidChangeNumber on element with no number attribs");
-
   NS_ASSERTION(aAttrEnum < info.mNumberCount, "aAttrEnum out of range");
 
-  nsAutoString serializedValue;
-  info.mNumbers[aAttrEnum].GetBaseValueString(serializedValue);
+  nsAttrValue attrValue;
+  attrValue.SetTo(info.mNumbers[aAttrEnum].GetBaseValue(), nsnull);
 
-  nsAttrValue attrValue(serializedValue);
   SetParsedAttr(kNameSpaceID_None, *info.mNumberInfo[aAttrEnum].mName, nsnull,
                 attrValue, true);
 }
 
 void
 nsSVGElement::DidAnimateNumber(PRUint8 aAttrEnum)
 {
   nsIFrame* frame = GetPrimaryFrame();
@@ -1700,35 +1905,37 @@ nsSVGElement::GetNumberPairInfo()
 
 void nsSVGElement::NumberPairAttributesInfo::Reset(PRUint8 aAttrEnum)
 {
   mNumberPairs[aAttrEnum].Init(aAttrEnum,
                                mNumberPairInfo[aAttrEnum].mDefaultValue1,
                                mNumberPairInfo[aAttrEnum].mDefaultValue2);
 }
 
-void
-nsSVGElement::DidChangeNumberPair(PRUint8 aAttrEnum, bool aDoSetAttr)
+nsAttrValue
+nsSVGElement::WillChangeNumberPair(PRUint8 aAttrEnum)
 {
-  if (!aDoSetAttr)
-    return;
+  return WillChangeValue(*GetNumberPairInfo().mNumberPairInfo[aAttrEnum].mName);
+}
 
+void
+nsSVGElement::DidChangeNumberPair(PRUint8 aAttrEnum,
+                                  const nsAttrValue& aEmptyOrOldValue)
+{
   NumberPairAttributesInfo info = GetNumberPairInfo();
 
   NS_ASSERTION(info.mNumberPairCount > 0,
                "DidChangePairNumber on element with no number pair attribs");
-
   NS_ASSERTION(aAttrEnum < info.mNumberPairCount, "aAttrEnum out of range");
 
-  nsAutoString serializedValue;
-  info.mNumberPairs[aAttrEnum].GetBaseValueString(serializedValue);
+  nsAttrValue newValue;
+  newValue.SetTo(info.mNumberPairs[aAttrEnum], nsnull);
 
-  nsAttrValue attrValue(serializedValue);
-  SetParsedAttr(kNameSpaceID_None, *info.mNumberPairInfo[aAttrEnum].mName, nsnull,
-                attrValue, true);
+  DidChangeValue(*info.mNumberPairInfo[aAttrEnum].mName, aEmptyOrOldValue,
+                 newValue);
 }
 
 void
 nsSVGElement::DidAnimateNumberPair(PRUint8 aAttrEnum)
 {
   nsIFrame* frame = GetPrimaryFrame();
 
   if (frame) {
@@ -1747,32 +1954,27 @@ nsSVGElement::GetIntegerInfo()
 
 void nsSVGElement::IntegerAttributesInfo::Reset(PRUint8 aAttrEnum)
 {
   mIntegers[aAttrEnum].Init(aAttrEnum,
                             mIntegerInfo[aAttrEnum].mDefaultValue);
 }
 
 void
-nsSVGElement::DidChangeInteger(PRUint8 aAttrEnum, bool aDoSetAttr)
+nsSVGElement::DidChangeInteger(PRUint8 aAttrEnum)
 {
-  if (!aDoSetAttr)
-    return;
-
   IntegerAttributesInfo info = GetIntegerInfo();
 
   NS_ASSERTION(info.mIntegerCount > 0,
                "DidChangeInteger on element with no integer attribs");
-
   NS_ASSERTION(aAttrEnum < info.mIntegerCount, "aAttrEnum out of range");
 
-  nsAutoString serializedValue;
-  info.mIntegers[aAttrEnum].GetBaseValueString(serializedValue);
+  nsAttrValue attrValue;
+  attrValue.SetTo(info.mIntegers[aAttrEnum].GetBaseValue(), nsnull);
 
-  nsAttrValue attrValue(serializedValue);
   SetParsedAttr(kNameSpaceID_None, *info.mIntegerInfo[aAttrEnum].mName, nsnull,
                 attrValue, true);
 }
 
 void
 nsSVGElement::DidAnimateInteger(PRUint8 aAttrEnum)
 {
   nsIFrame* frame = GetPrimaryFrame();
@@ -1814,35 +2016,38 @@ nsSVGElement::GetIntegerPairInfo()
 
 void nsSVGElement::IntegerPairAttributesInfo::Reset(PRUint8 aAttrEnum)
 {
   mIntegerPairs[aAttrEnum].Init(aAttrEnum,
                                 mIntegerPairInfo[aAttrEnum].mDefaultValue1,
                                 mIntegerPairInfo[aAttrEnum].mDefaultValue2);
 }
 
-void
-nsSVGElement::DidChangeIntegerPair(PRUint8 aAttrEnum, bool aDoSetAttr)
+nsAttrValue
+nsSVGElement::WillChangeIntegerPair(PRUint8 aAttrEnum)
 {
-  if (!aDoSetAttr)
-    return;
+  return WillChangeValue(
+    *GetIntegerPairInfo().mIntegerPairInfo[aAttrEnum].mName);
+}
 
+void
+nsSVGElement::DidChangeIntegerPair(PRUint8 aAttrEnum,
+                                   const nsAttrValue& aEmptyOrOldValue)
+{
   IntegerPairAttributesInfo info = GetIntegerPairInfo();
 
   NS_ASSERTION(info.mIntegerPairCount > 0,
                "DidChangeIntegerPair on element with no integer pair attribs");
-
   NS_ASSERTION(aAttrEnum < info.mIntegerPairCount, "aAttrEnum out of range");
 
-  nsAutoString serializedValue;
-  info.mIntegerPairs[aAttrEnum].GetBaseValueString(serializedValue);
+  nsAttrValue newValue;
+  newValue.SetTo(info.mIntegerPairs[aAttrEnum], nsnull);
 
-  nsAttrValue attrValue(serializedValue);
-  SetParsedAttr(kNameSpaceID_None, *info.mIntegerPairInfo[aAttrEnum].mName, nsnull,
-                attrValue, true);
+  DidChangeValue(*info.mIntegerPairInfo[aAttrEnum].mName, aEmptyOrOldValue,
+                 newValue);
 }
 
 void
 nsSVGElement::DidAnimateIntegerPair(PRUint8 aAttrEnum)
 {
   nsIFrame* frame = GetPrimaryFrame();
   
   if (frame) {
@@ -1861,35 +2066,36 @@ nsSVGElement::GetAngleInfo()
 
 void nsSVGElement::AngleAttributesInfo::Reset(PRUint8 aAttrEnum)
 {
   mAngles[aAttrEnum].Init(aAttrEnum, 
                           mAngleInfo[aAttrEnum].mDefaultValue,
                           mAngleInfo[aAttrEnum].mDefaultUnitType);
 }
 
-void
-nsSVGElement::DidChangeAngle(PRUint8 aAttrEnum, bool aDoSetAttr)
+nsAttrValue
+nsSVGElement::WillChangeAngle(PRUint8 aAttrEnum)
 {
-  if (!aDoSetAttr)
-    return;
+  return WillChangeValue(*GetAngleInfo().mAngleInfo[aAttrEnum].mName);
+}
 
+void
+nsSVGElement::DidChangeAngle(PRUint8 aAttrEnum,
+                             const nsAttrValue& aEmptyOrOldValue)
+{
   AngleAttributesInfo info = GetAngleInfo();
 
   NS_ASSERTION(info.mAngleCount > 0,
                "DidChangeAngle on element with no angle attribs");
-
   NS_ASSERTION(aAttrEnum < info.mAngleCount, "aAttrEnum out of range");
 
-  nsAutoString serializedValue;
-  info.mAngles[aAttrEnum].GetBaseValueString(serializedValue);
+  nsAttrValue newValue;
+  newValue.SetTo(info.mAngles[aAttrEnum], nsnull);
 
-  nsAttrValue attrValue(serializedValue);
-  SetParsedAttr(kNameSpaceID_None, *info.mAngleInfo[aAttrEnum].mName, nsnull,
-                attrValue, true);
+  DidChangeValue(*info.mAngleInfo[aAttrEnum].mName, aEmptyOrOldValue, newValue);
 }
 
 void
 nsSVGElement::DidAnimateAngle(PRUint8 aAttrEnum)
 {
   nsIFrame* frame = GetPrimaryFrame();
 
   if (frame) {
@@ -1908,32 +2114,25 @@ nsSVGElement::GetBooleanInfo()
 
 void nsSVGElement::BooleanAttributesInfo::Reset(PRUint8 aAttrEnum)
 {
   mBooleans[aAttrEnum].Init(aAttrEnum,
                             mBooleanInfo[aAttrEnum].mDefaultValue);
 }
 
 void
-nsSVGElement::DidChangeBoolean(PRUint8 aAttrEnum, bool aDoSetAttr)
+nsSVGElement::DidChangeBoolean(PRUint8 aAttrEnum)
 {
-  if (!aDoSetAttr)
-    return;
-
   BooleanAttributesInfo info = GetBooleanInfo();
 
   NS_ASSERTION(info.mBooleanCount > 0,
                "DidChangeBoolean on element with no boolean attribs");
-
   NS_ASSERTION(aAttrEnum < info.mBooleanCount, "aAttrEnum out of range");
 
-  nsAutoString serializedValue;
-  info.mBooleans[aAttrEnum].GetBaseValueString(serializedValue);
-
-  nsAttrValue attrValue(serializedValue);
+  nsAttrValue attrValue(info.mBooleans[aAttrEnum].GetBaseValueAtom());
   SetParsedAttr(kNameSpaceID_None, *info.mBooleanInfo[aAttrEnum].mName, nsnull,
                 attrValue, true);
 }
 
 void
 nsSVGElement::DidAnimateBoolean(PRUint8 aAttrEnum)
 {
   nsIFrame* frame = GetPrimaryFrame();
@@ -1954,32 +2153,25 @@ nsSVGElement::GetEnumInfo()
 
 void nsSVGElement::EnumAttributesInfo::Reset(PRUint8 aAttrEnum)
 {
   mEnums[aAttrEnum].Init(aAttrEnum,
                          mEnumInfo[aAttrEnum].mDefaultValue);
 }
 
 void
-nsSVGElement::DidChangeEnum(PRUint8 aAttrEnum, bool aDoSetAttr)
+nsSVGElement::DidChangeEnum(PRUint8 aAttrEnum)
 {
-  if (!aDoSetAttr)
-    return;
-
   EnumAttributesInfo info = GetEnumInfo();
 
   NS_ASSERTION(info.mEnumCount > 0,
                "DidChangeEnum on element with no enum attribs");
-
   NS_ASSERTION(aAttrEnum < info.mEnumCount, "aAttrEnum out of range");
 
-  nsAutoString serializedValue;
-  info.mEnums[aAttrEnum].GetBaseValueString(serializedValue, this);
-
-  nsAttrValue attrValue(serializedValue);
+  nsAttrValue attrValue(info.mEnums[aAttrEnum].GetBaseValueAtom(this));
   SetParsedAttr(kNameSpaceID_None, *info.mEnumInfo[aAttrEnum].mName, nsnull,
                 attrValue, true);
 }
 
 void
 nsSVGElement::DidAnimateEnum(PRUint8 aAttrEnum)
 {
   nsIFrame* frame = GetPrimaryFrame();
@@ -1993,32 +2185,33 @@ nsSVGElement::DidAnimateEnum(PRUint8 aAt
 }
 
 nsSVGViewBox *
 nsSVGElement::GetViewBox()
 {
   return nsnull;
 }
 
-void
-nsSVGElement::DidChangeViewBox(bool aDoSetAttr)
+nsAttrValue
+nsSVGElement::WillChangeViewBox()
 {
-  if (!aDoSetAttr)
-    return;
+  return WillChangeValue(nsGkAtoms::viewBox);
+}
 
+void
+nsSVGElement::DidChangeViewBox(const nsAttrValue& aEmptyOrOldValue)
+{
   nsSVGViewBox *viewBox = GetViewBox();
 
   NS_ASSERTION(viewBox, "DidChangeViewBox on element with no viewBox attrib");
 
-  nsAutoString serializedValue;
-  viewBox->GetBaseValueString(serializedValue);
+  nsAttrValue newValue;
+  newValue.SetTo(*viewBox, nsnull);
 
-  nsAttrValue attrValue(serializedValue);
-  SetParsedAttr(kNameSpaceID_None, nsGkAtoms::viewBox, nsnull,
-                attrValue, true);
+  DidChangeValue(nsGkAtoms::viewBox, aEmptyOrOldValue, newValue);
 }
 
 void
 nsSVGElement::DidAnimateViewBox()
 {
   nsIFrame* frame = GetPrimaryFrame();
   
   if (frame) {
@@ -2029,64 +2222,66 @@ nsSVGElement::DidAnimateViewBox()
 }
 
 SVGAnimatedPreserveAspectRatio *
 nsSVGElement::GetPreserveAspectRatio()
 {
   return nsnull;
 }
 
-void
-nsSVGElement::DidChangePreserveAspectRatio(bool aDoSetAttr)
+nsAttrValue
+nsSVGElement::WillChangePreserveAspectRatio()
 {
-  if (!aDoSetAttr)
-    return;
+  return WillChangeValue(nsGkAtoms::preserveAspectRatio);
+}
 
+void
+nsSVGElement::DidChangePreserveAspectRatio(const nsAttrValue& aEmptyOrOldValue)
+{
   SVGAnimatedPreserveAspectRatio *preserveAspectRatio =
     GetPreserveAspectRatio();
 
   NS_ASSERTION(preserveAspectRatio,
-               "DidChangePreserveAspectRatio on element with no preserveAspectRatio attrib");
+               "DidChangePreserveAspectRatio on element with no "
+               "preserveAspectRatio attrib");
 
-  nsAutoString serializedValue;
-  preserveAspectRatio->GetBaseValueString(serializedValue);
+  nsAttrValue newValue;
+  newValue.SetTo(*preserveAspectRatio, nsnull);
 
-  nsAttrValue attrValue(serializedValue);
-  SetParsedAttr(kNameSpaceID_None, nsGkAtoms::preserveAspectRatio, nsnull,
-                attrValue, true);
+  DidChangeValue(nsGkAtoms::preserveAspectRatio, aEmptyOrOldValue, newValue);
 }
 
 void
 nsSVGElement::DidAnimatePreserveAspectRatio()
 {
   nsIFrame* frame = GetPrimaryFrame();
   
   if (frame) {
     frame->AttributeChanged(kNameSpaceID_None,
                             nsGkAtoms::preserveAspectRatio,
                             nsIDOMMutationEvent::MODIFICATION);
   }
 }
 
-void
-nsSVGElement::DidChangeTransformList(bool aDoSetAttr)
+nsAttrValue
+nsSVGElement::WillChangeTransformList()
 {
-  if (!aDoSetAttr)
-    return;
+  return WillChangeValue(GetTransformListAttrName());
+}
 
-  SVGAnimatedTransformList* transformList = GetAnimatedTransformList();
-  NS_ABORT_IF_FALSE(transformList,
-                    "DidChangeTransformList on element with no transform list");
+void
+nsSVGElement::DidChangeTransformList(const nsAttrValue& aEmptyOrOldValue)
+{
+  NS_ABORT_IF_FALSE(GetTransformListAttrName(),
+                    "Changing non-existent transform list?");
 
-  nsAutoString serializedValue;
-  transformList->GetBaseValue().GetValueAsString(serializedValue);
+  nsAttrValue newValue;
+  newValue.SetTo(GetAnimatedTransformList()->GetBaseValue(), nsnull);
 
-  nsAttrValue attrValue(serializedValue);
-  SetParsedAttr(kNameSpaceID_None, GetTransformListAttrName(), nsnull,
-                attrValue, true);
+  DidChangeValue(GetTransformListAttrName(), aEmptyOrOldValue, newValue);
 }
 
 void
 nsSVGElement::DidAnimateTransformList()
 {
   NS_ABORT_IF_FALSE(GetTransformListAttrName(),
                     "Animating non-existent transform data?");
 
@@ -2150,39 +2345,59 @@ nsSVGElement::DidAnimateString(PRUint8 a
 }
 
 nsSVGElement::StringListAttributesInfo
 nsSVGElement::GetStringListInfo()
 {
   return StringListAttributesInfo(nsnull, nsnull, 0);
 }
 
+nsAttrValue
+nsSVGElement::WillChangeStringList(bool aIsConditionalProcessingAttribute,
+                                   PRUint8 aAttrEnum)
+{
+  nsIAtom* name;
+  if (aIsConditionalProcessingAttribute) {
+    nsCOMPtr<DOMSVGTests> tests(do_QueryInterface(this));
+    name = tests->GetAttrName(aAttrEnum);
+  } else {
+    name = *GetStringListInfo().mStringListInfo[aAttrEnum].mName;
+  }
+  return WillChangeValue(name);
+}
+
 void
 nsSVGElement::DidChangeStringList(bool aIsConditionalProcessingAttribute,
-                                  PRUint8 aAttrEnum)
+                                  PRUint8 aAttrEnum,
+                                  const nsAttrValue& aEmptyOrOldValue)
 {
+  nsIAtom* name;
+  nsAttrValue newValue;
+  nsCOMPtr<DOMSVGTests> tests;
+
   if (aIsConditionalProcessingAttribute) {
-    nsCOMPtr<DOMSVGTests> tests(do_QueryInterface(this));
-    tests->DidChangeStringList(aAttrEnum);
-    return;
+    tests = do_QueryInterface(this);
+    name = tests->GetAttrName(aAttrEnum);
+    tests->GetAttrValue(aAttrEnum, newValue);
+  } else {
+    StringListAttributesInfo info = GetStringListInfo();
+
+    NS_ASSERTION(info.mStringListCount > 0,
+                 "DidChangeStringList on element with no string list attribs");
+    NS_ASSERTION(aAttrEnum < info.mStringListCount, "aAttrEnum out of range");
+
+    name = *info.mStringListInfo[aAttrEnum].mName;
+    newValue.SetTo(info.mStringLists[aAttrEnum], nsnull);
   }
 
-  StringListAttributesInfo info = GetStringListInfo();
-
-  NS_ASSERTION(info.mStringListCount > 0,
-               "DidChangeStringList on element with no string list attribs");
-
-  NS_ASSERTION(aAttrEnum < info.mStringListCount, "aAttrEnum out of range");
+  DidChangeValue(name, aEmptyOrOldValue, newValue);
 
-  nsAutoString serializedValue;
-  info.mStringLists[aAttrEnum].GetValue(serializedValue, this);
-
-  nsAttrValue attrValue(serializedValue);
-  SetParsedAttr(kNameSpaceID_None, *info.mStringListInfo[aAttrEnum].mName,
-                nsnull, attrValue, true);
+  if (aIsConditionalProcessingAttribute) {
+    tests->MaybeInvalidate();
+  }
 }
 
 void
 nsSVGElement::StringListAttributesInfo::Reset(PRUint8 aAttrEnum)
 {
   mStringLists[aAttrEnum].Clear();
   // caller notifies
 }
--- a/content/svg/content/src/nsSVGElement.h
+++ b/content/svg/content/src/nsSVGElement.h
@@ -164,34 +164,54 @@ public:
 
   bool IsStringAnimatable(PRUint8 aAttrEnum) {
     return GetStringInfo().mStringInfo[aAttrEnum].mIsAnimatable;
   }
   bool NumberAttrAllowsPercentage(PRUint8 aAttrEnum) {
     return GetNumberInfo().mNumberInfo[aAttrEnum].mPercentagesAllowed;
   }
   void SetLength(nsIAtom* aName, const nsSVGLength2 &aLength);
-  virtual void DidChangeLength(PRUint8 aAttrEnum, bool aDoSetAttr);
-  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);
-  virtual void DidChangeEnum(PRUint8 aAttrEnum, bool aDoSetAttr);
-  virtual void DidChangeViewBox(bool aDoSetAttr);
-  virtual void DidChangePreserveAspectRatio(bool aDoSetAttr);
-  virtual void DidChangeNumberList(PRUint8 aAttrEnum, bool aDoSetAttr);
-  virtual void DidChangeLengthList(PRUint8 aAttrEnum, bool aDoSetAttr);
-  virtual void DidChangePointList(bool aDoSetAttr);
-  virtual void DidChangePathSegList(bool aDoSetAttr);
-  virtual void DidChangeTransformList(bool aDoSetAttr);
-  virtual void DidChangeString(PRUint8 aAttrEnum) {}
+
+  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);
+  nsAttrValue WillChangePointList();
+  nsAttrValue WillChangePathSegList();
+  nsAttrValue WillChangeTransformList();
+  nsAttrValue WillChangeStringList(bool aIsConditionalProcessingAttribute,
+                                   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);
+  void DidChangeNumberList(PRUint8 aAttrEnum,
+                           const nsAttrValue& aEmptyOrOldValue);
+  void DidChangeLengthList(PRUint8 aAttrEnum,
+                           const nsAttrValue& aEmptyOrOldValue);
+  void DidChangePointList(const nsAttrValue& aEmptyOrOldValue);
+  void DidChangePathSegList(const nsAttrValue& aEmptyOrOldValue);
+  void DidChangeTransformList(const nsAttrValue& aEmptyOrOldValue);
+  void DidChangeString(PRUint8 aAttrEnum) {}
   void DidChangeStringList(bool aIsConditionalProcessingAttribute,
-                           PRUint8 aAttrEnum);
+                           PRUint8 aAttrEnum,
+                           const nsAttrValue& aEmptyOrOldValue);
 
   virtual void DidAnimateLength(PRUint8 aAttrEnum);
   virtual void DidAnimateNumber(PRUint8 aAttrEnum);
   virtual void DidAnimateNumberPair(PRUint8 aAttrEnum);
   virtual void DidAnimateInteger(PRUint8 aAttrEnum);
   virtual void DidAnimateIntegerPair(PRUint8 aAttrEnum);
   virtual void DidAnimateAngle(PRUint8 aAttrEnum);
   virtual void DidAnimateBoolean(PRUint8 aAttrEnum);
@@ -244,31 +264,46 @@ public:
   virtual nsIAtom* GetPathDataAttrName() const {
     return nsnull;
   }
   virtual nsIAtom* GetTransformListAttrName() const {
     return nsnull;
   }
 
 protected:
+#ifdef DEBUG
+  // We define BeforeSetAttr here and mark it MOZ_FINAL to ensure it is NOT used
+  // by SVG elements.
+  // This is because we're not currently passing the correct value for aValue to
+  // BeforeSetAttr since it would involve allocating extra SVG value types.
+  // See the comment in nsSVGElement::WillChangeValue.
+  virtual nsresult BeforeSetAttr(PRInt32 aNamespaceID, nsIAtom* aName,
+                                 const nsAttrValueOrString* aValue,
+                                 bool aNotify) MOZ_FINAL { return NS_OK; }
+#endif // DEBUG
   virtual nsresult AfterSetAttr(PRInt32 aNamespaceID, nsIAtom* aName,
                                 const nsAttrValue* aValue, bool aNotify);
   virtual bool ParseAttribute(PRInt32 aNamespaceID, nsIAtom* aAttribute,
                                 const nsAString& aValue, nsAttrValue& aResult);
   static nsresult ReportAttributeParseFailure(nsIDocument* aDocument,
                                               nsIAtom* aAttribute,
                                               const nsAString& aValue);
 
   // Hooks for subclasses
   virtual bool IsEventName(nsIAtom* aName);
 
   void UpdateContentStyleRule();
   void UpdateAnimatedContentStyleRule();
   mozilla::css::StyleRule* GetAnimatedContentStyleRule();
 
+  nsAttrValue WillChangeValue(nsIAtom* aName);
+  void DidChangeValue(nsIAtom* aName, const nsAttrValue& aEmptyOrOldValue,
+                      nsAttrValue& aNewValue);
+  void MaybeSerializeAttrBeforeRemoval(nsIAtom* aName, bool aNotify);
+
   static nsIAtom* GetEventNameForAttr(nsIAtom* aAttr);
 
   struct LengthInfo {
     nsIAtom** mName;
     float     mDefaultValue;
     PRUint8   mDefaultUnitType;
     PRUint8   mCtxType;
   };
--- a/content/svg/content/src/nsSVGEnum.cpp
+++ b/content/svg/content/src/nsSVGEnum.cpp
@@ -62,25 +62,22 @@ nsSVGEnum::GetMapping(nsSVGElement *aSVG
 
   NS_ASSERTION(info.mEnumCount > 0 && mAttrEnum < info.mEnumCount,
                "mapping request for a non-attrib enum");
 
   return info.mEnumInfo[mAttrEnum].mMapping;
 }
 
 nsresult
-nsSVGEnum::SetBaseValueString(const nsAString& aValue,
-                              nsSVGElement *aSVGElement)
+nsSVGEnum::SetBaseValueAtom(const nsIAtom* aValue, nsSVGElement *aSVGElement)
 {
-  nsCOMPtr<nsIAtom> valAtom = do_GetAtom(aValue);
-
   nsSVGEnumMapping *mapping = GetMapping(aSVGElement);
 
   while (mapping && mapping->mKey) {
-    if (valAtom == *(mapping->mKey)) {
+    if (aValue == *(mapping->mKey)) {
       mIsBaseSet = true;
       if (mBaseVal != mapping->mVal) {
         mBaseVal = mapping->mVal;
         if (!mIsAnimated) {
           mAnimVal = mBaseVal;
         }
         else {
           aSVGElement->AnimationNeedsResample();
@@ -94,29 +91,29 @@ nsSVGEnum::SetBaseValueString(const nsAS
     mapping++;
   }
 
   // only a warning since authors may mistype attribute values
   NS_WARNING("unknown enumeration key");
   return NS_ERROR_DOM_SYNTAX_ERR;
 }
 
-void
-nsSVGEnum::GetBaseValueString(nsAString& aValue, nsSVGElement *aSVGElement)
+nsIAtom*
+nsSVGEnum::GetBaseValueAtom(nsSVGElement *aSVGElement)
 {
   nsSVGEnumMapping *mapping = GetMapping(aSVGElement);
 
   while (mapping && mapping->mKey) {
     if (mBaseVal == mapping->mVal) {
-      (*mapping->mKey)->ToString(aValue);
-      return;
+      return *mapping->mKey;
     }
     mapping++;
   }
   NS_ERROR("unknown enumeration value");
+  return nsGkAtoms::_empty;
 }
 
 nsresult
 nsSVGEnum::SetBaseValue(PRUint16 aValue,
                         nsSVGElement *aSVGElement)
 {
   nsSVGEnumMapping *mapping = GetMapping(aSVGElement);
 
@@ -126,17 +123,17 @@ nsSVGEnum::SetBaseValue(PRUint16 aValue,
       if (mBaseVal != PRUint8(aValue)) {
         mBaseVal = PRUint8(aValue);
         if (!mIsAnimated) {
           mAnimVal = mBaseVal;
         }
         else {
           aSVGElement->AnimationNeedsResample();
         }
-        aSVGElement->DidChangeEnum(mAttrEnum, true);
+        aSVGElement->DidChangeEnum(mAttrEnum);
       }
       return NS_OK;
     }
     mapping++;
   }
   return NS_ERROR_DOM_SYNTAX_ERR;
 }
 
--- a/content/svg/content/src/nsSVGEnum.h
+++ b/content/svg/content/src/nsSVGEnum.h
@@ -60,21 +60,18 @@ class nsSVGEnum
 public:
   void Init(PRUint8 aAttrEnum, PRUint16 aValue) {
     mAnimVal = mBaseVal = PRUint8(aValue);
     mAttrEnum = aAttrEnum;
     mIsAnimated = false;
     mIsBaseSet = false;
   }
 
-  nsresult SetBaseValueString(const nsAString& aValue,
-                              nsSVGElement *aSVGElement);
-  void GetBaseValueString(nsAString& aValue,
-                          nsSVGElement *aSVGElement);
-
+  nsresult SetBaseValueAtom(const nsIAtom* aValue, nsSVGElement *aSVGElement);
+  nsIAtom* GetBaseValueAtom(nsSVGElement *aSVGElement);
   nsresult SetBaseValue(PRUint16 aValue,
                         nsSVGElement *aSVGElement);
   PRUint16 GetBaseValue() const
     { return mBaseVal; }
 
   void SetAnimValue(PRUint16 aValue, nsSVGElement *aSVGElement);
   PRUint16 GetAnimValue() const
     { return mAnimVal; }
--- a/content/svg/content/src/nsSVGInteger.cpp
+++ b/content/svg/content/src/nsSVGInteger.cpp
@@ -101,28 +101,35 @@ nsSVGInteger::SetBaseValueString(const n
 void
 nsSVGInteger::GetBaseValueString(nsAString & aValueAsString)
 {
   aValueAsString.Truncate();
   aValueAsString.AppendInt(mBaseVal);
 }
 
 void
-nsSVGInteger::SetBaseValue(int aValue,
-                           nsSVGElement *aSVGElement)
+nsSVGInteger::SetBaseValue(int aValue, nsSVGElement *aSVGElement)
 {
+  // We can't just rely on SetParsedAttrValue (as called by DidChangeInteger)
+  // detecting redundant changes since it will compare false if the existing
+  // attribute value has an associated serialized version (a string value) even
+  // if the integers match due to the way integers are stored in nsAttrValue.
+  if (aValue == mBaseVal && mIsBaseSet) {
+    return;
+  }
+
   mBaseVal = aValue;
   mIsBaseSet = true;
   if (!mIsAnimated) {
     mAnimVal = mBaseVal;
   }
   else {
     aSVGElement->AnimationNeedsResample();
   }
-  aSVGElement->DidChangeInteger(mAttrEnum, true);
+  aSVGElement->DidChangeInteger(mAttrEnum);
 }
 
 void
 nsSVGInteger::SetAnimValue(int aValue, nsSVGElement *aSVGElement)
 {
   mAnimVal = aValue;
   mIsAnimated = true;
   aSVGElement->DidAnimateInteger(mAttrEnum);
--- a/content/svg/content/src/nsSVGIntegerPair.cpp
+++ b/content/svg/content/src/nsSVGIntegerPair.cpp
@@ -123,57 +123,67 @@ nsSVGIntegerPair::SetBaseValueString(con
 
   // We don't need to call DidChange* here - we're only called by
   // nsSVGElement::ParseAttribute under nsGenericElement::SetAttr,
   // which takes care of notifying.
   return NS_OK;
 }
 
 void
-nsSVGIntegerPair::GetBaseValueString(nsAString &aValueAsString)
+nsSVGIntegerPair::GetBaseValueString(nsAString &aValueAsString) const
 {
   aValueAsString.Truncate();
   aValueAsString.AppendInt(mBaseVal[0]);
   if (mBaseVal[0] != mBaseVal[1]) {
     aValueAsString.AppendLiteral(", ");
     aValueAsString.AppendInt(mBaseVal[1]);
   }
 }
 
 void
 nsSVGIntegerPair::SetBaseValue(PRInt32 aValue, PairIndex aPairIndex,
                                nsSVGElement *aSVGElement)
 {
   PRUint32 index = (aPairIndex == eFirst ? 0 : 1);
+  if (mIsBaseSet && mBaseVal[index] == aValue) {
+    return;
+  }
+
+  nsAttrValue emptyOrOldValue = aSVGElement->WillChangeIntegerPair(mAttrEnum);
   mBaseVal[index] = aValue;
   mIsBaseSet = true;
   if (!mIsAnimated) {
     mAnimVal[index] = aValue;
   }
   else {
     aSVGElement->AnimationNeedsResample();
   }
-  aSVGElement->DidChangeIntegerPair(mAttrEnum, true);
+  aSVGElement->DidChangeIntegerPair(mAttrEnum, emptyOrOldValue);
 }
 
 void
 nsSVGIntegerPair::SetBaseValues(PRInt32 aValue1, PRInt32 aValue2,
                                 nsSVGElement *aSVGElement)
 {
+  if (mIsBaseSet && mBaseVal[0] == aValue1 && mBaseVal[1] == aValue2) {
+    return;
+  }
+
+  nsAttrValue emptyOrOldValue = aSVGElement->WillChangeIntegerPair(mAttrEnum);
   mBaseVal[0] = aValue1;
   mBaseVal[1] = aValue2;
   mIsBaseSet = true;
   if (!mIsAnimated) {
     mAnimVal[0] = aValue1;
     mAnimVal[1] = aValue2;
   }
   else {
     aSVGElement->AnimationNeedsResample();
   }
-  aSVGElement->DidChangeIntegerPair(mAttrEnum, true);
+  aSVGElement->DidChangeIntegerPair(mAttrEnum, emptyOrOldValue);
 }
 
 void
 nsSVGIntegerPair::SetAnimValue(const PRInt32 aValue[2], nsSVGElement *aSVGElement)
 {
   mAnimVal[0] = aValue[0];
   mAnimVal[1] = aValue[1];
   mIsAnimated = true;
--- a/content/svg/content/src/nsSVGIntegerPair.h
+++ b/content/svg/content/src/nsSVGIntegerPair.h
@@ -61,17 +61,17 @@ public:
     mAnimVal[1] = mBaseVal[1] = aValue2;
     mAttrEnum = aAttrEnum;
     mIsAnimated = false;
     mIsBaseSet = false;
   }
 
   nsresult SetBaseValueString(const nsAString& aValue,
                               nsSVGElement *aSVGElement);
-  void GetBaseValueString(nsAString& aValue);
+  void GetBaseValueString(nsAString& aValue) const;
 
   void SetBaseValue(PRInt32 aValue, PairIndex aIndex, nsSVGElement *aSVGElement);
   void SetBaseValues(PRInt32 aValue1, PRInt32 aValue2, nsSVGElement *aSVGElement);
   PRInt32 GetBaseValue(PairIndex aIndex) const
     { return mBaseVal[aIndex == eFirst ? 0 : 1]; }
   void SetAnimValue(const PRInt32 aValue[2], nsSVGElement *aSVGElement);
   PRInt32 GetAnimValue(PairIndex aIndex) const
     { return mAnimVal[aIndex == eFirst ? 0 : 1]; }
--- a/content/svg/content/src/nsSVGLength2.cpp
+++ b/content/svg/content/src/nsSVGLength2.cpp
@@ -302,64 +302,94 @@ nsSVGLength2::GetUnitScaleFactor(nsIFram
   default:
     NS_NOTREACHED("Unknown unit type");
     return 0;
   }
 }
 
 void
 nsSVGLength2::SetBaseValueInSpecifiedUnits(float aValue,
-                                           nsSVGElement *aSVGElement)
+                                           nsSVGElement *aSVGElement,
+                                           bool aDoSetAttr)
 {
+  if (mIsBaseSet && mBaseVal == aValue) {
+    return;
+  }
+
+  nsAttrValue emptyOrOldValue;
+  if (aDoSetAttr) {
+    emptyOrOldValue = aSVGElement->WillChangeLength(mAttrEnum);
+  }
   mBaseVal = aValue;
   mIsBaseSet = true;
   if (!mIsAnimated) {
     mAnimVal = mBaseVal;
   }
   else {
     aSVGElement->AnimationNeedsResample();
   }
-  aSVGElement->DidChangeLength(mAttrEnum, true);
+  if (aDoSetAttr) {
+    aSVGElement->DidChangeLength(mAttrEnum, emptyOrOldValue);
+  }
 }
 
 nsresult
 nsSVGLength2::ConvertToSpecifiedUnits(PRUint16 unitType,
                                       nsSVGElement *aSVGElement)
 {
   if (!IsValidUnitType(unitType))
     return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
 
-  float valueInUserUnits = 
+  if (mIsBaseSet && mSpecifiedUnitType == PRUint8(unitType))
+    return NS_OK;
+
+  // Even though we're not changing the visual effect this length will have
+  // on the document, we still need to send out notifications in case we have
+  // mutation listeners, since the actual string value of the attribute will
+  // change.
+  nsAttrValue emptyOrOldValue = aSVGElement->WillChangeLength(mAttrEnum);
+
+  float valueInUserUnits =
     mBaseVal / GetUnitScaleFactor(aSVGElement, mSpecifiedUnitType);
   mSpecifiedUnitType = PRUint8(unitType);
-  SetBaseValue(valueInUserUnits, aSVGElement);
+  // Setting aDoSetAttr to false here will ensure we don't call
+  // Will/DidChangeAngle a second time (and dispatch duplicate notifications).
+  SetBaseValue(valueInUserUnits, aSVGElement, false);
+
+  aSVGElement->DidChangeLength(mAttrEnum, emptyOrOldValue);
 
   return NS_OK;
 }
 
 nsresult
 nsSVGLength2::NewValueSpecifiedUnits(PRUint16 unitType,
                                      float valueInSpecifiedUnits,
                                      nsSVGElement *aSVGElement)
 {
   NS_ENSURE_FINITE(valueInSpecifiedUnits, NS_ERROR_ILLEGAL_VALUE);
 
   if (!IsValidUnitType(unitType))
     return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
 
+  if (mIsBaseSet && mBaseVal == valueInSpecifiedUnits &&
+      mSpecifiedUnitType == PRUint8(unitType)) {
+    return NS_OK;
+  }
+
+  nsAttrValue emptyOrOldValue = aSVGElement->WillChangeLength(mAttrEnum);
   mBaseVal = valueInSpecifiedUnits;
   mIsBaseSet = true;
   mSpecifiedUnitType = PRUint8(unitType);
   if (!mIsAnimated) {
     mAnimVal = mBaseVal;
   }
   else {
     aSVGElement->AnimationNeedsResample();
   }
-  aSVGElement->DidChangeLength(mAttrEnum, true);
+  aSVGElement->DidChangeLength(mAttrEnum, emptyOrOldValue);
   return NS_OK;
 }
 
 nsresult
 nsSVGLength2::ToDOMBaseVal(nsIDOMSVGLength **aResult, nsSVGElement *aSVGElement)
 {
   *aResult = sBaseSVGLengthTearoffTable.GetTearoff(this);
   if (!*aResult) {
@@ -402,54 +432,66 @@ nsSVGLength2::DOMAnimVal::~DOMAnimVal()
 
 nsresult
 nsSVGLength2::SetBaseValueString(const nsAString &aValueAsString,
                                  nsSVGElement *aSVGElement,
                                  bool aDoSetAttr)
 {
   float value;
   PRUint16 unitType;
-  
+
   nsresult rv = GetValueFromString(aValueAsString, &value, &unitType);
   if (NS_FAILED(rv)) {
     return rv;
   }
-  
+
+  if (mIsBaseSet && mBaseVal == value &&
+      mSpecifiedUnitType == PRUint8(unitType)) {
+    return NS_OK;
+  }
+
+  nsAttrValue emptyOrOldValue;
+  if (aDoSetAttr) {
+    emptyOrOldValue = aSVGElement->WillChangeLength(mAttrEnum);
+  }
   mBaseVal = value;
   mIsBaseSet = true;
   mSpecifiedUnitType = PRUint8(unitType);
   if (!mIsAnimated) {
     mAnimVal = mBaseVal;
   }
   else {
     aSVGElement->AnimationNeedsResample();
   }
 
-  aSVGElement->DidChangeLength(mAttrEnum, aDoSetAttr);
+  if (aDoSetAttr) {
+    aSVGElement->DidChangeLength(mAttrEnum, emptyOrOldValue);
+  }
   return NS_OK;
 }
 
 void
-nsSVGLength2::GetBaseValueString(nsAString & aValueAsString)
+nsSVGLength2::GetBaseValueString(nsAString & aValueAsString) const
 {
   GetValueString(aValueAsString, mBaseVal, mSpecifiedUnitType);
 }
 
 void
-nsSVGLength2::GetAnimValueString(nsAString & aValueAsString)
+nsSVGLength2::GetAnimValueString(nsAString & aValueAsString) const
 {
   GetValueString(aValueAsString, mAnimVal, mSpecifiedUnitType);
 }
 
 void
-nsSVGLength2::SetBaseValue(float aValue, nsSVGElement *aSVGElement)
+nsSVGLength2::SetBaseValue(float aValue, nsSVGElement *aSVGElement,
+                           bool aDoSetAttr)
 {
   SetBaseValueInSpecifiedUnits(aValue * GetUnitScaleFactor(aSVGElement,
                                                            mSpecifiedUnitType),
-                               aSVGElement);
+                               aSVGElement, aDoSetAttr);
 }
 
 void
 nsSVGLength2::SetAnimValueInSpecifiedUnits(float aValue,
                                            nsSVGElement* aSVGElement)
 {
   mAnimVal = aValue;
   mIsAnimated = true;
--- a/content/svg/content/src/nsSVGLength2.h
+++ b/content/svg/content/src/nsSVGLength2.h
@@ -77,18 +77,18 @@ public:
     mIsAnimated = aLength.mIsAnimated;
     mIsBaseSet = aLength.mIsBaseSet;
     return *this;
   }
 
   nsresult SetBaseValueString(const nsAString& aValue,
                               nsSVGElement *aSVGElement,
                               bool aDoSetAttr);
-  void GetBaseValueString(nsAString& aValue);
-  void GetAnimValueString(nsAString& aValue);
+  void GetBaseValueString(nsAString& aValue) const;
+  void GetAnimValueString(nsAString& aValue) const;
 
   float GetBaseValue(nsSVGElement* aSVGElement) const
     { return mBaseVal / GetUnitScaleFactor(aSVGElement, mSpecifiedUnitType); }
   float GetAnimValue(nsSVGElement* aSVGElement) const
     { return mAnimVal / GetUnitScaleFactor(aSVGElement, mSpecifiedUnitType); }
   float GetAnimValue(nsIFrame* aFrame) const
     { return mAnimVal / GetUnitScaleFactor(aFrame, mSpecifiedUnitType); }
 
@@ -143,18 +143,19 @@ private:
   static float GetEmLength(nsSVGElement *aSVGElement)
     { return nsSVGUtils::GetFontSize(aSVGElement); }
   static float GetExLength(nsSVGElement *aSVGElement)
     { return nsSVGUtils::GetFontXHeight(aSVGElement); }
   float GetUnitScaleFactor(nsSVGElement *aSVGElement, PRUint8 aUnitType) const;
   float GetUnitScaleFactor(nsSVGSVGElement *aCtx, PRUint8 aUnitType) const;
 
   // SetBaseValue and SetAnimValue set the value in user units
-  void SetBaseValue(float aValue, nsSVGElement *aSVGElement);
-  void SetBaseValueInSpecifiedUnits(float aValue, nsSVGElement *aSVGElement);
+  void SetBaseValue(float aValue, nsSVGElement *aSVGElement, bool aDoSetAttr);
+  void SetBaseValueInSpecifiedUnits(float aValue, nsSVGElement *aSVGElement,
+                                    bool aDoSetAttr);
   void SetAnimValue(float aValue, nsSVGElement *aSVGElement);
   void SetAnimValueInSpecifiedUnits(float aValue, nsSVGElement *aSVGElement);
   nsresult NewValueSpecifiedUnits(PRUint16 aUnitType, float aValue,
                                   nsSVGElement *aSVGElement);
   nsresult ConvertToSpecifiedUnits(PRUint16 aUnitType, nsSVGElement *aSVGElement);
   nsresult ToDOMBaseVal(nsIDOMSVGLength **aResult, nsSVGElement* aSVGElement);
   nsresult ToDOMAnimVal(nsIDOMSVGLength **aResult, nsSVGElement* aSVGElement);
 
@@ -175,28 +176,28 @@ private:
 
     NS_IMETHOD GetValue(float* aResult)
       { *aResult = mVal->GetBaseValue(mSVGElement); return NS_OK; }
     NS_IMETHOD SetValue(float aValue)
       {
         if (!NS_finite(aValue)) {
           return NS_ERROR_ILLEGAL_VALUE;
         }
-        mVal->SetBaseValue(aValue, mSVGElement);
+        mVal->SetBaseValue(aValue, mSVGElement, true);
         return NS_OK;
       }
 
     NS_IMETHOD GetValueInSpecifiedUnits(float* aResult)
       { *aResult = mVal->mBaseVal; return NS_OK; }
     NS_IMETHOD SetValueInSpecifiedUnits(float aValue)
       {
         if (!NS_finite(aValue)) {
           return NS_ERROR_ILLEGAL_VALUE;
         }
-        mVal->SetBaseValueInSpecifiedUnits(aValue, mSVGElement);
+        mVal->SetBaseValueInSpecifiedUnits(aValue, mSVGElement, true);
         return NS_OK;
       }
 
     NS_IMETHOD SetValueAsString(const nsAString& aValue)
       { return mVal->SetBaseValueString(aValue, mSVGElement, true); }
     NS_IMETHOD GetValueAsString(nsAString& aValue)
       { mVal->GetBaseValueString(aValue); return NS_OK; }
 
--- a/content/svg/content/src/nsSVGMarkerElement.cpp
+++ b/content/svg/content/src/nsSVGMarkerElement.cpp
@@ -219,17 +219,17 @@ NS_IMETHODIMP nsSVGMarkerElement::SetOri
 {
   if (!angle)
     return NS_ERROR_DOM_SVG_WRONG_TYPE_ERR;
 
   float f;
   nsresult rv = angle->GetValue(&f);
   NS_ENSURE_SUCCESS(rv, rv);
   NS_ENSURE_FINITE(f, NS_ERROR_DOM_SVG_WRONG_TYPE_ERR);
-  mAngleAttributes[ORIENT].SetBaseValue(f, this);
+  mAngleAttributes[ORIENT].SetBaseValue(f, this, true);
 
   return NS_OK;
 }
 
 //----------------------------------------------------------------------
 // nsIContent methods
 
 NS_IMETHODIMP_(bool)
--- a/content/svg/content/src/nsSVGNumber2.cpp
+++ b/content/svg/content/src/nsSVGNumber2.cpp
@@ -144,28 +144,31 @@ nsSVGNumber2::SetBaseValueString(const n
 void
 nsSVGNumber2::GetBaseValueString(nsAString & aValueAsString)
 {
   aValueAsString.Truncate();
   aValueAsString.AppendFloat(mBaseVal);
 }
 
 void
-nsSVGNumber2::SetBaseValue(float aValue,
-                           nsSVGElement *aSVGElement)
+nsSVGNumber2::SetBaseValue(float aValue, nsSVGElement *aSVGElement)
 {
+  if (mIsBaseSet && aValue == mBaseVal) {
+    return;
+  }
+
   mBaseVal = aValue;
   mIsBaseSet = true;
   if (!mIsAnimated) {
     mAnimVal = mBaseVal;
   }
   else {
     aSVGElement->AnimationNeedsResample();
   }
-  aSVGElement->DidChangeNumber(mAttrEnum, true);
+  aSVGElement->DidChangeNumber(mAttrEnum);
 }
 
 void
 nsSVGNumber2::SetAnimValue(float aValue, nsSVGElement *aSVGElement)
 {
   mAnimVal = aValue;
   mIsAnimated = true;
   aSVGElement->DidAnimateNumber(mAttrEnum);
--- a/content/svg/content/src/nsSVGNumberPair.cpp
+++ b/content/svg/content/src/nsSVGNumberPair.cpp
@@ -116,64 +116,72 @@ nsSVGNumberPair::SetBaseValueString(cons
   if (!mIsAnimated) {
     mAnimVal[0] = mBaseVal[0];
     mAnimVal[1] = mBaseVal[1];
   }
   else {
     aSVGElement->AnimationNeedsResample();
   }
 
-  // We don't need to call DidChange* here - we're only called by
+  // We don't need to call Will/DidChange* here - we're only called by
   // nsSVGElement::ParseAttribute under nsGenericElement::SetAttr,
   // which takes care of notifying.
   return NS_OK;
 }
 
 void
-nsSVGNumberPair::GetBaseValueString(nsAString &aValueAsString)
+nsSVGNumberPair::GetBaseValueString(nsAString &aValueAsString) const
 {
   aValueAsString.Truncate();
   aValueAsString.AppendFloat(mBaseVal[0]);
   if (mBaseVal[0] != mBaseVal[1]) {
     aValueAsString.AppendLiteral(", ");
     aValueAsString.AppendFloat(mBaseVal[1]);
   }
 }
 
 void
 nsSVGNumberPair::SetBaseValue(float aValue, PairIndex aPairIndex,
                               nsSVGElement *aSVGElement)
 {
   PRUint32 index = (aPairIndex == eFirst ? 0 : 1);
+  if (mIsBaseSet && mBaseVal[index] == aValue) {
+    return;
+  }
+  nsAttrValue emptyOrOldValue = aSVGElement->WillChangeNumberPair(mAttrEnum);
   mBaseVal[index] = aValue;
   mIsBaseSet = true;
   if (!mIsAnimated) {
     mAnimVal[index] = aValue;
   }
   else {
     aSVGElement->AnimationNeedsResample();
   }
-  aSVGElement->DidChangeNumberPair(mAttrEnum, true);
+  aSVGElement->DidChangeNumberPair(mAttrEnum, emptyOrOldValue);
 }
 
 void
 nsSVGNumberPair::SetBaseValues(float aValue1, float aValue2,
                                nsSVGElement *aSVGElement)
 {
+  if (mIsBaseSet && mBaseVal[0] == aValue1 && mBaseVal[1] == aValue2) {
+    return;
+  }
+  nsAttrValue emptyOrOldValue = aSVGElement->WillChangeNumberPair(mAttrEnum);
   mBaseVal[0] = aValue1;
   mBaseVal[1] = aValue2;
   mIsBaseSet = true;
   if (!mIsAnimated) {
     mAnimVal[0] = aValue1;
     mAnimVal[1] = aValue2;
   }
   else {
     aSVGElement->AnimationNeedsResample();
   }
-  aSVGElement->DidChangeNumberPair(mAttrEnum, true);
+  aSVGElement->DidChangeNumberPair(mAttrEnum, emptyOrOldValue);
 }
 
 void
 nsSVGNumberPair::SetAnimValue(const float aValue[2], nsSVGElement *aSVGElement)
 {
   mAnimVal[0] = aValue[0];
   mAnimVal[1] = aValue[1];
   mIsAnimated = true;
--- a/content/svg/content/src/nsSVGNumberPair.h
+++ b/content/svg/content/src/nsSVGNumberPair.h
@@ -62,17 +62,17 @@ public:
     mAnimVal[1] = mBaseVal[1] = aValue2;
     mAttrEnum = aAttrEnum;
     mIsAnimated = false;
     mIsBaseSet = false;
   }
 
   nsresult SetBaseValueString(const nsAString& aValue,
                               nsSVGElement *aSVGElement);
-  void GetBaseValueString(nsAString& aValue);
+  void GetBaseValueString(nsAString& aValue) const;
 
   void SetBaseValue(float aValue, PairIndex aIndex, nsSVGElement *aSVGElement);
   void SetBaseValues(float aValue1, float aValue2, nsSVGElement *aSVGElement);
   float GetBaseValue(PairIndex aIndex) const
     { return mBaseVal[aIndex == eFirst ? 0 : 1]; }
   void SetAnimValue(const float aValue[2], nsSVGElement *aSVGElement);
   float GetAnimValue(PairIndex aIndex) const
     { return mAnimVal[aIndex == eFirst ? 0 : 1]; }
--- a/content/svg/content/src/nsSVGViewBox.cpp
+++ b/content/svg/content/src/nsSVGViewBox.cpp
@@ -122,20 +122,26 @@ nsSVGViewBox::SetAnimValue(float aX, flo
   }
   aSVGElement->DidAnimateViewBox();
 }
 
 void
 nsSVGViewBox::SetBaseValue(float aX, float aY, float aWidth, float aHeight,
                            nsSVGElement *aSVGElement)
 {
+  if (mHasBaseVal && mBaseVal == nsSVGViewBoxRect(aX, aY, aWidth, aHeight)) {
+    return;
+  }
+
+  nsAttrValue emptyOrOldValue = aSVGElement->WillChangeViewBox();
+
   mBaseVal = nsSVGViewBoxRect(aX, aY, aWidth, aHeight);
   mHasBaseVal = true;
 
-  aSVGElement->DidChangeViewBox(true);
+  aSVGElement->DidChangeViewBox(emptyOrOldValue);
   if (mAnimVal) {
     aSVGElement->AnimationNeedsResample();
   }
 }
 
 static nsresult
 ToSVGViewBoxRect(const nsAString& aStr, nsSVGViewBoxRect *aViewBox)
 {
@@ -180,17 +186,17 @@ nsSVGViewBox::SetBaseValueString(const n
   nsresult res = ToSVGViewBoxRect(aValue, &viewBox);
   if (NS_SUCCEEDED(res)) {
     mBaseVal = nsSVGViewBoxRect(viewBox.x, viewBox.y, viewBox.width, viewBox.height);
     mHasBaseVal = true;
 
     if (mAnimVal) {
       aSVGElement->AnimationNeedsResample();
     }
-    // We don't need to call DidChange* here - we're only called by
+    // We don't need to call Will/DidChange* here - we're only called by
     // nsSVGElement::ParseAttribute under nsGenericElement::SetAttr,
     // which takes care of notifying.
   }
   return res;
 }
 
 void
 nsSVGViewBox::GetBaseValueString(nsAString& aValue) const
--- a/content/svg/content/test/Makefile.in
+++ b/content/svg/content/test/Makefile.in
@@ -45,31 +45,33 @@ include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 # Disabled:
 #		bbox-helper.svg \
 #		test_length.xhtml \
 
 _TEST_FILES = \
 		matrixUtils.js \
+		MutationEventChecker.js \
 		test_a_href_01.xhtml \
 		test_a_href_02.xhtml \
 		a_href_destination.svg \
 		a_href_helper_01.svg \
 		a_href_helper_02_03.svg \
 		a_href_helper_04.svg \
 		test_animLengthObjectIdentity.xhtml \
 		test_animLengthReadonly.xhtml \
 		test_animLengthUnits.xhtml \
 		test_bbox.xhtml \
 		test_bbox-with-invalid-viewBox.xhtml \
 		test_bounds.html \
 		bbox-helper.svg \
 		bounds-helper.svg \
 		test_dataTypes.html \
+		test_dataTypesModEvents.html \
 		dataTypes-helper.svg \
 		getCTM-helper.svg \
 		test_getCTM.html \
 		test_getElementById.xhtml \
 		test_getSubStringLength.xhtml \
 		getSubStringLength-helper.svg \
 		test_isSupported.xhtml \
 		test_nonAnimStrings.xhtml \
@@ -83,17 +85,19 @@ 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_SVGPointList.xhtml \
 		test_SVGStyleElement.xhtml \
 		test_SVGStringList.xhtml \
 		test_SVGTransformList.xhtml \
 		test_SVGTransformListAddition.xhtml \
 		test_SVGxxxList.xhtml \
 		test_SVGxxxListIndexing.xhtml \
 		test_switch.xhtml \
 		switch-helper.svg \
new file mode 100644
--- /dev/null
+++ b/content/svg/content/test/MutationEventChecker.js
@@ -0,0 +1,245 @@
+// Helper class to check DOM MutationEvents
+//
+// Usage:
+//
+// * Create a new event checker:
+//     var eventChecker = new MutationEventChecker;
+// * Set the attribute to watch
+//     eventChecker.watchAttr(<DOM element>, "<attribute name>");
+// * Set the events to expect (0..n)
+//     eventChecker.expect("add", "modify");
+//     OR
+//     eventChecker.expect("add modify");
+//     OR
+//     eventChecker.expect(MutationEvent.ADDITION, MutationEvent.MODIFICATION);
+//
+//  An empty string or empty set of arguments is also fine as a way of checking
+//  that all expected events have been received and indicating no events are
+//  expected from the following code, e.g.
+//
+//    eventChecker.expect("");
+//    // changes that are not expected to generate events
+//    eventChecker.expect("modify");
+//    // change that is expected to generate an event
+//    ...
+//
+// * Either finish listening or set the next attribute to watch
+//     eventChecker.finish();
+//     eventChecker.watchAttr(element, "nextAttribute");
+//
+//   In either case a check is performed that all expected events have been
+//   received.
+//
+// * Event checking can be temporarily disabled with ignoreEvents(). The next
+//   call to expect() will cause it to resume.
+
+function MutationEventChecker()
+{
+  this.expectedEvents = [];
+
+  this.watchAttr = function(element, attr)
+  {
+    if (this.attr) {
+      this.finish();
+    }
+
+    this.expectedEvents = [];
+    this.element        = element;
+    this.attr           = attr;
+    this.oldValue       = element.getAttribute(attr);
+    this.giveUp         = false;
+    this.ignore         = false;
+
+    this.element.addEventListener('DOMAttrModified', this._listener, false);
+  }
+
+  this.expect = function()
+  {
+    if (this.giveUp) {
+      return;
+    }
+
+    ok(this.expectedEvents.length == 0,
+       "Expecting new events for " + this.attr +
+       " but the following previously expected events have still not been " +
+       "received: " + this._stillExpecting());
+    if (this.expectedEvents.length != 0) {
+      this.giveUp = true;
+      return;
+    }
+
+    this.ignore = false;
+
+    if (arguments.length == 0 ||
+        arguments.length == 1 && arguments[0] == "") {
+      return;
+    }
+
+    // Turn arguments object into an array
+    var args = Array.prototype.slice.call(arguments);
+    // Check for whitespace separated keywords
+    if (args.length == 1 && typeof args[0] === 'string' &&
+        args[0].indexOf(' ') > 0) {
+      args = args[0].split(' ');
+    }
+    // Convert strings to event Ids
+    this.expectedEvents = args.map(this._argToEventId);
+  }
+
+  // Temporarily disable event checking
+  this.ignoreEvents = function()
+  {
+    // Check all events have been received
+    ok(this.giveUp || this.expectedEvents.length == 0,
+      "Going to ignore subsequent events on " + this.attr +
+      " attribute, but we're still expecting the following events: " +
+      this._stillExpecting());
+
+    this.ignore = true;
+  }
+
+  this.finish = function()
+  {
+    // Check all events have been received
+    ok(this.giveUp || this.expectedEvents.length == 0,
+      "Finishing listening to " + this.attr +
+      " attribute, but we're still expecting the following events: " +
+      this._stillExpecting());
+
+    this.element.removeEventListener('DOMAttrModified', this._listener, false);
+    this.attr = "";
+  }
+
+  this._receiveEvent = function(e)
+  {
+    if (this.giveUp || this.ignore) {
+      this.oldValue = e.newValue;
+      return;
+    }
+
+    // Make sure we're expecting something at all
+    if (this.expectedEvents.length == 0) {
+      ok(false, 'Unexpected ' + this._eventToName(e.attrChange) +
+         ' event when none expected on ' + this.attr + ' attribute.');
+      return;
+    }
+
+    var expectedEvent = this.expectedEvents.shift();
+
+    // Make sure we got the event we expected
+    if (e.attrChange != expectedEvent) {
+      ok(false, 'Unexpected ' + this._eventToName(e.attrChange) +
+        ' on ' + this.attr + ' attribute. Expected ' +
+        this._eventToName(expectedEvent) + ' (followed by: ' +
+        this._stillExpecting() + ")");
+      // If we get events out of sequence, it doesn't make sense to do any
+      // further testing since we don't really know what to expect
+      this.giveUp = true;
+      return;
+    }
+
+    // Common param checking
+    is(e.target, this.element,
+       'Unexpected node for mutation event on ' + this.attr + ' attribute');
+    is(e.attrName, this.attr, 'Unexpected attribute name for mutation event');
+
+    // Don't bother testing e.relatedNode since Attr nodes are on the way
+    // out anyway (but then, so are mutation events...)
+
+    // Event-specific checking
+    if (e.attrChange == MutationEvent.MODIFICATION) {
+      ok(this.element.hasAttribute(this.attr),
+         'Attribute not set after modification');
+      is(e.prevValue, this.oldValue,
+         'Unexpected old value for modification to ' + this.attr +
+         ' attribute');
+      isnot(e.newValue, this.oldValue,
+         'Unexpected new value for modification to ' + this.attr +
+         ' attribute');
+    } else if (e.attrChange == MutationEvent.REMOVAL) {
+      ok(!this.element.hasAttribute(this.attr), 'Attribute set after removal');
+      is(e.prevValue, this.oldValue,
+         'Unexpected old value for removal of ' + this.attr +
+         ' attribute');
+      // DOM 3 Events doesn't say what value newValue will be for a removal
+      // event but generally empty strings are used for other events when an
+      // attribute isn't relevant
+      ok(e.newValue === "",
+         'Unexpected new value for removal of ' + this.attr +
+         ' attribute');
+    } else if (e.attrChange == MutationEvent.ADDITION) {
+      ok(this.element.hasAttribute(this.attr),
+         'Attribute not set after addition');
+      // DOM 3 Events doesn't say what value prevValue will be for an addition
+      // event but generally empty strings are used for other events when an
+      // attribute isn't relevant
+      ok(e.prevValue === "",
+         'Unexpected old value for addition of ' + this.attr +
+         ' attribute');
+      ok(typeof(e.newValue) == 'string' && e.newValue !== "",
+         'Unexpected new value for addition of ' + this.attr +
+         ' attribute');
+    } else {
+      ok(false, 'Unexpected mutation event type: ' + e.attrChange);
+      this.giveUp = true;
+    }
+    this.oldValue = e.newValue;
+  }
+  this._listener = this._receiveEvent.bind(this);
+
+  this._stillExpecting = function()
+  {
+    if (this.expectedEvents.length == 0) {
+      return "(nothing)";
+    }
+    var eventNames = [];
+    for (var i=0; i < this.expectedEvents.length; i++) {
+      eventNames.push(this._eventToName(this.expectedEvents[i]));
+    }
+    return eventNames.join(", ");
+  }
+
+  this._eventToName = function(evtId)
+  {
+    switch (evtId)
+    {
+    case MutationEvent.MODIFICATION:
+      return "modification";
+    case MutationEvent.ADDITION:
+      return "addition";
+    case MutationEvent.REMOVAL:
+      return "removal";
+    }
+  }
+
+  this._argToEventId = function(arg)
+  {
+    if (typeof arg === 'number')
+      return arg;
+
+    if (typeof arg !== 'string') {
+      ok(false, "Unexpected event type: " + arg);
+      return 0;
+    }
+
+    switch (arg.toLowerCase())
+    {
+    case "mod":
+    case "modify":
+    case "modification":
+      return MutationEvent.MODIFICATION;
+
+    case "add":
+    case "addition":
+      return MutationEvent.ADDITION;
+
+    case "removal":
+    case "remove":
+      return MutationEvent.REMOVAL;
+
+    default:
+      ok(false, "Unexpected event name: " + arg);
+      return 0;
+    }
+  }
+}
--- 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>
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>
--- a/content/svg/content/test/test_SVGPathSegList.xhtml
+++ b/content/svg/content/test/test_SVGPathSegList.xhtml
@@ -1,15 +1,16 @@
 <html xmlns="http://www.w3.org/1999/xhtml">
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=611138
 -->
 <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="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=611138">Mozilla Bug 611138</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">
   <path id="path"/>
@@ -81,27 +82,35 @@ function run_tests()
   ok(list.numberOfItems == 3 && list.getItem(1) == seg,
     'insertItemBefore should be able insert items into an invalid path');
 
   seg = path.createSVGPathSegLinetoAbs(1, 2);
   list.initialize(seg);
   ok(list.numberOfItems == 1 && list.getItem(0) == seg,
     'initialize should be able initialize an invalid path with a non-moveto item');
 
+  // Test mutation events
+
+  eventChecker = new MutationEventChecker;
   d = 'M0,0 L12,34'
   path.setAttribute('d', d);
-  function check_old_value(e) {
-    is(e.target, path, 'check mutation event is for expected node');
-    is(e.attrName, 'd', 'check mutation event is for expected attribute');
-    is(e.prevValue, d, 'check old attribute value is correctly reported');
-    isnot(e.newValue, d, 'check attribute value has changed');
-  }
-  path.addEventListener('DOMAttrModified', check_old_value, false);
-  list.getItem(1).y = 35;
-  path.removeEventListener('DOMAttrModified', check_old_value, false);
+  eventChecker.watchAttr(path, "d");
+
+  // -- Actual changes
+  eventChecker.expect("modify modify modify");
+  list[0].x = 10;
+  list[0].y = 5;
+  path.setAttribute("d", "M20,5 L12,34");
+
+  // -- Redundant changes
+  eventChecker.expect("");
+  list[0].x = 20;
+  list[1].y = 34;
+  path.setAttribute("d", "M20,5 L12,34");
+  eventChecker.finish();
 
   SimpleTest.finish();
 }
 
 window.addEventListener("load", run_tests, false);
 
 ]]>
 </script>
new file mode 100644
--- /dev/null
+++ b/content/svg/content/test/test_SVGPointList.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 SVGPointList</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">
+  <polyline id="polyline" points="50,375 150,380"/>
+</svg>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+<![CDATA[
+
+SimpleTest.waitForExplicitFinish();
+
+/*
+This file runs a series of SVGPointList 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 polyline = document.getElementById("polyline");
+  var points = polyline.points;
+
+  is(points.numberOfItems, 2, 'Checking numberOfItems');
+
+  // Test mutation events
+  // --- Initialization
+  eventChecker = new MutationEventChecker;
+  eventChecker.watchAttr(polyline, "points");
+  // -- Actual changes
+  eventChecker.expect("modify modify");
+  points[0].x = 40;
+  polyline.setAttribute("points", "30,375 150,380");
+  // -- Redundant changes
+  eventChecker.expect("");
+  points[0].x = 30;
+  points[1].y = 380;
+  polyline.setAttribute("points", "30,375 150,380");
+  eventChecker.finish();
+
+  SimpleTest.finish();
+}
+
+window.addEventListener("load", run_tests, false);
+
+]]>
+</script>
+</pre>
+</body>
+</html>
--- a/content/svg/content/test/test_SVGTransformList.xhtml
+++ b/content/svg/content/test/test_SVGTransformList.xhtml
@@ -2,16 +2,17 @@
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=602759
 -->
 <head>
   <title>Tests specific to SVGTransformList</title>
   <script type="text/javascript" src="/MochiKit/packed.js"></script>
   <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=602759">
   Mozilla Bug 602759</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"
@@ -36,17 +37,18 @@ function main()
   var g = $('g');
   var tests =
     [ testConsolidateMatrix,
       testConsolidateMatrixOneElem,
       testConsolidateMatrixZeroElem,
       testCreateSVGTransformFromMatrix,
       testReadOnly,
       testOrphan,
-      testFailedSet
+      testFailedSet,
+      testMutationEvents
     ];
   for (var i = 0; i < tests.length; i++) {
     tests[i](g);
   }
   SimpleTest.finish();
 }
 
 function testConsolidateMatrix(g)
@@ -350,15 +352,85 @@ function testFailedSet(g)
   // Attempt to set bad value
   g.setAttribute("transform", "translate(40 50) scale(a)");
   is(list.numberOfItems, 0,
      "Transform list should be empty after setting bad value");
   is(g.transform.animVal.numberOfItems, 0,
      "Animated transform list should also be empty after setting bad value");
 }
 
+function testMutationEvents(g)
+{
+  // Check mutation events
+
+  // Set initial value
+  g.setAttribute("transform", "translate(50 90)");
+  var list = g.transform.baseVal;
+  is(list.numberOfItems, 1, "Unexpected initial length of list");
+  eventChecker = new MutationEventChecker;
+  eventChecker.watchAttr(g, "transform");
+
+  // consolidate
+  //
+  // Consolidate happens to generate two modification events in our
+  // implementation--it's not ideal but it's better than none
+  eventChecker.expect("modify modify modify");
+  g.setAttribute("transform", "translate(10 10) translate(10 10)");
+  list.consolidate();
+
+  // In the following, each of the operations is performed twice but only one
+  // mutation event is expected. This is to check that redundant mutation
+  // events are not sent.
+
+  // transform.setMatrix
+  eventChecker.expect("modify");
+  var mx = $('svg').createSVGMatrix();
+  list[0].setMatrix(mx);
+  list[0].setMatrix(mx);
+
+  // transform.setTranslate
+  eventChecker.expect("modify");
+  list[0].setTranslate(10, 10);
+  list[0].setTranslate(10, 10);
+
+  // transform.setScale
+  eventChecker.expect("modify");
+  list[0].setScale(2, 2);
+  list[0].setScale(2, 2);
+
+  // transform.setRotate
+  eventChecker.expect("modify");
+  list[0].setRotate(45, 1, 2);
+  list[0].setRotate(45, 1, 2);
+
+  // transform.setSkewX
+  eventChecker.expect("modify");
+  list[0].setSkewX(45);
+  list[0].setSkewX(45);
+
+  // transform.setSkewY
+  eventChecker.expect("modify");
+  list[0].setSkewY(25);
+  list[0].setSkewY(25);
+
+  // transform.matrix
+  eventChecker.expect("modify modify");
+  list[0].matrix.a = 1;
+  list[0].matrix.a = 1;
+  list[0].matrix.e = 5;
+  list[0].matrix.e = 5;
+
+  // setAttribute interaction
+  eventChecker.expect("modify");
+  list[0].setMatrix(mx);
+  eventChecker.expect("");
+  g.setAttribute("transform", "matrix(1, 0, 0, 1, 0, 0)");
+  list[0].setMatrix(mx);
+  eventChecker.finish();
+}
+
 window.addEventListener("load", main, false);
 
 ]]>
 </script>
 </pre>
 </body>
 </html>
--- 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.
--- a/content/svg/content/test/test_dataTypes.html
+++ b/content/svg/content/test/test_dataTypes.html
@@ -33,18 +33,21 @@ function runTests()
   is(filter.className.baseVal, "foo", "className baseVal");
   is(filter.className.animVal, "foo", "className animVal");
   filter.className.baseVal = "bar";
   is(filter.getAttribute("class"), "bar", "class attribute");
   is(filter.className.baseVal, "bar", "className baseVal");
   is(filter.className.animVal, "bar", "className animVal");
   filter.removeAttribute("class");
   is(filter.hasAttribute("class"), false, "class attribute");
+  ok(filter.getAttribute("class") === null, "removed class attribute");
   is(filter.className.baseVal, "", "className baseVal");
   is(filter.className.animVal, "", "className animVal");
+  filter.setAttribute("class", "");
+  ok(filter.getAttribute("class") === "", "empty class attribute");
 
   // length attribute
 
   marker.setAttribute("markerWidth", "12.5");
   is(marker.markerWidth.baseVal.value, 12.5, "length baseVal");
   is(marker.markerWidth.animVal.value, 12.5, "length animVal");
 
   var baseMarkerWidth = marker.markerWidth.baseVal;
@@ -52,26 +55,36 @@ function runTests()
   marker.setAttribute("markerWidth", "15.5");
   is(baseMarkerWidth.value, 15.5, "length baseVal");
   is(animMarkerWidth.value, 15.5, "length animVal");
 
   marker.markerWidth.baseVal.value = 7.5;
   is(marker.markerWidth.animVal.value, 7.5, "length animVal");
   is(marker.getAttribute("markerWidth"), "7.5", "length attribute");
 
+  marker.setAttribute("markerWidth", "");
+  ok(marker.getAttribute("markerWidth") === "", "empty length attribute");
+  marker.removeAttribute("markerWidth");
+  ok(marker.getAttribute("markerWidth") === null, "removed length attribute");
+
   // number attribute
 
   convolve.setAttribute("divisor", "12.5");
   is(convolve.divisor.baseVal, 12.5, "number baseVal");
   is(convolve.divisor.animVal, 12.5, "number animVal");
 
   convolve.divisor.baseVal = 7.5;
   is(convolve.divisor.animVal, 7.5, "number animVal");
   is(convolve.getAttribute("divisor"), "7.5", "number attribute");
 
+  convolve.setAttribute("divisor", "");
+  ok(convolve.getAttribute("divisor") === "", "empty number attribute");
+  convolve.removeAttribute("divisor");
+  ok(convolve.getAttribute("divisor") === null, "removed number attribute");
+
   // number-optional-number attribute
 
   blur.setAttribute("stdDeviation", "20.5");
   is(blur.stdDeviationX.baseVal, 20.5, "number-optional-number first baseVal");
   is(blur.stdDeviationX.animVal, 20.5, "number-optional-number first animVal");
   is(blur.stdDeviationY.baseVal, 20.5, "number-optional-number second baseVal");
   is(blur.stdDeviationY.animVal, 20.5, "number-optional-number second animVal");
 
@@ -84,24 +97,35 @@ function runTests()
   is(blur.getAttribute("stdDeviation"), "8.5", "number-optional-number attribute");
 
   blur.setStdDeviation(24.5, 0.5);
   is(blur.stdDeviationX.baseVal, 24.5, "integer-optional-integer first baseVal");
   is(blur.stdDeviationX.animVal, 24.5, "integer-optional-integer first animVal");
   is(blur.stdDeviationY.baseVal, 0.5, "integer-optional-integer second baseVal");
   is(blur.stdDeviationY.animVal, 0.5, "integer-optional-integer second animVal");
 
+  blur.setAttribute("stdDeviation", "");
+  ok(blur.getAttribute("stdDeviation") === "",
+     "empty number-optional-number attribute");
+  blur.removeAttribute("stdDeviation");
+  ok(blur.getAttribute("stdDeviation") === null,
+     "removed number-optional-number attribute");
+
   // integer attribute
 
   convolve.setAttribute("targetX", "12");
   is(convolve.targetX.baseVal, 12, "integer baseVal");
   is(convolve.targetX.animVal, 12, "integer animVal");
   convolve.targetX.baseVal = 7;
   is(convolve.targetX.animVal, 7, "integer animVal");
   is(convolve.getAttribute("targetX"), "7", "integer attribute");
+  convolve.setAttribute("targetX", "");
+  ok(convolve.getAttribute("targetX") === "", "empty integer attribute");
+  convolve.removeAttribute("targetX");
+  ok(convolve.getAttribute("targetX") === null, "removed integer attribute");
 
   // integer-optional-integer attribute
 
   filter.setAttribute("filterRes&