| /* |
| * Copyright (C) 2013 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following disclaimer |
| * in the documentation and/or other materials provided with the |
| * distribution. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "third_party/blink/renderer/core/animation/compositor_animations.h" |
| |
| #include <algorithm> |
| #include <cmath> |
| #include <memory> |
| |
| #include "third_party/blink/public/platform/platform.h" |
| #include "third_party/blink/renderer/core/animation/animation_effect.h" |
| #include "third_party/blink/renderer/core/animation/css/compositor_keyframe_color.h" |
| #include "third_party/blink/renderer/core/animation/css/compositor_keyframe_double.h" |
| #include "third_party/blink/renderer/core/animation/css/compositor_keyframe_filter_operations.h" |
| #include "third_party/blink/renderer/core/animation/css/compositor_keyframe_transform.h" |
| #include "third_party/blink/renderer/core/animation/css/compositor_keyframe_value.h" |
| #include "third_party/blink/renderer/core/animation/element_animations.h" |
| #include "third_party/blink/renderer/core/animation/keyframe_effect_model.h" |
| #include "third_party/blink/renderer/core/css/background_color_paint_image_generator.h" |
| #include "third_party/blink/renderer/core/css/properties/computed_style_utils.h" |
| #include "third_party/blink/renderer/core/dom/dom_node_ids.h" |
| #include "third_party/blink/renderer/core/dom/node_computed_style.h" |
| #include "third_party/blink/renderer/core/frame/local_frame.h" |
| #include "third_party/blink/renderer/core/frame/settings.h" |
| #include "third_party/blink/renderer/core/layout/layout_box.h" |
| #include "third_party/blink/renderer/core/layout/layout_box_model_object.h" |
| #include "third_party/blink/renderer/core/layout/layout_object.h" |
| #include "third_party/blink/renderer/core/layout/svg/layout_svg_transformable_container.h" |
| #include "third_party/blink/renderer/core/paint/filter_effect_builder.h" |
| #include "third_party/blink/renderer/core/paint/object_paint_properties.h" |
| #include "third_party/blink/renderer/platform/animation/animation_translation_util.h" |
| #include "third_party/blink/renderer/platform/animation/compositor_animation.h" |
| #include "third_party/blink/renderer/platform/animation/compositor_color_animation_curve.h" |
| #include "third_party/blink/renderer/platform/animation/compositor_filter_animation_curve.h" |
| #include "third_party/blink/renderer/platform/animation/compositor_filter_keyframe.h" |
| #include "third_party/blink/renderer/platform/animation/compositor_float_animation_curve.h" |
| #include "third_party/blink/renderer/platform/animation/compositor_float_keyframe.h" |
| #include "third_party/blink/renderer/platform/animation/compositor_keyframe_model.h" |
| #include "third_party/blink/renderer/platform/animation/compositor_transform_animation_curve.h" |
| #include "third_party/blink/renderer/platform/animation/compositor_transform_keyframe.h" |
| #include "third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h" |
| #include "third_party/blink/renderer/platform/runtime_enabled_features.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| bool ConsiderAnimationAsIncompatible(const Animation& animation, |
| const Animation& animation_to_add, |
| const EffectModel& effect_to_add) { |
| if (&animation == &animation_to_add) |
| return false; |
| |
| if (animation.PendingInternal()) |
| return true; |
| |
| switch (animation.CalculateAnimationPlayState()) { |
| case Animation::kIdle: |
| return false; |
| case Animation::kRunning: |
| return true; |
| case Animation::kPaused: |
| case Animation::kFinished: |
| if (Animation::HasLowerCompositeOrdering( |
| &animation, &animation_to_add, |
| Animation::CompareAnimationsOrdering::kPointerOrder)) { |
| return effect_to_add.AffectedByUnderlyingAnimations(); |
| } |
| return true; |
| default: |
| NOTREACHED(); |
| return true; |
| } |
| } |
| |
| bool IsTransformRelatedCSSProperty(const PropertyHandle property) { |
| return property.IsCSSProperty() && |
| (property.GetCSSProperty().IDEquals(CSSPropertyID::kRotate) || |
| property.GetCSSProperty().IDEquals(CSSPropertyID::kScale) || |
| property.GetCSSProperty().IDEquals(CSSPropertyID::kTransform) || |
| property.GetCSSProperty().IDEquals(CSSPropertyID::kTranslate)); |
| } |
| |
| bool IsTransformRelatedAnimation(const Element& target_element, |
| const Animation* animation) { |
| return animation->Affects(target_element, GetCSSPropertyTransform()) || |
| animation->Affects(target_element, GetCSSPropertyRotate()) || |
| animation->Affects(target_element, GetCSSPropertyScale()) || |
| animation->Affects(target_element, GetCSSPropertyTranslate()); |
| } |
| |
| bool HasIncompatibleAnimations(const Element& target_element, |
| const Animation& animation_to_add, |
| const EffectModel& effect_to_add) { |
| if (!target_element.HasAnimations()) |
| return false; |
| |
| ElementAnimations* element_animations = target_element.GetElementAnimations(); |
| DCHECK(element_animations); |
| |
| const bool affects_opacity = |
| effect_to_add.Affects(PropertyHandle(GetCSSPropertyOpacity())); |
| const bool affects_transform = effect_to_add.IsTransformRelatedEffect(); |
| const bool affects_filter = |
| effect_to_add.Affects(PropertyHandle(GetCSSPropertyFilter())); |
| const bool affects_backdrop_filter = |
| effect_to_add.Affects(PropertyHandle(GetCSSPropertyBackdropFilter())); |
| |
| for (const auto& entry : element_animations->Animations()) { |
| const Animation* attached_animation = entry.key; |
| if (!ConsiderAnimationAsIncompatible(*attached_animation, animation_to_add, |
| effect_to_add)) { |
| continue; |
| } |
| |
| if ((affects_opacity && attached_animation->Affects( |
| target_element, GetCSSPropertyOpacity())) || |
| (affects_transform && |
| IsTransformRelatedAnimation(target_element, attached_animation)) || |
| (affects_filter && |
| attached_animation->Affects(target_element, GetCSSPropertyFilter())) || |
| (affects_backdrop_filter && |
| attached_animation->Affects(target_element, |
| GetCSSPropertyBackdropFilter()))) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| void DefaultToUnsupportedProperty( |
| PropertyHandleSet* unsupported_properties, |
| const PropertyHandle& property, |
| CompositorAnimations::FailureReasons* reasons) { |
| (*reasons) |= CompositorAnimations::kUnsupportedCSSProperty; |
| if (unsupported_properties) { |
| unsupported_properties->insert(property); |
| } |
| } |
| |
| // True if it is either a no-op background-color animation, or a no-op custom |
| // property animation. |
| bool IsNoOpBGColorOrVariableAnimation(const PropertyHandle& property, |
| const LayoutObject* layout_object) { |
| // If the background color paint worklet was painted, a unique id will be |
| // generated. See BackgroundColorPaintWorklet::GetBGColorPaintWorkletParams |
| // for details. |
| // Similar to that, if a CSS paint worklet was painted, a unique id will be |
| // generated. See CSSPaintValue::GetImage for details. |
| bool has_unique_id = layout_object->FirstFragment().HasUniqueId(); |
| if (has_unique_id) |
| return false; |
| // Now the |has_unique_id| == false. |
| bool is_no_op_bgcolor_anim = |
| RuntimeEnabledFeatures::CompositeBGColorAnimationEnabled() && |
| property.GetCSSProperty().PropertyID() == CSSPropertyID::kBackgroundColor; |
| bool is_no_op_variable_anim = |
| property.GetCSSProperty().PropertyID() == CSSPropertyID::kVariable; |
| return is_no_op_variable_anim || is_no_op_bgcolor_anim; |
| } |
| |
| bool CompositedAnimationRequiresProperties(const PropertyHandle& property) { |
| if (!property.IsCSSProperty()) |
| return false; |
| switch (property.GetCSSProperty().PropertyID()) { |
| case CSSPropertyID::kOpacity: |
| case CSSPropertyID::kBackdropFilter: |
| case CSSPropertyID::kRotate: |
| case CSSPropertyID::kScale: |
| case CSSPropertyID::kTranslate: |
| case CSSPropertyID::kTransform: |
| case CSSPropertyID::kFilter: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| } // namespace |
| |
| CompositorElementIdNamespace |
| CompositorAnimations::CompositorElementNamespaceForProperty( |
| CSSPropertyID property) { |
| switch (property) { |
| case CSSPropertyID::kOpacity: |
| case CSSPropertyID::kBackdropFilter: |
| return CompositorElementIdNamespace::kPrimaryEffect; |
| case CSSPropertyID::kRotate: |
| case CSSPropertyID::kScale: |
| case CSSPropertyID::kTranslate: |
| case CSSPropertyID::kTransform: |
| return CompositorElementIdNamespace::kPrimaryTransform; |
| case CSSPropertyID::kFilter: |
| return CompositorElementIdNamespace::kEffectFilter; |
| case CSSPropertyID::kBackgroundColor: |
| case CSSPropertyID::kVariable: |
| // TODO(crbug.com/883721): Variables and background color should not |
| // require the target element to have any composited property tree nodes - |
| // i.e. should not need to check for existence of a property tree node. |
| // For now, variable animations target the primary animation target |
| // node - the effect namespace. |
| return CompositorElementIdNamespace::kPrimaryEffect; |
| default: |
| NOTREACHED(); |
| } |
| return CompositorElementIdNamespace::kPrimary; |
| } |
| |
| CompositorAnimations::FailureReasons |
| CompositorAnimations::CheckCanStartEffectOnCompositor( |
| const Timing& timing, |
| const Element& target_element, |
| const Animation* animation_to_add, |
| const EffectModel& effect, |
| const PaintArtifactCompositor* paint_artifact_compositor, |
| double animation_playback_rate, |
| PropertyHandleSet* unsupported_properties) { |
| FailureReasons reasons = kNoFailure; |
| const auto& keyframe_effect = To<KeyframeEffectModelBase>(effect); |
| |
| LayoutObject* layout_object = target_element.GetLayoutObject(); |
| if (paint_artifact_compositor) { |
| // Elements with subtrees containing will-change: contents are not |
| // composited for animations as if the contents change the tiles |
| // would need to be rerastered anyways. |
| if (layout_object && layout_object->Style()->SubtreeWillChangeContents()) { |
| reasons |= kTargetHasInvalidCompositingState; |
| } |
| } |
| |
| PropertyHandleSet properties = keyframe_effect.Properties(); |
| unsigned transform_property_count = 0; |
| for (const auto& property : properties) { |
| if (!property.IsCSSProperty()) { |
| // None of the below reasons make any sense if |property| isn't CSS, so we |
| // skip the rest of the loop in that case. |
| reasons |= kAnimationAffectsNonCSSProperties; |
| continue; |
| } |
| |
| if (IsTransformRelatedCSSProperty(property)) { |
| // We use this later in computing element IDs too. |
| if (layout_object && !layout_object->IsTransformApplicable()) { |
| reasons |= kTransformRelatedPropertyCannotBeAcceleratedOnTarget; |
| } |
| if (const auto* svg_element = DynamicTo<SVGElement>(target_element)) { |
| reasons |= |
| CheckCanStartTransformAnimationOnCompositorForSVG(*svg_element); |
| } |
| transform_property_count++; |
| } |
| |
| const PropertySpecificKeyframeVector& keyframes = |
| *keyframe_effect.GetPropertySpecificKeyframes(property); |
| DCHECK_GE(keyframes.size(), 2U); |
| for (const auto& keyframe : keyframes) { |
| if (keyframe->Composite() != EffectModel::kCompositeReplace && |
| !keyframe->IsNeutral()) { |
| reasons |= kEffectHasNonReplaceCompositeMode; |
| } |
| |
| // FIXME: Determine candidacy based on the CSSValue instead of a snapshot |
| // CompositorKeyframeValue. |
| switch (property.GetCSSProperty().PropertyID()) { |
| case CSSPropertyID::kOpacity: |
| break; |
| case CSSPropertyID::kRotate: |
| case CSSPropertyID::kScale: |
| case CSSPropertyID::kTranslate: |
| case CSSPropertyID::kTransform: |
| // TODO(crbug.com/389359): Currently only CSS boxes support |
| // compositing box-size-dependent transform animations. Once such |
| // support is fully working for SVG, this section (and the flag) |
| // should be removed. |
| if (!RuntimeEnabledFeatures::CompositeRelativeKeyframesEnabled() || |
| (layout_object && layout_object->IsSVGChild())) { |
| if (keyframe->GetCompositorKeyframeValue() && |
| To<CompositorKeyframeTransform>( |
| keyframe->GetCompositorKeyframeValue()) |
| ->GetTransformOperations() |
| .BoxSizeDependencies()) { |
| reasons |= kTransformRelatedPropertyDependsOnBoxSize; |
| } |
| } |
| break; |
| case CSSPropertyID::kFilter: |
| if (keyframe->GetCompositorKeyframeValue() && |
| To<CompositorKeyframeFilterOperations>( |
| keyframe->GetCompositorKeyframeValue()) |
| ->Operations() |
| .HasFilterThatMovesPixels()) { |
| reasons |= kFilterRelatedPropertyMayMovePixels; |
| } |
| break; |
| case CSSPropertyID::kBackdropFilter: |
| // Backdrop-filter pixel moving filters do not change the layer bounds |
| // like regular filters do, so they can still be composited. |
| break; |
| case CSSPropertyID::kBackgroundColor: { |
| bool background_transfers_to_view = false; |
| Animation* compositable_animation = nullptr; |
| if (RuntimeEnabledFeatures::CompositeBGColorAnimationEnabled()) { |
| // Not having a layout object is a reason for not compositing marked |
| // in CompositorAnimations::CheckCanStartElementOnCompositor. |
| if (!layout_object) |
| continue; |
| BackgroundColorPaintImageGenerator* generator = |
| target_element.GetDocument() |
| .GetFrame() |
| ->GetBackgroundColorPaintImageGenerator(); |
| // The generator may be null in tests. |
| if (generator) { |
| compositable_animation = |
| generator->GetAnimationIfCompositable(&target_element); |
| } |
| // When this is true, we have a background-color animation in the |
| // body element, while the view is responsible for painting the |
| // body's background. In this case, we need to let the |
| // background-color animation run on the main thread because the |
| // wbody is not painted ith BackgroundColorPaintWorklet. |
| background_transfers_to_view = |
| target_element.GetLayoutBoxModelObject() && |
| target_element.GetLayoutBoxModelObject() |
| ->BackgroundTransfersToView(); |
| } |
| // The table rows and table cols are painted into table cells, which |
| // means their background is never painted using |
| // BackgroundColorPaintWorklet, as a result, we should not composite |
| // the background color animation on the table rows or cols. |
| if (!RuntimeEnabledFeatures::CompositeBGColorAnimationEnabled() || |
| layout_object->IsLayoutTableCol() || |
| layout_object->IsTableRow() || background_transfers_to_view || |
| !compositable_animation) { |
| DefaultToUnsupportedProperty(unsupported_properties, property, |
| &reasons); |
| } |
| break; |
| } |
| case CSSPropertyID::kVariable: { |
| // Custom properties are supported only in the case of |
| // OffMainThreadCSSPaintEnabled, and even then only for some specific |
| // property types. Otherwise they are treated as unsupported. |
| const CompositorKeyframeValue* keyframe_value = |
| keyframe->GetCompositorKeyframeValue(); |
| if (keyframe_value) { |
| DCHECK(RuntimeEnabledFeatures::OffMainThreadCSSPaintEnabled()); |
| DCHECK(keyframe_value->IsDouble() || keyframe_value->IsColor()); |
| // If a custom property is not used by CSS Paint, then we should not |
| // support that on the compositor thread. |
| if (layout_object && layout_object->Style() && |
| !layout_object->Style()->HasCSSPaintImagesUsingCustomProperty( |
| property.CustomPropertyName(), |
| layout_object->GetDocument())) { |
| DefaultToUnsupportedProperty(unsupported_properties, property, |
| &reasons); |
| } |
| // TODO: Add support for keyframes containing different types |
| if (!keyframes.front() || |
| !keyframes.front()->GetCompositorKeyframeValue() || |
| keyframes.front()->GetCompositorKeyframeValue()->GetType() != |
| keyframe_value->GetType()) { |
| reasons |= kMixedKeyframeValueTypes; |
| } |
| } else { |
| // We skip the rest of the loop in this case for the same reason as |
| // unsupported CSS properties - see below. |
| DefaultToUnsupportedProperty(unsupported_properties, property, |
| &reasons); |
| continue; |
| } |
| break; |
| } |
| default: |
| // We skip the rest of the loop in this case because |
| // |GetCompositorKeyframeValue()| will be false so we will |
| // accidentally count this as kInvalidAnimationOrEffect as well. |
| DefaultToUnsupportedProperty(unsupported_properties, property, |
| &reasons); |
| continue; |
| } |
| |
| // If an element does not have style, then it will never have taken a |
| // snapshot of its (non-existent) value for the compositor to use. |
| if (!keyframe->GetCompositorKeyframeValue()) { |
| reasons |= kInvalidAnimationOrEffect; |
| } |
| } |
| } |
| |
| if (CompositorPropertyAnimationsHaveNoEffect(target_element, effect, |
| paint_artifact_compositor)) { |
| reasons |= kCompositorPropertyAnimationsHaveNoEffect; |
| } |
| |
| // TODO: Support multiple transform property animations on the compositor |
| if (transform_property_count > 1) |
| reasons |= kMultipleTransformAnimationsOnSameTarget; |
| |
| if (animation_to_add && |
| HasIncompatibleAnimations(target_element, *animation_to_add, effect)) { |
| reasons |= kTargetHasIncompatibleAnimations; |
| } |
| |
| CompositorTiming out; |
| base::TimeDelta time_offset = |
| animation_to_add ? animation_to_add->ComputeCompositorTimeOffset() |
| : base::TimeDelta(); |
| if (!ConvertTimingForCompositor(timing, time_offset, out, |
| animation_playback_rate)) { |
| reasons |= kEffectHasUnsupportedTimingParameters; |
| } |
| |
| return reasons; |
| } |
| |
| bool CompositorAnimations::CompositorPropertyAnimationsHaveNoEffect( |
| const Element& target_element, |
| const EffectModel& effect, |
| const PaintArtifactCompositor* paint_artifact_compositor) { |
| LayoutObject* layout_object = target_element.GetLayoutObject(); |
| if (!layout_object || !layout_object->FirstFragment().PaintProperties()) |
| return false; |
| |
| if (!paint_artifact_compositor) { |
| // TODO(pdr): This should return true. This likely only affects tests. |
| return false; |
| } |
| |
| bool any_compositor_properties_missing = false; |
| bool any_compositor_properties_present = false; |
| |
| const auto& keyframe_effect = To<KeyframeEffectModelBase>(effect); |
| const auto& groups = keyframe_effect.GetPropertySpecificKeyframeGroups(); |
| for (const PropertyHandle& property : groups.Keys()) { |
| if (!CompositedAnimationRequiresProperties(property)) |
| continue; |
| |
| CompositorElementId target_element_id = |
| CompositorElementIdFromUniqueObjectId( |
| layout_object->UniqueId(), |
| CompositorAnimations::CompositorElementNamespaceForProperty( |
| property.GetCSSProperty().PropertyID())); |
| DCHECK(target_element_id); |
| if (paint_artifact_compositor->HasComposited(target_element_id)) |
| any_compositor_properties_present = true; |
| else |
| any_compositor_properties_missing = true; |
| } |
| |
| // Because animations are a direct compositing reason for paint properties, |
| // the only case when we wouldn't have compositor paint properties if when |
| // they were optimized out due to not having an effect. An example of this is |
| // hidden animations that do not paint. |
| if (any_compositor_properties_missing) { |
| // Because animations create all properties (crbug.com/900241), we should |
| // either have all properties or be missing all properties. |
| DCHECK(!any_compositor_properties_present); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| CompositorAnimations::FailureReasons |
| CompositorAnimations::CheckCanStartElementOnCompositor( |
| const Element& target_element, |
| const EffectModel& model) { |
| FailureReasons reasons = kNoFailure; |
| |
| // Both of these checks are required. It is legal to enable the compositor |
| // thread but disable threaded animations, and there are situations where |
| // threaded animations are enabled globally but this particular LocalFrame |
| // does not have a compositor (e.g. for overlays). |
| const Settings* settings = target_element.GetDocument().GetSettings(); |
| if ((settings && !settings->GetAcceleratedCompositingEnabled()) || |
| !Platform::Current()->IsThreadedAnimationEnabled()) { |
| reasons |= kAcceleratedAnimationsDisabled; |
| } |
| |
| if (const auto* svg_element = DynamicTo<SVGElement>(target_element)) |
| reasons |= CheckCanStartSVGElementOnCompositor(*svg_element); |
| |
| if (const auto* layout_object = target_element.GetLayoutObject()) { |
| // We query paint property tree state below to determine whether the |
| // animation is compositable. TODO(crbug.com/676456): There is a known |
| // lifecycle violation where an animation can be cancelled during style |
| // update. See CompositorAnimations::CancelAnimationOnCompositor(). |
| // When this is fixed we would like to enable the DCHECK below. |
| // DCHECK_GE(GetDocument().Lifecycle().GetState(), |
| // DocumentLifecycle::kPrePaintClean); |
| bool has_direct_compositing_reasons = false; |
| if (layout_object->FirstFragment().NextFragment()) { |
| // Composited animation on multiple fragments is not supported. |
| reasons |= kTargetHasInvalidCompositingState; |
| } else if (const auto* paint_properties = |
| layout_object->FirstFragment().PaintProperties()) { |
| const auto* transform = paint_properties->Transform(); |
| const auto* effect = paint_properties->Effect(); |
| has_direct_compositing_reasons = |
| (transform && transform->HasDirectCompositingReasons()) || |
| (effect && effect->HasDirectCompositingReasons()); |
| } |
| if (!has_direct_compositing_reasons && |
| To<KeyframeEffectModelBase>(model).RequiresPropertyNode()) { |
| reasons |= kTargetHasInvalidCompositingState; |
| } |
| } else { |
| reasons |= kTargetHasInvalidCompositingState; |
| } |
| |
| return reasons; |
| } |
| |
| // TODO(crbug.com/809685): consider refactor this function. |
| CompositorAnimations::FailureReasons |
| CompositorAnimations::CheckCanStartAnimationOnCompositor( |
| const Timing& timing, |
| const Element& target_element, |
| const Animation* animation_to_add, |
| const EffectModel& effect, |
| const PaintArtifactCompositor* paint_artifact_compositor, |
| double animation_playback_rate, |
| PropertyHandleSet* unsupported_properties) { |
| FailureReasons reasons = CheckCanStartEffectOnCompositor( |
| timing, target_element, animation_to_add, effect, |
| paint_artifact_compositor, animation_playback_rate, |
| unsupported_properties); |
| return reasons | CheckCanStartElementOnCompositor(target_element, effect); |
| } |
| |
| void CompositorAnimations::CancelIncompatibleAnimationsOnCompositor( |
| const Element& target_element, |
| const Animation& animation_to_add, |
| const EffectModel& effect_to_add) { |
| const bool affects_opacity = |
| effect_to_add.Affects(PropertyHandle(GetCSSPropertyOpacity())); |
| const bool affects_transform = effect_to_add.IsTransformRelatedEffect(); |
| const bool affects_filter = |
| effect_to_add.Affects(PropertyHandle(GetCSSPropertyFilter())); |
| const bool affects_backdrop_filter = |
| effect_to_add.Affects(PropertyHandle(GetCSSPropertyBackdropFilter())); |
| |
| if (!target_element.HasAnimations()) |
| return; |
| |
| ElementAnimations* element_animations = target_element.GetElementAnimations(); |
| DCHECK(element_animations); |
| |
| for (const auto& entry : element_animations->Animations()) { |
| Animation* attached_animation = entry.key; |
| if (!ConsiderAnimationAsIncompatible(*attached_animation, animation_to_add, |
| effect_to_add)) { |
| continue; |
| } |
| |
| if ((affects_opacity && attached_animation->Affects( |
| target_element, GetCSSPropertyOpacity())) || |
| (affects_transform && |
| IsTransformRelatedAnimation(target_element, attached_animation)) || |
| (affects_filter && |
| attached_animation->Affects(target_element, GetCSSPropertyFilter())) || |
| (affects_backdrop_filter && |
| attached_animation->Affects(target_element, |
| GetCSSPropertyBackdropFilter()))) { |
| attached_animation->CancelAnimationOnCompositor(); |
| } |
| } |
| } |
| |
| void CompositorAnimations::StartAnimationOnCompositor( |
| const Element& element, |
| int group, |
| absl::optional<double> start_time, |
| base::TimeDelta time_offset, |
| const Timing& timing, |
| const Animation* animation, |
| CompositorAnimation& compositor_animation, |
| const EffectModel& effect, |
| Vector<int>& started_keyframe_model_ids, |
| double animation_playback_rate) { |
| DCHECK(started_keyframe_model_ids.IsEmpty()); |
| // TODO(petermayo): Pass the PaintArtifactCompositor before |
| // BlinkGenPropertyTrees is always on. |
| DCHECK_EQ( |
| CheckCanStartAnimationOnCompositor(timing, element, animation, effect, |
| nullptr, animation_playback_rate), |
| kNoFailure); |
| |
| const auto& keyframe_effect = To<KeyframeEffectModelBase>(effect); |
| |
| Vector<std::unique_ptr<CompositorKeyframeModel>> keyframe_models; |
| GetAnimationOnCompositor(element, timing, group, start_time, time_offset, |
| keyframe_effect, keyframe_models, |
| animation_playback_rate); |
| DCHECK(!keyframe_models.IsEmpty()); |
| for (auto& compositor_keyframe_model : keyframe_models) { |
| int id = compositor_keyframe_model->Id(); |
| compositor_animation.AddKeyframeModel(std::move(compositor_keyframe_model)); |
| started_keyframe_model_ids.push_back(id); |
| } |
| DCHECK(!started_keyframe_model_ids.IsEmpty()); |
| } |
| |
| void CompositorAnimations::CancelAnimationOnCompositor( |
| const Element& element, |
| CompositorAnimation* compositor_animation, |
| int id, |
| const EffectModel& model) { |
| if (CheckCanStartElementOnCompositor(element, model) != kNoFailure) { |
| // When an element is being detached, we cancel any associated |
| // Animations for CSS animations. But by the time we get |
| // here the mapping will have been removed. |
| // FIXME: Defer remove/pause operations until after the |
| // compositing update. |
| return; |
| } |
| if (compositor_animation) |
| compositor_animation->RemoveKeyframeModel(id); |
| } |
| |
| void CompositorAnimations::PauseAnimationForTestingOnCompositor( |
| const Element& element, |
| const Animation& animation, |
| int id, |
| base::TimeDelta pause_time, |
| const EffectModel& model) { |
| DCHECK_EQ(CheckCanStartElementOnCompositor(element, model), kNoFailure); |
| CompositorAnimation* compositor_animation = |
| animation.GetCompositorAnimation(); |
| DCHECK(compositor_animation); |
| compositor_animation->PauseKeyframeModel(id, pause_time); |
| } |
| |
| void CompositorAnimations::AttachCompositedLayers( |
| Element& element, |
| CompositorAnimation* compositor_animation) { |
| if (!compositor_animation) |
| return; |
| |
| CompositorElementIdNamespace element_id_namespace = |
| CompositorElementIdNamespace::kPrimary; |
| // We create an animation namespace element id when an element has created all |
| // property tree nodes which may be required by the keyframe effects. The |
| // animation affects multiple element ids, and one is pushed each |
| // KeyframeModel. See |GetAnimationOnCompositor|. We use the kPrimaryEffect |
| // node to know if nodes have been created for animations. |
| element_id_namespace = CompositorElementIdNamespace::kPrimaryEffect; |
| compositor_animation->AttachElement(CompositorElementIdFromUniqueObjectId( |
| element.GetLayoutObject()->UniqueId(), element_id_namespace)); |
| } |
| |
| bool CompositorAnimations::ConvertTimingForCompositor( |
| const Timing& timing, |
| base::TimeDelta time_offset, |
| CompositorTiming& out, |
| double animation_playback_rate) { |
| timing.AssertValid(); |
| |
| if (animation_playback_rate == 0) |
| return false; |
| |
| // FIXME: Compositor does not know anything about endDelay. |
| if (!timing.end_delay.is_zero()) |
| return false; |
| |
| if (!timing.iteration_duration || !timing.iteration_count || |
| timing.iteration_duration->is_zero() || |
| timing.iteration_duration->is_max()) |
| return false; |
| |
| // Compositor's time offset is positive for seeking into the animation. |
| DCHECK(animation_playback_rate); |
| double delay = |
| animation_playback_rate > 0 ? timing.start_delay.InSecondsF() : 0; |
| |
| base::TimeDelta scaled_delay = |
| base::TimeDelta::FromSecondsD(delay / animation_playback_rate); |
| |
| // Arithmetic operations involving a value that is effectively +/-infinity |
| // result in a value that is +/-infinity or undefined. Check before computing |
| // the scaled time offset to guard against the following: |
| // infinity - infinity or |
| // -infinity + infinity |
| // The result of either of these edge cases is undefined. |
| if (scaled_delay.is_max() || scaled_delay.is_min()) |
| return false; |
| |
| out.scaled_time_offset = -scaled_delay + time_offset; |
| // Delay is effectively +/- infinity. |
| if (out.scaled_time_offset.is_max() || out.scaled_time_offset.is_min()) |
| return false; |
| |
| out.adjusted_iteration_count = std::isfinite(timing.iteration_count) |
| ? timing.iteration_count |
| : std::numeric_limits<double>::infinity(); |
| out.scaled_duration = timing.iteration_duration.value(); |
| out.direction = timing.direction; |
| |
| out.playback_rate = animation_playback_rate; |
| out.fill_mode = timing.fill_mode == Timing::FillMode::AUTO |
| ? Timing::FillMode::NONE |
| : timing.fill_mode; |
| out.iteration_start = timing.iteration_start; |
| |
| DCHECK_GT(out.scaled_duration, AnimationTimeDelta()); |
| DCHECK(out.adjusted_iteration_count > 0 || |
| out.adjusted_iteration_count == |
| std::numeric_limits<double>::infinity()); |
| DCHECK(std::isfinite(out.playback_rate) && out.playback_rate); |
| DCHECK_GE(out.iteration_start, 0); |
| |
| return true; |
| } |
| |
| namespace { |
| |
| void AddKeyframeToCurve(CompositorFilterAnimationCurve& curve, |
| Keyframe::PropertySpecificKeyframe* keyframe, |
| const CompositorKeyframeValue* value, |
| const TimingFunction& keyframe_timing_function) { |
| FilterEffectBuilder builder(FloatRect(), 1); |
| CompositorFilterKeyframe filter_keyframe( |
| keyframe->Offset(), |
| builder.BuildFilterOperations( |
| To<CompositorKeyframeFilterOperations>(value)->Operations()), |
| keyframe_timing_function); |
| curve.AddKeyframe(filter_keyframe); |
| } |
| |
| void AddKeyframeToCurve(CompositorFloatAnimationCurve& curve, |
| Keyframe::PropertySpecificKeyframe* keyframe, |
| const CompositorKeyframeValue* value, |
| const TimingFunction& keyframe_timing_function) { |
| CompositorFloatKeyframe float_keyframe( |
| keyframe->Offset(), To<CompositorKeyframeDouble>(value)->ToDouble(), |
| keyframe_timing_function); |
| curve.AddKeyframe(float_keyframe); |
| } |
| |
| void AddKeyframeToCurve(CompositorColorAnimationCurve& curve, |
| Keyframe::PropertySpecificKeyframe* keyframe, |
| const CompositorKeyframeValue* value, |
| const TimingFunction& keyframe_timing_function) { |
| CompositorColorKeyframe color_keyframe( |
| keyframe->Offset(), To<CompositorKeyframeColor>(value)->ToColor(), |
| keyframe_timing_function); |
| curve.AddKeyframe(color_keyframe); |
| } |
| |
| void AddKeyframeToCurve(CompositorTransformAnimationCurve& curve, |
| Keyframe::PropertySpecificKeyframe* keyframe, |
| const CompositorKeyframeValue* value, |
| const TimingFunction& keyframe_timing_function, |
| const FloatSize& box_size) { |
| CompositorTransformOperations ops; |
| ToCompositorTransformOperations( |
| To<CompositorKeyframeTransform>(value)->GetTransformOperations(), &ops, |
| box_size); |
| |
| CompositorTransformKeyframe transform_keyframe( |
| keyframe->Offset(), std::move(ops), keyframe_timing_function); |
| curve.AddKeyframe(transform_keyframe); |
| } |
| |
| template <typename PlatformAnimationCurveType, typename... Args> |
| void AddKeyframesToCurve(PlatformAnimationCurveType& curve, |
| const PropertySpecificKeyframeVector& keyframes, |
| Args... parameters) { |
| Keyframe::PropertySpecificKeyframe* last_keyframe = keyframes.back(); |
| for (const auto& keyframe : keyframes) { |
| const TimingFunction* keyframe_timing_function = nullptr; |
| // Ignore timing function of last frame. |
| if (keyframe == last_keyframe) |
| keyframe_timing_function = LinearTimingFunction::Shared(); |
| else |
| keyframe_timing_function = &keyframe->Easing(); |
| |
| const CompositorKeyframeValue* value = |
| keyframe->GetCompositorKeyframeValue(); |
| AddKeyframeToCurve(curve, keyframe, value, *keyframe_timing_function, |
| parameters...); |
| } |
| } |
| |
| } // namespace |
| |
| void CompositorAnimations::GetAnimationOnCompositor( |
| const Element& target_element, |
| const Timing& timing, |
| int group, |
| absl::optional<double> start_time, |
| base::TimeDelta time_offset, |
| const KeyframeEffectModelBase& effect, |
| Vector<std::unique_ptr<CompositorKeyframeModel>>& keyframe_models, |
| double animation_playback_rate) { |
| DCHECK(keyframe_models.IsEmpty()); |
| CompositorTiming compositor_timing; |
| bool timing_valid = ConvertTimingForCompositor( |
| timing, time_offset, compositor_timing, animation_playback_rate); |
| ALLOW_UNUSED_LOCAL(timing_valid); |
| |
| PropertyHandleSet properties = effect.Properties(); |
| DCHECK(!properties.IsEmpty()); |
| for (const auto& property : properties) { |
| AtomicString custom_property_name = ""; |
| CompositorPaintWorkletInput::NativePropertyType native_property_type = |
| CompositorPaintWorkletInput::NativePropertyType::kInvalid; |
| // If the animation duration is infinite, it doesn't make sense to scale |
| // the keyframe offset, so use a scale of 1.0. This is connected to |
| // the known issue of how the Web Animations spec handles infinite |
| // durations. See https://github.com/w3c/web-animations/issues/142 |
| double scale = compositor_timing.scaled_duration.InSecondsF(); |
| if (!std::isfinite(scale)) |
| scale = 1.0; |
| const PropertySpecificKeyframeVector& values = |
| *effect.GetPropertySpecificKeyframes(property); |
| |
| compositor_target_property::Type target_property; |
| std::unique_ptr<CompositorAnimationCurve> curve; |
| DCHECK(timing.timing_function); |
| switch (property.GetCSSProperty().PropertyID()) { |
| case CSSPropertyID::kOpacity: { |
| target_property = compositor_target_property::OPACITY; |
| auto float_curve = std::make_unique<CompositorFloatAnimationCurve>(); |
| AddKeyframesToCurve(*float_curve, values); |
| float_curve->SetTimingFunction(*timing.timing_function); |
| float_curve->SetScaledDuration(scale); |
| curve = std::move(float_curve); |
| break; |
| } |
| case CSSPropertyID::kFilter: |
| case CSSPropertyID::kBackdropFilter: { |
| target_property = compositor_target_property::FILTER; |
| if (property.GetCSSProperty().PropertyID() == |
| CSSPropertyID::kBackdropFilter) { |
| target_property = compositor_target_property::BACKDROP_FILTER; |
| } |
| auto filter_curve = std::make_unique<CompositorFilterAnimationCurve>(); |
| AddKeyframesToCurve(*filter_curve, values); |
| filter_curve->SetTimingFunction(*timing.timing_function); |
| filter_curve->SetScaledDuration(scale); |
| curve = std::move(filter_curve); |
| break; |
| } |
| case CSSPropertyID::kRotate: |
| case CSSPropertyID::kScale: |
| case CSSPropertyID::kTranslate: |
| case CSSPropertyID::kTransform: { |
| FloatSize box_size = ComputedStyleUtils::ReferenceBoxForTransform( |
| *target_element.GetLayoutObject()) |
| .Size(); |
| target_property = compositor_target_property::TRANSFORM; |
| auto transform_curve = |
| std::make_unique<CompositorTransformAnimationCurve>(); |
| AddKeyframesToCurve(*transform_curve, values, box_size); |
| transform_curve->SetTimingFunction(*timing.timing_function); |
| transform_curve->SetScaledDuration(scale); |
| curve = std::move(transform_curve); |
| break; |
| } |
| case CSSPropertyID::kBackgroundColor: { |
| native_property_type = |
| CompositorPaintWorkletInput::NativePropertyType::kBackgroundColor; |
| auto float_curve = std::make_unique<CompositorFloatAnimationCurve>(); |
| target_property = compositor_target_property::NATIVE_PROPERTY; |
| AddKeyframesToCurve(*float_curve, values); |
| float_curve->SetTimingFunction(*timing.timing_function); |
| float_curve->SetScaledDuration(scale); |
| curve = std::move(float_curve); |
| break; |
| } |
| case CSSPropertyID::kVariable: { |
| DCHECK(RuntimeEnabledFeatures::OffMainThreadCSSPaintEnabled()); |
| custom_property_name = property.CustomPropertyName(); |
| target_property = compositor_target_property::CSS_CUSTOM_PROPERTY; |
| |
| // Create curve based on the keyframe value type |
| if (values.front()->GetCompositorKeyframeValue()->IsColor()) { |
| auto color_curve = std::make_unique<CompositorColorAnimationCurve>(); |
| AddKeyframesToCurve(*color_curve, values); |
| color_curve->SetTimingFunction(*timing.timing_function); |
| color_curve->SetScaledDuration(scale); |
| curve = std::move(color_curve); |
| } else { |
| auto float_curve = std::make_unique<CompositorFloatAnimationCurve>(); |
| AddKeyframesToCurve(*float_curve, values); |
| float_curve->SetTimingFunction(*timing.timing_function); |
| float_curve->SetScaledDuration(scale); |
| curve = std::move(float_curve); |
| } |
| break; |
| } |
| default: |
| NOTREACHED(); |
| continue; |
| } |
| DCHECK(curve.get()); |
| |
| std::unique_ptr<CompositorKeyframeModel> keyframe_model; |
| if (!custom_property_name.IsEmpty()) { |
| keyframe_model = std::make_unique<CompositorKeyframeModel>( |
| *curve, target_property, 0, group, std::move(custom_property_name)); |
| } else if (native_property_type != |
| CompositorPaintWorkletInput::NativePropertyType::kInvalid) { |
| keyframe_model = std::make_unique<CompositorKeyframeModel>( |
| *curve, target_property, 0, group, native_property_type); |
| } else { |
| keyframe_model = std::make_unique<CompositorKeyframeModel>( |
| *curve, target_property, 0, group); |
| } |
| |
| if (start_time) |
| keyframe_model->SetStartTime(start_time.value()); |
| |
| // By default, it is a kInvalidElementId. |
| CompositorElementId id; |
| if (!IsNoOpBGColorOrVariableAnimation(property, |
| target_element.GetLayoutObject())) { |
| id = CompositorElementIdFromUniqueObjectId( |
| target_element.GetLayoutObject()->UniqueId(), |
| CompositorElementNamespaceForProperty( |
| property.GetCSSProperty().PropertyID())); |
| } |
| keyframe_model->SetElementId(id); |
| keyframe_model->SetIterations(compositor_timing.adjusted_iteration_count); |
| keyframe_model->SetIterationStart(compositor_timing.iteration_start); |
| keyframe_model->SetTimeOffset(compositor_timing.scaled_time_offset); |
| keyframe_model->SetDirection(compositor_timing.direction); |
| keyframe_model->SetPlaybackRate(compositor_timing.playback_rate); |
| keyframe_model->SetFillMode(compositor_timing.fill_mode); |
| keyframe_models.push_back(std::move(keyframe_model)); |
| } |
| DCHECK(!keyframe_models.IsEmpty()); |
| } |
| |
| bool CompositorAnimations::CheckUsesCompositedScrolling(Node* target) { |
| if (!target) |
| return false; |
| DCHECK(target->GetDocument().Lifecycle().GetState() >= |
| DocumentLifecycle::kCompositingAssignmentsClean); |
| auto* layout_box_model_object = target->GetLayoutBoxModelObject(); |
| if (!layout_box_model_object) |
| return false; |
| return layout_box_model_object->UsesCompositedScrolling(); |
| } |
| |
| CompositorAnimations::FailureReasons |
| CompositorAnimations::CheckCanStartSVGElementOnCompositor( |
| const SVGElement& svg_element) { |
| FailureReasons reasons = kNoFailure; |
| if (svg_element.HasNonCSSPropertyAnimations()) |
| reasons |= kTargetHasIncompatibleAnimations; |
| if (!svg_element.InstancesForElement().IsEmpty()) { |
| // TODO(crbug.com/785246): Currently when an SVGElement has svg:use |
| // instances, each instance gets style from the original element, using |
| // the original element's animation (thus the animation affects |
| // transform nodes). This should be removed once instances style |
| // themmselves and create their own blink::Animation objects for CSS |
| // animations and transitions. |
| reasons |= kTargetHasInvalidCompositingState; |
| } |
| return reasons; |
| } |
| |
| CompositorAnimations::FailureReasons |
| CompositorAnimations::CheckCanStartTransformAnimationOnCompositorForSVG( |
| const SVGElement& svg_element) { |
| FailureReasons reasons = kNoFailure; |
| if (const auto* layout_object = svg_element.GetLayoutObject()) { |
| if (layout_object->IsSVGViewportContainer()) { |
| // Nested SVG doesn't support transforms for now. |
| reasons |= kTransformRelatedPropertyCannotBeAcceleratedOnTarget; |
| } else if (layout_object->StyleRef().EffectiveZoom() != 1) { |
| // TODO(crbug.com/1186312): Composited transform animation with non-1 |
| // effective zoom is incorrectly scaled for now. |
| // TODO(crbug.com/1134775): If a foreignObject's effect zoom is not 1, |
| // its transform node contains an additional scale which would be removed |
| // by composited animation. |
| reasons |= kTransformRelatedPropertyCannotBeAcceleratedOnTarget; |
| } else if (layout_object->IsSVGTransformableContainer() && |
| !To<LayoutSVGTransformableContainer>(layout_object) |
| ->AdditionalTranslation() |
| .IsZero()) { |
| // TODO(crbug.com/1134775): Similarly, composited animation would also |
| // remove the additional translation of LayoutSVGTransformableContainer. |
| reasons |= kTransformRelatedPropertyCannotBeAcceleratedOnTarget; |
| } else if (layout_object->TransformAffectsVectorEffect()) { |
| // If the subtree has vector effect, transform affects paint thus |
| // animation can not be composited. |
| reasons |= kTransformRelatedPropertyCannotBeAcceleratedOnTarget; |
| } |
| } |
| return reasons; |
| } |
| |
| bool CompositorAnimations::CanStartTransformAnimationOnCompositorForSVG( |
| const SVGElement& svg_element) { |
| return CheckCanStartSVGElementOnCompositor(svg_element) == kNoFailure && |
| CheckCanStartTransformAnimationOnCompositorForSVG(svg_element) == |
| kNoFailure; |
| } |
| |
| } // namespace blink |