1 // Copyright 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.
5 // TODO(viona): Write a README document/instructions.
7 /** This view displays the event waterfall. */
8 var WaterfallView = (function() {
11 // We inherit from DivView.
12 var superClass = DivView;
17 function WaterfallView() {
18 assertFirstConstructorCall(WaterfallView);
20 // Call superclass's constructor.
21 superClass.call(this, WaterfallView.MAIN_BOX_ID);
23 SourceTracker.getInstance().addSourceEntryObserver(this);
25 // For adjusting the range of view.
26 $(WaterfallView.SCALE_ID).addEventListener(
27 'click', this.setStartEndTimes_.bind(this), true);
29 $(WaterfallView.MAIN_BOX_ID).addEventListener(
30 'mousewheel', this.scrollToZoom_.bind(this), true);
32 $(WaterfallView.MAIN_BOX_ID).addEventListener(
33 'scroll', this.scrollInfoTable_.bind(this), true);
35 this.initializeSourceList_();
37 window.onload = this.scrollInfoTable_();
40 WaterfallView.TAB_ID = 'tab-handle-waterfall';
41 WaterfallView.TAB_NAME = 'Waterfall';
42 WaterfallView.TAB_HASH = '#waterfall';
44 // IDs for special HTML elements in events_waterfall_view.html.
45 WaterfallView.MAIN_BOX_ID = 'waterfall-view-tab-content';
46 WaterfallView.BAR_TABLE_ID = 'waterfall-view-time-bar-table';
47 WaterfallView.BAR_TBODY_ID = 'waterfall-view-time-bar-tbody';
48 WaterfallView.SCALE_ID = 'waterfall-view-adjust-to-window';
49 WaterfallView.TIME_SCALE_HEADER_ID = 'waterfall-view-time-scale-labels';
50 WaterfallView.TIME_RANGE_ID = 'waterfall-view-time-range-submit';
51 WaterfallView.START_TIME_ID = 'waterfall-view-start-input';
52 WaterfallView.END_TIME_ID = 'waterfall-view-end-input';
53 WaterfallView.INFO_TABLE_ID = 'waterfall-view-information-table';
54 WaterfallView.INFO_TBODY_ID = 'waterfall-view-information-tbody';
55 WaterfallView.CONTROLS_ID = 'waterfall-view-controls';
56 WaterfallView.ID_HEADER_ID = 'waterfall-view-id-header';
57 WaterfallView.URL_HEADER_ID = 'waterfall-view-url-header';
59 // The number of units mouse wheel deltas increase for each tick of the
61 var MOUSE_WHEEL_UNITS_PER_CLICK = 120;
63 // Amount we zoom for one vertical tick of the mouse wheel, as a ratio.
64 var MOUSE_WHEEL_ZOOM_RATE = 1.25;
65 // Amount we scroll for one horizontal tick of the mouse wheel, in pixels.
66 var MOUSE_WHEEL_SCROLL_RATE = MOUSE_WHEEL_UNITS_PER_CLICK;
68 cr.addSingletonGetter(WaterfallView);
70 WaterfallView.prototype = {
71 // Inherit the superclass's methods.
72 __proto__: superClass.prototype,
75 * Creates new WaterfallRows for URL Requests when the sourceEntries are
76 * updated if they do not already exist.
77 * Updates pre-existing WaterfallRows that correspond to updated sources.
79 onSourceEntriesUpdated: function(sourceEntries) {
80 for (var i = 0; i < sourceEntries.length; ++i) {
81 var sourceEntry = sourceEntries[i];
82 var id = sourceEntry.getSourceId();
83 if (sourceEntry.getSourceType() == EventSourceType.URL_REQUEST) {
84 var row = this.sourceIdToRowMap_[id];
86 var newRow = new WaterfallRow(this, sourceEntry);
87 this.sourceIdToRowMap_[id] = newRow;
89 row.onSourceUpdated();
93 this.scrollInfoTable_();
94 this.positionBarTable_();
95 this.updateTimeScale_(this.scaleFactor_);
98 onAllSourceEntriesDeleted: function() {
99 this.initializeSourceList_();
102 onLoadLogFinish: function(data) {
106 getScaleFactor: function() {
107 return this.scaleFactor_;
110 setGeometry: function(left, top, width, height) {
111 superClass.prototype.setGeometry.call(this, left, top, width, height);
112 this.scrollInfoTable_();
115 show: function(isVisible) {
116 superClass.prototype.show.call(this, isVisible);
118 this.scrollInfoTable_();
123 * Initializes the list of source entries. If source entries are already
124 * being displayed, removes them all in the process.
126 initializeSourceList_: function() {
127 this.sourceIdToRowMap_ = {};
128 $(WaterfallView.BAR_TBODY_ID).innerHTML = '';
129 $(WaterfallView.INFO_TBODY_ID).innerHTML = '';
130 this.scaleFactor_ = 0.1;
134 * Changes scroll position of the window such that horizontally, everything
135 * within the specified range fits into the user's viewport.
137 adjustToWindow_: function(windowStart, windowEnd) {
138 var waterfallLeft = $(WaterfallView.INFO_TABLE_ID).offsetWidth +
139 $(WaterfallView.INFO_TABLE_ID).offsetLeft +
140 $(WaterfallView.ID_HEADER_ID).offsetWidth;
141 var maxWidth = $(WaterfallView.MAIN_BOX_ID).offsetWidth - waterfallLeft;
142 var totalDuration = 0;
143 if (windowEnd != -1) {
144 totalDuration = windowEnd - windowStart;
146 for (var id in this.sourceIdToRowMap_) {
147 var row = this.sourceIdToRowMap_[id];
148 var rowDuration = row.getEndTicks();
149 if (totalDuration < rowDuration && !row.hide) {
150 totalDuration = rowDuration;
154 if (totalDuration <= 0) {
157 this.scaleAll_(maxWidth / totalDuration);
158 $(WaterfallView.MAIN_BOX_ID).scrollLeft =
159 windowStart * this.scaleFactor_;
162 /** Updates the time tick indicators. */
163 updateTimeScale_: function(scaleFactor) {
165 var minTickDistance = 20;
167 $(WaterfallView.TIME_SCALE_HEADER_ID).innerHTML = '';
169 // Holder provides environment to prevent wrapping.
170 var timeTickRow = addNode($(WaterfallView.TIME_SCALE_HEADER_ID), 'div');
171 timeTickRow.classList.add('waterfall-view-time-scale-row');
173 var availableWidth = $(WaterfallView.BAR_TBODY_ID).clientWidth;
174 var tickDistance = scaleFactor * timePerTick;
176 while (tickDistance < minTickDistance) {
177 timePerTick = timePerTick * 10;
178 tickDistance = scaleFactor * timePerTick;
181 var tickCount = availableWidth / tickDistance;
182 for (var i = 0; i < tickCount; ++i) {
183 var timeCell = addNode(timeTickRow, 'div');
184 setNodeWidth(timeCell, tickDistance);
185 timeCell.classList.add('waterfall-view-time-scale');
186 timeCell.title = i * timePerTick + ' to ' +
187 (i + 1) * timePerTick + ' ms';
188 // Red marker for every 5th bar.
190 timeCell.classList.add('waterfall-view-time-scale-special');
196 * Scales all existing rows by scaleFactor.
198 scaleAll_: function(scaleFactor) {
199 this.scaleFactor_ = scaleFactor;
200 for (var id in this.sourceIdToRowMap_) {
201 var row = this.sourceIdToRowMap_[id];
204 this.updateTimeScale_(scaleFactor);
207 scrollToZoom_: function(event) {
208 // To use scrolling to control zoom, hold down the alt key and scroll.
209 if ('wheelDelta' in event && event.altKey) {
210 event.preventDefault();
211 var zoomFactor = Math.pow(MOUSE_WHEEL_ZOOM_RATE,
212 event.wheelDeltaY / MOUSE_WHEEL_UNITS_PER_CLICK);
214 var waterfallLeft = $(WaterfallView.ID_HEADER_ID).offsetWidth +
215 $(WaterfallView.URL_HEADER_ID).offsetWidth;
216 var oldCursorPosition = event.pageX +
217 $(WaterfallView.MAIN_BOX_ID).scrollLeft;
218 var oldCursorPositionInTable = oldCursorPosition - waterfallLeft;
220 this.scaleAll_(this.scaleFactor_ * zoomFactor);
222 // Shifts the view when scrolling. newScroll could be less than 0 or
223 // more than the maximum scroll position, but both cases are handled
224 // by the inbuilt scrollLeft implementation.
226 oldCursorPositionInTable * zoomFactor - event.pageX + waterfallLeft;
227 $(WaterfallView.MAIN_BOX_ID).scrollLeft = newScroll;
232 * Positions the bar table such that it is in line with the right edge of
235 positionBarTable_: function() {
236 var offsetLeft = $(WaterfallView.INFO_TABLE_ID).offsetWidth +
237 $(WaterfallView.INFO_TABLE_ID).offsetLeft;
238 $(WaterfallView.BAR_TABLE_ID).style.left = offsetLeft + 'px';
242 * Moves the info table when the page is scrolled vertically, ensuring that
243 * the correct information is displayed on the page, and that no elements
244 * are blocked unnecessarily.
246 scrollInfoTable_: function(event) {
247 $(WaterfallView.INFO_TABLE_ID).style.top =
248 $(WaterfallView.MAIN_BOX_ID).offsetTop +
249 $(WaterfallView.BAR_TABLE_ID).offsetTop -
250 $(WaterfallView.MAIN_BOX_ID).scrollTop + 'px';
252 if ($(WaterfallView.INFO_TABLE_ID).offsetHeight >
253 $(WaterfallView.MAIN_BOX_ID).clientHeight) {
254 var scroll = $(WaterfallView.MAIN_BOX_ID).scrollTop;
256 $(WaterfallView.MAIN_BOX_ID).clientHeight -
257 $(WaterfallView.BAR_TABLE_ID).offsetTop +
258 $(WaterfallView.MAIN_BOX_ID).scrollTop;
259 // Clips the information table such that it does not cover the scroll
260 // bars or the controls bar.
261 $(WaterfallView.INFO_TABLE_ID).style.clip = 'rect(' + scroll +
262 'px auto ' + bottomClip + 'px auto)';
266 /** Parses user input, then calls adjustToWindow to shift that into view. */
267 setStartEndTimes_: function() {
268 var windowStart = parseInt($(WaterfallView.START_TIME_ID).value);
269 var windowEnd = parseInt($(WaterfallView.END_TIME_ID).value);
270 if ($(WaterfallView.END_TIME_ID).value == '') {
273 if ($(WaterfallView.START_TIME_ID).value == '') {
276 this.adjustToWindow_(windowStart, windowEnd);
282 return WaterfallView;