smil_mappedAttrs
author Daniel Holbert <dholbert@cs.stanford.edu>
Thu, 28 Jan 2010 03:39:47 -0800
changeset 1074 50bc0a4c6fe9c61f8924d04fb9ba895308c1922e
parent 1053 f58f8f297134789dca657282c75aed6589198534
child 1082 6f25b404235e86bb141aaff6b4a08c5cbe5aa26c
permissions -rw-r--r--
smil_mappedAttrs: Add missing "FlushChangesToTargetAttr" call in nsSMILMappedAttribute::GetBaseValue, after we've re-inserted the animated value.

From: Daniel Holbert <dholbert@cs.stanford.edu>
Bug 534028, Patch C: Support SMIL animation of SVG attributes that are mapped to CSS properties. r=roc

diff --git a/content/base/src/nsPropertyTable.h b/content/base/src/nsPropertyTable.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.
    **/
diff --git a/content/smil/Makefile.in b/content/smil/Makefile.in
--- a/content/smil/Makefile.in
+++ b/content/smil/Makefile.in
@@ -56,16 +56,17 @@ ifdef MOZ_SMIL
 CPPSRCS		+= \
 	nsSMILAnimationController.cpp \
 	nsSMILAnimationFunction.cpp \
 	nsSMILCompositor.cpp \
 	nsSMILCSSProperty.cpp \
 	nsSMILCSSValueType.cpp \
 	nsSMILFloatType.cpp \
 	nsSMILInstanceTime.cpp \
+	nsSMILMappedAttribute.cpp \
 	nsSMILNullType.cpp \
 	nsSMILParserUtils.cpp \
 	nsSMILRepeatCount.cpp \
 	nsSMILSetAnimationFunction.cpp \
 	nsSMILTimeContainer.cpp \
 	nsSMILTimedElement.cpp \
 	nsSMILTimeValue.cpp \
 	nsSMILTimeValueSpec.cpp \
@@ -85,17 +86,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 \
diff --git a/content/smil/nsSMILMappedAttribute.cpp b/content/smil/nsSMILMappedAttribute.cpp
new file mode 100644
--- /dev/null
+++ b/content/smil/nsSMILMappedAttribute.cpp
@@ -0,0 +1,162 @@
+/* -*- 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) {
+    // Attribute is set -- use nsSMILCSSValueType::ValueFromString
+    // to convert it into a nsSMILValue.
+    nsSMILCSSValueType::sSingleton.Init(baseValue);
+    if (!nsSMILCSSValueType::sSingleton.ValueFromString(mPropID, mElement,
+                                                        baseStringValue,
+                                                        baseValue)) {
+      nsSMILCSSValueType::sSingleton.Destroy(baseValue);
+      NS_ABORT_IF_FALSE(baseValue.IsNull(),
+                        "Destroy should leave us with null-typed value");
+    }
+  } 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);
+
+  nsresult rv;
+  nsAutoString valStr;
+  if (nsSMILCSSValueType::sSingleton.ValueToString(aValue, valStr)) {
+    nsStringBuffer* valStrBuf = nsCSSValue::BufferFromString(nsString(valStr));
+    nsRefPtr<nsIAtom> attrName = GetAttrNameAtom();
+    rv = mElement->SetProperty(SMIL_MAPPED_ATTR_ANIMVAL, attrName, valStrBuf,
+                               ReleaseStringBufferPropertyValue);
+    if (rv == NS_PROPTABLE_PROP_OVERWRITTEN) {
+      rv = NS_OK;
+    }
+  } else {
+    NS_WARNING("Failed to convert nsSMILValue for mapped attr into a string");
+    rv = NS_ERROR_FAILURE;
+  }
+  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));
+}
diff --git a/content/smil/nsSMILMappedAttribute.h b/content/smil/nsSMILMappedAttribute.h
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_
diff --git a/content/svg/content/src/nsSVGElement.cpp b/content/svg/content/src/nsSVGElement.cpp
--- a/content/svg/content/src/nsSVGElement.cpp
+++ b/content/svg/content/src/nsSVGElement.cpp
@@ -87,16 +87,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},
@@ -726,16 +727,42 @@ 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, check whether this is an animation
+  // restyle. (In a non-animation restyle, any style changes can potentially
+  // trigger CSS transitions.  We don't want SMIL's changes to trigger those.)
+  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->IsProcessingAnimationStyleChange()) {
+      // 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 },
@@ -1151,16 +1178,97 @@ nsSVGElement::UpdateContentStyleRule()
     if (!mappedAttrParser.ParseMappedAttrValue(attrName->Atom(), value)) {
       // Error initializing declaration
       return;
     }
   }
   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);
+
+  // NOTE: This next call might fail (i.e. if we're in low-memory conditions),
+  // and ideally we'd stop enumeration in that case.  It's not very important
+  // that we handle that, though -- the current behavior is to just make a few
+  // more calls to ParseMappedAttrValue, for our other animated mapped
+  // attributes, and those calls will just fail as well.
+  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, 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();
@@ -1818,16 +1926,29 @@ nsSVGElement::GetAnimatedAttr(nsIAtom* a
     BooleanAttributesInfo info = GetBooleanInfo();
     for (PRUint32 i = 0; i < info.mBooleanCount; i++) {
       if (aName == *info.mBooleanInfo[i].mName) {
         return info.mBooleans[i].ToSMILAttr(this);
       }
     }
   }
 
+  // Mapped attributes:
+  if (IsAttributeMapped(aName)) {
+    nsAutoString mappedAttrName;
+    aName->ToString(mappedAttrName);
+    nsCSSProperty prop = nsCSSProps::LookupProperty(mappedAttrName);
+    // 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) {
diff --git a/content/svg/content/src/nsSVGElement.h b/content/svg/content/src/nsSVGElement.h
--- a/content/svg/content/src/nsSVGElement.h
+++ b/content/svg/content/src/nsSVGElement.h
@@ -183,16 +183,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;