1f53172d9694ad0d34ad4476209e712d5f7cf242
[platform/framework/web/crosswalk.git] / src / third_party / trace-viewer / src / tracing / timeline_view.js
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 'use strict';
6
7 /**
8  * @fileoverview View visualizes TRACE_EVENT events using the
9  * tracing.Timeline component and adds in selection summary and control buttons.
10  */
11 base.requireStylesheet('ui.trace_viewer');
12 base.requireStylesheet('tracing.timeline_view');
13 base.requireTemplate('tracing.timeline_view');
14
15 base.require('base.utils');
16 base.require('base.settings');
17 base.require('tracing.analysis.analysis_view');
18 base.require('tracing.find_control');
19 base.require('tracing.timeline_track_view');
20 base.require('ui.dom_helpers');
21 base.require('ui.overlay');
22 base.require('ui.drag_handle');
23
24 base.require('tracing.analysis.cpu_slice_view');
25 base.require('tracing.analysis.thread_time_slice_view');
26
27 base.exportTo('tracing', function() {
28
29   /**
30    * View
31    * @constructor
32    * @extends {HTMLUnknownElement}
33    */
34   var TimelineView = ui.define('x-timeline-view');
35
36   TimelineView.prototype = {
37     __proto__: HTMLUnknownElement.prototype,
38
39     decorate: function() {
40       var node = base.instantiateTemplate('#timeline-view-template');
41       this.appendChild(node);
42
43       this.titleEl_ = this.querySelector('.title');
44       this.leftControlsEl_ = this.querySelector('#left-controls');
45       this.rightControlsEl_ = this.querySelector('#right-controls');
46       this.timelineContainer_ = this.querySelector('.container');
47
48       this.findCtl_ = new tracing.FindControl();
49       this.findCtl_.controller = new tracing.FindController();
50
51       this.showFlowEvents_ = false;
52       this.rightControls.appendChild(ui.createCheckBox(
53           this, 'showFlowEvents',
54           'tracing.TimelineView.showFlowEvents', false,
55           'Flow events'));
56
57       this.rightControls.appendChild(this.createMetadataButton_());
58       this.rightControls.appendChild(this.findCtl_);
59       this.rightControls.appendChild(this.createHelpButton_());
60
61       this.dragEl_ = new ui.DragHandle();
62       this.appendChild(this.dragEl_);
63
64       this.analysisEl_ = new tracing.analysis.AnalysisView();
65       this.appendChild(this.analysisEl_);
66       this.addEventListener('requestSelectionChange',
67                             this.onRequestSelectionChange_.bind(this));
68
69       // Bookkeeping.
70       this.onSelectionChanged_ = this.onSelectionChanged_.bind(this);
71       document.addEventListener('keydown', this.onKeyDown_.bind(this), true);
72       document.addEventListener('keypress', this.onKeypress_.bind(this), true);
73
74       this.dragEl_.target = this.analysisEl_;
75     },
76
77     get showFlowEvents() {
78       return this.showFlowEvents_;
79     },
80
81     set showFlowEvents(showFlowEvents) {
82       this.showFlowEvents_ = showFlowEvents;
83       if (!this.timeline_)
84         return;
85       this.timeline_.viewport.showFlowEvents = showFlowEvents;
86     },
87
88     createHelpButton_: function() {
89       var node = base.instantiateTemplate('#help-btn-template');
90       var showEl = node.querySelector('.view-help-button');
91       var helpTextEl = node.querySelector('.view-help-text');
92
93       var dlg = new ui.Overlay();
94       dlg.title = 'chrome://tracing Help';
95       dlg.classList.add('view-help-overlay');
96       dlg.appendChild(node);
97
98       function onClick(e) {
99         dlg.visible = !dlg.visible;
100
101         var mod = base.isMac ? 'cmd ' : 'ctrl';
102         var spans = helpTextEl.querySelectorAll('span.mod');
103         for (var i = 0; i < spans.length; i++) {
104           spans[i].textContent = mod;
105         }
106
107         // Stop event so it doesn't trigger new click listener on document.
108         e.stopPropagation();
109         return false;
110       }
111       showEl.addEventListener('click', onClick.bind(this));
112
113       return showEl;
114     },
115
116     createMetadataButton_: function() {
117       var node = base.instantiateTemplate('#metadata-btn-template');
118       var showEl = node.querySelector('.view-metadata-button');
119       var textEl = node.querySelector('.info-button-text');
120
121       var dlg = new ui.Overlay();
122       dlg.title = 'Metadata for trace';
123       dlg.classList.add('view-metadata-overlay');
124       dlg.appendChild(node);
125
126       function onClick(e) {
127         dlg.visible = true;
128
129         var metadataStrings = [];
130
131         var model = this.model;
132         for (var data in model.metadata) {
133           var meta = model.metadata[data];
134           var name = JSON.stringify(meta.name);
135           var value = JSON.stringify(meta.value, undefined, ' ');
136
137           metadataStrings.push(name + ': ' + value);
138         }
139         textEl.textContent = metadataStrings.join('\n');
140
141         e.stopPropagation();
142         return false;
143       }
144       showEl.addEventListener('click', onClick.bind(this));
145
146       function updateVisibility() {
147         showEl.style.display =
148             (this.model && this.model.metadata.length) ? '' : 'none';
149       }
150       var updateVisibility_ = updateVisibility.bind(this);
151       updateVisibility_();
152       this.addEventListener('modelChange', updateVisibility_);
153
154       return showEl;
155     },
156
157     get leftControls() {
158       return this.leftControlsEl_;
159     },
160
161     get rightControls() {
162       return this.rightControlsEl_;
163     },
164
165     get viewTitle() {
166       return this.titleEl_.textContent.substring(
167           this.titleEl_.textContent.length - 2);
168     },
169
170     set viewTitle(text) {
171       if (text === undefined) {
172         this.titleEl_.textContent = '';
173         this.titleEl_.hidden = true;
174         return;
175       }
176       this.titleEl_.hidden = false;
177       this.titleEl_.textContent = text;
178     },
179
180     get model() {
181       if (this.timeline_)
182         return this.timeline_.model;
183       return undefined;
184     },
185
186     set model(model) {
187       var modelInstanceChanged = model != this.model;
188       var modelValid = model && !model.bounds.isEmpty;
189
190       // Remove old timeline if the model has completely changed.
191       if (modelInstanceChanged) {
192         this.timelineContainer_.textContent = '';
193         if (this.timeline_) {
194           this.timeline_.removeEventListener(
195               'selectionChange', this.onSelectionChanged_);
196           this.timeline_.detach();
197           this.timeline_ = undefined;
198           this.findCtl_.controller.timeline = undefined;
199         }
200       }
201
202       // Create new timeline if needed.
203       if (modelValid && !this.timeline_) {
204         this.timeline_ = new tracing.TimelineTrackView();
205         this.timeline_.focusElement =
206             this.focusElement_ ? this.focusElement_ : this.parentElement;
207         this.timelineContainer_.appendChild(this.timeline_);
208         this.findCtl_.controller.timeline = this.timeline_;
209         this.timeline_.addEventListener(
210             'selectionChange', this.onSelectionChanged_);
211         this.timeline_.viewport.showFlowEvents = this.showFlowEvents;
212         this.analysisEl_.clearSelectionHistory();
213       }
214
215       // Set the model.
216       if (modelValid)
217         this.timeline_.model = model;
218       base.dispatchSimpleEvent(this, 'modelChange');
219
220       // Do things that are selection specific
221       if (modelInstanceChanged)
222         this.onSelectionChanged_();
223     },
224
225     get timeline() {
226       return this.timeline_;
227     },
228
229     get settings() {
230       if (!this.settings_)
231         this.settings_ = new base.Settings();
232       return this.settings_;
233     },
234
235     /**
236      * Sets the element whose focus state will determine whether
237      * to respond to keybaord input.
238      */
239     set focusElement(value) {
240       this.focusElement_ = value;
241       if (this.timeline_)
242         this.timeline_.focusElement = value;
243     },
244
245     /**
246      * @return {Element} The element whose focused state determines
247      * whether to respond to keyboard inputs.
248      * Defaults to the parent element.
249      */
250     get focusElement() {
251       if (this.focusElement_)
252         return this.focusElement_;
253       return this.parentElement;
254     },
255
256     /**
257      * @return {boolean} Whether the current timeline is attached to the
258      * document.
259      */
260     get isAttachedToDocument_() {
261       var cur = this;
262       while (cur.parentNode)
263         cur = cur.parentNode;
264       return cur == this.ownerDocument;
265     },
266
267     get listenToKeys_() {
268       if (!this.isAttachedToDocument_)
269         return;
270       if (!this.focusElement_)
271         return true;
272       if (this.focusElement.tabIndex >= 0)
273         return document.activeElement == this.focusElement;
274       return true;
275     },
276
277     onKeyDown_: function(e) {
278       if (!this.listenToKeys_)
279         return;
280
281       if (e.keyCode === 27) { // ESC
282         this.focus();
283         e.preventDefault();
284       }
285     },
286
287     onKeypress_: function(e) {
288       if (!this.listenToKeys_)
289         return;
290
291       if (e.keyCode === '/'.charCodeAt(0)) {
292         if (this.findCtl_.hasFocus())
293           this.focus();
294         else
295           this.findCtl_.focus();
296         e.preventDefault();
297       } else if (e.keyCode === '?'.charCodeAt(0)) {
298         this.querySelector('.view-help-button').click();
299         e.preventDefault();
300       }
301     },
302
303     beginFind: function() {
304       if (this.findInProgress_)
305         return;
306       this.findInProgress_ = true;
307       var dlg = tracing.FindControl();
308       dlg.controller = new tracing.FindController();
309       dlg.controller.timeline = this.timeline;
310       dlg.visible = true;
311       dlg.addEventListener('close', function() {
312         this.findInProgress_ = false;
313       }.bind(this));
314       dlg.addEventListener('findNext', function() {
315       });
316       dlg.addEventListener('findPrevious', function() {
317       });
318     },
319
320     onSelectionChanged_: function(e) {
321       var oldScrollTop = this.timelineContainer_.scrollTop;
322
323       var selection = this.timeline_ ?
324           this.timeline_.selectionOfInterest :
325           new tracing.Selection();
326       this.analysisEl_.selection = selection;
327       this.timelineContainer_.scrollTop = oldScrollTop;
328     },
329
330     onRequestSelectionChange_: function(e) {
331       this.timeline_.selection = e.selection;
332       e.stopPropagation();
333     }
334   };
335
336   return {
337     TimelineView: TimelineView
338   };
339 });