Rubber-stamped by Brady Eidson.
[webbrowser.git] / WebCore / inspector / front-end / AbstractTimelinePanel.js
blobc5180f6f51566a7efb32fab4f37914633244e16a
1 /*
2 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
3 * Copyright (C) 2008, 2009 Anthony Ricaud <rik@webkit.org>
4 * Copyright (C) 2009 Google Inc. All rights reserved.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
16 * its contributors may be used to endorse or promote products derived
17 * from this software without specific prior written permission.
19 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
20 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 WebInspector.AbstractTimelinePanel = function()
33 WebInspector.Panel.call(this);
34 this._items = [];
35 this._staleItems = [];
38 WebInspector.AbstractTimelinePanel.prototype = {
39 get categories()
41 // Should be implemented by the concrete subclasses.
42 return {};
45 populateSidebar: function()
47 // Should be implemented by the concrete subclasses.
50 createItemTreeElement: function(item)
52 // Should be implemented by the concrete subclasses.
55 createItemGraph: function(item)
57 // Should be implemented by the concrete subclasses.
60 get items()
62 return this._items;
65 createInterface: function()
67 this.containerElement = document.createElement("div");
68 this.containerElement.id = "resources-container";
69 this.containerElement.addEventListener("scroll", this._updateDividersLabelBarPosition.bind(this), false);
70 this.element.appendChild(this.containerElement);
72 this.createSidebar(this.containerElement, this.element);
73 this.sidebarElement.id = "resources-sidebar";
74 this.populateSidebar();
76 this._containerContentElement = document.createElement("div");
77 this._containerContentElement.id = "resources-container-content";
78 this.containerElement.appendChild(this._containerContentElement);
80 this.summaryBar = new WebInspector.SummaryBar(this.categories);
81 this.summaryBar.element.id = "resources-summary";
82 this._containerContentElement.appendChild(this.summaryBar.element);
84 this._timelineGrid = new WebInspector.TimelineGrid();
85 this._containerContentElement.appendChild(this._timelineGrid.element);
86 this.itemsGraphsElement = this._timelineGrid.itemsGraphsElement;
89 createFilterPanel: function()
91 this.filterBarElement = document.createElement("div");
92 this.filterBarElement.id = "resources-filter";
93 this.filterBarElement.className = "scope-bar";
94 this.element.appendChild(this.filterBarElement);
96 function createFilterElement(category)
98 if (category === "all")
99 var label = WebInspector.UIString("All");
100 else if (this.categories[category])
101 var label = this.categories[category].title;
103 var categoryElement = document.createElement("li");
104 categoryElement.category = category;
105 categoryElement.addStyleClass(category);
106 categoryElement.appendChild(document.createTextNode(label));
107 categoryElement.addEventListener("click", this._updateFilter.bind(this), false);
108 this.filterBarElement.appendChild(categoryElement);
110 return categoryElement;
113 this.filterAllElement = createFilterElement.call(this, "all");
115 // Add a divider
116 var dividerElement = document.createElement("div");
117 dividerElement.addStyleClass("divider");
118 this.filterBarElement.appendChild(dividerElement);
120 for (var category in this.categories)
121 createFilterElement.call(this, category);
124 showCategory: function(category)
126 var filterClass = "filter-" + category.toLowerCase();
127 this.itemsGraphsElement.addStyleClass(filterClass);
128 this.itemsTreeElement.childrenListElement.addStyleClass(filterClass);
131 hideCategory: function(category)
133 var filterClass = "filter-" + category.toLowerCase();
134 this.itemsGraphsElement.removeStyleClass(filterClass);
135 this.itemsTreeElement.childrenListElement.removeStyleClass(filterClass);
138 filter: function(target, selectMultiple)
140 function unselectAll()
142 for (var i = 0; i < this.filterBarElement.childNodes.length; ++i) {
143 var child = this.filterBarElement.childNodes[i];
144 if (!child.category)
145 continue;
147 child.removeStyleClass("selected");
148 this.hideCategory(child.category);
152 if (target === this.filterAllElement) {
153 if (target.hasStyleClass("selected")) {
154 // We can't unselect All, so we break early here
155 return;
158 // If All wasn't selected, and now is, unselect everything else.
159 unselectAll.call(this);
160 } else {
161 // Something other than All is being selected, so we want to unselect All.
162 if (this.filterAllElement.hasStyleClass("selected")) {
163 this.filterAllElement.removeStyleClass("selected");
164 this.hideCategory("all");
168 if (!selectMultiple) {
169 // If multiple selection is off, we want to unselect everything else
170 // and just select ourselves.
171 unselectAll.call(this);
173 target.addStyleClass("selected");
174 this.showCategory(target.category);
175 return;
178 if (target.hasStyleClass("selected")) {
179 // If selectMultiple is turned on, and we were selected, we just
180 // want to unselect ourselves.
181 target.removeStyleClass("selected");
182 this.hideCategory(target.category);
183 } else {
184 // If selectMultiple is turned on, and we weren't selected, we just
185 // want to select ourselves.
186 target.addStyleClass("selected");
187 this.showCategory(target.category);
191 _updateFilter: function(e)
193 var isMac = WebInspector.isMac();
194 var selectMultiple = false;
195 if (isMac && e.metaKey && !e.ctrlKey && !e.altKey && !e.shiftKey)
196 selectMultiple = true;
197 if (!isMac && e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey)
198 selectMultiple = true;
200 this.filter(e.target, selectMultiple);
202 // When we are updating our filtering, scroll to the top so we don't end up
203 // in blank graph under all the resources.
204 this.containerElement.scrollTop = 0;
207 updateGraphDividersIfNeeded: function(force)
209 if (!this.visible) {
210 this.needsRefresh = true;
211 return false;
213 return this._timelineGrid.updateDividers(force, this.calculator);
216 _updateDividersLabelBarPosition: function()
218 var scrollTop = this.containerElement.scrollTop;
219 var dividersTop = (scrollTop < this.summaryBar.element.offsetHeight ? this.summaryBar.element.offsetHeight : scrollTop);
220 this._timelineGrid.setScrollAndDividerTop(scrollTop, dividersTop);
223 get needsRefresh()
225 return this._needsRefresh;
228 set needsRefresh(x)
230 if (this._needsRefresh === x)
231 return;
233 this._needsRefresh = x;
235 if (x) {
236 if (this.visible && !("_refreshTimeout" in this))
237 this._refreshTimeout = setTimeout(this.refresh.bind(this), 500);
238 } else {
239 if ("_refreshTimeout" in this) {
240 clearTimeout(this._refreshTimeout);
241 delete this._refreshTimeout;
246 refreshIfNeeded: function()
248 if (this.needsRefresh)
249 this.refresh();
252 show: function()
254 WebInspector.Panel.prototype.show.call(this);
256 this._updateDividersLabelBarPosition();
257 this.refreshIfNeeded();
260 resize: function()
262 this.updateGraphDividersIfNeeded();
265 updateMainViewWidth: function(width)
267 this._containerContentElement.style.left = width + "px";
268 this.updateGraphDividersIfNeeded();
271 invalidateAllItems: function()
273 this._staleItems = this._items.slice();
276 refresh: function()
278 this.needsRefresh = false;
280 var staleItemsLength = this._staleItems.length;
282 var boundariesChanged = false;
284 for (var i = 0; i < staleItemsLength; ++i) {
285 var item = this._staleItems[i];
286 if (!item._itemsTreeElement) {
287 // Create the timeline tree element and graph.
288 item._itemsTreeElement = this.createItemTreeElement(item);
289 item._itemsTreeElement._itemGraph = this.createItemGraph(item);
291 this.itemsTreeElement.appendChild(item._itemsTreeElement);
292 this.itemsGraphsElement.appendChild(item._itemsTreeElement._itemGraph.graphElement);
295 if (item._itemsTreeElement.refresh)
296 item._itemsTreeElement.refresh();
298 if (this.calculator.updateBoundaries(item))
299 boundariesChanged = true;
302 if (boundariesChanged) {
303 // The boundaries changed, so all item graphs are stale.
304 this._staleItems = this._items.slice();
305 staleItemsLength = this._staleItems.length;
308 for (var i = 0; i < staleItemsLength; ++i)
309 this._staleItems[i]._itemsTreeElement._itemGraph.refresh(this.calculator);
311 this._staleItems = [];
313 this.updateGraphDividersIfNeeded();
316 reset: function()
318 this.containerElement.scrollTop = 0;
320 if (this._calculator)
321 this._calculator.reset();
323 if (this._items) {
324 var itemsLength = this._items.length;
325 for (var i = 0; i < itemsLength; ++i) {
326 var item = this._items[i];
327 delete item._itemsTreeElement;
331 this._items = [];
332 this._staleItems = [];
334 this.itemsTreeElement.removeChildren();
335 this.itemsGraphsElement.removeChildren();
337 this.updateGraphDividersIfNeeded(true);
340 get calculator()
342 return this._calculator;
345 set calculator(x)
347 if (!x || this._calculator === x)
348 return;
350 this._calculator = x;
351 this._calculator.reset();
353 this._staleItems = this._items.slice();
354 this.refresh();
357 addItem: function(item)
359 this._items.push(item);
360 this.refreshItem(item);
363 removeItem: function(item)
365 this._items.remove(item, true);
367 if (item._itemsTreeElement) {
368 this.itemsTreeElement.removeChild(item._itemsTreeElement);
369 this.itemsGraphsElement.removeChild(item._itemsTreeElement._itemGraph.graphElement);
372 delete item._itemsTreeElement;
373 this.adjustScrollPosition();
376 refreshItem: function(item)
378 this._staleItems.push(item);
379 this.needsRefresh = true;
382 revealAndSelectItem: function(item)
384 if (item._itemsTreeElement) {
385 item._itemsTreeElement.reveal();
386 item._itemsTreeElement.select(true);
390 sortItems: function(sortingFunction)
392 var sortedElements = [].concat(this.itemsTreeElement.children);
393 sortedElements.sort(sortingFunction);
395 var sortedElementsLength = sortedElements.length;
396 for (var i = 0; i < sortedElementsLength; ++i) {
397 var treeElement = sortedElements[i];
398 if (treeElement === this.itemsTreeElement.children[i])
399 continue;
401 var wasSelected = treeElement.selected;
402 this.itemsTreeElement.removeChild(treeElement);
403 this.itemsTreeElement.insertChild(treeElement, i);
404 if (wasSelected)
405 treeElement.select(true);
407 var graphElement = treeElement._itemGraph.graphElement;
408 this.itemsGraphsElement.insertBefore(graphElement, this.itemsGraphsElement.children[i]);
412 adjustScrollPosition: function()
414 // Prevent the container from being scrolled off the end.
415 if ((this.containerElement.scrollTop + this.containerElement.offsetHeight) > this.sidebarElement.offsetHeight)
416 this.containerElement.scrollTop = (this.sidebarElement.offsetHeight - this.containerElement.offsetHeight);
419 addEventDivider: function(divider)
421 this._timelineGrid.addEventDivider(divider);
425 WebInspector.AbstractTimelinePanel.prototype.__proto__ = WebInspector.Panel.prototype;
427 WebInspector.AbstractTimelineCalculator = function()
431 WebInspector.AbstractTimelineCalculator.prototype = {
432 computeSummaryValues: function(items)
434 var total = 0;
435 var categoryValues = {};
437 var itemsLength = items.length;
438 for (var i = 0; i < itemsLength; ++i) {
439 var item = items[i];
440 var value = this._value(item);
441 if (typeof value === "undefined")
442 continue;
443 if (!(item.category.name in categoryValues))
444 categoryValues[item.category.name] = 0;
445 categoryValues[item.category.name] += value;
446 total += value;
449 return {categoryValues: categoryValues, total: total};
452 computeBarGraphPercentages: function(item)
454 return {start: 0, middle: 0, end: (this._value(item) / this.boundarySpan) * 100};
457 computeBarGraphLabels: function(item)
459 const label = this.formatValue(this._value(item));
460 return {left: label, right: label, tooltip: label};
463 get boundarySpan()
465 return this.maximumBoundary - this.minimumBoundary;
468 updateBoundaries: function(item)
470 this.minimumBoundary = 0;
472 var value = this._value(item);
473 if (typeof this.maximumBoundary === "undefined" || value > this.maximumBoundary) {
474 this.maximumBoundary = value;
475 return true;
477 return false;
480 reset: function()
482 delete this.minimumBoundary;
483 delete this.maximumBoundary;
486 _value: function(item)
488 return 0;
491 formatValue: function(value)
493 return value.toString();
497 WebInspector.AbstractTimelineCategory = function(name, title, color)
499 this.name = name;
500 this.title = title;
501 this.color = color;
504 WebInspector.AbstractTimelineCategory.prototype = {
505 toString: function()
507 return this.title;