Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(8)

Side by Side Diff: third_party/WebKit/Source/devtools/front_end/animation/AnimationTimeline.js

Issue 2466123002: DevTools: reformat front-end code to match chromium style. (Closed)
Patch Set: all done Created 4 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2015 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 /**
5 * @implements {WebInspector.TargetManager.Observer}
6 * @unrestricted
7 */
8 WebInspector.AnimationTimeline = class extends WebInspector.VBox {
9 constructor() {
10 super(true);
11 this.registerRequiredCSS('animation/animationTimeline.css');
12 this.element.classList.add('animations-timeline');
4 13
5 /** 14 this._grid = this.contentElement.createSVGChild('svg', 'animation-timeline-g rid');
6 * @constructor
7 * @extends {WebInspector.VBox}
8 * @implements {WebInspector.TargetManager.Observer}
9 */
10 WebInspector.AnimationTimeline = function()
11 {
12 WebInspector.VBox.call(this, true);
13 this.registerRequiredCSS("animation/animationTimeline.css");
14 this.element.classList.add("animations-timeline");
15
16 this._grid = this.contentElement.createSVGChild("svg", "animation-timeline-g rid");
17 15
18 this._playbackRate = 1; 16 this._playbackRate = 1;
19 this._allPaused = false; 17 this._allPaused = false;
20 this._createHeader(); 18 this._createHeader();
21 this._animationsContainer = this.contentElement.createChild("div", "animatio n-timeline-rows"); 19 this._animationsContainer = this.contentElement.createChild('div', 'animatio n-timeline-rows');
22 var timelineHint = this.contentElement.createChild("div", "animation-timelin e-rows-hint"); 20 var timelineHint = this.contentElement.createChild('div', 'animation-timelin e-rows-hint');
23 timelineHint.textContent = WebInspector.UIString("Select an effect above to inspect and modify."); 21 timelineHint.textContent = WebInspector.UIString('Select an effect above to inspect and modify.');
24 22
25 /** @const */ this._defaultDuration = 100; 23 /** @const */ this._defaultDuration = 100;
26 this._duration = this._defaultDuration; 24 this._duration = this._defaultDuration;
27 /** @const */ this._timelineControlsWidth = 150; 25 /** @const */ this._timelineControlsWidth = 150;
28 /** @type {!Map.<!DOMAgent.BackendNodeId, !WebInspector.AnimationTimeline.No deUI>} */ 26 /** @type {!Map.<!DOMAgent.BackendNodeId, !WebInspector.AnimationTimeline.No deUI>} */
29 this._nodesMap = new Map(); 27 this._nodesMap = new Map();
30 this._uiAnimations = []; 28 this._uiAnimations = [];
31 this._groupBuffer = []; 29 this._groupBuffer = [];
32 /** @type {!Map.<!WebInspector.AnimationModel.AnimationGroup, !WebInspector. AnimationGroupPreviewUI>} */ 30 /** @type {!Map.<!WebInspector.AnimationModel.AnimationGroup, !WebInspector. AnimationGroupPreviewUI>} */
33 this._previewMap = new Map(); 31 this._previewMap = new Map();
34 this._symbol = Symbol("animationTimeline"); 32 this._symbol = Symbol('animationTimeline');
35 /** @type {!Map.<string, !WebInspector.AnimationModel.Animation>} */ 33 /** @type {!Map.<string, !WebInspector.AnimationModel.Animation>} */
36 this._animationsMap = new Map(); 34 this._animationsMap = new Map();
37 WebInspector.targetManager.addModelListener(WebInspector.DOMModel, WebInspec tor.DOMModel.Events.NodeRemoved, this._nodeRemoved, this); 35 WebInspector.targetManager.addModelListener(
36 WebInspector.DOMModel, WebInspector.DOMModel.Events.NodeRemoved, this._n odeRemoved, this);
38 WebInspector.targetManager.observeTargets(this, WebInspector.Target.Capabili ty.DOM); 37 WebInspector.targetManager.observeTargets(this, WebInspector.Target.Capabili ty.DOM);
39 WebInspector.context.addFlavorChangeListener(WebInspector.DOMNode, this._nod eChanged, this); 38 WebInspector.context.addFlavorChangeListener(WebInspector.DOMNode, this._nod eChanged, this);
39 }
40
41 /**
42 * @override
43 */
44 wasShown() {
45 for (var target of WebInspector.targetManager.targets(WebInspector.Target.Ca pability.DOM))
46 this._addEventListeners(target);
47 }
48
49 /**
50 * @override
51 */
52 willHide() {
53 for (var target of WebInspector.targetManager.targets(WebInspector.Target.Ca pability.DOM))
54 this._removeEventListeners(target);
55 this._popoverHelper.hidePopover();
56 }
57
58 /**
59 * @override
60 * @param {!WebInspector.Target} target
61 */
62 targetAdded(target) {
63 if (this.isShowing())
64 this._addEventListeners(target);
65 }
66
67 /**
68 * @override
69 * @param {!WebInspector.Target} target
70 */
71 targetRemoved(target) {
72 this._removeEventListeners(target);
73 }
74
75 /**
76 * @param {!WebInspector.Target} target
77 */
78 _addEventListeners(target) {
79 var animationModel = WebInspector.AnimationModel.fromTarget(target);
80 animationModel.ensureEnabled();
81 animationModel.addEventListener(
82 WebInspector.AnimationModel.Events.AnimationGroupStarted, this._animatio nGroupStarted, this);
83 animationModel.addEventListener(WebInspector.AnimationModel.Events.ModelRese t, this._reset, this);
84 }
85
86 /**
87 * @param {!WebInspector.Target} target
88 */
89 _removeEventListeners(target) {
90 var animationModel = WebInspector.AnimationModel.fromTarget(target);
91 animationModel.removeEventListener(
92 WebInspector.AnimationModel.Events.AnimationGroupStarted, this._animatio nGroupStarted, this);
93 animationModel.removeEventListener(WebInspector.AnimationModel.Events.ModelR eset, this._reset, this);
94 }
95
96 _nodeChanged() {
97 for (var nodeUI of this._nodesMap.values())
98 nodeUI._nodeChanged();
99 }
100
101 /**
102 * @return {!Element} element
103 */
104 _createScrubber() {
105 this._timelineScrubber = createElementWithClass('div', 'animation-scrubber h idden');
106 this._timelineScrubberLine = this._timelineScrubber.createChild('div', 'anim ation-scrubber-line');
107 this._timelineScrubberLine.createChild('div', 'animation-scrubber-head');
108 this._timelineScrubber.createChild('div', 'animation-time-overlay');
109 return this._timelineScrubber;
110 }
111
112 _createHeader() {
113 var toolbarContainer = this.contentElement.createChild('div', 'animation-tim eline-toolbar-container');
114 var topToolbar = new WebInspector.Toolbar('animation-timeline-toolbar', tool barContainer);
115 var clearButton = new WebInspector.ToolbarButton(WebInspector.UIString('Clea r all'), 'clear-toolbar-item');
116 clearButton.addEventListener('click', this._reset.bind(this));
117 topToolbar.appendToolbarItem(clearButton);
118 topToolbar.appendSeparator();
119
120 this._pauseButton = new WebInspector.ToolbarToggle(WebInspector.UIString('Pa use all'), 'pause-toolbar-item');
121 this._pauseButton.addEventListener('click', this._togglePauseAll.bind(this)) ;
122 topToolbar.appendToolbarItem(this._pauseButton);
123
124 var playbackRateControl = toolbarContainer.createChild('div', 'animation-pla yback-rate-control');
125 this._playbackRateButtons = [];
126 for (var playbackRate of WebInspector.AnimationTimeline.GlobalPlaybackRates) {
127 var button = playbackRateControl.createChild('div', 'animation-playback-ra te-button');
128 button.textContent =
129 playbackRate ? WebInspector.UIString(playbackRate * 100 + '%') : WebIn spector.UIString('Pause');
130 button.playbackRate = playbackRate;
131 button.addEventListener('click', this._setPlaybackRate.bind(this, playback Rate));
132 button.title = WebInspector.UIString('Set speed to ') + button.textContent ;
133 this._playbackRateButtons.push(button);
134 }
135 this._updatePlaybackControls();
136
137 this._previewContainer = this.contentElement.createChild('div', 'animation-t imeline-buffer');
138 this._popoverHelper = new WebInspector.PopoverHelper(this._previewContainer, true);
139 this._popoverHelper.initializeCallbacks(
140 this._getPopoverAnchor.bind(this), this._showPopover.bind(this), this._o nHidePopover.bind(this));
141 this._popoverHelper.setTimeout(0);
142 var emptyBufferHint = this.contentElement.createChild('div', 'animation-time line-buffer-hint');
143 emptyBufferHint.textContent = WebInspector.UIString('Listening for animation s...');
144 var container = this.contentElement.createChild('div', 'animation-timeline-h eader');
145 var controls = container.createChild('div', 'animation-controls');
146 this._currentTime = controls.createChild('div', 'animation-timeline-current- time monospace');
147
148 var toolbar = new WebInspector.Toolbar('animation-controls-toolbar', control s);
149 this._controlButton =
150 new WebInspector.ToolbarButton(WebInspector.UIString('Replay timeline'), 'animation-control-toolbar-item');
151 this._controlButton.setState(WebInspector.AnimationTimeline._ControlState.Re play);
152 this._controlButton.addEventListener('click', this._controlButtonToggle.bind (this));
153 toolbar.appendToolbarItem(this._controlButton);
154
155 var gridHeader = container.createChild('div', 'animation-grid-header');
156 WebInspector.installDragHandle(
157 gridHeader, this._repositionScrubber.bind(this), this._scrubberDragMove. bind(this),
158 this._scrubberDragEnd.bind(this), 'text');
159 container.appendChild(this._createScrubber());
160 WebInspector.installDragHandle(
161 this._timelineScrubberLine, this._scrubberDragStart.bind(this), this._sc rubberDragMove.bind(this),
162 this._scrubberDragEnd.bind(this), 'col-resize');
163 this._currentTime.textContent = '';
164
165 return container;
166 }
167
168 /**
169 * @param {!Element} element
170 * @param {!Event} event
171 * @return {!Element|!AnchorBox|undefined}
172 */
173 _getPopoverAnchor(element, event) {
174 if (element.isDescendant(this._previewContainer))
175 return element;
176 }
177
178 /**
179 * @param {!Element} anchor
180 * @param {!WebInspector.Popover} popover
181 */
182 _showPopover(anchor, popover) {
183 var animGroup;
184 for (var group of this._previewMap.keysArray()) {
185 if (this._previewMap.get(group).element === anchor.parentElement)
186 animGroup = group;
187 }
188 console.assert(animGroup);
189 var screenshots = animGroup.screenshots();
190 if (!screenshots.length)
191 return;
192
193 if (!screenshots[0].complete)
194 screenshots[0].onload = onFirstScreenshotLoaded.bind(null, screenshots);
195 else
196 onFirstScreenshotLoaded(screenshots);
197
198 /**
199 * @param {!Array.<!Image>} screenshots
200 */
201 function onFirstScreenshotLoaded(screenshots) {
202 var content = new WebInspector.AnimationScreenshotPopover(screenshots);
203 popover.setNoPadding(true);
204 popover.showView(content, anchor);
205 }
206 }
207
208 _onHidePopover() {
209 }
210
211 _togglePauseAll() {
212 this._allPaused = !this._allPaused;
213 this._pauseButton.setToggled(this._allPaused);
214 this._setPlaybackRate(this._playbackRate);
215 this._pauseButton.setTitle(
216 this._allPaused ? WebInspector.UIString('Resume all') : WebInspector.UIS tring('Pause all'));
217 }
218
219 /**
220 * @param {number} playbackRate
221 */
222 _setPlaybackRate(playbackRate) {
223 this._playbackRate = playbackRate;
224 var target = WebInspector.targetManager.mainTarget();
225 if (target)
226 WebInspector.AnimationModel.fromTarget(target).setPlaybackRate(this._allPa used ? 0 : this._playbackRate);
227 WebInspector.userMetrics.actionTaken(WebInspector.UserMetrics.Action.Animati onsPlaybackRateChanged);
228 if (this._scrubberPlayer)
229 this._scrubberPlayer.playbackRate = this._effectivePlaybackRate();
230
231 this._updatePlaybackControls();
232 }
233
234 _updatePlaybackControls() {
235 for (var button of this._playbackRateButtons) {
236 var selected = this._playbackRate === button.playbackRate;
237 button.classList.toggle('selected', selected);
238 }
239 }
240
241 _controlButtonToggle() {
242 if (this._controlButton.state() === WebInspector.AnimationTimeline._ControlS tate.Play)
243 this._togglePause(false);
244 else if (this._controlButton.state() === WebInspector.AnimationTimeline._Con trolState.Replay)
245 this._replay();
246 else
247 this._togglePause(true);
248 }
249
250 _updateControlButton() {
251 this._controlButton.setEnabled(!!this._selectedGroup);
252 if (this._selectedGroup && this._selectedGroup.paused()) {
253 this._controlButton.setState(WebInspector.AnimationTimeline._ControlState. Play);
254 this._controlButton.setTitle(WebInspector.UIString('Play timeline'));
255 } else if (!this._scrubberPlayer || this._scrubberPlayer.currentTime >= this .duration()) {
256 this._controlButton.setState(WebInspector.AnimationTimeline._ControlState. Replay);
257 this._controlButton.setTitle(WebInspector.UIString('Replay timeline'));
258 } else {
259 this._controlButton.setState(WebInspector.AnimationTimeline._ControlState. Pause);
260 this._controlButton.setTitle(WebInspector.UIString('Pause timeline'));
261 }
262 }
263
264 /**
265 * @return {number}
266 */
267 _effectivePlaybackRate() {
268 return (this._allPaused || (this._selectedGroup && this._selectedGroup.pause d())) ? 0 : this._playbackRate;
269 }
270
271 /**
272 * @param {boolean} pause
273 */
274 _togglePause(pause) {
275 this._selectedGroup.togglePause(pause);
276 if (this._scrubberPlayer)
277 this._scrubberPlayer.playbackRate = this._effectivePlaybackRate();
278 this._previewMap.get(this._selectedGroup).element.classList.toggle('paused', pause);
279 this._updateControlButton();
280 }
281
282 _replay() {
283 if (!this._selectedGroup)
284 return;
285 this._selectedGroup.seekTo(0);
286 this._animateTime(0);
287 this._updateControlButton();
288 }
289
290 /**
291 * @return {number}
292 */
293 duration() {
294 return this._duration;
295 }
296
297 /**
298 * @param {number} duration
299 */
300 setDuration(duration) {
301 this._duration = duration;
302 this.scheduleRedraw();
303 }
304
305 _clearTimeline() {
306 this._uiAnimations = [];
307 this._nodesMap.clear();
308 this._animationsMap.clear();
309 this._animationsContainer.removeChildren();
310 this._duration = this._defaultDuration;
311 this._timelineScrubber.classList.add('hidden');
312 delete this._selectedGroup;
313 if (this._scrubberPlayer)
314 this._scrubberPlayer.cancel();
315 delete this._scrubberPlayer;
316 this._currentTime.textContent = '';
317 this._updateControlButton();
318 }
319
320 _reset() {
321 this._clearTimeline();
322 if (this._allPaused) {
323 this._playbackRate = 1;
324 this._togglePauseAll();
325 } else {
326 this._setPlaybackRate(1);
327 }
328 for (var group of this._groupBuffer)
329 group.release();
330 this._groupBuffer = [];
331 this._previewMap.clear();
332 this._previewContainer.removeChildren();
333 this._popoverHelper.hidePopover();
334 this._renderGrid();
335 }
336
337 /**
338 * @param {!WebInspector.Event} event
339 */
340 _animationGroupStarted(event) {
341 this._addAnimationGroup(/** @type {!WebInspector.AnimationModel.AnimationGro up} */ (event.data));
342 }
343
344 /**
345 * @param {!WebInspector.AnimationModel.AnimationGroup} group
346 */
347 _addAnimationGroup(group) {
348 /**
349 * @param {!WebInspector.AnimationModel.AnimationGroup} left
350 * @param {!WebInspector.AnimationModel.AnimationGroup} right
351 */
352 function startTimeComparator(left, right) {
353 return left.startTime() > right.startTime();
354 }
355
356 if (this._previewMap.get(group)) {
357 if (this._selectedGroup === group)
358 this._syncScrubber();
359 else
360 this._previewMap.get(group).replay();
361 return;
362 }
363 this._groupBuffer.sort(startTimeComparator);
364 // Discard oldest groups from buffer if necessary
365 var groupsToDiscard = [];
366 var bufferSize = this.width() / 50;
367 while (this._groupBuffer.length > bufferSize) {
368 var toDiscard = this._groupBuffer.splice(this._groupBuffer[0] === this._se lectedGroup ? 1 : 0, 1);
369 groupsToDiscard.push(toDiscard[0]);
370 }
371 for (var g of groupsToDiscard) {
372 this._previewMap.get(g).element.remove();
373 this._previewMap.delete(g);
374 g.release();
375 }
376 // Generate preview
377 var preview = new WebInspector.AnimationGroupPreviewUI(group);
378 this._groupBuffer.push(group);
379 this._previewMap.set(group, preview);
380 this._previewContainer.appendChild(preview.element);
381 preview.removeButton().addEventListener('click', this._removeAnimationGroup. bind(this, group));
382 preview.element.addEventListener('click', this._selectAnimationGroup.bind(th is, group));
383 }
384
385 /**
386 * @param {!WebInspector.AnimationModel.AnimationGroup} group
387 * @param {!Event} event
388 */
389 _removeAnimationGroup(group, event) {
390 this._groupBuffer.remove(group);
391 this._previewMap.get(group).element.remove();
392 this._previewMap.delete(group);
393 group.release();
394 event.consume(true);
395
396 if (this._selectedGroup === group) {
397 this._clearTimeline();
398 this._renderGrid();
399 }
400 }
401
402 /**
403 * @param {!WebInspector.AnimationModel.AnimationGroup} group
404 */
405 _selectAnimationGroup(group) {
406 /**
407 * @param {!WebInspector.AnimationGroupPreviewUI} ui
408 * @param {!WebInspector.AnimationModel.AnimationGroup} group
409 * @this {!WebInspector.AnimationTimeline}
410 */
411 function applySelectionClass(ui, group) {
412 ui.element.classList.toggle('selected', this._selectedGroup === group);
413 }
414
415 if (this._selectedGroup === group) {
416 this._togglePause(false);
417 this._replay();
418 return;
419 }
420 this._clearTimeline();
421 this._selectedGroup = group;
422 this._previewMap.forEach(applySelectionClass, this);
423 this.setDuration(Math.max(500, group.finiteDuration() + 100));
424 for (var anim of group.animations())
425 this._addAnimation(anim);
426 this.scheduleRedraw();
427 this._timelineScrubber.classList.remove('hidden');
428 this._togglePause(false);
429 this._replay();
430 }
431
432 /**
433 * @param {!WebInspector.AnimationModel.Animation} animation
434 */
435 _addAnimation(animation) {
436 /**
437 * @param {?WebInspector.DOMNode} node
438 * @this {WebInspector.AnimationTimeline}
439 */
440 function nodeResolved(node) {
441 nodeUI.nodeResolved(node);
442 uiAnimation.setNode(node);
443 if (node)
444 node[this._symbol] = nodeUI;
445 }
446
447 var nodeUI = this._nodesMap.get(animation.source().backendNodeId());
448 if (!nodeUI) {
449 nodeUI = new WebInspector.AnimationTimeline.NodeUI(animation.source());
450 this._animationsContainer.appendChild(nodeUI.element);
451 this._nodesMap.set(animation.source().backendNodeId(), nodeUI);
452 }
453 var nodeRow = nodeUI.createNewRow();
454 var uiAnimation = new WebInspector.AnimationUI(animation, this, nodeRow);
455 animation.source().deferredNode().resolve(nodeResolved.bind(this));
456 this._uiAnimations.push(uiAnimation);
457 this._animationsMap.set(animation.id(), animation);
458 }
459
460 /**
461 * @param {!WebInspector.Event} event
462 */
463 _nodeRemoved(event) {
464 var node = event.data.node;
465 if (node[this._symbol])
466 node[this._symbol].nodeRemoved();
467 }
468
469 _renderGrid() {
470 /** @const */ var gridSize = 250;
471 this._grid.setAttribute('width', this.width() + 10);
472 this._grid.setAttribute('height', this._cachedTimelineHeight + 30);
473 this._grid.setAttribute('shape-rendering', 'crispEdges');
474 this._grid.removeChildren();
475 var lastDraw = undefined;
476 for (var time = 0; time < this.duration(); time += gridSize) {
477 var line = this._grid.createSVGChild('rect', 'animation-timeline-grid-line ');
478 line.setAttribute('x', time * this.pixelMsRatio() + 10);
479 line.setAttribute('y', 23);
480 line.setAttribute('height', '100%');
481 line.setAttribute('width', 1);
482 }
483 for (var time = 0; time < this.duration(); time += gridSize) {
484 var gridWidth = time * this.pixelMsRatio();
485 if (lastDraw === undefined || gridWidth - lastDraw > 50) {
486 lastDraw = gridWidth;
487 var label = this._grid.createSVGChild('text', 'animation-timeline-grid-l abel');
488 label.textContent = WebInspector.UIString(Number.millisToString(time));
489 label.setAttribute('x', gridWidth + 10);
490 label.setAttribute('y', 16);
491 }
492 }
493 }
494
495 scheduleRedraw() {
496 this._renderQueue = [];
497 for (var ui of this._uiAnimations)
498 this._renderQueue.push(ui);
499 if (this._redrawing)
500 return;
501 this._redrawing = true;
502 this._renderGrid();
503 this._animationsContainer.window().requestAnimationFrame(this._render.bind(t his));
504 }
505
506 /**
507 * @param {number=} timestamp
508 */
509 _render(timestamp) {
510 while (this._renderQueue.length && (!timestamp || window.performance.now() - timestamp < 50))
511 this._renderQueue.shift().redraw();
512 if (this._renderQueue.length)
513 this._animationsContainer.window().requestAnimationFrame(this._render.bind (this));
514 else
515 delete this._redrawing;
516 }
517
518 /**
519 * @override
520 */
521 onResize() {
522 this._cachedTimelineWidth = Math.max(0, this._animationsContainer.offsetWidt h - this._timelineControlsWidth) || 0;
523 this._cachedTimelineHeight = this._animationsContainer.offsetHeight;
524 this.scheduleRedraw();
525 if (this._scrubberPlayer)
526 this._syncScrubber();
527 delete this._gridOffsetLeft;
528 }
529
530 /**
531 * @return {number}
532 */
533 width() {
534 return this._cachedTimelineWidth || 0;
535 }
536
537 /**
538 * @param {!WebInspector.AnimationModel.Animation} animation
539 * @return {boolean}
540 */
541 _resizeWindow(animation) {
542 var resized = false;
543
544 // This shows at most 3 iterations
545 var duration = animation.source().duration() * Math.min(2, animation.source( ).iterations());
546 var requiredDuration = animation.source().delay() + duration + animation.sou rce().endDelay();
547 if (requiredDuration > this._duration) {
548 resized = true;
549 this._duration = requiredDuration + 200;
550 }
551 return resized;
552 }
553
554 _syncScrubber() {
555 if (!this._selectedGroup)
556 return;
557 this._selectedGroup.currentTimePromise()
558 .then(this._animateTime.bind(this))
559 .then(this._updateControlButton.bind(this));
560 }
561
562 /**
563 * @param {number} currentTime
564 */
565 _animateTime(currentTime) {
566 if (this._scrubberPlayer)
567 this._scrubberPlayer.cancel();
568
569 this._scrubberPlayer = this._timelineScrubber.animate(
570 [{transform: 'translateX(0px)'}, {transform: 'translateX(' + this.width( ) + 'px)'}],
571 {duration: this.duration(), fill: 'forwards'});
572 this._scrubberPlayer.playbackRate = this._effectivePlaybackRate();
573 this._scrubberPlayer.onfinish = this._updateControlButton.bind(this);
574 this._scrubberPlayer.currentTime = currentTime;
575 this.element.window().requestAnimationFrame(this._updateScrubber.bind(this)) ;
576 }
577
578 /**
579 * @return {number}
580 */
581 pixelMsRatio() {
582 return this.width() / this.duration() || 0;
583 }
584
585 /**
586 * @param {number} timestamp
587 */
588 _updateScrubber(timestamp) {
589 if (!this._scrubberPlayer)
590 return;
591 this._currentTime.textContent = WebInspector.UIString(Number.millisToString( this._scrubberPlayer.currentTime));
592 if (this._scrubberPlayer.playState === 'pending' || this._scrubberPlayer.pla yState === 'running') {
593 this.element.window().requestAnimationFrame(this._updateScrubber.bind(this ));
594 } else if (this._scrubberPlayer.playState === 'finished') {
595 this._currentTime.textContent = '';
596 }
597 }
598
599 /**
600 * @param {!Event} event
601 * @return {boolean}
602 */
603 _repositionScrubber(event) {
604 if (!this._selectedGroup)
605 return false;
606
607 // Seek to current mouse position.
608 if (!this._gridOffsetLeft)
609 this._gridOffsetLeft = this._grid.totalOffsetLeft() + 10;
610 var seekTime = Math.max(0, event.x - this._gridOffsetLeft) / this.pixelMsRat io();
611 this._selectedGroup.seekTo(seekTime);
612 this._togglePause(true);
613 this._animateTime(seekTime);
614
615 // Interface with scrubber drag.
616 this._originalScrubberTime = seekTime;
617 this._originalMousePosition = event.x;
618 return true;
619 }
620
621 /**
622 * @param {!Event} event
623 * @return {boolean}
624 */
625 _scrubberDragStart(event) {
626 if (!this._scrubberPlayer || !this._selectedGroup)
627 return false;
628
629 this._originalScrubberTime = this._scrubberPlayer.currentTime;
630 this._timelineScrubber.classList.remove('animation-timeline-end');
631 this._scrubberPlayer.pause();
632 this._originalMousePosition = event.x;
633
634 this._togglePause(true);
635 return true;
636 }
637
638 /**
639 * @param {!Event} event
640 */
641 _scrubberDragMove(event) {
642 var delta = event.x - this._originalMousePosition;
643 var currentTime = Math.max(0, Math.min(this._originalScrubberTime + delta / this.pixelMsRatio(), this.duration()));
644 this._scrubberPlayer.currentTime = currentTime;
645 this._currentTime.textContent = WebInspector.UIString(Number.millisToString( Math.round(currentTime)));
646 this._selectedGroup.seekTo(currentTime);
647 }
648
649 /**
650 * @param {!Event} event
651 */
652 _scrubberDragEnd(event) {
653 var currentTime = Math.max(0, this._scrubberPlayer.currentTime);
654 this._scrubberPlayer.play();
655 this._scrubberPlayer.currentTime = currentTime;
656 this._currentTime.window().requestAnimationFrame(this._updateScrubber.bind(t his));
657 }
40 }; 658 };
41 659
42 WebInspector.AnimationTimeline.GlobalPlaybackRates = [1, 0.25, 0.1]; 660 WebInspector.AnimationTimeline.GlobalPlaybackRates = [1, 0.25, 0.1];
43 661
44 /** @enum {string} */ 662 /** @enum {string} */
45 WebInspector.AnimationTimeline._ControlState = { 663 WebInspector.AnimationTimeline._ControlState = {
46 Play: "play-outline", 664 Play: 'play-outline',
47 Replay: "replay-outline", 665 Replay: 'replay-outline',
48 Pause: "pause-outline" 666 Pause: 'pause-outline'
49 }; 667 };
50 668
51 WebInspector.AnimationTimeline.prototype = { 669 /**
52 wasShown: function() 670 * @unrestricted
53 { 671 */
54 for (var target of WebInspector.targetManager.targets(WebInspector.Targe t.Capability.DOM)) 672 WebInspector.AnimationTimeline.NodeUI = class {
55 this._addEventListeners(target); 673 /**
56 }, 674 * @param {!WebInspector.AnimationModel.AnimationEffect} animationEffect
57 675 */
58 willHide: function() 676 constructor(animationEffect) {
59 { 677 this.element = createElementWithClass('div', 'animation-node-row');
60 for (var target of WebInspector.targetManager.targets(WebInspector.Targe t.Capability.DOM)) 678 this._description = this.element.createChild('div', 'animation-node-descript ion');
61 this._removeEventListeners(target); 679 this._timelineElement = this.element.createChild('div', 'animation-node-time line');
62 this._popoverHelper.hidePopover(); 680 }
63 }, 681
64 682 /**
65 /** 683 * @param {?WebInspector.DOMNode} node
66 * @override 684 */
67 * @param {!WebInspector.Target} target 685 nodeResolved(node) {
68 */ 686 if (!node) {
69 targetAdded: function(target) 687 this._description.createTextChild(WebInspector.UIString('<node>'));
70 { 688 return;
71 if (this.isShowing()) 689 }
72 this._addEventListeners(target); 690 this._node = node;
73 }, 691 this._nodeChanged();
74 692 this._description.appendChild(WebInspector.DOMPresentationUtils.linkifyNodeR eference(node));
75 /** 693 if (!node.ownerDocument)
76 * @override 694 this.nodeRemoved();
77 * @param {!WebInspector.Target} target 695 }
78 */ 696
79 targetRemoved: function(target) 697 /**
80 { 698 * @return {!Element}
81 this._removeEventListeners(target); 699 */
82 }, 700 createNewRow() {
83 701 return this._timelineElement.createChild('div', 'animation-timeline-row');
84 /** 702 }
85 * @param {!WebInspector.Target} target 703
86 */ 704 nodeRemoved() {
87 _addEventListeners: function(target) 705 this.element.classList.add('animation-node-removed');
88 { 706 this._node = null;
89 var animationModel = WebInspector.AnimationModel.fromTarget(target); 707 }
90 animationModel.ensureEnabled(); 708
91 animationModel.addEventListener(WebInspector.AnimationModel.Events.Anima tionGroupStarted, this._animationGroupStarted, this); 709 _nodeChanged() {
92 animationModel.addEventListener(WebInspector.AnimationModel.Events.Model Reset, this._reset, this); 710 this.element.classList.toggle(
93 }, 711 'animation-node-selected', this._node && this._node === WebInspector.con text.flavor(WebInspector.DOMNode));
94 712 }
95 /**
96 * @param {!WebInspector.Target} target
97 */
98 _removeEventListeners: function(target)
99 {
100 var animationModel = WebInspector.AnimationModel.fromTarget(target);
101 animationModel.removeEventListener(WebInspector.AnimationModel.Events.An imationGroupStarted, this._animationGroupStarted, this);
102 animationModel.removeEventListener(WebInspector.AnimationModel.Events.Mo delReset, this._reset, this);
103 },
104
105 _nodeChanged: function()
106 {
107 for (var nodeUI of this._nodesMap.values())
108 nodeUI._nodeChanged();
109 },
110
111 /**
112 * @return {!Element} element
113 */
114 _createScrubber: function() {
115 this._timelineScrubber = createElementWithClass("div", "animation-scrubb er hidden");
116 this._timelineScrubberLine = this._timelineScrubber.createChild("div", " animation-scrubber-line");
117 this._timelineScrubberLine.createChild("div", "animation-scrubber-head") ;
118 this._timelineScrubber.createChild("div", "animation-time-overlay");
119 return this._timelineScrubber;
120 },
121
122 _createHeader: function()
123 {
124 var toolbarContainer = this.contentElement.createChild("div", "animation -timeline-toolbar-container");
125 var topToolbar = new WebInspector.Toolbar("animation-timeline-toolbar", toolbarContainer);
126 var clearButton = new WebInspector.ToolbarButton(WebInspector.UIString(" Clear all"), "clear-toolbar-item");
127 clearButton.addEventListener("click", this._reset.bind(this));
128 topToolbar.appendToolbarItem(clearButton);
129 topToolbar.appendSeparator();
130
131 this._pauseButton = new WebInspector.ToolbarToggle(WebInspector.UIString ("Pause all"), "pause-toolbar-item");
132 this._pauseButton.addEventListener("click", this._togglePauseAll.bind(th is));
133 topToolbar.appendToolbarItem(this._pauseButton);
134
135 var playbackRateControl = toolbarContainer.createChild("div", "animation -playback-rate-control");
136 this._playbackRateButtons = [];
137 for (var playbackRate of WebInspector.AnimationTimeline.GlobalPlaybackRa tes) {
138 var button = playbackRateControl.createChild("div", "animation-playb ack-rate-button");
139 button.textContent = playbackRate ? WebInspector.UIString(playbackRa te * 100 + "%") : WebInspector.UIString("Pause");
140 button.playbackRate = playbackRate;
141 button.addEventListener("click", this._setPlaybackRate.bind(this, pl aybackRate));
142 button.title = WebInspector.UIString("Set speed to ") + button.textC ontent;
143 this._playbackRateButtons.push(button);
144 }
145 this._updatePlaybackControls();
146
147 this._previewContainer = this.contentElement.createChild("div", "animati on-timeline-buffer");
148 this._popoverHelper = new WebInspector.PopoverHelper(this._previewContai ner, true);
149 this._popoverHelper.initializeCallbacks(this._getPopoverAnchor.bind(this ), this._showPopover.bind(this), this._onHidePopover.bind(this));
150 this._popoverHelper.setTimeout(0);
151 var emptyBufferHint = this.contentElement.createChild("div", "animation- timeline-buffer-hint");
152 emptyBufferHint.textContent = WebInspector.UIString("Listening for anima tions...");
153 var container = this.contentElement.createChild("div", "animation-timeli ne-header");
154 var controls = container.createChild("div", "animation-controls");
155 this._currentTime = controls.createChild("div", "animation-timeline-curr ent-time monospace");
156
157 var toolbar = new WebInspector.Toolbar("animation-controls-toolbar", con trols);
158 this._controlButton = new WebInspector.ToolbarButton(WebInspector.UIStri ng("Replay timeline"), "animation-control-toolbar-item");
159 this._controlButton.setState(WebInspector.AnimationTimeline._ControlStat e.Replay);
160 this._controlButton.addEventListener("click", this._controlButtonToggle. bind(this));
161 toolbar.appendToolbarItem(this._controlButton);
162
163 var gridHeader = container.createChild("div", "animation-grid-header");
164 WebInspector.installDragHandle(gridHeader, this._repositionScrubber.bind (this), this._scrubberDragMove.bind(this), this._scrubberDragEnd.bind(this), "te xt");
165 container.appendChild(this._createScrubber());
166 WebInspector.installDragHandle(this._timelineScrubberLine, this._scrubbe rDragStart.bind(this), this._scrubberDragMove.bind(this), this._scrubberDragEnd. bind(this), "col-resize");
167 this._currentTime.textContent = "";
168
169 return container;
170 },
171
172 /**
173 * @param {!Element} element
174 * @param {!Event} event
175 * @return {!Element|!AnchorBox|undefined}
176 */
177 _getPopoverAnchor: function(element, event)
178 {
179 if (element.isDescendant(this._previewContainer))
180 return element;
181 },
182
183 /**
184 * @param {!Element} anchor
185 * @param {!WebInspector.Popover} popover
186 */
187 _showPopover: function(anchor, popover)
188 {
189 var animGroup;
190 for (var group of this._previewMap.keysArray()) {
191 if (this._previewMap.get(group).element === anchor.parentElement)
192 animGroup = group;
193 }
194 console.assert(animGroup);
195 var screenshots = animGroup.screenshots();
196 if (!screenshots.length)
197 return;
198
199 if (!screenshots[0].complete)
200 screenshots[0].onload = onFirstScreenshotLoaded.bind(null, screensho ts);
201 else
202 onFirstScreenshotLoaded(screenshots);
203
204 /**
205 * @param {!Array.<!Image>} screenshots
206 */
207 function onFirstScreenshotLoaded(screenshots)
208 {
209 var content = new WebInspector.AnimationScreenshotPopover(screenshot s);
210 popover.setNoPadding(true);
211 popover.showView(content, anchor);
212 }
213 },
214
215 _onHidePopover: function()
216 {
217 },
218
219 _togglePauseAll: function()
220 {
221 this._allPaused = !this._allPaused;
222 this._pauseButton.setToggled(this._allPaused);
223 this._setPlaybackRate(this._playbackRate);
224 this._pauseButton.setTitle(this._allPaused ? WebInspector.UIString("Resu me all") : WebInspector.UIString("Pause all"));
225 },
226
227 /**
228 * @param {number} playbackRate
229 */
230 _setPlaybackRate: function(playbackRate)
231 {
232 this._playbackRate = playbackRate;
233 var target = WebInspector.targetManager.mainTarget();
234 if (target)
235 WebInspector.AnimationModel.fromTarget(target).setPlaybackRate(this. _allPaused ? 0 : this._playbackRate);
236 WebInspector.userMetrics.actionTaken(WebInspector.UserMetrics.Action.Ani mationsPlaybackRateChanged);
237 if (this._scrubberPlayer)
238 this._scrubberPlayer.playbackRate = this._effectivePlaybackRate();
239
240 this._updatePlaybackControls();
241 },
242
243 _updatePlaybackControls: function()
244 {
245 for (var button of this._playbackRateButtons) {
246 var selected = this._playbackRate === button.playbackRate;
247 button.classList.toggle("selected", selected);
248 }
249 },
250
251 _controlButtonToggle: function()
252 {
253 if (this._controlButton.state() === WebInspector.AnimationTimeline._Cont rolState.Play)
254 this._togglePause(false);
255 else if (this._controlButton.state() === WebInspector.AnimationTimeline. _ControlState.Replay)
256 this._replay();
257 else
258 this._togglePause(true);
259 },
260
261 _updateControlButton: function()
262 {
263 this._controlButton.setEnabled(!!this._selectedGroup);
264 if (this._selectedGroup && this._selectedGroup.paused()) {
265 this._controlButton.setState(WebInspector.AnimationTimeline._Control State.Play);
266 this._controlButton.setTitle(WebInspector.UIString("Play timeline")) ;
267 } else if (!this._scrubberPlayer || this._scrubberPlayer.currentTime >= this.duration()) {
268 this._controlButton.setState(WebInspector.AnimationTimeline._Control State.Replay);
269 this._controlButton.setTitle(WebInspector.UIString("Replay timeline" ));
270 } else {
271 this._controlButton.setState(WebInspector.AnimationTimeline._Control State.Pause);
272 this._controlButton.setTitle(WebInspector.UIString("Pause timeline") );
273 }
274 },
275
276 /**
277 * @return {number}
278 */
279 _effectivePlaybackRate: function()
280 {
281 return (this._allPaused || (this._selectedGroup && this._selectedGroup.p aused())) ? 0 : this._playbackRate;
282 },
283
284 /**
285 * @param {boolean} pause
286 */
287 _togglePause: function(pause)
288 {
289 this._selectedGroup.togglePause(pause);
290 if (this._scrubberPlayer)
291 this._scrubberPlayer.playbackRate = this._effectivePlaybackRate();
292 this._previewMap.get(this._selectedGroup).element.classList.toggle("paus ed", pause);
293 this._updateControlButton();
294 },
295
296 _replay: function()
297 {
298 if (!this._selectedGroup)
299 return;
300 this._selectedGroup.seekTo(0);
301 this._animateTime(0);
302 this._updateControlButton();
303 },
304
305 /**
306 * @return {number}
307 */
308 duration: function()
309 {
310 return this._duration;
311 },
312
313 /**
314 * @param {number} duration
315 */
316 setDuration: function(duration)
317 {
318 this._duration = duration;
319 this.scheduleRedraw();
320 },
321
322 _clearTimeline: function()
323 {
324 this._uiAnimations = [];
325 this._nodesMap.clear();
326 this._animationsMap.clear();
327 this._animationsContainer.removeChildren();
328 this._duration = this._defaultDuration;
329 this._timelineScrubber.classList.add("hidden");
330 delete this._selectedGroup;
331 if (this._scrubberPlayer)
332 this._scrubberPlayer.cancel();
333 delete this._scrubberPlayer;
334 this._currentTime.textContent = "";
335 this._updateControlButton();
336 },
337
338 _reset: function()
339 {
340 this._clearTimeline();
341 if (this._allPaused) {
342 this._playbackRate = 1;
343 this._togglePauseAll();
344 } else {
345 this._setPlaybackRate(1);
346 }
347 for (var group of this._groupBuffer)
348 group.release();
349 this._groupBuffer = [];
350 this._previewMap.clear();
351 this._previewContainer.removeChildren();
352 this._popoverHelper.hidePopover();
353 this._renderGrid();
354 },
355
356 /**
357 * @param {!WebInspector.Event} event
358 */
359 _animationGroupStarted: function(event)
360 {
361 this._addAnimationGroup(/** @type {!WebInspector.AnimationModel.Animatio nGroup} */(event.data));
362 },
363
364 /**
365 * @param {!WebInspector.AnimationModel.AnimationGroup} group
366 */
367 _addAnimationGroup: function(group)
368 {
369 /**
370 * @param {!WebInspector.AnimationModel.AnimationGroup} left
371 * @param {!WebInspector.AnimationModel.AnimationGroup} right
372 */
373 function startTimeComparator(left, right)
374 {
375 return left.startTime() > right.startTime();
376 }
377
378 if (this._previewMap.get(group)) {
379 if (this._selectedGroup === group)
380 this._syncScrubber();
381 else
382 this._previewMap.get(group).replay();
383 return;
384 }
385 this._groupBuffer.sort(startTimeComparator);
386 // Discard oldest groups from buffer if necessary
387 var groupsToDiscard = [];
388 var bufferSize = this.width() / 50;
389 while (this._groupBuffer.length > bufferSize) {
390 var toDiscard = this._groupBuffer.splice(this._groupBuffer[0] === th is._selectedGroup ? 1 : 0, 1);
391 groupsToDiscard.push(toDiscard[0]);
392 }
393 for (var g of groupsToDiscard) {
394 this._previewMap.get(g).element.remove();
395 this._previewMap.delete(g);
396 g.release();
397 }
398 // Generate preview
399 var preview = new WebInspector.AnimationGroupPreviewUI(group);
400 this._groupBuffer.push(group);
401 this._previewMap.set(group, preview);
402 this._previewContainer.appendChild(preview.element);
403 preview.removeButton().addEventListener("click", this._removeAnimationGr oup.bind(this, group));
404 preview.element.addEventListener("click", this._selectAnimationGroup.bin d(this, group));
405 },
406
407 /**
408 * @param {!WebInspector.AnimationModel.AnimationGroup} group
409 * @param {!Event} event
410 */
411 _removeAnimationGroup: function(group, event)
412 {
413 this._groupBuffer.remove(group);
414 this._previewMap.get(group).element.remove();
415 this._previewMap.delete(group);
416 group.release();
417 event.consume(true);
418
419 if (this._selectedGroup === group) {
420 this._clearTimeline();
421 this._renderGrid();
422 }
423 },
424
425 /**
426 * @param {!WebInspector.AnimationModel.AnimationGroup} group
427 */
428 _selectAnimationGroup: function(group)
429 {
430 /**
431 * @param {!WebInspector.AnimationGroupPreviewUI} ui
432 * @param {!WebInspector.AnimationModel.AnimationGroup} group
433 * @this {!WebInspector.AnimationTimeline}
434 */
435 function applySelectionClass(ui, group)
436 {
437 ui.element.classList.toggle("selected", this._selectedGroup === grou p);
438 }
439
440 if (this._selectedGroup === group) {
441 this._togglePause(false);
442 this._replay();
443 return;
444 }
445 this._clearTimeline();
446 this._selectedGroup = group;
447 this._previewMap.forEach(applySelectionClass, this);
448 this.setDuration(Math.max(500, group.finiteDuration() + 100));
449 for (var anim of group.animations())
450 this._addAnimation(anim);
451 this.scheduleRedraw();
452 this._timelineScrubber.classList.remove("hidden");
453 this._togglePause(false);
454 this._replay();
455 },
456
457 /**
458 * @param {!WebInspector.AnimationModel.Animation} animation
459 */
460 _addAnimation: function(animation)
461 {
462 /**
463 * @param {?WebInspector.DOMNode} node
464 * @this {WebInspector.AnimationTimeline}
465 */
466 function nodeResolved(node)
467 {
468 nodeUI.nodeResolved(node);
469 uiAnimation.setNode(node);
470 if (node)
471 node[this._symbol] = nodeUI;
472 }
473
474 var nodeUI = this._nodesMap.get(animation.source().backendNodeId());
475 if (!nodeUI) {
476 nodeUI = new WebInspector.AnimationTimeline.NodeUI(animation.source( ));
477 this._animationsContainer.appendChild(nodeUI.element);
478 this._nodesMap.set(animation.source().backendNodeId(), nodeUI);
479 }
480 var nodeRow = nodeUI.createNewRow();
481 var uiAnimation = new WebInspector.AnimationUI(animation, this, nodeRow) ;
482 animation.source().deferredNode().resolve(nodeResolved.bind(this));
483 this._uiAnimations.push(uiAnimation);
484 this._animationsMap.set(animation.id(), animation);
485 },
486
487 /**
488 * @param {!WebInspector.Event} event
489 */
490 _nodeRemoved: function(event)
491 {
492 var node = event.data.node;
493 if (node[this._symbol])
494 node[this._symbol].nodeRemoved();
495 },
496
497 _renderGrid: function()
498 {
499 /** @const */ var gridSize = 250;
500 this._grid.setAttribute("width", this.width() + 10);
501 this._grid.setAttribute("height", this._cachedTimelineHeight + 30);
502 this._grid.setAttribute("shape-rendering", "crispEdges");
503 this._grid.removeChildren();
504 var lastDraw = undefined;
505 for (var time = 0; time < this.duration(); time += gridSize) {
506 var line = this._grid.createSVGChild("rect", "animation-timeline-gri d-line");
507 line.setAttribute("x", time * this.pixelMsRatio() + 10);
508 line.setAttribute("y", 23);
509 line.setAttribute("height", "100%");
510 line.setAttribute("width", 1);
511 }
512 for (var time = 0; time < this.duration(); time += gridSize) {
513 var gridWidth = time * this.pixelMsRatio();
514 if (lastDraw === undefined || gridWidth - lastDraw > 50) {
515 lastDraw = gridWidth;
516 var label = this._grid.createSVGChild("text", "animation-timelin e-grid-label");
517 label.textContent = WebInspector.UIString(Number.millisToString( time));
518 label.setAttribute("x", gridWidth + 10);
519 label.setAttribute("y", 16);
520 }
521 }
522 },
523
524 scheduleRedraw: function()
525 {
526 this._renderQueue = [];
527 for (var ui of this._uiAnimations)
528 this._renderQueue.push(ui);
529 if (this._redrawing)
530 return;
531 this._redrawing = true;
532 this._renderGrid();
533 this._animationsContainer.window().requestAnimationFrame(this._render.bi nd(this));
534 },
535
536 /**
537 * @param {number=} timestamp
538 */
539 _render: function(timestamp)
540 {
541 while (this._renderQueue.length && (!timestamp || window.performance.now () - timestamp < 50))
542 this._renderQueue.shift().redraw();
543 if (this._renderQueue.length)
544 this._animationsContainer.window().requestAnimationFrame(this._rende r.bind(this));
545 else
546 delete this._redrawing;
547 },
548
549 onResize: function()
550 {
551 this._cachedTimelineWidth = Math.max(0, this._animationsContainer.offset Width - this._timelineControlsWidth) || 0;
552 this._cachedTimelineHeight = this._animationsContainer.offsetHeight;
553 this.scheduleRedraw();
554 if (this._scrubberPlayer)
555 this._syncScrubber();
556 delete this._gridOffsetLeft;
557 },
558
559 /**
560 * @return {number}
561 */
562 width: function()
563 {
564 return this._cachedTimelineWidth || 0;
565 },
566
567 /**
568 * @param {!WebInspector.AnimationModel.Animation} animation
569 * @return {boolean}
570 */
571 _resizeWindow: function(animation)
572 {
573 var resized = false;
574
575 // This shows at most 3 iterations
576 var duration = animation.source().duration() * Math.min(2, animation.sou rce().iterations());
577 var requiredDuration = animation.source().delay() + duration + animation .source().endDelay();
578 if (requiredDuration > this._duration) {
579 resized = true;
580 this._duration = requiredDuration + 200;
581 }
582 return resized;
583 },
584
585 _syncScrubber: function()
586 {
587 if (!this._selectedGroup)
588 return;
589 this._selectedGroup.currentTimePromise()
590 .then(this._animateTime.bind(this))
591 .then(this._updateControlButton.bind(this));
592 },
593
594 /**
595 * @param {number} currentTime
596 */
597 _animateTime: function(currentTime)
598 {
599 if (this._scrubberPlayer)
600 this._scrubberPlayer.cancel();
601
602 this._scrubberPlayer = this._timelineScrubber.animate([
603 { transform: "translateX(0px)" },
604 { transform: "translateX(" + this.width() + "px)" }
605 ], { duration: this.duration(), fill: "forwards" });
606 this._scrubberPlayer.playbackRate = this._effectivePlaybackRate();
607 this._scrubberPlayer.onfinish = this._updateControlButton.bind(this);
608 this._scrubberPlayer.currentTime = currentTime;
609 this.element.window().requestAnimationFrame(this._updateScrubber.bind(th is));
610 },
611
612 /**
613 * @return {number}
614 */
615 pixelMsRatio: function()
616 {
617 return this.width() / this.duration() || 0;
618 },
619
620 /**
621 * @param {number} timestamp
622 */
623 _updateScrubber: function(timestamp)
624 {
625 if (!this._scrubberPlayer)
626 return;
627 this._currentTime.textContent = WebInspector.UIString(Number.millisToStr ing(this._scrubberPlayer.currentTime));
628 if (this._scrubberPlayer.playState === "pending" || this._scrubberPlayer .playState === "running") {
629 this.element.window().requestAnimationFrame(this._updateScrubber.bin d(this));
630 } else if (this._scrubberPlayer.playState === "finished") {
631 this._currentTime.textContent = "";
632 }
633 },
634
635 /**
636 * @param {!Event} event
637 * @return {boolean}
638 */
639 _repositionScrubber: function(event)
640 {
641 if (!this._selectedGroup)
642 return false;
643
644 // Seek to current mouse position.
645 if (!this._gridOffsetLeft)
646 this._gridOffsetLeft = this._grid.totalOffsetLeft() + 10;
647 var seekTime = Math.max(0, event.x - this._gridOffsetLeft) / this.pixelM sRatio();
648 this._selectedGroup.seekTo(seekTime);
649 this._togglePause(true);
650 this._animateTime(seekTime);
651
652 // Interface with scrubber drag.
653 this._originalScrubberTime = seekTime;
654 this._originalMousePosition = event.x;
655 return true;
656 },
657
658 /**
659 * @param {!Event} event
660 * @return {boolean}
661 */
662 _scrubberDragStart: function(event)
663 {
664 if (!this._scrubberPlayer || !this._selectedGroup)
665 return false;
666
667 this._originalScrubberTime = this._scrubberPlayer.currentTime;
668 this._timelineScrubber.classList.remove("animation-timeline-end");
669 this._scrubberPlayer.pause();
670 this._originalMousePosition = event.x;
671
672 this._togglePause(true);
673 return true;
674 },
675
676 /**
677 * @param {!Event} event
678 */
679 _scrubberDragMove: function(event)
680 {
681 var delta = event.x - this._originalMousePosition;
682 var currentTime = Math.max(0, Math.min(this._originalScrubberTime + delt a / this.pixelMsRatio(), this.duration()));
683 this._scrubberPlayer.currentTime = currentTime;
684 this._currentTime.textContent = WebInspector.UIString(Number.millisToStr ing(Math.round(currentTime)));
685 this._selectedGroup.seekTo(currentTime);
686 },
687
688 /**
689 * @param {!Event} event
690 */
691 _scrubberDragEnd: function(event)
692 {
693 var currentTime = Math.max(0, this._scrubberPlayer.currentTime);
694 this._scrubberPlayer.play();
695 this._scrubberPlayer.currentTime = currentTime;
696 this._currentTime.window().requestAnimationFrame(this._updateScrubber.bi nd(this));
697 },
698
699 __proto__: WebInspector.VBox.prototype
700 }; 713 };
701 714
702 /** 715 /**
703 * @constructor 716 * @unrestricted
704 * @param {!WebInspector.AnimationModel.AnimationEffect} animationEffect
705 */ 717 */
706 WebInspector.AnimationTimeline.NodeUI = function(animationEffect) 718 WebInspector.AnimationTimeline.StepTimingFunction = class {
707 { 719 /**
708 this.element = createElementWithClass("div", "animation-node-row"); 720 * @param {number} steps
709 this._description = this.element.createChild("div", "animation-node-descript ion"); 721 * @param {string} stepAtPosition
710 this._timelineElement = this.element.createChild("div", "animation-node-time line"); 722 */
711 }; 723 constructor(steps, stepAtPosition) {
712
713 WebInspector.AnimationTimeline.NodeUI.prototype = {
714 /**
715 * @param {?WebInspector.DOMNode} node
716 */
717 nodeResolved: function(node)
718 {
719 if (!node) {
720 this._description.createTextChild(WebInspector.UIString("<node>"));
721 return;
722 }
723 this._node = node;
724 this._nodeChanged();
725 this._description.appendChild(WebInspector.DOMPresentationUtils.linkifyN odeReference(node));
726 if (!node.ownerDocument)
727 this.nodeRemoved();
728 },
729
730 /**
731 * @return {!Element}
732 */
733 createNewRow: function()
734 {
735 return this._timelineElement.createChild("div", "animation-timeline-row" );
736 },
737
738 nodeRemoved: function()
739 {
740 this.element.classList.add("animation-node-removed");
741 this._node = null;
742 },
743
744 _nodeChanged: function()
745 {
746 this.element.classList.toggle("animation-node-selected", this._node && t his._node === WebInspector.context.flavor(WebInspector.DOMNode));
747 }
748 };
749
750 /**
751 * @constructor
752 * @param {number} steps
753 * @param {string} stepAtPosition
754 */
755 WebInspector.AnimationTimeline.StepTimingFunction = function(steps, stepAtPositi on)
756 {
757 this.steps = steps; 724 this.steps = steps;
758 this.stepAtPosition = stepAtPosition; 725 this.stepAtPosition = stepAtPosition;
759 }; 726 }
760 727
761 /** 728 /**
762 * @param {string} text 729 * @param {string} text
763 * @return {?WebInspector.AnimationTimeline.StepTimingFunction} 730 * @return {?WebInspector.AnimationTimeline.StepTimingFunction}
764 */ 731 */
765 WebInspector.AnimationTimeline.StepTimingFunction.parse = function(text) { 732 static parse(text) {
766 var match = text.match(/^steps\((\d+), (start|middle)\)$/); 733 var match = text.match(/^steps\((\d+), (start|middle)\)$/);
767 if (match) 734 if (match)
768 return new WebInspector.AnimationTimeline.StepTimingFunction(parseInt(ma tch[1], 10), match[2]); 735 return new WebInspector.AnimationTimeline.StepTimingFunction(parseInt(matc h[1], 10), match[2]);
769 match = text.match(/^steps\((\d+)\)$/); 736 match = text.match(/^steps\((\d+)\)$/);
770 if (match) 737 if (match)
771 return new WebInspector.AnimationTimeline.StepTimingFunction(parseInt(ma tch[1], 10), "end"); 738 return new WebInspector.AnimationTimeline.StepTimingFunction(parseInt(matc h[1], 10), 'end');
772 return null; 739 return null;
740 }
773 }; 741 };
742
743
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698