Implement parsing and storage of @keyframes rule. (Bug 435442, patch 5) r=bzbarsky
authorL. David Baron <dbaron@dbaron.org>
Mon, 11 Apr 2011 23:18:43 -0700
changeset 67979 f8dba37f47618e7d652aa1837faa408cc7878b45
parent 67978 2597d6ff2793ec4c9335a9953d382b7913bfff99
child 67980 23d79d8f5eda84afe7caf167bd48ac8e030245fe
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbzbarsky
bugs435442
milestone2.2a1pre
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
Implement parsing and storage of @keyframes rule. (Bug 435442, patch 5) r=bzbarsky
dom/base/nsDOMClassInfo.cpp
dom/base/nsDOMClassInfoClasses.h
dom/interfaces/base/domstubs.idl
dom/interfaces/css/Makefile.in
dom/interfaces/css/nsIDOMCSSRule.idl
dom/interfaces/css/nsIDOMMozCSSKeyframeRule.idl
dom/interfaces/css/nsIDOMMozCSSKeyframesRule.idl
dom/locales/en-US/chrome/layout/css.properties
layout/style/nsCSSParser.cpp
layout/style/nsCSSParser.h
layout/style/nsCSSRules.cpp
layout/style/nsCSSRules.h
layout/style/nsICSSRule.h
layout/style/test/Makefile.in
layout/style/test/test_keyframes_rules.html
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -322,16 +322,18 @@
 #include "nsIDOMProgressEvent.h"
 #include "nsIDOMNSUIEvent.h"
 #include "nsIDOMCSS2Properties.h"
 #include "nsIDOMCSSCharsetRule.h"
 #include "nsIDOMCSSImportRule.h"
 #include "nsIDOMCSSMediaRule.h"
 #include "nsIDOMCSSFontFaceRule.h"
 #include "nsIDOMCSSMozDocumentRule.h"
+#include "nsIDOMMozCSSKeyframeRule.h"
+#include "nsIDOMMozCSSKeyframesRule.h"
 #include "nsIDOMCSSPrimitiveValue.h"
 #include "nsIDOMCSSStyleRule.h"
 #include "nsIDOMCSSStyleSheet.h"
 #include "nsDOMCSSValueList.h"
 #include "nsIDOMOrientationEvent.h"
 #include "nsIDOMRange.h"
 #include "nsIDOMNSRange.h"
 #include "nsIDOMRangeException.h"
@@ -1477,16 +1479,21 @@ static nsDOMClassInfoData sClassInfoData
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(IDBVersionChangeRequest, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(IDBDatabaseException, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
   NS_DEFINE_CLASSINFO_DATA(EventException, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
+
+  NS_DEFINE_CLASSINFO_DATA(MozCSSKeyframeRule, nsDOMGenericSH,
+                           DOM_DEFAULT_SCRIPTABLE_FLAGS)
+  NS_DEFINE_CLASSINFO_DATA(MozCSSKeyframesRule, nsDOMGenericSH,
+                           DOM_DEFAULT_SCRIPTABLE_FLAGS)
 };
 
 // Objects that should be constructable through |new Name();|
 struct nsContractIDMapData
 {
   PRInt32 mDOMClassInfoID;
   const char *mContractID;
 };
@@ -4209,16 +4216,24 @@ nsDOMClassInfo::Init()
     DOM_CLASSINFO_MAP_ENTRY(nsIException)
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(EventException, nsIDOMEventException)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMEventException)
     DOM_CLASSINFO_MAP_ENTRY(nsIException)
   DOM_CLASSINFO_MAP_END
 
+  DOM_CLASSINFO_MAP_BEGIN(MozCSSKeyframeRule, nsIDOMMozCSSKeyframeRule)
+    DOM_CLASSINFO_MAP_ENTRY(nsIDOMMozCSSKeyframeRule)
+  DOM_CLASSINFO_MAP_END
+
+  DOM_CLASSINFO_MAP_BEGIN(MozCSSKeyframesRule, nsIDOMMozCSSKeyframesRule)
+    DOM_CLASSINFO_MAP_ENTRY(nsIDOMMozCSSKeyframesRule)
+  DOM_CLASSINFO_MAP_END
+
 #ifdef NS_DEBUG
   {
     PRUint32 i = NS_ARRAY_LENGTH(sClassInfoData);
 
     if (i != eDOMClassInfoIDCount) {
       NS_ERROR("The number of items in sClassInfoData doesn't match the "
                "number of nsIDOMClassInfo ID's, this is bad! Fix it!");
 
--- a/dom/base/nsDOMClassInfoClasses.h
+++ b/dom/base/nsDOMClassInfoClasses.h
@@ -502,8 +502,11 @@ DOMCI_CLASS(IDBCursor)
 DOMCI_CLASS(IDBCursorWithValue)
 DOMCI_CLASS(IDBKeyRange)
 DOMCI_CLASS(IDBIndex)
 DOMCI_CLASS(IDBVersionChangeEvent)
 DOMCI_CLASS(IDBVersionChangeRequest)
 DOMCI_CLASS(IDBDatabaseException)
 
 DOMCI_CLASS(EventException)
+
+DOMCI_CLASS(MozCSSKeyframeRule)
+DOMCI_CLASS(MozCSSKeyframesRule)
--- a/dom/interfaces/base/domstubs.idl
+++ b/dom/interfaces/base/domstubs.idl
@@ -107,16 +107,17 @@ interface nsIDOMHTMLCollection;
 interface nsIDOMHTMLHeadElement;
 
 // CSS
 interface nsIDOMCSSValue;
 interface nsIDOMCSSValueList;
 interface nsIDOMCSSPrimitiveValue;
 interface nsIDOMCSSRule;
 interface nsIDOMCSSRuleList;
+interface nsIDOMMozCSSKeyframeRule;
 interface nsIDOMCSSStyleSheet;
 interface nsIDOMCSSStyleDeclaration;
 interface nsIDOMCounter;
 interface nsIDOMRect;
 interface nsIDOMRGBColor;
 interface nsIDOMCSSStyleRule;
 interface nsIDOMCSSStyleRuleCollection;
 interface nsIDOMHTMLTableCaptionElement;
--- a/dom/interfaces/css/Makefile.in
+++ b/dom/interfaces/css/Makefile.in
@@ -59,16 +59,18 @@ SDK_XPIDLSRCS = 				\
 
 XPIDLSRCS =					\
 	nsIDOMCSS2Properties.idl		\
 	nsIDOMCSSCharsetRule.idl		\
 	nsIDOMCSSFontFaceRule.idl		\
 	nsIDOMCSSImportRule.idl			\
 	nsIDOMCSSMediaRule.idl			\
 	nsIDOMCSSMozDocumentRule.idl		\
+	nsIDOMMozCSSKeyframeRule.idl		\
+	nsIDOMMozCSSKeyframesRule.idl		\
 	nsIDOMCSSPageRule.idl			\
 	nsIDOMCSSStyleRule.idl			\
 	nsIDOMCSSUnknownRule.idl		\
 	nsIDOMCounter.idl			\
 	nsIDOMDocumentCSS.idl			\
 	nsIDOMRGBColor.idl			\
 	nsIDOMRect.idl				\
 	nsIDOMViewCSS.idl			\
--- a/dom/interfaces/css/nsIDOMCSSRule.idl
+++ b/dom/interfaces/css/nsIDOMCSSRule.idl
@@ -42,27 +42,29 @@
 /**
  * The nsIDOMCSSRule interface is a datatype for a CSS style rule in
  * the Document Object Model.
  *
  * For more information on this interface please see
  * http://www.w3.org/TR/DOM-Level-2-Style
  */
 
-[scriptable, uuid(a6cf90c1-15b3-11d2-932e-00805f8add32)]
+[scriptable, uuid(2938307a-9d70-4b63-8afc-0197e82318ad)]
 interface nsIDOMCSSRule : nsISupports
 {
   // RuleType
   const unsigned short      UNKNOWN_RULE                   = 0;
   const unsigned short      STYLE_RULE                     = 1;
   const unsigned short      CHARSET_RULE                   = 2;
   const unsigned short      IMPORT_RULE                    = 3;
   const unsigned short      MEDIA_RULE                     = 4;
   const unsigned short      FONT_FACE_RULE                 = 5;
   const unsigned short      PAGE_RULE                      = 6;
+  const unsigned short      MOZ_KEYFRAMES_RULE             = 7;
+  const unsigned short      MOZ_KEYFRAME_RULE              = 8;
 
   readonly attribute unsigned short      type;
            attribute DOMString           cssText;
                                         // raises(DOMException) on setting
 
   readonly attribute nsIDOMCSSStyleSheet parentStyleSheet;
   readonly attribute nsIDOMCSSRule       parentRule;
 };
new file mode 100644
--- /dev/null
+++ b/dom/interfaces/css/nsIDOMMozCSSKeyframeRule.idl
@@ -0,0 +1,45 @@
+/* -*- Mode: IDL; 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 code for css3-animations.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   L. David Baron <dbaron@dbaron.org>, Mozilla Corporation (original author)
+ *
+ * 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 "nsIDOMCSSRule.idl"
+
+[scriptable, uuid(38a19612-dc58-414a-954c-233183808484)]
+interface nsIDOMMozCSSKeyframeRule : nsIDOMCSSRule
+{
+           attribute DOMString                 keyText;
+  readonly attribute nsIDOMCSSStyleDeclaration style;
+};
new file mode 100644
--- /dev/null
+++ b/dom/interfaces/css/nsIDOMMozCSSKeyframesRule.idl
@@ -0,0 +1,49 @@
+/* -*- Mode: IDL; 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 code for css3-animations.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   L. David Baron <dbaron@dbaron.org>, Mozilla Corporation (original author)
+ *
+ * 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 "nsIDOMCSSRule.idl"
+
+[scriptable, uuid(aa4ea11f-791b-4671-b192-b931e6539669)]
+interface nsIDOMMozCSSKeyframesRule : nsIDOMCSSRule
+{
+           attribute DOMString         name;
+  readonly attribute nsIDOMCSSRuleList cssRules;
+
+  void                     insertRule(in DOMString rule);
+  void                     deleteRule(in DOMString key);
+  nsIDOMMozCSSKeyframeRule findRule(in DOMString key);
+};
--- a/dom/locales/en-US/chrome/layout/css.properties
+++ b/dom/locales/en-US/chrome/layout/css.properties
@@ -55,19 +55,23 @@ PEImportNotURI=Expected URI in @import r
 PEImportBadURI=Invalid URI in @import rule: '%1$S'.
 PEImportUnexpected=Found unexpected '%1$S' within @import.
 PEGroupRuleEOF=end of @media or @-moz-document rule
 PEMozDocRuleBadFunc=Expected url(), url-prefix(), or domain() in @-moz-document rule but found '%1$S'.
 PEMozDocRuleNotURI=Expected URI in @-moz-document rule but found '%1$S'.
 PEAtNSPrefixEOF=namespace prefix in @namespace rule
 PEAtNSURIEOF=namespace URI in @namespace rule
 PEAtNSUnexpected=Unexpected token within @namespace: '%1$S'.
+PEKeyframeNameEOF=name of @keyframes rule.
+PEKeyframeBadName=Expected identifier for name of @keyframes rule.
+PEKeyframeBrace=Expected opening { of @keyframes rule.
 PESkipDeclBraceEOF=closing } of declaration block
 PESkipRSBraceEOF=closing } of invalid rule set
 PEBadSelectorRSIgnored=Ruleset ignored due to bad selector.
+PEBadSelectorKeyframeRuleIgnored=Keyframe rule ignored due to bad selector.
 PESelectorListExtraEOF=',' or '{'
 PESelectorListExtra=Expected ',' or '{' but found '%1$S'.
 PESelectorGroupNoSelector=Selector expected.
 PESelectorGroupExtraCombinator=Dangling combinator.
 PEClassSelEOF=class name
 PEClassSelNotIdent=Expected identifier for class selector but found '%1$S'.
 PETypeSelEOF=element type
 PETypeSelNotType=Expected element name or '*' but found '%1$S'.
@@ -133,9 +137,9 @@ PEUnknownFontDesc=Unknown descriptor '%1
 PEMQExpectedExpressionStart=Expected '(' to start media query expression but found '%1$S'.
 PEMQExpressionEOF=contents of media query expression
 PEMQExpectedFeatureName=Expected media feature name but found '%1$S'.
 PEMQExpectedFeatureNameEnd=Expected ':' or ')' after media feature name but found '%1$S'.
 PEMQNoMinMaxWithoutValue=Media features with min- or max- must have a value.
 PEMQExpectedFeatureValue=Found invalid value for media feature.
 PEBadFontBlockStart=Expected '{' to begin @font-face rule but found '%1$S'.
 PEBadFontBlockEnd=Expected '}' to end @font-face rule but found '%1$S'.
-PEAnonBoxNotAlone=Did not expect anonymous box.
\ No newline at end of file
+PEAnonBoxNotAlone=Did not expect anonymous box.
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -249,16 +249,26 @@ public:
                             PRUint32 aLineNumber, // for error reporting
                             nscolor* aColor);
 
   nsresult ParseSelectorString(const nsSubstring& aSelectorString,
                                nsIURI* aURL, // for error reporting
                                PRUint32 aLineNumber, // for error reporting
                                nsCSSSelectorList **aSelectorList);
 
+  already_AddRefed<nsCSSKeyframeRule>
+  ParseKeyframeRule(const nsSubstring& aBuffer,
+                    nsIURI*            aURL,
+                    PRUint32           aLineNumber);
+
+  bool ParseKeyframeSelectorString(const nsSubstring& aSelectorString,
+                                   nsIURI* aURL, // for error reporting
+                                   PRUint32 aLineNumber, // for error reporting
+                                   nsTArray<float>& aSelectorList);
+
 protected:
   class nsAutoParseCompoundProperty;
   friend class nsAutoParseCompoundProperty;
 
   void AppendRule(nsICSSRule* aRule);
   friend void AppendRuleToSheet(nsICSSRule*, void*); // calls AppendRule
 
   /**
@@ -352,16 +362,19 @@ protected:
                         void* aProcessData);
 
   PRBool ParseFontFaceRule(RuleAppendFunc aAppendFunc, void* aProcessData);
   PRBool ParseFontDescriptor(nsCSSFontFaceRule* aRule);
   PRBool ParseFontDescriptorValue(nsCSSFontDesc aDescID,
                                   nsCSSValue& aValue);
 
   PRBool ParsePageRule(RuleAppendFunc aAppendFunc, void* aProcessData);
+  PRBool ParseKeyframesRule(RuleAppendFunc aAppendFunc, void* aProcessData);
+  already_AddRefed<nsCSSKeyframeRule> ParseKeyframeRule();
+  PRBool ParseKeyframeSelectorList(nsTArray<float>& aSelectorList);
 
   enum nsSelectorParsingStatus {
     // we have parsed a selector and we saw a token that cannot be
     // part of a selector:
     eSelectorParsingStatus_Done,
     // we should continue parsing the selector:
     eSelectorParsingStatus_Continue,
     // we saw an unexpected token or token value,
@@ -1295,16 +1308,66 @@ CSSParserImpl::ParseSelectorString(const
 
   NS_ASSERTION(!*aSelectorList, "Shouldn't have list!");
   if (prefixErr)
     return NS_ERROR_DOM_NAMESPACE_ERR;
 
   return NS_ERROR_DOM_SYNTAX_ERR;
 }
 
+
+already_AddRefed<nsCSSKeyframeRule>
+CSSParserImpl::ParseKeyframeRule(const nsSubstring&  aBuffer,
+                                 nsIURI*             aURI,
+                                 PRUint32            aLineNumber)
+{
+  InitScanner(aBuffer, aURI, aLineNumber, aURI, nsnull);
+
+  AssertInitialState();
+
+  nsRefPtr<nsCSSKeyframeRule> result = ParseKeyframeRule();
+  if (GetToken(PR_TRUE)) {
+    // extra garbage at the end
+    result = nsnull;
+  }
+
+  OUTPUT_ERROR();
+  ReleaseScanner();
+
+  return result.forget();
+}
+
+bool
+CSSParserImpl::ParseKeyframeSelectorString(const nsSubstring& aSelectorString,
+                                           nsIURI* aURI, // for error reporting
+                                           PRUint32 aLineNumber, // for error reporting
+                                           nsTArray<float>& aSelectorList)
+{
+  NS_ABORT_IF_FALSE(aSelectorList.IsEmpty(), "given list should start empty");
+
+  InitScanner(aSelectorString, aURI, aLineNumber, aURI, nsnull);
+
+  AssertInitialState();
+
+  bool success = ParseKeyframeSelectorList(aSelectorList) &&
+                 // must consume entire input string
+                 !GetToken(PR_TRUE);
+
+  OUTPUT_ERROR();
+  ReleaseScanner();
+
+  if (success) {
+    NS_ASSERTION(!aSelectorList.IsEmpty(), "should not be empty");
+  } else {
+    aSelectorList.Clear();
+  }
+
+  return success;
+}
+
 //----------------------------------------------------------------------
 
 PRBool
 CSSParserImpl::GetToken(PRBool aSkipWS)
 {
   for (;;) {
     if (!mHavePushBack) {
       if (!mScanner.Next(mToken)) {
@@ -1525,16 +1588,20 @@ CSSParserImpl::ParseAtRule(RuleAppendFun
   } else if (mToken.mIdent.LowerCaseEqualsLiteral("font-face")) {
     parseFunc = &CSSParserImpl::ParseFontFaceRule;
     newSection = eCSSSection_General;
 
   } else if (mToken.mIdent.LowerCaseEqualsLiteral("page")) {
     parseFunc = &CSSParserImpl::ParsePageRule;
     newSection = eCSSSection_General;
 
+  } else if (mToken.mIdent.LowerCaseEqualsLiteral("-moz-keyframes")) {
+    parseFunc = &CSSParserImpl::ParseKeyframesRule;
+    newSection = eCSSSection_General;
+
   } else {
     if (!NonMozillaVendorIdentifier(mToken.mIdent)) {
       REPORT_UNEXPECTED_TOKEN(PEUnknownAtRule);
       OUTPUT_ERROR();
     }
     // Skip over unsupported at rule, don't advance section
     return SkipAtRule(PR_FALSE);
   }
@@ -2237,16 +2304,111 @@ CSSParserImpl::ParseFontDescriptor(nsCSS
 
 PRBool
 CSSParserImpl::ParsePageRule(RuleAppendFunc aAppendFunc, void* aData)
 {
   // XXX not yet implemented
   return PR_FALSE;
 }
 
+PRBool
+CSSParserImpl::ParseKeyframesRule(RuleAppendFunc aAppendFunc, void* aData)
+{
+  if (!GetToken(PR_TRUE)) {
+    REPORT_UNEXPECTED_EOF(PEKeyframeNameEOF);
+    return PR_FALSE;
+  }
+
+  if (mToken.mType != eCSSToken_Ident) {
+    REPORT_UNEXPECTED_TOKEN(PEKeyframeBadName);
+    UngetToken();
+    return PR_FALSE;
+  }
+  nsString name(mToken.mIdent);
+
+  if (!ExpectSymbol('{', PR_TRUE)) {
+    REPORT_UNEXPECTED_TOKEN(PEKeyframeBrace);
+    return PR_FALSE;
+  }
+
+  nsRefPtr<nsCSSKeyframesRule> rule = new nsCSSKeyframesRule(name);
+
+  while (!ExpectSymbol('}', PR_TRUE)) {
+    nsRefPtr<nsCSSKeyframeRule> kid = ParseKeyframeRule();
+    if (kid) {
+      rule->AppendStyleRule(kid);
+    } else {
+      OUTPUT_ERROR();
+      SkipRuleSet(PR_TRUE);
+    }
+  }
+
+  (*aAppendFunc)(rule, aData);
+  return PR_TRUE;
+}
+
+already_AddRefed<nsCSSKeyframeRule>
+CSSParserImpl::ParseKeyframeRule()
+{
+  nsTArray<float> selectorList;
+  if (!ParseKeyframeSelectorList(selectorList)) {
+    REPORT_UNEXPECTED(PEBadSelectorKeyframeRuleIgnored);
+    return nsnull;
+  }
+
+  nsAutoPtr<css::Declaration> declaration(ParseDeclarationBlock(PR_TRUE));
+  if (!declaration) {
+    REPORT_UNEXPECTED(PEBadSelectorKeyframeRuleIgnored);
+    return nsnull;
+  }
+
+  // Takes ownership of declaration, and steals contents of selectorList.
+  nsRefPtr<nsCSSKeyframeRule> rule =
+    new nsCSSKeyframeRule(selectorList, declaration);
+
+  return rule.forget();
+}
+
+PRBool
+CSSParserImpl::ParseKeyframeSelectorList(nsTArray<float>& aSelectorList)
+{
+  for (;;) {
+    if (!GetToken(PR_TRUE)) {
+      // The first time through the loop, this means we got an empty
+      // list.  Otherwise, it means we have a trailing comma.
+      return PR_FALSE;
+    }
+    float value;
+    switch (mToken.mType) {
+      case eCSSToken_Percentage:
+        value = mToken.mNumber;
+        break;
+      case eCSSToken_Ident:
+        if (mToken.mIdent.LowerCaseEqualsLiteral("from")) {
+          value = 0.0f;
+          break;
+        }
+        if (mToken.mIdent.LowerCaseEqualsLiteral("to")) {
+          value = 1.0f;
+          break;
+        }
+        // fall through
+      default:
+        UngetToken();
+        // The first time through the loop, this means we got an empty
+        // list.  Otherwise, it means we have a trailing comma.
+        return PR_FALSE;
+    }
+    aSelectorList.AppendElement(value);
+    if (!ExpectSymbol(',', PR_TRUE)) {
+      return PR_TRUE;
+    }
+  }
+}
+
 void
 CSSParserImpl::SkipUntil(PRUnichar aStopSymbol)
 {
   nsCSSToken* tk = &mToken;
   nsAutoTArray<PRUnichar, 16> stack;
   stack.AppendElement(aStopSymbol);
   for (;;) {
     if (!GetToken(PR_TRUE)) {
@@ -2416,17 +2578,16 @@ CSSParserImpl::ParseRuleSet(RuleAppendFu
     return PR_FALSE;
   }
   NS_ASSERTION(nsnull != slist, "null selector list");
   CLEAR_ERROR();
 
   // Next parse the declaration block
   css::Declaration* declaration = ParseDeclarationBlock(PR_TRUE);
   if (nsnull == declaration) {
-    // XXX skip something here
     delete slist;
     return PR_FALSE;
   }
 
 #if 0
   slist->Dump();
   fputs("{\n", stdout);
   declaration->List();
@@ -8653,8 +8814,28 @@ nsresult
 nsCSSParser::ParseSelectorString(const nsSubstring&  aSelectorString,
                                  nsIURI*             aURI,
                                  PRUint32            aLineNumber,
                                  nsCSSSelectorList** aSelectorList)
 {
   return static_cast<CSSParserImpl*>(mImpl)->
     ParseSelectorString(aSelectorString, aURI, aLineNumber, aSelectorList);
 }
+
+already_AddRefed<nsCSSKeyframeRule>
+nsCSSParser::ParseKeyframeRule(const nsSubstring& aBuffer,
+                               nsIURI*            aURI,
+                               PRUint32           aLineNumber)
+{
+  return static_cast<CSSParserImpl*>(mImpl)->
+    ParseKeyframeRule(aBuffer, aURI, aLineNumber);
+}
+
+bool
+nsCSSParser::ParseKeyframeSelectorString(const nsSubstring& aSelectorString,
+                                         nsIURI*            aURI,
+                                         PRUint32           aLineNumber,
+                                         nsTArray<float>&   aSelectorList)
+{
+  return static_cast<CSSParserImpl*>(mImpl)->
+    ParseKeyframeSelectorString(aSelectorString, aURI, aLineNumber,
+                                aSelectorList);
+}
--- a/layout/style/nsCSSParser.h
+++ b/layout/style/nsCSSParser.h
@@ -39,24 +39,26 @@
 
 #ifndef nsCSSParser_h___
 #define nsCSSParser_h___
 
 #include "nsAString.h"
 #include "nsCSSProperty.h"
 #include "nsColor.h"
 #include "nsCOMArray.h"
+#include "nsCOMPtr.h"
 
 class nsICSSRule;
 class nsCSSStyleSheet;
 class nsIPrincipal;
 class nsIURI;
 class nsIUnicharInputStream;
 struct nsCSSSelectorList;
 class nsMediaList;
+class nsCSSKeyframeRule;
 
 namespace mozilla {
 namespace css {
 class Declaration;
 class Loader;
 class StyleRule;
 }
 }
@@ -189,16 +191,34 @@ public:
    * Parse aBuffer into a selector list.  On success, caller must
    * delete *aSelectorList when done with it.
    */
   nsresult ParseSelectorString(const nsSubstring&  aSelectorString,
                                nsIURI*             aURL,
                                PRUint32            aLineNumber,
                                nsCSSSelectorList** aSelectorList);
 
+  /*
+   * Parse a keyframe rule (which goes inside an @keyframes rule).
+   * Return it if the parse was successful.
+   */
+  already_AddRefed<nsCSSKeyframeRule>
+  ParseKeyframeRule(const nsSubstring& aBuffer,
+                    nsIURI*            aURL,
+                    PRUint32           aLineNumber);
+
+  /*
+   * Parse a selector list for a keyframe rule.  Return whether
+   * the parse succeeded.
+   */
+  bool ParseKeyframeSelectorString(const nsSubstring& aSelectorString,
+                                   nsIURI*            aURL,
+                                   PRUint32           aLineNumber,
+                                   nsTArray<float>&   aSelectorList);
+
 protected:
   // This is a CSSParserImpl*, but if we expose that type name in this
   // header, we can't put the type definition (in nsCSSParser.cpp) in
   // the anonymous namespace.
   void* mImpl;
 };
 
 #endif /* nsCSSParser_h___ */
--- a/layout/style/nsCSSRules.cpp
+++ b/layout/style/nsCSSRules.cpp
@@ -57,16 +57,17 @@
 #include "nsIDocument.h"
 #include "nsPresContext.h"
 
 #include "nsContentUtils.h"
 #include "nsStyleConsts.h"
 #include "nsDOMError.h"
 #include "nsStyleUtil.h"
 #include "mozilla/css/Declaration.h"
+#include "nsCSSParser.h"
 #include "nsPrintfCString.h"
 
 namespace css = mozilla::css;
 
 #define IMPL_STYLE_RULE_INHERIT_GET_DOM_RULE_WEAK(class_, super_) \
 nsIDOMCSSRule* class_::GetDOMRuleWeak(nsresult *aResult) \
   { *aResult = NS_OK; return this; }
 #define IMPL_STYLE_RULE_INHERIT_MAP_RULE_INFO_INTO(class_, super_) \
@@ -1672,20 +1673,460 @@ nsCSSFontFaceRule::GetStyle(nsIDOMCSSSty
 // Arguably these should forward to nsCSSFontFaceStyleDecl methods.
 void
 nsCSSFontFaceRule::SetDesc(nsCSSFontDesc aDescID, nsCSSValue const & aValue)
 {
   NS_PRECONDITION(aDescID > eCSSFontDesc_UNKNOWN &&
                   aDescID < eCSSFontDesc_COUNT,
                   "aDescID out of range in nsCSSFontFaceRule::SetDesc");
 
+  // FIXME: handle dynamic changes
+
   mDecl.*nsCSSFontFaceStyleDecl::Fields[aDescID] = aValue;
 }
 
 void
 nsCSSFontFaceRule::GetDesc(nsCSSFontDesc aDescID, nsCSSValue & aValue)
 {
   NS_PRECONDITION(aDescID > eCSSFontDesc_UNKNOWN &&
                   aDescID < eCSSFontDesc_COUNT,
                   "aDescID out of range in nsCSSFontFaceRule::GetDesc");
 
   aValue = mDecl.*nsCSSFontFaceStyleDecl::Fields[aDescID];
 }
+
+// -------------------------------------------
+// nsCSSKeyframeStyleDeclaration
+//
+
+nsCSSKeyframeStyleDeclaration::nsCSSKeyframeStyleDeclaration(nsCSSKeyframeRule *aRule)
+  : mRule(aRule)
+{
+}
+
+nsCSSKeyframeStyleDeclaration::~nsCSSKeyframeStyleDeclaration()
+{
+  NS_ASSERTION(!mRule, "DropReference not called.");
+}
+
+NS_IMPL_ADDREF(nsCSSKeyframeStyleDeclaration)
+NS_IMPL_RELEASE(nsCSSKeyframeStyleDeclaration)
+
+css::Declaration*
+nsCSSKeyframeStyleDeclaration::GetCSSDeclaration(PRBool aAllocate)
+{
+  if (mRule) {
+    return mRule->Declaration();
+  } else {
+    return nsnull;
+  }
+}
+
+/*
+ * This is a utility function.  It will only fail if it can't get a
+ * parser.  This means it can return NS_OK without aURI or aCSSLoader
+ * being initialized.
+ */
+nsresult
+nsCSSKeyframeStyleDeclaration::GetCSSParsingEnvironment(nsIURI** aSheetURI,
+                                                nsIURI** aBaseURI,
+                                                nsIPrincipal** aSheetPrincipal,
+                                                css::Loader** aCSSLoader)
+{
+  return GetCSSParsingEnvironmentForRule(mRule, aSheetURI, aBaseURI,
+                                         aSheetPrincipal, aCSSLoader);
+}
+
+NS_IMETHODIMP
+nsCSSKeyframeStyleDeclaration::GetParentRule(nsIDOMCSSRule **aParent)
+{
+  NS_ENSURE_ARG_POINTER(aParent);
+
+  NS_IF_ADDREF(*aParent = mRule);
+  return NS_OK;
+}
+
+nsresult
+nsCSSKeyframeStyleDeclaration::SetCSSDeclaration(css::Declaration* aDecl)
+{
+  NS_ABORT_IF_FALSE(aDecl, "must be non-null");
+  mRule->ChangeDeclaration(aDecl);
+  return NS_OK;
+}
+
+nsIDocument*
+nsCSSKeyframeStyleDeclaration::DocToUpdate()
+{
+  return nsnull;
+}
+
+// -------------------------------------------
+// nsCSSKeyframeRule
+//
+
+nsCSSKeyframeRule::nsCSSKeyframeRule(const nsCSSKeyframeRule& aCopy)
+  // copy everything except our reference count and mDOMDeclaration
+  : Rule(aCopy)
+  , mKeys(aCopy.mKeys)
+  , mDeclaration(new mozilla::css::Declaration(*aCopy.mDeclaration))
+{
+}
+
+nsCSSKeyframeRule::~nsCSSKeyframeRule()
+{
+  if (mDOMDeclaration) {
+    mDOMDeclaration->DropReference();
+  }
+}
+
+/* virtual */ already_AddRefed<nsICSSRule>
+nsCSSKeyframeRule::Clone() const
+{
+  nsCOMPtr<nsICSSRule> clone = new nsCSSKeyframeRule(*this);
+  return clone.forget();
+}
+
+NS_IMPL_ADDREF(nsCSSKeyframeRule)
+NS_IMPL_RELEASE(nsCSSKeyframeRule)
+
+DOMCI_DATA(MozCSSKeyframeRule, nsCSSKeyframeRule)
+
+// QueryInterface implementation for nsCSSKeyframeRule
+NS_INTERFACE_MAP_BEGIN(nsCSSKeyframeRule)
+  NS_INTERFACE_MAP_ENTRY(nsICSSRule)
+  NS_INTERFACE_MAP_ENTRY(nsIStyleRule)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMMozCSSKeyframeRule)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMCSSRule)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsICSSRule)
+  NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(MozCSSKeyframeRule)
+NS_INTERFACE_MAP_END
+
+IMPL_STYLE_RULE_INHERIT_GET_DOM_RULE_WEAK(nsCSSKeyframeRule, Rule)
+
+/* virtual */ void
+nsCSSKeyframeRule::MapRuleInfoInto(nsRuleData* aRuleData)
+{
+  // We need to implement MapRuleInfoInto because the animation manager
+  // constructs a rule node pointing to us in order to compute the
+  // styles it needs to animate.
+
+  // FIXME (spec): The spec doesn't say what to do with !important.
+  // We'll just map them.
+  if (mDeclaration->HasImportantData()) {
+    mDeclaration->MapImportantRuleInfoInto(aRuleData);
+  }
+  mDeclaration->MapNormalRuleInfoInto(aRuleData);
+}
+
+#ifdef DEBUG
+void
+nsCSSKeyframeRule::List(FILE* out, PRInt32 aIndent) const
+{
+  // FIXME: WRITE ME
+}
+#endif
+
+/* virtual */ PRInt32
+nsCSSKeyframeRule::GetType() const
+{
+  return nsICSSRule::KEYFRAME_RULE;
+}
+
+NS_IMETHODIMP
+nsCSSKeyframeRule::GetType(PRUint16* aType)
+{
+  *aType = nsIDOMCSSRule::MOZ_KEYFRAME_RULE;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSKeyframeRule::GetCssText(nsAString& aCssText)
+{
+  nsCSSKeyframeRule::GetKeyText(aCssText);
+  aCssText.AppendLiteral(" { ");
+  nsAutoString tmp;
+  mDeclaration->ToString(tmp);
+  aCssText.Append(tmp);
+  aCssText.AppendLiteral(" }");
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSKeyframeRule::SetCssText(const nsAString& aCssText)
+{
+  // FIXME: implement???
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsCSSKeyframeRule::GetParentStyleSheet(nsIDOMCSSStyleSheet** aSheet)
+{
+  NS_IF_ADDREF(*aSheet = mSheet);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSKeyframeRule::GetParentRule(nsIDOMCSSRule** aParentRule)
+{
+  if (mParentRule) {
+    return mParentRule->GetDOMRule(aParentRule);
+  }
+  *aParentRule = nsnull;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSKeyframeRule::GetKeyText(nsAString& aKeyText)
+{
+  aKeyText.Truncate();
+  PRUint32 i = 0, i_end = mKeys.Length();
+  NS_ABORT_IF_FALSE(i_end != 0, "must have some keys");
+  for (;;) {
+    aKeyText.AppendFloat(mKeys[i] * 100.0f);
+    aKeyText.Append(PRUnichar('%'));
+    if (++i == i_end) {
+      break;
+    }
+    aKeyText.AppendLiteral(", ");
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSKeyframeRule::SetKeyText(const nsAString& aKeyText)
+{
+  nsCSSParser parser;
+  NS_ENSURE_TRUE(parser, NS_ERROR_OUT_OF_MEMORY);
+
+  nsTArray<float> newSelectors;
+  // FIXME: pass filename and line number
+  if (parser.ParseKeyframeSelectorString(aKeyText, nsnull, 0, newSelectors)) {
+    newSelectors.SwapElements(mKeys);
+  } else {
+    // for now, we don't do anything if the parse fails
+  }
+
+  mSheet->SetModifiedByChildRule();
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSKeyframeRule::GetStyle(nsIDOMCSSStyleDeclaration** aStyle)
+{
+  if (!mDOMDeclaration) {
+    mDOMDeclaration = new nsCSSKeyframeStyleDeclaration(this);
+  }
+  NS_ADDREF(*aStyle = mDOMDeclaration);
+  return NS_OK;
+}
+
+void
+nsCSSKeyframeRule::ChangeDeclaration(mozilla::css::Declaration* aDeclaration)
+{
+  mDeclaration = aDeclaration;
+
+  mSheet->SetModifiedByChildRule();
+}
+
+// -------------------------------------------
+// nsCSSKeyframesRule
+//
+
+nsCSSKeyframesRule::nsCSSKeyframesRule(const nsCSSKeyframesRule& aCopy)
+  // copy everything except our reference count.  GroupRule's copy
+  // constructor also doesn't copy the lazily-constructed
+  // mRuleCollection.
+  : GroupRule(aCopy),
+    mName(aCopy.mName)
+{
+}
+
+nsCSSKeyframesRule::~nsCSSKeyframesRule()
+{
+}
+
+/* virtual */ already_AddRefed<nsICSSRule>
+nsCSSKeyframesRule::Clone() const
+{
+  nsCOMPtr<nsICSSRule> clone = new nsCSSKeyframesRule(*this);
+  return clone.forget();
+}
+
+NS_IMPL_ADDREF_INHERITED(nsCSSKeyframesRule, mozilla::css::GroupRule)
+NS_IMPL_RELEASE_INHERITED(nsCSSKeyframesRule, mozilla::css::GroupRule)
+
+DOMCI_DATA(MozCSSKeyframesRule, nsCSSKeyframesRule)
+
+// QueryInterface implementation for nsCSSKeyframesRule
+NS_INTERFACE_MAP_BEGIN(nsCSSKeyframesRule)
+  NS_INTERFACE_MAP_ENTRY(nsICSSRule)
+  NS_INTERFACE_MAP_ENTRY(nsIStyleRule)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMCSSRule)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMMozCSSKeyframesRule)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsICSSRule)
+  NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(MozCSSKeyframesRule)
+NS_INTERFACE_MAP_END
+
+#ifdef DEBUG
+void
+nsCSSKeyframesRule::List(FILE* out, PRInt32 aIndent) const
+{
+  // FIXME: WRITE ME
+}
+#endif
+
+/* virtual */ PRInt32
+nsCSSKeyframesRule::GetType() const
+{
+  return nsICSSRule::KEYFRAMES_RULE;
+}
+
+NS_IMETHODIMP
+nsCSSKeyframesRule::GetType(PRUint16* aType)
+{
+  *aType = nsIDOMCSSRule::MOZ_KEYFRAMES_RULE;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSKeyframesRule::GetCssText(nsAString& aCssText)
+{
+  aCssText.AssignLiteral("@-moz-keyframes ");
+  aCssText.Append(mName);
+  aCssText.AppendLiteral(" {\n");
+  nsAutoString tmp;
+  for (PRUint32 i = 0, i_end = mRules.Count(); i != i_end; ++i) {
+    static_cast<nsCSSKeyframeRule*>(mRules[i])->GetCssText(tmp);
+    aCssText.Append(tmp);
+    aCssText.AppendLiteral("\n");
+  }
+  aCssText.AppendLiteral("}");
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSKeyframesRule::SetCssText(const nsAString& aCssText)
+{
+  // FIXME: implement???
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsCSSKeyframesRule::GetParentStyleSheet(nsIDOMCSSStyleSheet** aSheet)
+{
+  NS_IF_ADDREF(*aSheet = mSheet);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSKeyframesRule::GetParentRule(nsIDOMCSSRule** aParentRule)
+{
+  if (mParentRule) {
+    return mParentRule->GetDOMRule(aParentRule);
+  }
+  *aParentRule = nsnull;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSKeyframesRule::GetName(nsAString& aName)
+{
+  aName = mName;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSKeyframesRule::SetName(const nsAString& aName)
+{
+  mName = aName;
+
+  mSheet->SetModifiedByChildRule();
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSKeyframesRule::GetCssRules(nsIDOMCSSRuleList* *aRuleList)
+{
+  NS_ADDREF(*aRuleList = GroupRule::GetCssRules());
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSKeyframesRule::InsertRule(const nsAString& aRule)
+{
+  // The spec is confusing, and I think we should just append the rule,
+  // which also turns out to match WebKit:
+  // http://lists.w3.org/Archives/Public/www-style/2011Apr/0034.html
+  nsCSSParser parser;
+  NS_ENSURE_TRUE(parser, NS_OK);
+
+  // FIXME: pass filename and line number
+  nsRefPtr<nsCSSKeyframeRule> rule =
+    parser.ParseKeyframeRule(aRule, nsnull, 0);
+  if (rule) {
+    mRules.AppendObject(rule);
+    mSheet->SetModifiedByChildRule();
+  }
+
+  return NS_OK;
+}
+
+static const PRUint32 RULE_NOT_FOUND = PRUint32(-1);
+
+PRUint32
+nsCSSKeyframesRule::FindRuleIndexForKey(const nsAString& aKey)
+{
+  nsCSSParser parser;
+  NS_ENSURE_TRUE(parser, RULE_NOT_FOUND);
+
+  nsTArray<float> keys;
+  // FIXME: pass filename and line number
+  if (parser.ParseKeyframeSelectorString(aKey, nsnull, 0, keys)) {
+    // The spec isn't clear, but we'll match on the key list, which
+    // mostly matches what WebKit does, except we'll do last-match
+    // instead of first-match, and handling parsing differences better.
+    // http://lists.w3.org/Archives/Public/www-style/2011Apr/0036.html
+    // http://lists.w3.org/Archives/Public/www-style/2011Apr/0037.html
+    for (PRUint32 i = mRules.Count(); i-- != 0; ) {
+      if (static_cast<nsCSSKeyframeRule*>(mRules[i])->GetKeys() == keys) {
+        return i;
+      }
+    }
+  }
+
+  return RULE_NOT_FOUND;
+}
+
+NS_IMETHODIMP
+nsCSSKeyframesRule::DeleteRule(const nsAString& aKey)
+{
+  PRUint32 index = FindRuleIndexForKey(aKey);
+  if (index != RULE_NOT_FOUND) {
+    mRules.RemoveObjectAt(index);
+    mSheet->SetModifiedByChildRule();
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSKeyframesRule::FindRule(const nsAString& aKey,
+                             nsIDOMMozCSSKeyframeRule** aResult)
+{
+  PRUint32 index = FindRuleIndexForKey(aKey);
+  if (index == RULE_NOT_FOUND) {
+    *aResult = nsnull;
+  } else {
+    NS_ADDREF(*aResult = static_cast<nsCSSKeyframeRule*>(mRules[index]));
+  }
+  return NS_OK;
+}
+
+// GroupRule interface
+/* virtual */ PRBool
+nsCSSKeyframesRule::UseForPresentation(nsPresContext* aPresContext,
+                                       nsMediaQueryResultCacheKey& aKey)
+{
+  NS_ABORT_IF_FALSE(PR_FALSE, "should not be called");
+  return PR_FALSE;
+}
+
--- a/layout/style/nsCSSRules.h
+++ b/layout/style/nsCSSRules.h
@@ -43,21 +43,33 @@
 #ifndef nsCSSRules_h_
 #define nsCSSRules_h_
 
 #include "Rule.h"
 #include "mozilla/css/GroupRule.h"
 #include "nsIDOMCSSMediaRule.h"
 #include "nsIDOMCSSMozDocumentRule.h"
 #include "nsIDOMCSSFontFaceRule.h"
+#include "nsIDOMMozCSSKeyframeRule.h"
+#include "nsIDOMMozCSSKeyframesRule.h"
 #include "nsIDOMCSSStyleDeclaration.h"
+#include "nsICSSRuleList.h"
 #include "nsAutoPtr.h"
 #include "nsCSSProperty.h"
 #include "nsCSSValue.h"
 #include "nsIDOMCSSCharsetRule.h"
+#include "nsTArray.h"
+#include "nsDOMCSSDeclaration.h"
+#include "Declaration.h"
+
+namespace mozilla {
+namespace css {
+class StyleRule;
+}
+}
 
 class nsMediaList;
 
 namespace mozilla {
 namespace css {
 
 class NS_FINAL_CLASS MediaRule : public GroupRule,
                                  public nsIDOMCSSMediaRule
@@ -296,9 +308,135 @@ public:
 
 private:
   nsString  mEncoding;
 };
 
 } // namespace css
 } // namespace mozilla
 
+class nsCSSKeyframeRule;
+
+class NS_FINAL_CLASS nsCSSKeyframeStyleDeclaration
+                         : public nsDOMCSSDeclaration
+{
+public:
+  nsCSSKeyframeStyleDeclaration(nsCSSKeyframeRule *aRule);
+  virtual ~nsCSSKeyframeStyleDeclaration();
+
+  NS_IMETHOD GetParentRule(nsIDOMCSSRule **aParent);
+  void DropReference() { mRule = nsnull; }
+  virtual mozilla::css::Declaration* GetCSSDeclaration(PRBool aAllocate);
+  virtual nsresult SetCSSDeclaration(mozilla::css::Declaration* aDecl);
+  virtual nsresult GetCSSParsingEnvironment(nsIURI** aSheetURI,
+                                            nsIURI** aBaseURI,
+                                            nsIPrincipal** aSheetPrincipal,
+                                            mozilla::css::Loader** aCSSLoader);
+  virtual nsIDocument* DocToUpdate();
+
+  NS_IMETHOD_(nsrefcnt) AddRef();
+  NS_IMETHOD_(nsrefcnt) Release();
+
+  virtual nsINode *GetParentObject()
+  {
+    return nsnull;
+  }
+
+protected:
+  nsAutoRefCnt mRefCnt;
+  NS_DECL_OWNINGTHREAD
+
+  // This reference is not reference-counted. The rule object tells us
+  // when it's about to go away.
+  nsCSSKeyframeRule *mRule;
+};
+
+class NS_FINAL_CLASS nsCSSKeyframeRule : public mozilla::css::Rule,
+                                         public nsIDOMMozCSSKeyframeRule
+{
+public:
+  // WARNING: Steals the contents of aKeys *and* aDeclaration
+  nsCSSKeyframeRule(nsTArray<float> aKeys,
+                    nsAutoPtr<mozilla::css::Declaration> aDeclaration)
+    : mDeclaration(aDeclaration)
+  {
+    mKeys.SwapElements(aKeys);
+  }
+
+  nsCSSKeyframeRule(const nsCSSKeyframeRule& aCopy);
+  ~nsCSSKeyframeRule();
+
+  NS_DECL_ISUPPORTS
+
+  // nsIStyleRule methods
+#ifdef DEBUG
+  virtual void List(FILE* out = stdout, PRInt32 aIndent = 0) const;
+#endif
+
+  // nsICSSRule methods
+  DECL_STYLE_RULE_INHERIT
+  virtual PRInt32 GetType() const;
+  virtual already_AddRefed<nsICSSRule> Clone() const;
+
+  // nsIDOMCSSRule interface
+  NS_DECL_NSIDOMCSSRULE
+
+  // nsIDOMMozCSSKeyframeRule interface
+  NS_DECL_NSIDOMMOZCSSKEYFRAMERULE
+
+  const nsTArray<float>& GetKeys() const     { return mKeys; }
+  mozilla::css::Declaration* Declaration()   { return mDeclaration; }
+
+  void ChangeDeclaration(mozilla::css::Declaration* aDeclaration);
+
+private:
+  nsAutoTArray<float, 1>                     mKeys;
+  nsAutoPtr<mozilla::css::Declaration>       mDeclaration;
+  // lazily created when needed:
+  nsRefPtr<nsCSSKeyframeStyleDeclaration>    mDOMDeclaration;
+};
+
+class NS_FINAL_CLASS nsCSSKeyframesRule : public mozilla::css::GroupRule,
+                                          public nsIDOMMozCSSKeyframesRule
+{
+public:
+  nsCSSKeyframesRule(const nsSubstring& aName)
+    : mName(aName)
+  {
+  }
+  nsCSSKeyframesRule(const nsCSSKeyframesRule& aCopy);
+  ~nsCSSKeyframesRule();
+
+  NS_DECL_ISUPPORTS_INHERITED
+
+  // nsIStyleRule methods
+#ifdef DEBUG
+  virtual void List(FILE* out = stdout, PRInt32 aIndent = 0) const;
+#endif
+
+  // nsICSSRule methods
+  virtual PRInt32 GetType() const;
+  virtual already_AddRefed<nsICSSRule> Clone() const;
+  virtual nsIDOMCSSRule* GetDOMRuleWeak(nsresult *aResult)
+  {
+    *aResult = NS_OK;
+    return this;
+  }
+
+  // nsIDOMCSSRule interface
+  NS_DECL_NSIDOMCSSRULE
+
+  // nsIDOMMozCSSKeyframesRule interface
+  NS_DECL_NSIDOMMOZCSSKEYFRAMESRULE
+
+  // rest of GroupRule
+  virtual PRBool UseForPresentation(nsPresContext* aPresContext,
+                                    nsMediaQueryResultCacheKey& aKey);
+
+  const nsString& GetName() { return mName; }
+
+private:
+  PRUint32 FindRuleIndexForKey(const nsAString& aKey);
+
+  nsString                                   mName;
+};
+
 #endif /* !defined(nsCSSRules_h_) */
--- a/layout/style/nsICSSRule.h
+++ b/layout/style/nsICSSRule.h
@@ -73,16 +73,18 @@ public:
     UNKNOWN_RULE = 0,
     CHARSET_RULE,
     IMPORT_RULE,
     NAMESPACE_RULE,
     STYLE_RULE,
     MEDIA_RULE,
     FONT_FACE_RULE,
     PAGE_RULE,
+    KEYFRAME_RULE,
+    KEYFRAMES_RULE,
     DOCUMENT_RULE
   };
 
   virtual PRInt32 GetType() const = 0;
 
   virtual nsIStyleSheet* GetStyleSheet() const = 0;
   virtual void SetStyleSheet(nsCSSStyleSheet* aSheet) = 0;
   virtual void SetParentRule(mozilla::css::GroupRule* aRule) = 0;
--- a/layout/style/test/Makefile.in
+++ b/layout/style/test/Makefile.in
@@ -147,16 +147,17 @@ GARBAGE += css_properties.js
 		test_dont_use_document_colors.html \
 		test_font_face_parser.html \
 		test_garbage_at_end_of_declarations.html \
 		test_ident_escaping.html \
 		test_inherit_computation.html \
 		test_inherit_storage.html \
 		test_initial_computation.html \
 		test_initial_storage.html \
+		test_keyframes_rules.html \
 		test_media_queries.html \
 		test_media_queries_dynamic.html \
 		test_media_queries_dynamic_xbl.html \
 		test_moz_device_pixel_ratio.html \
 		test_namespace_rule.html \
 		test_of_type_selectors.xhtml \
 		test_parse_ident.html \
 		test_parse_rule.html \
new file mode 100644
--- /dev/null
+++ b/layout/style/test/test_keyframes_rules.html
@@ -0,0 +1,135 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=577974
+-->
+<head>
+  <title>Test for Bug 577974</title>
+  <script type="application/javascript" src="/MochiKit/packed.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <style id="style">
+
+  @-moz-keyframes bounce {
+    from {
+      margin-left: 0
+    }
+
+    /*
+     * These rules should get dropped due to syntax errors.  The script
+     * below tests that the 25% rule following them is at cssRules[1].
+     */
+    from, { margin-left: 0 }
+    from , { margin-left: 0 }
+    , from { margin-left: 0 }
+    ,from { margin-left: 0 }
+    from from { margin-left: 0 }
+    from, 1 { margin-left: 0 }
+    1 { margin-left: 0 }
+    1, from { margin-left: 0 }
+    from, 1.0 { margin-left: 0 }
+    1.0 { margin-left: 0 }
+    1.0, from { margin-left: 0 }
+
+    25% {
+      margin-left: 25px;
+    }
+
+    75%, 85% {
+      margin-left: 90px;
+    }
+
+    100% {
+      margin-left: 100px;
+    }
+  }
+
+  </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=577974">Mozilla Bug 577974</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 577974 **/
+
+var sheet = document.getElementById("style").sheet;
+
+var bounce = sheet.cssRules[0];
+is(bounce.type, CSSRule.MOZ_KEYFRAMES_RULE, "bounce.type");
+is(bounce.type, 7, "bounce.type");
+is(bounce.name, "bounce", "bounce.name");
+bounce.name = "bouncier";
+is(bounce.name, "bouncier", "setting bounce.name");
+
+is(bounce.cssRules[0].type, CSSRule.MOZ_KEYFRAME_RULE, "keyframe rule type");
+is(bounce.cssRules[0].type, 8, "keyframe rule type");
+is(bounce.cssRules[0].keyText, "0%", "keyframe rule keyText");
+is(bounce.cssRules[1].keyText, "25%", "keyframe rule keyText");
+is(bounce.cssRules[2].keyText, "75%, 85%", "keyframe rule keyText");
+is(bounce.cssRules[3].keyText, "100%", "keyframe rule keyText");
+is(bounce.cssRules[0].style.marginLeft, "0pt", "keyframe rule style");
+is(bounce.cssRules[1].style.marginLeft, "25px", "keyframe rule style");
+
+is(bounce.cssRules[0].cssText, "0% { margin-left: 0pt; }");
+is(bounce.cssText, "@-moz-keyframes bouncier {\n" +
+                   "0% { margin-left: 0pt; }\n" +
+                   "25% { margin-left: 25px; }\n" +
+                   "75%, 85% { margin-left: 90px; }\n" +
+                   "100% { margin-left: 100px; }\n" +
+                   "}");
+
+bounce.cssRules[1].keyText = "from, 1"; // syntax error
+bounce.cssRules[1].keyText = "from, x"; // syntax error
+bounce.cssRules[1].keyText = "from,"; // syntax error
+bounce.cssRules[1].keyText = "from x"; // syntax error
+bounce.cssRules[1].keyText = "x"; // syntax error
+is(bounce.cssRules[1].keyText, "25%", "keyframe rule keyText parsing");
+bounce.cssRules[1].keyText = "from, 10%";
+is(bounce.cssRules[1].keyText, "0%, 10%", "keyframe rule keyText parsing");
+bounce.cssRules[1].keyText = "from, 0%";
+is(bounce.cssRules[1].keyText, "0%, 0%", "keyframe rule keyText parsing");
+bounce.cssRules[1].keyText = "from, from, from";
+is(bounce.cssRules[1].keyText, "0%, 0%, 0%", "keyframe rule keyText parsing");
+
+is(bounce.findRule("75%"), null, "findRule should match all keys");
+is(bounce.findRule("85%, 75%"), null,
+   "findRule should match all keys in order");
+is(bounce.findRule("75%,85%"), bounce.cssRules[2],
+   "findRule should match all keys in order, parsed");
+is(bounce.findRule("to"), bounce.cssRules[3],
+   "findRule should match keys as parsed");
+is(bounce.findRule("100%"), bounce.cssRules[3],
+   "findRule should match keys as parsed");
+is(bounce.findRule("100%, 100%"), null,
+   "findRule should match key list");
+is(bounce.findRule("100%,"), null,
+   "findRule should fail when given bad selector");
+is(bounce.findRule(",100%"), null,
+   "findRule should fail when given bad selector");
+is(bounce.cssRules.length, 4, "length of css rules");
+bounce.deleteRule("85%");
+is(bounce.cssRules.length, 4, "deleteRule should match all keys");
+bounce.deleteRule("85%, 75%");
+is(bounce.cssRules.length, 4, "deleteRule should match key list");
+bounce.deleteRule("75%  ,85%");
+is(bounce.cssRules.length, 3, "deleteRule should match keys in order, parsed");
+bounce.deleteRule("0%");
+is(bounce.cssRules.length, 2, "deleteRule should match keys in order, parsed");
+bounce.insertRule("from { color: blue }");
+is(bounce.cssRules.length, 3, "insertRule should append");
+is(bounce.cssRules[2].keyText, "0%", "insertRule should append");
+bounce.insertRule("from { color: blue }");
+is(bounce.cssRules.length, 4, "insertRule should append");
+is(bounce.cssRules[3].keyText, "0%", "insertRule should append");
+bounce.insertRule("from { color: blue } to { color: green }");
+is(bounce.cssRules.length, 4, "insertRule should ignore garbage at end");
+
+</script>
+</pre>
+</body>
+</html>