// 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. 'use strict'; /** * @fileoverview View visualizes TRACE_EVENT events using the * tracing.Timeline component and adds in selection summary and control buttons. */ base.requireStylesheet('tracing.timeline_view'); base.requireTemplate('tracing.timeline_view'); base.require('base.utils'); base.require('base.settings'); base.require('tracing.analysis.analysis_view'); base.require('tracing.category_filter_dialog'); base.require('tracing.filter'); base.require('tracing.find_control'); base.require('tracing.timeline_track_view'); base.require('ui.overlay'); base.require('ui.drag_handle'); base.exportTo('tracing', function() { /** * View * @constructor * @extends {HTMLDivElement} */ var TimelineView = ui.define('div'); TimelineView.prototype = { __proto__: HTMLDivElement.prototype, decorate: function() { this.classList.add('timeline-view'); var node = base.instantiateTemplate('#timeline-view-template'); this.appendChild(node); this.titleEl_ = this.querySelector('.title'); this.leftControlsEl_ = this.querySelector('#left-controls'); this.rightControlsEl_ = this.querySelector('#right-controls'); this.timelineContainer_ = this.querySelector('.container'); this.categoryFilterButton_ = this.createCategoryFilterButton_(); this.categoryFilterButton_.callback = this.updateCategoryFilter_.bind(this); this.findCtl_ = new tracing.FindControl(); this.findCtl_.controller = new tracing.FindController(); this.rightControls.appendChild(this.createImportErrorsButton_()); this.rightControls.appendChild(this.categoryFilterButton_); this.rightControls.appendChild(this.createMetadataButton_()); this.rightControls.appendChild(this.findCtl_); this.rightControls.appendChild(this.createHelpButton_()); this.dragEl_ = new ui.DragHandle(); this.appendChild(this.dragEl_); this.analysisEl_ = new tracing.analysis.AnalysisView(); this.analysisEl_.addEventListener( 'requestSelectionChange', this.onRequestSelectionChange_.bind(this)); this.appendChild(this.analysisEl_); // Bookkeeping. this.onSelectionChanged_ = this.onSelectionChanged_.bind(this); document.addEventListener('keypress', this.onKeypress_.bind(this), true); this.dragEl_.target = this.analysisEl_; }, createImportErrorsButton_: function() { var node = base.instantiateTemplate('#import-errors-btn-template'); var showEl = node.querySelector('.view-import-errors-button'); var containerEl = node.querySelector('.info-button-container'); var textEl = containerEl.querySelector('.info-button-text'); var dlg = new ui.Overlay(); dlg.classList.add('view-import-errors-overlay'); dlg.obeyCloseEvents = true; dlg.appendChild(containerEl); function onClick() { dlg.visible = true; textEl.textContent = this.model.importErrors.join('\n'); } showEl.addEventListener('click', onClick.bind(this)); function updateVisibility() { showEl.style.display = (this.model && this.model.importErrors.length) ? '' : 'none'; } var updateVisibility_ = updateVisibility.bind(this); updateVisibility_(); this.addEventListener('modelChange', updateVisibility_); return showEl; }, updateCategoryFilter_: function(categories) { if (!this.timeline_) return; this.timeline_.categoryFilter = new tracing.CategoryFilter(categories); }, createCategoryFilterButton_: function() { var node = base.instantiateTemplate('#category-filter-btn-template'); var showEl = node.querySelector('.view-info-button'); function onClick() { var dlg = new tracing.CategoryFilterDialog(); dlg.categories = this.model.categories; dlg.settings_key = 'categories'; dlg.settingUpdatedCallback = this.updateCategoryFilter_.bind(this); dlg.visible = true; } showEl.addEventListener('click', onClick.bind(this)); function updateVisibility() { showEl.style.display = this.model ? '' : 'none'; } var updateVisibility_ = updateVisibility.bind(this); updateVisibility_(); this.addEventListener('modelChange', updateVisibility_); return showEl; }, createHelpButton_: function() { var node = base.instantiateTemplate('#help-btn-template'); var showEl = node.querySelector('.view-help-button'); var helpTextEl = node.querySelector('.view-help-text'); var dlg = new ui.Overlay(); dlg.classList.add('view-help-overlay'); dlg.obeyCloseEvents = true; dlg.additionalCloseKeyCodes.push('?'.charCodeAt(0)); dlg.appendChild(helpTextEl); function onClick(e) { dlg.visible = true; helpTextEl.textContent = this.timeline_ ? this.timeline_.keyHelp : 'No content loaded. For interesting help, load something.'; // Stop event so it doesn't trigger new click listener on document. e.stopPropagation(); return false; } showEl.addEventListener('click', onClick.bind(this)); return showEl; }, createMetadataButton_: function() { var node = base.instantiateTemplate('#metadata-btn-template'); var showEl = node.querySelector('.view-metadata-button'); var containerEl = node.querySelector('.info-button-container'); var textEl = containerEl.querySelector('.info-button-text'); var dlg = new ui.Overlay(); dlg.classList.add('view-metadata-overlay'); dlg.obeyCloseEvents = true; dlg.appendChild(containerEl); function onClick() { 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'); } 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 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; }, set traceData(traceData) { this.model = new tracing.TraceModel(traceData); }, get model() { if (this.timeline_) return this.timeline_.model; return undefined; }, set model(model) { var modelInstanceChanged = model != this.model; var modelValid = model && !model.bounds.isEmpty; // Remove old timeline if the model has completely changed. if (modelInstanceChanged) { this.timelineContainer_.textContent = ''; if (this.timeline_) { this.timeline_.removeEventListener( 'selectionChange', this.onSelectionChanged_); this.timeline_.detach(); this.timeline_ = undefined; this.findCtl_.controller.timeline = undefined; } } // Create new timeline if needed. if (modelValid && !this.timeline_) { this.timeline_ = new tracing.TimelineTrackView(); this.timeline_.focusElement = this.focusElement_ ? this.focusElement_ : this.parentElement; this.timelineContainer_.appendChild(this.timeline_); this.findCtl_.controller.timeline = this.timeline_; this.timeline_.addEventListener( 'selectionChange', this.onSelectionChanged_); this.analysisEl_.clearSelectionHistory(); } // Set the model. if (modelValid) this.timeline_.model = model; base.dispatchSimpleEvent(this, 'modelChange'); // Do things that are selection specific if (modelInstanceChanged) this.onSelectionChanged_(); }, get timeline() { return this.timeline_; }, get settings() { if (!this.settings_) this.settings_ = new base.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.timeline_) this.timeline_.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; }, /** * @return {boolean} Whether the current timeline is attached to the * document. */ get isAttachedToDocument_() { var cur = this; while (cur.parentNode) cur = cur.parentNode; return cur == this.ownerDocument; }, get listenToKeys_() { if (!this.isAttachedToDocument_) return; if (!this.focusElement_) return true; if (this.focusElement.tabIndex >= 0) return document.activeElement == this.focusElement; return true; }, onKeypress_: function(e) { if (!this.listenToKeys_) return; if (event.keyCode == '/'.charCodeAt(0)) { // / key this.findCtl_.focus(); event.preventDefault(); return; } else if (e.keyCode == '?'.charCodeAt(0)) { this.querySelector('.view-help-button').click(); e.preventDefault(); } }, beginFind: function() { if (this.findInProgress_) return; this.findInProgress_ = true; var dlg = tracing.FindControl(); dlg.controller = new tracing.FindController(); dlg.controller.timeline = this.timeline; dlg.visible = true; dlg.addEventListener('close', function() { this.findInProgress_ = false; }.bind(this)); dlg.addEventListener('findNext', function() { }); dlg.addEventListener('findPrevious', function() { }); }, onSelectionChanged_: function(e) { var oldScrollTop = this.timelineContainer_.scrollTop; var selection = this.timeline_ ? this.timeline_.selection : new tracing.Selection(); this.analysisEl_.selection = selection; this.timelineContainer_.scrollTop = oldScrollTop; }, onRequestSelectionChange_: function(e) { this.timeline_.selection = e.selection; e.stopPropagation(); } }; return { TimelineView: TimelineView }; });