Keyframe animation doesn't 't show up in the Animations timeline
[WebKit-https.git] / Source / WebCore / inspector / agents / InspectorAnimationAgent.cpp
1 /*
2  * Copyright (C) 2016 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "InspectorAnimationAgent.h"
28
29 #include "AnimationEffect.h"
30 #include "AnimationEffectPhase.h"
31 #include "CSSAnimation.h"
32 #include "CSSComputedStyleDeclaration.h"
33 #include "CSSPropertyNames.h"
34 #include "CSSTransition.h"
35 #include "CSSValue.h"
36 #include "DeclarativeAnimation.h"
37 #include "Element.h"
38 #include "Event.h"
39 #include "FillMode.h"
40 #include "Frame.h"
41 #include "InspectorDOMAgent.h"
42 #include "InstrumentingAgents.h"
43 #include "JSExecState.h"
44 #include "JSWebAnimation.h"
45 #include "KeyframeEffect.h"
46 #include "KeyframeList.h"
47 #include "Page.h"
48 #include "PlaybackDirection.h"
49 #include "RenderElement.h"
50 #include "TimingFunction.h"
51 #include "WebAnimation.h"
52 #include <JavaScriptCore/IdentifiersFactory.h>
53 #include <JavaScriptCore/InjectedScriptManager.h>
54 #include <JavaScriptCore/InspectorEnvironment.h>
55 #include <JavaScriptCore/ScriptCallStackFactory.h>
56 #include <wtf/HashMap.h>
57 #include <wtf/Optional.h>
58 #include <wtf/Seconds.h>
59 #include <wtf/Stopwatch.h>
60 #include <wtf/Vector.h>
61 #include <wtf/text/StringBuilder.h>
62 #include <wtf/text/WTFString.h>
63
64 namespace WebCore {
65
66 using namespace Inspector;
67
68 static Optional<double> protocolValueForSeconds(const Seconds& seconds)
69 {
70     if (seconds == Seconds::infinity() || seconds == Seconds::nan())
71         return WTF::nullopt;
72     return seconds.milliseconds();
73 }
74
75 static Optional<Inspector::Protocol::Animation::PlaybackDirection> protocolValueForPlaybackDirection(PlaybackDirection playbackDirection)
76 {
77     switch (playbackDirection) {
78     case PlaybackDirection::Normal:
79         return Inspector::Protocol::Animation::PlaybackDirection::Normal;
80     case PlaybackDirection::Reverse:
81         return Inspector::Protocol::Animation::PlaybackDirection::Reverse;
82     case PlaybackDirection::Alternate:
83         return Inspector::Protocol::Animation::PlaybackDirection::Alternate;
84     case PlaybackDirection::AlternateReverse:
85         return Inspector::Protocol::Animation::PlaybackDirection::AlternateReverse;
86     }
87
88     ASSERT_NOT_REACHED();
89     return WTF::nullopt;
90 }
91
92 static Optional<Inspector::Protocol::Animation::FillMode> protocolValueForFillMode(FillMode fillMode)
93 {
94     switch (fillMode) {
95     case FillMode::None:
96         return Inspector::Protocol::Animation::FillMode::None;
97     case FillMode::Forwards:
98         return Inspector::Protocol::Animation::FillMode::Forwards;
99     case FillMode::Backwards:
100         return Inspector::Protocol::Animation::FillMode::Backwards;
101     case FillMode::Both:
102         return Inspector::Protocol::Animation::FillMode::Both;
103     case FillMode::Auto:
104         return Inspector::Protocol::Animation::FillMode::Auto;
105     }
106
107     ASSERT_NOT_REACHED();
108     return WTF::nullopt;
109 }
110
111 static Ref<JSON::ArrayOf<Inspector::Protocol::Animation::Keyframe>> buildObjectForKeyframes(KeyframeEffect& keyframeEffect)
112 {
113     auto keyframesPayload = JSON::ArrayOf<Inspector::Protocol::Animation::Keyframe>::create();
114
115     const auto& blendingKeyframes = keyframeEffect.blendingKeyframes();
116     const auto& parsedKeyframes = keyframeEffect.parsedKeyframes();
117
118     if (is<DeclarativeAnimation>(keyframeEffect.animation())) {
119         auto& declarativeAnimation = downcast<DeclarativeAnimation>(*keyframeEffect.animation());
120
121         auto* target = keyframeEffect.target();
122         auto* renderer = keyframeEffect.renderer();
123
124         // Synthesize CSS style declarations for each keyframe so the frontend can display them.
125         ComputedStyleExtractor computedStyleExtractor(target, false, target->pseudoId());
126
127         for (size_t i = 0; i < blendingKeyframes.size(); ++i) {
128             auto& blendingKeyframe = blendingKeyframes[i];
129
130             ASSERT(blendingKeyframe.style());
131             auto& style = *blendingKeyframe.style();
132
133             auto keyframePayload = Inspector::Protocol::Animation::Keyframe::create()
134                 .setOffset(blendingKeyframe.key())
135                 .release();
136
137             RefPtr<TimingFunction> timingFunction;
138             if (!parsedKeyframes.isEmpty())
139                 timingFunction = parsedKeyframes[i].timingFunction;
140             if (!timingFunction)
141                 timingFunction = blendingKeyframe.timingFunction();
142             if (!timingFunction)
143                 timingFunction = declarativeAnimation.backingAnimation().timingFunction();
144             if (timingFunction)
145                 keyframePayload->setEasing(timingFunction->cssText());
146
147             StringBuilder stylePayloadBuilder;
148             auto& cssPropertyIds = blendingKeyframe.properties();
149             size_t count = cssPropertyIds.size();
150             for (auto cssPropertyId : cssPropertyIds) {
151                 --count;
152                 if (cssPropertyId == CSSPropertyCustom)
153                     continue;
154
155                 stylePayloadBuilder.append(getPropertyNameString(cssPropertyId));
156                 stylePayloadBuilder.append(": ");
157                 if (auto value = computedStyleExtractor.valueForPropertyInStyle(style, cssPropertyId, renderer))
158                     stylePayloadBuilder.append(value->cssText());
159                 stylePayloadBuilder.append(';');
160                 if (count > 0)
161                     stylePayloadBuilder.append(' ');
162             }
163             if (!stylePayloadBuilder.isEmpty())
164                 keyframePayload->setStyle(stylePayloadBuilder.toString());
165
166             keyframesPayload->addItem(WTFMove(keyframePayload));
167         }
168     } else {
169         for (const auto& parsedKeyframe : parsedKeyframes) {
170             auto keyframePayload = Inspector::Protocol::Animation::Keyframe::create()
171                 .setOffset(parsedKeyframe.computedOffset)
172                 .release();
173
174             if (!parsedKeyframe.easing.isEmpty())
175                 keyframePayload->setEasing(parsedKeyframe.easing);
176             else if (const auto& timingFunction = parsedKeyframe.timingFunction)
177                 keyframePayload->setEasing(timingFunction->cssText());
178
179             if (!parsedKeyframe.style->isEmpty())
180                 keyframePayload->setStyle(parsedKeyframe.style->asText());
181
182             keyframesPayload->addItem(WTFMove(keyframePayload));
183         }
184     }
185
186     return keyframesPayload;
187 }
188
189 static Ref<Inspector::Protocol::Animation::Effect> buildObjectForEffect(AnimationEffect& effect)
190 {
191     auto effectPayload = Inspector::Protocol::Animation::Effect::create()
192         .release();
193
194     if (auto startDelay = protocolValueForSeconds(effect.delay()))
195         effectPayload->setStartDelay(startDelay.value());
196
197     if (auto endDelay = protocolValueForSeconds(effect.endDelay()))
198         effectPayload->setEndDelay(endDelay.value());
199
200     effectPayload->setIterationCount(effect.iterations() == std::numeric_limits<double>::infinity() ? -1 : effect.iterations());
201     effectPayload->setIterationStart(effect.iterationStart());
202
203     if (auto iterationDuration = protocolValueForSeconds(effect.iterationDuration()))
204         effectPayload->setIterationDuration(iterationDuration.value());
205
206     if (auto* timingFunction = effect.timingFunction())
207         effectPayload->setTimingFunction(timingFunction->cssText());
208
209     if (auto playbackDirection = protocolValueForPlaybackDirection(effect.direction()))
210         effectPayload->setPlaybackDirection(playbackDirection.value());
211
212     if (auto fillMode = protocolValueForFillMode(effect.fill()))
213         effectPayload->setFillMode(fillMode.value());
214
215     if (is<KeyframeEffect>(effect))
216         effectPayload->setKeyframes(buildObjectForKeyframes(downcast<KeyframeEffect>(effect)));
217
218     return effectPayload;
219 }
220
221 InspectorAnimationAgent::InspectorAnimationAgent(PageAgentContext& context)
222     : InspectorAgentBase("Animation"_s, context)
223     , m_frontendDispatcher(makeUnique<Inspector::AnimationFrontendDispatcher>(context.frontendRouter))
224     , m_backendDispatcher(Inspector::AnimationBackendDispatcher::create(context.backendDispatcher, this))
225     , m_injectedScriptManager(context.injectedScriptManager)
226     , m_inspectedPage(context.inspectedPage)
227     , m_animationDestroyedTimer(*this, &InspectorAnimationAgent::animationDestroyedTimerFired)
228 {
229 }
230
231 InspectorAnimationAgent::~InspectorAnimationAgent() = default;
232
233 void InspectorAnimationAgent::didCreateFrontendAndBackend(FrontendRouter*, BackendDispatcher*)
234 {
235     ASSERT(m_instrumentingAgents.persistentAnimationAgent() != this);
236     m_instrumentingAgents.setPersistentAnimationAgent(this);
237 }
238
239 void InspectorAnimationAgent::willDestroyFrontendAndBackend(DisconnectReason)
240 {
241     ErrorString ignored;
242     stopTracking(ignored);
243     disable(ignored);
244
245     ASSERT(m_instrumentingAgents.persistentAnimationAgent() == this);
246     m_instrumentingAgents.setPersistentAnimationAgent(nullptr);
247 }
248
249 void InspectorAnimationAgent::enable(ErrorString& errorString)
250 {
251     if (m_instrumentingAgents.enabledAnimationAgent() == this) {
252         errorString = "Animation domain already enabled"_s;
253         return;
254     }
255
256     m_instrumentingAgents.setEnabledAnimationAgent(this);
257
258     const auto existsInCurrentPage = [&] (ScriptExecutionContext* scriptExecutionContext) {
259         if (!is<Document>(scriptExecutionContext))
260             return false;
261
262         // FIXME: <https://webkit.org/b/168475> Web Inspector: Correctly display iframe's WebSockets
263         auto* document = downcast<Document>(scriptExecutionContext);
264         return document->page() == &m_inspectedPage;
265     };
266
267     {
268         for (auto* animation : WebAnimation::instances()) {
269             if (existsInCurrentPage(animation->scriptExecutionContext()))
270                 bindAnimation(*animation, false);
271         }
272     }
273 }
274
275 void InspectorAnimationAgent::disable(ErrorString&)
276 {
277     m_instrumentingAgents.setEnabledAnimationAgent(nullptr);
278
279     reset();
280 }
281
282 void InspectorAnimationAgent::requestEffectTarget(ErrorString& errorString, const String& animationId, int* nodeId)
283 {
284     auto* animation = assertAnimation(errorString, animationId);
285     if (!animation)
286         return;
287
288     auto* domAgent = m_instrumentingAgents.persistentDOMAgent();
289     if (!domAgent) {
290         errorString = "DOM domain must be enabled"_s;
291         return;
292     }
293
294     auto* effect = animation->effect();
295     if (!is<KeyframeEffect>(effect)) {
296         errorString = "Animation for given animationId does not have an effect"_s;
297         return;
298     }
299
300     auto& keyframeEffect = downcast<KeyframeEffect>(*effect);
301
302     auto* target = keyframeEffect.targetElementOrPseudoElement();
303     if (!target) {
304         errorString = "Animation for given animationId does not have a target"_s;
305         return;
306     }
307
308     *nodeId = domAgent->pushNodePathToFrontend(errorString, target);
309 }
310
311 void InspectorAnimationAgent::resolveAnimation(ErrorString& errorString, const String& animationId, const String* objectGroup, RefPtr<Inspector::Protocol::Runtime::RemoteObject>& result)
312 {
313     auto* animation = assertAnimation(errorString, animationId);
314     if (!animation)
315         return;
316
317     auto* state = animation->scriptExecutionContext()->execState();
318     auto injectedScript = m_injectedScriptManager.injectedScriptFor(state);
319     ASSERT(!injectedScript.hasNoValue());
320
321     JSC::JSValue value;
322     {
323         JSC::JSLockHolder lock(state);
324
325         auto* globalObject = deprecatedGlobalObjectForPrototype(state);
326         value = toJS(state, globalObject, animation);
327     }
328
329     if (!value) {
330         ASSERT_NOT_REACHED();
331         errorString = "Internal error: unknown Animation for given animationId"_s;
332         return;
333     }
334
335     String objectGroupName = objectGroup ? *objectGroup : String();
336     result = injectedScript.wrapObject(value, objectGroupName);
337 }
338
339 void InspectorAnimationAgent::startTracking(ErrorString& errorString)
340 {
341     if (m_instrumentingAgents.trackingAnimationAgent() == this) {
342         errorString = "Animation domain already tracking"_s;
343         return;
344     }
345
346     m_instrumentingAgents.setTrackingAnimationAgent(this);
347
348     ASSERT(m_trackedDeclarativeAnimationData.isEmpty());
349
350     m_frontendDispatcher->trackingStart(m_environment.executionStopwatch().elapsedTime().seconds());
351 }
352
353 void InspectorAnimationAgent::stopTracking(ErrorString&)
354 {
355     if (m_instrumentingAgents.trackingAnimationAgent() != this)
356         return;
357
358     m_instrumentingAgents.setTrackingAnimationAgent(nullptr);
359
360     m_trackedDeclarativeAnimationData.clear();
361
362     m_frontendDispatcher->trackingComplete(m_environment.executionStopwatch().elapsedTime().seconds());
363 }
364
365 static bool isDelayed(ComputedEffectTiming& computedTiming)
366 {
367     if (!computedTiming.localTime)
368         return false;
369     return computedTiming.localTime.value() < (computedTiming.endTime - computedTiming.activeDuration);
370 }
371
372 void InspectorAnimationAgent::willApplyKeyframeEffect(Element& target, KeyframeEffect& keyframeEffect, ComputedEffectTiming computedTiming)
373 {
374     auto* animation = keyframeEffect.animation();
375     if (!is<DeclarativeAnimation>(animation))
376         return;
377
378     auto ensureResult = m_trackedDeclarativeAnimationData.ensure(downcast<DeclarativeAnimation>(animation), [&] () -> TrackedDeclarativeAnimationData {
379         return { makeString("animation:"_s, IdentifiersFactory::createIdentifier()), computedTiming };
380     });
381     auto& trackingData = ensureResult.iterator->value;
382
383     Optional<Inspector::Protocol::Animation::AnimationState> animationAnimationState;
384
385     if ((ensureResult.isNewEntry || !isDelayed(trackingData.lastComputedTiming)) && isDelayed(computedTiming))
386         animationAnimationState = Inspector::Protocol::Animation::AnimationState::Delayed;
387     else if (ensureResult.isNewEntry || trackingData.lastComputedTiming.phase != computedTiming.phase) {
388         switch (computedTiming.phase) {
389         case AnimationEffectPhase::Before:
390             animationAnimationState = Inspector::Protocol::Animation::AnimationState::Ready;
391             break;
392
393         case AnimationEffectPhase::Active:
394             animationAnimationState = Inspector::Protocol::Animation::AnimationState::Active;
395             break;
396
397         case AnimationEffectPhase::After:
398             animationAnimationState = Inspector::Protocol::Animation::AnimationState::Done;
399             break;
400
401         case AnimationEffectPhase::Idle:
402             animationAnimationState = Inspector::Protocol::Animation::AnimationState::Canceled;
403             break;
404         }
405     } else if (trackingData.lastComputedTiming.currentIteration != computedTiming.currentIteration) {
406         // Iterations are represented by sequential "active" state events.
407         animationAnimationState = Inspector::Protocol::Animation::AnimationState::Active;
408     }
409
410     trackingData.lastComputedTiming = computedTiming;
411
412     if (!animationAnimationState)
413         return;
414
415     auto event = Inspector::Protocol::Animation::TrackingUpdate::create()
416         .setTrackingAnimationId(trackingData.trackingAnimationId)
417         .setAnimationState(animationAnimationState.value())
418         .release();
419
420     if (ensureResult.isNewEntry) {
421         if (auto* domAgent = m_instrumentingAgents.persistentDOMAgent()) {
422             if (auto nodeId = domAgent->pushNodeToFrontend(&target))
423                 event->setNodeId(nodeId);
424         }
425
426         if (is<CSSAnimation>(animation))
427             event->setAnimationName(downcast<CSSAnimation>(*animation).animationName());
428         else if (is<CSSTransition>(animation))
429             event->setTransitionProperty(downcast<CSSTransition>(*animation).transitionProperty());
430         else
431             ASSERT_NOT_REACHED();
432     }
433
434     m_frontendDispatcher->trackingUpdate(m_environment.executionStopwatch().elapsedTime().seconds(), WTFMove(event));
435 }
436
437 void InspectorAnimationAgent::didChangeWebAnimationName(WebAnimation& animation)
438 {
439     // The `animationId` may be empty if Animation is tracking but not enabled.
440     auto animationId = findAnimationId(animation);
441     if (animationId.isEmpty())
442         return;
443
444     auto name = animation.id();
445     m_frontendDispatcher->nameChanged(animationId, !name.isEmpty() ? &name : nullptr);
446 }
447
448 void InspectorAnimationAgent::didSetWebAnimationEffect(WebAnimation& animation)
449 {
450     if (is<DeclarativeAnimation>(animation))
451         stopTrackingDeclarativeAnimation(downcast<DeclarativeAnimation>(animation));
452
453     didChangeWebAnimationEffectTiming(animation);
454     didChangeWebAnimationEffectTarget(animation);
455 }
456
457 void InspectorAnimationAgent::didChangeWebAnimationEffectTiming(WebAnimation& animation)
458 {
459     // The `animationId` may be empty if Animation is tracking but not enabled.
460     auto animationId = findAnimationId(animation);
461     if (animationId.isEmpty())
462         return;
463
464     if (auto* effect = animation.effect())
465         m_frontendDispatcher->effectChanged(animationId, buildObjectForEffect(*effect));
466     else
467         m_frontendDispatcher->effectChanged(animationId, nullptr);
468 }
469
470 void InspectorAnimationAgent::didChangeWebAnimationEffectTarget(WebAnimation& animation)
471 {
472     // The `animationId` may be empty if Animation is tracking but not enabled.
473     auto animationId = findAnimationId(animation);
474     if (animationId.isEmpty())
475         return;
476
477     m_frontendDispatcher->targetChanged(animationId);
478 }
479
480 void InspectorAnimationAgent::didCreateWebAnimation(WebAnimation& animation)
481 {
482     if (!findAnimationId(animation).isEmpty()) {
483         ASSERT_NOT_REACHED();
484         return;
485     }
486
487     bindAnimation(animation, true);
488 }
489
490 void InspectorAnimationAgent::willDestroyWebAnimation(WebAnimation& animation)
491 {
492     if (is<DeclarativeAnimation>(animation))
493         stopTrackingDeclarativeAnimation(downcast<DeclarativeAnimation>(animation));
494
495     // The `animationId` may be empty if Animation is tracking but not enabled.
496     auto animationId = findAnimationId(animation);
497     if (!animationId.isEmpty())
498         unbindAnimation(animationId);
499 }
500
501 void InspectorAnimationAgent::frameNavigated(Frame& frame)
502 {
503     if (frame.isMainFrame()) {
504         reset();
505         return;
506     }
507
508     Vector<String> animationIdsToRemove;
509     for (auto& [animationId, animation] : m_animationIdMap) {
510         if (auto* scriptExecutionContext = animation->scriptExecutionContext()) {
511             if (is<Document>(scriptExecutionContext) && downcast<Document>(*scriptExecutionContext).frame() == &frame)
512                 animationIdsToRemove.append(animationId);
513         }
514     }
515     for (const auto& animationId : animationIdsToRemove)
516         unbindAnimation(animationId);
517 }
518
519 String InspectorAnimationAgent::findAnimationId(WebAnimation& animation)
520 {
521     for (auto& [animationId, existingAnimation] : m_animationIdMap) {
522         if (existingAnimation == &animation)
523             return animationId;
524     }
525     return nullString();
526 }
527
528 WebAnimation* InspectorAnimationAgent::assertAnimation(ErrorString& errorString, const String& animationId)
529 {
530     auto* animation = m_animationIdMap.get(animationId);
531     if (!animation)
532         errorString = "Missing animation for given animationId"_s;
533     return animation;
534 }
535
536 void InspectorAnimationAgent::bindAnimation(WebAnimation& animation, bool captureBacktrace)
537 {
538     auto animationId = makeString("animation:" + IdentifiersFactory::createIdentifier());
539     m_animationIdMap.set(animationId, &animation);
540
541     auto animationPayload = Inspector::Protocol::Animation::Animation::create()
542         .setAnimationId(animationId)
543         .release();
544
545     auto name = animation.id();
546     if (!name.isEmpty())
547         animationPayload->setName(name);
548
549     if (is<CSSAnimation>(animation))
550         animationPayload->setCssAnimationName(downcast<CSSAnimation>(animation).animationName());
551     else if (is<CSSTransition>(animation))
552         animationPayload->setCssTransitionProperty(downcast<CSSTransition>(animation).transitionProperty());
553
554     if (auto* effect = animation.effect())
555         animationPayload->setEffect(buildObjectForEffect(*effect));
556
557     if (captureBacktrace) {
558         auto stackTrace = Inspector::createScriptCallStack(JSExecState::currentState(), Inspector::ScriptCallStack::maxCallStackSizeToCapture);
559         animationPayload->setBacktrace(stackTrace->buildInspectorArray());
560     }
561
562     m_frontendDispatcher->animationCreated(WTFMove(animationPayload));
563 }
564
565 void InspectorAnimationAgent::unbindAnimation(const String& animationId)
566 {
567     m_animationIdMap.remove(animationId);
568
569     // This can be called in response to GC. Due to the single-process model used in WebKit1, the
570     // event must be dispatched from a timer to prevent the frontend from making JS allocations
571     // while the GC is still active.
572     m_removedAnimationIds.append(animationId);
573
574     if (!m_animationDestroyedTimer.isActive())
575         m_animationDestroyedTimer.startOneShot(0_s);
576 }
577
578 void InspectorAnimationAgent::animationDestroyedTimerFired()
579 {
580     if (!m_removedAnimationIds.size())
581         return;
582
583     for (auto& identifier : m_removedAnimationIds)
584         m_frontendDispatcher->animationDestroyed(identifier);
585
586     m_removedAnimationIds.clear();
587 }
588
589 void InspectorAnimationAgent::reset()
590 {
591     m_animationIdMap.clear();
592
593     m_removedAnimationIds.clear();
594
595     if (m_animationDestroyedTimer.isActive())
596         m_animationDestroyedTimer.stop();
597 }
598
599 void InspectorAnimationAgent::stopTrackingDeclarativeAnimation(DeclarativeAnimation& animation)
600 {
601     auto it = m_trackedDeclarativeAnimationData.find(&animation);
602     if (it == m_trackedDeclarativeAnimationData.end())
603         return;
604
605     if (it->value.lastComputedTiming.phase != AnimationEffectPhase::After && it->value.lastComputedTiming.phase != AnimationEffectPhase::Idle) {
606         auto event = Inspector::Protocol::Animation::TrackingUpdate::create()
607             .setTrackingAnimationId(it->value.trackingAnimationId)
608             .setAnimationState(Inspector::Protocol::Animation::AnimationState::Canceled)
609             .release();
610         m_frontendDispatcher->trackingUpdate(m_environment.executionStopwatch().elapsedTime().seconds(), WTFMove(event));
611     }
612
613     m_trackedDeclarativeAnimationData.remove(it);
614 }
615
616 } // namespace WebCore