Source/WebCore/ChangeLog

 12020-05-10 Antoine Quint <graouts@apple.com>
 2
 3 [Web Animations] Refactor animation comparison by composite order in a single utility function
 4 https://bugs.webkit.org/show_bug.cgi?id=211695
 5
 6 Reviewed by NOBODY (OOPS!).
 7
 8 We used to split sorting of animations by composite order across several functions and files. Specifically,
 9 DocumentTimeline::getAnimations() would first collect animations by class (CSS Transitions, then CSS
 10 Animations, then JS-originated animations), and then sort each class, calling into the static function
 11 compareDeclarativeAnimationOwningElementPositionsInDocumentTreeOrder() in some cases and into the
 12 WebAnimationUtilities compareAnimationsByCompositeOrder() function in other.
 13
 14 Since we need to be able to sort animations by composite order in other situations, for instance when sorting
 15 events when updating animations and sending events (which we will do in a future patch), we refactor all
 16 of the comparison logic into compareAnimationsByCompositeOrder(), removing the need to provide an AnimationList,
 17 which is specific to the case where we know we are comparing CSSAnimation objects targeting a shared element.
 18
 19 This effectively empties DocumentTimeline::getAnimations() so we remove this function and filter relevant
 20 animations in Document::matchingAnimations() and call compareAnimationsByCompositeOrder() before returning
 21 the compiled animations.
 22
 23 No new tests since there is no change of behavior.
 24
 25 * animation/DocumentTimeline.cpp:
 26 (WebCore::compareDeclarativeAnimationOwningElementPositionsInDocumentTreeOrder): Deleted.
 27 (WebCore::DocumentTimeline::getAnimations const): Deleted.
 28 * animation/DocumentTimeline.h:
 29 * animation/KeyframeEffectStack.cpp:
 30 (WebCore::KeyframeEffectStack::ensureEffectsAreSorted):
 31 * animation/WebAnimation.cpp:
 32 (WebCore::WebAnimation::commitStyles):
 33 * animation/WebAnimationUtilities.cpp:
 34 (WebCore::compareDeclarativeAnimationOwningElementPositionsInDocumentTreeOrder):
 35 (WebCore::compareCSSTransitions):
 36 (WebCore::compareCSSAnimations):
 37 (WebCore::compareAnimationsByCompositeOrder):
 38 * animation/WebAnimationUtilities.h:
 39 * dom/Document.cpp:
 40 (WebCore::Document::matchingAnimations):
 41
1422020-05-07 Simon Fraser <simon.fraser@apple.com>
243
344 MayBegin wheel event in a <select> doesn't flash the scrollers

Source/WebCore/animation/DocumentTimeline.cpp

2727#include "DocumentTimeline.h"
2828
2929#include "AnimationEventBase.h"
30 #include "CSSAnimation.h"
3130#include "CSSTransition.h"
3231#include "DeclarativeAnimation.h"
3332#include "Document.h"

3837#include "KeyframeEffectStack.h"
3938#include "Node.h"
4039#include "Page.h"
41 #include "PseudoElement.h"
4240#include "RenderElement.h"
4341#include "RenderLayer.h"
4442#include "RenderLayerBacking.h"

@@void DocumentTimeline::detachFromDocument()
10098 m_document = nullptr;
10199}
102100
103 static inline bool compareDeclarativeAnimationOwningElementPositionsInDocumentTreeOrder(Element* lhsOwningElement, Element* rhsOwningElement)
104 {
105  // With regard to pseudo-elements, the sort order is as follows:
106  // - element
107  // - ::before
108  // - ::after
109  // - element children
110 
111  // We could be comparing two pseudo-elements that are hosted on the same element.
112  if (is<PseudoElement>(lhsOwningElement) && is<PseudoElement>(rhsOwningElement)) {
113  auto* lhsPseudoElement = downcast<PseudoElement>(lhsOwningElement);
114  auto* rhsPseudoElement = downcast<PseudoElement>(rhsOwningElement);
115  if (lhsPseudoElement->hostElement() == rhsPseudoElement->hostElement())
116  return lhsPseudoElement->isBeforePseudoElement();
117  }
118 
119  // Or comparing a pseudo-element that is compared to another non-pseudo element, in which case
120  // we want to see if it's hosted on that other element, and if not use its host element to compare.
121  if (is<PseudoElement>(lhsOwningElement)) {
122  auto* lhsHostElement = downcast<PseudoElement>(lhsOwningElement)->hostElement();
123  if (rhsOwningElement == lhsHostElement)
124  return false;
125  lhsOwningElement = lhsHostElement;
126  }
127 
128  if (is<PseudoElement>(rhsOwningElement)) {
129  auto* rhsHostElement = downcast<PseudoElement>(rhsOwningElement)->hostElement();
130  if (lhsOwningElement == rhsHostElement)
131  return true;
132  rhsOwningElement = rhsHostElement;
133  }
134 
135  return lhsOwningElement->compareDocumentPosition(*rhsOwningElement) & Node::DOCUMENT_POSITION_FOLLOWING;
136 }
137 
138 Vector<RefPtr<WebAnimation>> DocumentTimeline::getAnimations() const
139 {
140  ASSERT(m_document);
141 
142  Vector<RefPtr<WebAnimation>> cssTransitions;
143  Vector<RefPtr<WebAnimation>> cssAnimations;
144  Vector<RefPtr<WebAnimation>> webAnimations;
145 
146  // First, let's get all qualifying animations in their right group.
147  for (const auto& animation : m_animations) {
148  if (!animation || !animation->isRelevant() || animation->timeline() != this || !is<KeyframeEffect>(animation->effect()))
149  continue;
150 
151  auto* target = downcast<KeyframeEffect>(animation->effect())->target();
152  if (!target || !target->isDescendantOf(*m_document))
153  continue;
154 
155  if (is<CSSTransition>(animation.get()) && downcast<CSSTransition>(animation.get())->owningElement())
156  cssTransitions.append(animation);
157  else if (is<CSSAnimation>(animation.get()) && downcast<CSSAnimation>(animation.get())->owningElement())
158  cssAnimations.append(animation);
159  else
160  webAnimations.append(animation);
161  }
162 
163  // Now sort CSS Transitions by their composite order.
164  std::stable_sort(cssTransitions.begin(), cssTransitions.end(), [](auto& lhs, auto& rhs) {
165  // https://drafts.csswg.org/css-transitions-2/#animation-composite-order
166  auto* lhsTransition = downcast<CSSTransition>(lhs.get());
167  auto* rhsTransition = downcast<CSSTransition>(rhs.get());
168 
169  auto* lhsOwningElement = lhsTransition->owningElement();
170  auto* rhsOwningElement = rhsTransition->owningElement();
171 
172  // If the owning element of A and B differs, sort A and B by tree order of their corresponding owning elements.
173  if (lhsOwningElement != rhsOwningElement)
174  return compareDeclarativeAnimationOwningElementPositionsInDocumentTreeOrder(lhsOwningElement, rhsOwningElement);
175 
176  // Otherwise, if A and B have different transition generation values, sort by their corresponding transition generation in ascending order.
177  if (lhsTransition->generationTime() != rhsTransition->generationTime())
178  return lhsTransition->generationTime() < rhsTransition->generationTime();
179 
180  // Otherwise, sort A and B in ascending order by the Unicode codepoints that make up the expanded transition property name of each transition
181  // (i.e. without attempting case conversion and such that ‘-moz-column-width’ sorts before ‘column-width’).
182  return lhsTransition->transitionProperty().utf8() < rhsTransition->transitionProperty().utf8();
183  });
184 
185  // Now sort CSS Animations by their composite order.
186  std::stable_sort(cssAnimations.begin(), cssAnimations.end(), [](auto& lhs, auto& rhs) {
187  // https://drafts.csswg.org/css-animations-2/#animation-composite-order
188  auto* lhsOwningElement = downcast<CSSAnimation>(lhs.get())->owningElement();
189  auto* rhsOwningElement = downcast<CSSAnimation>(rhs.get())->owningElement();
190 
191  // If the owning element of A and B differs, sort A and B by tree order of their corresponding owning elements.
192  if (lhsOwningElement != rhsOwningElement)
193  return compareDeclarativeAnimationOwningElementPositionsInDocumentTreeOrder(lhsOwningElement, rhsOwningElement);
194 
195  // Otherwise, sort A and B based on their position in the computed value of the animation-name property of the (common) owning element.
196  return compareAnimationsByCompositeOrder(*lhs, *rhs, lhsOwningElement->ensureKeyframeEffectStack().cssAnimationList());
197  });
198 
199  std::stable_sort(webAnimations.begin(), webAnimations.end(), [](auto& lhs, auto& rhs) {
200  return lhs->globalPosition() < rhs->globalPosition();
201  });
202 
203  // Finally, we can concatenate the sorted CSS Transitions, CSS Animations and Web Animations in their relative composite order.
204  Vector<RefPtr<WebAnimation>> animations;
205  animations.appendRange(cssTransitions.begin(), cssTransitions.end());
206  animations.appendRange(cssAnimations.begin(), cssAnimations.end());
207  animations.appendRange(webAnimations.begin(), webAnimations.end());
208  return animations;
209 }
210 
211101Seconds DocumentTimeline::animationInterval() const
212102{
213103 if (!m_document || !m_document->page())

Source/WebCore/animation/DocumentTimeline.h

@@public:
4646
4747 bool isDocumentTimeline() const final { return true; }
4848
49  Vector<RefPtr<WebAnimation>> getAnimations() const;
50 
5149 Document* document() const { return m_document.get(); }
5250
5351 Optional<Seconds> currentTime() override;

Source/WebCore/animation/KeyframeEffectStack.cpp

@@void KeyframeEffectStack::ensureEffectsAreSorted()
9898 RELEASE_ASSERT(lhsAnimation);
9999 RELEASE_ASSERT(rhsAnimation);
100100
101  return compareAnimationsByCompositeOrder(*lhsAnimation, *rhsAnimation, m_cssAnimationList.get());
 101 return compareAnimationsByCompositeOrder(*lhsAnimation, *rhsAnimation);
102102 });
103103
104104 m_isSorted = true;

Source/WebCore/animation/WebAnimation.cpp

@@ExceptionOr<void> WebAnimation::commitStyles()
14051405 inlineStyle->setCssText(styledElement.getAttribute("style"));
14061406
14071407 auto& keyframeStack = styledElement.ensureKeyframeEffectStack();
1408  auto* cssAnimationList = keyframeStack.cssAnimationList();
14091408
14101409 // 2.5 For each property, property, in targeted properties:
14111410 for (auto property : effect->animatedProperties()) {

@@ExceptionOr<void> WebAnimation::commitStyles()
14221421 // effect stack and stop when we've found this animation's effect or when we've found an effect associated with an animation with a higher composite order.
14231422 auto animatedStyle = RenderStyle::clonePtr(style);
14241423 for (const auto& effectInStack : keyframeStack.sortedEffects()) {
1425  if (effectInStack->animation() != this && !compareAnimationsByCompositeOrder(*effectInStack->animation(), *this, cssAnimationList))
 1424 if (effectInStack->animation() != this && !compareAnimationsByCompositeOrder(*effectInStack->animation(), *this))
14261425 break;
14271426 if (effectInStack->animatedProperties().contains(property))
14281427 effectInStack->animation()->resolve(*animatedStyle);

Source/WebCore/animation/WebAnimationUtilities.cpp

3131#include "CSSAnimation.h"
3232#include "CSSTransition.h"
3333#include "DeclarativeAnimation.h"
 34#include "Element.h"
 35#include "KeyframeEffectStack.h"
 36#include "PseudoElement.h"
3437#include "WebAnimation.h"
3538
3639namespace WebCore {
3740
38 bool compareAnimationsByCompositeOrder(WebAnimation& lhsAnimation, WebAnimation& rhsAnimation, const AnimationList* cssAnimationList)
 41inline bool compareDeclarativeAnimationOwningElementPositionsInDocumentTreeOrder(Element* lhsOwningElement, Element* rhsOwningElement)
 42{
 43 // With regard to pseudo-elements, the sort order is as follows:
 44 // - element
 45 // - ::before
 46 // - ::after
 47 // - element children
 48
 49 // We could be comparing two pseudo-elements that are hosted on the same element.
 50 if (is<PseudoElement>(lhsOwningElement) && is<PseudoElement>(rhsOwningElement)) {
 51 auto* lhsPseudoElement = downcast<PseudoElement>(lhsOwningElement);
 52 auto* rhsPseudoElement = downcast<PseudoElement>(rhsOwningElement);
 53 if (lhsPseudoElement->hostElement() == rhsPseudoElement->hostElement())
 54 return lhsPseudoElement->isBeforePseudoElement();
 55 }
 56
 57 // Or comparing a pseudo-element that is compared to another non-pseudo element, in which case
 58 // we want to see if it's hosted on that other element, and if not use its host element to compare.
 59 if (is<PseudoElement>(lhsOwningElement)) {
 60 auto* lhsHostElement = downcast<PseudoElement>(lhsOwningElement)->hostElement();
 61 if (rhsOwningElement == lhsHostElement)
 62 return false;
 63 lhsOwningElement = lhsHostElement;
 64 }
 65
 66 if (is<PseudoElement>(rhsOwningElement)) {
 67 auto* rhsHostElement = downcast<PseudoElement>(rhsOwningElement)->hostElement();
 68 if (lhsOwningElement == rhsHostElement)
 69 return true;
 70 rhsOwningElement = rhsHostElement;
 71 }
 72
 73 return lhsOwningElement->compareDocumentPosition(*rhsOwningElement) & Node::DOCUMENT_POSITION_FOLLOWING;
 74}
 75
 76inline bool compareCSSTransitions(const CSSTransition& lhsTransition, const CSSTransition& rhsTransition)
 77{
 78 auto* lhsOwningElement = lhsTransition.owningElement();
 79 auto* rhsOwningElement = rhsTransition.owningElement();
 80
 81 // If the owning element of A and B differs, sort A and B by tree order of their corresponding owning elements.
 82 if (lhsOwningElement != rhsOwningElement)
 83 return compareDeclarativeAnimationOwningElementPositionsInDocumentTreeOrder(lhsOwningElement, rhsOwningElement);
 84
 85 // Otherwise, if A and B have different transition generation values, sort by their corresponding transition generation in ascending order.
 86 if (lhsTransition.generationTime() != rhsTransition.generationTime())
 87 return lhsTransition.generationTime() < rhsTransition.generationTime();
 88
 89 // Otherwise, sort A and B in ascending order by the Unicode codepoints that make up the expanded transition property name of each transition
 90 // (i.e. without attempting case conversion and such that ‘-moz-column-width’ sorts before ‘column-width’).
 91 return lhsTransition.transitionProperty().utf8() < rhsTransition.transitionProperty().utf8();
 92}
 93
 94inline bool compareCSSAnimations(const CSSAnimation& lhs, const CSSAnimation& rhs)
 95{
 96 // https://drafts.csswg.org/css-animations-2/#animation-composite-order
 97 auto* lhsOwningElement = lhs.owningElement();
 98 auto* rhsOwningElement = rhs.owningElement();
 99
 100 // If the owning element of A and B differs, sort A and B by tree order of their corresponding owning elements.
 101 if (lhsOwningElement != rhsOwningElement)
 102 return compareDeclarativeAnimationOwningElementPositionsInDocumentTreeOrder(lhsOwningElement, rhsOwningElement);
 103
 104 // Sort A and B based on their position in the computed value of the animation-name property of the (common) owning element.
 105 auto* cssAnimationList = lhsOwningElement->ensureKeyframeEffectStack().cssAnimationList();
 106 ASSERT(cssAnimationList);
 107 ASSERT(!cssAnimationList->isEmpty());
 108
 109 auto& lhsBackingAnimation = lhs.backingAnimation();
 110 auto& rhsBackingAnimation = rhs.backingAnimation();
 111 for (size_t i = 0; i < cssAnimationList->size(); ++i) {
 112 auto& animation = cssAnimationList->animation(i);
 113 if (animation == lhsBackingAnimation)
 114 return true;
 115 if (animation == rhsBackingAnimation)
 116 return false;
 117 }
 118
 119 // We should have found either of those CSS animations in the CSS animations list.
 120 RELEASE_ASSERT_NOT_REACHED();
 121}
 122
 123bool compareAnimationsByCompositeOrder(const WebAnimation& lhsAnimation, const WebAnimation& rhsAnimation)
39124{
40125 // We should not ever be calling this function with two WebAnimation objects that are the same. If that were the case,
41126 // then comparing objects of this kind would yield inconsistent results when comparing A == B and B == A. As such,

@@bool compareAnimationsByCompositeOrder(WebAnimation& lhsAnimation, WebAnimation&
49134 bool lhsIsCSSTransition = lhsHasOwningElement && is<CSSTransition>(lhsAnimation);
50135 bool rhsIsCSSTransition = rhsHasOwningElement && is<CSSTransition>(rhsAnimation);
51136 if (lhsIsCSSTransition || rhsIsCSSTransition) {
52  if (lhsIsCSSTransition != rhsIsCSSTransition)
53  return !rhsIsCSSTransition;
54 
55  // Sort transitions first by their generation time, and then by transition-property.
56  // https://drafts.csswg.org/css-transitions-2/#animation-composite-order
57  auto& lhsCSSTransition = downcast<CSSTransition>(lhsAnimation);
58  auto& rhsCSSTransition = downcast<CSSTransition>(rhsAnimation);
59  if (lhsCSSTransition.generationTime() != rhsCSSTransition.generationTime())
60  return lhsCSSTransition.generationTime() < rhsCSSTransition.generationTime();
61  auto lhsCSSTransitionProperty = lhsCSSTransition.transitionProperty().utf8();
62  auto rhsCSSTransitionProperty = rhsCSSTransition.transitionProperty().utf8();
63  if (lhsCSSTransitionProperty != rhsCSSTransitionProperty)
64  return lhsCSSTransitionProperty < rhsCSSTransitionProperty;
 137 if (lhsIsCSSTransition == rhsIsCSSTransition)
 138 return compareCSSTransitions(downcast<CSSTransition>(lhsAnimation), downcast<CSSTransition>(rhsAnimation));
 139 return !rhsIsCSSTransition;
65140 }
66141
67142 // CSS Animations sort next.
68143 bool lhsIsCSSAnimation = lhsHasOwningElement && is<CSSAnimation>(lhsAnimation);
69144 bool rhsIsCSSAnimation = rhsHasOwningElement && is<CSSAnimation>(rhsAnimation);
70145 if (lhsIsCSSAnimation || rhsIsCSSAnimation) {
71  if (lhsIsCSSAnimation != rhsIsCSSAnimation)
72  return !rhsIsCSSAnimation;
73 
74  // We must have a list of CSS Animations if we have CSS Animations to sort through.
75  ASSERT(cssAnimationList);
76  ASSERT(!cssAnimationList->isEmpty());
77 
78  // https://drafts.csswg.org/css-animations-2/#animation-composite-order
79  // Sort A and B based on their position in the computed value of the animation-name property of the (common) owning element.
80  auto& lhsBackingAnimation = downcast<CSSAnimation>(lhsAnimation).backingAnimation();
81  auto& rhsBackingAnimation = downcast<CSSAnimation>(rhsAnimation).backingAnimation();
82 
83  for (size_t i = 0; i < cssAnimationList->size(); ++i) {
84  auto& animation = cssAnimationList->animation(i);
85  if (animation == lhsBackingAnimation)
86  return true;
87  if (animation == rhsBackingAnimation)
88  return false;
89  }
90 
91  // We should have found either of those CSS animations in the CSS animations list.
92  RELEASE_ASSERT_NOT_REACHED();
 146 if (lhsIsCSSAnimation == rhsIsCSSAnimation)
 147 return compareCSSAnimations(downcast<CSSAnimation>(lhsAnimation), downcast<CSSAnimation>(rhsAnimation));
 148 return !rhsIsCSSAnimation;
93149 }
94150
95151 // JS-originated animations sort last based on their position in the global animation list.

Source/WebCore/animation/WebAnimationUtilities.h

3131
3232namespace WebCore {
3333
34 class AnimationList;
 34class Element;
3535class WebAnimation;
3636
3737inline double secondsToWebAnimationsAPITime(const Seconds time)

@@inline double secondsToWebAnimationsAPITime(const Seconds time)
5050
5151const auto timeEpsilon = Seconds::fromMilliseconds(0.001);
5252
53 bool compareAnimationsByCompositeOrder(WebAnimation&, WebAnimation&, const AnimationList*);
 53bool compareAnimationsByCompositeOrder(const WebAnimation&, const WebAnimation&);
5454
5555} // namespace WebCore
5656

Source/WebCore/dom/Document.cpp

233233#include "VisitedLinkState.h"
234234#include "VisualViewport.h"
235235#include "WebAnimation.h"
 236#include "WebAnimationUtilities.h"
236237#include "WheelEvent.h"
237238#include "WindowEventLoop.h"
238239#include "WindowFeatures.h"

@@Vector<RefPtr<WebAnimation>> Document::matchingAnimations(const WTF::Function<bo
81398140 return { };
81408141
81418142 Vector<RefPtr<WebAnimation>> animations;
8142  for (auto& animation : m_timeline->getAnimations()) {
8143  auto* effect = animation->effect();
8144  ASSERT(is<KeyframeEffect>(animation->effect()));
8145  auto* target = downcast<KeyframeEffect>(*effect).targetElementOrPseudoElement();
8146  ASSERT(target);
8147  if (function(*target))
 8143 for (auto& animation : m_timeline->relevantAnimations()) {
 8144 if (!animation || !animation->isRelevant() || !is<KeyframeEffect>(animation->effect()))
 8145 continue;
 8146
 8147 auto* target = downcast<KeyframeEffect>(*animation->effect()).targetElementOrPseudoElement();
 8148 if (target && target->isDescendantOf(this) && function(*target))
81488149 animations.append(animation);
81498150 }
 8151
 8152 std::stable_sort(animations.begin(), animations.end(), [](auto& lhs, auto& rhs) {
 8153 return compareAnimationsByCompositeOrder(*lhs, *rhs);
 8154 });
 8155
81508156 return animations;
81518157}
81528158