4cca0e4593df4a538bf8f7a70d98c901c6967ee8
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / TimelineView.js
1 /*
2  * Copyright (C) 2013 Apple Inc. All rights reserved.
3  * Copyright (C) 2015 University of Washington.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
15  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
16  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
18  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
24  * THE POSSIBILITY OF SUCH DAMAGE.
25  */
26
27 WebInspector.TimelineView = function(representedObject)
28 {
29     // This class should not be instantiated directly. Create a concrete subclass instead.
30     console.assert(this.constructor !== WebInspector.TimelineView && this instanceof WebInspector.TimelineView);
31
32     WebInspector.ContentView.call(this, representedObject);
33
34     this._contentTreeOutline = WebInspector.timelineSidebarPanel.createContentTreeOutline();
35     this._contentTreeOutline.onselect = this.treeElementSelected.bind(this);
36     this._contentTreeOutline.ondeselect = this.treeElementDeselected.bind(this);
37
38     this.element.classList.add(WebInspector.TimelineView.StyleClassName);
39
40     this._zeroTime = 0;
41     this._startTime = 0;
42     this._endTime = 5;
43     this._currentTime = 0;
44 };
45
46 WebInspector.TimelineView.StyleClassName = "timeline-view";
47
48 WebInspector.TimelineView.prototype = {
49     constructor: WebInspector.TimelineView,
50     __proto__: WebInspector.ContentView.prototype,
51
52     // Public
53
54     get navigationSidebarTreeOutline()
55     {
56         return this._contentTreeOutline;
57     },
58
59     get navigationSidebarTreeOutlineLabel()
60     {
61         // Implemented by sub-classes if needed.
62         return null;
63     },
64
65     get selectionPathComponents()
66     {
67         if (!this._contentTreeOutline.selectedTreeElement || this._contentTreeOutline.selectedTreeElement.hidden)
68             return null;
69
70         var pathComponent = new WebInspector.GeneralTreeElementPathComponent(this._contentTreeOutline.selectedTreeElement);
71         pathComponent.addEventListener(WebInspector.HierarchicalPathComponent.Event.SiblingWasSelected, this.treeElementPathComponentSelected, this);
72         return [pathComponent];
73     },
74
75     get zeroTime()
76     {
77         return this._zeroTime;
78     },
79
80     set zeroTime(x)
81     {
82         if (this._zeroTime === x)
83             return;
84
85         this._zeroTime = x || 0;
86
87         this.needsLayout();
88     },
89
90     get startTime()
91     {
92         return this._startTime;
93     },
94
95     set startTime(x)
96     {
97         if (this._startTime === x)
98             return;
99
100         this._startTime = x || 0;
101
102         this.needsLayout();
103     },
104
105     get endTime()
106     {
107         return this._endTime;
108     },
109
110     set endTime(x)
111     {
112         if (this._endTime === x)
113             return;
114
115         this._endTime = x || 0;
116
117         this.needsLayout();
118     },
119
120     get currentTime()
121     {
122         return this._currentTime;
123     },
124
125     set currentTime(x)
126     {
127         if (this._currentTime === x)
128             return;
129
130         var oldCurrentTime = this._currentTime;
131
132         this._currentTime = x || 0;
133
134         function checkIfLayoutIsNeeded(currentTime)
135         {
136             // Include some wiggle room since the current time markers can be clipped off the ends a bit and still partially visible.
137             const wiggleTime = 0.05; // 50ms
138             return this._startTime - wiggleTime <= currentTime && currentTime <= this._endTime + wiggleTime;
139         }
140
141         if (checkIfLayoutIsNeeded.call(this, oldCurrentTime) || checkIfLayoutIsNeeded.call(this, this._currentTime))
142             this.needsLayout();
143     },
144
145     reset: function()
146     {
147         this._contentTreeOutline.removeChildren();
148     },
149
150
151     filterDidChange: function()
152     {
153         // Implemented by sub-classes if needed.
154     },
155
156     matchTreeElementAgainstCustomFilters: function(treeElement)
157     {
158         // Implemented by sub-classes if needed.
159         return true;
160     },
161
162     updateLayout: function()
163     {
164         if (this._scheduledLayoutUpdateIdentifier) {
165             cancelAnimationFrame(this._scheduledLayoutUpdateIdentifier);
166             delete this._scheduledLayoutUpdateIdentifier;
167         }
168
169         // Implemented by sub-classes if needed.
170     },
171
172     updateLayoutIfNeeded: function()
173     {
174         if (!this._scheduledLayoutUpdateIdentifier)
175             return;
176         this.updateLayout();
177     },
178
179     filterUpdated: function()
180     {
181         this.dispatchEventToListeners(WebInspector.ContentView.Event.SelectionPathComponentsDidChange);
182     },
183
184     // Protected
185
186     showContentViewForTreeElement: function(treeElement)
187     {
188         // Implemented by sub-classes if needed.
189
190         if (!(treeElement instanceof WebInspector.TimelineRecordTreeElement)) {
191             console.error("Unknown tree element selected.", treeElement);
192             return false;
193         }
194
195         var sourceCodeLocation = treeElement.record.sourceCodeLocation;
196         if (!sourceCodeLocation) {
197             WebInspector.timelineSidebarPanel.showTimelineViewForTimeline(this.representedObject);
198             return true;
199         }
200
201         WebInspector.resourceSidebarPanel.showOriginalOrFormattedSourceCodeLocation(sourceCodeLocation);
202         return true;
203     },
204
205     treeElementPathComponentSelected: function(event)
206     {
207         // Implemented by sub-classes if needed.
208     },
209
210     treeElementDeselected: function(treeElement)
211     {
212         // Implemented by sub-classes if needed.
213
214         if (this._closeStatusButton && treeElement.status === this._closeStatusButton.element)
215             treeElement.status = "";
216     },
217
218     treeElementSelected: function(treeElement, selectedByUser)
219     {
220         // Implemented by sub-classes if needed.
221
222         if (!WebInspector.timelineSidebarPanel.canShowDifferentContentView())
223             return;
224
225         if (treeElement instanceof WebInspector.FolderTreeElement)
226             return;
227
228         if (!this.showContentViewForTreeElement(treeElement))
229             return;
230
231         this._updateTreeElementWithCloseButton(treeElement);
232     },
233
234     needsLayout: function()
235     {
236         if (!this.visible)
237             return;
238
239         if (this._scheduledLayoutUpdateIdentifier)
240             return;
241
242         this._scheduledLayoutUpdateIdentifier = requestAnimationFrame(this.updateLayout.bind(this));
243     },
244
245     // Private
246
247     _closeStatusButtonClicked: function(event)
248     {
249         if (this.navigationSidebarTreeOutline.selectedTreeElement)
250             this.navigationSidebarTreeOutline.selectedTreeElement.deselect();
251
252         WebInspector.timelineSidebarPanel.showTimelineViewForTimeline(this.representedObject);
253     },
254
255     _updateTreeElementWithCloseButton: function(treeElement)
256     {
257         if (this._closeStatusButton) {
258             treeElement.status = this._closeStatusButton.element;
259             return;
260         }
261
262         wrappedSVGDocument(platformImagePath("Close.svg"), null, WebInspector.UIString("Close resource view"), function(element) {
263             this._closeStatusButton = new WebInspector.TreeElementStatusButton(element);
264             this._closeStatusButton.addEventListener(WebInspector.TreeElementStatusButton.Event.Clicked, this._closeStatusButtonClicked, this);
265             if (treeElement === this.navigationSidebarTreeOutline.selectedTreeElement)
266                 this._updateTreeElementWithCloseButton(treeElement);
267         }.bind(this));
268     }
269 };