Fixes a bug where the Inspector could have 0ms timers firing
[WebKit-https.git] / WebCore / page / inspector / ProfilesPanel.js
1 /*
2  * Copyright (C) 2008 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. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 WebInspector.ProfilesPanel = function()
27 {
28     WebInspector.Panel.call(this);
29
30     this.element.addStyleClass("profiles");
31
32     this.sidebarElement = document.createElement("div");
33     this.sidebarElement.id = "profiles-sidebar";
34     this.sidebarElement.className = "sidebar";
35     this.element.appendChild(this.sidebarElement);
36
37     this.sidebarResizeElement = document.createElement("div");
38     this.sidebarResizeElement.className = "sidebar-resizer-vertical";
39     this.sidebarResizeElement.addEventListener("mousedown", this._startSidebarDragging.bind(this), false);
40     this.element.appendChild(this.sidebarResizeElement);
41
42     this.sidebarTreeElement = document.createElement("ol");
43     this.sidebarTreeElement.className = "sidebar-tree";
44     this.sidebarElement.appendChild(this.sidebarTreeElement);
45
46     this.sidebarTree = new TreeOutline(this.sidebarTreeElement);
47
48     this.profileViews = document.createElement("div");
49     this.profileViews.id = "profile-views";
50     this.element.appendChild(this.profileViews);
51
52     this.recordButton = document.createElement("button");
53     this.recordButton.title = WebInspector.UIString("Start profiling.");
54     this.recordButton.id = "record-profile-status-bar-item";
55     this.recordButton.className = "status-bar-item";
56     this.recordButton.addEventListener("click", this._recordClicked.bind(this), false);
57
58     this.recording = false;
59
60     this.profileViewStatusBarItemsContainer = document.createElement("div");
61     this.profileViewStatusBarItemsContainer.id = "profile-view-status-bar-items";
62
63     this.reset();
64 }
65
66 WebInspector.ProfilesPanel.prototype = {
67     toolbarItemClass: "profiles",
68
69     get toolbarItemLabel()
70     {
71         return WebInspector.UIString("Profiles");
72     },
73
74     get statusBarItems()
75     {
76         return [this.recordButton, this.profileViewStatusBarItemsContainer];
77     },
78
79     show: function()
80     {
81         WebInspector.Panel.prototype.show.call(this);
82         this._updateSidebarWidth();
83         if (this._shouldPopulateProfiles)
84             this._populateProfiles();
85     },
86
87     populateInterface: function()
88     {
89         if (this.visible)
90             this._populateProfiles();
91         else
92             this._shouldPopulateProfiles = true;
93     },
94
95     reset: function()
96     {
97         this.nextUserInitiatedProfileNumber = 1;
98
99         if (this._profiles) {
100             var profiledLength = this._profiles.length;
101             for (var i = 0; i < profiledLength; ++i) {
102                 var profile = this._profiles[i];
103                 delete profile._profileView;
104             }
105         }
106
107         this._profiles = [];
108
109         this.sidebarTree.removeChildren();
110         this.profileViews.removeChildren();
111
112         this._shouldPopulateProfiles = true;
113     },
114
115     handleKeyEvent: function(event)
116     {
117         this.sidebarTree.handleKeyEvent(event);
118     },
119
120     addProfile: function(profile)
121     {
122         this._profiles.push(profile);
123
124         var profileTreeElement = new WebInspector.ProfileSidebarTreeElement(profile);
125         profile._profilesTreeElement = profileTreeElement;
126
127         this.sidebarTree.appendChild(profileTreeElement);
128     },
129
130     showProfile: function(profile)
131     {
132         if (!profile)
133             return;
134
135         if (this.visibleProfileView)
136             this.visibleProfileView.hide();
137
138         var view = profile._profileView;
139         if (!view) {
140             view = new WebInspector.ProfileView(profile);
141             profile._profileView = view;
142         }
143
144         view.show(this.profileViews);
145
146         this.visibleProfileView = view;
147
148         this.profileViewStatusBarItemsContainer.removeChildren();
149
150         var statusBarItems = view.statusBarItems;
151         for (var i = 0; i < statusBarItems.length; ++i)
152             this.profileViewStatusBarItemsContainer.appendChild(statusBarItems[i]);
153     },
154
155     closeVisibleView: function()
156     {
157         if (this.visibleProfileView)
158             this.visibleProfileView.hide();
159         delete this.visibleProfileView;
160     },
161
162     _recordClicked: function()
163     {
164         this.recording = !this.recording;
165
166         if (this.recording) {
167             this.recordButton.addStyleClass("toggled-on");
168             this.recordButton.title = WebInspector.UIString("Stop profiling.");
169             InspectorController.inspectedWindow().console.profile("org.webkit.profiles.user-initiated");
170         } else {
171             this.recordButton.removeStyleClass("toggled-on");
172             this.recordButton.title = WebInspector.UIString("Start profiling.");
173             InspectorController.inspectedWindow().console.profileEnd("org.webkit.profiles.user-initiated");
174         }
175     },
176
177     _populateProfiles: function()
178     {
179         this.sidebarTree.removeChildren();
180
181         var profiles = InspectorController.profiles();
182         var profilesLength = profiles.length;
183         for (var i = 0; i < profilesLength; ++i) {
184             var profile = profiles[i];
185             this.addProfile(profile);
186         }
187
188         if (this.sidebarTree.children[0])
189             this.sidebarTree.children[0].select();
190
191         delete this._shouldPopulateProfiles;
192     },
193
194     _startSidebarDragging: function(event)
195     {
196         WebInspector.elementDragStart(this.sidebarResizeElement, this._sidebarDragging.bind(this), this._endSidebarDragging.bind(this), event, "col-resize");
197     },
198
199     _sidebarDragging: function(event)
200     {
201         this._updateSidebarWidth(event.pageX);
202
203         event.preventDefault();
204     },
205
206     _endSidebarDragging: function(event)
207     {
208         WebInspector.elementDragEnd(event);
209     },
210
211     _updateSidebarWidth: function(width)
212     {
213         if (this.sidebarElement.offsetWidth <= 0) {
214             // The stylesheet hasn't loaded yet or the window is closed,
215             // so we can't calculate what is need. Return early.
216             return;
217         }
218
219         if (!("_currentSidebarWidth" in this))
220             this._currentSidebarWidth = this.sidebarElement.offsetWidth;
221
222         if (typeof width === "undefined")
223             width = this._currentSidebarWidth;
224
225         width = Number.constrain(width, Preferences.minSidebarWidth, window.innerWidth / 2);
226
227         this._currentSidebarWidth = width;
228
229         this.sidebarElement.style.width = width + "px";
230         this.profileViews.style.left = width + "px";
231         this.profileViewStatusBarItemsContainer.style.left = width + "px";
232         this.sidebarResizeElement.style.left = (width - 3) + "px";
233     }
234 }
235
236 WebInspector.ProfilesPanel.prototype.__proto__ = WebInspector.Panel.prototype;
237
238 WebInspector.ProfileSidebarTreeElement = function(profile)
239 {
240     this.profile = profile;
241
242     if (this.profile.title === "org.webkit.profiles.user-initiated")
243         this._profileNumber = WebInspector.panels.profiles.nextUserInitiatedProfileNumber++;
244
245     WebInspector.SidebarTreeElement.call(this, "profile-sidebar-tree-item", "", "", profile, false);
246
247     this.refreshTitles();
248 }
249
250 WebInspector.ProfileSidebarTreeElement.prototype = {
251     onselect: function()
252     {
253         WebInspector.panels.profiles.showProfile(this.profile);
254     },
255
256     get mainTitle()
257     {
258         if (this.profile.title === "org.webkit.profiles.user-initiated")
259             return WebInspector.UIString("Profile %d", this._profileNumber);
260         return this.profile.title;
261     },
262
263     set mainTitle(x)
264     {
265         // Can't change mainTitle.
266     },
267
268     get subtitle()
269     {
270         // There is no subtitle.
271     },
272
273     set subtitle(x)
274     {
275         // Can't change subtitle.
276     }
277 }
278
279 WebInspector.ProfileSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;