Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(150)

Side by Side Diff: chrome/browser/resources/tracing/timeline.js

Issue 10543144: Remove old tracing code now that it has moved to third_party. (Closed) Base URL: http://git.chromium.org/chromium/src.git@master
Patch Set: Created 8 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2012 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 Interactive visualizaiton of TimelineModel objects
9 * based loosely on gantt charts. Each thread in the TimelineModel is given a
10 * set of TimelineTracks, one per subrow in the thread. The Timeline class
11 * acts as a controller, creating the individual tracks, while TimelineTracks
12 * do actual drawing.
13 *
14 * Visually, the Timeline produces (prettier) visualizations like the following:
15 * Thread1: AAAAAAAAAA AAAAA
16 * BBBB BB
17 * Thread2: CCCCCC CCCCC
18 *
19 */
20 cr.define('tracing', function() {
21
22 /**
23 * The TimelineViewport manages the transform used for navigating
24 * within the timeline. It is a simple transform:
25 * x' = (x+pan) * scale
26 *
27 * The timeline code tries to avoid directly accessing this transform,
28 * instead using this class to do conversion between world and view space,
29 * as well as the math for centering the viewport in various interesting
30 * ways.
31 *
32 * @constructor
33 * @extends {cr.EventTarget}
34 */
35 function TimelineViewport(parentEl) {
36 this.parentEl_ = parentEl;
37 this.scaleX_ = 1;
38 this.panX_ = 0;
39 this.gridTimebase_ = 0;
40 this.gridStep_ = 1000 / 60;
41 this.gridEnabled_ = false;
42 this.hasCalledSetupFunction_ = false;
43
44 this.onResizeBoundToThis_ = this.onResize_.bind(this);
45
46 // The following code uses an interval to detect when the parent element
47 // is attached to the document. That is a trigger to run the setup function
48 // and install a resize listener.
49 this.checkForAttachInterval_ = setInterval(
50 this.checkForAttach_.bind(this), 250);
51 }
52
53 TimelineViewport.prototype = {
54 __proto__: cr.EventTarget.prototype,
55
56 /**
57 * Allows initialization of the viewport when the viewport's parent element
58 * has been attached to the document and given a size.
59 * @param {Function} fn Function to call when the viewport can be safely
60 * initialized.
61 */
62 setWhenPossible: function(fn) {
63 this.pendingSetFunction_ = fn;
64 },
65
66 /**
67 * @return {boolean} Whether the current timeline is attached to the
68 * document.
69 */
70 get isAttachedToDocument_() {
71 var cur = this.parentEl_;
72 while (cur.parentNode)
73 cur = cur.parentNode;
74 return cur == this.parentEl_.ownerDocument;
75 },
76
77 onResize_: function() {
78 this.dispatchChangeEvent();
79 },
80
81 /**
82 * Checks whether the parentNode is attached to the document.
83 * When it is, it installs the iframe-based resize detection hook
84 * and then runs the pendingSetFunction_, if present.
85 */
86 checkForAttach_: function() {
87 if (!this.isAttachedToDocument_ || this.clientWidth == 0)
88 return;
89
90 if (!this.iframe_) {
91 this.iframe_ = document.createElement('iframe');
92 this.iframe_.style.cssText =
93 'position:absolute;width:100%;height:0;border:0;visibility:hidden;';
94 this.parentEl_.appendChild(this.iframe_);
95
96 this.iframe_.contentWindow.addEventListener('resize',
97 this.onResizeBoundToThis_);
98 }
99
100 var curSize = this.clientWidth + 'x' + this.clientHeight;
101 if (this.pendingSetFunction_) {
102 this.lastSize_ = curSize;
103 this.pendingSetFunction_();
104 this.pendingSetFunction_ = undefined;
105 }
106
107 window.clearInterval(this.checkForAttachInterval_);
108 this.checkForAttachInterval_ = undefined;
109 },
110
111 /**
112 * Fires the change event on this viewport. Used to notify listeners
113 * to redraw when the underlying model has been mutated.
114 */
115 dispatchChangeEvent: function() {
116 cr.dispatchSimpleEvent(this, 'change');
117 },
118
119 detach: function() {
120 if (this.checkForAttachInterval_) {
121 window.clearInterval(this.checkForAttachInterval_);
122 this.checkForAttachInterval_ = undefined;
123 }
124 this.iframe_.removeEventListener('resize', this.onResizeBoundToThis_);
125 this.parentEl_.removeChild(this.iframe_);
126 },
127
128 get scaleX() {
129 return this.scaleX_;
130 },
131 set scaleX(s) {
132 var changed = this.scaleX_ != s;
133 if (changed) {
134 this.scaleX_ = s;
135 this.dispatchChangeEvent();
136 }
137 },
138
139 get panX() {
140 return this.panX_;
141 },
142 set panX(p) {
143 var changed = this.panX_ != p;
144 if (changed) {
145 this.panX_ = p;
146 this.dispatchChangeEvent();
147 }
148 },
149
150 setPanAndScale: function(p, s) {
151 var changed = this.scaleX_ != s || this.panX_ != p;
152 if (changed) {
153 this.scaleX_ = s;
154 this.panX_ = p;
155 this.dispatchChangeEvent();
156 }
157 },
158
159 xWorldToView: function(x) {
160 return (x + this.panX_) * this.scaleX_;
161 },
162
163 xWorldVectorToView: function(x) {
164 return x * this.scaleX_;
165 },
166
167 xViewToWorld: function(x) {
168 return (x / this.scaleX_) - this.panX_;
169 },
170
171 xViewVectorToWorld: function(x) {
172 return x / this.scaleX_;
173 },
174
175 xPanWorldPosToViewPos: function(worldX, viewX, viewWidth) {
176 if (typeof viewX == 'string') {
177 if (viewX == 'left') {
178 viewX = 0;
179 } else if (viewX == 'center') {
180 viewX = viewWidth / 2;
181 } else if (viewX == 'right') {
182 viewX = viewWidth - 1;
183 } else {
184 throw Error('unrecognized string for viewPos. left|center|right');
185 }
186 }
187 this.panX = (viewX / this.scaleX_) - worldX;
188 },
189
190 xPanWorldRangeIntoView: function(worldMin, worldMax, viewWidth) {
191 if (this.xWorldToView(worldMin) < 0)
192 this.xPanWorldPosToViewPos(worldMin, 'left', viewWidth);
193 else if (this.xWorldToView(worldMax) > viewWidth)
194 this.xPanWorldPosToViewPos(worldMax, 'right', viewWidth);
195 },
196
197 xSetWorldRange: function(worldMin, worldMax, viewWidth) {
198 var worldRange = worldMax - worldMin;
199 var scaleX = viewWidth / worldRange;
200 var panX = -worldMin;
201 this.setPanAndScale(panX, scaleX);
202 },
203
204 get gridEnabled() {
205 return this.gridEnabled_;
206 },
207
208 set gridEnabled(enabled) {
209 if (this.gridEnabled_ == enabled)
210 return;
211 this.gridEnabled_ = enabled && true;
212 this.dispatchChangeEvent();
213 },
214
215 get gridTimebase() {
216 return this.gridTimebase_;
217 },
218
219 set gridTimebase(timebase) {
220 if (this.gridTimebase_ == timebase)
221 return;
222 this.gridTimebase_ = timebase;
223 cr.dispatchSimpleEvent(this, 'change');
224 },
225
226 get gridStep() {
227 return this.gridStep_;
228 },
229
230 applyTransformToCanavs: function(ctx) {
231 ctx.transform(this.scaleX_, 0, 0, 1, this.panX_ * this.scaleX_, 0);
232 }
233 };
234
235 function TimelineSelectionSliceHit(track, slice) {
236 this.track = track;
237 this.slice = slice;
238 }
239 TimelineSelectionSliceHit.prototype = {
240 get selected() {
241 return this.slice.selected;
242 },
243 set selected(v) {
244 this.slice.selected = v;
245 }
246 };
247
248 function TimelineSelectionCounterSampleHit(track, counter, sampleIndex) {
249 this.track = track;
250 this.counter = counter;
251 this.sampleIndex = sampleIndex;
252 }
253 TimelineSelectionCounterSampleHit.prototype = {
254 get selected() {
255 return this.track.selectedSamples[this.sampleIndex] == true;
256 },
257 set selected(v) {
258 if (v)
259 this.track.selectedSamples[this.sampleIndex] = true;
260 else
261 this.track.selectedSamples[this.sampleIndex] = false;
262 this.track.invalidate();
263 }
264 };
265
266
267 /**
268 * Represents a selection within a Timeline and its associated set of tracks.
269 * @constructor
270 */
271 function TimelineSelection() {
272 this.range_dirty_ = true;
273 this.range_ = {};
274 this.length_ = 0;
275 }
276 TimelineSelection.prototype = {
277 __proto__: Object.prototype,
278
279 get range() {
280 if (this.range_dirty_) {
281 var wmin = Infinity;
282 var wmax = -wmin;
283 for (var i = 0; i < this.length_; i++) {
284 var hit = this[i];
285 if (hit.slice) {
286 wmin = Math.min(wmin, hit.slice.start);
287 wmax = Math.max(wmax, hit.slice.end);
288 }
289 }
290 this.range_ = {
291 min: wmin,
292 max: wmax
293 };
294 this.range_dirty_ = false;
295 }
296 return this.range_;
297 },
298
299 get duration() {
300 return this.range.max - this.range.min;
301 },
302
303 get length() {
304 return this.length_;
305 },
306
307 clear: function() {
308 for (var i = 0; i < this.length_; ++i)
309 delete this[i];
310 this.length_ = 0;
311 this.range_dirty_ = true;
312 },
313
314 push_: function(hit) {
315 this[this.length_++] = hit;
316 this.range_dirty_ = true;
317 return hit;
318 },
319
320 addSlice: function(track, slice) {
321 return this.push_(new TimelineSelectionSliceHit(track, slice));
322 },
323
324 addCounterSample: function(track, counter, sampleIndex) {
325 return this.push_(
326 new TimelineSelectionCounterSampleHit(
327 track, counter, sampleIndex));
328 },
329
330 subSelection: function(index, count) {
331 count = count || 1;
332
333 var selection = new TimelineSelection();
334 selection.range_dirty_ = true;
335 if (index < 0 || index + count > this.length_)
336 throw 'Index out of bounds';
337
338 for (var i = index; i < index + count; i++)
339 selection.push_(this[i]);
340
341 return selection;
342 },
343
344 getCounterSampleHits: function() {
345 var selection = new TimelineSelection();
346 for (var i = 0; i < this.length_; i++)
347 if (this[i] instanceof TimelineSelectionCounterSampleHit)
348 selection.push_(this[i]);
349 return selection;
350 },
351
352 getSliceHits: function() {
353 var selection = new TimelineSelection();
354 for (var i = 0; i < this.length_; i++)
355 if (this[i] instanceof TimelineSelectionSliceHit)
356 selection.push_(this[i]);
357 return selection;
358 },
359
360 map: function(fn) {
361 for (var i = 0; i < this.length_; i++)
362 fn(this[i]);
363 },
364
365 /**
366 * Helper for selection previous or next.
367 * @param {boolean} forwardp If true, select one forward (next).
368 * Else, select previous.
369 * @return {boolean} true if current selection changed.
370 */
371 getShiftedSelection: function(offset) {
372 var newSelection = new TimelineSelection();
373 for (var i = 0; i < this.length_; i++) {
374 var hit = this[i];
375 hit.track.addItemNearToProvidedHitToSelection(
376 hit, offset, newSelection);
377 }
378
379 if (newSelection.length == 0)
380 return undefined;
381 return newSelection;
382 },
383 };
384
385 /**
386 * Renders a TimelineModel into a div element, making one
387 * TimelineTrack for each subrow in each thread of the model, managing
388 * overall track layout, and handling user interaction with the
389 * viewport.
390 *
391 * @constructor
392 * @extends {HTMLDivElement}
393 */
394 var Timeline = cr.ui.define('div');
395
396 Timeline.prototype = {
397 __proto__: HTMLDivElement.prototype,
398
399 model_: null,
400
401 decorate: function() {
402 this.classList.add('timeline');
403
404 this.viewport_ = new TimelineViewport(this);
405
406 this.tracks_ = this.ownerDocument.createElement('div');
407 this.appendChild(this.tracks_);
408
409 this.dragBox_ = this.ownerDocument.createElement('div');
410 this.dragBox_.className = 'timeline-drag-box';
411 this.appendChild(this.dragBox_);
412 this.hideDragBox_();
413
414 this.bindEventListener_(document, 'keypress', this.onKeypress_, this);
415 this.bindEventListener_(document, 'keydown', this.onKeydown_, this);
416 this.bindEventListener_(document, 'mousedown', this.onMouseDown_, this);
417 this.bindEventListener_(document, 'mousemove', this.onMouseMove_, this);
418 this.bindEventListener_(document, 'mouseup', this.onMouseUp_, this);
419 this.bindEventListener_(document, 'dblclick', this.onDblClick_, this);
420
421 this.lastMouseViewPos_ = {x: 0, y: 0};
422
423 this.selection_ = new TimelineSelection();
424 },
425
426 /**
427 * Wraps the standard addEventListener but automatically binds the provided
428 * func to the provided target, tracking the resulting closure. When detach
429 * is called, these listeners will be automatically removed.
430 */
431 bindEventListener_: function(object, event, func, target) {
432 if (!this.boundListeners_)
433 this.boundListeners_ = [];
434 var boundFunc = func.bind(target);
435 this.boundListeners_.push({object: object,
436 event: event,
437 boundFunc: boundFunc});
438 object.addEventListener(event, boundFunc);
439 },
440
441 detach: function() {
442 for (var i = 0; i < this.tracks_.children.length; i++)
443 this.tracks_.children[i].detach();
444
445 for (var i = 0; i < this.boundListeners_.length; i++) {
446 var binding = this.boundListeners_[i];
447 binding.object.removeEventListener(binding.event, binding.boundFunc);
448 }
449 this.boundListeners_ = undefined;
450 this.viewport_.detach();
451 },
452
453 get viewport() {
454 return this.viewport_;
455 },
456
457 get model() {
458 return this.model_;
459 },
460
461 set model(model) {
462 if (!model)
463 throw Error('Model cannot be null');
464 if (this.model) {
465 throw Error('Cannot set model twice.');
466 }
467 this.model_ = model;
468
469 // Figure out all the headings.
470 var allHeadings = [];
471 model.getAllThreads().forEach(function(t) {
472 allHeadings.push(t.userFriendlyName);
473 });
474 model.getAllCounters().forEach(function(c) {
475 allHeadings.push(c.name);
476 });
477 model.getAllCpus().forEach(function(c) {
478 allHeadings.push('CPU ' + c.cpuNumber);
479 });
480
481 // Figure out the maximum heading size.
482 var maxHeadingWidth = 0;
483 var measuringStick = new tracing.MeasuringStick();
484 var headingEl = document.createElement('div');
485 headingEl.style.position = 'fixed';
486 headingEl.className = 'timeline-canvas-based-track-title';
487 allHeadings.forEach(function(text) {
488 headingEl.textContent = text + ':__';
489 var w = measuringStick.measure(headingEl).width;
490 // Limit heading width to 300px.
491 if (w > 300)
492 w = 300;
493 if (w > maxHeadingWidth)
494 maxHeadingWidth = w;
495 });
496 maxHeadingWidth = maxHeadingWidth + 'px';
497
498 // Reset old tracks.
499 for (var i = 0; i < this.tracks_.children.length; i++)
500 this.tracks_.children[i].detach();
501 this.tracks_.textContent = '';
502
503 // Add the viewport track
504 var viewportTrack = new tracing.TimelineViewportTrack();
505 viewportTrack.headingWidth = maxHeadingWidth;
506 viewportTrack.viewport = this.viewport_;
507 this.tracks_.appendChild(viewportTrack);
508
509 // Get a sorted list of CPUs
510 var cpus = model.getAllCpus();
511 cpus.sort(tracing.TimelineCpu.compare);
512
513 // Create tracks for each CPU.
514 cpus.forEach(function(cpu) {
515 var track = new tracing.TimelineCpuTrack();
516 track.heading = 'CPU ' + cpu.cpuNumber + ':';
517 track.headingWidth = maxHeadingWidth;
518 track.viewport = this.viewport_;
519 track.cpu = cpu;
520 this.tracks_.appendChild(track);
521
522 for (var counterName in cpu.counters) {
523 var counter = cpu.counters[counterName];
524 track = new tracing.TimelineCounterTrack();
525 track.heading = 'CPU ' + cpu.cpuNumber + ' ' + counter.name + ':';
526 track.headingWidth = maxHeadingWidth;
527 track.viewport = this.viewport_;
528 track.counter = counter;
529 this.tracks_.appendChild(track);
530 }
531 }.bind(this));
532
533 // Get a sorted list of processes.
534 var processes = model.getAllProcesses();
535 processes.sort(tracing.TimelineProcess.compare);
536
537 // Create tracks for each process.
538 processes.forEach(function(process) {
539 // Add counter tracks for this process.
540 var counters = [];
541 for (var tid in process.counters)
542 counters.push(process.counters[tid]);
543 counters.sort(tracing.TimelineCounter.compare);
544
545 // Create the counters for this process.
546 counters.forEach(function(counter) {
547 var track = new tracing.TimelineCounterTrack();
548 track.heading = counter.name + ':';
549 track.headingWidth = maxHeadingWidth;
550 track.viewport = this.viewport_;
551 track.counter = counter;
552 this.tracks_.appendChild(track);
553 }.bind(this));
554
555 // Get a sorted list of threads.
556 var threads = [];
557 for (var tid in process.threads)
558 threads.push(process.threads[tid]);
559 threads.sort(tracing.TimelineThread.compare);
560
561 // Create the threads.
562 threads.forEach(function(thread) {
563 var track = new tracing.TimelineThreadTrack();
564 track.heading = thread.userFriendlyName + ':';
565 track.tooltip = thread.userFriendlyDetails;
566 track.headingWidth = maxHeadingWidth;
567 track.viewport = this.viewport_;
568 track.thread = thread;
569 this.tracks_.appendChild(track);
570 }.bind(this));
571 }.bind(this));
572
573 // Set up a reasonable viewport.
574 this.viewport_.setWhenPossible(function() {
575 var w = this.firstCanvas.width;
576 this.viewport_.xSetWorldRange(this.model_.minTimestamp,
577 this.model_.maxTimestamp,
578 w);
579 }.bind(this));
580 },
581
582 /**
583 * @param {TimelineFilter} filter The filter to use for finding matches.
584 * @param {TimelineSelection} selection The selection to add matches to.
585 * @return {Array} An array of objects that match the provided
586 * TimelineFilter.
587 */
588 addAllObjectsMatchingFilterToSelection: function(filter, selection) {
589 for (var i = 0; i < this.tracks_.children.length; ++i)
590 this.tracks_.children[i].addAllObjectsMatchingFilterToSelection(
591 filter, selection);
592 },
593
594 /**
595 * @return {Element} The element whose focused state determines
596 * whether to respond to keyboard inputs.
597 * Defaults to the parent element.
598 */
599 get focusElement() {
600 if (this.focusElement_)
601 return this.focusElement_;
602 return this.parentElement;
603 },
604
605 /**
606 * Sets the element whose focus state will determine whether
607 * to respond to keybaord input.
608 */
609 set focusElement(value) {
610 this.focusElement_ = value;
611 },
612
613 get listenToKeys_() {
614 if (!this.viewport_.isAttachedToDocument_)
615 return false;
616 if (!this.focusElement_)
617 return true;
618 if (this.focusElement.tabIndex >= 0)
619 return document.activeElement == this.focusElement;
620 return true;
621 },
622
623 onKeypress_: function(e) {
624 var vp = this.viewport_;
625 if (!this.firstCanvas)
626 return;
627 if (!this.listenToKeys_)
628 return;
629 var viewWidth = this.firstCanvas.clientWidth;
630 var curMouseV, curCenterW;
631 switch (e.keyCode) {
632 case 101: // e
633 var vX = this.lastMouseViewPos_.x;
634 var wX = vp.xViewToWorld(this.lastMouseViewPos_.x);
635 var distFromCenter = vX - (viewWidth / 2);
636 var percFromCenter = distFromCenter / viewWidth;
637 var percFromCenterSq = percFromCenter * percFromCenter;
638 vp.xPanWorldPosToViewPos(wX, 'center', viewWidth);
639 break;
640 case 119: // w
641 this.zoomBy_(1.5);
642 break;
643 case 115: // s
644 this.zoomBy_(1 / 1.5);
645 break;
646 case 103: // g
647 this.onGridToggle_(true);
648 break;
649 case 71: // G
650 this.onGridToggle_(false);
651 break;
652 case 87: // W
653 this.zoomBy_(10);
654 break;
655 case 83: // S
656 this.zoomBy_(1 / 10);
657 break;
658 case 97: // a
659 vp.panX += vp.xViewVectorToWorld(viewWidth * 0.1);
660 break;
661 case 100: // d
662 vp.panX -= vp.xViewVectorToWorld(viewWidth * 0.1);
663 break;
664 case 65: // A
665 vp.panX += vp.xViewVectorToWorld(viewWidth * 0.5);
666 break;
667 case 68: // D
668 vp.panX -= vp.xViewVectorToWorld(viewWidth * 0.5);
669 break;
670 }
671 },
672
673 // Not all keys send a keypress.
674 onKeydown_: function(e) {
675 if (!this.listenToKeys_)
676 return;
677 var sel;
678 switch (e.keyCode) {
679 case 37: // left arrow
680 sel = this.selection.getShiftedSelection(-1);
681 if (sel) {
682 this.setSelectionAndMakeVisible(sel);
683 e.preventDefault();
684 }
685 break;
686 case 39: // right arrow
687 sel = this.selection.getShiftedSelection(1);
688 if (sel) {
689 this.setSelectionAndMakeVisible(sel);
690 e.preventDefault();
691 }
692 break;
693 case 9: // TAB
694 if (this.focusElement.tabIndex == -1) {
695 if (e.shiftKey)
696 this.selectPrevious_(e);
697 else
698 this.selectNext_(e);
699 e.preventDefault();
700 }
701 break;
702 }
703 },
704
705 /**
706 * Zoom in or out on the timeline by the given scale factor.
707 * @param {integer} scale The scale factor to apply. If <1, zooms out.
708 */
709 zoomBy_: function(scale) {
710 if (!this.firstCanvas)
711 return;
712 var vp = this.viewport_;
713 var viewWidth = this.firstCanvas.clientWidth;
714 var curMouseV = this.lastMouseViewPos_.x;
715 var curCenterW = vp.xViewToWorld(curMouseV);
716 vp.scaleX = vp.scaleX * scale;
717 vp.xPanWorldPosToViewPos(curCenterW, curMouseV, viewWidth);
718 },
719
720 get keyHelp() {
721 var help = 'Keyboard shortcuts:\n' +
722 ' w/s : Zoom in/out (with shift: go faster)\n' +
723 ' a/d : Pan left/right\n' +
724 ' e : Center on mouse\n' +
725 ' g/G : Shows grid at the start/end of the selected task\n';
726
727 if (this.focusElement.tabIndex) {
728 help += ' <- : Select previous event on current timeline\n' +
729 ' -> : Select next event on current timeline\n';
730 } else {
731 help += ' <-,^TAB : Select previous event on current timeline\n' +
732 ' ->, TAB : Select next event on current timeline\n';
733 }
734 help +=
735 '\n' +
736 'Dbl-click to zoom in; Shift dbl-click to zoom out\n';
737 return help;
738 },
739
740 get selection() {
741 return this.selection_;
742 },
743
744 set selection(selection) {
745 if (!(selection instanceof TimelineSelection))
746 throw 'Expected TimelineSelection';
747
748 // Clear old selection.
749 var i;
750 for (i = 0; i < this.selection_.length; i++)
751 this.selection_[i].selected = false;
752
753 this.selection_ = selection;
754
755 cr.dispatchSimpleEvent(this, 'selectionChange');
756 for (i = 0; i < this.selection_.length; i++)
757 this.selection_[i].selected = true;
758 this.viewport_.dispatchChangeEvent(); // Triggers a redraw.
759 },
760
761 setSelectionAndMakeVisible: function(selection, zoomAllowed) {
762 if (!(selection instanceof TimelineSelection))
763 throw 'Expected TimelineSelection';
764 this.selection = selection;
765 var range = this.selection.range;
766 var size = this.viewport_.xWorldVectorToView(range.max - range.min);
767 if (zoomAllowed && size < 50) {
768 var worldCenter = range.min + (range.max - range.min) * 0.5;
769 var worldRange = (range.max - range.min) * 5;
770 this.viewport_.xSetWorldRange(worldCenter - worldRange * 0.5,
771 worldCenter + worldRange * 0.5,
772 this.firstCanvas.width);
773 return;
774 }
775
776 this.viewport_.xPanWorldRangeIntoView(range.min, range.max,
777 this.firstCanvas.width);
778 },
779
780 get firstCanvas() {
781 return this.tracks_.firstChild ?
782 this.tracks_.firstChild.firstCanvas : undefined;
783 },
784
785 hideDragBox_: function() {
786 this.dragBox_.style.left = '-1000px';
787 this.dragBox_.style.top = '-1000px';
788 this.dragBox_.style.width = 0;
789 this.dragBox_.style.height = 0;
790 },
791
792 setDragBoxPosition_: function(eDown, eCur) {
793 var loX = Math.min(eDown.clientX, eCur.clientX);
794 var hiX = Math.max(eDown.clientX, eCur.clientX);
795 var loY = Math.min(eDown.clientY, eCur.clientY);
796 var hiY = Math.max(eDown.clientY, eCur.clientY);
797
798 this.dragBox_.style.left = loX + 'px';
799 this.dragBox_.style.top = loY + 'px';
800 this.dragBox_.style.width = hiX - loX + 'px';
801 this.dragBox_.style.height = hiY - loY + 'px';
802
803 var canv = this.firstCanvas;
804 var loWX = this.viewport_.xViewToWorld(loX - canv.offsetLeft);
805 var hiWX = this.viewport_.xViewToWorld(hiX - canv.offsetLeft);
806
807 var roundedDuration = Math.round((hiWX - loWX) * 100) / 100;
808 this.dragBox_.textContent = roundedDuration + 'ms';
809
810 var e = new cr.Event('selectionChanging');
811 e.loWX = loWX;
812 e.hiWX = hiWX;
813 this.dispatchEvent(e);
814 },
815
816 onGridToggle_: function(left) {
817 var tb;
818 if (left)
819 tb = this.selection_.range.min;
820 else
821 tb = this.selection_.range.max;
822
823 // Shift the timebase left until its just left of minTimestamp.
824 var numInterfvalsSinceStart = Math.ceil((tb - this.model_.minTimestamp) /
825 this.viewport_.gridStep_);
826 this.viewport_.gridTimebase = tb -
827 (numInterfvalsSinceStart + 1) * this.viewport_.gridStep_;
828 this.viewport_.gridEnabled = true;
829 },
830
831 onMouseDown_: function(e) {
832 var canv = this.firstCanvas;
833 var rect = this.tracks_.getClientRects()[0];
834 var inside = rect &&
835 e.clientX >= rect.left &&
836 e.clientX < rect.right &&
837 e.clientY >= rect.top &&
838 e.clientY < rect.bottom &&
839 e.x >= canv.offsetLeft;
840 if (!inside)
841 return;
842
843 var pos = {
844 x: e.clientX - canv.offsetLeft,
845 y: e.clientY - canv.offsetTop
846 };
847
848 var wX = this.viewport_.xViewToWorld(pos.x);
849
850 this.dragBeginEvent_ = e;
851 e.preventDefault();
852 if (this.focusElement.tabIndex >= 0)
853 this.focusElement.focus();
854 },
855
856 onMouseMove_: function(e) {
857 if (!this.firstCanvas)
858 return;
859 var canv = this.firstCanvas;
860 var pos = {
861 x: e.clientX - canv.offsetLeft,
862 y: e.clientY - canv.offsetTop
863 };
864
865 // Remember position. Used during keyboard zooming.
866 this.lastMouseViewPos_ = pos;
867
868 // Update the drag box
869 if (this.dragBeginEvent_) {
870 this.setDragBoxPosition_(this.dragBeginEvent_, e);
871 }
872 },
873
874 onMouseUp_: function(e) {
875 var i;
876 if (this.dragBeginEvent_) {
877 // Stop the dragging.
878 this.hideDragBox_();
879 var eDown = this.dragBeginEvent_;
880 this.dragBeginEvent_ = null;
881
882 // Figure out extents of the drag.
883 var loX = Math.min(eDown.clientX, e.clientX);
884 var hiX = Math.max(eDown.clientX, e.clientX);
885 var loY = Math.min(eDown.clientY, e.clientY);
886 var hiY = Math.max(eDown.clientY, e.clientY);
887
888 // Convert to worldspace.
889 var canv = this.firstCanvas;
890 var loWX = this.viewport_.xViewToWorld(loX - canv.offsetLeft);
891 var hiWX = this.viewport_.xViewToWorld(hiX - canv.offsetLeft);
892
893 // Figure out what has been hit.
894 var selection = new TimelineSelection();
895 for (i = 0; i < this.tracks_.children.length; i++) {
896 var track = this.tracks_.children[i];
897
898 // Only check tracks that insersect the rect.
899 var trackClientRect = track.getBoundingClientRect();
900 var a = Math.max(loY, trackClientRect.top);
901 var b = Math.min(hiY, trackClientRect.bottom);
902 if (a <= b) {
903 track.addIntersectingItemsInRangeToSelection(
904 loWX, hiWX, loY, hiY, selection);
905 }
906 }
907 // Activate the new selection.
908 this.selection = selection;
909 }
910 },
911
912 onDblClick_: function(e) {
913 var canv = this.firstCanvas;
914 if (e.x < canv.offsetLeft)
915 return;
916
917 var scale = 4;
918 if (e.shiftKey)
919 scale = 1 / scale;
920 this.zoomBy_(scale);
921 e.preventDefault();
922 }
923 };
924
925 /**
926 * The TimelineModel being viewed by the timeline
927 * @type {TimelineModel}
928 */
929 cr.defineProperty(Timeline, 'model', cr.PropertyKind.JS);
930
931 return {
932 Timeline: Timeline,
933 TimelineSelectionSliceHit: TimelineSelectionSliceHit,
934 TimelineSelectionCounterSampleHit: TimelineSelectionCounterSampleHit,
935 TimelineSelection: TimelineSelection,
936 TimelineViewport: TimelineViewport
937 };
938 });
OLDNEW
« no previous file with comments | « chrome/browser/resources/tracing/timeline.css ('k') | chrome/browser/resources/tracing/timeline_analysis.css » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698