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 if (this.startTime_ == null && sourceEntries.length > 0) {
81 var logEntries = sourceEntries[0].getLogEntries();
82 this.startTime_ = timeutil.convertTimeTicksToTime(logEntries[0].time);
83 // Initial scale factor.
84 this.scaleFactor_ = 0.1;
86 for (var i = 0; i < sourceEntries.length; ++i) {
87 var sourceEntry = sourceEntries[i];
88 var id = sourceEntry.getSourceId();
89 if (sourceEntry.getSourceType() == EventSourceType.URL_REQUEST) {
90 var row = this.sourceIdToRowMap_[id];
92 var newRow = new WaterfallRow(this, sourceEntry);
93 this.sourceIdToRowMap_[id] = newRow;
95 row.onSourceUpdated();
99 this.scrollInfoTable_();
100 this.positionBarTable_();
101 this.updateTimeScale_(this.scaleFactor_);
104 onAllSourceEntriesDeleted: function() {
105 this.initializeSourceList_();
108 onLoadLogFinish: function(data) {
112 getScaleFactor: function() {
113 return this.scaleFactor_;
116 getStartTime: function() {
117 return this.startTime_;
120 setGeometry: function(left, top, width, height) {
121 superClass.prototype.setGeometry.call(this, left, top, width, height);
122 this.scrollInfoTable_();
125 show: function(isVisible) {
126 superClass.prototype.show.call(this, isVisible);
128 this.scrollInfoTable_();
133 * Initializes the list of source entries. If source entries are already
134 * being displayed, removes them all in the process.
136 initializeSourceList_: function() {
137 this.sourceIdToRowMap_ = {};
138 $(WaterfallView.BAR_TBODY_ID).innerHTML = '';
139 $(WaterfallView.INFO_TBODY_ID).innerHTML = '';
140 this.startTime_ = null;
141 this.scaleFactor_ = null;
145 * Changes scroll position of the window such that horizontally, everything
146 * within the specified range fits into the user's viewport.
148 adjustToWindow_: function(windowStart, windowEnd) {
149 var waterfallLeft = $(WaterfallView.INFO_TABLE_ID).offsetWidth +
150 $(WaterfallView.INFO_TABLE_ID).offsetLeft +
151 $(WaterfallView.ID_HEADER_ID).offsetWidth;
152 var maxWidth = $(WaterfallView.MAIN_BOX_ID).offsetWidth - waterfallLeft;
153 var totalDuration = 0;
154 if (windowEnd != -1) {
155 totalDuration = windowEnd - windowStart;
157 for (var id in this.sourceIdToRowMap_) {
158 var row = this.sourceIdToRowMap_[id];
159 var rowDuration = row.getEndTime() - this.startTime_;
160 if (totalDuration < rowDuration && !row.hide) {
161 totalDuration = rowDuration;
165 if (totalDuration <= 0) {
168 this.scaleAll_(maxWidth / totalDuration);
169 $(WaterfallView.MAIN_BOX_ID).scrollLeft =
170 windowStart * this.scaleFactor_;
173 /** Updates the time tick indicators. */
174 updateTimeScale_: function(scaleFactor) {
176 var minTickDistance = 20;
178 $(WaterfallView.TIME_SCALE_HEADER_ID).innerHTML = '';
180 // Holder provides environment to prevent wrapping.
181 var timeTickRow = addNode($(WaterfallView.TIME_SCALE_HEADER_ID), 'div');
182 timeTickRow.classList.add('waterfall-view-time-scale-row');
184 var availableWidth = $(WaterfallView.BAR_TBODY_ID).clientWidth;
185 var tickDistance = scaleFactor * timePerTick;
187 while (tickDistance < minTickDistance) {
188 timePerTick = timePerTick * 10;
189 tickDistance = scaleFactor * timePerTick;
192 var tickCount = availableWidth / tickDistance;
193 for (var i = 0; i < tickCount; ++i) {
194 var timeCell = addNode(timeTickRow, 'div');
195 setNodeWidth(timeCell, tickDistance);
196 timeCell.classList.add('waterfall-view-time-scale');
197 timeCell.title = i * timePerTick + ' to ' +
198 (i + 1) * timePerTick + ' ms';
199 // Red marker for every 5th bar.
201 timeCell.classList.add('waterfall-view-time-scale-special');
207 * Scales all existing rows by scaleFactor.
209 scaleAll_: function(scaleFactor) {
210 this.scaleFactor_ = scaleFactor;
211 for (var id in this.sourceIdToRowMap_) {
212 var row = this.sourceIdToRowMap_[id];
215 this.updateTimeScale_(scaleFactor);
218 scrollToZoom_: function(event) {
219 // To use scrolling to control zoom, hold down the alt key and scroll.
220 if ('wheelDelta' in event && event.altKey) {
221 event.preventDefault();
222 var zoomFactor = Math.pow(MOUSE_WHEEL_ZOOM_RATE,
223 event.wheelDeltaY / MOUSE_WHEEL_UNITS_PER_CLICK);
225 var waterfallLeft = $(WaterfallView.ID_HEADER_ID).offsetWidth +
226 $(WaterfallView.URL_HEADER_ID).offsetWidth;
227 var oldCursorPosition = event.pageX +
228 $(WaterfallView.MAIN_BOX_ID).scrollLeft;
229 var oldCursorPositionInTable = oldCursorPosition - waterfallLeft;
231 this.scaleAll_(this.scaleFactor_ * zoomFactor);
233 // Shifts the view when scrolling. newScroll could be less than 0 or
234 // more than the maximum scroll position, but both cases are handled
235 // by the inbuilt scrollLeft implementation.
237 oldCursorPositionInTable * zoomFactor - event.pageX + waterfallLeft;
238 $(WaterfallView.MAIN_BOX_ID).scrollLeft = newScroll;
243 * Positions the bar table such that it is in line with the right edge of
246 positionBarTable_: function() {
247 var offsetLeft = $(WaterfallView.INFO_TABLE_ID).offsetWidth +
248 $(WaterfallView.INFO_TABLE_ID).offsetLeft;
249 $(WaterfallView.BAR_TABLE_ID).style.left = offsetLeft + 'px';
253 * Moves the info table when the page is scrolled vertically, ensuring that
254 * the correct information is displayed on the page, and that no elements
255 * are blocked unnecessarily.
257 scrollInfoTable_: function(event) {
258 $(WaterfallView.INFO_TABLE_ID).style.top =
259 $(WaterfallView.MAIN_BOX_ID).offsetTop +
260 $(WaterfallView.BAR_TABLE_ID).offsetTop -
261 $(WaterfallView.MAIN_BOX_ID).scrollTop + 'px';
263 if ($(WaterfallView.INFO_TABLE_ID).offsetHeight >
264 $(WaterfallView.MAIN_BOX_ID).clientHeight) {
265 var scroll = $(WaterfallView.MAIN_BOX_ID).scrollTop;
267 $(WaterfallView.MAIN_BOX_ID).clientHeight -
268 $(WaterfallView.BAR_TABLE_ID).offsetTop +
269 $(WaterfallView.MAIN_BOX_ID).scrollTop;
270 // Clips the information table such that it does not cover the scroll
271 // bars or the controls bar.
272 $(WaterfallView.INFO_TABLE_ID).style.clip = 'rect(' + scroll +
273 'px auto ' + bottomClip + 'px auto)';
277 /** Parses user input, then calls adjustToWindow to shift that into view. */
278 setStartEndTimes_: function() {
279 var windowStart = parseInt($(WaterfallView.START_TIME_ID).value);
280 var windowEnd = parseInt($(WaterfallView.END_TIME_ID).value);
281 if ($(WaterfallView.END_TIME_ID).value == '') {
284 if ($(WaterfallView.START_TIME_ID).value == '') {
287 this.adjustToWindow_(windowStart, windowEnd);
293 return WaterfallView;