| <!DOCTYPE html> |
| <!-- |
| Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| Use of this source code is governed by a BSD-style license that can be |
| found in the LICENSE file. |
| --> |
| |
| <link rel="stylesheet" href="/base/ui/common.css"> |
| <link rel="stylesheet" href="/core/timeline_view.css"> |
| |
| <link rel="import" href="/base/utils.html"> |
| <link rel="import" href="/base/settings.html"> |
| <link rel="import" href="/base/ui/dom_helpers.html"> |
| <link rel="import" href="/base/ui/overlay.html"> |
| <link rel="import" href="/base/ui/drag_handle.html"> |
| <link rel="import" href="/core/analysis/analysis_view.html"> |
| <link rel="import" href="/core/favicons.html"> |
| <link rel="import" href="/core/find_control.html"> |
| <link rel="import" href="/core/find_controller.html"> |
| <link rel="import" href="/core/timeline_track_view.html"> |
| <link rel="import" href="/core/scripting_control.html"> |
| <link rel="import" href="/core/scripting_controller.html"> |
| <link rel="import" href="/core/side_panel/side_panel_container.html"> |
| |
| <template id="timeline-view-template"> |
| <div class="control"> |
| <div class="bar"> |
| <div id="left-controls" class="controls"></div> |
| <div class="title">^_^</div> |
| <div id="right-controls" class="controls"></div> |
| </div> |
| <div id="collapsing-controls" class="controls"></div> |
| </div> |
| <middle-container> |
| <track-view-container></track-view-container> |
| <tv-c-side-panel-container></tv-c-side-panel-container> |
| </middle-container> |
| <x-drag-handle></x-drag-handle> |
| <tracing-analysis-view id="analysis"></tracing-analysis-view> |
| </template> |
| |
| <template id="help-btn-template"> |
| <div class="button view-help-button">?</div> |
| <div class="view-help-text"> |
| <div class="column left"> |
| <h2>Navigation</h2> |
| <div class='pair'> |
| <div class='command'>w/s</div> |
| <div class='action'>Zoom in/out (+shift: faster)</div> |
| </div> |
| |
| <div class='pair'> |
| <div class='command'>a/d</div> |
| <div class='action'>Pan left/right (+shift: faster)</div> |
| </div> |
| |
| <div class='pair'> |
| <div class='command'>→/shift-TAB</div> |
| <div class='action'>Select previous event</div> |
| </div> |
| |
| <div class='pair'> |
| <div class='command'>←/TAB</div> |
| <div class='action'>Select next event</div> |
| </div> |
| |
| <h2>Mouse Controls</h2> |
| <div class='pair'> |
| <div class='command'>click</div> |
| <div class='action'>Select event</div> |
| </div> |
| <div class='pair'> |
| <div class='command'>alt-mousewheel</div> |
| <div class='action'>Zoom in/out</div> |
| </div> |
| |
| <h3> |
| <span class='mouse-mode-icon select-mode'></span> |
| Select mode |
| </h3> |
| <div class='pair'> |
| <div class='command'>drag</div> |
| <div class='action'>Box select</div> |
| </div> |
| |
| <div class='pair'> |
| <div class='command'>double click</div> |
| <div class='action'>Select all events with same title</div> |
| </div> |
| |
| <h3> |
| <span class='mouse-mode-icon pan-mode'></span> |
| Pan mode |
| </h3> |
| <div class='pair'> |
| <div class='command'>drag</div> |
| <div class='action'>Pan the view</div> |
| </div> |
| |
| <h3> |
| <span class='mouse-mode-icon zoom-mode'></span> |
| Zoom mode |
| </h3> |
| <div class='pair'> |
| <div class='command'>drag</div> |
| <div class='action'>Zoom in/out by dragging up/down</div> |
| </div> |
| |
| <h3> |
| <span class='mouse-mode-icon timing-mode'></span> |
| Timing mode |
| </h3> |
| <div class='pair'> |
| <div class='command'>drag</div> |
| <div class='action'>Create or move markers</div> |
| </div> |
| |
| <div class='pair'> |
| <div class='command'>double click</div> |
| <div class='action'>Set marker range to slice</div> |
| </div> |
| </div> |
| |
| <div class="column right"> |
| <h2>General</h2> |
| <div class='pair'> |
| <div class='command'>1-4</div> |
| <div class='action'>Switch mouse mode</div> |
| </div> |
| |
| <div class='pair'> |
| <div class='command'>shift</div> |
| <div class='action'>Hold for temporary select</div> |
| </div> |
| |
| <div class='pair'> |
| <div class='command'>space</div> |
| <div class='action'>Hold for temporary pan</div> |
| </div> |
| |
| <div class='pair'> |
| <div class='command'><span class='mod'></span></div> |
| <div class='action'>Hold for temporary zoom</div> |
| </div> |
| |
| <div class='pair'> |
| <div class='command'>/</div> |
| <div class='action'>Search</div> |
| </div> |
| |
| <div class='pair'> |
| <div class='command'>enter</div> |
| <div class='action'>Step through search results</div> |
| </div> |
| |
| <div class='pair'> |
| <div class='command'>f</div> |
| <div class='action'>Zoom into selection</div> |
| </div> |
| |
| <div class='pair'> |
| <div class='command'>z/0</div> |
| <div class='action'>Reset zoom and pan</div> |
| </div> |
| |
| <div class='pair'> |
| <div class='command'>g/G</div> |
| <div class='action'>Toggle 60hz grid</div> |
| </div> |
| |
| <div class='pair'> |
| <div class='command'>v</div> |
| <div class='action'>Highlight VSync</div> |
| </div> |
| |
| <div class='pair'> |
| <div class='command'>h</div> |
| <div class='action'>Toggle low/high details</div> |
| </div> |
| |
| <div class='pair'> |
| <div class='command'>m</div> |
| <div class='action'>Mark current selection</div> |
| </div> |
| |
| <div class='pair'> |
| <div class='command'>`</div> |
| <div class='action'>Show or hide the scripting console</div> |
| </div> |
| |
| <div class='pair'> |
| <div class='command'>?</div> |
| <div class='action'>Show help</div> |
| </div> |
| </div> |
| </div> |
| </template> |
| |
| <template id="metadata-btn-template"> |
| <div class="button view-metadata-button view-info-button">Metadata</div> |
| <div class="info-button-text metadata-dialog-text"></div> |
| </template> |
| |
| <template id="console-btn-template"> |
| <div class="button view-console-button">»</div> |
| </template> |
| |
| <script> |
| 'use strict'; |
| |
| /** |
| * @fileoverview View visualizes TRACE_EVENT events using the |
| * tv.c.Timeline component and adds in selection summary and control buttons. |
| */ |
| tv.exportTo('tv.c', function() { |
| var THIS_DOC = document.currentScript.ownerDocument; |
| |
| /** |
| * View |
| * @constructor |
| * @extends {HTMLUnknownElement} |
| */ |
| var TimelineView = tv.b.ui.define('x-timeline-view'); |
| |
| TimelineView.prototype = { |
| __proto__: HTMLUnknownElement.prototype, |
| |
| decorate: function() { |
| var node = tv.b.instantiateTemplate('#timeline-view-template', THIS_DOC); |
| this.appendChild(node); |
| |
| this.titleEl_ = this.querySelector('.title'); |
| this.leftControlsEl_ = this.querySelector('#left-controls'); |
| this.rightControlsEl_ = this.querySelector('#right-controls'); |
| this.collapsingControlsEl_ = this.querySelector('#collapsing-controls'); |
| this.sidePanelContainer_ = this.querySelector( |
| 'tv-c-side-panel-container'); |
| this.trackViewContainer_ = this.querySelector('track-view-container'); |
| |
| |
| this.findCtl_ = new TracingFindControl(); |
| this.findCtl_.controller = new tv.c.FindController(); |
| this.scriptingCtl_ = new TracingScriptingControl(); |
| this.scriptingCtl_.controller = document.createElement( |
| 'tv-c-scripting-controller'); |
| |
| this.showFlowEvents_ = false; |
| this.rightControls.appendChild(tv.b.ui.createCheckBox( |
| this, 'showFlowEvents', |
| 'tv.c.TimelineView.showFlowEvents', false, |
| 'Flow events')); |
| this.highlightVSync_ = false; |
| this.highlightVSyncCheckbox_ = tv.b.ui.createCheckBox( |
| this, 'highlightVSync', |
| 'tv.c.TimelineView.highlightVSync', false, |
| 'Highlight VSync'); |
| this.rightControls.appendChild(this.highlightVSyncCheckbox_); |
| |
| this.rightControls.appendChild(this.createMetadataButton_()); |
| this.rightControls.appendChild(this.findCtl_); |
| this.rightControls.appendChild(this.createConsoleButton_()); |
| this.rightControls.appendChild(this.createHelpButton_()); |
| this.collapsingControls.appendChild(this.scriptingCtl_); |
| |
| this.dragEl_ = this.querySelector('x-drag-handle'); |
| tv.b.ui.decorate(this.dragEl_, tv.b.ui.DragHandle); |
| |
| this.analysisEl_ = this.querySelector('#analysis'); |
| |
| this.addEventListener('requestSelectionChange', |
| this.onRequestSelectionChange_.bind(this)); |
| |
| // Bookkeeping. |
| this.onViewportChanged_ = this.onViewportChanged_.bind(this); |
| this.onSelectionChanged_ = this.onSelectionChanged_.bind(this); |
| document.addEventListener('keydown', this.onKeyDown_.bind(this), true); |
| document.addEventListener('keypress', this.onKeypress_.bind(this), true); |
| |
| this.dragEl_.target = this.analysisEl_; |
| |
| // State management on selection change. |
| this.selections_ = {}; |
| window.addEventListener('popstate', this.onPopState_.bind(this)); |
| }, |
| |
| updateDocumentFavicon: function() { |
| var hue; |
| if (!this.model) |
| hue = 'blue'; |
| else |
| hue = this.model.faviconHue; |
| |
| var faviconData = tv.c.FaviconsByHue[hue]; |
| if (faviconData === undefined) |
| faviconData = tv.c.FaviconsByHue['blue']; |
| |
| // Find link if its there |
| var link = document.head.querySelector('link[rel="shortcut icon"]'); |
| if (!link) { |
| link = document.createElement('link'); |
| link.rel = 'shortcut icon'; |
| document.head.appendChild(link); |
| } |
| link.href = faviconData; |
| }, |
| |
| get showFlowEvents() { |
| return this.showFlowEvents_; |
| }, |
| |
| set showFlowEvents(showFlowEvents) { |
| this.showFlowEvents_ = showFlowEvents; |
| if (!this.trackView_) |
| return; |
| this.trackView_.viewport.showFlowEvents = showFlowEvents; |
| }, |
| |
| get highlightVSync() { |
| return this.highlightVSync_; |
| }, |
| |
| set highlightVSync(highlightVSync) { |
| this.highlightVSync_ = highlightVSync; |
| if (!this.trackView_) |
| return; |
| this.trackView_.viewport.highlightVSync = highlightVSync; |
| }, |
| |
| createHelpButton_: function() { |
| var node = tv.b.instantiateTemplate('#help-btn-template', THIS_DOC); |
| var showEl = node.querySelector('.view-help-button'); |
| var helpTextEl = node.querySelector('.view-help-text'); |
| |
| var dlg = new tv.b.ui.Overlay(); |
| dlg.title = 'chrome://tracing Help'; |
| dlg.classList.add('view-help-overlay'); |
| dlg.appendChild(node); |
| |
| function onClick(e) { |
| dlg.visible = !dlg.visible; |
| |
| var mod = tv.isMac ? 'cmd ' : 'ctrl'; |
| var spans = helpTextEl.querySelectorAll('span.mod'); |
| for (var i = 0; i < spans.length; i++) { |
| spans[i].textContent = mod; |
| } |
| |
| // Stop event so it doesn't trigger new click listener on document. |
| e.stopPropagation(); |
| return false; |
| } |
| showEl.addEventListener('click', onClick.bind(this)); |
| |
| return showEl; |
| }, |
| |
| createConsoleButton_: function() { |
| var node = tv.b.instantiateTemplate('#console-btn-template', THIS_DOC); |
| var toggleEl = node.querySelector('.view-console-button'); |
| |
| function onClick(e) { |
| this.scriptingCtl_.toggleVisibility(); |
| e.stopPropagation(); |
| return false; |
| } |
| toggleEl.addEventListener('click', onClick.bind(this)); |
| |
| return toggleEl; |
| }, |
| |
| createMetadataButton_: function() { |
| var node = tv.b.instantiateTemplate('#metadata-btn-template', THIS_DOC); |
| var showEl = node.querySelector('.view-metadata-button'); |
| var textEl = node.querySelector('.info-button-text'); |
| |
| var dlg = new tv.b.ui.Overlay(); |
| dlg.title = 'Metadata for trace'; |
| dlg.classList.add('view-metadata-overlay'); |
| dlg.appendChild(node); |
| |
| function onClick(e) { |
| dlg.visible = true; |
| |
| var metadataStrings = []; |
| |
| var model = this.model; |
| for (var data in model.metadata) { |
| var meta = model.metadata[data]; |
| var name = JSON.stringify(meta.name); |
| var value = JSON.stringify(meta.value, undefined, ' '); |
| |
| metadataStrings.push(name + ': ' + value); |
| } |
| textEl.textContent = metadataStrings.join('\n'); |
| |
| e.stopPropagation(); |
| return false; |
| } |
| showEl.addEventListener('click', onClick.bind(this)); |
| |
| function updateVisibility() { |
| showEl.style.display = |
| (this.model && this.model.metadata.length) ? '' : 'none'; |
| } |
| var updateVisibility_ = updateVisibility.bind(this); |
| updateVisibility_(); |
| this.addEventListener('modelChange', updateVisibility_); |
| |
| return showEl; |
| }, |
| |
| get leftControls() { |
| return this.leftControlsEl_; |
| }, |
| |
| get rightControls() { |
| return this.rightControlsEl_; |
| }, |
| |
| get collapsingControls() { |
| return this.collapsingControlsEl_; |
| }, |
| |
| get viewTitle() { |
| return this.titleEl_.textContent.substring( |
| this.titleEl_.textContent.length - 2); |
| }, |
| |
| set viewTitle(text) { |
| if (text === undefined) { |
| this.titleEl_.textContent = ''; |
| this.titleEl_.hidden = true; |
| return; |
| } |
| this.titleEl_.hidden = false; |
| this.titleEl_.textContent = text; |
| }, |
| |
| get model() { |
| if (this.trackView_) |
| return this.trackView_.model; |
| return undefined; |
| }, |
| |
| set model(model) { |
| var modelInstanceChanged = model != this.model; |
| var modelValid = model && !model.bounds.isEmpty; |
| |
| // Remove old trackView if the model has completely changed. |
| if (modelInstanceChanged) { |
| this.trackViewContainer_.textContent = ''; |
| if (this.trackView_) { |
| this.trackView_.viewport.removeEventListener( |
| 'change', this.onViewportChanged_); |
| this.trackView_.removeEventListener( |
| 'selectionChange', this.onSelectionChanged_); |
| this.trackView_.detach(); |
| this.trackView_ = undefined; |
| this.findCtl_.controller.timeline = undefined; |
| this.scriptingCtl_.controller.timeline = undefined; |
| } |
| this.sidePanelContainer_.model = undefined; |
| } |
| |
| // Create new trackView if needed. |
| if (modelValid && !this.trackView_) { |
| this.trackView_ = new tv.c.TimelineTrackView(); |
| this.trackView_.focusElement = |
| this.focusElement_ ? this.focusElement_ : this.parentElement; |
| this.trackViewContainer_.appendChild(this.trackView_); |
| this.findCtl_.controller.timeline = this.trackView_; |
| this.scriptingCtl_.controller.timeline = this.trackView_; |
| this.trackView_.addEventListener( |
| 'selectionChange', this.onSelectionChanged_); |
| this.trackView_.viewport.addEventListener( |
| 'change', this.onViewportChanged_); |
| } |
| |
| // Set the model. |
| if (modelValid) { |
| this.trackView_.model = model; |
| this.sidePanelContainer_.model = model; |
| this.trackView_.viewport.showFlowEvents = this.showFlowEvents; |
| this.trackView_.viewport.highlightVSync = this.highlightVSync; |
| this.clearSelectionHistory_(); |
| } |
| tv.b.dispatchSimpleEvent(this, 'modelChange'); |
| |
| // Do things that are selection specific |
| if (modelInstanceChanged) { |
| this.onSelectionChanged_(); |
| this.onViewportChanged_(); |
| } |
| }, |
| |
| get timeline() { |
| return this.trackView_; |
| }, |
| |
| get settings() { |
| if (!this.settings_) |
| this.settings_ = new tv.b.Settings(); |
| return this.settings_; |
| }, |
| |
| /** |
| * Sets the element whose focus state will determine whether |
| * to respond to keybaord input. |
| */ |
| set focusElement(value) { |
| this.focusElement_ = value; |
| if (this.trackView_) |
| this.trackView_.focusElement = value; |
| }, |
| |
| /** |
| * @return {Element} The element whose focused state determines |
| * whether to respond to keyboard inputs. |
| * Defaults to the parent element. |
| */ |
| get focusElement() { |
| if (this.focusElement_) |
| return this.focusElement_; |
| return this.parentElement; |
| }, |
| |
| get listenToKeys_() { |
| if (!tv.b.ui.isElementAttachedToDocument(this)) |
| return; |
| if (document.activeElement.type === 'textarea') |
| return false; |
| if (this.sidePanelContainer_.activePanel && |
| this.sidePanelContainer_.activePanel.listeningToKeys) |
| return false; |
| if (!this.focusElement_) |
| return true; |
| if (this.focusElement.tabIndex >= 0) |
| return document.activeElement == this.focusElement; |
| return true; |
| }, |
| |
| onKeyDown_: function(e) { |
| if (!this.listenToKeys_) |
| return; |
| |
| if (e.keyCode === 27) { // ESC |
| this.focus(); |
| e.preventDefault(); |
| } |
| }, |
| |
| onKeypress_: function(e) { |
| if (!this.listenToKeys_) |
| return; |
| |
| // Shortcuts that *can* steal focus from the console and the filter text |
| // box. |
| switch (e.keyCode) { |
| case '`'.charCodeAt(0): |
| this.scriptingCtl_.toggleVisibility(); |
| if (!this.scriptingCtl_.hasFocus) |
| this.focus(); |
| e.preventDefault(); |
| break; |
| } |
| |
| if (this.scriptingCtl_.hasFocus) |
| return; |
| |
| // Shortcuts that *can* steal focus from the filter text box. |
| switch (e.keyCode) { |
| case '/'.charCodeAt(0): |
| if (this.findCtl_.hasFocus) |
| this.focus(); |
| else |
| this.findCtl_.focus(); |
| e.preventDefault(); |
| break; |
| case '?'.charCodeAt(0): |
| this.querySelector('.view-help-button').click(); |
| e.preventDefault(); |
| break; |
| } |
| |
| if (this.findCtl_.hasFocus) |
| return; |
| |
| // Shortcuts that *can't* steal focus from the filter text box or the |
| // console. |
| switch (e.keyCode) { |
| case 'v'.charCodeAt(0): |
| this.toggleHighlightVSync_(); |
| e.preventDefault(); |
| break; |
| } |
| }, |
| |
| onSelectionChanged_: function(e) { |
| var oldScrollTop = this.trackViewContainer_.scrollTop; |
| |
| var selection = this.trackView_ ? |
| this.trackView_.selectionOfInterest : |
| new tv.c.Selection(); |
| this.analysisEl_.selection = selection; |
| this.trackViewContainer_.scrollTop = oldScrollTop; |
| this.sidePanelContainer_.selection = selection; |
| }, |
| |
| onRequestSelectionChange_: function(e) { |
| // Save the selection so that when back button is pressed, |
| // it could be retrieved. |
| this.selections_[e.selection.guid] = e.selection; |
| var state = { |
| selection_guid: e.selection.guid |
| }; |
| window.history.pushState(state, ''); |
| |
| this.trackView_.selection = e.selection; |
| e.stopPropagation(); |
| }, |
| |
| onPopState_: function(e) { |
| if (e.state === null) |
| return; |
| |
| var selection = this.selections_[e.state.selection_guid]; |
| if (selection) |
| this.trackView_.selection = selection; |
| e.stopPropagation(); |
| }, |
| |
| clearSelectionHistory_: function() { |
| this.selections_ = {}; |
| }, |
| |
| onViewportChanged_: function(e) { |
| var spc = this.sidePanelContainer_; |
| if (!this.trackView_) { |
| spc.rangeOfInterest.reset(); |
| return; |
| } |
| |
| var vr = this.trackView_.viewport.interestRange.asRangeObject(); |
| if (!spc.rangeOfInterest.equals(vr)) |
| spc.rangeOfInterest = vr; |
| }, |
| |
| toggleHighlightVSync_: function() { |
| this.highlightVSyncCheckbox_.checked = |
| !this.highlightVSyncCheckbox_.checked; |
| } |
| }; |
| |
| return { |
| TimelineView: TimelineView |
| }; |
| }); |
| </script> |