Bug 1291468 - Part 2: Implement keyframe composite(accumulate). r?birtles
Test cases in file_composite.html are matching pair of tests in
web-platform-tests.
MozReview-Commit-ID: ApuvVCHKQ8Y
--- a/dom/animation/KeyframeEffectReadOnly.cpp
+++ b/dom/animation/KeyframeEffectReadOnly.cpp
@@ -317,20 +317,22 @@ KeyframeEffectReadOnly::CompositeValue(
return aValueToComposite;
case dom::CompositeOperation::Add:
// So far nothing to do since we come to here only in case of missing
// keyframe, that means we have only to use the base value or the
// underlying value as the composited value.
// FIXME: Bug 1311620: Once we implement additive operation, we need to
// calculate it here.
return aUnderlyingValue;
- case dom::CompositeOperation::Accumulate:
- // FIXME: Bug 1291468: Implement accumulate operation.
- MOZ_ASSERT_UNREACHABLE("Not implemented yet");
- break;
+ case dom::CompositeOperation::Accumulate: {
+ StyleAnimationValue result(aValueToComposite);
+ return StyleAnimationValue::Accumulate(aProperty,
+ aUnderlyingValue,
+ Move(result));
+ }
default:
MOZ_ASSERT_UNREACHABLE("Unknown compisite operation type");
break;
}
return StyleAnimationValue();
}
StyleAnimationValue
@@ -996,16 +998,20 @@ KeyframeEffectReadOnly::GetKeyframes(JSC
MOZ_ASSERT(keyframe.mComputedOffset != Keyframe::kComputedOffsetNotSet,
"Invalid computed offset");
keyframeDict.mComputedOffset.Construct(keyframe.mComputedOffset);
if (keyframe.mTimingFunction) {
keyframeDict.mEasing.Truncate();
keyframe.mTimingFunction.ref().AppendToString(keyframeDict.mEasing);
} // else if null, leave easing as its default "linear".
+ if (keyframe.mComposite) {
+ keyframeDict.mComposite.Construct(keyframe.mComposite.value());
+ }
+
JS::Rooted<JS::Value> keyframeJSValue(aCx);
if (!ToJSValue(aCx, keyframeDict, &keyframeJSValue)) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
JS::Rooted<JSObject*> keyframeObject(aCx, &keyframeJSValue.toObject());
for (const PropertyValuePair& propertyValue : keyframe.mPropertyValues) {
--- a/dom/animation/KeyframeUtils.cpp
+++ b/dom/animation/KeyframeUtils.cpp
@@ -808,16 +808,23 @@ ConvertKeyframeSequence(JSContext* aCx,
Keyframe* keyframe = aResult.AppendElement(fallible);
if (!keyframe) {
return false;
}
if (!keyframeDict.mOffset.IsNull()) {
keyframe->mOffset.emplace(keyframeDict.mOffset.Value());
}
+ if (keyframeDict.mComposite.WasPassed()) {
+ // FIXME: Bug 1311620: We don't support additive animation yet.
+ if (keyframeDict.mComposite.Value() != dom::CompositeOperation::Add) {
+ keyframe->mComposite.emplace(keyframeDict.mComposite.Value());
+ }
+ }
+
ErrorResult rv;
keyframe->mTimingFunction =
TimingParams::ParseEasing(keyframeDict.mEasing, aDocument, rv);
if (rv.MaybeSetPendingException(aCx)) {
return false;
}
// Look for additional property-values pairs on the object.
@@ -1367,16 +1374,18 @@ BuildSegmentsFromValueEntries(nsTArray<K
// Now generate the segment.
AnimationPropertySegment* segment =
animationProperty->mSegments.AppendElement();
segment->mFromKey = aEntries[i].mOffset;
segment->mToKey = aEntries[j].mOffset;
segment->mFromValue = aEntries[i].mValue;
segment->mToValue = aEntries[j].mValue;
segment->mTimingFunction = aEntries[i].mTimingFunction;
+ segment->mFromComposite = aEntries[i].mComposite;
+ segment->mToComposite = aEntries[j].mComposite;
i = j;
}
}
/**
* Converts a JS object representing a property-indexed keyframe into
* an array of Keyframe objects.
@@ -1403,16 +1412,24 @@ GetKeyframeListFromPropertyIndexedKeyfra
// get its explicit dictionary members.
dom::binding_detail::FastBasePropertyIndexedKeyframe keyframeDict;
if (!keyframeDict.Init(aCx, aValue, "BasePropertyIndexedKeyframe argument",
false)) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
+ Maybe<dom::CompositeOperation> composite;
+ if (keyframeDict.mComposite.WasPassed()) {
+ // FIXME: Bug 1311620: We don't support additive animation yet.
+ if (keyframeDict.mComposite.Value() != dom::CompositeOperation::Add) {
+ composite.emplace(keyframeDict.mComposite.Value());
+ }
+ }
+
Maybe<ComputedTimingFunction> easing =
TimingParams::ParseEasing(keyframeDict.mEasing, aDocument, aRv);
if (aRv.Failed()) {
return;
}
// Get all the property--value-list pairs off the object.
JS::Rooted<JSObject*> object(aCx, &aValue.toObject());
@@ -1448,16 +1465,17 @@ GetKeyframeListFromPropertyIndexedKeyfra
size_t n = pair.mValues.Length() - 1;
size_t i = 0;
for (const nsString& stringValue : pair.mValues) {
double offset = i++ / double(n);
Keyframe* keyframe = processedKeyframes.LookupOrAdd(offset);
if (keyframe->mPropertyValues.IsEmpty()) {
keyframe->mTimingFunction = easing;
+ keyframe->mComposite = composite;
keyframe->mComputedOffset = offset;
}
keyframe->mPropertyValues.AppendElement(
MakePropertyValuePair(pair.mProperty, stringValue, parser, aDocument));
}
}
aResult.SetCapacity(processedKeyframes.Count());
--- a/dom/animation/test/mochitest.ini
+++ b/dom/animation/test/mochitest.ini
@@ -48,16 +48,17 @@ support-files =
mozilla/file_transform_limits.html
mozilla/file_transition_finish_on_compositor.html
mozilla/file_underlying-discrete-value.html
mozilla/file_set-easing.html
style/file_animation-seeking-with-current-time.html
style/file_animation-seeking-with-start-time.html
style/file_animation-setting-effect.html
style/file_animation-setting-spacing.html
+ style/file_composite.html
style/file_missing-keyframe.html
style/file_missing-keyframe-on-compositor.html
testcommon.js
[css-animations/test_animations-dynamic-changes.html]
[css-animations/test_animation-cancel.html]
[css-animations/test_animation-computed-timing.html]
[css-animations/test_animation-currenttime.html]
@@ -104,10 +105,11 @@ support-files =
[mozilla/test_transform_limits.html]
[mozilla/test_transition_finish_on_compositor.html]
skip-if = toolkit == 'android'
[mozilla/test_underlying-discrete-value.html]
[style/test_animation-seeking-with-current-time.html]
[style/test_animation-seeking-with-start-time.html]
[style/test_animation-setting-effect.html]
[style/test_animation-setting-spacing.html]
+[style/test_composite.html]
[style/test_missing-keyframe.html]
[style/test_missing-keyframe-on-compositor.html]
new file mode 100644
--- /dev/null
+++ b/dom/animation/test/style/file_composite.html
@@ -0,0 +1,91 @@
+<!doctype html>
+<meta charset=utf-8>
+<script src="../testcommon.js"></script>
+<script src="/tests/SimpleTest/paint_listener.js"></script>
+<style>
+div {
+ /* Element needs geometry to be eligible for layerization */
+ width: 100px;
+ height: 100px;
+ background-color: white;
+}
+</style>
+<body>
+<script>
+'use strict';
+
+if (!SpecialPowers.DOMWindowUtils.layerManagerRemote ||
+ !SpecialPowers.getBoolPref(
+ 'layers.offmainthreadcomposition.async-animations')) {
+ // If OMTA is disabled, nothing to run.
+ done();
+}
+
+function waitForPaintsFlushed() {
+ return new Promise(function(resolve, reject) {
+ waitForAllPaintsFlushed(resolve);
+ });
+}
+
+promise_test(t => {
+ // Without this, the first test case fails on Android.
+ return waitForDocumentLoad();
+}, 'Ensure document has been loaded');
+
+promise_test(t => {
+ useTestRefreshMode(t);
+
+ var div = addDiv(t, { style: 'transform: translateX(100px)' });
+ div.animate({ transform: ['translateX(0px)', 'translateX(200px)'],
+ composite: 'accumulate' },
+ 100 * MS_PER_SEC);
+
+ return waitForPaintsFlushed().then(() => {
+ SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(50 * MS_PER_SEC);
+ var transform =
+ SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
+ assert_matrix_equals(transform, 'matrix(1, 0, 0, 1, 200, 0)',
+ 'Transform value at 50%');
+ });
+}, 'Accumulate onto the base value');
+
+promise_test(t => {
+ useTestRefreshMode(t);
+
+ var div = addDiv(t);
+ div.animate({ transform: ['translateX(100px)', 'translateX(200px)'],
+ composite: 'replace' },
+ 100 * MS_PER_SEC);
+ div.animate({ transform: ['translateX(0px)', 'translateX(100px)'],
+ composite: 'accumulate' },
+ 100 * MS_PER_SEC);
+
+ return waitForPaintsFlushed().then(() => {
+ SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(50 * MS_PER_SEC);
+ var transform =
+ SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
+ assert_matrix_equals(transform, 'matrix(1, 0, 0, 1, 200, 0)',
+ 'Transform value at 50%');
+ });
+}, 'Accumulate onto an underlying animation value');
+
+promise_test(t => {
+ useTestRefreshMode(t);
+
+ var div = addDiv(t, { style: 'transform: translateX(100px)' });
+ div.animate([{ transform: 'translateX(100px)', composite: 'accumulate' },
+ { transform: 'translateX(300px)', composite: 'replace' }],
+ 100 * MS_PER_SEC);
+
+ return waitForPaintsFlushed().then(() => {
+ SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(50 * MS_PER_SEC);
+ var transform =
+ SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
+ assert_matrix_equals(transform, 'matrix(1, 0, 0, 1, 250, 0)',
+ 'Transform value at 50s');
+ });
+}, 'Composite when mixing accumulate and replace');
+
+done();
+</script>
+</body>
new file mode 100644
--- /dev/null
+++ b/dom/animation/test/style/test_composite.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<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', true]] },
+ function() {
+ window.open('file_composite.html');
+ });
+</script>
+</html>
--- a/testing/web-platform/meta/web-animations/animation-model/combining-effects/effect-composition.html.ini
+++ b/testing/web-platform/meta/web-animations/animation-model/combining-effects/effect-composition.html.ini
@@ -1,13 +1,4 @@
[composite.html]
- [Accumulate onto the base value]
- expected: FAIL
-
- [Accumulate onto an underlying animation value]
- expected: FAIL
-
- [Composite when mixing accumulate and replace]
- expected: FAIL
-
[Composite specified on a keyframe overrides the composite mode of the effect]
expected: FAIL