1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "third_party/blink/renderer/core/inspector/inspector_animation_agent.h"
6 
7 #include <memory>
8 
9 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
10 #include "third_party/blink/renderer/bindings/core/v8/v8_computed_effect_timing.h"
11 #include "third_party/blink/renderer/bindings/core/v8/v8_optional_effect_timing.h"
12 #include "third_party/blink/renderer/core/animation/animation.h"
13 #include "third_party/blink/renderer/core/animation/animation_effect.h"
14 #include "third_party/blink/renderer/core/animation/css/css_animation.h"
15 #include "third_party/blink/renderer/core/animation/css/css_animations.h"
16 #include "third_party/blink/renderer/core/animation/css/css_transition.h"
17 #include "third_party/blink/renderer/core/animation/document_timeline.h"
18 #include "third_party/blink/renderer/core/animation/effect_model.h"
19 #include "third_party/blink/renderer/core/animation/element_animations.h"
20 #include "third_party/blink/renderer/core/animation/keyframe_effect.h"
21 #include "third_party/blink/renderer/core/animation/keyframe_effect_model.h"
22 #include "third_party/blink/renderer/core/animation/string_keyframe.h"
23 #include "third_party/blink/renderer/core/css/css_keyframe_rule.h"
24 #include "third_party/blink/renderer/core/css/css_keyframes_rule.h"
25 #include "third_party/blink/renderer/core/css/css_rule_list.h"
26 #include "third_party/blink/renderer/core/css/css_style_rule.h"
27 #include "third_party/blink/renderer/core/css/resolver/style_resolver.h"
28 #include "third_party/blink/renderer/core/frame/local_frame.h"
29 #include "third_party/blink/renderer/core/inspector/identifiers_factory.h"
30 #include "third_party/blink/renderer/core/inspector/inspected_frames.h"
31 #include "third_party/blink/renderer/core/inspector/inspector_css_agent.h"
32 #include "third_party/blink/renderer/core/inspector/inspector_style_sheet.h"
33 #include "third_party/blink/renderer/core/inspector/v8_inspector_string.h"
34 #include "third_party/blink/renderer/platform/animation/timing_function.h"
35 #include "third_party/blink/renderer/platform/heap/heap.h"
36 #include "third_party/blink/renderer/platform/wtf/text/base64.h"
37 
38 namespace blink {
39 
40 namespace {
41 
AnimationDisplayName(const Animation & animation)42 String AnimationDisplayName(const Animation& animation) {
43   if (!animation.id().IsEmpty())
44     return animation.id();
45   else if (auto* css_animation = DynamicTo<CSSAnimation>(animation))
46     return css_animation->animationName();
47   else if (auto* css_transition = DynamicTo<CSSTransition>(animation))
48     return css_transition->transitionProperty();
49   else
50     return animation.id();
51 }
52 
53 }  // namespace
54 
55 using protocol::Response;
56 
InspectorAnimationAgent(InspectedFrames * inspected_frames,InspectorCSSAgent * css_agent,v8_inspector::V8InspectorSession * v8_session)57 InspectorAnimationAgent::InspectorAnimationAgent(
58     InspectedFrames* inspected_frames,
59     InspectorCSSAgent* css_agent,
60     v8_inspector::V8InspectorSession* v8_session)
61     : inspected_frames_(inspected_frames),
62       css_agent_(css_agent),
63       v8_session_(v8_session),
64       is_cloning_(false),
65       enabled_(&agent_state_, /*default_value=*/false),
66       playback_rate_(&agent_state_, /*default_value=*/1.0) {}
67 
Restore()68 void InspectorAnimationAgent::Restore() {
69   if (enabled_.Get()) {
70     instrumenting_agents_->AddInspectorAnimationAgent(this);
71     setPlaybackRate(playback_rate_.Get());
72   }
73 }
74 
enable()75 Response InspectorAnimationAgent::enable() {
76   enabled_.Set(true);
77   instrumenting_agents_->AddInspectorAnimationAgent(this);
78   return Response::Success();
79 }
80 
disable()81 Response InspectorAnimationAgent::disable() {
82   setPlaybackRate(1.0);
83   for (const auto& clone : id_to_animation_clone_.Values())
84     clone->cancel();
85   enabled_.Clear();
86   instrumenting_agents_->RemoveInspectorAnimationAgent(this);
87   id_to_animation_.clear();
88   id_to_animation_clone_.clear();
89   cleared_animations_.clear();
90   return Response::Success();
91 }
92 
DidCommitLoadForLocalFrame(LocalFrame * frame)93 void InspectorAnimationAgent::DidCommitLoadForLocalFrame(LocalFrame* frame) {
94   if (frame == inspected_frames_->Root()) {
95     id_to_animation_.clear();
96     id_to_animation_clone_.clear();
97     cleared_animations_.clear();
98   }
99   setPlaybackRate(playback_rate_.Get());
100 }
101 
102 static std::unique_ptr<protocol::Animation::AnimationEffect>
BuildObjectForAnimationEffect(KeyframeEffect * effect)103 BuildObjectForAnimationEffect(KeyframeEffect* effect) {
104   ComputedEffectTiming* computed_timing = effect->getComputedTiming();
105   double delay = computed_timing->delay();
106   double duration = computed_timing->duration().GetAsUnrestrictedDouble();
107   String easing = effect->SpecifiedTiming().timing_function->ToString();
108 
109   std::unique_ptr<protocol::Animation::AnimationEffect> animation_object =
110       protocol::Animation::AnimationEffect::create()
111           .setDelay(delay)
112           .setEndDelay(computed_timing->endDelay())
113           .setIterationStart(computed_timing->iterationStart())
114           .setIterations(computed_timing->iterations())
115           .setDuration(duration)
116           .setDirection(computed_timing->direction())
117           .setFill(computed_timing->fill())
118           .setEasing(easing)
119           .build();
120   if (effect->EffectTarget()) {
121     animation_object->setBackendNodeId(
122         IdentifiersFactory::IntIdForNode(effect->EffectTarget()));
123   }
124   return animation_object;
125 }
126 
127 static std::unique_ptr<protocol::Animation::KeyframeStyle>
BuildObjectForStringKeyframe(const StringKeyframe * keyframe,double computed_offset)128 BuildObjectForStringKeyframe(const StringKeyframe* keyframe,
129                              double computed_offset) {
130   String offset = String::NumberToStringECMAScript(computed_offset * 100) + "%";
131 
132   std::unique_ptr<protocol::Animation::KeyframeStyle> keyframe_object =
133       protocol::Animation::KeyframeStyle::create()
134           .setOffset(offset)
135           .setEasing(keyframe->Easing().ToString())
136           .build();
137   return keyframe_object;
138 }
139 
140 static std::unique_ptr<protocol::Animation::KeyframesRule>
BuildObjectForAnimationKeyframes(const KeyframeEffect * effect)141 BuildObjectForAnimationKeyframes(const KeyframeEffect* effect) {
142   if (!effect || !effect->Model() || !effect->Model()->IsKeyframeEffectModel())
143     return nullptr;
144   const KeyframeEffectModelBase* model = effect->Model();
145   Vector<double> computed_offsets =
146       KeyframeEffectModelBase::GetComputedOffsets(model->GetFrames());
147   auto keyframes =
148       std::make_unique<protocol::Array<protocol::Animation::KeyframeStyle>>();
149 
150   for (wtf_size_t i = 0; i < model->GetFrames().size(); i++) {
151     const Keyframe* keyframe = model->GetFrames().at(i);
152     // Ignore CSS Transitions
153     if (!keyframe->IsStringKeyframe())
154       continue;
155     const auto* string_keyframe = To<StringKeyframe>(keyframe);
156     keyframes->emplace_back(
157         BuildObjectForStringKeyframe(string_keyframe, computed_offsets.at(i)));
158   }
159   return protocol::Animation::KeyframesRule::create()
160       .setKeyframes(std::move(keyframes))
161       .build();
162 }
163 
164 std::unique_ptr<protocol::Animation::Animation>
BuildObjectForAnimation(blink::Animation & animation)165 InspectorAnimationAgent::BuildObjectForAnimation(blink::Animation& animation) {
166   String animation_type = AnimationType::WebAnimation;
167   std::unique_ptr<protocol::Animation::AnimationEffect> animation_effect_object;
168 
169   if (animation.effect()) {
170     animation_effect_object =
171         BuildObjectForAnimationEffect(To<KeyframeEffect>(animation.effect()));
172 
173     if (IsA<CSSTransition>(animation)) {
174       animation_type = AnimationType::CSSTransition;
175     } else {
176       animation_effect_object->setKeyframesRule(
177           BuildObjectForAnimationKeyframes(
178               To<KeyframeEffect>(animation.effect())));
179 
180       if (IsA<CSSAnimation>(animation))
181         animation_type = AnimationType::CSSAnimation;
182     }
183   }
184 
185   String id = String::Number(animation.SequenceNumber());
186   id_to_animation_.Set(id, &animation);
187 
188   std::unique_ptr<protocol::Animation::Animation> animation_object =
189       protocol::Animation::Animation::create()
190           .setId(id)
191           .setName(AnimationDisplayName(animation))
192           .setPausedState(animation.Paused())
193           .setPlayState(animation.PlayStateString())
194           .setPlaybackRate(animation.playbackRate())
195           .setStartTime(NormalizedStartTime(animation))
196           .setCurrentTime(animation.currentTime())
197           .setType(animation_type)
198           .build();
199   if (animation_type != AnimationType::WebAnimation)
200     animation_object->setCssId(CreateCSSId(animation));
201   if (animation_effect_object)
202     animation_object->setSource(std::move(animation_effect_object));
203   return animation_object;
204 }
205 
getPlaybackRate(double * playback_rate)206 Response InspectorAnimationAgent::getPlaybackRate(double* playback_rate) {
207   *playback_rate = ReferenceTimeline().PlaybackRate();
208   return Response::Success();
209 }
210 
setPlaybackRate(double playback_rate)211 Response InspectorAnimationAgent::setPlaybackRate(double playback_rate) {
212   for (LocalFrame* frame : *inspected_frames_)
213     frame->GetDocument()->Timeline().SetPlaybackRate(playback_rate);
214   playback_rate_.Set(playback_rate);
215   return Response::Success();
216 }
217 
getCurrentTime(const String & id,double * current_time)218 Response InspectorAnimationAgent::getCurrentTime(const String& id,
219                                                  double* current_time) {
220   blink::Animation* animation = nullptr;
221   Response response = AssertAnimation(id, animation);
222   if (!response.IsSuccess())
223     return response;
224   if (id_to_animation_clone_.at(id))
225     animation = id_to_animation_clone_.at(id);
226 
227   if (animation->Paused() || !animation->timeline()->IsActive()) {
228     *current_time = animation->currentTime();
229   } else {
230     // Use startTime where possible since currentTime is limited.
231     base::Optional<double> timeline_time = animation->timeline()->CurrentTime();
232     // TODO(crbug.com/916117): Handle NaN values for scroll linked animations.
233     *current_time =
234         timeline_time ? timeline_time.value() -
235                             animation->startTime().value_or(Timing::NullValue())
236                       : Timing::NullValue();
237   }
238   return Response::Success();
239 }
240 
setPaused(std::unique_ptr<protocol::Array<String>> animation_ids,bool paused)241 Response InspectorAnimationAgent::setPaused(
242     std::unique_ptr<protocol::Array<String>> animation_ids,
243     bool paused) {
244   for (const String& animation_id : *animation_ids) {
245     blink::Animation* animation = nullptr;
246     Response response = AssertAnimation(animation_id, animation);
247     if (!response.IsSuccess())
248       return response;
249     blink::Animation* clone = AnimationClone(animation);
250     if (!clone)
251       return Response::ServerError("Failed to clone detached animation");
252     if (paused && !clone->Paused()) {
253       // Ensure we restore a current time if the animation is limited.
254       double current_time = 0;
255       if (!clone->timeline()->IsActive()) {
256         current_time = clone->currentTime();
257       } else {
258         base::Optional<double> timeline_time = clone->timeline()->CurrentTime();
259         // TODO(crbug.com/916117): Handle NaN values.
260         current_time =
261             timeline_time ? timeline_time.value() -
262                                 clone->startTime().value_or(Timing::NullValue())
263                           : Timing::NullValue();
264       }
265       clone->pause();
266       clone->setCurrentTime(current_time, false);
267     } else if (!paused && clone->Paused()) {
268       clone->Unpause();
269     }
270   }
271   return Response::Success();
272 }
273 
AnimationClone(blink::Animation * animation)274 blink::Animation* InspectorAnimationAgent::AnimationClone(
275     blink::Animation* animation) {
276   const String id = String::Number(animation->SequenceNumber());
277   if (!id_to_animation_clone_.at(id)) {
278     auto* old_effect = To<KeyframeEffect>(animation->effect());
279     DCHECK(old_effect->Model()->IsKeyframeEffectModel());
280     KeyframeEffectModelBase* old_model = old_effect->Model();
281     KeyframeEffectModelBase* new_model = nullptr;
282     // Clone EffectModel.
283     // TODO(samli): Determine if this is an animations bug.
284     if (old_model->IsStringKeyframeEffectModel()) {
285       auto* old_string_keyframe_model =
286           To<StringKeyframeEffectModel>(old_model);
287       KeyframeVector old_keyframes = old_string_keyframe_model->GetFrames();
288       StringKeyframeVector new_keyframes;
289       for (auto& old_keyframe : old_keyframes)
290         new_keyframes.push_back(To<StringKeyframe>(*old_keyframe));
291       new_model =
292           MakeGarbageCollected<StringKeyframeEffectModel>(new_keyframes);
293     } else if (old_model->IsTransitionKeyframeEffectModel()) {
294       auto* old_transition_keyframe_model =
295           To<TransitionKeyframeEffectModel>(old_model);
296       KeyframeVector old_keyframes = old_transition_keyframe_model->GetFrames();
297       TransitionKeyframeVector new_keyframes;
298       for (auto& old_keyframe : old_keyframes)
299         new_keyframes.push_back(To<TransitionKeyframe>(*old_keyframe));
300       new_model =
301           MakeGarbageCollected<TransitionKeyframeEffectModel>(new_keyframes);
302     }
303 
304     auto* new_effect = MakeGarbageCollected<KeyframeEffect>(
305         old_effect->EffectTarget(), new_model, old_effect->SpecifiedTiming());
306     is_cloning_ = true;
307     blink::Animation* clone =
308         blink::Animation::Create(new_effect, animation->timeline());
309     is_cloning_ = false;
310     id_to_animation_clone_.Set(id, clone);
311     id_to_animation_.Set(String::Number(clone->SequenceNumber()), clone);
312     clone->play();
313     clone->setStartTime(animation->startTime().value_or(Timing::NullValue()),
314                         false);
315 
316     animation->SetEffectSuppressed(true);
317   }
318   return id_to_animation_clone_.at(id);
319 }
320 
seekAnimations(std::unique_ptr<protocol::Array<String>> animation_ids,double current_time)321 Response InspectorAnimationAgent::seekAnimations(
322     std::unique_ptr<protocol::Array<String>> animation_ids,
323     double current_time) {
324   for (const String& animation_id : *animation_ids) {
325     blink::Animation* animation = nullptr;
326     Response response = AssertAnimation(animation_id, animation);
327     if (!response.IsSuccess())
328       return response;
329     blink::Animation* clone = AnimationClone(animation);
330     if (!clone)
331       return Response::ServerError("Failed to clone a detached animation.");
332     if (!clone->Paused())
333       clone->play();
334     clone->setCurrentTime(current_time, false);
335   }
336   return Response::Success();
337 }
338 
releaseAnimations(std::unique_ptr<protocol::Array<String>> animation_ids)339 Response InspectorAnimationAgent::releaseAnimations(
340     std::unique_ptr<protocol::Array<String>> animation_ids) {
341   for (const String& animation_id : *animation_ids) {
342     blink::Animation* animation = id_to_animation_.at(animation_id);
343     if (animation)
344       animation->SetEffectSuppressed(false);
345     blink::Animation* clone = id_to_animation_clone_.at(animation_id);
346     if (clone)
347       clone->cancel();
348     id_to_animation_clone_.erase(animation_id);
349     id_to_animation_.erase(animation_id);
350     cleared_animations_.insert(animation_id);
351   }
352   return Response::Success();
353 }
354 
setTiming(const String & animation_id,double duration,double delay)355 Response InspectorAnimationAgent::setTiming(const String& animation_id,
356                                             double duration,
357                                             double delay) {
358   blink::Animation* animation = nullptr;
359   Response response = AssertAnimation(animation_id, animation);
360   if (!response.IsSuccess())
361     return response;
362 
363   animation = AnimationClone(animation);
364   NonThrowableExceptionState exception_state;
365 
366   OptionalEffectTiming* timing = OptionalEffectTiming::Create();
367   UnrestrictedDoubleOrString unrestricted_duration;
368   unrestricted_duration.SetUnrestrictedDouble(duration);
369   timing->setDuration(unrestricted_duration);
370   timing->setDelay(delay);
371   animation->effect()->updateTiming(timing, exception_state);
372   return Response::Success();
373 }
374 
resolveAnimation(const String & animation_id,std::unique_ptr<v8_inspector::protocol::Runtime::API::RemoteObject> * result)375 Response InspectorAnimationAgent::resolveAnimation(
376     const String& animation_id,
377     std::unique_ptr<v8_inspector::protocol::Runtime::API::RemoteObject>*
378         result) {
379   blink::Animation* animation = nullptr;
380   Response response = AssertAnimation(animation_id, animation);
381   if (!response.IsSuccess())
382     return response;
383   if (id_to_animation_clone_.at(animation_id))
384     animation = id_to_animation_clone_.at(animation_id);
385   const Element* element =
386       To<KeyframeEffect>(animation->effect())->EffectTarget();
387   Document* document = element->ownerDocument();
388   LocalFrame* frame = document ? document->GetFrame() : nullptr;
389   ScriptState* script_state =
390       frame ? ToScriptStateForMainWorld(frame) : nullptr;
391   if (!script_state)
392     return Response::ServerError("Element not associated with a document.");
393 
394   ScriptState::Scope scope(script_state);
395   static const char kAnimationObjectGroup[] = "animation";
396   v8_session_->releaseObjectGroup(
397       ToV8InspectorStringView(kAnimationObjectGroup));
398   *result = v8_session_->wrapObject(
399       script_state->GetContext(),
400       ToV8(animation, script_state->GetContext()->Global(),
401            script_state->GetIsolate()),
402       ToV8InspectorStringView(kAnimationObjectGroup),
403       false /* generatePreview */);
404   if (!*result)
405     return Response::ServerError("Element not associated with a document.");
406   return Response::Success();
407 }
408 
CreateCSSId(blink::Animation & animation)409 String InspectorAnimationAgent::CreateCSSId(blink::Animation& animation) {
410   static const CSSProperty* g_animation_properties[] = {
411       &GetCSSPropertyAnimationDelay(),
412       &GetCSSPropertyAnimationDirection(),
413       &GetCSSPropertyAnimationDuration(),
414       &GetCSSPropertyAnimationFillMode(),
415       &GetCSSPropertyAnimationIterationCount(),
416       &GetCSSPropertyAnimationName(),
417       &GetCSSPropertyAnimationTimingFunction(),
418   };
419   static const CSSProperty* g_transition_properties[] = {
420       &GetCSSPropertyTransitionDelay(), &GetCSSPropertyTransitionDuration(),
421       &GetCSSPropertyTransitionProperty(),
422       &GetCSSPropertyTransitionTimingFunction(),
423   };
424 
425   auto* effect = To<KeyframeEffect>(animation.effect());
426   Vector<const CSSProperty*> css_properties;
427   if (IsA<CSSAnimation>(animation)) {
428     for (const CSSProperty* property : g_animation_properties)
429       css_properties.push_back(property);
430   } else if (auto* css_transition = DynamicTo<CSSTransition>(animation)) {
431     for (const CSSProperty* property : g_transition_properties)
432       css_properties.push_back(property);
433     css_properties.push_back(&css_transition->TransitionCSSProperty());
434   } else {
435     NOTREACHED();
436   }
437 
438   Element* element = effect->EffectTarget();
439   HeapVector<Member<CSSStyleDeclaration>> styles =
440       css_agent_->MatchingStyles(element);
441   Digestor digestor(kHashAlgorithmSha1);
442   digestor.UpdateUtf8(IsA<CSSTransition>(animation)
443                           ? AnimationType::CSSTransition
444                           : AnimationType::CSSAnimation);
445   digestor.UpdateUtf8(animation.id());
446   for (const CSSProperty* property : css_properties) {
447     CSSStyleDeclaration* style =
448         css_agent_->FindEffectiveDeclaration(*property, styles);
449     // Ignore inline styles.
450     if (!style || !style->ParentStyleSheet() || !style->parentRule() ||
451         style->parentRule()->type() != CSSRule::kStyleRule)
452       continue;
453     digestor.UpdateUtf8(property->GetPropertyNameString());
454     digestor.UpdateUtf8(css_agent_->StyleSheetId(style->ParentStyleSheet()));
455     digestor.UpdateUtf8(To<CSSStyleRule>(style->parentRule())->selectorText());
456   }
457   DigestValue digest_result;
458   digestor.Finish(digest_result);
459   DCHECK(!digestor.has_failed());
460   return Base64Encode(base::make_span(digest_result).first<10>());
461 }
462 
DidCreateAnimation(unsigned sequence_number)463 void InspectorAnimationAgent::DidCreateAnimation(unsigned sequence_number) {
464   if (is_cloning_)
465     return;
466   GetFrontend()->animationCreated(String::Number(sequence_number));
467 }
468 
AnimationPlayStateChanged(blink::Animation * animation,blink::Animation::AnimationPlayState old_play_state,blink::Animation::AnimationPlayState new_play_state)469 void InspectorAnimationAgent::AnimationPlayStateChanged(
470     blink::Animation* animation,
471     blink::Animation::AnimationPlayState old_play_state,
472     blink::Animation::AnimationPlayState new_play_state) {
473   const String& animation_id = String::Number(animation->SequenceNumber());
474 
475   // We no longer care about animations that have been released.
476   if (cleared_animations_.Contains(animation_id))
477     return;
478 
479   // Record newly starting animations only once, as |buildObjectForAnimation|
480   // constructs and caches our internal representation of the given |animation|.
481   if ((new_play_state == blink::Animation::kRunning ||
482        new_play_state == blink::Animation::kFinished) &&
483       !id_to_animation_.Contains(animation_id))
484     GetFrontend()->animationStarted(BuildObjectForAnimation(*animation));
485   else if (new_play_state == blink::Animation::kIdle ||
486            new_play_state == blink::Animation::kPaused)
487     GetFrontend()->animationCanceled(animation_id);
488 }
489 
DidClearDocumentOfWindowObject(LocalFrame * frame)490 void InspectorAnimationAgent::DidClearDocumentOfWindowObject(
491     LocalFrame* frame) {
492   if (!enabled_.Get())
493     return;
494   DCHECK(frame->GetDocument());
495   frame->GetDocument()->Timeline().SetPlaybackRate(
496       ReferenceTimeline().PlaybackRate());
497 }
498 
AssertAnimation(const String & id,blink::Animation * & result)499 Response InspectorAnimationAgent::AssertAnimation(const String& id,
500                                                   blink::Animation*& result) {
501   result = id_to_animation_.at(id);
502   if (!result)
503     return Response::ServerError("Could not find animation with given id");
504   return Response::Success();
505 }
506 
ReferenceTimeline()507 DocumentTimeline& InspectorAnimationAgent::ReferenceTimeline() {
508   return inspected_frames_->Root()->GetDocument()->Timeline();
509 }
510 
NormalizedStartTime(blink::Animation & animation)511 double InspectorAnimationAgent::NormalizedStartTime(
512     blink::Animation& animation) {
513   double time_ms = animation.startTime().value_or(Timing::NullValue());
514   auto* document_timeline = DynamicTo<DocumentTimeline>(animation.timeline());
515   if (document_timeline) {
516     if (ReferenceTimeline().PlaybackRate() == 0) {
517       time_ms +=
518           ReferenceTimeline().currentTime() - document_timeline->currentTime();
519     } else {
520       time_ms +=
521           (document_timeline->ZeroTime() - ReferenceTimeline().ZeroTime())
522               .InMillisecondsF() *
523           ReferenceTimeline().PlaybackRate();
524     }
525   }
526   // Round to the closest microsecond.
527   return std::round(time_ms * 1000) / 1000;
528 }
529 
Trace(Visitor * visitor)530 void InspectorAnimationAgent::Trace(Visitor* visitor) {
531   visitor->Trace(inspected_frames_);
532   visitor->Trace(css_agent_);
533   visitor->Trace(id_to_animation_);
534   visitor->Trace(id_to_animation_clone_);
535   InspectorBaseAgent::Trace(visitor);
536 }
537 
538 }  // namespace blink
539