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
;