Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / resources / net_internals / waterfall_view.js
blob928730ba8d432dfc0c582251d9eeac46f588ec04
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() {
9   'use strict';
11   // We inherit from DivView.
12   var superClass = DivView;
14   /**
15    * @constructor
16    */
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_();
38   }
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
60   // wheel.
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,
74     /**
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.
78      */
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];
85           if (!row) {
86             var newRow = new WaterfallRow(this, sourceEntry);
87             this.sourceIdToRowMap_[id] = newRow;
88           } else {
89             row.onSourceUpdated();
90           }
91         }
92       }
93       this.scrollInfoTable_();
94       this.positionBarTable_();
95       this.updateTimeScale_(this.scaleFactor_);
96     },
98     onAllSourceEntriesDeleted: function() {
99       this.initializeSourceList_();
100     },
102     onLoadLogFinish: function(data) {
103       return true;
104     },
106     getScaleFactor: function() {
107       return this.scaleFactor_;
108     },
110     setGeometry: function(left, top, width, height) {
111       superClass.prototype.setGeometry.call(this, left, top, width, height);
112       this.scrollInfoTable_();
113     },
115     show: function(isVisible) {
116       superClass.prototype.show.call(this, isVisible);
117       if (isVisible) {
118         this.scrollInfoTable_();
119       }
120     },
122     /**
123      * Initializes the list of source entries.  If source entries are already
124      * being displayed, removes them all in the process.
125      */
126     initializeSourceList_: function() {
127       this.sourceIdToRowMap_ = {};
128       $(WaterfallView.BAR_TBODY_ID).innerHTML = '';
129       $(WaterfallView.INFO_TBODY_ID).innerHTML = '';
130       this.scaleFactor_ = 0.1;
131     },
133     /**
134      * Changes scroll position of the window such that horizontally, everything
135      * within the specified range fits into the user's viewport.
136      */
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;
145       } else {
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;
151           }
152         }
153       }
154       if (totalDuration <= 0) {
155         return;
156       }
157       this.scaleAll_(maxWidth / totalDuration);
158       $(WaterfallView.MAIN_BOX_ID).scrollLeft =
159           windowStart * this.scaleFactor_;
160     },
162     /** Updates the time tick indicators. */
163     updateTimeScale_: function(scaleFactor) {
164       var timePerTick = 1;
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;
179       }
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.
189         if (i % 5 == 0) {
190           timeCell.classList.add('waterfall-view-time-scale-special');
191         }
192       }
193     },
195     /**
196      * Scales all existing rows by scaleFactor.
197      */
198     scaleAll_: function(scaleFactor) {
199       this.scaleFactor_ = scaleFactor;
200       for (var id in this.sourceIdToRowMap_) {
201         var row = this.sourceIdToRowMap_[id];
202         row.updateRow();
203       }
204       this.updateTimeScale_(scaleFactor);
205     },
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.
225         var newScroll =
226             oldCursorPositionInTable * zoomFactor - event.pageX + waterfallLeft;
227         $(WaterfallView.MAIN_BOX_ID).scrollLeft = newScroll;
228       }
229     },
231     /**
232      * Positions the bar table such that it is in line with the right edge of
233      * the info table.
234      */
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';
239     },
241     /**
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.
245      */
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;
255         var bottomClip =
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)';
263       }
264     },
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 == '') {
271         windowEnd = -1;
272       }
273       if ($(WaterfallView.START_TIME_ID).value == '') {
274         windowStart = 0;
275       }
276       this.adjustToWindow_(windowStart, windowEnd);
277     },
280   };
282   return WaterfallView;
283 })();