1/*
2 * Copyright (C) 2014 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
26WebInspector.TimelineDataGridNode = function(graphOnly, graphDataSource, hasChildren)
27{
28    WebInspector.DataGridNode.call(this, {}, hasChildren);
29
30    this._graphOnly = graphOnly || false;
31    this._graphDataSource = graphDataSource || null;
32
33    if (graphDataSource) {
34        this._graphContainerElement = document.createElement("div");
35        this._timelineRecordBars = [];
36    }
37};
38
39WebInspector.Object.addConstructorFunctions(WebInspector.TimelineDataGridNode);
40
41WebInspector.TimelineDataGridNode.prototype = {
42    constructor: WebInspector.TimelineDataGridNode,
43    __proto__: WebInspector.DataGridNode.prototype,
44
45    // Public
46
47    get records()
48    {
49        // Implemented by subclasses.
50        return [];
51    },
52
53    get graphDataSource()
54    {
55        return this._graphDataSource;
56    },
57
58    get data()
59    {
60        if (!this._graphDataSource)
61            return {};
62
63        var records = this.records || [];
64        return {graph: records.length ? records[0].startTime : 0};
65    },
66
67    collapse: function()
68    {
69        WebInspector.DataGridNode.prototype.collapse.call(this);
70
71        if (!this._graphDataSource || !this.revealed)
72            return;
73
74        // Refresh to show child bars in our graph now that we collapsed.
75        this.refreshGraph();
76    },
77
78    expand: function()
79    {
80        WebInspector.DataGridNode.prototype.expand.call(this);
81
82        if (!this._graphDataSource || !this.revealed)
83            return;
84
85        // Refresh to remove child bars from our graph now that we expanded.
86        this.refreshGraph();
87
88        // Refresh child graphs since they haven't been updating while we were collapsed.
89        var childNode = this.children[0];
90        while (childNode) {
91            if (childNode instanceof WebInspector.TimelineDataGridNode)
92                childNode.refreshGraph();
93            childNode = childNode.traverseNextNode(true, this);
94        }
95    },
96
97    createCellContent: function(columnIdentifier, cell)
98    {
99        if (columnIdentifier === "graph" && this._graphDataSource) {
100            this.needsGraphRefresh();
101            return this._graphContainerElement;
102        }
103
104        var value = this.data[columnIdentifier];
105        if (!value)
106            return "\u2014";
107
108        if (value instanceof WebInspector.SourceCodeLocation) {
109            if (value.sourceCode instanceof WebInspector.Resource) {
110                cell.classList.add(WebInspector.ResourceTreeElement.ResourceIconStyleClassName);
111                cell.classList.add(value.sourceCode.type);
112            } else if (value.sourceCode instanceof WebInspector.Script) {
113                if (value.sourceCode.url) {
114                    cell.classList.add(WebInspector.ResourceTreeElement.ResourceIconStyleClassName);
115                    cell.classList.add(WebInspector.Resource.Type.Script);
116                } else
117                    cell.classList.add(WebInspector.ScriptTreeElement.AnonymousScriptIconStyleClassName);
118            } else
119                console.error("Unknown SourceCode subclass.");
120
121            // Give the whole cell a tooltip and keep it up to date.
122            value.populateLiveDisplayLocationTooltip(cell);
123
124            var fragment = document.createDocumentFragment();
125
126            var goToArrowButtonLink = WebInspector.createSourceCodeLocationLink(value, false, true);
127            fragment.appendChild(goToArrowButtonLink);
128
129            var icon = document.createElement("div");
130            icon.className = WebInspector.ScriptTimelineDataGridNode.IconStyleClassName;
131            fragment.appendChild(icon);
132
133            var titleElement = document.createElement("span");
134            value.populateLiveDisplayLocationString(titleElement, "textContent");
135            fragment.appendChild(titleElement);
136
137            return fragment;
138        }
139
140        if (value instanceof WebInspector.CallFrame) {
141            var callFrame = value;
142
143            var isAnonymousFunction = false;
144            var functionName = callFrame.functionName;
145            if (!functionName) {
146                functionName = WebInspector.UIString("(anonymous function)");
147                isAnonymousFunction = true;
148            }
149
150            cell.classList.add(WebInspector.CallFrameTreeElement.FunctionIconStyleClassName);
151
152            var fragment = document.createDocumentFragment();
153
154            if (callFrame.sourceCodeLocation && callFrame.sourceCodeLocation.sourceCode) {
155                // Give the whole cell a tooltip and keep it up to date.
156                callFrame.sourceCodeLocation.populateLiveDisplayLocationTooltip(cell);
157
158                var goToArrowButtonLink = WebInspector.createSourceCodeLocationLink(callFrame.sourceCodeLocation, false, true);
159                fragment.appendChild(goToArrowButtonLink);
160
161                var icon = document.createElement("div");
162                icon.className = WebInspector.LayoutTimelineDataGridNode.IconStyleClassName;
163                fragment.appendChild(icon);
164
165                if (isAnonymousFunction) {
166                    // For anonymous functions we show the resource or script icon and name.
167                    if (callFrame.sourceCodeLocation.sourceCode instanceof WebInspector.Resource) {
168                        cell.classList.add(WebInspector.ResourceTreeElement.ResourceIconStyleClassName);
169                        cell.classList.add(callFrame.sourceCodeLocation.sourceCode.type);
170                    } else if (callFrame.sourceCodeLocation.sourceCode instanceof WebInspector.Script) {
171                        if (callFrame.sourceCodeLocation.sourceCode.url) {
172                            cell.classList.add(WebInspector.ResourceTreeElement.ResourceIconStyleClassName);
173                            cell.classList.add(WebInspector.Resource.Type.Script);
174                        } else
175                            cell.classList.add(WebInspector.ScriptTreeElement.AnonymousScriptIconStyleClassName);
176                    } else
177                        console.error("Unknown SourceCode subclass.");
178
179                    var titleElement = document.createElement("span");
180                    callFrame.sourceCodeLocation.populateLiveDisplayLocationString(titleElement, "textContent");
181
182                    fragment.appendChild(titleElement);
183                } else {
184                    // Show the function name and icon.
185                    cell.classList.add(WebInspector.CallFrameTreeElement.FunctionIconStyleClassName);
186
187                    fragment.appendChild(document.createTextNode(functionName));
188
189                    var subtitleElement = document.createElement("span");
190                    subtitleElement.className = WebInspector.LayoutTimelineDataGridNode.SubtitleStyleClassName;
191                    callFrame.sourceCodeLocation.populateLiveDisplayLocationString(subtitleElement, "textContent");
192
193                    fragment.appendChild(subtitleElement);
194                }
195
196                return fragment;
197            }
198
199            var icon = document.createElement("div");
200            icon.className = WebInspector.LayoutTimelineDataGridNode.IconStyleClassName;
201            fragment.appendChild(icon);
202
203            fragment.appendChild(document.createTextNode(functionName));
204
205            return fragment;
206        }
207
208        return WebInspector.DataGridNode.prototype.createCellContent.call(this, columnIdentifier, cell);
209    },
210
211    refresh: function()
212    {
213        if (this._graphDataSource && this._graphOnly) {
214            this.needsGraphRefresh();
215            return;
216        }
217
218        WebInspector.DataGridNode.prototype.refresh.call(this);
219    },
220
221    refreshGraph: function()
222    {
223        if (!this._graphDataSource)
224            return;
225
226        if (this._scheduledGraphRefreshIdentifier) {
227            cancelAnimationFrame(this._scheduledGraphRefreshIdentifier);
228            delete this._scheduledGraphRefreshIdentifier;
229        }
230
231        // We are not visible, but an ancestor will draw our graph.
232        // They need notified by using our needsGraphRefresh.
233        console.assert(this.revealed);
234        if (!this.revealed)
235            return;
236
237        var secondsPerPixel = this._graphDataSource.secondsPerPixel;
238        console.assert(isFinite(secondsPerPixel) && secondsPerPixel > 0);
239
240        var recordBarIndex = 0;
241
242        function createBar(records, renderMode)
243        {
244            var timelineRecordBar = this._timelineRecordBars[recordBarIndex];
245            if (!timelineRecordBar)
246                timelineRecordBar = this._timelineRecordBars[recordBarIndex] = new WebInspector.TimelineRecordBar(records, renderMode);
247            else {
248                timelineRecordBar.renderMode = renderMode;
249                timelineRecordBar.records = records;
250            }
251            timelineRecordBar.refresh(this._graphDataSource);
252            if (!timelineRecordBar.element.parentNode)
253                this._graphContainerElement.appendChild(timelineRecordBar.element);
254            ++recordBarIndex;
255        }
256
257        var boundCreateBar = createBar.bind(this);
258
259        if (this.expanded) {
260            // When expanded just use the records for this node.
261            WebInspector.TimelineRecordBar.createCombinedBars(this.records, secondsPerPixel, this._graphDataSource, boundCreateBar);
262        } else {
263            // When collapsed use the records for this node and its descendants.
264            // To share bars better, group records by type.
265
266            var recordTypeMap = new Map;
267
268            function collectRecordsByType(records)
269            {
270                for (var record of records) {
271                    var typedRecords = recordTypeMap.get(record.type);
272                    if (!typedRecords) {
273                        typedRecords = [];
274                        recordTypeMap.set(record.type, typedRecords);
275                    }
276
277                    typedRecords.push(record);
278                }
279            }
280
281            collectRecordsByType(this.records);
282
283            var childNode = this.children[0];
284            while (childNode) {
285                if (childNode instanceof WebInspector.TimelineDataGridNode)
286                    collectRecordsByType(childNode.records);
287                childNode = childNode.traverseNextNode(false, this);
288            }
289
290            for (var records of recordTypeMap.values())
291                WebInspector.TimelineRecordBar.createCombinedBars(records, secondsPerPixel, this._graphDataSource, boundCreateBar);
292        }
293
294        // Remove the remaining unused TimelineRecordBars.
295        for (; recordBarIndex < this._timelineRecordBars.length; ++recordBarIndex) {
296            this._timelineRecordBars[recordBarIndex].records = null;
297            this._timelineRecordBars[recordBarIndex].element.remove();
298        }
299    },
300
301    needsGraphRefresh: function()
302    {
303        if (!this.revealed) {
304            // We are not visible, but an ancestor will be drawing our graph.
305            // Notify the next visible ancestor that their graph needs to refresh.
306            var ancestor = this;
307            while (ancestor && !ancestor.root) {
308                if (ancestor.revealed && ancestor instanceof WebInspector.TimelineDataGridNode) {
309                    ancestor.needsGraphRefresh();
310                    return;
311                }
312
313                ancestor = ancestor.parent;
314            }
315
316            return;
317        }
318
319        if (!this._graphDataSource || this._scheduledGraphRefreshIdentifier)
320            return;
321
322        this._scheduledGraphRefreshIdentifier = requestAnimationFrame(this.refreshGraph.bind(this));
323    },
324
325    // Protected
326
327    isRecordVisible: function(record)
328    {
329        if (!this._graphDataSource)
330            return false;
331
332        if (isNaN(record.startTime))
333            return false;
334
335        // If this bar is completely before the bounds of the graph, not visible.
336        if (record.endTime < this.graphDataSource.startTime)
337            return false;
338
339        // If this record is completely after the current time or end time, not visible.
340        if (record.startTime > this.graphDataSource.currentTime || record.startTime > this.graphDataSource.endTime)
341            return false;
342
343        return true;
344    }
345};
346