Keyframe animation doesn't 't show up in the Animations timeline
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / MediaTimelineDataGridNode.js
1 /*
2  * Copyright (C) 2018 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 WI.MediaTimelineDataGridNode = class MediaTimelineDataGridNode extends WI.TimelineDataGridNode
27 {
28     constructor(record, options = {})
29     {
30         console.assert(record instanceof WI.MediaTimelineRecord);
31
32         super([record], options);
33     }
34
35     // Public
36
37     get data()
38     {
39         if (this._cachedData)
40             return this._cachedData;
41
42         this._cachedData = super.data;
43         this._cachedData.name = this.record.displayName;
44         this._cachedData.element = this.record.domNode;
45         this._cachedData.source = this.record.domNode; // Timeline Overview
46         return this._cachedData;
47     }
48
49     createCellContent(columnIdentifier, cell)
50     {
51         let value = this.data[columnIdentifier];
52
53         switch (columnIdentifier) {
54         case "name":
55             cell.classList.add(...this.iconClassNames());
56             return this._createNameCellDocumentFragment();
57
58         case "element":
59         case "source": // Timeline Overview
60             if (!value)
61                 return emDash;
62             if (!(value instanceof WI.DOMNode)) {
63                 cell.classList.add(WI.DOMTreeElementPathComponent.DOMNodeIconStyleClassName);
64                 return value.displayName;
65             }
66             break;
67         }
68
69         return super.createCellContent(columnIdentifier, cell);
70     }
71
72     // TimelineRecordBar delegate
73
74     timelineRecordBarCustomChildren(timelineRecordBar)
75     {
76         let children = [];
77
78         let record = this.record;
79         let timestamps = record.timestamps;
80
81         switch (record.eventType) {
82         case WI.MediaTimelineRecord.EventType.CSSAnimation:
83         case WI.MediaTimelineRecord.EventType.CSSTransition: {
84             let readyStartTime = NaN;
85             function addReadySegment(startTime, endTime) {
86                 children.push({
87                     startTime,
88                     endTime,
89                     classNames: ["segment", "css-animation-ready"],
90                     title: WI.UIString("Ready", "Tooltip for a time range bar that represents when a CSS animation/transition exists but has not started processing"),
91                 });
92                 readyStartTime = NaN;
93             }
94
95             let delayStartTime = NaN;
96             function addDelaySegment(startTime, endTime) {
97                 children.push({
98                     startTime,
99                     endTime,
100                     classNames: ["segment", "css-animation-delay"],
101                     title: WI.UIString("Delay", "Tooltip for a time range bar that represents when a CSS animation/transition is delayed"),
102                 });
103                 delayStartTime = NaN;
104             }
105
106             let activeStartTime = NaN;
107             function addActiveSegment(startTime, endTime) {
108                 children.push({
109                     startTime,
110                     endTime,
111                     classNames: ["segment", "css-animation-active"],
112                     title: WI.UIString("Active", "Tooltip for a time range bar that represents when a CSS animation/transition is running"),
113                 });
114                 activeStartTime = NaN;
115             }
116
117             for (let item of timestamps) {
118                 switch (item.type) {
119                 case WI.MediaTimelineRecord.TimestampType.CSSAnimationReady:
120                     if (isNaN(readyStartTime))
121                         readyStartTime = item.timestamp;
122                     break;
123                 case WI.MediaTimelineRecord.TimestampType.CSSAnimationDelay:
124                     if (isNaN(delayStartTime))
125                         delayStartTime = item.timestamp;
126                     if (!isNaN(readyStartTime))
127                         addReadySegment(readyStartTime, item.timestamp);
128                     break;
129                 case WI.MediaTimelineRecord.TimestampType.CSSAnimationActive:
130                     if (isNaN(activeStartTime))
131                         activeStartTime = item.timestamp;
132                     if (!isNaN(readyStartTime))
133                         addReadySegment(readyStartTime, item.timestamp);
134                     if (!isNaN(delayStartTime))
135                         addDelaySegment(delayStartTime, item.timestamp);
136                     break;
137                 case WI.MediaTimelineRecord.TimestampType.CSSAnimationCancel:
138                 case WI.MediaTimelineRecord.TimestampType.CSSAnimationDone:
139                     if (!isNaN(readyStartTime))
140                         addReadySegment(readyStartTime, item.timestamp);
141                     if (!isNaN(delayStartTime))
142                         addDelaySegment(delayStartTime, item.timestamp);
143                     if (!isNaN(activeStartTime))
144                         addActiveSegment(activeStartTime, item.timestamp);
145                     break;
146                 }
147             }
148
149             if (!isNaN(readyStartTime))
150                 addReadySegment(readyStartTime, NaN);
151             if (!isNaN(delayStartTime))
152                 addDelaySegment(delayStartTime, NaN);
153             if (!isNaN(activeStartTime))
154                 addActiveSegment(activeStartTime, NaN);
155
156             break;
157         }
158
159         case WI.MediaTimelineRecord.EventType.MediaElement: {
160             let fullScreenSegments = [];
161             let powerEfficientPlaybackSegments = [];
162             let activeSegments = [];
163
164             let fullScreenStartTime = NaN;
165             let fullScreenOriginator = null;
166             function addFullScreenSegment(startTime, endTime) {
167                 fullScreenSegments.push({
168                     startTime,
169                     endTime,
170                     classNames: ["segment", "media-element-full-screen"],
171                     title: fullScreenOriginator ? WI.UIString("Full-Screen from \u201C%s\u201D").format(fullScreenOriginator.displayName) : WI.UIString("Full-Screen"),
172                 });
173                 fullScreenStartTime = NaN;
174                 fullScreenOriginator = null;
175             }
176
177             let powerEfficientPlaybackStartTime = NaN;
178             function addPowerEfficientPlaybackSegment(startTime, endTime) {
179                 powerEfficientPlaybackSegments.push({
180                     startTime,
181                     endTime,
182                     classNames: ["segment", "media-element-power-efficient-playback"],
183                     title: WI.UIString("Power Efficient Playback"),
184                 });
185                 powerEfficientPlaybackStartTime = NaN;
186             }
187
188             let pausedStartTime = NaN;
189             function addPausedSegment(startTime, endTime) {
190                 activeSegments.push({
191                     startTime,
192                     endTime,
193                     classNames: ["segment", "media-element-paused"],
194                     title: WI.UIString("Paused", "Tooltip for a time range bar that represents when the playback of a audio/video element is paused"),
195                 });
196                 pausedStartTime = NaN;
197             }
198
199             let playingStartTime = NaN;
200             function addPlayingSegment(startTime, endTime) {
201                 activeSegments.push({
202                     startTime,
203                     endTime,
204                     classNames: ["segment", "media-element-playing"],
205                     title: WI.UIString("Playing", "Tooltip for a time range bar that represents when the playback of a audio/video element is running"),
206                 });
207                 playingStartTime = NaN;
208             }
209
210             for (let item of timestamps) {
211                 if (item.type === WI.MediaTimelineRecord.TimestampType.MediaElementDOMEvent) {
212                     if (WI.DOMNode.isPlayEvent(item.eventName)) {
213                         if (isNaN(playingStartTime))
214                             playingStartTime = item.timestamp;
215                         if (!isNaN(pausedStartTime))
216                             addPausedSegment(pausedStartTime, item.timestamp);
217                     } else if (WI.DOMNode.isPauseEvent(item.eventName)) {
218                         if (isNaN(pausedStartTime))
219                             pausedStartTime = item.timestamp;
220                         if (!isNaN(playingStartTime))
221                             addPlayingSegment(playingStartTime, item.timestamp);
222                     } else if (WI.DOMNode.isStopEvent(item.eventName)) {
223                         if (!isNaN(pausedStartTime))
224                             addPausedSegment(pausedStartTime, item.timestamp);
225                         if (!isNaN(playingStartTime))
226                             addPlayingSegment(playingStartTime, item.timestamp);
227                     } else if (item.eventName === "webkitfullscreenchange") {
228                         if (!fullScreenOriginator && item.originator)
229                             fullScreenOriginator = item.originator;
230
231                         if (isNaN(fullScreenStartTime)) {
232                             if (item.data && item.data.enabled)
233                                 fullScreenStartTime = item.timestamp;
234                             else
235                                 addFullScreenSegment(this.graphDataSource ? this.graphDataSource.startTime : record.startTime, item.timestamp);
236                         } else if (!item.data || !item.data.enabled)
237                             addFullScreenSegment(fullScreenStartTime, item.timestamp);
238                     }
239                 } else if (item.type === WI.MediaTimelineRecord.TimestampType.MediaElementPowerEfficientPlaybackStateChange) {
240                     if (isNaN(powerEfficientPlaybackStartTime)) {
241                         if (item.isPowerEfficient)
242                             powerEfficientPlaybackStartTime = item.timestamp;
243                         else
244                             addPowerEfficientPlaybackSegment(this.graphDataSource ? this.graphDataSource.startTime : record.startTime, item.timestamp);
245                     } else if (!item.isPowerEfficient)
246                         addPowerEfficientPlaybackSegment(powerEfficientPlaybackStartTime, item.timestamp);
247                 }
248             }
249
250             if (!isNaN(fullScreenStartTime))
251                 addFullScreenSegment(fullScreenStartTime, NaN);
252             if (!isNaN(powerEfficientPlaybackStartTime))
253                 addPowerEfficientPlaybackSegment(powerEfficientPlaybackStartTime, NaN);
254             if (!isNaN(pausedStartTime))
255                 addPausedSegment(pausedStartTime, NaN);
256             if (!isNaN(playingStartTime))
257                 addPlayingSegment(playingStartTime, NaN);
258
259             children.pushAll(fullScreenSegments);
260             children.pushAll(powerEfficientPlaybackSegments);
261             children.pushAll(activeSegments);
262             break;
263         }
264         }
265
266         timestamps.forEach((item, i) => {
267             let image = {
268                 startTime: item.timestamp,
269                 classNames: [],
270             };
271
272             switch (item.type) {
273             case WI.MediaTimelineRecord.TimestampType.CSSAnimationReady:
274             case WI.MediaTimelineRecord.TimestampType.CSSAnimationDelay:
275             case WI.MediaTimelineRecord.TimestampType.CSSAnimationDone:
276             case WI.MediaTimelineRecord.TimestampType.MediaElementPowerEfficientPlaybackStateChange:
277                 // These timestamps are handled by the range segments above.
278                 return;
279
280             case WI.MediaTimelineRecord.TimestampType.CSSAnimationActive:
281                 // Don't create a marker segment for the first active timestamp, as that will be
282                 // handled by an active range segment above.
283                 if (!i || timestamps[i - 1].type !== WI.MediaTimelineRecord.TimestampType.CSSAnimationActive)
284                     return;
285
286                 image.image = "Images/EventIteration.svg";
287                 image.title = WI.UIString("Iteration", "Tooltip for a timestamp marker that represents when a CSS animation/transition iterates");
288                 break;
289
290             case WI.MediaTimelineRecord.TimestampType.CSSAnimationCancel:
291                 image.image = "Images/EventCancel.svg";
292                 image.title = WI.UIString("Canceled", "Tooltip for a timestamp marker that represents when a CSS animation/transition is canceled");
293                 break;
294
295             case WI.MediaTimelineRecord.TimestampType.MediaElementDOMEvent:
296                 // Don't create a marker segment full-screen timestamps, as that will be handled by a
297                 // range segment above.
298                 if (item.eventName === "webkitfullscreenchange")
299                     return;
300
301                 image.title = WI.UIString("DOM Event \u201C%s\u201D").format(item.eventName);
302                 if (WI.DOMNode.isPlayEvent(item.eventName))
303                     image.image = "Images/EventPlay.svg";
304                 else if (WI.DOMNode.isPauseEvent(item.eventName))
305                     image.image = "Images/EventPause.svg";
306                 else if (WI.DOMNode.isStopEvent(item.eventName))
307                     image.image = "Images/EventStop.svg";
308                 else
309                     image.image = "Images/EventProcessing.svg";
310                 break;
311             }
312
313             children.push(image);
314         });
315
316         return children;
317     }
318
319     // Protected
320
321     filterableDataForColumn(columnIdentifier)
322     {
323         switch (columnIdentifier) {
324         case "name":
325             return [this.record.displayName, this.record.subtitle];
326
327         case "element":
328         case "source": // Timeline Overview
329             if (this.record.domNode)
330                 return this.record.domNode.displayName;
331             break;
332         }
333
334         return super.filterableDataForColumn(columnIdentifier);
335     }
336
337     // Private
338
339     _createNameCellDocumentFragment()
340     {
341         let fragment = document.createDocumentFragment();
342         fragment.append(this.record.displayName);
343
344         if (this.record.subtitle) {
345             let subtitleElement = fragment.appendChild(document.createElement("span"));
346             subtitleElement.className = "subtitle";
347             subtitleElement.textContent = this.record.subtitle;
348         }
349
350         return fragment;
351     }
352 };