2 * Copyright (C) 2013, 2015 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
26 WebInspector.TimelineSidebarPanel = class TimelineSidebarPanel extends WebInspector.NavigationSidebarPanel
28 constructor(contentBrowser)
30 super("timeline", WebInspector.UIString("Timelines"));
32 this.contentBrowser = contentBrowser;
34 var timelineEventsTitleBarContainer = document.createElement("div");
35 timelineEventsTitleBarContainer.classList.add(WebInspector.TimelineSidebarPanel.TitleBarStyleClass);
36 timelineEventsTitleBarContainer.classList.add(WebInspector.TimelineSidebarPanel.TimelineEventsTitleBarStyleClass);
38 this._timelineEventsTitleBarElement = document.createElement("div");
39 this._timelineEventsTitleBarElement.classList.add(WebInspector.TimelineSidebarPanel.TitleBarTextStyleClass);
40 timelineEventsTitleBarContainer.appendChild(this._timelineEventsTitleBarElement);
42 this._timelineEventsTitleBarScopeContainer = document.createElement("div");
43 this._timelineEventsTitleBarScopeContainer.classList.add(WebInspector.TimelineSidebarPanel.TitleBarScopeBarStyleClass);
44 timelineEventsTitleBarContainer.appendChild(this._timelineEventsTitleBarScopeContainer);
46 this.element.insertBefore(timelineEventsTitleBarContainer, this.element.firstChild);
48 this.contentTreeOutlineLabel = "";
50 this._timelinesContentContainerElement = document.createElement("div");
51 this._timelinesContentContainerElement.classList.add(WebInspector.TimelineSidebarPanel.TimelinesContentContainerStyleClass);
52 this.element.insertBefore(this._timelinesContentContainerElement, this.element.firstChild);
54 this._displayedRecording = null;
55 this._displayedContentView = null;
56 this._viewMode = null;
57 this._previousSelectedTimelineType = null;
59 // Maintain an invisible tree outline containing tree elements for all recordings.
60 // The visible recording's tree element is selected when the content view changes.
61 this._recordingTreeElementMap = new Map;
62 this._recordingsTreeOutline = this.createContentTreeOutline(true, true);
63 this._recordingsTreeOutline.element.classList.add(WebInspector.NavigationSidebarPanel.HideDisclosureButtonsStyleClassName);
64 this._recordingsTreeOutline.element.classList.add(WebInspector.NavigationSidebarPanel.ContentTreeOutlineElementHiddenStyleClassName);
65 this._recordingsTreeOutline.onselect = this._recordingsTreeElementSelected.bind(this);
66 this._timelinesContentContainerElement.appendChild(this._recordingsTreeOutline.element);
68 // Maintain a tree outline with tree elements for each timeline of the selected recording.
69 this._timelinesTreeOutline = this.createContentTreeOutline(true, true);
70 this._timelinesTreeOutline.element.classList.add(WebInspector.NavigationSidebarPanel.HideDisclosureButtonsStyleClassName);
71 this._timelinesTreeOutline.onselect = this._timelinesTreeElementSelected.bind(this);
72 this._timelinesContentContainerElement.appendChild(this._timelinesTreeOutline.element);
74 this._timelineTreeElementMap = new Map;
76 // COMPATIBILITY (iOS 8): TimelineAgent.EventType.RenderingFrame did not exist.
77 this._renderingFramesSupported = window.TimelineAgent && TimelineAgent.EventType.RenderingFrame;
79 if (this._renderingFramesSupported) {
80 var timelinesNavigationItem = new WebInspector.RadioButtonNavigationItem(WebInspector.TimelineSidebarPanel.ViewMode.Timelines, WebInspector.UIString("Timelines"))
81 var renderingFramesNavigationItem = new WebInspector.RadioButtonNavigationItem(WebInspector.TimelineSidebarPanel.ViewMode.RenderingFrames, WebInspector.UIString("Rendering Frames"))
82 this._viewModeNavigationBar = new WebInspector.NavigationBar(null, [timelinesNavigationItem, renderingFramesNavigationItem], "tablist");
83 this._viewModeNavigationBar.addEventListener(WebInspector.NavigationBar.Event.NavigationItemSelected, this._viewModeSelected, this);
85 var container = document.createElement("div");
86 container.className = "navigation-bar-container";
87 container.appendChild(this._viewModeNavigationBar.element);
88 this.element.insertBefore(container, this.element.firstChild);
90 this._chartColors = new Map;
91 this._chartColors.set(WebInspector.RenderingFrameTimelineRecord.TaskType.Script, "rgb(153, 113, 185)");
92 this._chartColors.set(WebInspector.RenderingFrameTimelineRecord.TaskType.Layout, "rgb(212, 108, 108)");
93 this._chartColors.set(WebInspector.RenderingFrameTimelineRecord.TaskType.Paint, "rgb(152, 188, 77)");
94 this._chartColors.set(WebInspector.RenderingFrameTimelineRecord.TaskType.Other, "rgb(221, 221, 221)");
96 this._frameSelectionChartRow = new WebInspector.ChartDetailsSectionRow(this);
97 this._frameSelectionChartRow.innerRadius = 0.5;
98 this._frameSelectionChartRow.addEventListener(WebInspector.ChartDetailsSectionRow.Event.LegendItemChecked, this._frameSelectionLegendItemChecked, this);
100 this._renderingFrameTaskFilter = new Set;
102 var chartGroup = new WebInspector.DetailsSectionGroup([this._frameSelectionChartRow]);
103 this._frameSelectionChartSection = new WebInspector.DetailsSection("frames-selection-chart", WebInspector.UIString("Selected Frames"), [chartGroup], null, true);
104 this._timelinesContentContainerElement.appendChild(this._frameSelectionChartSection.element);
106 var timelinesTitleBarElement = document.createElement("div");
107 timelinesTitleBarElement.textContent = WebInspector.UIString("Timelines");
108 timelinesTitleBarElement.classList.add(WebInspector.TimelineSidebarPanel.TitleBarStyleClass);
109 timelinesTitleBarElement.classList.add(WebInspector.TimelineSidebarPanel.TimelinesTitleBarStyleClass);
110 this.element.insertBefore(timelinesTitleBarElement, this.element.firstChild);
113 var statusBarElement = this._statusBarElement = document.createElement("div");
114 statusBarElement.classList.add(WebInspector.TimelineSidebarPanel.StatusBarStyleClass);
115 this.element.insertBefore(statusBarElement, this.element.firstChild);
117 this._recordGlyphElement = document.createElement("div");
118 this._recordGlyphElement.className = WebInspector.TimelineSidebarPanel.RecordGlyphStyleClass;
119 this._recordGlyphElement.title = WebInspector.UIString("Click or press the spacebar to record.")
120 this._recordGlyphElement.addEventListener("mouseover", this._recordGlyphMousedOver.bind(this));
121 this._recordGlyphElement.addEventListener("mouseout", this._recordGlyphMousedOut.bind(this));
122 this._recordGlyphElement.addEventListener("click", this._recordGlyphClicked.bind(this));
123 statusBarElement.appendChild(this._recordGlyphElement);
125 this._recordStatusElement = document.createElement("div");
126 this._recordStatusElement.className = WebInspector.TimelineSidebarPanel.RecordStatusStyleClass;
127 statusBarElement.appendChild(this._recordStatusElement);
129 WebInspector.showReplayInterfaceSetting.addEventListener(WebInspector.Setting.Event.Changed, this._updateReplayInterfaceVisibility, this);
131 // We always create a replay navigation bar; its visibility is controlled by WebInspector.showReplayInterfaceSetting.
132 this._replayNavigationBar = new WebInspector.NavigationBar;
133 this.element.appendChild(this._replayNavigationBar.element);
135 var toolTip = WebInspector.UIString("Begin Capturing");
136 var altToolTip = WebInspector.UIString("End Capturing");
137 this._replayCaptureButtonItem = new WebInspector.ActivateButtonNavigationItem("replay-capture", toolTip, altToolTip, "Images/Circle.svg", 16, 16);
138 this._replayCaptureButtonItem.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, this._replayCaptureButtonClicked, this);
139 this._replayCaptureButtonItem.enabled = true;
140 this._replayNavigationBar.addNavigationItem(this._replayCaptureButtonItem);
142 toolTip = WebInspector.UIString("Start Playback");
143 altToolTip = WebInspector.UIString("Pause Playback");
144 this._replayPauseResumeButtonItem = new WebInspector.ToggleButtonNavigationItem("replay-pause-resume", toolTip, altToolTip, "Images/Resume.svg", "Images/Pause.svg", 15, 15, true);
145 this._replayPauseResumeButtonItem.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, this._replayPauseResumeButtonClicked, this);
146 this._replayPauseResumeButtonItem.enabled = false;
147 this._replayNavigationBar.addNavigationItem(this._replayPauseResumeButtonItem);
149 WebInspector.replayManager.addEventListener(WebInspector.ReplayManager.Event.CaptureStarted, this._captureStarted, this);
150 WebInspector.replayManager.addEventListener(WebInspector.ReplayManager.Event.CaptureStopped, this._captureStopped, this);
152 this._statusBarElement.oncontextmenu = this._contextMenuNavigationBarOrStatusBar.bind(this);
153 this._replayNavigationBar.element.oncontextmenu = this._contextMenuNavigationBarOrStatusBar.bind(this);
154 this._updateReplayInterfaceVisibility();
156 WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.RecordingCreated, this._recordingCreated, this);
157 WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.RecordingLoaded, this._recordingLoaded, this);
159 this.contentBrowser.addEventListener(WebInspector.ContentBrowser.Event.CurrentContentViewDidChange, this._contentBrowserCurrentContentViewDidChange, this);
160 WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.CapturingStarted, this._capturingStarted, this);
161 WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.CapturingStopped, this._capturingStopped, this);
163 for (var recording of WebInspector.timelineManager.recordings)
164 this._addRecording(recording);
166 this._recordingCountChanged();
168 if (WebInspector.timelineManager.activeRecording)
169 this._recordingLoaded();
171 this._toggleRecordingShortcut = new WebInspector.KeyboardShortcut(null, WebInspector.KeyboardShortcut.Key.Space, this._toggleRecordingOnSpacebar.bind(this));
172 this._toggleRecordingShortcut.implicitlyPreventsDefault = false;
174 this._toggleNewRecordingShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.Shift, WebInspector.KeyboardShortcut.Key.Space, this._toggleNewRecordingOnSpacebar.bind(this));
175 this._toggleNewRecordingShortcut.implicitlyPreventsDefault = false;
184 if (this._displayedContentView)
185 this.contentBrowser.showContentView(this._displayedContentView);
187 if (this.viewMode === WebInspector.TimelineSidebarPanel.ViewMode.RenderingFrames)
188 this._refreshFrameSelectionChart();
190 this._toggleRecordingShortcut.disabled = false;
191 this._toggleNewRecordingShortcut.disabled = false;
198 this._toggleRecordingShortcut.disabled = true;
199 this._toggleNewRecordingShortcut.disabled = true;
206 WebInspector.showReplayInterfaceSetting.removeEventListener(null, null, this);
207 WebInspector.replayManager.removeEventListener(null, null, this);
208 WebInspector.timelineManager.removeEventListener(null, null, this);
210 WebInspector.timelineManager.reset();
215 return this._viewMode;
218 showDefaultContentView()
220 if (this._displayedContentView)
221 this.showTimelineOverview();
224 get hasSelectedElement()
226 return !!this._contentTreeOutline.selectedTreeElement || !!this._recordingsTreeOutline.selectedTreeElement;
229 treeElementForRepresentedObject(representedObject)
231 if (representedObject instanceof WebInspector.TimelineRecording)
232 return this._recordingTreeElementMap.get(representedObject);
234 // This fails if the timeline does not belong to the selected recording.
235 if (representedObject instanceof WebInspector.Timeline) {
236 var foundTreeElement = this._timelineTreeElementMap.get(representedObject);
237 if (foundTreeElement)
238 return foundTreeElement;
241 // The main resource is used as the representedObject instead of Frame in our tree.
242 if (representedObject instanceof WebInspector.Frame)
243 representedObject = representedObject.mainResource;
245 var foundTreeElement = this.contentTreeOutline.getCachedTreeElement(representedObject);
246 if (foundTreeElement)
247 return foundTreeElement;
249 // Look for TreeElements loosely based on represented objects that can contain the represented
250 // object we are really looking for. This allows a SourceCodeTimelineTreeElement or a
251 // TimelineRecordTreeElement to stay selected when the Resource it represents is showing.
253 function looselyCompareRepresentedObjects(candidateTreeElement)
255 if (!candidateTreeElement)
258 var candidateRepresentedObject = candidateTreeElement.representedObject;
259 if (candidateRepresentedObject instanceof WebInspector.SourceCodeTimeline) {
260 if (candidateRepresentedObject.sourceCode === representedObject)
263 } else if (candidateRepresentedObject instanceof WebInspector.Timeline && representedObject instanceof WebInspector.Timeline) {
264 // Reopen to the same timeline, even if a different parent recording is currently shown.
265 if (candidateRepresentedObject.type === representedObject.type)
270 if (candidateRepresentedObject instanceof WebInspector.TimelineRecord) {
271 if (!candidateRepresentedObject.sourceCodeLocation)
273 if (candidateRepresentedObject.sourceCodeLocation.sourceCode === representedObject)
278 if (candidateRepresentedObject instanceof WebInspector.ProfileNode)
281 console.error("Unknown TreeElement", candidateTreeElement);
285 // Check the selected tree element first so we don't need to do a longer search and it is
286 // likely to be the best candidate for the current view.
287 if (looselyCompareRepresentedObjects(this.contentTreeOutline.selectedTreeElement))
288 return this.contentTreeOutline.selectedTreeElement;
290 var currentTreeElement = this._contentTreeOutline.children[0];
291 while (currentTreeElement && !currentTreeElement.root) {
292 if (looselyCompareRepresentedObjects(currentTreeElement))
293 return currentTreeElement;
294 currentTreeElement = currentTreeElement.traverseNextTreeElement(false, null, false);
300 get contentTreeOutlineLabel()
302 return this._timelineEventsTitleBarElement.textContent;
305 set contentTreeOutlineLabel(label)
307 label = label || WebInspector.UIString("Timeline Events");
309 this._timelineEventsTitleBarElement.textContent = label;
310 this.filterBar.placeholder = WebInspector.UIString("Filter %s").format(label);
313 get contentTreeOutlineScopeBar()
315 return this._timelineEventsTitleBarScopeContainer.children;
318 set contentTreeOutlineScopeBar(scopeBar)
320 this._timelineEventsTitleBarScopeContainer.removeChildren();
322 if (!scopeBar || !scopeBar.element)
325 this._timelineEventsTitleBarScopeContainer.appendChild(scopeBar.element);
328 showTimelineOverview()
330 if (this._timelinesTreeOutline.selectedTreeElement)
331 this._timelinesTreeOutline.selectedTreeElement.deselect();
333 this._displayedContentView.showOverviewTimelineView();
334 this.contentBrowser.showContentView(this._displayedContentView);
336 var selectedByUser = false;
337 this._changeViewMode(WebInspector.TimelineSidebarPanel.ViewMode.Timelines, selectedByUser);
340 showTimelineViewForTimeline(timeline)
342 console.assert(timeline instanceof WebInspector.Timeline, timeline);
343 console.assert(this._displayedRecording.timelines.has(timeline.type), "Cannot show timeline because it does not belong to the shown recording.", timeline.type);
345 // The sidebar view mode must be in the correct state before changing the content view.
346 var selectedByUser = false;
347 this._changeViewMode(this._viewModeForTimeline(timeline), selectedByUser);
349 if (this._timelineTreeElementMap.has(timeline)) {
350 // Defer showing the relevant timeline to the onselect handler of the timelines tree element.
351 var wasSelectedByUser = true;
352 var shouldSuppressOnSelect = false;
353 this._timelineTreeElementMap.get(timeline).select(true, wasSelectedByUser, shouldSuppressOnSelect, true);
355 this._displayedContentView.showTimelineViewForTimeline(timeline);
356 this.contentBrowser.showContentView(this._displayedContentView);
360 updateFrameSelection(startFrameIndex, endFrameIndex)
362 console.assert(startFrameIndex <= endFrameIndex);
363 console.assert(this.viewMode === WebInspector.TimelineSidebarPanel.ViewMode.RenderingFrames, this._viewMode);
364 if (this._startFrameIndex === startFrameIndex && this._endFrameIndex === endFrameIndex)
367 this._startFrameIndex = startFrameIndex;
368 this._endFrameIndex = endFrameIndex;
370 this._refreshFrameSelectionChart();
373 formatChartValue(value)
375 return this._frameSelectionChartRow.total === 0 ? "" : Number.secondsToString(value);
382 super.updateFilter();
384 this._displayedContentView.filterDidChange();
392 matchTreeElementAgainstCustomFilters(treeElement)
394 if (!this._displayedContentView)
397 if (this._viewMode === WebInspector.TimelineSidebarPanel.ViewMode.RenderingFrames && this._renderingFrameTaskFilter.size) {
398 while (treeElement && !(treeElement.record instanceof WebInspector.TimelineRecord))
399 treeElement = treeElement.parent;
401 console.assert(treeElement, "Cannot apply task filter: no TimelineRecord found.");
406 if (!(treeElement.record instanceof WebInspector.RenderingFrameTimelineRecord)) {
407 var taskType = WebInspector.RenderingFrameTimelineRecord.taskTypeForTimelineRecord(treeElement.record);
408 if (!this._renderingFrameTaskFilter.has(taskType))
416 return this._displayedContentView.matchTreeElementAgainstCustomFilters(treeElement);
419 treeElementAddedOrChanged(treeElement)
421 if (treeElement.status)
424 if (!treeElement.treeOutline || typeof treeElement.treeOutline.__canShowContentViewForTreeElement !== "function")
427 if (!treeElement.treeOutline.__canShowContentViewForTreeElement(treeElement))
430 wrappedSVGDocument("Images/Close.svg", null, WebInspector.UIString("Close resource view"), function(element) {
431 var fragment = document.createDocumentFragment();
433 var closeButton = new WebInspector.TreeElementStatusButton(element);
434 closeButton.element.classList.add("close");
435 closeButton.addEventListener(WebInspector.TreeElementStatusButton.Event.Clicked, this._treeElementCloseButtonClicked, this);
436 fragment.appendChild(closeButton.element);
438 var goToButton = new WebInspector.TreeElementStatusButton(WebInspector.createGoToArrowButton());
439 goToButton.__treeElement = treeElement;
440 goToButton.addEventListener(WebInspector.TreeElementStatusButton.Event.Clicked, this._treeElementGoToArrowWasClicked, this);
441 fragment.appendChild(goToButton.element);
443 treeElement.status = fragment;
447 canShowDifferentContentView()
449 if (this._clickedTreeElementGoToArrow)
452 if (this.contentBrowser.currentContentView instanceof WebInspector.TimelineRecordingContentView)
455 return !this.restoringState || !this._restoredShowingTimelineRecordingContentView;
458 saveStateToCookie(cookie)
460 console.assert(cookie);
462 cookie[WebInspector.TimelineSidebarPanel.ShowingTimelineRecordingContentViewCookieKey] = this.contentBrowser.currentContentView instanceof WebInspector.TimelineRecordingContentView;
464 if (this._viewMode === WebInspector.TimelineSidebarPanel.ViewMode.RenderingFrames)
465 cookie[WebInspector.TimelineSidebarPanel.SelectedTimelineViewIdentifierCookieKey] = WebInspector.TimelineRecord.Type.RenderingFrame;
467 var selectedTreeElement = this._timelinesTreeOutline.selectedTreeElement;
468 if (selectedTreeElement)
469 cookie[WebInspector.TimelineSidebarPanel.SelectedTimelineViewIdentifierCookieKey] = selectedTreeElement.representedObject.type;
471 cookie[WebInspector.TimelineSidebarPanel.SelectedTimelineViewIdentifierCookieKey] = WebInspector.TimelineSidebarPanel.OverviewTimelineIdentifierCookieValue;
474 super.saveStateToCookie(cookie);
477 restoreStateFromCookie(cookie, relaxedMatchDelay)
479 console.assert(cookie);
481 // The _displayedContentView is not ready on initial load, so delay the restore.
482 // This matches the delayed work in the WebInspector.TimelineSidebarPanel constructor.
483 if (!this._displayedContentView) {
484 setTimeout(this.restoreStateFromCookie.bind(this, cookie, relaxedMatchDelay), 0);
488 this._restoredShowingTimelineRecordingContentView = cookie[WebInspector.TimelineSidebarPanel.ShowingTimelineRecordingContentViewCookieKey];
490 var selectedTimelineViewIdentifier = cookie[WebInspector.TimelineSidebarPanel.SelectedTimelineViewIdentifierCookieKey];
491 if (selectedTimelineViewIdentifier === WebInspector.TimelineRecord.Type.RenderingFrame && !this._renderingFramesSupported)
492 selectedTimelineViewIdentifier = null;
494 if (selectedTimelineViewIdentifier && this._displayedRecording.timelines.has(selectedTimelineViewIdentifier))
495 this.showTimelineViewForTimeline(this._displayedRecording.timelines.get(selectedTimelineViewIdentifier));
497 this.showTimelineOverview();
499 // Don't call NavigationSidebarPanel.restoreStateFromCookie, because it tries to match based
500 // on type only as a last resort. This would cause the first recording to be reselected on reload.
505 _toggleRecordingOnSpacebar(event)
507 if (WebInspector.isEventTargetAnEditableField(event))
510 this._toggleRecording();
513 _toggleNewRecordingOnSpacebar(event)
515 if (WebInspector.isEventTargetAnEditableField(event))
518 this._toggleRecording(true);
521 _toggleRecording(shouldCreateRecording)
523 if (WebInspector.timelineManager.isCapturing()) {
524 WebInspector.timelineManager.stopCapturing();
526 this._recordGlyphElement.title = WebInspector.UIString("Click or press the spacebar to record.")
528 WebInspector.timelineManager.startCapturing(shouldCreateRecording);
529 // Show the timeline to which events will be appended.
530 this._recordingLoaded();
532 this._recordGlyphElement.title = WebInspector.UIString("Click or press the spacebar to stop recording.")
536 _treeElementGoToArrowWasClicked(event)
538 this._clickedTreeElementGoToArrow = true;
540 var treeElement = event.target.__treeElement;
541 console.assert(treeElement instanceof WebInspector.TreeElement);
543 treeElement.select(true, true);
545 this._clickedTreeElementGoToArrow = false;
548 _treeElementCloseButtonClicked(event)
550 var currentTimelineView = this._displayedContentView ? this._displayedContentView.currentTimelineView : null;
551 if (currentTimelineView && currentTimelineView.representedObject instanceof WebInspector.Timeline)
552 this.showTimelineViewForTimeline(currentTimelineView.representedObject);
554 this.showTimelineOverview();
557 _recordingsTreeElementSelected(treeElement, selectedByUser)
559 console.assert(treeElement.representedObject instanceof WebInspector.TimelineRecording);
561 this._recordingSelected(treeElement.representedObject);
564 _renderingFrameTimelineTimesUpdated(event)
566 if (this.viewMode === WebInspector.TimelineSidebarPanel.ViewMode.RenderingFrames)
567 this._refreshFrameSelectionChart();
570 _timelinesTreeElementSelected(treeElement, selectedByUser)
572 console.assert(this._timelineTreeElementMap.get(treeElement.representedObject) === treeElement, treeElement);
574 // If not selected by user, then this selection merely synced the tree element with the content view's contents.
575 if (!selectedByUser) {
576 console.assert(this._displayedContentView.currentTimelineView.representedObject === treeElement.representedObject);
580 var timeline = treeElement.representedObject;
581 console.assert(timeline instanceof WebInspector.Timeline, timeline);
582 console.assert(this._displayedRecording.timelines.get(timeline.type) === timeline, timeline);
584 this._previousSelectedTimelineType = timeline.type;
586 this._displayedContentView.showTimelineViewForTimeline(timeline);
587 this.contentBrowser.showContentView(this._displayedContentView);
590 _contentBrowserCurrentContentViewDidChange(event)
592 var didShowTimelineRecordingContentView = this.contentBrowser.currentContentView instanceof WebInspector.TimelineRecordingContentView;
593 this.element.classList.toggle(WebInspector.TimelineSidebarPanel.TimelineRecordingContentViewShowingStyleClass, didShowTimelineRecordingContentView);
595 if (this.viewMode === WebInspector.TimelineSidebarPanel.ViewMode.RenderingFrames)
596 this._refreshFrameSelectionChart();
599 _capturingStarted(event)
601 this._recordStatusElement.textContent = WebInspector.UIString("Recording");
602 this._recordGlyphElement.classList.add(WebInspector.TimelineSidebarPanel.RecordGlyphRecordingStyleClass);
605 _capturingStopped(event)
607 this._recordStatusElement.textContent = "";
608 this._recordGlyphElement.classList.remove(WebInspector.TimelineSidebarPanel.RecordGlyphRecordingStyleClass);
611 _recordingCreated(event)
613 this._addRecording(event.data.recording)
614 this._recordingCountChanged();
617 _addRecording(recording)
619 console.assert(recording instanceof WebInspector.TimelineRecording, recording);
621 var recordingTreeElement = new WebInspector.GeneralTreeElement(WebInspector.TimelineSidebarPanel.StopwatchIconStyleClass, recording.displayName, null, recording);
622 this._recordingTreeElementMap.set(recording, recordingTreeElement);
623 this._recordingsTreeOutline.appendChild(recordingTreeElement);
626 _recordingCountChanged()
628 var previousTreeElement = null;
629 for (var treeElement of this._recordingTreeElementMap.values()) {
630 if (previousTreeElement) {
631 previousTreeElement.nextSibling = treeElement;
632 treeElement.previousSibling = previousTreeElement;
635 previousTreeElement = treeElement;
639 _recordingSelected(recording)
641 console.assert(recording instanceof WebInspector.TimelineRecording, recording);
643 var oldRecording = this._displayedRecording || null;
646 oldRecording.removeEventListener(WebInspector.TimelineRecording.Event.TimelineAdded, this._timelineAdded, this);
647 oldRecording.removeEventListener(WebInspector.TimelineRecording.Event.TimelineRemoved, this._timelineRemoved, this);
649 // Destroy tree elements in one operation to avoid unnecessary fixups.
650 this._timelinesTreeOutline.removeChildren();
651 this._timelineTreeElementMap.clear();
654 this._displayedRecording = recording;
655 this._displayedRecording.addEventListener(WebInspector.TimelineRecording.Event.TimelineAdded, this._timelineAdded, this);
656 this._displayedRecording.addEventListener(WebInspector.TimelineRecording.Event.TimelineRemoved, this._timelineRemoved, this);
658 for (var timeline of recording.timelines.values())
659 this._timelineAdded(timeline);
661 // Save the current state incase we need to restore it to a new recording.
663 this.saveStateToCookie(cookie);
665 // Try to get the recording content view if it exists already, if it does we don't want to restore the cookie.
666 var onlyExisting = true;
667 this._displayedContentView = this.contentBrowser.contentViewForRepresentedObject(this._displayedRecording, onlyExisting, {timelineSidebarPanel: this});
668 if (this._displayedContentView) {
669 // Show the timeline that was being shown to update the sidebar tree state.
670 var currentTimelineView = this._displayedContentView.currentTimelineView;
671 if (currentTimelineView && currentTimelineView.representedObject instanceof WebInspector.Timeline)
672 this.showTimelineViewForTimeline(currentTimelineView.representedObject);
674 this.showTimelineOverview();
680 onlyExisting = false;
681 this._displayedContentView = this.contentBrowser.contentViewForRepresentedObject(this._displayedRecording, onlyExisting, {timelineSidebarPanel: this});
683 // Restore the cookie to carry over the previous recording view state to the new recording.
684 this.restoreStateFromCookie(cookie);
689 _recordingLoaded(event)
691 this._recordingSelected(WebInspector.timelineManager.activeRecording);
694 _timelineAdded(timelineOrEvent)
696 var timeline = timelineOrEvent;
697 if (!(timeline instanceof WebInspector.Timeline))
698 timeline = timelineOrEvent.data.timeline;
700 console.assert(timeline instanceof WebInspector.Timeline, timeline);
701 console.assert(!this._timelineTreeElementMap.has(timeline), timeline);
703 if (timeline.type === WebInspector.TimelineRecord.Type.RenderingFrame) {
704 timeline.addEventListener(WebInspector.Timeline.Event.TimesUpdated, this._renderingFrameTimelineTimesUpdated, this);
708 var timelineTreeElement = new WebInspector.GeneralTreeElement([timeline.iconClassName, WebInspector.TimelineSidebarPanel.LargeIconStyleClass], timeline.displayName, null, timeline);
709 var tooltip = WebInspector.UIString("Close %s timeline view").format(timeline.displayName);
710 wrappedSVGDocument("Images/CloseLarge.svg", WebInspector.TimelineSidebarPanel.CloseButtonStyleClass, tooltip, function(element) {
711 var button = new WebInspector.TreeElementStatusButton(element);
712 button.addEventListener(WebInspector.TreeElementStatusButton.Event.Clicked, this.showTimelineOverview, this);
713 timelineTreeElement.status = button.element;
716 this._timelinesTreeOutline.appendChild(timelineTreeElement);
717 this._timelineTreeElementMap.set(timeline, timelineTreeElement);
719 this._timelineCountChanged();
722 _timelineRemoved(event)
724 var timeline = event.data.timeline;
725 console.assert(timeline instanceof WebInspector.Timeline, timeline);
727 if (timeline.type === WebInspector.TimelineRecord.Type.RenderingFrame) {
728 timeline.removeEventListener(WebInspector.Timeline.Event.TimesUpdated, this._renderingFrameTimelineTimesUpdated, this);
732 console.assert(this._timelineTreeElementMap.has(timeline), timeline);
734 var timelineTreeElement = this._timelineTreeElementMap.take(timeline);
735 var shouldSuppressOnDeselect = false;
736 var shouldSuppressSelectSibling = true;
737 this._timelinesTreeOutline.removeChild(timelineTreeElement, shouldSuppressOnDeselect, shouldSuppressSelectSibling);
738 this._timelineTreeElementMap.delete(timeline);
740 this._timelineCountChanged();
743 _timelineCountChanged()
745 var previousTreeElement = null;
746 for (var treeElement of this._timelineTreeElementMap.values()) {
747 if (previousTreeElement) {
748 previousTreeElement.nextSibling = treeElement;
749 treeElement.previousSibling = previousTreeElement;
752 previousTreeElement = treeElement;
755 var timelineHeight = 36;
756 var eventTitleBarOffset = 58;
757 var contentElementOffset = 81;
758 var timelineCount = this._timelineTreeElementMap.size;
760 this._timelinesContentContainerElement.style.height = (timelineHeight * timelineCount) + "px";
761 this._timelineEventsTitleBarElement.style.top = (timelineHeight * timelineCount + eventTitleBarOffset) + "px";
762 this.contentElement.style.top = (timelineHeight * timelineCount + contentElementOffset) + "px";
765 _recordGlyphMousedOver(event)
767 this._recordGlyphElement.classList.remove(WebInspector.TimelineSidebarPanel.RecordGlyphRecordingForcedStyleClass);
769 if (WebInspector.timelineManager.isCapturing())
770 this._recordStatusElement.textContent = WebInspector.UIString("Stop Recording");
772 this._recordStatusElement.textContent = WebInspector.UIString("Start Recording");
775 _recordGlyphMousedOut(event)
777 this._recordGlyphElement.classList.remove(WebInspector.TimelineSidebarPanel.RecordGlyphRecordingForcedStyleClass);
779 if (WebInspector.timelineManager.isCapturing())
780 this._recordStatusElement.textContent = WebInspector.UIString("Recording");
782 this._recordStatusElement.textContent = "";
785 _recordGlyphClicked(event)
787 // Add forced class to prevent the glyph from showing a confusing status after click.
788 this._recordGlyphElement.classList.add(WebInspector.TimelineSidebarPanel.RecordGlyphRecordingForcedStyleClass);
790 this._toggleRecording(event.shiftKey);
793 _viewModeSelected(event)
795 console.assert(event.target.selectedNavigationItem);
796 if (!event.target.selectedNavigationItem)
799 var selectedNavigationItem = event.target.selectedNavigationItem;
800 var selectedByUser = true;
801 this._changeViewMode(selectedNavigationItem.identifier, selectedByUser);
804 _viewModeForTimeline(timeline)
806 if (timeline && timeline.type === WebInspector.TimelineRecord.Type.RenderingFrame)
807 return WebInspector.TimelineSidebarPanel.ViewMode.RenderingFrames;
809 return WebInspector.TimelineSidebarPanel.ViewMode.Timelines;
812 _changeViewMode(mode, selectedByUser)
814 if (!this._renderingFramesSupported || this._viewMode === mode)
817 this._viewMode = mode;
818 this._viewModeNavigationBar.selectedNavigationItem = this._viewMode;
820 if (this._viewMode === WebInspector.TimelineSidebarPanel.ViewMode.Timelines) {
821 this._timelinesTreeOutline.element.classList.remove(WebInspector.NavigationSidebarPanel.ContentTreeOutlineElementHiddenStyleClassName);
822 this._frameSelectionChartSection.collapsed = true;
824 this._timelinesTreeOutline.element.classList.add(WebInspector.NavigationSidebarPanel.ContentTreeOutlineElementHiddenStyleClassName);
825 this._frameSelectionChartSection.collapsed = false;
828 if (selectedByUser) {
829 var timelineType = this._previousSelectedTimelineType;
830 if (this._viewMode === WebInspector.TimelineSidebarPanel.ViewMode.RenderingFrames) {
831 this._previousSelectedTimelineType = this._timelinesTreeOutline.selectedTreeElement ? this._timelinesTreeOutline.selectedTreeElement.representedObject.type : null;
832 timelineType = WebInspector.TimelineRecord.Type.RenderingFrame;
836 console.assert(this._displayedRecording.timelines.has(timelineType), timelineType);
837 this.showTimelineViewForTimeline(this._displayedRecording.timelines.get(timelineType));
839 this.showTimelineOverview();
845 _frameSelectionLegendItemChecked(event)
847 if (event.data.checked)
848 this._renderingFrameTaskFilter.delete(event.data.id);
850 this._renderingFrameTaskFilter.add(event.data.id);
855 _refreshFrameSelectionChart()
860 function getSelectedRecords()
862 console.assert(this._displayedRecording);
863 console.assert(this._displayedRecording.timelines.has(WebInspector.TimelineRecord.Type.RenderingFrame), "Cannot find rendering frames timeline in displayed recording");
865 var timeline = this._displayedRecording.timelines.get(WebInspector.TimelineRecord.Type.RenderingFrame);
866 var selectedRecords = [];
867 for (var record of timeline.records) {
868 console.assert(record instanceof WebInspector.RenderingFrameTimelineRecord);
869 // If this frame is completely before the bounds of the graph, skip this record.
870 if (record.frameIndex < this._startFrameIndex)
873 // If this record is completely after the end time, break out now.
874 // Records are sorted, so all records after this will be beyond the end time too.
875 if (record.frameIndex > this._endFrameIndex)
878 selectedRecords.push(record);
881 return selectedRecords;
884 var chart = this._frameSelectionChartRow;
885 var records = getSelectedRecords.call(this);
886 var chartData = Object.keys(WebInspector.RenderingFrameTimelineRecord.TaskType).map(function(taskTypeKey) {
887 var taskType = WebInspector.RenderingFrameTimelineRecord.TaskType[taskTypeKey];
888 var label = WebInspector.RenderingFrameTimelineRecord.displayNameForTaskType(taskType);
889 var value = records.reduce(function(previousValue, currentValue) { return previousValue + currentValue.durationForTask(taskType); }, 0);
890 var color = this._chartColors.get(taskType);
891 return {id: taskType, label, value, color, checkbox: taskType !== WebInspector.RenderingFrameTimelineRecord.TaskType.Other};
894 this._frameSelectionChartRow.data = chartData;
896 if (!records.length) {
897 this._frameSelectionChartRow.title = WebInspector.UIString("Frames: None Selected");
901 var firstRecord = records[0];
902 var lastRecord = records.lastValue;
904 if (records.length > 1) {
905 this._frameSelectionChartRow.title = WebInspector.UIString("Frames: %d – %d (%s – %s)").format(firstRecord.frameNumber, lastRecord.frameNumber,
906 Number.secondsToString(firstRecord.startTime), Number.secondsToString(lastRecord.endTime));
908 this._frameSelectionChartRow.title = WebInspector.UIString("Frame: %d (%s – %s)").format(firstRecord.frameNumber,
909 Number.secondsToString(firstRecord.startTime), Number.secondsToString(lastRecord.endTime));
913 // These methods are only used when ReplayAgent is available.
915 _updateReplayInterfaceVisibility()
917 var shouldShowReplayInterface = window.ReplayAgent && WebInspector.showReplayInterfaceSetting.value;
919 this._statusBarElement.classList.toggle(WebInspector.TimelineSidebarPanel.HiddenStyleClassName, shouldShowReplayInterface);
920 this._replayNavigationBar.element.classList.toggle(WebInspector.TimelineSidebarPanel.HiddenStyleClassName, !shouldShowReplayInterface);
923 _contextMenuNavigationBarOrStatusBar()
925 if (!window.ReplayAgent)
928 function toggleReplayInterface() {
929 WebInspector.showReplayInterfaceSetting.value = !WebInspector.showReplayInterfaceSetting.value;
932 var contextMenu = new WebInspector.ContextMenu(event);
933 if (WebInspector.showReplayInterfaceSetting.value)
934 contextMenu.appendItem(WebInspector.UIString("Hide Replay Controls"), toggleReplayInterface);
936 contextMenu.appendItem(WebInspector.UIString("Show Replay Controls"), toggleReplayInterface);
940 _replayCaptureButtonClicked()
942 if (!this._replayCaptureButtonItem.activated) {
943 WebInspector.replayManager.startCapturing();
944 WebInspector.timelineManager.startCapturing();
946 // De-bounce further presses until the backend has begun capturing.
947 this._replayCaptureButtonItem.activated = true;
948 this._replayCaptureButtonItem.enabled = false;
950 WebInspector.replayManager.stopCapturing();
951 WebInspector.timelineManager.stopCapturing();
953 this._replayCaptureButtonItem.enabled = false;
957 _replayPauseResumeButtonClicked()
959 if (this._replayPauseResumeButtonItem.toggled)
960 WebInspector.replayManager.pausePlayback();
962 WebInspector.replayManager.replayToCompletion();
967 this._replayCaptureButtonItem.enabled = true;
972 this._replayCaptureButtonItem.activated = false;
973 this._replayPauseResumeButtonItem.enabled = true;
978 this._replayPauseResumeButtonItem.toggled = true;
983 this._replayPauseResumeButtonItem.toggled = false;
987 WebInspector.TimelineSidebarPanel.ViewMode = {
988 Timelines: "timeline-sidebar-panel-view-mode-timelines",
989 RenderingFrames: "timeline-sidebar-panel-view-mode-frames"
992 WebInspector.TimelineSidebarPanel.HiddenStyleClassName = "hidden";
993 WebInspector.TimelineSidebarPanel.StatusBarStyleClass = "status-bar";
994 WebInspector.TimelineSidebarPanel.RecordGlyphStyleClass = "record-glyph";
995 WebInspector.TimelineSidebarPanel.RecordGlyphRecordingStyleClass = "recording";
996 WebInspector.TimelineSidebarPanel.RecordGlyphRecordingForcedStyleClass = "forced";
997 WebInspector.TimelineSidebarPanel.RecordStatusStyleClass = "record-status";
998 WebInspector.TimelineSidebarPanel.TitleBarStyleClass = "title-bar";
999 WebInspector.TimelineSidebarPanel.TitleBarTextStyleClass = "title-bar-text";
1000 WebInspector.TimelineSidebarPanel.TitleBarScopeBarStyleClass = "title-bar-scope-bar";
1001 WebInspector.TimelineSidebarPanel.TimelinesTitleBarStyleClass = "timelines";
1002 WebInspector.TimelineSidebarPanel.TimelineEventsTitleBarStyleClass = "timeline-events";
1003 WebInspector.TimelineSidebarPanel.TimelinesContentContainerStyleClass = "timelines-content";
1004 WebInspector.TimelineSidebarPanel.CloseButtonStyleClass = "close-button";
1005 WebInspector.TimelineSidebarPanel.LargeIconStyleClass = "large";
1006 WebInspector.TimelineSidebarPanel.StopwatchIconStyleClass = "stopwatch-icon";
1007 WebInspector.TimelineSidebarPanel.NetworkIconStyleClass = "network-icon";
1008 WebInspector.TimelineSidebarPanel.ColorsIconStyleClass = "colors-icon";
1009 WebInspector.TimelineSidebarPanel.ScriptIconStyleClass = "script-icon";
1010 WebInspector.TimelineSidebarPanel.RenderingFrameIconStyleClass = "rendering-frame-icon";
1011 WebInspector.TimelineSidebarPanel.TimelineRecordingContentViewShowingStyleClass = "timeline-recording-content-view-showing";
1013 WebInspector.TimelineSidebarPanel.ShowingTimelineRecordingContentViewCookieKey = "timeline-sidebar-panel-showing-timeline-recording-content-view";
1014 WebInspector.TimelineSidebarPanel.SelectedTimelineViewIdentifierCookieKey = "timeline-sidebar-panel-selected-timeline-view-identifier";
1015 WebInspector.TimelineSidebarPanel.OverviewTimelineIdentifierCookieValue = "overview";