Commit 41658d02 authored by Stephen McGruer's avatar Stephen McGruer Committed by Commit Bot

Support the KeyframeEffectReadOnly composite property

Previously we associated the 'composite' property with the Keyframe,
including the default 'replace' value. However the spec differentiates
between the KeyframeEffectReadOnly composite property and additional
keyframe-specific composite properties. This CL updates the code to
properly track these two concepts. Keyframe now has an optional
keyframe-specific composite operation, and the final composite operation
is determined when creating the property-specific keyframes.

Unfortunately due to the way the code is structured this change was
non-trivial. Major changes:

  * KeyframeEffectReadOnly and InertEffect now store a
    KeyframeEffectModelBase rather than an EffectModel, due to the
    need to store and retrieve the 'composite' value.
  * The KeyframeEffectModelBase on KeyframeEffectReadOnly is now
    non-nullable. Previously a null model indicated that a
    KeyframeEffectReadOnly had no keyframes; this is now tracked
    explicitly in the model.

Bug: 785526
Change-Id: I9b4967bf11c010bbd1f61f2d5ced1159fc0dd0c4
Reviewed-on: https://chromium-review.googlesource.com/786292
Commit-Queue: Stephen McGruer <smcgruer@chromium.org>
Reviewed-by: default avatarJeremy Roman <jbroman@chromium.org>
Reviewed-by: default avatarAndrey Kosyakov <caseq@chromium.org>
Reviewed-by: default avatarRobert Flack <flackr@chromium.org>
Cr-Commit-Position: refs/heads/master@{#520224}
parent 3a959a80
......@@ -2,12 +2,12 @@ This is a testharness.js-based test.
FAIL accumulate onto the base value assert_equals: Animated margin-left style at 50% expected "15px" but got "5px"
FAIL accumulate onto an underlying animation value assert_equals: Animated style at 50% expected "20px" but got "5px"
FAIL Composite when mixing accumulate and replace assert_equals: Animated style at 50% expected "25px" but got "20px"
FAIL accumulate specified on a keyframe overrides the composite mode of the effect assert_equals: Animated style at 50% expected "20px" but got "15px"
FAIL accumulate specified on a keyframe overrides the composite mode of the effect Failed to execute 'animate' on 'Element': Invalid composite value: 'accumulate'
FAIL unspecified composite mode on a keyframe is overriden by setting accumulate of the effect assert_equals: Animated style at 50% expected "20px" but got "15px"
PASS add onto the base value
PASS add onto an underlying animation value
PASS Composite when mixing add and replace
FAIL add specified on a keyframe overrides the composite mode of the effect assert_equals: Animated style at 50% expected "20px" but got "15px"
FAIL unspecified composite mode on a keyframe is overriden by setting add of the effect assert_equals: Animated style at 50% expected "20px" but got "15px"
PASS add specified on a keyframe overrides the composite mode of the effect
PASS unspecified composite mode on a keyframe is overriden by setting add of the effect
Harness: the test ran to completion.
This is a testharness.js-based test.
FAIL Default value assert_equals: The default value should be replace expected (string) "replace" but got (undefined) undefined
PASS Change composite value
FAIL Unspecified keyframe composite value when setting effect composite assert_equals: unspecified keyframe composite value should be absent even if effect composite is set expected (undefined) undefined but got (string) "replace"
PASS Specified keyframe composite value when setting effect composite
Harness: the test ran to completion.
......@@ -13,13 +13,13 @@ PASS KeyframeEffectReadOnly interface: existence and properties of interface pro
PASS KeyframeEffectReadOnly interface: existence and properties of interface prototype object's "constructor" property
FAIL KeyframeEffectReadOnly interface: attribute target assert_true: The prototype object must have a property "target" expected true got false
FAIL KeyframeEffectReadOnly interface: attribute iterationComposite assert_true: The prototype object must have a property "iterationComposite" expected true got false
FAIL KeyframeEffectReadOnly interface: attribute composite assert_true: The prototype object must have a property "composite" expected true got false
PASS KeyframeEffectReadOnly interface: attribute composite
PASS KeyframeEffectReadOnly interface: operation getKeyframes()
PASS KeyframeEffectReadOnly must be primary interface of new KeyframeEffectReadOnly(null, null)
PASS Stringification of new KeyframeEffectReadOnly(null, null)
FAIL KeyframeEffectReadOnly interface: new KeyframeEffectReadOnly(null, null) must inherit property "target" with the proper type assert_inherits: property "target" not found in prototype chain
FAIL KeyframeEffectReadOnly interface: new KeyframeEffectReadOnly(null, null) must inherit property "iterationComposite" with the proper type assert_inherits: property "iterationComposite" not found in prototype chain
FAIL KeyframeEffectReadOnly interface: new KeyframeEffectReadOnly(null, null) must inherit property "composite" with the proper type assert_inherits: property "composite" not found in prototype chain
PASS KeyframeEffectReadOnly interface: new KeyframeEffectReadOnly(null, null) must inherit property "composite" with the proper type
PASS KeyframeEffectReadOnly interface: new KeyframeEffectReadOnly(null, null) must inherit property "getKeyframes()" with the proper type
PASS AnimationEffectReadOnly interface: new KeyframeEffectReadOnly(null, null) must inherit property "timing" with the proper type
PASS AnimationEffectReadOnly interface: new KeyframeEffectReadOnly(null, null) must inherit property "getComputedTiming()" with the proper type
......@@ -30,18 +30,18 @@ PASS KeyframeEffect interface: existence and properties of interface prototype o
PASS KeyframeEffect interface: existence and properties of interface prototype object's "constructor" property
FAIL KeyframeEffect interface: attribute target assert_true: The prototype object must have a property "target" expected true got false
FAIL KeyframeEffect interface: attribute iterationComposite assert_true: The prototype object must have a property "iterationComposite" expected true got false
FAIL KeyframeEffect interface: attribute composite assert_true: The prototype object must have a property "composite" expected true got false
PASS KeyframeEffect interface: attribute composite
FAIL KeyframeEffect interface: operation setKeyframes(object) assert_own_property: interface prototype object missing non-static operation expected property "setKeyframes" missing
PASS KeyframeEffect must be primary interface of new KeyframeEffect(null, null)
PASS Stringification of new KeyframeEffect(null, null)
FAIL KeyframeEffect interface: new KeyframeEffect(null, null) must inherit property "target" with the proper type assert_inherits: property "target" not found in prototype chain
FAIL KeyframeEffect interface: new KeyframeEffect(null, null) must inherit property "iterationComposite" with the proper type assert_inherits: property "iterationComposite" not found in prototype chain
FAIL KeyframeEffect interface: new KeyframeEffect(null, null) must inherit property "composite" with the proper type assert_inherits: property "composite" not found in prototype chain
PASS KeyframeEffect interface: new KeyframeEffect(null, null) must inherit property "composite" with the proper type
FAIL KeyframeEffect interface: new KeyframeEffect(null, null) must inherit property "setKeyframes(object)" with the proper type assert_inherits: property "setKeyframes" not found in prototype chain
FAIL KeyframeEffect interface: calling setKeyframes(object) on new KeyframeEffect(null, null) with too few arguments must throw TypeError assert_inherits: property "setKeyframes" not found in prototype chain
FAIL KeyframeEffectReadOnly interface: new KeyframeEffect(null, null) must inherit property "target" with the proper type assert_inherits: property "target" not found in prototype chain
FAIL KeyframeEffectReadOnly interface: new KeyframeEffect(null, null) must inherit property "iterationComposite" with the proper type assert_inherits: property "iterationComposite" not found in prototype chain
FAIL KeyframeEffectReadOnly interface: new KeyframeEffect(null, null) must inherit property "composite" with the proper type assert_inherits: property "composite" not found in prototype chain
PASS KeyframeEffectReadOnly interface: new KeyframeEffect(null, null) must inherit property "composite" with the proper type
PASS KeyframeEffectReadOnly interface: new KeyframeEffect(null, null) must inherit property "getKeyframes()" with the proper type
PASS AnimationEffectReadOnly interface: new KeyframeEffect(null, null) must inherit property "timing" with the proper type
PASS AnimationEffectReadOnly interface: new KeyframeEffect(null, null) must inherit property "getComputedTiming()" with the proper type
......
......@@ -3937,9 +3937,12 @@ interface KeyboardEvent : UIEvent
method initKeyboardEvent
interface KeyframeEffect : KeyframeEffectReadOnly
attribute @@toStringTag
getter composite
method constructor
setter composite
interface KeyframeEffectReadOnly : AnimationEffectReadOnly
attribute @@toStringTag
getter composite
method constructor
method getKeyframes
interface LinearAccelerationSensor : Accelerometer
......
......@@ -35,6 +35,7 @@
#include "core/animation/DocumentTimeline.h"
#include "core/animation/ElementAnimations.h"
#include "core/animation/KeyframeEffect.h"
#include "core/animation/KeyframeEffectModel.h"
#include "core/animation/PendingAnimations.h"
#include "core/dom/DOMNodeIds.h"
#include "core/dom/Document.h"
......@@ -72,12 +73,16 @@ class AnimationAnimationTest : public RenderingTest {
void StartTimeline() { SimulateFrame(0); }
KeyframeEffectModelBase* MakeEmptyEffectModel() {
return StringKeyframeEffectModel::Create(StringKeyframeVector());
}
KeyframeEffect* MakeAnimation(double duration = 30,
double playback_rate = 1) {
Timing timing;
timing.iteration_duration = duration;
timing.playback_rate = playback_rate;
return KeyframeEffect::Create(nullptr, nullptr, timing);
return KeyframeEffect::Create(nullptr, MakeEmptyEffectModel(), timing);
}
bool SimulateFrame(double time,
......@@ -449,7 +454,8 @@ TEST_F(AnimationAnimationTest, FinishRaisesException) {
Timing timing;
timing.iteration_duration = 1;
timing.iteration_count = std::numeric_limits<double>::infinity();
animation->setEffect(KeyframeEffect::Create(nullptr, nullptr, timing));
animation->setEffect(
KeyframeEffect::Create(nullptr, MakeEmptyEffectModel(), timing));
animation->SetCurrentTimeInternal(10);
DummyExceptionStateForTesting exception_state;
......@@ -601,7 +607,7 @@ TEST_F(AnimationAnimationTest, AnimationsReturnTimeToNextEffect) {
timing.iteration_duration = 1;
timing.end_delay = 1;
KeyframeEffect* keyframe_effect =
KeyframeEffect::Create(nullptr, nullptr, timing);
KeyframeEffect::Create(nullptr, MakeEmptyEffectModel(), timing);
animation = timeline->Play(keyframe_effect);
animation->setStartTime(0, false);
......@@ -706,7 +712,7 @@ TEST_F(AnimationAnimationTest, AttachedAnimations) {
Timing timing;
KeyframeEffect* keyframe_effect =
KeyframeEffect::Create(element.Get(), nullptr, timing);
KeyframeEffect::Create(element.Get(), MakeEmptyEffectModel(), timing);
Animation* animation = timeline->Play(keyframe_effect);
SimulateFrame(0);
timeline->ServiceAnimations(kTimingUpdateForAnimationFrame);
......@@ -816,10 +822,11 @@ TEST_F(AnimationAnimationTest, NoCompositeWithoutCompositedElementId) {
timing.iteration_duration = 30;
timing.playback_rate = 1;
KeyframeEffect* keyframe_effect_composited = KeyframeEffect::Create(
ToElement(object_composited->GetNode()), nullptr, timing);
ToElement(object_composited->GetNode()), MakeEmptyEffectModel(), timing);
Animation* animation_composited = timeline->Play(keyframe_effect_composited);
KeyframeEffect* keyframe_effect_not_composited = KeyframeEffect::Create(
ToElement(object_not_composited->GetNode()), nullptr, timing);
KeyframeEffect* keyframe_effect_not_composited =
KeyframeEffect::Create(ToElement(object_not_composited->GetNode()),
MakeEmptyEffectModel(), timing);
Animation* animation_not_composited =
timeline->Play(keyframe_effect_not_composited);
......
......@@ -108,9 +108,11 @@ blink_core_sources("animation") {
"DocumentTimeline.h",
"EffectInput.cpp",
"EffectInput.h",
"EffectModel.cpp",
"EffectModel.h",
"EffectStack.cpp",
"EffectStack.h",
"ElementAnimation.cpp",
"ElementAnimation.h",
"ElementAnimations.cpp",
"ElementAnimations.h",
......
......@@ -84,6 +84,10 @@ class AnimationDocumentTimelineTest : public ::testing::Test {
timeline->ScheduleNextService();
}
KeyframeEffectModelBase* CreateEmptyEffectModel() {
return StringKeyframeEffectModel::Create(StringKeyframeVector());
}
std::unique_ptr<DummyPageHolder> page_holder;
Persistent<Document> document;
Persistent<Element> element;
......@@ -349,12 +353,10 @@ TEST_F(AnimationDocumentTimelineTest, PlaybackRateFastWithOriginTime) {
TEST_F(AnimationDocumentTimelineTest, PauseForTesting) {
float seek_time = 1;
timing.fill_mode = Timing::FillMode::FORWARDS;
KeyframeEffect* anim1 = KeyframeEffect::Create(
element.Get(), StringKeyframeEffectModel::Create(StringKeyframeVector()),
timing);
KeyframeEffect* anim2 = KeyframeEffect::Create(
element.Get(), StringKeyframeEffectModel::Create(StringKeyframeVector()),
timing);
KeyframeEffect* anim1 =
KeyframeEffect::Create(element.Get(), CreateEmptyEffectModel(), timing);
KeyframeEffect* anim2 =
KeyframeEffect::Create(element.Get(), CreateEmptyEffectModel(), timing);
Animation* animation1 = timeline->Play(anim1);
Animation* animation2 = timeline->Play(anim2);
timeline->PauseAnimationsForTesting(seek_time);
......@@ -368,7 +370,7 @@ TEST_F(AnimationDocumentTimelineTest, DelayBeforeAnimationStart) {
timing.start_delay = 5;
KeyframeEffect* keyframe_effect =
KeyframeEffect::Create(element.Get(), nullptr, timing);
KeyframeEffect::Create(element.Get(), CreateEmptyEffectModel(), timing);
timeline->Play(keyframe_effect);
......@@ -403,7 +405,7 @@ TEST_F(AnimationDocumentTimelineTest, PlayAfterDocumentDeref) {
document = nullptr;
KeyframeEffect* keyframe_effect =
KeyframeEffect::Create(nullptr, nullptr, timing);
KeyframeEffect::Create(nullptr, CreateEmptyEffectModel(), timing);
// Test passes if this does not crash.
timeline->Play(keyframe_effect);
}
......
......@@ -123,12 +123,18 @@ void SetKeyframeValue(Element& element,
keyframe.SetSVGAttributeValue(*svg_attribute, value);
}
EffectModel* CreateEffectModelFromKeyframes(
KeyframeEffectModelBase* CreateEmptyEffectModel(
EffectModel::CompositeOperation composite) {
return StringKeyframeEffectModel::Create(StringKeyframeVector(), composite);
}
KeyframeEffectModelBase* CreateEffectModel(
Element& element,
const StringKeyframeVector& keyframes,
EffectModel::CompositeOperation composite,
ExceptionState& exception_state) {
StringKeyframeEffectModel* keyframe_effect_model =
StringKeyframeEffectModel::Create(keyframes,
StringKeyframeEffectModel::Create(keyframes, composite,
LinearTimingFunction::Shared());
if (!RuntimeEnabledFeatures::CSSAdditiveAnimationsEnabled()) {
for (const auto& keyframe_group :
......@@ -141,12 +147,12 @@ EffectModel* CreateEffectModelFromKeyframes(
if (keyframe->IsNeutral()) {
exception_state.ThrowDOMException(
kNotSupportedError, "Partial keyframes are not supported.");
return nullptr;
return CreateEmptyEffectModel(composite);
}
if (keyframe->Composite() != EffectModel::kCompositeReplace) {
exception_state.ThrowDOMException(
kNotSupportedError, "Additive animations are not supported.");
return nullptr;
return CreateEmptyEffectModel(composite);
}
}
}
......@@ -174,18 +180,19 @@ bool ExhaustDictionaryIterator(DictionaryIterator& iterator,
} // namespace
// Spec: http://w3c.github.io/web-animations/#processing-a-keyframes-argument
EffectModel* EffectInput::Convert(
KeyframeEffectModelBase* EffectInput::Convert(
Element* element,
const DictionarySequenceOrDictionary& effect_input,
EffectModel::CompositeOperation composite,
ExecutionContext* execution_context,
ExceptionState& exception_state) {
// TODO(crbug.com/772014): The element is allowed to be null; remove check.
if (effect_input.IsNull() || !element)
return nullptr;
return CreateEmptyEffectModel(composite);
if (effect_input.IsDictionarySequence()) {
return ConvertArrayForm(*element, effect_input.GetAsDictionarySequence(),
execution_context, exception_state);
composite, execution_context, exception_state);
}
const Dictionary& dictionary = effect_input.GetAsDictionary();
......@@ -196,19 +203,20 @@ EffectModel* EffectInput::Convert(
Vector<Dictionary> keyframe_dictionaries;
if (ExhaustDictionaryIterator(iterator, execution_context, exception_state,
keyframe_dictionaries)) {
return ConvertArrayForm(*element, keyframe_dictionaries,
return ConvertArrayForm(*element, keyframe_dictionaries, composite,
execution_context, exception_state);
}
return nullptr;
return CreateEmptyEffectModel(composite);
}
return ConvertObjectForm(*element, dictionary, execution_context,
return ConvertObjectForm(*element, dictionary, composite, execution_context,
exception_state);
}
EffectModel* EffectInput::ConvertArrayForm(
KeyframeEffectModelBase* EffectInput::ConvertArrayForm(
Element& element,
const Vector<Dictionary>& keyframe_dictionaries,
EffectModel::CompositeOperation composite,
ExecutionContext* execution_context,
ExceptionState& exception_state) {
StringKeyframeVector keyframes;
......@@ -221,7 +229,7 @@ EffectModel* EffectInput::ConvertArrayForm(
if (DictionaryHelper::Get(keyframe_dictionary, "offset", offset) &&
!offset.IsNull()) {
if (!CheckOffset(offset.Get(), last_offset, exception_state))
return nullptr;
return CreateEmptyEffectModel(composite);
last_offset = offset.Get();
keyframe->SetOffset(offset.Get());
......@@ -231,7 +239,9 @@ EffectModel* EffectInput::ConvertArrayForm(
DictionaryHelper::Get(keyframe_dictionary, "composite", composite_string);
if (composite_string == "add")
keyframe->SetComposite(EffectModel::kCompositeAdd);
// TODO(alancutter): Support "accumulate" keyframe composition.
else if (composite_string == "replace")
keyframe->SetComposite(EffectModel::kCompositeReplace);
// TODO(crbug.com/788440): Support "accumulate" keyframe composition.
String timing_function_string;
if (DictionaryHelper::Get(keyframe_dictionary, "easing",
......@@ -240,14 +250,14 @@ EffectModel* EffectInput::ConvertArrayForm(
AnimationInputHelpers::ParseTimingFunction(
timing_function_string, &element.GetDocument(), exception_state);
if (!timing_function)
return nullptr;
return CreateEmptyEffectModel(composite);
keyframe->SetEasing(timing_function);
}
const Vector<String>& keyframe_properties =
keyframe_dictionary.GetPropertyNames(exception_state);
if (exception_state.HadException())
return nullptr;
return CreateEmptyEffectModel(composite);
for (const auto& property : keyframe_properties) {
if (property == "offset" || property == "composite" ||
property == "easing") {
......@@ -258,7 +268,7 @@ EffectModel* EffectInput::ConvertArrayForm(
if (DictionaryHelper::Get(keyframe_dictionary, property, values)) {
exception_state.ThrowTypeError(
"Lists of values not permitted in array-form list of keyframes");
return nullptr;
return CreateEmptyEffectModel(composite);
}
String value;
......@@ -272,7 +282,7 @@ EffectModel* EffectInput::ConvertArrayForm(
DCHECK(!exception_state.HadException());
return CreateEffectModelFromKeyframes(element, keyframes, exception_state);
return CreateEffectModel(element, keyframes, composite, exception_state);
}
static bool GetPropertyIndexedKeyframeValues(
......@@ -320,9 +330,10 @@ static bool GetPropertyIndexedKeyframeValues(
return !exception_state.HadException();
}
EffectModel* EffectInput::ConvertObjectForm(
KeyframeEffectModelBase* EffectInput::ConvertObjectForm(
Element& element,
const Dictionary& keyframe_dictionary,
EffectModel::CompositeOperation composite,
ExecutionContext* execution_context,
ExceptionState& exception_state) {
StringKeyframeVector keyframes;
......@@ -334,14 +345,14 @@ EffectModel* EffectInput::ConvertObjectForm(
timing_function = AnimationInputHelpers::ParseTimingFunction(
timing_function_string, &element.GetDocument(), exception_state);
if (!timing_function)
return nullptr;
return CreateEmptyEffectModel(composite);
}
Nullable<double> offset;
if (DictionaryHelper::Get(keyframe_dictionary, "offset", offset) &&
!offset.IsNull()) {
if (!CheckOffset(offset.Get(), 0.0, exception_state))
return nullptr;
return CreateEmptyEffectModel(composite);
}
String composite_string;
......@@ -350,7 +361,7 @@ EffectModel* EffectInput::ConvertObjectForm(
const Vector<String>& keyframe_properties =
keyframe_dictionary.GetPropertyNames(exception_state);
if (exception_state.HadException())
return nullptr;
return CreateEmptyEffectModel(composite);
for (const auto& property : keyframe_properties) {
if (property == "offset" || property == "composite" ||
property == "easing") {
......@@ -361,7 +372,7 @@ EffectModel* EffectInput::ConvertObjectForm(
if (!GetPropertyIndexedKeyframeValues(keyframe_dictionary, property,
execution_context, exception_state,
values))
return nullptr;
return CreateEmptyEffectModel(composite);
size_t num_keyframes = values.size();
for (size_t i = 0; i < num_keyframes; ++i) {
......@@ -379,7 +390,9 @@ EffectModel* EffectInput::ConvertObjectForm(
if (composite_string == "add")
keyframe->SetComposite(EffectModel::kCompositeAdd);
// TODO(alancutter): Support "accumulate" keyframe composition.
else if (composite_string == "replace")
keyframe->SetComposite(EffectModel::kCompositeReplace);
// TODO(crbug.com/788440): Support "accumulate" keyframe composition.
SetKeyframeValue(element, *keyframe.get(), property, values[i],
execution_context);
......@@ -391,7 +404,7 @@ EffectModel* EffectInput::ConvertObjectForm(
DCHECK(!exception_state.HadException());
return CreateEffectModelFromKeyframes(element, keyframes, exception_state);
return CreateEffectModel(element, keyframes, composite, exception_state);
}
} // namespace blink
......@@ -12,7 +12,7 @@
namespace blink {
class EffectModel;
class KeyframeEffectModelBase;
class DictionarySequenceOrDictionary;
class Dictionary;
class Element;
......@@ -24,20 +24,25 @@ class CORE_EXPORT EffectInput {
public:
// TODO(alancutter): Replace Element* parameter with Document&.
static EffectModel* Convert(Element*,
const DictionarySequenceOrDictionary&,
ExecutionContext*,
ExceptionState&);
static KeyframeEffectModelBase* Convert(Element*,
const DictionarySequenceOrDictionary&,
EffectModel::CompositeOperation,
ExecutionContext*,
ExceptionState&);
private:
static EffectModel* ConvertArrayForm(Element&,
const Vector<Dictionary>& keyframes,
ExecutionContext*,
ExceptionState&);
static EffectModel* ConvertObjectForm(Element&,
const Dictionary& keyframe,
ExecutionContext*,
ExceptionState&);
static KeyframeEffectModelBase* ConvertArrayForm(
Element&,
const Vector<Dictionary>& keyframes,
EffectModel::CompositeOperation,
ExecutionContext*,
ExceptionState&);
static KeyframeEffectModelBase* ConvertObjectForm(
Element&,
const Dictionary& keyframe,
EffectModel::CompositeOperation,
ExecutionContext*,
ExceptionState&);
};
} // namespace blink
......
......@@ -43,14 +43,12 @@ TEST(AnimationEffectInputTest, SortedOffsets) {
Dictionary(scope.GetIsolate(), keyframe2, scope.GetExceptionState()));
Element* element = AppendElement(scope.GetDocument());
EffectModel* animation_effect = EffectInput::Convert(
KeyframeEffectModelBase* effect = EffectInput::Convert(
element,
DictionarySequenceOrDictionary::FromDictionarySequence(js_keyframes),
nullptr, scope.GetExceptionState());
EffectModel::kCompositeReplace, nullptr, scope.GetExceptionState());
EXPECT_FALSE(scope.GetExceptionState().HadException());
const KeyframeEffectModelBase& keyframe_effect =
*ToKeyframeEffectModelBase(animation_effect);
EXPECT_EQ(1.0, keyframe_effect.GetFrames()[1]->Offset());
EXPECT_EQ(1.0, effect->GetFrames()[1]->Offset());
}
TEST(AnimationEffectInputTest, UnsortedOffsets) {
......@@ -73,7 +71,7 @@ TEST(AnimationEffectInputTest, UnsortedOffsets) {
EffectInput::Convert(
element,
DictionarySequenceOrDictionary::FromDictionarySequence(js_keyframes),
nullptr, scope.GetExceptionState());
EffectModel::kCompositeReplace, nullptr, scope.GetExceptionState());
EXPECT_TRUE(scope.GetExceptionState().HadException());
EXPECT_EQ(kV8TypeError, scope.GetExceptionState().Code());
}
......@@ -99,14 +97,12 @@ TEST(AnimationEffectInputTest, LooslySorted) {
Dictionary(scope.GetIsolate(), keyframe3, scope.GetExceptionState()));
Element* element = AppendElement(scope.GetDocument());
EffectModel* animation_effect = EffectInput::Convert(
KeyframeEffectModelBase* effect = EffectInput::Convert(
element,
DictionarySequenceOrDictionary::FromDictionarySequence(js_keyframes),
nullptr, scope.GetExceptionState());
EffectModel::kCompositeReplace, nullptr, scope.GetExceptionState());
EXPECT_FALSE(scope.GetExceptionState().HadException());
const KeyframeEffectModelBase& keyframe_effect =
*ToKeyframeEffectModelBase(animation_effect);
EXPECT_EQ(1, keyframe_effect.GetFrames()[2]->Offset());
EXPECT_EQ(1, effect->GetFrames()[2]->Offset());
}
TEST(AnimationEffectInputTest, OutOfOrderWithNullOffsets) {
......@@ -138,7 +134,7 @@ TEST(AnimationEffectInputTest, OutOfOrderWithNullOffsets) {
EffectInput::Convert(
element,
DictionarySequenceOrDictionary::FromDictionarySequence(js_keyframes),
nullptr, scope.GetExceptionState());
EffectModel::kCompositeReplace, nullptr, scope.GetExceptionState());
EXPECT_TRUE(scope.GetExceptionState().HadException());
}
......@@ -167,7 +163,7 @@ TEST(AnimationEffectInputTest, Invalid) {
EffectInput::Convert(
element,
DictionarySequenceOrDictionary::FromDictionarySequence(js_keyframes),
nullptr, scope.GetExceptionState());
EffectModel::kCompositeReplace, nullptr, scope.GetExceptionState());
EXPECT_TRUE(scope.GetExceptionState().HadException());
EXPECT_EQ(kV8TypeError, scope.GetExceptionState().Code());
}
......
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "core/animation/EffectModel.h"
#include "bindings/core/v8/ExceptionState.h"
namespace blink {
bool EffectModel::StringToCompositeOperation(String composite_string,
CompositeOperation& result,
ExceptionState* exception_state) {
// TODO(crbug.com/788440): Once all CompositeOperations are supported we can
// just DCHECK the input and directly convert it instead of handling failure.
if (composite_string == "add") {
result = kCompositeAdd;
return true;
}
if (composite_string == "replace") {
result = kCompositeReplace;
return true;
}
if (exception_state) {
exception_state->ThrowTypeError("Invalid composite value: '" +
composite_string + "'");
}
return false;
}
String EffectModel::CompositeOperationToString(CompositeOperation composite) {
switch (composite) {
case EffectModel::kCompositeAdd:
return "add";
case EffectModel::kCompositeReplace:
return "replace";
default:
NOTREACHED();
return "";
}
}
} // namespace blink
......@@ -39,6 +39,7 @@
namespace blink {
class ExceptionState;
class Interpolation;
// Time independent representation of an Animation's content.
......@@ -50,6 +51,10 @@ class CORE_EXPORT EffectModel : public GarbageCollectedFinalized<EffectModel> {
kCompositeReplace,
kCompositeAdd,
};
static bool StringToCompositeOperation(String,
CompositeOperation&,
ExceptionState* = nullptr);
static String CompositeOperationToString(CompositeOperation);
EffectModel() {}
virtual ~EffectModel() {}
......
......@@ -47,7 +47,8 @@ class AnimationEffectStackTest : public PageTestBase {
.sampled_effects_.size();
}
EffectModel* MakeEffectModel(CSSPropertyID id, const String& value) {
KeyframeEffectModelBase* MakeEffectModel(CSSPropertyID id,
const String& value) {
StringKeyframeVector keyframes(2);
keyframes[0] = StringKeyframe::Create();
keyframes[0]->SetOffset(0.0);
......@@ -60,13 +61,13 @@ class AnimationEffectStackTest : public PageTestBase {
return StringKeyframeEffectModel::Create(keyframes);
}
InertEffect* MakeInertEffect(EffectModel* effect) {
InertEffect* MakeInertEffect(KeyframeEffectModelBase* effect) {
Timing timing;
timing.fill_mode = Timing::FillMode::BOTH;
return InertEffect::Create(effect, timing, false, 0);
}
KeyframeEffect* MakeKeyframeEffect(EffectModel* effect,
KeyframeEffect* MakeKeyframeEffect(KeyframeEffectModelBase* effect,
double duration = 10) {
Timing timing;
timing.fill_mode = Timing::FillMode::BOTH;
......
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "core/animation/ElementAnimation.h"
#include "bindings/core/v8/ExceptionState.h"
#include "bindings/core/v8/dictionary_sequence_or_dictionary.h"
#include "core/animation/Animation.h"
#include "core/animation/DocumentTimeline.h"
#include "core/animation/EffectInput.h"
#include "core/animation/EffectModel.h"
#include "core/animation/KeyframeEffect.h"
#include "core/animation/KeyframeEffectModel.h"
#include "core/animation/KeyframeEffectReadOnly.h"
#include "core/animation/Timing.h"
#include "core/animation/TimingInput.h"
#include "core/dom/Document.h"
#include "core/dom/Element.h"
#include "core/dom/ExecutionContext.h"
#include "platform/bindings/ScriptState.h"
namespace blink {
Animation* ElementAnimation::animate(
ScriptState* script_state,
Element& element,
const DictionarySequenceOrDictionary& effect_input,
UnrestrictedDoubleOrKeyframeAnimationOptions options,
ExceptionState& exception_state) {
EffectModel::CompositeOperation composite = EffectModel::kCompositeReplace;
if (options.IsKeyframeAnimationOptions() &&
!EffectModel::StringToCompositeOperation(
options.GetAsKeyframeAnimationOptions().composite(), composite,
&exception_state)) {
return nullptr;
}
KeyframeEffectModelBase* effect = EffectInput::Convert(
&element, effect_input, composite, ExecutionContext::From(script_state),
exception_state);
if (exception_state.HadException())
return nullptr;
Timing timing;
if (!TimingInput::Convert(options, timing, &element.GetDocument(),
exception_state))
return nullptr;
Animation* animation = animateInternal(element, effect, timing);
if (options.IsKeyframeAnimationOptions())
animation->setId(options.GetAsKeyframeAnimationOptions().id());
return animation;
}
Animation* ElementAnimation::animate(
ScriptState* script_state,
Element& element,
const DictionarySequenceOrDictionary& effect_input,
ExceptionState& exception_state) {
KeyframeEffectModelBase* effect = EffectInput::Convert(
&element, effect_input, EffectModel::kCompositeReplace,
ExecutionContext::From(script_state), exception_state);
if (exception_state.HadException())
return nullptr;
return animateInternal(element, effect, Timing());
}
HeapVector<Member<Animation>> ElementAnimation::getAnimations(
Element& element) {
HeapVector<Member<Animation>> animations;
if (!element.HasAnimations())
return animations;
for (const auto& animation :
element.GetDocument().Timeline().getAnimations()) {
DCHECK(animation->effect());
if (ToKeyframeEffectReadOnly(animation->effect())->Target() == element &&
(animation->effect()->IsCurrent() || animation->effect()->IsInEffect()))
animations.push_back(animation);
}
return animations;
}
Animation* ElementAnimation::animateInternal(Element& element,
KeyframeEffectModelBase* effect,
const Timing& timing) {
KeyframeEffect* keyframe_effect =
KeyframeEffect::Create(&element, effect, timing);
return element.GetDocument().Timeline().Play(keyframe_effect);
}
} // namespace blink
......@@ -31,93 +31,47 @@
#ifndef ElementAnimation_h
#define ElementAnimation_h
#include "base/gtest_prod_util.h"
#include "bindings/core/v8/dictionary_sequence_or_dictionary.h"
#include "bindings/core/v8/unrestricted_double_or_keyframe_animation_options.h"
#include "core/animation/DocumentTimeline.h"
#include "core/animation/EffectInput.h"
#include "core/animation/ElementAnimations.h"
#include "core/animation/KeyframeEffect.h"
#include "core/animation/KeyframeEffectReadOnly.h"
#include "core/animation/TimingInput.h"
#include "core/dom/Document.h"
#include "core/dom/Element.h"
#include "core/dom/ExecutionContext.h"
#include "platform/bindings/ScriptState.h"
#include "core/CoreExport.h"
#include "platform/heap/HeapAllocator.h"
#include "platform/heap/Member.h"
#include "platform/wtf/Allocator.h"
namespace blink {
class Animation;
class DictionarySequenceOrDictionary;
class ExceptionState;
class Element;
class KeyframeEffectModelBase;
class ScriptState;
struct Timing;
// Implements the interface in ElementAnimation.idl.
class ElementAnimation {
class CORE_EXPORT ElementAnimation {
STATIC_ONLY(ElementAnimation);
public:
static Animation* animate(
ScriptState* script_state,
Element& element,
const DictionarySequenceOrDictionary& effect_input,
UnrestrictedDoubleOrKeyframeAnimationOptions options,
ExceptionState& exception_state) {
EffectModel* effect = EffectInput::Convert(
&element, effect_input, ExecutionContext::From(script_state),
exception_state);
if (exception_state.HadException())
return nullptr;
Timing timing;
if (!TimingInput::Convert(options, timing, &element.GetDocument(),
exception_state))
return nullptr;
if (options.IsKeyframeAnimationOptions()) {
Animation* animation = animateInternal(element, effect, timing);
animation->setId(options.GetAsKeyframeAnimationOptions().id());
return animation;
}
return animateInternal(element, effect, timing);
}
static Animation* animate(ScriptState* script_state,
Element& element,
const DictionarySequenceOrDictionary& effect_input,
ExceptionState& exception_state) {
EffectModel* effect = EffectInput::Convert(
&element, effect_input, ExecutionContext::From(script_state),
exception_state);
if (exception_state.HadException())
return nullptr;
return animateInternal(element, effect, Timing());
}
static HeapVector<Member<Animation>> getAnimations(Element& element) {
HeapVector<Member<Animation>> animations;
static Animation* animate(ScriptState*,
Element&,
const DictionarySequenceOrDictionary&,
UnrestrictedDoubleOrKeyframeAnimationOptions,
ExceptionState&);
if (!element.HasAnimations())
return animations;
static Animation* animate(ScriptState*,
Element&,
const DictionarySequenceOrDictionary&,
ExceptionState&);
for (const auto& animation :
element.GetDocument().Timeline().getAnimations()) {
DCHECK(animation->effect());
if (ToKeyframeEffectReadOnly(animation->effect())->Target() == element &&
(animation->effect()->IsCurrent() ||
animation->effect()->IsInEffect()))
animations.push_back(animation);
}
return animations;
}
static HeapVector<Member<Animation>> getAnimations(Element&);
private:
FRIEND_TEST_ALL_PREFIXES(AnimationSimTest, CustomPropertyBaseComputedStyle);
static Animation* animateInternal(Element& element,
EffectModel* effect,
const Timing& timing) {
KeyframeEffect* keyframe_effect =
KeyframeEffect::Create(&element, effect, timing);
return element.GetDocument().Timeline().Play(keyframe_effect);
}
static Animation* animateInternal(Element&,
KeyframeEffectModelBase*,
const Timing&);
};
} // namespace blink
......
......@@ -34,14 +34,14 @@
namespace blink {
InertEffect* InertEffect::Create(EffectModel* effect,
InertEffect* InertEffect::Create(KeyframeEffectModelBase* effect,
const Timing& timing,
bool paused,
double inherited_time) {
return new InertEffect(effect, timing, paused, inherited_time);
}
InertEffect::InertEffect(EffectModel* model,
InertEffect::InertEffect(KeyframeEffectModelBase* model,
const Timing& timing,
bool paused,
double inherited_time)
......
......@@ -34,7 +34,7 @@
#include "base/memory/scoped_refptr.h"
#include "core/CoreExport.h"
#include "core/animation/AnimationEffectReadOnly.h"
#include "core/animation/EffectModel.h"
#include "core/animation/KeyframeEffectModel.h"
namespace blink {
......@@ -43,12 +43,12 @@ namespace blink {
// Interpolation sampling.
class CORE_EXPORT InertEffect final : public AnimationEffectReadOnly {
public:
static InertEffect* Create(EffectModel*,
static InertEffect* Create(KeyframeEffectModelBase*,
const Timing&,
bool paused,
double inherited_time);
void Sample(Vector<scoped_refptr<Interpolation>>&) const;
EffectModel* Model() const { return model_.Get(); }
KeyframeEffectModelBase* Model() const { return model_.Get(); }
bool Paused() const { return paused_; }
bool IsInertEffect() const final { return true; }
......@@ -63,8 +63,11 @@ class CORE_EXPORT InertEffect final : public AnimationEffectReadOnly {
double time_to_next_iteration) const override;
private:
InertEffect(EffectModel*, const Timing&, bool paused, double inherited_time);
Member<EffectModel> model_;
InertEffect(KeyframeEffectModelBase*,
const Timing&,
bool paused,
double inherited_time);
Member<KeyframeEffectModelBase> model_;
bool paused_;
double inherited_time_;
};
......
......@@ -10,20 +10,6 @@
namespace blink {
namespace {
StringView CompositeOperationToString(EffectModel::CompositeOperation op) {
switch (op) {
case EffectModel::kCompositeAdd:
return "add";
case EffectModel::kCompositeReplace:
return "replace";
default:
NOTREACHED();
return "";
}
}
} // namespace
scoped_refptr<Interpolation>
Keyframe::PropertySpecificKeyframe::CreateInterpolation(
const PropertyHandle& property_handle,
......@@ -38,9 +24,11 @@ void Keyframe::AddKeyframePropertiesToV8Object(
V8ObjectBuilder& object_builder) const {
object_builder.Add("offset", offset_);
object_builder.Add("easing", easing_->ToString());
// TODO(crbug.com/785526): This should be absent if it matches the composite
// operation of the keyframe effect (which is not yet implemented).
object_builder.AddString("composite", CompositeOperationToString(composite_));
if (composite_.has_value()) {
object_builder.AddString(
"composite",
EffectModel::CompositeOperationToString(composite_.value()));
}
}
bool Keyframe::CompareOffsets(const scoped_refptr<Keyframe>& a,
......
......@@ -14,6 +14,7 @@
#include "core/animation/animatable/AnimatableValue.h"
#include "platform/wtf/Allocator.h"
#include "platform/wtf/Forward.h"
#include "platform/wtf/Optional.h"
#include "platform/wtf/RefCounted.h"
namespace blink {
......@@ -31,11 +32,13 @@ class V8ObjectBuilder;
//
// * A possibly-null keyframe offset, which represents the keyframe's position
// relative to other keyframes in the same effect.
// * A possibly-null composite operation, which specifies the operation used
// to combine values in this keyframe with an underlying value.
// * A non-null timing function, which applies to the period of time between
// this keyframe and the next keyframe in the same effect and influences
// the interpolation between them.
// * An optional keyframe-specific composite operation, which specifies a
// specific composite operation used to combine values in this keyframe
// with an underlying value. If this is missing, the keyframe effect
// composite operation is used instead.
//
// For spec details, refer to: http://w3c.github.io/web-animations/#keyframe
//
......@@ -43,9 +46,6 @@ class V8ObjectBuilder;
// operation, and timing function. It is left to subclasses to define and store
// the set of (property, value) pairs.
//
// TODO(smcgruer): Our implementation does not allow for a null composite
// operation; by the spec we should allow this and use the effect operation.
//
// === PropertySpecificKeyframes ===
//
// When calculating the effect value of a keyframe effect, the web-animations
......@@ -74,7 +74,10 @@ class CORE_EXPORT Keyframe : public RefCounted<Keyframe> {
void SetComposite(EffectModel::CompositeOperation composite) {
composite_ = composite;
}
EffectModel::CompositeOperation Composite() const { return composite_; }
bool HasComposite() const { return composite_.has_value(); }
EffectModel::CompositeOperation Composite() const {
return composite_.value();
}
// TODO(smcgruer): The keyframe timing function should be immutable.
void SetEasing(scoped_refptr<TimingFunction> easing) {
......@@ -167,13 +170,20 @@ class CORE_EXPORT Keyframe : public RefCounted<Keyframe> {
// Construct and return a property-specific keyframe for this keyframe.
//
// The 'effect_composite' parameter is the composite operation of the effect
// that owns the keyframe. If the keyframe has a keyframe-specific composite
// operation it should ignore this value when creating the property specific
// keyframe.
//
// The 'offset' parameter is the offset to use in the resultant
// PropertySpecificKeyframe. For CSS Transitions and CSS Animations, this is
// the normal offset from the keyframe itself. However in web-animations this
// will be a computed offset value which may differ from the keyframe offset.
virtual scoped_refptr<PropertySpecificKeyframe>
CreatePropertySpecificKeyframe(const PropertyHandle&,
double offset) const = 0;
CreatePropertySpecificKeyframe(
const PropertyHandle&,
EffectModel::CompositeOperation effect_composite,
double offset) const = 0;
// Comparator function for sorting Keyframes based on their offsets.
static bool CompareOffsets(const scoped_refptr<Keyframe>&,
......@@ -182,10 +192,10 @@ class CORE_EXPORT Keyframe : public RefCounted<Keyframe> {
protected:
Keyframe()
: offset_(NullValue()),
composite_(EffectModel::kCompositeReplace),
composite_(),
easing_(LinearTimingFunction::Shared()) {}
Keyframe(double offset,
EffectModel::CompositeOperation composite,
WTF::Optional<EffectModel::CompositeOperation> composite,
scoped_refptr<TimingFunction> easing)
: offset_(offset), composite_(composite), easing_(std::move(easing)) {
if (!easing_)
......@@ -193,7 +203,7 @@ class CORE_EXPORT Keyframe : public RefCounted<Keyframe> {
}
double offset_;
EffectModel::CompositeOperation composite_;
WTF::Optional<EffectModel::CompositeOperation> composite_;
scoped_refptr<TimingFunction> easing_;
DISALLOW_COPY_AND_ASSIGN(Keyframe);
};
......
......@@ -45,7 +45,7 @@ namespace blink {
KeyframeEffect* KeyframeEffect::Create(
Element* target,
EffectModel* model,
KeyframeEffectModelBase* model,
const Timing& timing,
KeyframeEffectReadOnly::Priority priority,
EventDelegate* event_delegate) {
......@@ -68,9 +68,18 @@ KeyframeEffect* KeyframeEffect::Create(
Document* document = element ? &element->GetDocument() : nullptr;
if (!TimingInput::Convert(options, timing, document, exception_state))
return nullptr;
EffectModel::CompositeOperation composite = EffectModel::kCompositeReplace;
if (options.IsKeyframeEffectOptions() &&
!EffectModel::StringToCompositeOperation(
options.GetAsKeyframeEffectOptions().composite(), composite,
&exception_state)) {
return nullptr;
}
return Create(element,
EffectInput::Convert(element, effect_input, execution_context,
exception_state),
EffectInput::Convert(element, effect_input, composite,
execution_context, exception_state),
timing);
}
......@@ -86,13 +95,14 @@ KeyframeEffect* KeyframeEffect::Create(
WebFeature::kAnimationConstructorKeyframeListEffectNoTiming);
}
return Create(element,
EffectInput::Convert(element, effect_input, execution_context,
exception_state),
EffectInput::Convert(element, effect_input,
EffectModel::kCompositeReplace,
execution_context, exception_state),
Timing());
}
KeyframeEffect::KeyframeEffect(Element* target,
EffectModel* model,
KeyframeEffectModelBase* model,
const Timing& timing,
KeyframeEffectReadOnly::Priority priority,
EventDelegate* event_delegate)
......@@ -100,6 +110,12 @@ KeyframeEffect::KeyframeEffect(Element* target,
KeyframeEffect::~KeyframeEffect() {}
void KeyframeEffect::setComposite(String composite_string) {
EffectModel::CompositeOperation composite;
if (EffectModel::StringToCompositeOperation(composite_string, composite))
Model()->SetComposite(composite);
}
AnimationEffectTiming* KeyframeEffect::timing() {
return AnimationEffectTiming::Create(this);
}
......
......@@ -33,7 +33,7 @@
#include "core/CoreExport.h"
#include "core/animation/AnimationEffectTiming.h"
#include "core/animation/EffectModel.h"
#include "core/animation/KeyframeEffectModel.h"
#include "core/animation/KeyframeEffectReadOnly.h"
namespace blink {
......@@ -49,7 +49,7 @@ class CORE_EXPORT KeyframeEffect final : public KeyframeEffectReadOnly {
public:
static KeyframeEffect* Create(Element*,
EffectModel*,
KeyframeEffectModelBase*,
const Timing&,
KeyframeEffectReadOnly::Priority =
KeyframeEffectReadOnly::kDefaultPriority,
......@@ -69,13 +69,16 @@ class CORE_EXPORT KeyframeEffect final : public KeyframeEffectReadOnly {
~KeyframeEffect() override;
// IDL implementation.
void setComposite(String);
bool IsKeyframeEffect() const override { return true; }
AnimationEffectTiming* timing() override;
private:
KeyframeEffect(Element*,
EffectModel*,
KeyframeEffectModelBase*,
const Timing&,
KeyframeEffectReadOnly::Priority,
EventDelegate*);
......
......@@ -36,4 +36,5 @@
RaisesException=Constructor,
RuntimeEnabled=WebAnimationsAPI
] interface KeyframeEffect : KeyframeEffectReadOnly {
inherit attribute CompositeOperation composite;
};
......@@ -229,8 +229,8 @@ void KeyframeEffectModelBase::EnsureKeyframeGroups() const {
group = group_iter->value.get();
}
group->AppendKeyframe(
keyframe->CreatePropertySpecificKeyframe(property, computed_offset));
group->AppendKeyframe(keyframe->CreatePropertySpecificKeyframe(
property, composite_, computed_offset));
}
}
......
......@@ -80,8 +80,12 @@ class CORE_EXPORT KeyframeEffectModelBase : public EffectModel {
using KeyframeVector = Vector<scoped_refptr<Keyframe>>;
const KeyframeVector& GetFrames() const { return keyframes_; }
bool HasFrames() const { return !keyframes_.IsEmpty(); }
void SetFrames(KeyframeVector& keyframes);
CompositeOperation Composite() const { return composite_; }
void SetComposite(CompositeOperation composite) { composite_ = composite; }
const PropertySpecificKeyframeVector& GetPropertySpecificKeyframes(
const PropertyHandle& property) const {
EnsureKeyframeGroups();
......@@ -133,10 +137,12 @@ class CORE_EXPORT KeyframeEffectModelBase : public EffectModel {
bool IsTransformRelatedEffect() const override;
protected:
KeyframeEffectModelBase(scoped_refptr<TimingFunction> default_keyframe_easing)
KeyframeEffectModelBase(CompositeOperation composite,
scoped_refptr<TimingFunction> default_keyframe_easing)
: last_iteration_(0),
last_fraction_(std::numeric_limits<double>::quiet_NaN()),
last_iteration_duration_(0),
composite_(composite),
default_keyframe_easing_(std::move(default_keyframe_easing)),
has_synthetic_keyframes_(false),
needs_compositor_keyframes_snapshot_(true) {}
......@@ -154,6 +160,7 @@ class CORE_EXPORT KeyframeEffectModelBase : public EffectModel {
mutable int last_iteration_;
mutable double last_fraction_;
mutable double last_iteration_duration_;
CompositeOperation composite_;
scoped_refptr<TimingFunction> default_keyframe_easing_;
mutable bool has_synthetic_keyframes_;
......@@ -169,15 +176,17 @@ class KeyframeEffectModel final : public KeyframeEffectModelBase {
using KeyframeVector = Vector<scoped_refptr<Keyframe>>;
static KeyframeEffectModel<Keyframe>* Create(
const KeyframeVector& keyframes,
CompositeOperation composite = kCompositeReplace,
scoped_refptr<TimingFunction> default_keyframe_easing = nullptr) {
return new KeyframeEffectModel(keyframes,
return new KeyframeEffectModel(keyframes, composite,
std::move(default_keyframe_easing));
}
private:
KeyframeEffectModel(const KeyframeVector& keyframes,
CompositeOperation composite,
scoped_refptr<TimingFunction> default_keyframe_easing)
: KeyframeEffectModelBase(std::move(default_keyframe_easing)) {
: KeyframeEffectModelBase(composite, std::move(default_keyframe_easing)) {
keyframes_.AppendVector(keyframes);
}
......
......@@ -5,5 +5,6 @@
// https://w3c.github.io/web-animations/#the-keyframeeffectoptions-dictionary
dictionary KeyframeEffectOptions : AnimationEffectTimingProperties {
// TODO(alancutter): Implement iterationComposite, composite and spacing.
// TODO(alancutter): Implement iterationComposite
CompositeOperation composite = "replace";
};
......@@ -29,7 +29,7 @@ namespace blink {
KeyframeEffectReadOnly* KeyframeEffectReadOnly::Create(
Element* target,
EffectModel* model,
KeyframeEffectModelBase* model,
const Timing& timing,
Priority priority,
EventDelegate* event_delegate) {
......@@ -53,9 +53,18 @@ KeyframeEffectReadOnly* KeyframeEffectReadOnly::Create(
Document* document = element ? &element->GetDocument() : nullptr;
if (!TimingInput::Convert(options, timing, document, exception_state))
return nullptr;
EffectModel::CompositeOperation composite = EffectModel::kCompositeReplace;
if (options.IsKeyframeEffectOptions() &&
!EffectModel::StringToCompositeOperation(
options.GetAsKeyframeEffectOptions().composite(), composite,
&exception_state)) {
return nullptr;
}
return Create(element,
EffectInput::Convert(element, effect_input, execution_context,
exception_state),
EffectInput::Convert(element, effect_input, composite,
execution_context, exception_state),
timing);
}
......@@ -71,13 +80,14 @@ KeyframeEffectReadOnly* KeyframeEffectReadOnly::Create(
WebFeature::kAnimationConstructorKeyframeListEffectNoTiming);
}
return Create(element,
EffectInput::Convert(element, effect_input, execution_context,
exception_state),
EffectInput::Convert(element, effect_input,
EffectModel::kCompositeReplace,
execution_context, exception_state),
Timing());
}
KeyframeEffectReadOnly::KeyframeEffectReadOnly(Element* target,
EffectModel* model,
KeyframeEffectModelBase* model,
const Timing& timing,
Priority priority,
EventDelegate* event_delegate)
......@@ -86,7 +96,7 @@ KeyframeEffectReadOnly::KeyframeEffectReadOnly(Element* target,
model_(model),
sampled_effect_(nullptr),
priority_(priority) {
DCHECK(!model_ || model_->IsKeyframeEffectModel());
DCHECK(model_);
}
void KeyframeEffectReadOnly::Attach(Animation* animation) {
......@@ -161,7 +171,7 @@ bool KeyframeEffectReadOnly::HasIncompatibleStyle() {
void KeyframeEffectReadOnly::ApplyEffects() {
DCHECK(IsInEffect());
DCHECK(GetAnimation());
if (!target_ || !model_)
if (!target_ || !model_->HasFrames())
return;
if (HasIncompatibleStyle())
......@@ -212,7 +222,7 @@ void KeyframeEffectReadOnly::ClearEffects() {
}
void KeyframeEffectReadOnly::UpdateChildrenAndEffects() const {
if (!model_)
if (!model_->HasFrames())
return;
DCHECK(GetAnimation());
if (IsInEffect() && !GetAnimation()->EffectSuppressed())
......@@ -268,7 +278,7 @@ void KeyframeEffectReadOnly::NotifySampledEffectRemovedFromEffectStack() {
CompositorAnimations::FailureCode
KeyframeEffectReadOnly::CheckCanStartAnimationOnCompositor(
double animation_playback_rate) const {
if (!Model()) {
if (!model_->HasFrames()) {
return CompositorAnimations::FailureCode::Actionable(
"Animation effect has no keyframes");
}
......@@ -316,10 +326,14 @@ void KeyframeEffectReadOnly::StartAnimationOnCompositor(
DCHECK(!compositor_animation_ids_.IsEmpty());
}
String KeyframeEffectReadOnly::composite() const {
return EffectModel::CompositeOperationToString(compositeInternal());
}
Vector<ScriptValue> KeyframeEffectReadOnly::getKeyframes(
ScriptState* script_state) {
Vector<ScriptValue> computed_keyframes;
if (!model_)
if (!model_->HasFrames())
return computed_keyframes;
// getKeyframes() returns a list of 'ComputedKeyframes'. A ComputedKeyframe
......@@ -327,8 +341,7 @@ Vector<ScriptValue> KeyframeEffectReadOnly::getKeyframes(
// the given keyframe.
//
// https://w3c.github.io/web-animations/#dom-keyframeeffectreadonly-getkeyframes
const KeyframeVector& keyframes =
ToKeyframeEffectModelBase(model_)->GetFrames();
const KeyframeVector& keyframes = model_->GetFrames();
Vector<double> computed_offsets =
KeyframeEffectModelBase::GetComputedOffsets(keyframes);
computed_keyframes.ReserveInitialCapacity(keyframes.size());
......@@ -353,7 +366,7 @@ bool KeyframeEffectReadOnly::HasActiveAnimationsOnCompositor(
}
bool KeyframeEffectReadOnly::Affects(const PropertyHandle& property) const {
return model_ && model_->Affects(property);
return model_->Affects(property);
}
bool KeyframeEffectReadOnly::CancelAnimationOnCompositor() {
......@@ -380,7 +393,7 @@ void KeyframeEffectReadOnly::RestartAnimationOnCompositor() {
}
void KeyframeEffectReadOnly::CancelIncompatibleAnimationsOnCompositor() {
if (target_ && GetAnimation() && Model()) {
if (target_ && GetAnimation() && model_->HasFrames()) {
CompositorAnimations::CancelIncompatibleAnimationsOnCompositor(
*target_, *GetAnimation(), *Model());
}
......
......@@ -9,7 +9,7 @@
#include "core/CoreExport.h"
#include "core/animation/AnimationEffectReadOnly.h"
#include "core/animation/CompositorAnimations.h"
#include "core/animation/EffectModel.h"
#include "core/animation/KeyframeEffectModel.h"
namespace blink {
......@@ -31,7 +31,7 @@ class CORE_EXPORT KeyframeEffectReadOnly : public AnimationEffectReadOnly {
enum Priority { kDefaultPriority, kTransitionPriority };
static KeyframeEffectReadOnly* Create(Element*,
EffectModel*,
KeyframeEffectModelBase*,
const Timing&,
Priority = kDefaultPriority,
EventDelegate* = nullptr);
......@@ -53,12 +53,20 @@ class CORE_EXPORT KeyframeEffectReadOnly : public AnimationEffectReadOnly {
bool IsKeyframeEffectReadOnly() const override { return true; }
// IDL implementation.
String composite() const;
Vector<ScriptValue> getKeyframes(ScriptState*);
EffectModel::CompositeOperation compositeInternal() const {
return model_->Composite();
}
bool Affects(const PropertyHandle&) const;
const EffectModel* Model() const { return model_.Get(); }
EffectModel* Model() { return model_.Get(); }
void SetModel(EffectModel* model) { model_ = model; }
const KeyframeEffectModelBase* Model() const { return model_.Get(); }
KeyframeEffectModelBase* Model() { return model_.Get(); }
void SetModel(KeyframeEffectModelBase* model) {
DCHECK(model);
model_ = model;
}
Priority GetPriority() const { return priority_; }
Element* Target() const { return target_; }
......@@ -93,7 +101,7 @@ class CORE_EXPORT KeyframeEffectReadOnly : public AnimationEffectReadOnly {
protected:
KeyframeEffectReadOnly(Element*,
EffectModel*,
KeyframeEffectModelBase*,
const Timing&,
Priority,
EventDelegate*);
......@@ -113,7 +121,7 @@ class CORE_EXPORT KeyframeEffectReadOnly : public AnimationEffectReadOnly {
private:
Member<Element> target_;
Member<EffectModel> model_;
Member<KeyframeEffectModelBase> model_;
Member<SampledEffect> sampled_effect_;
Priority priority_;
......
......@@ -4,11 +4,14 @@
// https://w3c.github.io/web-animations/#the-keyframeeffect-interfaces
enum CompositeOperation { "replace", "add", "accumulate" };
[
Constructor(Element? target, (sequence<Dictionary> or Dictionary)? effect, optional (unrestricted double or KeyframeEffectOptions) options),
ConstructorCallWith=ExecutionContext,
RaisesException=Constructor,
RuntimeEnabled=WebAnimationsAPI
] interface KeyframeEffectReadOnly : AnimationEffectReadOnly {
readonly attribute CompositeOperation composite;
[CallWith=ScriptState] sequence<object> getKeyframes();
};
......@@ -38,6 +38,10 @@ class KeyframeEffectTest : public ::testing::Test {
Document& GetDocument() const { return page_holder->GetDocument(); }
KeyframeEffectModelBase* CreateEmptyEffectModel() {
return StringKeyframeEffectModel::Create(StringKeyframeVector());
}
std::unique_ptr<DummyPageHolder> page_holder;
Persistent<Element> element;
};
......@@ -117,8 +121,7 @@ TEST_F(AnimationKeyframeEffectV8Test, CanCreateAnAnimation) {
Element* target = animation->Target();
EXPECT_EQ(*element.Get(), *target);
const KeyframeVector keyframes =
ToKeyframeEffectModelBase(animation->Model())->GetFrames();
const KeyframeVector keyframes = animation->Model()->GetFrames();
EXPECT_EQ(0, keyframes[0]->Offset());
EXPECT_EQ(1, keyframes[1]->Offset());
......@@ -140,6 +143,70 @@ TEST_F(AnimationKeyframeEffectV8Test, CanCreateAnAnimation) {
keyframes[1]->Easing());
}
TEST_F(AnimationKeyframeEffectV8Test, SetAndRetrieveEffectComposite) {
V8TestingScope scope;
NonThrowableExceptionState exception_state;
v8::Local<v8::Object> effect_options = v8::Object::New(scope.GetIsolate());
SetV8ObjectPropertyAsString(scope.GetIsolate(), effect_options, "composite",
"add");
KeyframeEffectOptions effect_options_dictionary;
V8KeyframeEffectOptions::ToImpl(scope.GetIsolate(), effect_options,
effect_options_dictionary, exception_state);
EXPECT_FALSE(exception_state.HadException());
Vector<Dictionary> js_keyframes;
KeyframeEffect* effect =
CreateAnimation(element.Get(), js_keyframes, effect_options_dictionary);
EXPECT_EQ("add", effect->composite());
effect->setComposite("replace");
EXPECT_EQ("replace", effect->composite());
// TODO(crbug.com/788440): Once accumulate is supported as a composite
// property, setting it here should work.
effect->setComposite("accumulate");
EXPECT_EQ("replace", effect->composite());
}
TEST_F(AnimationKeyframeEffectV8Test, KeyframeCompositeOverridesEffect) {
V8TestingScope scope;
NonThrowableExceptionState exception_state;
v8::Local<v8::Object> effect_options = v8::Object::New(scope.GetIsolate());
SetV8ObjectPropertyAsString(scope.GetIsolate(), effect_options, "composite",
"add");
KeyframeEffectOptions effect_options_dictionary;
V8KeyframeEffectOptions::ToImpl(scope.GetIsolate(), effect_options,
effect_options_dictionary, exception_state);
EXPECT_FALSE(exception_state.HadException());
Vector<Dictionary> js_keyframes;
v8::Local<v8::Object> keyframe1 = v8::Object::New(scope.GetIsolate());
v8::Local<v8::Object> keyframe2 = v8::Object::New(scope.GetIsolate());
SetV8ObjectPropertyAsString(scope.GetIsolate(), keyframe1, "width", "100px");
SetV8ObjectPropertyAsString(scope.GetIsolate(), keyframe1, "composite",
"replace");
SetV8ObjectPropertyAsString(scope.GetIsolate(), keyframe2, "width", "0px");
js_keyframes.push_back(
Dictionary(scope.GetIsolate(), keyframe1, exception_state));
js_keyframes.push_back(
Dictionary(scope.GetIsolate(), keyframe2, exception_state));
KeyframeEffect* effect =
CreateAnimation(element.Get(), js_keyframes, effect_options_dictionary);
EXPECT_EQ("add", effect->composite());
PropertyHandle property(GetCSSPropertyWidth());
const PropertySpecificKeyframeVector& keyframes =
effect->Model()->GetPropertySpecificKeyframes(property);
EXPECT_EQ(EffectModel::kCompositeReplace, keyframes[0]->Composite());
EXPECT_EQ(EffectModel::kCompositeAdd, keyframes[1]->Composite());
}
TEST_F(AnimationKeyframeEffectV8Test, CanSetDuration) {
Vector<Dictionary, 0> js_keyframes;
double duration = 2000;
......@@ -326,7 +393,8 @@ TEST_F(KeyframeEffectTest, TimeToEffectChange) {
timing.start_delay = 100;
timing.end_delay = 100;
timing.fill_mode = Timing::FillMode::NONE;
KeyframeEffect* animation = KeyframeEffect::Create(nullptr, nullptr, timing);
KeyframeEffect* animation =
KeyframeEffect::Create(nullptr, CreateEmptyEffectModel(), timing);
Animation* player = GetDocument().Timeline().Play(animation);
double inf = std::numeric_limits<double>::infinity();
......@@ -358,7 +426,8 @@ TEST_F(KeyframeEffectTest, TimeToEffectChangeWithPlaybackRate) {
timing.end_delay = 100;
timing.playback_rate = 2;
timing.fill_mode = Timing::FillMode::NONE;
KeyframeEffect* animation = KeyframeEffect::Create(nullptr, nullptr, timing);
KeyframeEffect* animation =
KeyframeEffect::Create(nullptr, CreateEmptyEffectModel(), timing);
Animation* player = GetDocument().Timeline().Play(animation);
double inf = std::numeric_limits<double>::infinity();
......@@ -390,7 +459,8 @@ TEST_F(KeyframeEffectTest, TimeToEffectChangeWithNegativePlaybackRate) {
timing.end_delay = 100;
timing.playback_rate = -2;
timing.fill_mode = Timing::FillMode::NONE;
KeyframeEffect* animation = KeyframeEffect::Create(nullptr, nullptr, timing);
KeyframeEffect* animation =
KeyframeEffect::Create(nullptr, CreateEmptyEffectModel(), timing);
Animation* player = GetDocument().Timeline().Play(animation);
double inf = std::numeric_limits<double>::infinity();
......@@ -420,7 +490,7 @@ TEST_F(KeyframeEffectTest, ElementDestructorClearsAnimationTarget) {
Timing timing;
timing.iteration_duration = 5;
KeyframeEffect* animation =
KeyframeEffect::Create(element.Get(), nullptr, timing);
KeyframeEffect::Create(element.Get(), CreateEmptyEffectModel(), timing);
EXPECT_EQ(element.Get(), animation->Target());
GetDocument().Timeline().Play(animation);
page_holder.reset();
......
......@@ -129,11 +129,15 @@ scoped_refptr<Keyframe> StringKeyframe::Clone() const {
}
scoped_refptr<Keyframe::PropertySpecificKeyframe>
StringKeyframe::CreatePropertySpecificKeyframe(const PropertyHandle& property,
double offset) const {
StringKeyframe::CreatePropertySpecificKeyframe(
const PropertyHandle& property,
EffectModel::CompositeOperation effect_composite,
double offset) const {
EffectModel::CompositeOperation composite =
composite_.value_or(effect_composite);
if (property.IsCSSProperty()) {
return CSSPropertySpecificKeyframe::Create(
offset, &Easing(), &CssPropertyValue(property), Composite());
offset, &Easing(), &CssPropertyValue(property), composite);
}
if (property.IsPresentationAttribute()) {
......@@ -141,13 +145,12 @@ StringKeyframe::CreatePropertySpecificKeyframe(const PropertyHandle& property,
offset, &Easing(),
&PresentationAttributeValue(
property.PresentationAttribute().PropertyID()),
Composite());
composite);
}
DCHECK(property.IsSVGAttribute());
return SVGPropertySpecificKeyframe::Create(
offset, &Easing(), SvgPropertyValue(property.SvgAttribute()),
Composite());
offset, &Easing(), SvgPropertyValue(property.SvgAttribute()), composite);
}
bool StringKeyframe::CSSPropertySpecificKeyframe::PopulateAnimatableValue(
......
......@@ -172,8 +172,10 @@ class CORE_EXPORT StringKeyframe : public Keyframe {
scoped_refptr<Keyframe> Clone() const override;
scoped_refptr<Keyframe::PropertySpecificKeyframe>
CreatePropertySpecificKeyframe(const PropertyHandle&,
double offset) const override;
CreatePropertySpecificKeyframe(
const PropertyHandle&,
EffectModel::CompositeOperation effect_composite,
double offset) const override;
bool IsStringKeyframe() const override { return true; }
......
......@@ -34,10 +34,13 @@ void TransitionKeyframe::AddKeyframePropertiesToV8Object(
scoped_refptr<Keyframe::PropertySpecificKeyframe>
TransitionKeyframe::CreatePropertySpecificKeyframe(
const PropertyHandle& property,
EffectModel::CompositeOperation effect_composite,
double offset) const {
DCHECK(property == property_);
DCHECK(offset == offset_);
return PropertySpecificKeyframe::Create(Offset(), &Easing(), Composite(),
EffectModel::CompositeOperation composite =
composite_.value_or(effect_composite);
return PropertySpecificKeyframe::Create(Offset(), &Easing(), composite,
value_->Clone(), compositor_value_);
}
......
......@@ -101,8 +101,10 @@ class CORE_EXPORT TransitionKeyframe : public Keyframe {
}
scoped_refptr<Keyframe::PropertySpecificKeyframe>
CreatePropertySpecificKeyframe(const PropertyHandle&,
double offset) const final;
CreatePropertySpecificKeyframe(
const PropertyHandle&,
EffectModel::CompositeOperation effect_composite,
double offset) const final;
PropertyHandle property_;
std::unique_ptr<TypedInterpolationValue> value_;
......
......@@ -184,8 +184,8 @@ StringKeyframeEffectModel* CreateKeyframeEffectModel(
DCHECK(!keyframes.front()->Offset());
DCHECK_EQ(keyframes.back()->Offset(), 1);
StringKeyframeEffectModel* model =
StringKeyframeEffectModel::Create(keyframes, &keyframes[0]->Easing());
StringKeyframeEffectModel* model = StringKeyframeEffectModel::Create(
keyframes, EffectModel::kCompositeReplace, &keyframes[0]->Easing());
if (animation_index > 0 && model->HasSyntheticKeyframes()) {
UseCounter::Count(element_for_scoping->GetDocument(),
WebFeature::kCSSAnimationsStackedNeutralKeyframe);
......@@ -565,7 +565,7 @@ void CSSAnimations::MaybeApplyPendingUpdate(Element* element) {
TransitionEventDelegate* event_delegate =
new TransitionEventDelegate(element, property);
EffectModel* model = inert_animation->Model();
KeyframeEffectModelBase* model = inert_animation->Model();
if (retargeted_compositor_transitions.Contains(property)) {
const std::pair<Member<KeyframeEffectReadOnly>, double>& old_transition =
......
......@@ -266,7 +266,7 @@ StringKeyframeEffectModel* HTMLMarqueeElement::CreateEffectModel(
return StringKeyframeEffectModel::Create(
{std::move(keyframe1), std::move(keyframe2)},
LinearTimingFunction::Shared());
EffectModel::kCompositeReplace, LinearTimingFunction::Shared());
}
void HTMLMarqueeElement::ContinueAnimation() {
......
......@@ -12,9 +12,11 @@
#include "core/animation/ComputedTimingProperties.h"
#include "core/animation/EffectModel.h"
#include "core/animation/ElementAnimation.h"
#include "core/animation/ElementAnimations.h"
#include "core/animation/KeyframeEffectModel.h"
#include "core/animation/KeyframeEffectReadOnly.h"
#include "core/animation/StringKeyframe.h"
#include "core/animation/css/CSSAnimations.h"
#include "core/css/CSSKeyframeRule.h"
#include "core/css/CSSKeyframesRule.h"
#include "core/css/CSSRuleList.h"
......@@ -103,8 +105,7 @@ BuildObjectForAnimationEffect(KeyframeEffectReadOnly* effect,
if (is_transition) {
// Obtain keyframes and convert keyframes back to delay
DCHECK(effect->Model()->IsKeyframeEffectModel());
const KeyframeVector& keyframes =
ToKeyframeEffectModelBase(effect->Model())->GetFrames();
const KeyframeVector& keyframes = effect->Model()->GetFrames();
if (keyframes.size() == 3) {
DCHECK(!IsNull(keyframes.at(1)->Offset()));
delay = keyframes.at(1)->Offset() * duration;
......@@ -149,8 +150,7 @@ static std::unique_ptr<protocol::Animation::KeyframesRule>
BuildObjectForAnimationKeyframes(const KeyframeEffectReadOnly* effect) {
if (!effect || !effect->Model() || !effect->Model()->IsKeyframeEffectModel())
return nullptr;
const KeyframeEffectModelBase* model =
ToKeyframeEffectModelBase(effect->Model());
const KeyframeEffectModelBase* model = effect->Model();
Vector<double> computed_offsets =
KeyframeEffectModelBase::GetComputedOffsets(model->GetFrames());
std::unique_ptr<protocol::Array<protocol::Animation::KeyframeStyle>>
......@@ -293,9 +293,8 @@ blink::Animation* InspectorAnimationAgent::AnimationClone(
KeyframeEffectReadOnly* old_effect =
ToKeyframeEffectReadOnly(animation->effect());
DCHECK(old_effect->Model()->IsKeyframeEffectModel());
KeyframeEffectModelBase* old_model =
ToKeyframeEffectModelBase(old_effect->Model());
EffectModel* new_model = nullptr;
KeyframeEffectModelBase* old_model = old_effect->Model();
KeyframeEffectModelBase* new_model = nullptr;
// Clone EffectModel.
// TODO(samli): Determine if this is an animations bug.
if (old_model->IsStringKeyframeEffectModel()) {
......@@ -383,9 +382,8 @@ Response InspectorAnimationAgent::setTiming(const String& animation_id,
String type = id_to_animation_type_.at(animation_id);
if (type == AnimationType::CSSTransition) {
KeyframeEffect* effect = ToKeyframeEffect(animation->effect());
KeyframeEffectModelBase* model = ToKeyframeEffectModelBase(effect->Model());
const TransitionKeyframeEffectModel* old_model =
ToTransitionKeyframeEffectModel(model);
ToTransitionKeyframeEffectModel(effect->Model());
// Refer to CSSAnimations::calculateTransitionUpdateForProperty() for the
// structure of transitions.
const KeyframeVector& frames = old_model->GetFrames();
......@@ -396,7 +394,7 @@ Response InspectorAnimationAgent::setTiming(const String& animation_id,
// Update delay, represented by the distance between the first two
// keyframes.
new_frames[1]->SetOffset(delay / (delay + duration));
model->SetFrames(new_frames);
effect->Model()->SetFrames(new_frames);
AnimationEffectTiming* timing = effect->timing();
UnrestrictedDoubleOrString unrestricted_duration;
......
......@@ -221,9 +221,8 @@ bool WorkletAnimation::StartOnCompositor(String* failure_message) {
// keyframe groups have been created. To ensure this we manually snapshot the
// frames in the target effect.
// TODO(smcgruer): This shouldn't be necessary - Animation doesn't do this.
ToKeyframeEffectModelBase(target_effect->Model())
->SnapshotAllCompositorKeyframes(target, target.ComputedStyleRef(),
target.ParentComputedStyle());
target_effect->Model()->SnapshotAllCompositorKeyframes(
target, target.ComputedStyleRef(), target.ParentComputedStyle());
if (!CheckElementComposited(target)) {
if (failure_message)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment