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
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);
35 this._staleItems
= [];
38 WebInspector
.AbstractTimelinePanel
.prototype = {
41 // Should be implemented by the concrete subclasses.
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.
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");
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
];
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
158 // If All wasn't selected, and now is, unselect everything else.
159 unselectAll
.call(this);
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
);
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
);
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
)
210 this.needsRefresh
= true;
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
);
225 return this._needsRefresh
;
230 if (this._needsRefresh
=== x
)
233 this._needsRefresh
= x
;
236 if (this.visible
&& !("_refreshTimeout" in this))
237 this._refreshTimeout
= setTimeout(this.refresh
.bind(this), 500);
239 if ("_refreshTimeout" in this) {
240 clearTimeout(this._refreshTimeout
);
241 delete this._refreshTimeout
;
246 refreshIfNeeded: function()
248 if (this.needsRefresh
)
254 WebInspector
.Panel
.prototype.show
.call(this);
256 this._updateDividersLabelBarPosition();
257 this.refreshIfNeeded();
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();
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();
318 this.containerElement
.scrollTop
= 0;
320 if (this._calculator
)
321 this._calculator
.reset();
324 var itemsLength
= this._items
.length
;
325 for (var i
= 0; i
< itemsLength
; ++i
) {
326 var item
= this._items
[i
];
327 delete item
._itemsTreeElement
;
332 this._staleItems
= [];
334 this.itemsTreeElement
.removeChildren();
335 this.itemsGraphsElement
.removeChildren();
337 this.updateGraphDividersIfNeeded(true);
342 return this._calculator
;
347 if (!x
|| this._calculator
=== x
)
350 this._calculator
= x
;
351 this._calculator
.reset();
353 this._staleItems
= this._items
.slice();
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
])
401 var wasSelected
= treeElement
.selected
;
402 this.itemsTreeElement
.removeChild(treeElement
);
403 this.itemsTreeElement
.insertChild(treeElement
, i
);
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
)
435 var categoryValues
= {};
437 var itemsLength
= items
.length
;
438 for (var i
= 0; i
< itemsLength
; ++i
) {
440 var value
= this._value(item
);
441 if (typeof value
=== "undefined")
443 if (!(item
.category
.name
in categoryValues
))
444 categoryValues
[item
.category
.name
] = 0;
445 categoryValues
[item
.category
.name
] += 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
};
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
;
482 delete this.minimumBoundary
;
483 delete this.maximumBoundary
;
486 _value: function(item
)
491 formatValue: function(value
)
493 return value
.toString();
497 WebInspector
.AbstractTimelineCategory = function(name
, title
, color
)
504 WebInspector
.AbstractTimelineCategory
.prototype = {