Bug 1471814 - Add a preference for implicit keyframes; r?hiro r?bz
authorBrian Birtles <birtles@gmail.com>
Wed, 11 Jul 2018 15:40:37 +0900
changeset 1562547 3c0890d0a5126fb0c07181ff5316801702aec52f
parent 1562546 b030e4af9610e6341d681dada6153d2541b74be3
child 1562548 5e43e88b7427e72d2f989f9c61d47745cdfaa5f1
push id283705
push userbbirtles@mozilla.com
push dateWed, 11 Jul 2018 07:11:44 +0000
treeherdertry@fa47e595ff31 [default view] [failures only]
reviewershiro, bz
bugs1471814
milestone63.0a1
Bug 1471814 - Add a preference for implicit keyframes; r?hiro r?bz This preference controls whether or not authors are allowed to specify animations without a 0% or 100% keyframe. We intend to ship this soon but this preference acts as a safeguard in case we discover we need to disable it. This feature is very convenient and commonly used so this patch ensures it is always enabled for Chrome content.
dom/animation/KeyframeUtils.cpp
dom/animation/test/crashtests/crashtests.list
dom/animation/test/mochitest.ini
dom/animation/test/mozilla/file_disable_animations_api_core.html
dom/animation/test/mozilla/file_disable_animations_api_implicit_keyframes.html
dom/animation/test/mozilla/test_disable_animations_api_implicit_keyframes.html
dom/base/nsContentUtils.cpp
dom/base/nsContentUtils.h
dom/base/nsDocument.cpp
dom/base/nsDocument.h
layout/reftests/web-animations/reftest.list
layout/style/crashtests/crashtests.list
layout/style/test/mochitest.ini
modules/libpref/init/all.js
testing/web-platform/meta/css/css-animations/__dir__.ini
testing/web-platform/meta/web-animations/__dir__.ini
--- a/dom/animation/KeyframeUtils.cpp
+++ b/dom/animation/KeyframeUtils.cpp
@@ -18,21 +18,21 @@
 #include "mozilla/dom/BaseKeyframeTypesBinding.h" // For FastBaseKeyframe etc.
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/KeyframeEffectBinding.h"
 #include "mozilla/dom/KeyframeEffect.h" // For PropertyValuesPair etc.
 #include "mozilla/dom/Nullable.h"
 #include "jsapi.h" // For ForOfIterator etc.
 #include "nsClassHashtable.h"
 #include "nsContentUtils.h" // For GetContextForContent, and
-                            // AnimationsAPICoreEnabled
+                            // AnimationsAPIImplicitKeyframesEnabled
 #include "nsCSSPropertyIDSet.h"
 #include "nsCSSProps.h"
 #include "nsCSSPseudoElements.h" // For CSSPseudoElementType
-#include "nsDocument.h" // For nsDocument::IsWebAnimationsEnabled
+#include "nsDocument.h" // For nsDocument::AreWebAnimationsImplicitKeyframesEnabled
 #include "nsIScriptError.h"
 #include "nsTArray.h"
 #include <algorithm> // For std::stable_sort, std::min
 
 using mozilla::dom::Nullable;
 
 namespace mozilla {
 
@@ -201,17 +201,17 @@ BuildSegmentsFromValueEntries(nsTArray<K
 static void
 GetKeyframeListFromPropertyIndexedKeyframe(JSContext* aCx,
                                            nsIDocument* aDocument,
                                            JS::Handle<JS::Value> aValue,
                                            nsTArray<Keyframe>& aResult,
                                            ErrorResult& aRv);
 
 static bool
-RequiresAdditiveAnimation(const nsTArray<Keyframe>& aKeyframes,
+HasImplicitKeyframeValues(const nsTArray<Keyframe>& aKeyframes,
                           nsIDocument* aDocument);
 
 static void
 DistributeRange(const Range<Keyframe>& aRange);
 
 // ------------------------------------------------------------------
 //
 // Public API
@@ -251,18 +251,18 @@ KeyframeUtils::GetKeyframesFromObject(JS
   }
 
   if (aRv.Failed()) {
     MOZ_ASSERT(keyframes.IsEmpty(),
                "Should not set any keyframes when there is an error");
     return keyframes;
   }
 
-  if (!nsDocument::IsWebAnimationsEnabled(aCx, nullptr) &&
-      RequiresAdditiveAnimation(keyframes, aDocument)) {
+  if (!nsDocument::AreWebAnimationsImplicitKeyframesEnabled(aCx, nullptr) &&
+      HasImplicitKeyframeValues(keyframes, aDocument)) {
     keyframes.Clear();
     aRv.Throw(NS_ERROR_DOM_ANIM_MISSING_PROPS_ERR);
   }
 
   return keyframes;
 }
 
 /* static */ void
@@ -776,28 +776,27 @@ AppendFinalSegment(AnimationProperty* aA
   segment->mFromValue      = aLastEntry.mValue;
   segment->mFromComposite  = aLastEntry.mComposite;
   segment->mToKey          = 1.0f;
   segment->mTimingFunction = aLastEntry.mTimingFunction;
 }
 
 // Returns a newly created AnimationProperty if one was created to fill-in the
 // missing keyframe, nullptr otherwise (if we decided not to fill the keyframe
-// becase we don't support additive animation).
+// becase we don't support implicit keyframes).
 static AnimationProperty*
 HandleMissingInitialKeyframe(nsTArray<AnimationProperty>& aResult,
                              const KeyframeValueEntry& aEntry)
 {
   MOZ_ASSERT(aEntry.mOffset != 0.0f,
              "The offset of the entry should not be 0.0");
 
-  // If the preference of the core Web Animations API is not enabled, don't fill
-  // in the missing keyframe since the missing keyframe requires support for
-  // additive animation which is guarded by this pref.
-  if (!nsContentUtils::AnimationsAPICoreEnabled()) {
+  // If the preference for implicit keyframes is not enabled, don't fill in the
+  // missing keyframe.
+  if (!nsContentUtils::AnimationsAPIImplicitKeyframesEnabled()) {
     return nullptr;
   }
 
   AnimationProperty* result = aResult.AppendElement();
   result->mProperty = aEntry.mProperty;
 
   AppendInitialSegment(result, aEntry);
 
@@ -807,20 +806,19 @@ HandleMissingInitialKeyframe(nsTArray<An
 static void
 HandleMissingFinalKeyframe(nsTArray<AnimationProperty>& aResult,
                            const KeyframeValueEntry& aEntry,
                            AnimationProperty* aCurrentAnimationProperty)
 {
   MOZ_ASSERT(aEntry.mOffset != 1.0f,
              "The offset of the entry should not be 1.0");
 
-  // If the preference of the core Web Animations API is not enabled, don't fill
-  // in the missing keyframe since the missing keyframe requires support for
-  // additive animation which is guarded by this pref.
-  if (!nsContentUtils::AnimationsAPICoreEnabled()) {
+  // If the preference for implicit keyframes is not enabled, don't fill
+  // in the missing keyframe.
+  if (!nsContentUtils::AnimationsAPIImplicitKeyframesEnabled()) {
     // If we have already appended a new entry for the property so we have to
     // remove it.
     if (aCurrentAnimationProperty) {
       aResult.RemoveLastElement();
     }
     return;
   }
 
@@ -1050,19 +1048,19 @@ GetKeyframeListFromPropertyIndexedKeyfra
   for (const PropertyValuesPair& pair : propertyValuesPairs) {
     size_t count = pair.mValues.Length();
     if (count == 0) {
       // No animation values for this property.
       continue;
     }
 
     // If we only have one value, we should animate from the underlying value
-    // using additive animation--however, we don't support additive animation
-    // when the core animation API pref is switched off.
-    if (!nsContentUtils::AnimationsAPICoreEnabled() && count == 1) {
+    // but not if the pref for supporting implicit keyframes is disabled.
+    if (!nsContentUtils::AnimationsAPIImplicitKeyframesEnabled() &&
+        count == 1) {
       aRv.Throw(NS_ERROR_DOM_ANIM_MISSING_PROPS_ERR);
       return;
     }
 
     size_t n = pair.mValues.Length() - 1;
     size_t i = 0;
 
     for (const nsString& stringValue : pair.mValues) {
@@ -1211,38 +1209,37 @@ GetKeyframeListFromPropertyIndexedKeyfra
       }
     }
   }
 }
 
 /**
  * Returns true if the supplied set of keyframes has keyframe values for
  * any property for which it does not also supply a value for the 0% and 100%
- * offsets. In this case we are supposed to synthesize an additive zero value
- * but since we don't support additive animation yet we can't support this
- * case. We try to detect that here so we can throw an exception. The check is
- * not entirely accurate but should detect most common cases.
+ * offsets. The check is not entirely accurate but should detect most common
+ * cases.
  *
  * @param aKeyframes The set of keyframes to analyze.
  * @param aDocument The document to use when parsing keyframes so we can
  *   try to detect where we have an invalid value at 0%/100%.
  */
 static bool
-RequiresAdditiveAnimation(const nsTArray<Keyframe>& aKeyframes,
+HasImplicitKeyframeValues(const nsTArray<Keyframe>& aKeyframes,
                           nsIDocument* aDocument)
 {
   // We are looking to see if that every property referenced in |aKeyframes|
   // has a valid property at offset 0.0 and 1.0. The check as to whether a
   // property is valid or not, however, is not precise. We only check if the
   // property can be parsed, NOT whether it can also be converted to a
   // StyleAnimationValue since doing that requires a target element bound to
   // a document which we might not always have at the point where we want to
   // perform this check.
   //
-  // This is only a temporary measure until we implement additive animation.
+  // This is only a temporary measure until we ship implicit keyframes and
+  // remove the corresponding pref.
   // So as long as this check catches most cases, and we don't do anything
   // horrible in one of the cases we can't detect, it should be sufficient.
 
   nsCSSPropertyIDSet properties;              // All properties encountered.
   nsCSSPropertyIDSet propertiesWithFromValue; // Those with a defined 0% value.
   nsCSSPropertyIDSet propertiesWithToValue;   // Those with a defined 100% value.
 
   auto addToPropertySets = [&](nsCSSPropertyID aProperty, double aOffset) {
--- a/dom/animation/test/crashtests/crashtests.list
+++ b/dom/animation/test/crashtests/crashtests.list
@@ -10,35 +10,36 @@ pref(dom.animations-api.core.enabled,tru
 pref(dom.animations-api.core.enabled,true) load 1272475-2.html
 pref(dom.animations-api.core.enabled,true) load 1278485-1.html
 pref(dom.animations-api.core.enabled,true) pref(dom.animations-api.timelines.enabled,true) load 1277272-1.html
 pref(dom.animations-api.core.enabled,true) load 1282691-1.html
 pref(dom.animations-api.core.enabled,true) load 1291413-1.html
 pref(dom.animations-api.core.enabled,true) load 1291413-2.html
 pref(dom.animations-api.core.enabled,true) load 1304886-1.html
 pref(dom.animations-api.core.enabled,true) load 1309198-1.html
-pref(dom.animations-api.core.enabled,true) load 1322382-1.html
-pref(dom.animations-api.core.enabled,true) load 1322291-1.html
+pref(dom.animations-api.implicit-keyframes.enabled,true) load 1322382-1.html
+pref(dom.animations-api.core.enabled,true) pref(dom.animations-api.implicit-keyframes.enabled,true) load 1322291-1.html
 pref(dom.animations-api.core.enabled,true) load 1322291-2.html
-pref(dom.animations-api.core.enabled,true) load 1323114-1.html
+pref(dom.animations-api.core.enabled,true) pref(dom.animations-api.implicit-keyframes.enabled,true) load 1323114-1.html
 pref(dom.animations-api.core.enabled,true) load 1323114-2.html
-pref(dom.animations-api.core.enabled,true) load 1323119-1.html
+pref(dom.animations-api.implicit-keyframes.enabled,true) load 1323119-1.html
 pref(dom.animations-api.core.enabled,true) load 1324554-1.html
-pref(dom.animations-api.core.enabled,true) load 1325193-1.html
-pref(dom.animations-api.core.enabled,true) load 1330190-1.html
-pref(dom.animations-api.core.enabled,true) load 1330190-2.html
-pref(dom.animations-api.core.enabled,true) load 1330513-1.html
+pref(dom.animations-api.core.enabled,true) pref(dom.animations-api.implicit-keyframes.enabled,true) load 1325193-1.html
+pref(dom.animations-api.implicit-keyframes.enabled,true) load 1330190-1.html
+pref(dom.animations-api.core.enabled,true) pref(dom.animations-api.implicit-keyframes.enabled,true) load 1330190-2.html
+pref(dom.animations-api.core.enabled,true) pref(dom.animations-api.implicit-keyframes.enabled,true) load 1330513-1.html
 pref(dom.animations-api.core.enabled,true) pref(dom.animations-api.timelines.enabled,true) load 1333539-1.html
 pref(dom.animations-api.core.enabled,true) pref(dom.animations-api.timelines.enabled,true) load 1333539-2.html
 pref(dom.animations-api.core.enabled,true) load 1334582-1.html
 pref(dom.animations-api.core.enabled,true) load 1334582-2.html
 pref(dom.animations-api.core.enabled,true) load 1334583-1.html
 pref(dom.animations-api.core.enabled,true) load 1335998-1.html
 pref(dom.animations-api.core.enabled,true) load 1343589-1.html
 pref(dom.animations-api.core.enabled,true) load 1359658-1.html
-pref(dom.animations-api.core.enabled,true) load 1373712-1.html
-pref(dom.animations-api.core.enabled,true) load 1379606-1.html
-pref(dom.animations-api.core.enabled,true) load 1393605-1.html
+pref(dom.animations-api.implicit-keyframes.enabled,true) load 1373712-1.html
+pref(dom.animations-api.implicit-keyframes.enabled,true) load 1379606-1.html
+load 1393605-1.html
 load 1400022-1.html
-pref(dom.animations-api.core.enabled,true) load 1401809.html
-pref(dom.animations-api.core.enabled,true) pref(dom.animations-api.timelines.enabled,true) load 1411318-1.html
-load 1468294-1.html
-load 1467277-1.html
+pref(dom.animations-api.core.enabled,true) pref(dom.animations-api.implicit-keyframes.enabled,true) load 1401809.html
+pref(dom.animations-api.core.enabled,true)
+pref(dom.animations-api.timelines.enabled,true) pref(dom.animations-api.implicit-keyframes.enabled,true) load 1411318-1.html
+pref(dom.animations-api.implicit-keyframes.enabled,true) load 1468294-1.html
+pref(dom.animations-api.implicit-keyframes.enabled,true) load 1467277-1.html
--- a/dom/animation/test/mochitest.ini
+++ b/dom/animation/test/mochitest.ini
@@ -1,19 +1,21 @@
 [DEFAULT]
 prefs =
   dom.animations-api.core.enabled=true
+  dom.animations-api.implicit-keyframes.enabled=true
   dom.animations-api.timelines.enabled=true
 # Support files for chrome tests that we want to load over HTTP need
 # to go in here, not chrome.ini.
 support-files =
   chrome/file_animate_xrays.html
   mozilla/xhr_doc.html
   mozilla/file_deferred_start.html
   mozilla/file_disable_animations_api_core.html
+  mozilla/file_disable_animations_api_implicit_keyframes.html
   mozilla/file_disable_animations_api_timelines.html
   mozilla/file_discrete_animations.html
   mozilla/file_restyles.html
   mozilla/file_transition_finish_on_compositor.html
   ../../../layout/style/test/property_database.js
   testcommon.js
   !/dom/events/test/event_leak_utils.js
 
@@ -35,16 +37,17 @@ support-files =
 [document-timeline/test_document-timeline.html]
 skip-if = (verify && !debug && (os == 'mac'))
 [document-timeline/test_request_animation_frame.html]
 [mozilla/test_cascade.html]
 [mozilla/test_cubic_bezier_limits.html]
 [mozilla/test_deferred_start.html]
 skip-if = (toolkit == 'android' && debug) || (os == 'win' && bits == 64) # Bug 1363957
 [mozilla/test_disable_animations_api_core.html]
+[mozilla/test_disable_animations_api_implicit_keyframes.html]
 [mozilla/test_disable_animations_api_timelines.html]
 [mozilla/test_disabled_properties.html]
 [mozilla/test_discrete_animations.html]
 [mozilla/test_distance_of_basic_shape.html]
 [mozilla/test_distance_of_filter.html]
 [mozilla/test_distance_of_transform.html]
 [mozilla/test_document_timeline_origin_time_range.html]
 [mozilla/test_hide_and_show.html]
--- a/dom/animation/test/mozilla/file_disable_animations_api_core.html
+++ b/dom/animation/test/mozilla/file_disable_animations_api_core.html
@@ -20,43 +20,11 @@ test(function(t) {
   // check that style value is not affected by iterationComposite.
   anim.currentTime = 200 * MS_PER_SEC;
   assert_equals(getComputedStyle(div).marginLeft, '0px',
     'Animated style should not be accumulated when the Web Animations API is ' +
     'not enabled even if accumulate is specified in the constructor');
 }, 'iterationComposite should not affect at all if the Web Animations API ' +
    'is not enabled');
 
-// Tests for cases we don't handle and should throw an exception for in case
-// the Web Animation API is disabled.
-var gTests = [
-  { desc: "single Keyframe with no offset",
-    keyframes: [{ left: "100px" }] },
-  { desc: "multiple Keyframes with missing 0% Keyframe",
-    keyframes: [{ left: "100px", offset: 0.25 },
-                { left: "200px", offset: 0.50 },
-                { left: "300px", offset: 1.00 }] },
-  { desc: "multiple Keyframes with missing 100% Keyframe",
-    keyframes: [{ left: "100px", offset: 0.00 },
-                { left: "200px", offset: 0.50 },
-                { left: "300px", offset: 0.75 }] },
-  { desc: "multiple Keyframes with missing properties on first Keyframe",
-    keyframes: [{ left: "100px", offset: 0.0 },
-                { left: "200px", top: "200px", offset: 0.5 },
-                { left: "300px", top: "300px", offset: 1.0 }] },
-  { desc: "multiple Keyframes with missing properties on last Keyframe",
-    keyframes: [{ left: "100px", top: "200px", offset: 0.0 },
-                { left: "200px", top: "200px", offset: 0.5 },
-                { left: "300px", offset: 1.0 }] },
-];
-
-gTests.forEach(function(subtest) {
-  test(function(t) {
-    var div = addDiv(t);
-    assert_throws("NotSupportedError", function() {
-      div.animate(subtest.keyframes, 100 * MS_PER_SEC);
-    });
-  }, "Element.animate() throws with " + subtest.desc);
-});
-
 done();
 </script>
 </body>
copy from dom/animation/test/mozilla/file_disable_animations_api_core.html
copy to dom/animation/test/mozilla/file_disable_animations_api_implicit_keyframes.html
--- a/dom/animation/test/mozilla/file_disable_animations_api_core.html
+++ b/dom/animation/test/mozilla/file_disable_animations_api_implicit_keyframes.html
@@ -1,40 +1,26 @@
 <!doctype html>
 <meta charset=utf-8>
 <script src="../testcommon.js"></script>
 <body>
 <script>
 'use strict';
 
-test(function(t) {
-  var div = addDiv(t);
-  var anim =
-    div.animate({ marginLeft: ['0px', '10px'] },
-                { duration: 100 * MS_PER_SEC,
-                  easing: 'linear',
-                  iterations: 10,
-                  iterationComposite: 'accumulate' });
-  anim.pause();
-
-  // NOTE: We can't check iterationComposite value itself though API since
-  // Animation.effect is also behind the the Web Animations API.  So we just
-  // check that style value is not affected by iterationComposite.
-  anim.currentTime = 200 * MS_PER_SEC;
-  assert_equals(getComputedStyle(div).marginLeft, '0px',
-    'Animated style should not be accumulated when the Web Animations API is ' +
-    'not enabled even if accumulate is specified in the constructor');
-}, 'iterationComposite should not affect at all if the Web Animations API ' +
-   'is not enabled');
-
-// Tests for cases we don't handle and should throw an exception for in case
-// the Web Animation API is disabled.
+// Tests for cases we should throw an exception for if implicit keyframes are
+// disabled.
 var gTests = [
+  { desc: "single Keyframe value",
+    keyframes: { left: "100px" } },
   { desc: "single Keyframe with no offset",
     keyframes: [{ left: "100px" }] },
+  { desc: "single Keyframe with 0% offset",
+    keyframes: [{ left: "100px", offset: 0 }] },
+  { desc: "single Keyframe with 100% offset",
+    keyframes: [{ left: "100px", offset: 1 }] },
   { desc: "multiple Keyframes with missing 0% Keyframe",
     keyframes: [{ left: "100px", offset: 0.25 },
                 { left: "200px", offset: 0.50 },
                 { left: "300px", offset: 1.00 }] },
   { desc: "multiple Keyframes with missing 100% Keyframe",
     keyframes: [{ left: "100px", offset: 0.00 },
                 { left: "200px", offset: 0.50 },
                 { left: "300px", offset: 0.75 }] },
copy from dom/animation/test/mozilla/test_disable_animations_api_core.html
copy to dom/animation/test/mozilla/test_disable_animations_api_implicit_keyframes.html
--- a/dom/animation/test/mozilla/test_disable_animations_api_core.html
+++ b/dom/animation/test/mozilla/test_disable_animations_api_implicit_keyframes.html
@@ -2,13 +2,13 @@
 <meta charset=utf-8>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <div id="log"></div>
 <script>
 'use strict';
 setup({explicit_done: true});
 SpecialPowers.pushPrefEnv(
-  { "set": [["dom.animations-api.core.enabled", false]]},
+  { "set": [["dom.animations-api.implicit-keyframes.enabled", false]]},
   function() {
-    window.open("file_disable_animations_api_core.html");
+    window.open("file_disable_animations_api_implicit_keyframes.html");
   });
 </script>
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -297,16 +297,17 @@ bool nsContentUtils::sIsResourceTimingEn
 bool nsContentUtils::sIsPerformanceNavigationTimingEnabled = false;
 bool nsContentUtils::sIsFormAutofillAutocompleteEnabled = false;
 bool nsContentUtils::sIsShadowDOMEnabled = false;
 bool nsContentUtils::sIsCustomElementsEnabled = false;
 bool nsContentUtils::sSendPerformanceTimingNotifications = false;
 bool nsContentUtils::sUseActivityCursor = false;
 bool nsContentUtils::sAnimationsAPICoreEnabled = false;
 bool nsContentUtils::sAnimationsAPIElementAnimateEnabled = false;
+bool nsContentUtils::sAnimationsAPIImplicitKeyframesEnabled = false;
 bool nsContentUtils::sAnimationsAPITimelinesEnabled = false;
 bool nsContentUtils::sGetBoxQuadsEnabled = false;
 bool nsContentUtils::sSkipCursorMoveForSameValueSet = false;
 bool nsContentUtils::sRequestIdleCallbackEnabled = false;
 bool nsContentUtils::sLowerNetworkPriority = false;
 bool nsContentUtils::sTailingEnabled = false;
 bool nsContentUtils::sShowInputPlaceholderOnFocus = true;
 bool nsContentUtils::sAutoFocusEnabled = true;
@@ -701,16 +702,20 @@ nsContentUtils::Init()
                                "ui.use_activity_cursor", false);
 
   Preferences::AddBoolVarCache(&sAnimationsAPICoreEnabled,
                                "dom.animations-api.core.enabled", false);
 
   Preferences::AddBoolVarCache(&sAnimationsAPIElementAnimateEnabled,
                                "dom.animations-api.element-animate.enabled", false);
 
+  Preferences::AddBoolVarCache(&sAnimationsAPIImplicitKeyframesEnabled,
+                               "dom.animations-api.implicit-keyframes.enabled",
+                               false);
+
   Preferences::AddBoolVarCache(&sAnimationsAPITimelinesEnabled,
                                "dom.animations-api.timelines.enabled",
                                false);
 
   Preferences::AddBoolVarCache(&sGetBoxQuadsEnabled,
                                "layout.css.getBoxQuads.enabled", false);
 
   Preferences::AddBoolVarCache(&sSkipCursorMoveForSameValueSet,
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -2353,16 +2353,25 @@ public:
    * Returns true if the DOM Animations Element.animate() API should be enabled.
    */
   static bool AnimationsAPIElementAnimateEnabled()
   {
     return sAnimationsAPIElementAnimateEnabled;
   }
 
   /**
+   * Returns true if the animations without explicit 0%/100% keyframes should
+   * be allowed by the DOM Animations API.
+   */
+  static bool AnimationsAPIImplicitKeyframesEnabled()
+  {
+    return sAnimationsAPIImplicitKeyframesEnabled;
+  }
+
+  /**
    * Returns true if the timelines component of the DOM Animations API should be
    * enabled.
    */
   static bool AnimationsAPITimelinesEnabled()
   {
     return sAnimationsAPITimelinesEnabled;
   }
 
@@ -3414,16 +3423,17 @@ private:
   static bool sIsFrameTimingPrefEnabled;
   static bool sIsFormAutofillAutocompleteEnabled;
   static bool sIsShadowDOMEnabled;
   static bool sIsCustomElementsEnabled;
   static bool sSendPerformanceTimingNotifications;
   static bool sUseActivityCursor;
   static bool sAnimationsAPICoreEnabled;
   static bool sAnimationsAPIElementAnimateEnabled;
+  static bool sAnimationsAPIImplicitKeyframesEnabled;
   static bool sAnimationsAPITimelinesEnabled;
   static bool sGetBoxQuadsEnabled;
   static bool sSkipCursorMoveForSameValueSet;
   static bool sRequestIdleCallbackEnabled;
   static bool sLowerNetworkPriority;
   static bool sTailingEnabled;
   static bool sShowInputPlaceholderOnFocus;
   static bool sAutoFocusEnabled;
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -3300,16 +3300,27 @@ nsDocument::IsWebAnimationsEnabled(Calle
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   return aCallerType == dom::CallerType::System ||
          nsContentUtils::AnimationsAPICoreEnabled();
 }
 
 bool
+nsDocument::AreWebAnimationsImplicitKeyframesEnabled(JSContext* aCx,
+                                                     JSObject* /*unused*/
+)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  return nsContentUtils::IsSystemCaller(aCx) ||
+         nsContentUtils::AnimationsAPIImplicitKeyframesEnabled();
+}
+
+bool
 nsDocument::AreWebAnimationsTimelinesEnabled(JSContext* aCx,
                                              JSObject* /*unused*/
 )
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   return nsContentUtils::IsSystemCaller(aCx) ||
          nsContentUtils::AnimationsAPITimelinesEnabled();
--- a/dom/base/nsDocument.h
+++ b/dom/base/nsDocument.h
@@ -156,16 +156,18 @@ public:
                                      bool aReset = true,
                                      nsIContentSink* aContentSink = nullptr) override = 0;
 
   virtual void StopDocumentLoad() override;
 
   static bool IsElementAnimateEnabled(JSContext* aCx, JSObject* aObject);
   static bool IsWebAnimationsEnabled(JSContext* aCx, JSObject* aObject);
   static bool IsWebAnimationsEnabled(mozilla::dom::CallerType aCallerType);
+  static bool AreWebAnimationsImplicitKeyframesEnabled(JSContext* aCx,
+                                                       JSObject* aObject);
   static bool AreWebAnimationsTimelinesEnabled(JSContext* aCx,
                                                JSObject* aObject);
 
   virtual void EndUpdate() override;
   virtual void BeginLoad() override;
   virtual void EndLoad() override;
 
   // nsIRadioGroupContainer
--- a/layout/reftests/web-animations/reftest.list
+++ b/layout/reftests/web-animations/reftest.list
@@ -1,24 +1,24 @@
-test-pref(dom.animations-api.core.enabled,true) == 1246046-1.html green-box.html
+== 1246046-1.html green-box.html
 test-pref(dom.animations-api.core.enabled,true) == 1267937-1.html 1267937-ref.html
 test-pref(dom.animations-api.core.enabled,true) == 1298742-1.html 1298742-ref.html
-test-pref(dom.animations-api.core.enabled,true) == 1363639-1.html green-box.html
+== 1363639-1.html green-box.html
 test-pref(dom.animations-api.core.enabled,true) == stacking-context-transform-none-animation-before-appending-element.html stacking-context-animation-ref.html
 test-pref(dom.animations-api.core.enabled,true) == stacking-context-opacity-changing-keyframe.html stacking-context-animation-ref.html
 test-pref(dom.animations-api.core.enabled,true) == stacking-context-opacity-changing-keyframe-in-delay.html stacking-context-animation-ref.html
 test-pref(dom.animations-api.core.enabled,true) == stacking-context-opacity-changing-target.html stacking-context-animation-changing-target-ref.html
 test-pref(dom.animations-api.core.enabled,true) == stacking-context-opacity-changing-target-in-delay.html stacking-context-animation-changing-target-ref.html
 test-pref(dom.animations-api.core.enabled,true) == stacking-context-opacity-changing-effect.html stacking-context-animation-ref.html
-test-pref(dom.animations-api.core.enabled,true) == stacking-context-opacity-losing-css-animation-in-delay.html stacking-context-animation-ref.html
+== stacking-context-opacity-losing-css-animation-in-delay.html stacking-context-animation-ref.html
 test-pref(dom.animations-api.core.enabled,true) == stacking-context-transform-changing-keyframe.html stacking-context-animation-ref.html
 test-pref(dom.animations-api.core.enabled,true) == stacking-context-transform-changing-keyframe-in-delay.html stacking-context-animation-ref.html
 test-pref(dom.animations-api.core.enabled,true) == stacking-context-transform-changing-target.html stacking-context-animation-changing-target-ref.html
 
 test-pref(dom.animations-api.core.enabled,true) == stacking-context-transform-changing-target-in-delay.html stacking-context-animation-changing-target-ref.html
 test-pref(dom.animations-api.core.enabled,true) == stacking-context-transform-changing-effect.html stacking-context-animation-ref.html
 test-pref(dom.animations-api.core.enabled,true) == stacking-context-transform-changing-display-property.html stacking-context-animation-ref.html
-test-pref(dom.animations-api.core.enabled,true) == stacking-context-transform-losing-css-animation-in-delay.html stacking-context-animation-ref.html
+== stacking-context-transform-losing-css-animation-in-delay.html stacking-context-animation-ref.html
 test-pref(dom.animations-api.core.enabled,true) == style-updates-on-iteration-composition-changed-from-accumulate-to-replace.html style-updates-for-iteration-composite-ref.html
 test-pref(dom.animations-api.core.enabled,true) == style-updates-on-iteration-composition-changed-from-replace-to-accumulate.html style-updates-for-iteration-composite-ref.html
 test-pref(dom.animations-api.core.enabled,true) == style-updates-on-current-iteration-changed.html style-updates-for-iteration-composite-ref.html
 test-pref(dom.animations-api.core.enabled,true) == cancel-animation-with-selector-matching.html about:blank
-test-pref(dom.animations-api.core.enabled,true) == child-in-animating-element-display-none.html child-in-animating-element-display-none-ref.html
+== child-in-animating-element-display-none.html child-in-animating-element-display-none-ref.html
--- a/layout/style/crashtests/crashtests.list
+++ b/layout/style/crashtests/crashtests.list
@@ -171,17 +171,17 @@ load 1314531.html
 load 1315889-1.html
 load 1315894-1.html
 load 1319072-1.html
 HTTP load 1320423-1.html
 load 1321357-1.html
 load 1328535-1.html
 load 1331272.html
 HTTP load 1333001-1.html
-pref(dom.animations-api.core.enabled,true) load 1340344.html
+pref(dom.animations-api.implicit-keyframes.enabled,true) load 1340344.html
 load 1342316-1.html
 load 1344210.html
 load 1356601-1.html
 load 1364139-1.html
 load 1370793-1.xhtml
 skip-if(Android) load 1371450-1.html
 load 1374175-1.html
 load 1375812-1.html
@@ -190,21 +190,21 @@ load 1377256-1.html
 load 1378064-1.html
 load 1378814.html
 load 1380800.html
 load link-transition-before.html
 load 1381420-1.html
 load 1381682.html
 load 1382672.html
 load 1382710.html
-pref(dom.animations-api.core.enabled,true) load 1383493-1.html
+pref(dom.animations-api.implicit-keyframes.enabled,true) load 1383493-1.html
 load 1383001.html
 load 1383001-2.html
 load 1383319.html
-pref(dom.animations-api.core.enabled,true) load 1383589-1.html
+pref(dom.animations-api.implicit-keyframes.enabled,true) load 1383589-1.html
 load 1383975.html
 load border-image-visited-link.html
 load content-only-on-link-before.html
 load content-only-on-visited-before.html
 load font-face-truncated-src.html
 load large_border_image_width.html
 load link-transition-before.html
 load long-url-list-stack-overflow.html
@@ -214,37 +214,37 @@ load 1383981-3.html
 load 1384824-1.html
 load 1384824-2.html
 load 1386773.html
 load 1387481-1.html
 load 1387499.html
 load 1388234.html
 load 1391577.html
 load 1393189.html
-load 1393580.html
+pref(dom.animations-api.implicit-keyframes.enabled,true) load 1393580.html
 load 1389645.html
 load 1390726.html
 load 1393791.html
 load 1384232.html
 load 1395725.html
 load 1396041.html
-load 1397363-1.html
+pref(dom.animations-api.implicit-keyframes.enabled,true) load 1397363-1.html
 load 1397439-1.html
 load 1395719.html
 load 1397091.html
 load 1398479.html
 load 1398581.html
 load 1399006.html
 load 1399546.html
 load 1400035.html
 load 1400325.html
 load 1400926.html
 load 1400936-1.html
 load 1400936-2.html
-load 1401256.html
+pref(dom.animations-api.implicit-keyframes.enabled,true) load 1401256.html
 load 1401692.html
 load 1401706.html
 load 1401801.html
 load 1401825.html
 load 1402218-1.html
 load 1402366.html
 load 1402419.html
 load 1402472.html
@@ -270,17 +270,17 @@ load 1410226-2.html
 load 1411008.html
 load 1411143.html
 load 1411478.html
 load 1413288.html
 load 1413361.html
 load 1413670.html
 pref(dom.webcomponents.shadowdom.enabled,true) load 1415353.html
 load 1418059.html
-test-pref(dom.animations-api.core.enabled,true) load 1418867.html
+test-pref(dom.animations-api.implicit-keyframes.enabled,true) load 1418867.html
 pref(dom.webcomponents.shadowdom.enabled,true) load 1419554.html
 load 1426312.html
 load 1439793.html
 load 1409183.html
 pref(dom.webcomponents.shadowdom.enabled,true) load 1445682.html
 pref(dom.webcomponents.shadowdom.enabled,true) load 1449243.html
 load 1450691.html
 pref(dom.webcomponents.shadowdom.enabled,true) load 1453206.html
--- a/layout/style/test/mochitest.ini
+++ b/layout/style/test/mochitest.ini
@@ -1,11 +1,12 @@
 [DEFAULT]
 prefs =
   dom.animations-api.core.enabled=true
+  dom.animations-api.implicit-keyframes.enabled=true
   dom.animations-api.timelines.enabled=true
 support-files =
   animation_utils.js
   ccd-quirks.html
   ccd.sjs
   ccd-standards.html
   chrome/bug418986-2.js
   chrome/match.png
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -3079,16 +3079,24 @@ pref("dom.animations-api.core.enabled", 
 #endif
 
 // Is support for the Element.animate() function (a subset of the Web Animations
 // API) enabled?
 // Note that if dom.animations-api.core.enabled is true, this preference is
 // ignored.
 pref("dom.animations-api.element-animate.enabled", true);
 
+// Is support for animations from the Web Animations API without 0%/100%
+// keyframes enabled?
+#ifdef RELEASE_OR_BETA
+pref("dom.animations-api.implicit-keyframes.enabled", false);
+#else
+pref("dom.animations-api.implicit-keyframes.enabled", true);
+#endif
+
 // Is support for timelines from the Web Animations API enabled?
 #ifdef RELEASE_OR_BETA
 pref("dom.animations-api.timelines.enabled", false);
 #else
 pref("dom.animations-api.timelines.enabled", true);
 #endif
 
 // Pref to throttle offsreen animations
--- a/testing/web-platform/meta/css/css-animations/__dir__.ini
+++ b/testing/web-platform/meta/css/css-animations/__dir__.ini
@@ -1,2 +1,3 @@
 prefs: [dom.animations-api.core.enabled:true,
+        dom.animations-api.implicit-keyframes.enabled:true,
         dom.animations-api.timelines.enabled:true]
--- a/testing/web-platform/meta/web-animations/__dir__.ini
+++ b/testing/web-platform/meta/web-animations/__dir__.ini
@@ -1,3 +1,4 @@
 prefs: [dom.animations-api.core.enabled:true,
+        dom.animations-api.implicit-keyframes.enabled:true,
         dom.animations-api.timelines.enabled:true,
         layout.css.frames-timing.enabled:true]