Bug 534028, Patch C: Support SMIL animation of SVG attributes that are mapped to CSS properties. r=roc
authorDaniel Holbert <dholbert@cs.stanford.edu>
Tue, 16 Mar 2010 16:17:33 -0700
changeset 39488 33632ecef69c
parent 39487 782e82ce2c29
child 39489 cf62ca96cd0d
push id12227
push userdholbert@mozilla.com
push dateTue, 16 Mar 2010 23:20:42 +0000
treeherdermozilla-central@9e96f8553258 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc
bugs534028
milestone1.9.3a4pre
Bug 534028, Patch C: Support SMIL animation of SVG attributes that are mapped to CSS properties. r=roc
content/base/src/nsPropertyTable.h
content/smil/Makefile.in
content/smil/nsSMILMappedAttribute.cpp
content/smil/nsSMILMappedAttribute.h
content/svg/content/src/nsSVGElement.cpp
content/svg/content/src/nsSVGElement.h
--- a/content/base/src/nsPropertyTable.h
+++ b/content/base/src/nsPropertyTable.h
@@ -93,16 +93,19 @@ public:
 private:
   const void* mObject;
 };
 
 // Categories of properties
 // 0 is global.
 #define DOM_USER_DATA         1
 #define DOM_USER_DATA_HANDLER 2
+#ifdef MOZ_SMIL
+#define SMIL_MAPPED_ATTR_ANIMVAL 3
+#endif // MOZ_SMIL
 
 class nsPropertyTable
 {
  public:
   /**
    * Get the value of the property |aPropertyName| for node |aObject|.
    * |aResult|, if supplied, is filled in with a return status code.
    **/
--- a/content/smil/Makefile.in
+++ b/content/smil/Makefile.in
@@ -57,16 +57,17 @@ CPPSRCS		+= \
 	nsSMILAnimationController.cpp \
 	nsSMILAnimationFunction.cpp \
 	nsSMILCompositor.cpp \
 	nsSMILCSSProperty.cpp \
 	nsSMILCSSValueType.cpp \
 	nsSMILFloatType.cpp \
 	nsSMILInstanceTime.cpp \
 	nsSMILInterval.cpp \
+	nsSMILMappedAttribute.cpp \
 	nsSMILNullType.cpp \
 	nsSMILParserUtils.cpp \
 	nsSMILRepeatCount.cpp \
 	nsSMILSetAnimationFunction.cpp \
 	nsSMILTimeContainer.cpp \
 	nsSMILTimedElement.cpp \
 	nsSMILTimeValue.cpp \
 	nsSMILTimeValueSpec.cpp \
@@ -87,17 +88,19 @@ ifdef ENABLE_TESTS
 TOOL_DIRS		+= test
 endif # ENABLE_TESTS
 
 EXPORTS		+= \
 	  nsISMILAnimationElement.h \
 	  nsISMILAttr.h \
 	  nsSMILAnimationController.h \
 	  nsSMILCompositorTable.h \
+	  nsSMILCSSProperty.h \
 	  nsSMILKeySpline.h \
+	  nsSMILMappedAttribute.h \
 	  nsSMILMilestone.h \
 	  nsSMILTimeContainer.h \
 	  nsSMILTypes.h \
 	  $(NULL)
 
 INCLUDES += 	\
 		-I$(srcdir)/../base/src \
 		-I$(srcdir)/../../layout/style \
new file mode 100644
--- /dev/null
+++ b/content/smil/nsSMILMappedAttribute.cpp
@@ -0,0 +1,156 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** 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 SMIL module.
+ *
+ * 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):
+ *   Daniel Holbert <dholbert@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of 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 ***** */
+
+/* representation of a SMIL-animatable mapped attribute on an element */
+#include "nsSMILMappedAttribute.h"
+#include "nsPropertyTable.h"
+#include "nsContentErrors.h" // For NS_PROPTABLE_PROP_OVERWRITTEN
+#include "nsSMILValue.h"
+#include "nsSMILCSSValueType.h"
+#include "nsIContent.h"
+#include "nsIDocument.h"
+#include "nsIPresShell.h"
+#include "nsCSSProps.h"
+
+// Callback function, for freeing string buffers stored in property table
+static void
+ReleaseStringBufferPropertyValue(void*    aObject,       /* unused */
+                                 nsIAtom* aPropertyName, /* unused */
+                                 void*    aPropertyValue,
+                                 void*    aData          /* unused */)
+{
+  nsStringBuffer* buf = static_cast<nsStringBuffer*>(aPropertyValue);
+  buf->Release();
+}
+
+nsSMILValue
+nsSMILMappedAttribute::GetBaseValue() const
+{
+  nsAutoString baseStringValue;
+  nsRefPtr<nsIAtom> attrName = GetAttrNameAtom();
+  PRBool success = mElement->GetAttr(kNameSpaceID_None, attrName,
+                                     baseStringValue);
+  nsSMILValue baseValue;
+  if (success) {
+    nsSMILCSSValueType::ValueFromString(mPropID, mElement,
+                                        baseStringValue, baseValue);
+  } else {
+    // Attribute is unset -- use computed value.
+    // FIRST: Temporarily clear animated value, to make sure it doesn't pollute
+    // the computed value. (We want base value, _without_ animations applied.)
+    void* buf = mElement->UnsetProperty(SMIL_MAPPED_ATTR_ANIMVAL,
+                                        attrName, nsnull);
+    FlushChangesToTargetAttr();
+
+    // SECOND: we use nsSMILCSSProperty::GetBaseValue to look up the property's
+    // computed value.  NOTE: This call will temporarily clear the SMIL
+    // override-style for the corresponding CSS property on our target element.
+    // This prevents any animations that target the CSS property from affecting
+    // animations that target the mapped attribute.
+    baseValue = nsSMILCSSProperty::GetBaseValue();
+
+    // FINALLY: If we originally had an animated value set, then set it again.
+    if (buf) {
+      mElement->SetProperty(SMIL_MAPPED_ATTR_ANIMVAL, attrName, buf,
+                            ReleaseStringBufferPropertyValue);
+      FlushChangesToTargetAttr();
+    }
+  }
+  return baseValue;
+}
+
+nsresult
+nsSMILMappedAttribute::SetAnimValue(const nsSMILValue& aValue)
+{
+  NS_ENSURE_TRUE(IsPropertyAnimatable(mPropID), NS_ERROR_FAILURE);
+
+  // Convert nsSMILValue to string
+  nsAutoString valStr;
+  if (!nsSMILCSSValueType::ValueToString(aValue, valStr)) {
+    NS_WARNING("Failed to convert nsSMILValue for mapped attr into a string");
+    return NS_ERROR_FAILURE;
+  }
+
+  // Set the string as this mapped attribute's animated value.
+  nsStringBuffer* valStrBuf = nsCSSValue::BufferFromString(nsString(valStr));
+  nsRefPtr<nsIAtom> attrName = GetAttrNameAtom();
+  nsresult rv = mElement->SetProperty(SMIL_MAPPED_ATTR_ANIMVAL,
+                                      attrName, valStrBuf,
+                                      ReleaseStringBufferPropertyValue);
+  if (rv == NS_PROPTABLE_PROP_OVERWRITTEN) {
+    rv = NS_OK;
+  }
+  FlushChangesToTargetAttr();
+
+  return rv;
+}
+
+void
+nsSMILMappedAttribute::ClearAnimValue()
+{
+  nsRefPtr<nsIAtom> attrName = GetAttrNameAtom();
+  nsresult rv = mElement->DeleteProperty(SMIL_MAPPED_ATTR_ANIMVAL, attrName);
+  if (NS_FAILED(rv)) {
+    // XXXdholbert Can this ever happen? Leaving this warning for now, to
+    // see if we ever trigger this.
+    NS_WARNING("couldn't clear animated value (perhaps it wasn't set?)");
+  }
+  FlushChangesToTargetAttr();
+}
+
+void
+nsSMILMappedAttribute::FlushChangesToTargetAttr() const
+{
+  // Clear animated content-style-rule
+  mElement->DeleteProperty(SMIL_MAPPED_ATTR_ANIMVAL,
+                           SMIL_MAPPED_ATTR_STYLERULE_ATOM);
+  nsIDocument* doc = mElement->GetCurrentDoc();
+
+  // Request animation restyle
+  if (doc) {
+    nsIPresShell* shell = doc->GetPrimaryShell();
+    if (shell) {
+      shell->RestyleForAnimation(mElement);
+    }
+  }
+}
+
+already_AddRefed<nsIAtom>
+nsSMILMappedAttribute::GetAttrNameAtom() const
+{
+  return do_GetAtom(nsCSSProps::GetStringValue(mPropID));
+}
new file mode 100644
--- /dev/null
+++ b/content/smil/nsSMILMappedAttribute.h
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** 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 SMIL module.
+ *
+ * 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):
+ *   Daniel Holbert <dholbert@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of 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 ***** */
+
+/* representation of a SMIL-animatable mapped attribute on an element */
+
+#ifndef NS_SMILMAPPEDATTRIBUTE_H_
+#define NS_SMILMAPPEDATTRIBUTE_H_
+
+#include "nsSMILCSSProperty.h"
+
+/* We'll use the empty-string atom |nsGkAtoms::_empty| as the key for storing
+ * an element's animated content style rule in its Property Table, under the
+ * property-category SMIL_MAPPED_ATTR_ANIMVAL.  Everything else stored in that
+ * category is keyed off of the XML attribute name, so the empty string is a
+ * good "reserved" key to use for storing the style rule (since XML attributes
+ * all have nonempty names).
+ */
+#define SMIL_MAPPED_ATTR_STYLERULE_ATOM nsGkAtoms::_empty
+
+/**
+ * nsSMILMappedAttribute: Implements the nsISMILAttr interface for SMIL
+ * animations whose targets are attributes that map to CSS properties.  An
+ * instance of this class represents a particular animation-targeted mapped
+ * attribute on a particular element.
+ */
+class nsSMILMappedAttribute : public nsSMILCSSProperty {
+public:
+  /**
+   * Constructs a new nsSMILMappedAttribute.
+   *
+   * @param  aPropID   The CSS property for the mapped attribute we're
+   *                   interested in animating.
+   * @param  aElement  The element whose attribute is being animated.
+   */
+  nsSMILMappedAttribute(nsCSSProperty aPropID, nsIContent* aElement) :
+    nsSMILCSSProperty(aPropID, aElement) {}
+
+  // nsISMILAttr methods
+  virtual nsSMILValue GetBaseValue() const;
+  virtual nsresult    SetAnimValue(const nsSMILValue& aValue);
+  virtual void        ClearAnimValue();
+
+protected:
+  // Helper Methods
+  void FlushChangesToTargetAttr() const;
+  already_AddRefed<nsIAtom> GetAttrNameAtom() const;
+};
+#endif // NS_SMILMAPPEDATTRIBUTE_H_
--- a/content/svg/content/src/nsSVGElement.cpp
+++ b/content/svg/content/src/nsSVGElement.cpp
@@ -86,16 +86,17 @@
 #include "nsIDOMSVGTransformList.h"
 #include "nsIDOMSVGAnimTransformList.h"
 #include "nsIDOMSVGAnimatedRect.h"
 #include "nsSVGRect.h"
 #include "nsIFrame.h"
 #include "prdtoa.h"
 #include <stdarg.h>
 #ifdef MOZ_SMIL
+#include "nsSMILMappedAttribute.h"
 #include "nsSVGTransformSMILAttr.h"
 #include "nsSVGAnimatedTransformList.h"
 #include "nsIDOMSVGTransformable.h"
 #endif // MOZ_SMIL
 
 nsSVGEnumMapping nsSVGElement::sSVGUnitTypesMap[] = {
   {&nsGkAtoms::userSpaceOnUse, nsIDOMSVGUnitTypes::SVG_UNIT_TYPE_USERSPACEONUSE},
   {&nsGkAtoms::objectBoundingBox, nsIDOMSVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX},
@@ -733,16 +734,50 @@ nsSVGElement::WalkContentStyleRules(nsRu
   if (!mContentStyleRule)
     UpdateContentStyleRule();
 
   if (mContentStyleRule) {
     mContentStyleRule->RuleMatched();
     aRuleWalker->Forward(mContentStyleRule);
   }
 
+#ifdef MOZ_SMIL
+  // Update & walk the animated content style rule, to include style from
+  // animated mapped attributes.  But first, get nsPresContext to check
+  // whether this is a "no-animation restyle". (This should match the check
+  // in nsHTMLCSSStyleSheet::RulesMatching(), where we determine whether to
+  // apply the SMILOverrideStyle.)
+  nsIDocument* doc = GetOwnerDoc();
+  NS_ASSERTION(doc, "SVG element without doc");
+  if (doc) {
+    nsIPresShell* shell = doc->GetPrimaryShell();
+    nsPresContext* context = shell ? shell->GetPresContext() : nsnull;
+    if (context && context->IsProcessingRestyles() &&
+        !context->IsProcessingAnimationStyleChange()) {
+      // Any style changes right now could trigger CSS Transitions. We don't
+      // want that to happen from SMIL-animated value of mapped attrs, so
+      // ignore animated value for now, and request an animation restyle to
+      // get our animated value noticed.
+      context->PresShell()->RestyleForAnimation(this);
+    } else {
+      // Ok, this is an animation restyle -- go ahead and update/walk the
+      // animated content style rule.
+      nsICSSStyleRule* animContentStyleRule = GetAnimatedContentStyleRule();
+      if (!animContentStyleRule) {
+        UpdateAnimatedContentStyleRule();
+        animContentStyleRule = GetAnimatedContentStyleRule();
+      }
+      if (animContentStyleRule) {
+        animContentStyleRule->RuleMatched();
+        aRuleWalker->Forward(animContentStyleRule);
+      }
+    }
+  }
+#endif // MOZ_SMIL
+
   return NS_OK;
 }
 
 // PresentationAttributes-FillStroke
 /* static */ const nsGenericElement::MappedAttributeEntry
 nsSVGElement::sFillStrokeMap[] = {
   { &nsGkAtoms::fill },
   { &nsGkAtoms::fill_opacity },
@@ -1152,16 +1187,94 @@ nsSVGElement::UpdateContentStyleRule()
 
     nsAutoString value;
     mAttrsAndChildren.AttrAt(i)->ToString(value);
     mappedAttrParser.ParseMappedAttrValue(attrName->Atom(), value);
   }
   mContentStyleRule = mappedAttrParser.CreateStyleRule();
 }
 
+#ifdef MOZ_SMIL
+static void
+ParseMappedAttrAnimValueCallback(void*    aObject,
+                                 nsIAtom* aPropertyName,
+                                 void*    aPropertyValue,
+                                 void*    aData)
+{
+  NS_ABORT_IF_FALSE(aPropertyName != SMIL_MAPPED_ATTR_STYLERULE_ATOM,
+                    "animated content style rule should have been removed "
+                    "from properties table already (we're rebuilding it now)");
+
+  MappedAttrParser* mappedAttrParser =
+    static_cast<MappedAttrParser*>(aData);
+
+  nsStringBuffer* valueBuf = static_cast<nsStringBuffer*>(aPropertyValue);
+  nsAutoString value;
+  PRUint32 len = NS_strlen(static_cast<PRUnichar*>(valueBuf->Data()));
+  valueBuf->ToString(len, value);
+
+  mappedAttrParser->ParseMappedAttrValue(aPropertyName, value);
+}
+
+// Callback for freeing animated content style rule, in property table.
+static void
+ReleaseStyleRule(void*    aObject,       /* unused */
+                 nsIAtom* aPropertyName,
+                 void*    aPropertyValue,
+                 void*    aData          /* unused */)
+{
+  NS_ABORT_IF_FALSE(aPropertyName == SMIL_MAPPED_ATTR_STYLERULE_ATOM,
+                    "unexpected property name, for "
+                    "animated content style rule");
+  nsICSSStyleRule* styleRule = static_cast<nsICSSStyleRule*>(aPropertyValue);
+  NS_ABORT_IF_FALSE(styleRule, "unexpected null style rule");
+  styleRule->Release();
+}
+
+void
+nsSVGElement::UpdateAnimatedContentStyleRule()
+{
+  NS_ABORT_IF_FALSE(!GetAnimatedContentStyleRule(),
+                    "Animated content style rule already set");
+
+  nsIDocument* doc = GetOwnerDoc();
+  if (!doc) {
+    NS_ERROR("SVG element without owner document");
+    return;
+  }
+
+  MappedAttrParser mappedAttrParser(doc->CSSLoader(), doc->GetDocumentURI(),
+                                    GetBaseURI(), NodePrincipal());
+  doc->PropertyTable()->Enumerate(this, SMIL_MAPPED_ATTR_ANIMVAL,
+                                  ParseMappedAttrAnimValueCallback,
+                                  &mappedAttrParser);
+ 
+  nsRefPtr<nsICSSStyleRule>
+    animContentStyleRule(mappedAttrParser.CreateStyleRule());
+
+  if (animContentStyleRule) {
+    nsresult rv = SetProperty(SMIL_MAPPED_ATTR_ANIMVAL,
+                              SMIL_MAPPED_ATTR_STYLERULE_ATOM,
+                              animContentStyleRule.get(), ReleaseStyleRule);
+    animContentStyleRule.forget();
+    NS_ABORT_IF_FALSE(rv == NS_OK,
+                      "SetProperty failed (or overwrote something)");
+  }
+}
+
+nsICSSStyleRule*
+nsSVGElement::GetAnimatedContentStyleRule()
+{
+  return
+    static_cast<nsICSSStyleRule*>(GetProperty(SMIL_MAPPED_ATTR_ANIMVAL,
+                                              SMIL_MAPPED_ATTR_STYLERULE_ATOM,
+                                              nsnull));
+}
+#endif // MOZ_SMIL
+
 nsISVGValue*
 nsSVGElement::GetMappedAttribute(PRInt32 aNamespaceID, nsIAtom* aName)
 {
   const nsAttrValue* attrVal = mMappedAttributes.GetAttr(aName, aNamespaceID);
   if (!attrVal)
     return nsnull;
 
   return attrVal->GetSVGValue();
@@ -1908,16 +2021,27 @@ nsSVGElement::GetAnimatedAttr(nsIAtom* a
   }
 
   // preserveAspectRatio:
   if (aName == nsGkAtoms::preserveAspectRatio) {
     nsSVGPreserveAspectRatio *preserveAspectRatio = GetPreserveAspectRatio();
     return preserveAspectRatio ? preserveAspectRatio->ToSMILAttr(this) : nsnull;
   }
 
+  // Mapped attributes:
+  if (IsAttributeMapped(aName)) {
+    nsCSSProperty prop = nsCSSProps::LookupProperty(nsAtomString(aName));
+    // Check IsPropertyAnimatable to avoid attributes that...
+    //  - map to explicitly unanimatable properties (e.g. 'direction')
+    //  - map to unsupported attributes (e.g. 'glyph-orientation-horizontal')
+    if (nsSMILCSSProperty::IsPropertyAnimatable(prop)) {
+      return new nsSMILMappedAttribute(prop, this);
+    }
+  }
+
   return nsnull;
 }
 
 void
 nsSVGElement::AnimationNeedsResample()
 {
   nsIDocument* doc = GetCurrentDoc();
   if (doc && doc->HasAnimationController()) {
--- a/content/svg/content/src/nsSVGElement.h
+++ b/content/svg/content/src/nsSVGElement.h
@@ -188,16 +188,21 @@ protected:
   static nsresult ReportAttributeParseFailure(nsIDocument* aDocument,
                                               nsIAtom* aAttribute,
                                               const nsAString& aValue);
 
   // Hooks for subclasses
   virtual PRBool IsEventName(nsIAtom* aName);
 
   void UpdateContentStyleRule();
+#ifdef MOZ_SMIL
+  void UpdateAnimatedContentStyleRule();
+  nsICSSStyleRule* GetAnimatedContentStyleRule();
+#endif // MOZ_SMIL
+
   nsISVGValue* GetMappedAttribute(PRInt32 aNamespaceID, nsIAtom* aName);
   nsresult AddMappedSVGValue(nsIAtom* aName, nsISupports* aValue,
                              PRInt32 aNamespaceID = kNameSpaceID_None);
   
   static nsIAtom* GetEventNameForAttr(nsIAtom* aAttr);
 
   struct LengthInfo {
     nsIAtom** mName;