• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
26const UserInitiatedProfileName = "org.webkit.profiles.user-initiated";
27
28WebInspector.ProfilesPanel = function()
29{
30    WebInspector.Panel.call(this);
31
32    this.element.addStyleClass("profiles");
33
34    var panelEnablerHeading = WebInspector.UIString("You need to enable profiling before you can use the Profiles panel.");
35    var panelEnablerDisclaimer = WebInspector.UIString("Enabling profiling will make scripts run slower.");
36    var panelEnablerButton = WebInspector.UIString("Enable Profiling");
37    this.panelEnablerView = new WebInspector.PanelEnablerView("profiles", panelEnablerHeading, panelEnablerDisclaimer, panelEnablerButton);
38    this.panelEnablerView.addEventListener("enable clicked", this._enableProfiling, this);
39
40    this.element.appendChild(this.panelEnablerView.element);
41
42    this.sidebarElement = document.createElement("div");
43    this.sidebarElement.id = "profiles-sidebar";
44    this.sidebarElement.className = "sidebar";
45    this.element.appendChild(this.sidebarElement);
46
47    this.sidebarResizeElement = document.createElement("div");
48    this.sidebarResizeElement.className = "sidebar-resizer-vertical";
49    this.sidebarResizeElement.addEventListener("mousedown", this._startSidebarDragging.bind(this), false);
50    this.element.appendChild(this.sidebarResizeElement);
51
52    this.sidebarTreeElement = document.createElement("ol");
53    this.sidebarTreeElement.className = "sidebar-tree";
54    this.sidebarElement.appendChild(this.sidebarTreeElement);
55
56    this.sidebarTree = new TreeOutline(this.sidebarTreeElement);
57
58    this.profileViews = document.createElement("div");
59    this.profileViews.id = "profile-views";
60    this.element.appendChild(this.profileViews);
61
62    this.enableToggleButton = this.createStatusBarButton();
63    this.enableToggleButton.className = "enable-toggle-status-bar-item status-bar-item";
64    this.enableToggleButton.addEventListener("click", this._toggleProfiling.bind(this), false);
65
66    this.recordButton = this.createStatusBarButton();
67    this.recordButton.title = WebInspector.UIString("Start profiling.");
68    this.recordButton.id = "record-profile-status-bar-item";
69    this.recordButton.className = "status-bar-item";
70    this.recordButton.addEventListener("click", this._recordClicked.bind(this), false);
71
72    this.recording = false;
73
74    this.profileViewStatusBarItemsContainer = document.createElement("div");
75    this.profileViewStatusBarItemsContainer.id = "profile-view-status-bar-items";
76
77    this.reset();
78}
79
80WebInspector.ProfilesPanel.prototype = {
81    toolbarItemClass: "profiles",
82
83    get toolbarItemLabel()
84    {
85        return WebInspector.UIString("Profiles");
86    },
87
88    get statusBarItems()
89    {
90        return [this.enableToggleButton, this.recordButton, this.profileViewStatusBarItemsContainer];
91    },
92
93    show: function()
94    {
95        WebInspector.Panel.prototype.show.call(this);
96        this._updateSidebarWidth();
97        if (this._shouldPopulateProfiles)
98            this._populateProfiles();
99    },
100
101    populateInterface: function()
102    {
103        if (this.visible)
104            this._populateProfiles();
105        else
106            this._shouldPopulateProfiles = true;
107    },
108
109    profilerWasEnabled: function()
110    {
111        this.reset();
112        this.populateInterface();
113    },
114
115    profilerWasDisabled: function()
116    {
117        this.reset();
118    },
119
120    reset: function()
121    {
122        if (this._profiles) {
123            var profiledLength = this._profiles.length;
124            for (var i = 0; i < profiledLength; ++i) {
125                var profile = this._profiles[i];
126                delete profile._profileView;
127            }
128        }
129
130        delete this.currentQuery;
131        this.searchCanceled();
132
133        this._profiles = [];
134        this._profilesIdMap = {};
135        this._profileGroups = {};
136        this._profileGroupsForLinks = {}
137
138        this.sidebarTreeElement.removeStyleClass("some-expandable");
139
140        this.sidebarTree.removeChildren();
141        this.profileViews.removeChildren();
142
143        this.profileViewStatusBarItemsContainer.removeChildren();
144
145        this._updateInterface();
146    },
147
148    handleKeyEvent: function(event)
149    {
150        this.sidebarTree.handleKeyEvent(event);
151    },
152
153    addProfile: function(profile)
154    {
155        this._profiles.push(profile);
156        this._profilesIdMap[profile.uid] = profile;
157
158        var sidebarParent = this.sidebarTree;
159        var small = false;
160        var alternateTitle;
161
162        if (profile.title.indexOf(UserInitiatedProfileName) !== 0) {
163            if (!(profile.title in this._profileGroups))
164                this._profileGroups[profile.title] = [];
165
166            var group = this._profileGroups[profile.title];
167            group.push(profile);
168
169            if (group.length === 2) {
170                // Make a group TreeElement now that there are 2 profiles.
171                group._profilesTreeElement = new WebInspector.ProfileGroupSidebarTreeElement(profile.title);
172
173                // Insert at the same index for the first profile of the group.
174                var index = this.sidebarTree.children.indexOf(group[0]._profilesTreeElement);
175                this.sidebarTree.insertChild(group._profilesTreeElement, index);
176
177                // Move the first profile to the group.
178                var selected = group[0]._profilesTreeElement.selected;
179                this.sidebarTree.removeChild(group[0]._profilesTreeElement);
180                group._profilesTreeElement.appendChild(group[0]._profilesTreeElement);
181                if (selected) {
182                    group[0]._profilesTreeElement.select();
183                    group[0]._profilesTreeElement.reveal();
184                }
185
186                group[0]._profilesTreeElement.small = true;
187                group[0]._profilesTreeElement.mainTitle = WebInspector.UIString("Run %d", 1);
188
189                this.sidebarTreeElement.addStyleClass("some-expandable");
190            }
191
192            if (group.length >= 2) {
193                sidebarParent = group._profilesTreeElement;
194                alternateTitle = WebInspector.UIString("Run %d", group.length);
195                small = true;
196            }
197        }
198
199        var profileTreeElement = new WebInspector.ProfileSidebarTreeElement(profile);
200        profileTreeElement.small = small;
201        if (alternateTitle)
202            profileTreeElement.mainTitle = alternateTitle;
203        profile._profilesTreeElement = profileTreeElement;
204
205        sidebarParent.appendChild(profileTreeElement);
206    },
207
208    showProfile: function(profile)
209    {
210        if (!profile)
211            return;
212
213        if (this.visibleView)
214            this.visibleView.hide();
215
216        var view = this.profileViewForProfile(profile);
217
218        view.show(this.profileViews);
219
220        profile._profilesTreeElement.select(true);
221        profile._profilesTreeElement.reveal();
222
223        this.visibleView = view;
224
225        this.profileViewStatusBarItemsContainer.removeChildren();
226
227        var statusBarItems = view.statusBarItems;
228        for (var i = 0; i < statusBarItems.length; ++i)
229            this.profileViewStatusBarItemsContainer.appendChild(statusBarItems[i]);
230    },
231
232    showView: function(view)
233    {
234        this.showProfile(view.profile);
235    },
236
237    profileViewForProfile: function(profile)
238    {
239        if (!profile)
240            return null;
241        if (!profile._profileView)
242            profile._profileView = new WebInspector.ProfileView(profile);
243        return profile._profileView;
244    },
245
246    showProfileById: function(uid)
247    {
248        this.showProfile(this._profilesIdMap[uid]);
249    },
250
251    closeVisibleView: function()
252    {
253        if (this.visibleView)
254            this.visibleView.hide();
255        delete this.visibleView;
256    },
257
258    displayTitleForProfileLink: function(title)
259    {
260        title = unescape(title);
261        if (title.indexOf(UserInitiatedProfileName) === 0) {
262            title = WebInspector.UIString("Profile %d", title.substring(UserInitiatedProfileName.length + 1));
263        } else {
264            if (!(title in this._profileGroupsForLinks))
265                this._profileGroupsForLinks[title] = 0;
266
267            groupNumber = ++this._profileGroupsForLinks[title];
268
269            if (groupNumber > 2)
270                // The title is used in the console message announcing that a profile has started so it gets
271                // incremented twice as often as it's displayed
272                title += " " + WebInspector.UIString("Run %d", groupNumber / 2);
273        }
274
275        return title;
276    },
277
278    get searchableViews()
279    {
280        var views = [];
281
282        const visibleView = this.visibleView;
283        if (visibleView && visibleView.performSearch)
284            views.push(visibleView);
285
286        var profilesLength = this._profiles.length;
287        for (var i = 0; i < profilesLength; ++i) {
288            var view = this.profileViewForProfile(this._profiles[i]);
289            if (!view.performSearch || view === visibleView)
290                continue;
291            views.push(view);
292        }
293
294        return views;
295    },
296
297    searchMatchFound: function(view, matches)
298    {
299        view.profile._profilesTreeElement.searchMatches = matches;
300    },
301
302    searchCanceled: function(startingNewSearch)
303    {
304        WebInspector.Panel.prototype.searchCanceled.call(this, startingNewSearch);
305
306        if (!this._profiles)
307            return;
308
309        for (var i = 0; i < this._profiles.length; ++i) {
310            var profile = this._profiles[i];
311            profile._profilesTreeElement.searchMatches = 0;
312        }
313    },
314
315    setRecordingProfile: function(isProfiling)
316    {
317        this.recording = isProfiling;
318
319        if (isProfiling) {
320            this.recordButton.addStyleClass("toggled-on");
321            this.recordButton.title = WebInspector.UIString("Stop profiling.");
322        } else {
323            this.recordButton.removeStyleClass("toggled-on");
324            this.recordButton.title = WebInspector.UIString("Start profiling.");
325        }
326    },
327
328    resize: function()
329    {
330        var visibleView = this.visibleView;
331        if (visibleView && "resize" in visibleView)
332            visibleView.resize();
333    },
334
335    _updateInterface: function()
336    {
337        if (InspectorController.profilerEnabled()) {
338            this.enableToggleButton.title = WebInspector.UIString("Profiling enabled. Click to disable.");
339            this.enableToggleButton.addStyleClass("toggled-on");
340            this.recordButton.removeStyleClass("hidden");
341            this.profileViewStatusBarItemsContainer.removeStyleClass("hidden");
342            this.panelEnablerView.visible = false;
343        } else {
344            this.enableToggleButton.title = WebInspector.UIString("Profiling disabled. Click to enable.");
345            this.enableToggleButton.removeStyleClass("toggled-on");
346            this.recordButton.addStyleClass("hidden");
347            this.profileViewStatusBarItemsContainer.addStyleClass("hidden");
348            this.panelEnablerView.visible = true;
349        }
350    },
351
352    _recordClicked: function()
353    {
354        this.recording = !this.recording;
355
356        if (this.recording)
357            InspectorController.startProfiling();
358        else
359            InspectorController.stopProfiling();
360    },
361
362    _enableProfiling: function()
363    {
364        if (InspectorController.profilerEnabled())
365            return;
366        this._toggleProfiling(this.panelEnablerView.alwaysEnabled);
367    },
368
369    _toggleProfiling: function(optionalAlways)
370    {
371        if (InspectorController.profilerEnabled())
372            InspectorController.disableProfiler(true);
373        else
374            InspectorController.enableProfiler(!!optionalAlways);
375    },
376
377    _populateProfiles: function()
378    {
379        if (this.sidebarTree.children.length)
380            return;
381
382        var profiles = InspectorController.profiles();
383        var profilesLength = profiles.length;
384        for (var i = 0; i < profilesLength; ++i) {
385            var profile = profiles[i];
386            this.addProfile(profile);
387        }
388
389        if (this.sidebarTree.children[0])
390            this.sidebarTree.children[0].select();
391
392        delete this._shouldPopulateProfiles;
393    },
394
395    _startSidebarDragging: function(event)
396    {
397        WebInspector.elementDragStart(this.sidebarResizeElement, this._sidebarDragging.bind(this), this._endSidebarDragging.bind(this), event, "col-resize");
398    },
399
400    _sidebarDragging: function(event)
401    {
402        this._updateSidebarWidth(event.pageX);
403
404        event.preventDefault();
405    },
406
407    _endSidebarDragging: function(event)
408    {
409        WebInspector.elementDragEnd(event);
410    },
411
412    _updateSidebarWidth: function(width)
413    {
414        if (this.sidebarElement.offsetWidth <= 0) {
415            // The stylesheet hasn't loaded yet or the window is closed,
416            // so we can't calculate what is need. Return early.
417            return;
418        }
419
420        if (!("_currentSidebarWidth" in this))
421            this._currentSidebarWidth = this.sidebarElement.offsetWidth;
422
423        if (typeof width === "undefined")
424            width = this._currentSidebarWidth;
425
426        width = Number.constrain(width, Preferences.minSidebarWidth, window.innerWidth / 2);
427
428        this._currentSidebarWidth = width;
429
430        this.sidebarElement.style.width = width + "px";
431        this.profileViews.style.left = width + "px";
432        this.profileViewStatusBarItemsContainer.style.left = width + "px";
433        this.sidebarResizeElement.style.left = (width - 3) + "px";
434
435        var visibleView = this.visibleView;
436        if (visibleView && "resize" in visibleView)
437            visibleView.resize();
438    }
439}
440
441WebInspector.ProfilesPanel.prototype.__proto__ = WebInspector.Panel.prototype;
442
443WebInspector.ProfileSidebarTreeElement = function(profile)
444{
445    this.profile = profile;
446
447    if (this.profile.title.indexOf(UserInitiatedProfileName) === 0)
448        this._profileNumber = this.profile.title.substring(UserInitiatedProfileName.length + 1);
449
450    WebInspector.SidebarTreeElement.call(this, "profile-sidebar-tree-item", "", "", profile, false);
451
452    this.refreshTitles();
453}
454
455WebInspector.ProfileSidebarTreeElement.prototype = {
456    onselect: function()
457    {
458        WebInspector.panels.profiles.showProfile(this.profile);
459    },
460
461    get mainTitle()
462    {
463        if (this._mainTitle)
464            return this._mainTitle;
465        if (this.profile.title.indexOf(UserInitiatedProfileName) === 0)
466            return WebInspector.UIString("Profile %d", this._profileNumber);
467        return this.profile.title;
468    },
469
470    set mainTitle(x)
471    {
472        this._mainTitle = x;
473        this.refreshTitles();
474    },
475
476    get subtitle()
477    {
478        // There is no subtitle.
479    },
480
481    set subtitle(x)
482    {
483        // Can't change subtitle.
484    },
485
486    set searchMatches(matches)
487    {
488        if (!matches) {
489            if (!this.bubbleElement)
490                return;
491            this.bubbleElement.removeStyleClass("search-matches");
492            this.bubbleText = "";
493            return;
494        }
495
496        this.bubbleText = matches;
497        this.bubbleElement.addStyleClass("search-matches");
498    }
499}
500
501WebInspector.ProfileSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;
502
503WebInspector.ProfileGroupSidebarTreeElement = function(title, subtitle)
504{
505    WebInspector.SidebarTreeElement.call(this, "profile-group-sidebar-tree-item", title, subtitle, null, true);
506}
507
508WebInspector.ProfileGroupSidebarTreeElement.prototype = {
509    onselect: function()
510    {
511        WebInspector.panels.profiles.showProfile(this.children[this.children.length - 1].profile);
512    }
513}
514
515WebInspector.ProfileGroupSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;
516