Bug 1345697 part 3 - Implement CSSKeyframesRule and CSSKeyframeRule for stylo. r=heycam draft
authorXidorn Quan <me@upsuper.org>
Fri, 19 May 2017 11:37:47 +1000
changeset 580917 41460384de0116c90dcbcb2caf9f2763a62b5f40
parent 580916 a97464201a12620b28bea580ad7e911fd64f8599
child 580918 a967a1284448a4958d8e8968606131d766e6ae16
push id59714
push userxquan@mozilla.com
push dateFri, 19 May 2017 05:01:48 +0000
reviewersheycam
bugs1345697
milestone55.0a1
Bug 1345697 part 3 - Implement CSSKeyframesRule and CSSKeyframeRule for stylo. r=heycam MozReview-Commit-ID: AeU0BWaBfYe
layout/style/ServoArcTypeList.h
layout/style/ServoBindingList.h
layout/style/ServoCSSRuleList.cpp
layout/style/ServoKeyframeRule.cpp
layout/style/ServoKeyframeRule.h
layout/style/ServoKeyframesRule.cpp
layout/style/ServoKeyframesRule.h
layout/style/moz.build
--- a/layout/style/ServoArcTypeList.h
+++ b/layout/style/ServoArcTypeList.h
@@ -8,15 +8,17 @@
 
 SERVO_ARC_TYPE(CssRules, ServoCssRules)
 SERVO_ARC_TYPE(StyleSheet, RawServoStyleSheet)
 SERVO_ARC_TYPE(ComputedValues, ServoComputedValues)
 SERVO_ARC_TYPE(DeclarationBlock, RawServoDeclarationBlock)
 SERVO_ARC_TYPE(StyleRule, RawServoStyleRule)
 SERVO_ARC_TYPE(ImportRule, RawServoImportRule)
 SERVO_ARC_TYPE(AnimationValue, RawServoAnimationValue)
+SERVO_ARC_TYPE(Keyframe, RawServoKeyframe)
+SERVO_ARC_TYPE(KeyframesRule, RawServoKeyframesRule)
 SERVO_ARC_TYPE(MediaList, RawServoMediaList)
 SERVO_ARC_TYPE(MediaRule, RawServoMediaRule)
 SERVO_ARC_TYPE(NamespaceRule, RawServoNamespaceRule)
 SERVO_ARC_TYPE(PageRule, RawServoPageRule)
 SERVO_ARC_TYPE(SupportsRule, RawServoSupportsRule)
 SERVO_ARC_TYPE(DocumentRule, RawServoDocumentRule)
 SERVO_ARC_TYPE(RuleNode, RawServoRuleNode)
--- a/layout/style/ServoBindingList.h
+++ b/layout/style/ServoBindingList.h
@@ -99,48 +99,81 @@ SERVO_BINDING_FUNC(Servo_CssRules_Insert
                    RawServoStyleSheetBorrowed sheet, const nsACString* rule,
                    uint32_t index, bool nested, mozilla::css::Loader* loader,
                    mozilla::ServoStyleSheet* gecko_stylesheet,
                    uint16_t* rule_type)
 SERVO_BINDING_FUNC(Servo_CssRules_DeleteRule, nsresult,
                    ServoCssRulesBorrowed rules, uint32_t index)
 
 // CSS Rules
+#define BASIC_RULE_FUNCS_WITHOUT_GETTER(type_) \
+  SERVO_BINDING_FUNC(Servo_##type_##_Debug, void, \
+                     RawServo##type_##Borrowed rule, nsACString* result) \
+  SERVO_BINDING_FUNC(Servo_##type_##_GetCssText, void, \
+                     RawServo##type_##Borrowed rule, nsAString* result)
 #define BASIC_RULE_FUNCS(type_) \
   SERVO_BINDING_FUNC(Servo_CssRules_Get##type_##RuleAt, \
                      RawServo##type_##RuleStrong, \
                      ServoCssRulesBorrowed rules, uint32_t index, \
                      uint32_t* line, uint32_t* column) \
-  SERVO_BINDING_FUNC(Servo_##type_##Rule_Debug, void, \
-                     RawServo##type_##RuleBorrowed rule, nsACString* result) \
-  SERVO_BINDING_FUNC(Servo_##type_##Rule_GetCssText, void, \
-                     RawServo##type_##RuleBorrowed rule, nsAString* result)
+  BASIC_RULE_FUNCS_WITHOUT_GETTER(type_##Rule)
 #define GROUP_RULE_FUNCS(type_) \
   BASIC_RULE_FUNCS(type_) \
   SERVO_BINDING_FUNC(Servo_##type_##Rule_GetRules, ServoCssRulesStrong, \
                      RawServo##type_##RuleBorrowed rule)
 BASIC_RULE_FUNCS(Style)
+BASIC_RULE_FUNCS_WITHOUT_GETTER(Keyframe)
+BASIC_RULE_FUNCS(Keyframes)
 GROUP_RULE_FUNCS(Media)
 BASIC_RULE_FUNCS(Namespace)
 BASIC_RULE_FUNCS(Page)
 GROUP_RULE_FUNCS(Supports)
 GROUP_RULE_FUNCS(Document)
 #undef GROUP_RULE_FUNCS
 #undef BASIC_RULE_FUNCS
+#undef BASIC_RULE_FUNCS_WITHOUT_GETTER
 SERVO_BINDING_FUNC(Servo_CssRules_GetFontFaceRuleAt, nsCSSFontFaceRule*,
                    ServoCssRulesBorrowed rules, uint32_t index)
 SERVO_BINDING_FUNC(Servo_CssRules_GetCounterStyleRuleAt, nsCSSCounterStyleRule*,
                    ServoCssRulesBorrowed rules, uint32_t index)
 SERVO_BINDING_FUNC(Servo_StyleRule_GetStyle, RawServoDeclarationBlockStrong,
                    RawServoStyleRuleBorrowed rule)
 SERVO_BINDING_FUNC(Servo_StyleRule_SetStyle, void,
                    RawServoStyleRuleBorrowed rule,
                    RawServoDeclarationBlockBorrowed declarations)
 SERVO_BINDING_FUNC(Servo_StyleRule_GetSelectorText, void,
                    RawServoStyleRuleBorrowed rule, nsAString* result)
+SERVO_BINDING_FUNC(Servo_Keyframe_GetKeyText, void,
+                   RawServoKeyframeBorrowed keyframe, nsAString* result)
+// Returns whether it successfully changes the key text.
+SERVO_BINDING_FUNC(Servo_Keyframe_SetKeyText, bool,
+                   RawServoKeyframeBorrowed keyframe, const nsACString* text)
+SERVO_BINDING_FUNC(Servo_Keyframe_GetStyle, RawServoDeclarationBlockStrong,
+                   RawServoKeyframeBorrowed keyframe)
+SERVO_BINDING_FUNC(Servo_Keyframe_SetStyle, void,
+                   RawServoKeyframeBorrowed keyframe,
+                   RawServoDeclarationBlockBorrowed declarations)
+SERVO_BINDING_FUNC(Servo_KeyframesRule_GetName, nsIAtom*,
+                   RawServoKeyframesRuleBorrowed rule)
+// This method takes an addrefed nsIAtom.
+SERVO_BINDING_FUNC(Servo_KeyframesRule_SetName, void,
+                   RawServoKeyframesRuleBorrowed rule, nsIAtom* name)
+SERVO_BINDING_FUNC(Servo_KeyframesRule_GetCount, uint32_t,
+                   RawServoKeyframesRuleBorrowed rule)
+SERVO_BINDING_FUNC(Servo_KeyframesRule_GetKeyframe, RawServoKeyframeStrong,
+                   RawServoKeyframesRuleBorrowed rule, uint32_t index)
+// Returns the index of the rule, max value of uint32_t if nothing found.
+SERVO_BINDING_FUNC(Servo_KeyframesRule_FindRule, uint32_t,
+                   RawServoKeyframesRuleBorrowed rule, const nsACString* key)
+// Returns whether it successfully appends the rule.
+SERVO_BINDING_FUNC(Servo_KeyframesRule_AppendRule, bool,
+                   RawServoKeyframesRuleBorrowed rule,
+                   RawServoStyleSheetBorrowed sheet, const nsACString* css)
+SERVO_BINDING_FUNC(Servo_KeyframesRule_DeleteRule, void,
+                   RawServoKeyframesRuleBorrowed rule, uint32_t index)
 SERVO_BINDING_FUNC(Servo_MediaRule_GetMedia, RawServoMediaListStrong,
                    RawServoMediaRuleBorrowed rule)
 SERVO_BINDING_FUNC(Servo_NamespaceRule_GetPrefix, nsIAtom*,
                    RawServoNamespaceRuleBorrowed rule)
 SERVO_BINDING_FUNC(Servo_NamespaceRule_GetURI, nsIAtom*,
                    RawServoNamespaceRuleBorrowed rule)
 SERVO_BINDING_FUNC(Servo_PageRule_GetStyle, RawServoDeclarationBlockStrong,
                    RawServoPageRuleBorrowed rule)
--- a/layout/style/ServoCSSRuleList.cpp
+++ b/layout/style/ServoCSSRuleList.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* representation of CSSRuleList for stylo */
 
 #include "mozilla/ServoCSSRuleList.h"
 
 #include "mozilla/ServoBindings.h"
 #include "mozilla/ServoDocumentRule.h"
+#include "mozilla/ServoKeyframesRule.h"
 #include "mozilla/ServoMediaRule.h"
 #include "mozilla/ServoNamespaceRule.h"
 #include "mozilla/ServoPageRule.h"
 #include "mozilla/ServoStyleRule.h"
 #include "mozilla/ServoSupportsRule.h"
 #include "nsCSSCounterStyleRule.h"
 #include "nsCSSFontFaceRule.h"
 
@@ -92,16 +93,17 @@ ServoCSSRuleList::GetRule(uint32_t aInde
         RefPtr<RawServo##name_##Rule> rule =                                \
           Servo_CssRules_Get##name_##RuleAt(                                \
               mRawRules, aIndex, &line, &column                             \
           ).Consume();                                                      \
         ruleObj = new Servo##name_##Rule(rule.forget(), line, column);      \
         break;                                                              \
       }
       CASE_RULE(STYLE, Style)
+      CASE_RULE(KEYFRAMES, Keyframes)
       CASE_RULE(MEDIA, Media)
       CASE_RULE(NAMESPACE, Namespace)
       CASE_RULE(PAGE, Page)
       CASE_RULE(SUPPORTS, Supports)
       CASE_RULE(DOCUMENT, Document)
 #undef CASE_RULE
       // For @font-face and @counter-style rules, the function returns
       // a borrowed Gecko rule object directly, so we don't need to
@@ -110,18 +112,19 @@ ServoCSSRuleList::GetRule(uint32_t aInde
       case nsIDOMCSSRule::FONT_FACE_RULE: {
         ruleObj = Servo_CssRules_GetFontFaceRuleAt(mRawRules, aIndex);
         break;
       }
       case nsIDOMCSSRule::COUNTER_STYLE_RULE: {
         ruleObj = Servo_CssRules_GetCounterStyleRuleAt(mRawRules, aIndex);
         break;
       }
-      case nsIDOMCSSRule::KEYFRAMES_RULE:
-        // XXX create corresponding rules
+      case nsIDOMCSSRule::KEYFRAME_RULE:
+        MOZ_ASSERT_UNREACHABLE("keyframe rule cannot be here");
+        return nullptr;
       default:
         NS_WARNING("stylo: not implemented yet");
         return nullptr;
     }
     ruleObj->SetStyleSheet(mStyleSheet);
     ruleObj->SetParentRule(mParentRule);
     rule = CastToUint(ruleObj.forget().take());
     mRules[aIndex] = rule;
new file mode 100644
--- /dev/null
+++ b/layout/style/ServoKeyframeRule.cpp
@@ -0,0 +1,216 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ServoKeyframeRule.h"
+
+#include "nsDOMCSSDeclaration.h"
+#include "mozAutoDocUpdate.h"
+
+namespace mozilla {
+
+// -------------------------------------------
+// ServoKeyframeDeclaration
+//
+
+class ServoKeyframeDeclaration : public nsDOMCSSDeclaration
+{
+public:
+  explicit ServoKeyframeDeclaration(ServoKeyframeRule* aRule)
+    : mRule(aRule)
+  {
+    mDecls = new ServoDeclarationBlock(
+      Servo_Keyframe_GetStyle(aRule->Raw()).Consume());
+  }
+
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(
+    ServoKeyframeDeclaration, nsICSSDeclaration)
+
+  NS_IMETHOD GetParentRule(nsIDOMCSSRule** aParent) final
+  {
+    *aParent = mRule;
+    return NS_OK;
+  }
+
+  void DropReference() { mRule = nullptr; }
+
+  DeclarationBlock* GetCSSDeclaration(Operation aOperation) final
+  {
+    return mDecls;
+  }
+  nsresult SetCSSDeclaration(DeclarationBlock* aDecls) final
+  {
+    if (!mRule) {
+      return NS_OK;
+    }
+    mRule->UpdateRule([this, aDecls]() {
+      if (mDecls != aDecls) {
+        mDecls->SetOwningRule(nullptr);
+        mDecls = aDecls->AsServo();
+        mDecls->SetOwningRule(mRule);
+        Servo_Keyframe_SetStyle(mRule->Raw(), mDecls->Raw());
+      }
+    });
+    return NS_OK;
+  }
+  void GetCSSParsingEnvironment(CSSParsingEnvironment& aCSSParseEnv) final
+  {
+    MOZ_ASSERT_UNREACHABLE("GetCSSParsingEnvironment "
+                           "shouldn't be calling for a Servo rule");
+    GetCSSParsingEnvironmentForRule(mRule, aCSSParseEnv);
+  }
+  ServoCSSParsingEnvironment GetServoCSSParsingEnvironment() const final
+  {
+    return GetServoCSSParsingEnvironmentForRule(mRule);
+  }
+  nsIDocument* DocToUpdate() final { return nullptr; }
+
+  nsINode* GetParentObject() final
+  {
+    return mRule ? mRule->GetDocument() : nullptr;
+  }
+
+  size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+  {
+    size_t n = aMallocSizeOf(this);
+    // TODO we may want to add size of mDecls as well
+    return n;
+  }
+
+private:
+  virtual ~ServoKeyframeDeclaration() {}
+
+  ServoKeyframeRule* mRule;
+  RefPtr<ServoDeclarationBlock> mDecls;
+};
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ServoKeyframeDeclaration)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ServoKeyframeDeclaration)
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(ServoKeyframeDeclaration)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ServoKeyframeDeclaration)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+NS_INTERFACE_MAP_END_INHERITING(nsDOMCSSDeclaration)
+
+// -------------------------------------------
+// ServoKeyframeRule
+//
+
+ServoKeyframeRule::~ServoKeyframeRule()
+{
+}
+
+NS_IMPL_ADDREF_INHERITED(ServoKeyframeRule, dom::CSSKeyframeRule)
+NS_IMPL_RELEASE_INHERITED(ServoKeyframeRule, dom::CSSKeyframeRule)
+
+// QueryInterface implementation for nsCSSKeyframeRule
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ServoKeyframeRule)
+NS_INTERFACE_MAP_END_INHERITING(dom::CSSKeyframeRule)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(ServoKeyframeRule)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(ServoKeyframeRule,
+                                                dom::CSSKeyframeRule)
+  if (tmp->mDeclaration) {
+    tmp->mDeclaration->DropReference();
+    tmp->mDeclaration = nullptr;
+  }
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ServoKeyframeRule,
+                                                  dom::CSSKeyframeRule)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDeclaration)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+bool
+ServoKeyframeRule::IsCCLeaf() const
+{
+  return Rule::IsCCLeaf() && !mDeclaration;
+}
+
+/* virtual */ already_AddRefed<css::Rule>
+ServoKeyframeRule::Clone() const
+{
+  // Rule::Clone is only used when CSSStyleSheetInner is cloned in
+  // preparation of being mutated. However, ServoStyleSheet never clones
+  // anything, so this method should never be called.
+  MOZ_ASSERT_UNREACHABLE("Shouldn't be cloning ServoKeyframeRule");
+  return nullptr;
+}
+
+#ifdef DEBUG
+/* virtual */ void
+ServoKeyframeRule::List(FILE* out, int32_t aIndent) const
+{
+  nsAutoCString str;
+  for (int32_t i = 0; i < aIndent; i++) {
+    str.AppendLiteral("  ");
+  }
+  Servo_Keyframe_Debug(mRaw, &str);
+  fprintf_stderr(out, "%s\n", str.get());
+}
+#endif
+
+template<typename Func>
+void
+ServoKeyframeRule::UpdateRule(Func aCallback)
+{
+  nsIDocument* doc = GetDocument();
+  MOZ_AUTO_DOC_UPDATE(doc, UPDATE_STYLE, true);
+
+  aCallback();
+
+  if (StyleSheet* sheet = GetStyleSheet()) {
+    // FIXME sheet->AsGecko()->SetModifiedByChildRule();
+    if (doc) {
+      doc->StyleRuleChanged(sheet, this);
+    }
+  }
+}
+
+NS_IMETHODIMP
+ServoKeyframeRule::GetKeyText(nsAString& aKeyText)
+{
+  Servo_Keyframe_GetKeyText(mRaw, &aKeyText);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+ServoKeyframeRule::SetKeyText(const nsAString& aKeyText)
+{
+  NS_ConvertUTF16toUTF8 keyText(aKeyText);
+  UpdateRule([this, &keyText]() {
+    Servo_Keyframe_SetKeyText(mRaw, &keyText);
+  });
+  return NS_OK;
+}
+
+void
+ServoKeyframeRule::GetCssTextImpl(nsAString& aCssText) const
+{
+  Servo_Keyframe_GetCssText(mRaw, &aCssText);
+}
+
+nsICSSDeclaration*
+ServoKeyframeRule::Style()
+{
+  if (!mDeclaration) {
+    mDeclaration = new ServoKeyframeDeclaration(this);
+  }
+  return mDeclaration;
+}
+
+size_t
+ServoKeyframeRule::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  size_t n = aMallocSizeOf(this);
+  if (mDeclaration) {
+    n += mDeclaration->SizeOfIncludingThis(aMallocSizeOf);
+  }
+  return n;
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/layout/style/ServoKeyframeRule.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_ServoKeyframeRule_h
+#define mozilla_ServoKeyframeRule_h
+
+#include "mozilla/dom/CSSKeyframeRule.h"
+#include "mozilla/ServoBindingTypes.h"
+
+namespace mozilla {
+
+class ServoDeclarationBlock;
+class ServoKeyframeDeclaration;
+
+class ServoKeyframeRule final : public dom::CSSKeyframeRule
+{
+public:
+  explicit ServoKeyframeRule(already_AddRefed<RawServoKeyframe> aRaw)
+    : CSSKeyframeRule(0, 0)
+    , mRaw(aRaw) {}
+
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ServoKeyframeRule,
+                                           dom::CSSKeyframeRule)
+
+  bool IsCCLeaf() const final;
+#ifdef DEBUG
+  void List(FILE* out = stdout, int32_t aIndent = 0) const final;
+#endif
+  already_AddRefed<mozilla::css::Rule> Clone() const final;
+
+  RawServoKeyframe* Raw() const { return mRaw; }
+
+  // nsIDOMCSSKeyframeRule interface
+  NS_IMETHOD GetKeyText(nsAString& aKeyText) final;
+  NS_IMETHOD SetKeyText(const nsAString& aKeyText) final;
+
+  // WebIDL interface
+  void GetCssTextImpl(nsAString& aCssText) const final;
+  nsICSSDeclaration* Style() final;
+
+  size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const final;
+
+private:
+  virtual ~ServoKeyframeRule();
+
+  friend class ServoKeyframeDeclaration;
+
+  template<typename Func>
+  void UpdateRule(Func aCallback);
+
+  RefPtr<RawServoKeyframe> mRaw;
+  // lazily created when needed
+  RefPtr<ServoKeyframeDeclaration> mDeclaration;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ServoKeyframeRule_h
new file mode 100644
--- /dev/null
+++ b/layout/style/ServoKeyframesRule.cpp
@@ -0,0 +1,349 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ServoKeyframesRule.h"
+
+#include "mozAutoDocUpdate.h"
+#include "mozilla/ServoBindings.h"
+#include "mozilla/ServoKeyframeRule.h"
+
+#include <limits>
+
+namespace mozilla {
+
+// -------------------------------------------
+// ServoKeyframeList
+//
+
+class ServoKeyframeList : public dom::CSSRuleList
+{
+public:
+  explicit ServoKeyframeList(already_AddRefed<RawServoKeyframesRule> aRawRule)
+    : mRawRule(aRawRule)
+  {
+    mRules.SetCount(Servo_KeyframesRule_GetCount(mRawRule));
+  }
+
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ServoKeyframeList, dom::CSSRuleList)
+
+  void SetParentRule(ServoKeyframesRule* aParentRule)
+  {
+    mParentRule = aParentRule;
+    for (css::Rule* rule : mRules) {
+      if (rule) {
+        rule->SetParentRule(aParentRule);
+      }
+    }
+  }
+  void SetStyleSheet(ServoStyleSheet* aSheet)
+  {
+    mStyleSheet = aSheet;
+    for (css::Rule* rule : mRules) {
+      if (rule) {
+        rule->SetStyleSheet(aSheet);
+      }
+    }
+  }
+
+  ServoStyleSheet* GetParentObject() final { return mStyleSheet; }
+
+  ServoKeyframeRule* GetRule(uint32_t aIndex) {
+    if (!mRules[aIndex]) {
+      ServoKeyframeRule* rule = new ServoKeyframeRule(
+        Servo_KeyframesRule_GetKeyframe(mRawRule, aIndex).Consume());
+      mRules.ReplaceObjectAt(rule, aIndex);
+    }
+    return static_cast<ServoKeyframeRule*>(mRules[aIndex]);
+  }
+
+  ServoKeyframeRule* IndexedGetter(uint32_t aIndex, bool& aFound) final
+  {
+    if (aIndex >= mRules.Length()) {
+      aFound = false;
+      return nullptr;
+    }
+    aFound = true;
+    return GetRule(aIndex);
+  }
+
+  void AppendRule() {
+    mRules.AppendObject(nullptr);
+  }
+  void RemoveRule(uint32_t aIndex) {
+    mRules.RemoveObjectAt(aIndex);
+  }
+
+  uint32_t Length() final { return mRules.Length(); }
+
+  void DropReference()
+  {
+    mStyleSheet = nullptr;
+    mParentRule = nullptr;
+    DropAllRules();
+  }
+
+  size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+  {
+    size_t n = aMallocSizeOf(this);
+    for (const css::Rule* rule : mRules) {
+      n += rule ? rule->SizeOfIncludingThis(aMallocSizeOf) : 0;
+    }
+    return n;
+  }
+
+private:
+  virtual ~ServoKeyframeList() {}
+
+  void DropAllRules()
+  {
+    for (css::Rule* rule : mRules) {
+      if (rule) {
+        rule->SetStyleSheet(nullptr);
+        rule->SetParentRule(nullptr);
+      }
+    }
+    mRules.Clear();
+    mRawRule = nullptr;
+  }
+
+  // may be nullptr when the style sheet drops the reference to us.
+  ServoStyleSheet* mStyleSheet = nullptr;
+  ServoKeyframesRule* mParentRule = nullptr;
+  RefPtr<RawServoKeyframesRule> mRawRule;
+  nsCOMArray<css::Rule> mRules;
+};
+
+// QueryInterface implementation for ServoKeyframeList
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ServoKeyframeList)
+NS_INTERFACE_MAP_END_INHERITING(dom::CSSRuleList)
+
+NS_IMPL_ADDREF_INHERITED(ServoKeyframeList, dom::CSSRuleList)
+NS_IMPL_RELEASE_INHERITED(ServoKeyframeList, dom::CSSRuleList)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(ServoKeyframeList)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ServoKeyframeList)
+  tmp->DropAllRules();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED(dom::CSSRuleList)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ServoKeyframeList,
+                                                  dom::CSSRuleList)
+  for (css::Rule* rule : tmp->mRules) {
+    if (rule) {
+      NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mRules[i]");
+      cb.NoteXPCOMChild(rule);
+    }
+  }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+// -------------------------------------------
+// ServoKeyframesRule
+//
+
+ServoKeyframesRule::ServoKeyframesRule(RefPtr<RawServoKeyframesRule> aRawRule,
+                                       uint32_t aLine, uint32_t aColumn)
+  // Although this class inherits from GroupRule, we don't want to use
+  // it at all, so it is fine to call the constructor for Gecko. We can
+  // make CSSKeyframesRule inherit from Rule directly once we can get
+  // rid of nsCSSKeyframeRule.
+  : dom::CSSKeyframesRule(aLine, aColumn)
+  , mRawRule(Move(aRawRule))
+{
+}
+
+ServoKeyframesRule::~ServoKeyframesRule()
+{
+}
+
+NS_IMPL_ADDREF_INHERITED(ServoKeyframesRule, dom::CSSKeyframesRule)
+NS_IMPL_RELEASE_INHERITED(ServoKeyframesRule, dom::CSSKeyframesRule)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ServoKeyframesRule)
+NS_INTERFACE_MAP_END_INHERITING(dom::CSSKeyframesRule)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(ServoKeyframesRule)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(ServoKeyframesRule,
+                                                dom::CSSKeyframesRule)
+  if (tmp->mKeyframeList) {
+    tmp->mKeyframeList->DropReference();
+    tmp->mKeyframeList = nullptr;
+  }
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ServoKeyframesRule, Rule)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mKeyframeList)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+/* virtual */ bool
+ServoKeyframesRule::IsCCLeaf() const
+{
+  // If we don't have rule list constructed, we are a leaf.
+  return Rule::IsCCLeaf() && !mKeyframeList;
+}
+
+/* virtual */ already_AddRefed<css::Rule>
+ServoKeyframesRule::Clone() const
+{
+  // Rule::Clone is only used when CSSStyleSheetInner is cloned in
+  // preparation of being mutated. However, ServoStyleSheet never clones
+  // anything, so this method should never be called.
+  MOZ_ASSERT_UNREACHABLE("Shouldn't be cloning ServoKeyframesRule");
+  return nullptr;
+}
+
+#ifdef DEBUG
+/* virtual */ void
+ServoKeyframesRule::List(FILE* out, int32_t aIndent) const
+{
+  nsAutoCString str;
+  for (int32_t i = 0; i < aIndent; i++) {
+    str.AppendLiteral("  ");
+  }
+  Servo_KeyframesRule_Debug(mRawRule, &str);
+  fprintf_stderr(out, "%s\n", str.get());
+}
+#endif
+
+/* virtual */ void
+ServoKeyframesRule::SetStyleSheet(StyleSheet* aSheet)
+{
+  if (mKeyframeList) {
+    mKeyframeList->SetStyleSheet(aSheet ? aSheet->AsServo() : nullptr);
+  }
+  dom::CSSKeyframesRule::SetStyleSheet(aSheet);
+}
+
+static const uint32_t kRuleNotFound = std::numeric_limits<uint32_t>::max();
+
+uint32_t
+ServoKeyframesRule::FindRuleIndexForKey(const nsAString& aKey)
+{
+  NS_ConvertUTF16toUTF8 key(aKey);
+  return Servo_KeyframesRule_FindRule(mRawRule, &key);
+}
+
+template<typename Func>
+void
+ServoKeyframesRule::UpdateRule(Func aCallback)
+{
+  nsIDocument* doc = GetDocument();
+  MOZ_AUTO_DOC_UPDATE(doc, UPDATE_STYLE, true);
+
+  aCallback();
+
+  if (StyleSheet* sheet = GetStyleSheet()) {
+    // FIXME sheet->AsGecko()->SetModifiedByChildRule();
+    if (doc) {
+      doc->StyleRuleChanged(sheet, this);
+    }
+  }
+}
+
+NS_IMETHODIMP
+ServoKeyframesRule::GetName(nsAString& aName)
+{
+  nsIAtom* name = Servo_KeyframesRule_GetName(mRawRule);
+  aName = nsDependentAtomString(name);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+ServoKeyframesRule::SetName(const nsAString& aName)
+{
+  nsCOMPtr<nsIAtom> name = NS_Atomize(aName);
+  nsIAtom* oldName = Servo_KeyframesRule_GetName(mRawRule);
+  if (name == oldName) {
+    return NS_OK;
+  }
+
+  UpdateRule([this, &name]() {
+    Servo_KeyframesRule_SetName(mRawRule, name.forget().take());
+  });
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+ServoKeyframesRule::AppendRule(const nsAString& aRule)
+{
+  StyleSheet* sheet = GetStyleSheet();
+  if (!sheet) {
+    // We cannot parse the rule if we don't have a stylesheet.
+    return NS_OK;
+  }
+
+  NS_ConvertUTF16toUTF8 rule(aRule);
+  UpdateRule([this, sheet, &rule]() {
+    if (Servo_KeyframesRule_AppendRule(mRawRule, sheet->AsServo()->RawSheet(),
+                                       &rule)) {
+      if (mKeyframeList) {
+        mKeyframeList->AppendRule();
+      }
+    }
+  });
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+ServoKeyframesRule::DeleteRule(const nsAString& aKey)
+{
+  auto index = FindRuleIndexForKey(aKey);
+  if (index == kRuleNotFound) {
+    return NS_OK;
+  }
+
+  UpdateRule([this, index]() {
+    Servo_KeyframesRule_DeleteRule(mRawRule, index);
+    if (mKeyframeList) {
+      mKeyframeList->RemoveRule(index);
+    }
+  });
+  return NS_OK;
+}
+
+/* virtual */ void
+ServoKeyframesRule::GetCssTextImpl(nsAString& aCssText) const
+{
+  Servo_KeyframesRule_GetCssText(mRawRule, &aCssText);
+}
+
+/* virtual */ dom::CSSRuleList*
+ServoKeyframesRule::CssRules()
+{
+  if (!mKeyframeList) {
+    mKeyframeList = new ServoKeyframeList(do_AddRef(mRawRule));
+    mKeyframeList->SetParentRule(this);
+    if (StyleSheet* sheet = GetStyleSheet()) {
+      mKeyframeList->SetStyleSheet(sheet->AsServo());
+    }
+  }
+  return mKeyframeList;
+}
+
+/* virtual */ dom::CSSKeyframeRule*
+ServoKeyframesRule::FindRule(const nsAString& aKey)
+{
+  auto index = FindRuleIndexForKey(aKey);
+  if (index != kRuleNotFound) {
+    // Construct mKeyframeList but ignore the result.
+    CssRules();
+    return mKeyframeList->GetRule(index);
+  }
+  return nullptr;
+}
+
+/* virtual */ size_t
+ServoKeyframesRule::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  size_t n = aMallocSizeOf(this);
+  n += GroupRule::SizeOfExcludingThis(aMallocSizeOf);
+  if (mKeyframeList) {
+    n += mKeyframeList->SizeOfIncludingThis(aMallocSizeOf);
+  }
+  return n;
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/layout/style/ServoKeyframesRule.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_ServoKeyframesRule_h
+#define mozilla_ServoKeyframesRule_h
+
+#include "mozilla/dom/CSSKeyframesRule.h"
+#include "mozilla/ServoBindingTypes.h"
+
+namespace mozilla {
+
+class ServoKeyframeList;
+
+class ServoKeyframesRule final : public dom::CSSKeyframesRule
+{
+public:
+  ServoKeyframesRule(RefPtr<RawServoKeyframesRule> aRawRule,
+                     uint32_t aLine, uint32_t aColumn);
+
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ServoKeyframesRule,
+                                           dom::CSSKeyframesRule)
+  bool IsCCLeaf() const final;
+
+  already_AddRefed<css::Rule> Clone() const final;
+#ifdef DEBUG
+  void List(FILE* out = stdout, int32_t aIndent = 0) const final;
+#endif
+  void SetStyleSheet(StyleSheet* aSheet) final;
+
+  // nsIDOMCSSKeyframesRule interface
+  NS_IMETHOD GetName(nsAString& aName) final;
+  NS_IMETHOD SetName(const nsAString& aName) final;
+  NS_IMETHOD AppendRule(const nsAString& aRule) final;
+  NS_IMETHOD DeleteRule(const nsAString& aKey) final;
+  using nsIDOMCSSKeyframesRule::FindRule;
+
+  // WebIDL interface
+  void GetCssTextImpl(nsAString& aCssText) const final;
+  dom::CSSRuleList* CssRules() final;
+  dom::CSSKeyframeRule* FindRule(const nsAString& aKey) final;
+
+  size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const final;
+
+private:
+  uint32_t FindRuleIndexForKey(const nsAString& aKey);
+
+  template<typename Func>
+  void UpdateRule(Func aCallback);
+
+  virtual ~ServoKeyframesRule();
+
+  RefPtr<RawServoKeyframesRule> mRawRule;
+  RefPtr<ServoKeyframeList> mKeyframeList; // lazily constructed
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ServoKeyframesRule_h
--- a/layout/style/moz.build
+++ b/layout/style/moz.build
@@ -102,16 +102,18 @@ EXPORTS.mozilla += [
     'ServoBindingList.h',
     'ServoBindings.h',
     'ServoBindingTypes.h',
     'ServoCSSRuleList.h',
     'ServoDeclarationBlock.h',
     'ServoDocumentRule.h',
     'ServoElementSnapshot.h',
     'ServoElementSnapshotTable.h',
+    'ServoKeyframeRule.h',
+    'ServoKeyframesRule.h',
     'ServoMediaList.h',
     'ServoMediaRule.h',
     'ServoNamespaceRule.h',
     'ServoPageRule.h',
     'ServoPropPrefList.h',
     'ServoSpecifiedValues.h',
     'ServoStyleRule.h',
     'ServoStyleSet.h',
@@ -234,16 +236,18 @@ UNIFIED_SOURCES += [
     'PreloadedStyleSheet.cpp',
     'RuleNodeCacheConditions.cpp',
     'RuleProcessorCache.cpp',
     'ServoBindings.cpp',
     'ServoCSSRuleList.cpp',
     'ServoDeclarationBlock.cpp',
     'ServoDocumentRule.cpp',
     'ServoElementSnapshot.cpp',
+    'ServoKeyframeRule.cpp',
+    'ServoKeyframesRule.cpp',
     'ServoMediaList.cpp',
     'ServoMediaRule.cpp',
     'ServoNamespaceRule.cpp',
     'ServoPageRule.cpp',
     'ServoSpecifiedValues.cpp',
     'ServoStyleRule.cpp',
     'ServoStyleSet.cpp',
     'ServoStyleSheet.cpp',