3770ebc96bb096d6d0d80d53c568379e3e73044c
[WebKit-https.git] / WebCore / inspector / front-end / TimelinePanel.js
1 /*
2  * Copyright (C) 2009 Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *     * Neither the name of Google Inc. nor the names of its
15  * contributors may be used to endorse or promote products derived from
16  * this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 WebInspector.TimelinePanel = function()
32 {
33     WebInspector.AbstractTimelinePanel.call(this);
34
35     this.element.addStyleClass("timeline");
36
37     this._createOverview();
38     this.createInterface();
39     this.containerElement.id = "timeline-container";
40     this.summaryBar.element.id = "timeline-summary";
41     this.itemsGraphsElement.id = "timeline-graphs";
42
43     this._createStatusbarButtons();
44
45     this.calculator = new WebInspector.TimelineCalculator();
46     for (category in this.categories)
47         this.showCategory(category);
48     this._sendRequestRecords = {};
49 }
50
51 WebInspector.TimelinePanel.prototype = {
52     toolbarItemClass: "timeline",
53
54     get toolbarItemLabel()
55     {
56         return WebInspector.UIString("Timeline");
57     },
58
59     get statusBarItems()
60     {
61         return [this.toggleTimelineButton.element, this.clearButton.element];
62     },
63
64     get categories()
65     {
66         if (!this._categories) {
67             this._categories = {
68                 loading: new WebInspector.TimelineCategory("loading", WebInspector.UIString("Loading"), "rgb(47,102,236)"),
69                 scripting: new WebInspector.TimelineCategory("scripting", WebInspector.UIString("Scripting"), "rgb(157,231,119)"),
70                 rendering: new WebInspector.TimelineCategory("rendering", WebInspector.UIString("Rendering"), "rgb(164,60,255)")
71             };
72         }
73         return this._categories;
74     },
75
76     populateSidebar: function()
77     {
78         this.itemsTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("RECORDS"), {}, true);
79         this.itemsTreeElement.expanded = true;
80         this.sidebarTree.appendChild(this.itemsTreeElement);
81     },
82
83     _createStatusbarButtons: function()
84     {
85         this.toggleTimelineButton = new WebInspector.StatusBarButton("", "record-profile-status-bar-item");
86         this.toggleTimelineButton.addEventListener("click", this._toggleTimelineButtonClicked.bind(this), false);
87
88         this.clearButton = new WebInspector.StatusBarButton("", "timeline-clear-status-bar-item");
89         this.clearButton.addEventListener("click", this.reset.bind(this), false);
90     },
91
92     timelineWasStarted: function()
93     {
94         this.toggleTimelineButton.toggled = true;
95     },
96
97     timelineWasStopped: function()
98     {
99         this.toggleTimelineButton.toggled = false;
100     },
101
102     addRecordToTimeline: function(record)
103     {
104         var formattedRecord = this._formatRecord(record);
105         // Glue subsequent records with same category and title together if they are closer than 100ms to each other.
106         if (this._lastRecord && (!record.children || !record.children.length) &&
107                 this._lastRecord.category == formattedRecord.category &&
108                 this._lastRecord.title == formattedRecord.title &&
109                 this._lastRecord.details == formattedRecord.details &&
110                 formattedRecord.startTime - this._lastRecord.endTime < 0.1) {
111             this._lastRecord.endTime = formattedRecord.endTime;
112             this._lastRecord.count++;
113             this.refreshItem(this._lastRecord);
114         } else {
115             this.addItem(formattedRecord);
116
117             for (var i = 0; record.children && i < record.children.length; ++i)
118                 this.addRecordToTimeline(record.children[i]);
119             this._lastRecord = record.children && record.children.length ? null : formattedRecord;
120         }
121     },
122
123     createItemTreeElement: function(item)
124     {
125         return new WebInspector.TimelineRecordTreeElement(item);
126     },
127
128     createItemGraph: function(item)
129     {
130         return new WebInspector.TimelineGraph(item);
131     },
132
133     _toggleTimelineButtonClicked: function()
134     {
135         if (this.toggleTimelineButton.toggled)
136             InspectorController.stopTimelineProfiler();
137         else
138             InspectorController.startTimelineProfiler();
139     },
140
141     _formatRecord: function(record)
142     {
143         var recordTypes = WebInspector.TimelineAgent.RecordType;
144         if (!this._recordStyles) {
145             this._recordStyles = {};
146             this._recordStyles[recordTypes.EventDispatch] = { title: WebInspector.UIString("Event"), category: this.categories.scripting };
147             this._recordStyles[recordTypes.Layout] = { title: WebInspector.UIString("Layout"), category: this.categories.rendering };
148             this._recordStyles[recordTypes.RecalculateStyles] = { title: WebInspector.UIString("Recalculate Style"), category: this.categories.rendering };
149             this._recordStyles[recordTypes.Paint] = { title: WebInspector.UIString("Paint"), category: this.categories.rendering };
150             this._recordStyles[recordTypes.ParseHTML] = { title: WebInspector.UIString("Parse"), category: this.categories.loading };
151             this._recordStyles[recordTypes.TimerInstall] = { title: WebInspector.UIString("Install Timer"), category: this.categories.scripting };
152             this._recordStyles[recordTypes.TimerRemove] = { title: WebInspector.UIString("Remove Timer"), category: this.categories.scripting };
153             this._recordStyles[recordTypes.TimerFire] = { title: WebInspector.UIString("Timer Fired"), category: this.categories.scripting };
154             this._recordStyles[recordTypes.XHRReadyStateChange] = { title: WebInspector.UIString("XHR Ready State Change"), category: this.categories.scripting };
155             this._recordStyles[recordTypes.XHRLoad] = { title: WebInspector.UIString("XHR Load"), category: this.categories.scripting };
156             this._recordStyles[recordTypes.EvaluateScript] = { title: WebInspector.UIString("Evaluate Script"), category: this.categories.scripting };
157             this._recordStyles[recordTypes.MarkTimeline] = { title: WebInspector.UIString("Mark"), category: this.categories.scripting };
158             this._recordStyles[recordTypes.ResourceSendRequest] = { title: WebInspector.UIString("Send Request"), category: this.categories.loading };
159             this._recordStyles[recordTypes.ResourceReceiveResponse] = { title: WebInspector.UIString("Receive Response"), category: this.categories.loading };
160             this._recordStyles[recordTypes.ResourceFinish] = { title: WebInspector.UIString("Finish Loading"), category: this.categories.loading };
161         }
162
163         var style = this._recordStyles[record.type];
164         if (!style)
165             style = this._recordStyles[recordTypes.EventDispatch];
166
167         var formattedRecord = {};
168         formattedRecord.category = style.category;
169         formattedRecord.title = style.title;
170         formattedRecord.startTime = record.startTime / 1000;
171         formattedRecord.data = record.data;
172         formattedRecord.count = 1;
173         formattedRecord.type = record.type;
174         formattedRecord.endTime = (typeof record.endTime !== "undefined") ? record.endTime / 1000 : formattedRecord.startTime;
175         formattedRecord.record = record;
176
177         // Make resource receive record last since request was sent; make finish record last since response received.
178         if (record.type === WebInspector.TimelineAgent.RecordType.ResourceSendRequest) {
179             this._sendRequestRecords[record.data.identifier] = formattedRecord;
180         } else if (record.type === WebInspector.TimelineAgent.RecordType.ResourceReceiveResponse) {
181             var sendRequestRecord = this._sendRequestRecords[record.data.identifier];
182             sendRequestRecord._responseReceivedFormattedTime = formattedRecord.startTime;
183             formattedRecord.startTime = sendRequestRecord.startTime;
184             sendRequestRecord.details = this._getRecordDetails(record);
185             this.refreshItem(sendRequestRecord);
186         } else if (record.type === WebInspector.TimelineAgent.RecordType.ResourceFinish) {
187             var sendRequestRecord = this._sendRequestRecords[record.data.identifier];
188             if (sendRequestRecord) // False for main resource.
189                 formattedRecord.startTime = sendRequestRecord._responseReceivedFormattedTime;
190         }
191         formattedRecord.details = this._getRecordDetails(record);
192
193         return formattedRecord;
194     },
195
196     _getRecordDetails: function(record)
197     {
198         switch (record.type) {
199         case WebInspector.TimelineAgent.RecordType.EventDispatch:
200             return record.data ? record.data.type : "";
201         case WebInspector.TimelineAgent.RecordType.Paint:
202             return record.data.width + "\u2009\u00d7\u2009" + record.data.height;
203         case WebInspector.TimelineAgent.RecordType.TimerInstall:
204         case WebInspector.TimelineAgent.RecordType.TimerRemove:
205         case WebInspector.TimelineAgent.RecordType.TimerFire:
206             return record.data.timerId;
207         case WebInspector.TimelineAgent.RecordType.XHRReadyStateChange:
208         case WebInspector.TimelineAgent.RecordType.XHRLoad:
209         case WebInspector.TimelineAgent.RecordType.EvaluateScript:
210         case WebInspector.TimelineAgent.RecordType.ResourceSendRequest:
211             return WebInspector.displayNameForURL(record.data.url);
212         case WebInspector.TimelineAgent.RecordType.ResourceReceiveResponse:
213         case WebInspector.TimelineAgent.RecordType.ResourceFinish:
214             var sendRequestRecord = this._sendRequestRecords[record.data.identifier];
215             return sendRequestRecord ? WebInspector.displayNameForURL(sendRequestRecord.data.url) : "";
216         case WebInspector.TimelineAgent.RecordType.MarkTimeline:
217             return record.data.message;
218         default:
219             return "";
220         }
221     },
222
223     reset: function()
224     {
225         WebInspector.AbstractTimelinePanel.prototype.reset.call(this);
226         this._lastRecord = null;
227         this._overviewCalculator.reset();
228         for (var category in this.categories)
229             this._categoryGraphs[category].clearChunks();
230         this._setWindowPosition(0, this._overviewGridElement.clientWidth);
231         this._sendRequestRecords = {};
232     },
233
234     _createOverview: function()
235     {
236         var overviewPanelElement = document.createElement("div");
237         overviewPanelElement.id = "timeline-overview-panel";
238         this.element.appendChild(overviewPanelElement);
239
240         this._overviewSidebarElement = document.createElement("div");
241         this._overviewSidebarElement.id = "timeline-overview-sidebar";
242         overviewPanelElement.appendChild(this._overviewSidebarElement);
243
244         var overviewTreeElement = document.createElement("ol");
245         overviewTreeElement.className = "sidebar-tree";
246         this._overviewSidebarElement.appendChild(overviewTreeElement);
247         var sidebarTree = new TreeOutline(overviewTreeElement);
248
249         var categoriesTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("TIMELINES"), {}, true);
250         categoriesTreeElement.expanded = true;
251         sidebarTree.appendChild(categoriesTreeElement);
252
253         for (var category in this.categories)
254             categoriesTreeElement.appendChild(new WebInspector.TimelineCategoryTreeElement(this.categories[category]));
255
256         this._overviewGridElement = document.createElement("div");
257         this._overviewGridElement.id = "timeline-overview-grid";
258         overviewPanelElement.appendChild(this._overviewGridElement);
259         this._overviewGrid = new WebInspector.TimelineGrid(this._overviewGridElement);
260         this._overviewGrid.itemsGraphsElement.id = "timeline-overview-graphs";
261
262         this._categoryGraphs = {};
263         for (var category in this.categories) {
264             var categoryGraph = new WebInspector.TimelineCategoryGraph(this.categories[category]);
265             this._categoryGraphs[category] = categoryGraph;
266             this._overviewGrid.itemsGraphsElement.appendChild(categoryGraph.graphElement);
267         }
268         this._overviewGrid.setScrollAndDividerTop(0, 0);
269
270         this._overviewWindowElement = document.createElement("div");
271         this._overviewWindowElement.id = "timeline-overview-window";
272         this._overviewWindowElement.addEventListener("mousedown", this._dragWindow.bind(this), false);
273         this._overviewGridElement.appendChild(this._overviewWindowElement);
274
275         this._leftResizeElement = document.createElement("div");
276         this._leftResizeElement.className = "timeline-window-resizer";
277         this._leftResizeElement.style.left = 0;
278         this._overviewGridElement.appendChild(this._leftResizeElement);
279         this._leftResizeElement.addEventListener("mousedown", this._resizeWindow.bind(this, this._leftResizeElement), false);
280
281         this._rightResizeElement = document.createElement("div");
282         this._rightResizeElement.className = "timeline-window-resizer timeline-window-resizer-right";
283         this._rightResizeElement.style.right = 0;
284         this._overviewGridElement.appendChild(this._rightResizeElement);
285         this._rightResizeElement.addEventListener("mousedown", this._resizeWindow.bind(this, this._rightResizeElement), false);
286
287         this._overviewCalculator = new WebInspector.TimelineCalculator();
288
289         var separatorElement = document.createElement("div");
290         separatorElement.id = "timeline-overview-separator";
291         this.element.appendChild(separatorElement);
292     },
293
294     setSidebarWidth: function(width)
295     {
296         WebInspector.AbstractTimelinePanel.prototype.setSidebarWidth.call(this, width);
297         this._overviewSidebarElement.style.width = width + "px";
298     },
299
300     updateMainViewWidth: function(width)
301     {
302         WebInspector.AbstractTimelinePanel.prototype.updateMainViewWidth.call(this, width);
303         this._overviewGridElement.style.left = width + "px";
304     },
305
306     updateGraphDividersIfNeeded: function()
307     {
308         WebInspector.AbstractTimelinePanel.prototype.updateGraphDividersIfNeeded.call(this);
309         this._overviewGrid.updateDividers(true, this._overviewCalculator);
310     },
311
312     refresh: function()
313     {
314         WebInspector.AbstractTimelinePanel.prototype.refresh.call(this);
315         this.adjustScrollPosition();
316
317         // Clear summary bars.
318         var timelines = {};
319         for (var category in this.categories) {
320             timelines[category] = [];
321             this._categoryGraphs[category].clearChunks();
322         }
323
324         // Create sparse arrays with 101 cells each to fill with chunks for a given category.
325         for (var i = 0; i < this.items.length; ++i) {
326             var record = this.items[i];
327             this._overviewCalculator.updateBoundaries(record);
328             var percentages = this._overviewCalculator.computeBarGraphPercentages(record);
329             var end = Math.round(percentages.end);
330             var categoryName = record.category.name;
331             for (var j = Math.round(percentages.start); j <= end; ++j)
332                 timelines[categoryName][j] = true;
333         }
334
335         // Convert sparse arrays to continuous segments, render graphs for each.
336         for (var category in this.categories) {
337             var timeline = timelines[category];
338             window.timelineSaved = timeline;
339             var chunkStart = -1;
340             for (var j = 0; j < 101; ++j) {
341                 if (timeline[j]) {
342                     if (chunkStart === -1)
343                         chunkStart = j;
344                 } else {
345                     if (chunkStart !== -1) {
346                         this._categoryGraphs[category].addChunk(chunkStart, j);
347                         chunkStart = -1;
348                     }
349                 }
350             }
351             if (chunkStart !== -1) {
352                 this._categoryGraphs[category].addChunk(chunkStart, 100);
353                 chunkStart = -1;
354             }
355         }
356         this._overviewGrid.updateDividers(true, this._overviewCalculator);
357     },
358
359     _resizeWindow: function(resizeElement, event)
360     {
361         WebInspector.elementDragStart(resizeElement, this._windowResizeDragging.bind(this, resizeElement), this._endWindowDragging.bind(this), event, "col-resize");
362     },
363
364     _windowResizeDragging: function(resizeElement, event)
365     {
366         if (resizeElement === this._leftResizeElement)
367             this._resizeWindowLeft(event.pageX - this._overviewGridElement.offsetLeft);
368         else
369             this._resizeWindowRight(event.pageX - this._overviewGridElement.offsetLeft);
370         event.preventDefault();
371     },
372
373     _dragWindow: function(event)
374     {
375         WebInspector.elementDragStart(this._overviewWindowElement, this._windowDragging.bind(this, event.pageX,
376             this._leftResizeElement.offsetLeft, this._rightResizeElement.offsetLeft), this._endWindowDragging.bind(this), event, "ew-resize");
377     },
378
379     _windowDragging: function(startX, windowLeft, windowRight, event)
380     {
381         var delta = event.pageX - startX;
382         var start = windowLeft + delta;
383         var end = windowRight + delta;
384         var windowSize = windowRight - windowLeft;
385
386         if (start < 0) {
387             start = 0;
388             end = windowSize;
389         }
390
391         if (end > this._overviewGridElement.clientWidth) {
392             end = this._overviewGridElement.clientWidth;
393             start = end - windowSize;
394         }
395         this._setWindowPosition(start, end);
396
397         event.preventDefault();
398     },
399
400     _resizeWindowLeft: function(start)
401     {
402         // Glue to edge.
403         if (start < 10)
404             start = 0;
405         this._setWindowPosition(start, null);
406     },
407
408     _resizeWindowRight: function(end)
409     {
410         // Glue to edge.
411         if (end > this._overviewGridElement.clientWidth - 10)
412             end = this._overviewGridElement.clientWidth;
413         this._setWindowPosition(null, end);
414     },
415
416     _setWindowPosition: function(start, end)
417     {
418         this.calculator.reset();
419         this.invalidateAllItems();
420         if (typeof start === "number") {
421             if (start > this._rightResizeElement.offsetLeft - 25)
422                 start = this._rightResizeElement.offsetLeft - 25;
423
424             var windowLeft = start / this._overviewGridElement.clientWidth;
425             this.calculator.windowLeft = windowLeft;
426             this._leftResizeElement.style.left = windowLeft * 100 + "%";
427             this._overviewWindowElement.style.left = windowLeft * 100 + "%";
428         }
429         if (typeof end === "number") {
430             if (end < this._leftResizeElement.offsetLeft + 30)
431                 end = this._leftResizeElement.offsetLeft + 30;
432
433             var windowRight = end / this._overviewGridElement.clientWidth;
434             this.calculator.windowRight = windowRight;
435             this._rightResizeElement.style.left = windowRight * 100 + "%";
436         }
437         this._overviewWindowElement.style.width = (this.calculator.windowRight - this.calculator.windowLeft) * 100 + "%";
438         this.needsRefresh = true;
439     },
440
441     _endWindowDragging: function(event)
442     {
443         WebInspector.elementDragEnd(event);
444     }
445 }
446
447 WebInspector.TimelinePanel.prototype.__proto__ = WebInspector.AbstractTimelinePanel.prototype;
448
449
450 WebInspector.TimelineCategory = function(name, title, color)
451 {
452     WebInspector.AbstractTimelineCategory.call(this, name, title, color);
453 }
454
455 WebInspector.TimelineCategory.prototype = {
456 }
457
458 WebInspector.TimelineCategory.prototype.__proto__ = WebInspector.AbstractTimelineCategory.prototype;
459
460
461
462 WebInspector.TimelineCategoryTreeElement = function(category)
463 {
464     this._category = category;
465
466     // Pass an empty title, the title gets made later in onattach.
467     TreeElement.call(this, "", null, false);
468 }
469
470 WebInspector.TimelineCategoryTreeElement.prototype = {
471     onattach: function()
472     {
473         this.listItemElement.removeChildren();
474         this.listItemElement.addStyleClass("timeline-category-tree-item");
475         this.listItemElement.addStyleClass("timeline-category-" + this._category.name);
476
477         var label = document.createElement("label");
478
479         var checkElement = document.createElement("input");
480         checkElement.type = "checkbox";
481         checkElement.className = "timeline-category-checkbox";
482         checkElement.checked = true;
483         checkElement.addEventListener("click", this._onCheckboxClicked.bind(this));
484         label.appendChild(checkElement);
485
486         var typeElement = document.createElement("span");
487         typeElement.className = "type";
488         typeElement.textContent = this._category.title;
489         label.appendChild(typeElement);
490
491         this.listItemElement.appendChild(label);
492     },
493
494     _onCheckboxClicked: function (event) {
495         if (event.target.checked)
496             WebInspector.panels.timeline.showCategory(this._category.name);
497         else {
498             WebInspector.panels.timeline.hideCategory(this._category.name);
499             WebInspector.panels.timeline.adjustScrollPosition();
500         }
501         WebInspector.panels.timeline._categoryGraphs[this._category.name].dimmed = !event.target.checked;
502     }
503 }
504
505 WebInspector.TimelineCategoryTreeElement.prototype.__proto__ = TreeElement.prototype;
506
507
508 WebInspector.TimelineRecordTreeElement = function(record)
509 {
510     this._record = record;
511
512     // Pass an empty title, the title gets made later in onattach.
513     TreeElement.call(this, "", null, false);
514 }
515
516 WebInspector.TimelineRecordTreeElement.prototype = {
517     onattach: function()
518     {
519         this.listItemElement.removeChildren();
520         this.listItemElement.addStyleClass("timeline-tree-item");
521         this.listItemElement.addStyleClass("timeline-category-" + this._record.category.name);
522
523         var iconElement = document.createElement("span");
524         iconElement.className = "timeline-tree-icon";
525         this.listItemElement.appendChild(iconElement);
526
527         this.typeElement = document.createElement("span");
528         this.typeElement.className = "type";
529         this.typeElement.textContent = this._record.title;
530         this.listItemElement.appendChild(this.typeElement);
531
532         if (this._record.details) {
533             var separatorElement = document.createElement("span");
534             separatorElement.className = "separator";
535             separatorElement.textContent = " ";
536
537             this.dataElement = document.createElement("span");
538             this.dataElement.className = "data dimmed";
539             this._updateDetails();
540
541             this.listItemElement.appendChild(separatorElement);
542             this.listItemElement.appendChild(this.dataElement);
543         }
544     },
545
546     _updateDetails: function()
547     {
548         if (this.dataElement && this._record.details !== this._details) {
549             this._details = this._record.details;
550             this.dataElement.textContent = "(" + this._details + ")";
551             this.dataElement.title = this._details;
552         }
553     },
554
555     refresh: function()
556     {
557         this._updateDetails();
558
559         if (this._record.count <= 1)
560             return;
561
562         if (!this.repeatCountElement) {
563             this.repeatCountElement = document.createElement("span");
564             this.repeatCountElement.className = "count";
565             this.listItemElement.appendChild(this.repeatCountElement);
566         }
567
568         this.repeatCountElement.textContent = "\u2009\u00d7\u2009" + this._record.count;
569     }
570 }
571
572 WebInspector.TimelineRecordTreeElement.prototype.__proto__ = TreeElement.prototype;
573
574
575 WebInspector.TimelineCalculator = function()
576 {
577     WebInspector.AbstractTimelineCalculator.call(this);
578     this.windowLeft = 0.0;
579     this.windowRight = 1.0;
580     this._uiString = WebInspector.UIString.bind(WebInspector);
581 }
582
583 WebInspector.TimelineCalculator.prototype = {
584     computeBarGraphPercentages: function(record)
585     {
586         var start = (record.startTime - this.minimumBoundary) / this.boundarySpan * 100;
587         var end = (record.endTime - this.minimumBoundary) / this.boundarySpan * 100;
588         return {start: start, end: end};
589     },
590
591     computePercentageFromEventTime: function(eventTime)
592     {
593         return ((eventTime - this.minimumBoundary) / this.boundarySpan) * 100;
594     },
595
596     computeBarGraphLabels: function(record)
597     {
598         return {tooltip: record.title};
599     },
600
601     get minimumBoundary()
602     {
603         if (typeof this._minimumBoundary === "number")
604             return this._minimumBoundary;
605
606         if (typeof this.windowLeft === "number")
607             this._minimumBoundary = this._absoluteMinimumBoundary + this.windowLeft * (this._absoluteMaximumBoundary - this._absoluteMinimumBoundary);
608         else
609             this._minimumBoundary = this._absoluteMinimumBoundary;
610         return this._minimumBoundary;
611     },
612
613     get maximumBoundary()
614     {
615         if (typeof this._maximumBoundary === "number")
616             return this._maximumBoundary;
617
618         if (typeof this.windowLeft === "number")
619             this._maximumBoundary = this._absoluteMinimumBoundary + this.windowRight * (this._absoluteMaximumBoundary - this._absoluteMinimumBoundary);
620         else
621             this._maximumBoundary = this._absoluteMaximumBoundary;
622         return this._maximumBoundary;
623     },
624
625     reset: function()
626     {
627         delete this._absoluteMinimumBoundary;
628         delete this._absoluteMaximumBoundary;
629         delete this._minimumBoundary;
630         delete this._maximumBoundary;
631     },
632
633     updateBoundaries: function(record)
634     {
635         var didChange = false;
636
637         var lowerBound = record.startTime;
638
639         if (typeof this._absoluteMinimumBoundary === "undefined" || lowerBound < this._absoluteMinimumBoundary) {
640             this._absoluteMinimumBoundary = lowerBound;
641             delete this._minimumBoundary;
642             didChange = true;
643         }
644
645         var upperBound = record.endTime;
646         if (typeof this._absoluteMaximumBoundary === "undefined" || upperBound > this._absoluteMaximumBoundary) {
647             this._absoluteMaximumBoundary = upperBound;
648             delete this._maximumBoundary;
649             didChange = true;
650         }
651
652         return didChange;
653     },
654
655     formatValue: function(value)
656     {
657         return Number.secondsToString(value + this.minimumBoundary - this._absoluteMinimumBoundary, this._uiString);
658     }
659 }
660
661 WebInspector.TimelineCalculator.prototype.__proto__ = WebInspector.AbstractTimelineCalculator.prototype;
662
663
664 WebInspector.TimelineCategoryGraph = function(category)
665 {
666     this._category = category;
667
668     this._graphElement = document.createElement("div");
669     this._graphElement.className = "timeline-graph-side timeline-overview-graph-side";
670
671     this._barAreaElement = document.createElement("div");
672     this._barAreaElement.className = "timeline-graph-bar-area timeline-category-" + category.name;
673     this._graphElement.appendChild(this._barAreaElement);
674 }
675
676 WebInspector.TimelineCategoryGraph.prototype = {
677     get graphElement()
678     {
679         return this._graphElement;
680     },
681
682     addChunk: function(start, end)
683     {
684         var chunk = document.createElement("div");
685         chunk.className = "timeline-graph-bar";
686         this._barAreaElement.appendChild(chunk);
687         chunk.style.setProperty("left", start + "%");
688         chunk.style.setProperty("width", (end - start) + "%");
689     },
690
691     clearChunks: function()
692     {
693         this._barAreaElement.removeChildren();
694     },
695
696     set dimmed(dimmed)
697     {
698         if (dimmed)
699             this._barAreaElement.removeStyleClass("timeline-category-" + this._category.name);
700         else
701             this._barAreaElement.addStyleClass("timeline-category-" + this._category.name);
702     }
703 }
704
705
706 WebInspector.TimelineGraph = function(record)
707 {
708     this.record = record;
709
710     this._graphElement = document.createElement("div");
711     this._graphElement.className = "timeline-graph-side";
712
713     this._barAreaElement = document.createElement("div");
714     this._barAreaElement.className = "timeline-graph-bar-area";
715     this._graphElement.appendChild(this._barAreaElement);
716
717     this._barElement = document.createElement("div");
718     this._barElement.className = "timeline-graph-bar";
719     this._barAreaElement.appendChild(this._barElement);
720
721     this._graphElement.addStyleClass("timeline-category-" + record.category.name);
722     this._hidden = false;
723 }
724
725 WebInspector.TimelineGraph.prototype = {
726     get graphElement()
727     {
728         return this._graphElement;
729     },
730
731     refreshLabelPositions: function()
732     {
733     },
734
735     refresh: function(calculator)
736     {
737         var percentages = calculator.computeBarGraphPercentages(this.record);
738         var labels = calculator.computeBarGraphLabels(this.record);
739
740         this._percentages = percentages;
741
742         if (percentages.start > 100 || percentages.end < 0) {
743             if (!this._hidden) {
744                 this._graphElement.addStyleClass("hidden");
745                 this.record._itemsTreeElement.listItemElement.addStyleClass("hidden");
746                 this._hidden = true;
747             }
748         } else {
749             this._barElement.style.setProperty("left", percentages.start + "%");
750             this._barElement.style.setProperty("right", (100 - percentages.end) + "%");
751             if (this._hidden) {
752                 this._graphElement.removeStyleClass("hidden");
753                 this.record._itemsTreeElement.listItemElement.removeStyleClass("hidden");
754                 this._hidden = false;
755             }
756         }
757         var tooltip = (labels.tooltip || "");
758         this._barElement.title = tooltip;
759     }
760 }