Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / resources / net_internals / timeline_view.js
blobc4183d34a4cfdf84dbb0b0c1281996859d9927ee
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.
5 /**
6  * TimelineView displays a zoomable and scrollable graph of a number of values
7  * over time.  The TimelineView class itself is responsible primarily for
8  * updating the TimelineDataSeries its GraphView displays.
9  */
10 var TimelineView = (function() {
11   'use strict';
13   // We inherit from HorizontalSplitView.
14   var superClass = HorizontalSplitView;
16   /**
17    * @constructor
18    */
19   function TimelineView() {
20     assertFirstConstructorCall(TimelineView);
22     this.graphView_ = new TimelineGraphView(
23         TimelineView.GRAPH_DIV_ID,
24         TimelineView.GRAPH_CANVAS_ID,
25         TimelineView.SCROLLBAR_DIV_ID,
26         TimelineView.SCROLLBAR_INNER_DIV_ID);
28     // Call superclass's constructor.
30     var selectionView = new DivView(TimelineView.SELECTION_DIV_ID);
31     superClass.call(this, selectionView, this.graphView_);
33     this.selectionDivFullWidth_ = selectionView.getWidth();
34     $(TimelineView.SELECTION_TOGGLE_ID).onclick =
35         this.toggleSelectionDiv_.bind(this);
37     // Interval id returned by window.setInterval for update timer.
38     this.updateIntervalId_ = null;
40     // List of DataSeries.  These are shared with the TimelineGraphView.  The
41     // TimelineView updates their state, the TimelineGraphView reads their
42     // state and draws them.
43     this.dataSeries_ = [];
45     // DataSeries depend on some of the global constants, so they're only
46     // created once constants have been received.  We also use this message to
47     // recreate DataSeries when log files are being loaded.
48     g_browser.addConstantsObserver(this);
50     // We observe new log entries to determine the range of the graph, and pass
51     // them on to each DataSource.  We initialize the graph range to initially
52     // include all events, but after that, we only update it to be the current
53     // time on a timer.
54     EventsTracker.getInstance().addLogEntryObserver(this);
55     this.graphRangeInitialized_ = false;
56   }
58   TimelineView.TAB_ID = 'tab-handle-timeline';
59   TimelineView.TAB_NAME = 'Timeline';
60   TimelineView.TAB_HASH = '#timeline';
62   // IDs for special HTML elements in timeline_view.html
63   TimelineView.GRAPH_DIV_ID = 'timeline-view-graph-div';
64   TimelineView.GRAPH_CANVAS_ID = 'timeline-view-graph-canvas';
65   TimelineView.SELECTION_DIV_ID = 'timeline-view-selection-div';
66   TimelineView.SELECTION_TOGGLE_ID = 'timeline-view-selection-toggle';
67   TimelineView.SELECTION_UL_ID = 'timeline-view-selection-ul';
68   TimelineView.SCROLLBAR_DIV_ID = 'timeline-view-scrollbar-div';
69   TimelineView.SCROLLBAR_INNER_DIV_ID = 'timeline-view-scrollbar-inner-div';
71   TimelineView.OPEN_SOCKETS_ID = 'timeline-view-open-sockets';
72   TimelineView.IN_USE_SOCKETS_ID = 'timeline-view-in-use-sockets';
73   TimelineView.URL_REQUESTS_ID = 'timeline-view-url-requests';
74   TimelineView.DNS_REQUESTS_ID = 'timeline-view-dns-requests';
75   TimelineView.DNS_JOBS_ID = 'timeline-view-dns-jobs';
76   TimelineView.BYTES_RECEIVED_ID = 'timeline-view-bytes-received';
77   TimelineView.BYTES_SENT_ID = 'timeline-view-bytes-sent';
78   TimelineView.DISK_CACHE_BYTES_READ_ID =
79       'timeline-view-disk-cache-bytes-read';
80   TimelineView.DISK_CACHE_BYTES_WRITTEN_ID =
81       'timeline-view-disk-cache-bytes-written';
83   // Class used for hiding the colored squares next to the labels for the
84   // lines.
85   TimelineView.HIDDEN_CLASS = 'timeline-view-hidden';
87   cr.addSingletonGetter(TimelineView);
89   // Frequency with which we increase update the end date to be the current
90   // time, when actively capturing events.
91   var UPDATE_INTERVAL_MS = 2000;
93   TimelineView.prototype = {
94     // Inherit the superclass's methods.
95     __proto__: superClass.prototype,
97     setGeometry: function(left, top, width, height) {
98       superClass.prototype.setGeometry.call(this, left, top, width, height);
99     },
101     show: function(isVisible) {
102       superClass.prototype.show.call(this, isVisible);
103       // If we're hidden or not capturing events, we don't want to update the
104       // graph's range.
105       if (!isVisible || g_browser.isDisabled()) {
106         this.setUpdateEndDateInterval_(0);
107         return;
108       }
110       // Otherwise, update the visible range on a timer.
111       this.setUpdateEndDateInterval_(UPDATE_INTERVAL_MS);
112       this.updateEndDate_();
113     },
115     /**
116      * Starts calling the GraphView's updateEndDate function every |intervalMs|
117      * milliseconds.  If |intervalMs| is 0, stops calling the function.
118      */
119     setUpdateEndDateInterval_: function(intervalMs) {
120       if (this.updateIntervalId_ !== null) {
121         window.clearInterval(this.updateIntervalId_);
122         this.updateIntervalId_ = null;
123       }
124       if (intervalMs > 0) {
125         this.updateIntervalId_ =
126             window.setInterval(this.updateEndDate_.bind(this), intervalMs);
127       }
128     },
130     /**
131      * Updates the end date of graph to be the current time, unless the
132      * BrowserBridge is disabled.
133      */
134     updateEndDate_: function() {
135       // If we loaded a log file or capturing data was stopped, stop the timer.
136       if (g_browser.isDisabled()) {
137         this.setUpdateEndDateInterval_(0);
138         return;
139       }
140       this.graphView_.updateEndDate();
141     },
143     onLoadLogFinish: function(data) {
144       this.setUpdateEndDateInterval_(0);
145       return true;
146     },
148     /**
149      * Updates the visibility state of |dataSeries| to correspond to the
150      * current checked state of |checkBox|.  Also updates the class of
151      * |listItem| based on the new visibility state.
152      */
153     updateDataSeriesVisibility_: function(dataSeries, listItem, checkBox) {
154       dataSeries.show(checkBox.checked);
155       if (checkBox.checked)
156         listItem.classList.remove(TimelineView.HIDDEN_CLASS);
157       else
158         listItem.classList.add(TimelineView.HIDDEN_CLASS);
159     },
161     dataSeriesClicked_: function(dataSeries, listItem, checkBox) {
162       this.updateDataSeriesVisibility_(dataSeries, listItem, checkBox);
163       this.graphView_.repaint();
164     },
166     /**
167      * Adds the specified DataSeries to |dataSeries_|, and hooks up
168      * |listItemId|'s checkbox and color to correspond to the current state
169      * of the given DataSeries.
170      */
171     addDataSeries_: function(dataSeries, listItemId) {
172       this.dataSeries_.push(dataSeries);
173       var listItem = $(listItemId);
174       var checkBox = $(listItemId).querySelector('input');
176       // Make sure |listItem| is visible, and then use its color for the
177       // DataSource.
178       listItem.classList.remove(TimelineView.HIDDEN_CLASS);
179       dataSeries.setColor(getComputedStyle(listItem).color);
181       this.updateDataSeriesVisibility_(dataSeries, listItem, checkBox);
182       checkBox.onclick = this.dataSeriesClicked_.bind(this, dataSeries,
183                                                       listItem, checkBox);
184     },
186     /**
187      * Recreate all DataSeries.  Global constants must have been set before
188      * this is called.
189      */
190     createDataSeries_: function() {
191       this.graphRangeInitialized_ = false;
192       this.dataSeries_ = [];
194       this.addDataSeries_(new SourceCountDataSeries(
195                               EventSourceType.SOCKET,
196                               EventType.SOCKET_ALIVE),
197                           TimelineView.OPEN_SOCKETS_ID);
199       this.addDataSeries_(new SocketsInUseDataSeries(),
200                           TimelineView.IN_USE_SOCKETS_ID);
202       this.addDataSeries_(new SourceCountDataSeries(
203                               EventSourceType.URL_REQUEST,
204                               EventType.REQUEST_ALIVE),
205                           TimelineView.URL_REQUESTS_ID);
207       this.addDataSeries_(new SourceCountDataSeries(
208                               EventSourceType.HOST_RESOLVER_IMPL_REQUEST,
209                               EventType.HOST_RESOLVER_IMPL_REQUEST),
210                           TimelineView.DNS_REQUESTS_ID);
212       this.addDataSeries_(new SourceCountDataSeries(
213                               EventSourceType.HOST_RESOLVER_IMPL_JOB,
214                               EventType.HOST_RESOLVER_IMPL_JOB),
215                           TimelineView.DNS_JOBS_ID);
217       this.addDataSeries_(new NetworkTransferRateDataSeries(
218                               EventType.SOCKET_BYTES_RECEIVED,
219                               EventType.UDP_BYTES_RECEIVED),
220                           TimelineView.BYTES_RECEIVED_ID);
222       this.addDataSeries_(new NetworkTransferRateDataSeries(
223                               EventType.SOCKET_BYTES_SENT,
224                               EventType.UDP_BYTES_SENT),
225                           TimelineView.BYTES_SENT_ID);
227       this.addDataSeries_(new DiskCacheTransferRateDataSeries(
228                               EventType.ENTRY_READ_DATA),
229                           TimelineView.DISK_CACHE_BYTES_READ_ID);
231       this.addDataSeries_(new DiskCacheTransferRateDataSeries(
232                               EventType.ENTRY_WRITE_DATA),
233                           TimelineView.DISK_CACHE_BYTES_WRITTEN_ID);
235       this.graphView_.setDataSeries(this.dataSeries_);
236     },
238     /**
239      * When we receive the constants, create or recreate the DataSeries.
240      */
241     onReceivedConstants: function(constants) {
242       this.createDataSeries_();
243     },
245     /**
246      * When all log entries are deleted, recreate the DataSeries.
247      */
248     onAllLogEntriesDeleted: function() {
249       this.graphRangeInitialized_ = false;
250       this.createDataSeries_();
251     },
253     onReceivedLogEntries: function(entries) {
254       // Pass each entry to every DataSeries, one at a time.  Not having each
255       // data series get data directly from the EventsTracker saves us from
256       // having very un-Javascript-like destructors for when we load new,
257       // constants and slightly simplifies DataSeries objects.
258       for (var entry = 0; entry < entries.length; ++entry) {
259         for (var i = 0; i < this.dataSeries_.length; ++i)
260           this.dataSeries_[i].onReceivedLogEntry(entries[entry]);
261       }
263       // If this is the first non-empty set of entries we've received, or we're
264       // viewing a loaded log file, we will need to update the date range.
265       if (this.graphRangeInitialized_ && !MainView.isViewingLoadedLog())
266         return;
267       if (entries.length == 0)
268         return;
270       // Update the date range.
271       var startDate;
272       if (!this.graphRangeInitialized_) {
273         startDate = timeutil.convertTimeTicksToDate(entries[0].time);
274       } else {
275         startDate = this.graphView_.getStartDate();
276       }
277       var endDate =
278           timeutil.convertTimeTicksToDate(entries[entries.length - 1].time);
279       this.graphView_.setDateRange(startDate, endDate);
280       this.graphRangeInitialized_ = true;
281     },
283     toggleSelectionDiv_: function() {
284       var toggle = $(TimelineView.SELECTION_TOGGLE_ID);
285       var shouldCollapse = toggle.className == 'timeline-view-rotateleft';
287       setNodeDisplay($(TimelineView.SELECTION_UL_ID), !shouldCollapse);
288       toggle.className = shouldCollapse ?
289           'timeline-view-rotateright' : 'timeline-view-rotateleft';
291       // Figure out the appropriate width for the selection div.
292       var newWidth;
293       if (shouldCollapse) {
294         newWidth = toggle.offsetWidth;
295       } else {
296         newWidth = this.selectionDivFullWidth_;
297       }
299       // Change the width on the selection view (doesn't matter what we
300       // set the other values to, since we will re-layout in the next line).
301       this.leftView_.setGeometry(0, 0, newWidth, 100);
303       // Force a re-layout now that the left view has changed width.
304       this.setGeometry(this.getLeft(), this.getTop(), this.getWidth(),
305                        this.getHeight());
306     }
307   };
309   return TimelineView;
310 })();