Web Inspector: Support passing extra arguments to ContentViews during construction
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / TimelineView.js
1 /*
2  * Copyright (C) 2013, 2015 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, extraArguments)
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     console.assert(extraArguments);
33     console.assert(extraArguments.timelineSidebarPanel instanceof WebInspector.TimelineSidebarPanel);
34
35     WebInspector.ContentView.call(this, representedObject);
36
37     this._timelineSidebarPanel = extraArguments.timelineSidebarPanel;
38
39     this._contentTreeOutline = this._timelineSidebarPanel.createContentTreeOutline();
40     this._contentTreeOutline.onselect = this.treeElementSelected.bind(this);
41     this._contentTreeOutline.ondeselect = this.treeElementDeselected.bind(this);
42
43     this.element.classList.add("timeline-view");
44
45     this._zeroTime = 0;
46     this._startTime = 0;
47     this._endTime = 5;
48     this._currentTime = 0;
49 };
50
51 WebInspector.TimelineView.prototype = {
52     constructor: WebInspector.TimelineView,
53     __proto__: WebInspector.ContentView.prototype,
54
55     // Public
56
57     get navigationSidebarTreeOutline()
58     {
59         return this._contentTreeOutline;
60     },
61
62     get navigationSidebarTreeOutlineLabel()
63     {
64         // Implemented by sub-classes if needed.
65         return null;
66     },
67
68     get timelineSidebarPanel()
69     {
70         return this._timelineSidebarPanel;
71     },
72
73     get selectionPathComponents()
74     {
75         if (!this._contentTreeOutline.selectedTreeElement || this._contentTreeOutline.selectedTreeElement.hidden)
76             return null;
77
78         var pathComponent = new WebInspector.GeneralTreeElementPathComponent(this._contentTreeOutline.selectedTreeElement);
79         pathComponent.addEventListener(WebInspector.HierarchicalPathComponent.Event.SiblingWasSelected, this.treeElementPathComponentSelected, this);
80         return [pathComponent];
81     },
82
83     get zeroTime()
84     {
85         return this._zeroTime;
86     },
87
88     set zeroTime(x)
89     {
90         if (this._zeroTime === x)
91             return;
92
93         this._zeroTime = x || 0;
94
95         this.needsLayout();
96     },
97
98     get startTime()
99     {
100         return this._startTime;
101     },
102
103     set startTime(x)
104     {
105         if (this._startTime === x)
106             return;
107
108         this._startTime = x || 0;
109
110         this.needsLayout();
111     },
112
113     get endTime()
114     {
115         return this._endTime;
116     },
117
118     set endTime(x)
119     {
120         if (this._endTime === x)
121             return;
122
123         this._endTime = x || 0;
124
125         this.needsLayout();
126     },
127
128     get currentTime()
129     {
130         return this._currentTime;
131     },
132
133     set currentTime(x)
134     {
135         if (this._currentTime === x)
136             return;
137
138         var oldCurrentTime = this._currentTime;
139
140         this._currentTime = x || 0;
141
142         function checkIfLayoutIsNeeded(currentTime)
143         {
144             // Include some wiggle room since the current time markers can be clipped off the ends a bit and still partially visible.
145             const wiggleTime = 0.05; // 50ms
146             return this._startTime - wiggleTime <= currentTime && currentTime <= this._endTime + wiggleTime;
147         }
148
149         if (checkIfLayoutIsNeeded.call(this, oldCurrentTime) || checkIfLayoutIsNeeded.call(this, this._currentTime))
150             this.needsLayout();
151     },
152
153     reset: function()
154     {
155         this._contentTreeOutline.removeChildren();
156     },
157
158
159     filterDidChange: function()
160     {
161         // Implemented by sub-classes if needed.
162     },
163
164     matchTreeElementAgainstCustomFilters: function(treeElement)
165     {
166         // Implemented by sub-classes if needed.
167         return true;
168     },
169
170     updateLayout: function()
171     {
172         if (this._scheduledLayoutUpdateIdentifier) {
173             cancelAnimationFrame(this._scheduledLayoutUpdateIdentifier);
174             delete this._scheduledLayoutUpdateIdentifier;
175         }
176
177         // Implemented by sub-classes if needed.
178     },
179
180     updateLayoutIfNeeded: function()
181     {
182         if (!this._scheduledLayoutUpdateIdentifier)
183             return;
184         this.updateLayout();
185     },
186
187     filterUpdated: function()
188     {
189         this.dispatchEventToListeners(WebInspector.ContentView.Event.SelectionPathComponentsDidChange);
190     },
191
192     // Protected
193
194     showContentViewForTreeElement: function(treeElement)
195     {
196         // Implemented by sub-classes if needed.
197
198         if (!(treeElement instanceof WebInspector.TimelineRecordTreeElement)) {
199             console.error("Unknown tree element selected.", treeElement);
200             return false;
201         }
202
203         var sourceCodeLocation = treeElement.record.sourceCodeLocation;
204         if (!sourceCodeLocation) {
205             this._timelineSidebarPanel.showTimelineViewForTimeline(this.representedObject);
206             return true;
207         }
208
209         WebInspector.resourceSidebarPanel.showOriginalOrFormattedSourceCodeLocation(sourceCodeLocation);
210         return true;
211     },
212
213     treeElementPathComponentSelected: function(event)
214     {
215         // Implemented by sub-classes if needed.
216     },
217
218     treeElementDeselected: function(treeElement)
219     {
220         // Implemented by sub-classes if needed.
221
222         if (this._closeStatusButton && treeElement.status === this._closeStatusButton.element)
223             treeElement.status = "";
224     },
225
226     treeElementSelected: function(treeElement, selectedByUser)
227     {
228         // Implemented by sub-classes if needed.
229
230         if (!this._timelineSidebarPanel.canShowDifferentContentView())
231             return;
232
233         if (treeElement instanceof WebInspector.FolderTreeElement)
234             return;
235
236         if (!this.showContentViewForTreeElement(treeElement))
237             return;
238
239         this._updateTreeElementWithCloseButton(treeElement);
240     },
241
242     needsLayout: function()
243     {
244         if (!this.visible)
245             return;
246
247         if (this._scheduledLayoutUpdateIdentifier)
248             return;
249
250         this._scheduledLayoutUpdateIdentifier = requestAnimationFrame(this.updateLayout.bind(this));
251     },
252
253     // Private
254
255     _closeStatusButtonClicked: function(event)
256     {
257         if (this.navigationSidebarTreeOutline.selectedTreeElement)
258             this.navigationSidebarTreeOutline.selectedTreeElement.deselect();
259
260         this._timelineSidebarPanel.showTimelineViewForTimeline(this.representedObject);
261     },
262
263     _updateTreeElementWithCloseButton: function(treeElement)
264     {
265         if (this._closeStatusButton) {
266             treeElement.status = this._closeStatusButton.element;
267             return;
268         }
269
270         wrappedSVGDocument(platformImagePath("Close.svg"), null, WebInspector.UIString("Close resource view"), function(element) {
271             this._closeStatusButton = new WebInspector.TreeElementStatusButton(element);
272             this._closeStatusButton.addEventListener(WebInspector.TreeElementStatusButton.Event.Clicked, this._closeStatusButtonClicked, this);
273             if (treeElement === this.navigationSidebarTreeOutline.selectedTreeElement)
274                 this._updateTreeElementWithCloseButton(treeElement);
275         }.bind(this));
276     }
277 };