diff options
Diffstat (limited to 'WebCore/inspector/front-end/AbstractTimelinePanel.js')
-rw-r--r-- | WebCore/inspector/front-end/AbstractTimelinePanel.js | 548 |
1 files changed, 548 insertions, 0 deletions
diff --git a/WebCore/inspector/front-end/AbstractTimelinePanel.js b/WebCore/inspector/front-end/AbstractTimelinePanel.js new file mode 100644 index 0000000..75e4062 --- /dev/null +++ b/WebCore/inspector/front-end/AbstractTimelinePanel.js @@ -0,0 +1,548 @@ +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2008, 2009 Anthony Ricaud <rik@webkit.org> + * Copyright (C) 2009 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.AbstractTimelinePanel = function() +{ + WebInspector.Panel.call(this); + this._items = []; + this._staleItems = []; +} + +WebInspector.AbstractTimelinePanel.prototype = { + get categories() + { + // Should be implemented by the concrete subclasses. + return {}; + }, + + populateSidebar: function() + { + // Should be implemented by the concrete subclasses. + }, + + createItemTreeElement: function(item) + { + // Should be implemented by the concrete subclasses. + }, + + createItemGraph: function(item) + { + // Should be implemented by the concrete subclasses. + }, + + createInterface: function() + { + this._createFilterPanel(); + + this.containerElement = document.createElement("div"); + this.containerElement.id = "resources-container"; + this.containerElement.addEventListener("scroll", this._updateDividersLabelBarPosition.bind(this), false); + this.element.appendChild(this.containerElement); + + this.createSidebar(this.containerElement, this.element); + this.sidebarElement.id = "resources-sidebar"; + this.populateSidebar(); + + this._createGraph(); + }, + + _createFilterPanel: function() + { + this.filterBarElement = document.createElement("div"); + this.filterBarElement.id = "resources-filter"; + this.filterBarElement.className = "scope-bar"; + this.element.appendChild(this.filterBarElement); + + function createFilterElement(category) + { + if (category === "all") + var label = WebInspector.UIString("All"); + else if (this.categories[category]) + var label = this.categories[category].title; + + var categoryElement = document.createElement("li"); + categoryElement.category = category; + categoryElement.addStyleClass(category); + categoryElement.appendChild(document.createTextNode(label)); + categoryElement.addEventListener("click", this._updateFilter.bind(this), false); + this.filterBarElement.appendChild(categoryElement); + + return categoryElement; + } + + this.filterAllElement = createFilterElement.call(this, "all"); + + // Add a divider + var dividerElement = document.createElement("div"); + dividerElement.addStyleClass("divider"); + this.filterBarElement.appendChild(dividerElement); + + for (var category in this.categories) + createFilterElement.call(this, category); + }, + + _showCategory: function(category) + { + var filterClass = "filter-" + category.toLowerCase(); + this.itemsGraphsElement.addStyleClass(filterClass); + this.itemsTreeElement.childrenListElement.addStyleClass(filterClass); + }, + + _hideCategory: function(category) + { + var filterClass = "filter-" + category.toLowerCase(); + this.itemsGraphsElement.removeStyleClass(filterClass); + this.itemsTreeElement.childrenListElement.removeStyleClass(filterClass); + }, + + filter: function(target, selectMultiple) + { + function unselectAll() + { + for (var i = 0; i < this.filterBarElement.childNodes.length; ++i) { + var child = this.filterBarElement.childNodes[i]; + if (!child.category) + continue; + + child.removeStyleClass("selected"); + this._hideCategory(child.category); + } + } + + if (target === this.filterAllElement) { + if (target.hasStyleClass("selected")) { + // We can't unselect All, so we break early here + return; + } + + // If All wasn't selected, and now is, unselect everything else. + unselectAll.call(this); + } else { + // Something other than All is being selected, so we want to unselect All. + if (this.filterAllElement.hasStyleClass("selected")) { + this.filterAllElement.removeStyleClass("selected"); + this._hideCategory("all"); + } + } + + if (!selectMultiple) { + // If multiple selection is off, we want to unselect everything else + // and just select ourselves. + unselectAll.call(this); + + target.addStyleClass("selected"); + this._showCategory(target.category); + return; + } + + if (target.hasStyleClass("selected")) { + // If selectMultiple is turned on, and we were selected, we just + // want to unselect ourselves. + target.removeStyleClass("selected"); + this._hideCategory(target.category); + } else { + // If selectMultiple is turned on, and we weren't selected, we just + // want to select ourselves. + target.addStyleClass("selected"); + this._showCategory(target.category); + } + }, + + _updateFilter: function(e) + { + var isMac = InspectorController.platform().indexOf("mac-") === 0; + var selectMultiple = false; + if (isMac && e.metaKey && !e.ctrlKey && !e.altKey && !e.shiftKey) + selectMultiple = true; + if (!isMac && e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey) + selectMultiple = true; + + this.filter(e.target, selectMultiple); + + // When we are updating our filtering, scroll to the top so we don't end up + // in blank graph under all the resources. + this.containerElement.scrollTop = 0; + }, + + _createGraph: function() + { + this._containerContentElement = document.createElement("div"); + this._containerContentElement.id = "resources-container-content"; + this.containerElement.appendChild(this._containerContentElement); + + this.summaryBar = new WebInspector.SummaryBar(this.categories); + this.summaryBar.element.id = "resources-summary"; + this._containerContentElement.appendChild(this.summaryBar.element); + + this.itemsGraphsElement = document.createElement("div"); + this.itemsGraphsElement.id = "resources-graphs"; + this._containerContentElement.appendChild(this.itemsGraphsElement); + + this.dividersElement = document.createElement("div"); + this.dividersElement.id = "resources-dividers"; + this._containerContentElement.appendChild(this.dividersElement); + + this.eventDividersElement = document.createElement("div"); + this.eventDividersElement.id = "resources-event-dividers"; + this._containerContentElement.appendChild(this.eventDividersElement); + + this.dividersLabelBarElement = document.createElement("div"); + this.dividersLabelBarElement.id = "resources-dividers-label-bar"; + this._containerContentElement.appendChild(this.dividersLabelBarElement); + }, + + updateGraphDividersIfNeeded: function(force) + { + if (!this.visible) { + this.needsRefresh = true; + return false; + } + + if (document.body.offsetWidth <= 0) { + // The stylesheet hasn't loaded yet or the window is closed, + // so we can't calculate what is need. Return early. + return false; + } + + var dividerCount = Math.round(this.dividersElement.offsetWidth / 64); + var slice = this.calculator.boundarySpan / dividerCount; + if (!force && this._currentDividerSlice === slice) + return false; + + this._currentDividerSlice = slice; + + this.dividersElement.removeChildren(); + this.eventDividersElement.removeChildren(); + this.dividersLabelBarElement.removeChildren(); + + for (var i = 1; i <= dividerCount; ++i) { + var divider = document.createElement("div"); + divider.className = "resources-divider"; + if (i === dividerCount) + divider.addStyleClass("last"); + divider.style.left = ((i / dividerCount) * 100) + "%"; + + this.dividersElement.appendChild(divider.cloneNode()); + + var label = document.createElement("div"); + label.className = "resources-divider-label"; + if (!isNaN(slice)) + label.textContent = this.calculator.formatValue(slice * i); + divider.appendChild(label); + + this.dividersLabelBarElement.appendChild(divider); + } + }, + + _updateDividersLabelBarPosition: function() + { + var scrollTop = this.containerElement.scrollTop; + var dividersTop = (scrollTop < this.summaryBar.element.offsetHeight ? this.summaryBar.element.offsetHeight : scrollTop); + this.dividersElement.style.top = scrollTop + "px"; + this.eventDividersElement.style.top = scrollTop + "px"; + this.dividersLabelBarElement.style.top = dividersTop + "px"; + }, + + get needsRefresh() + { + return this._needsRefresh; + }, + + set needsRefresh(x) + { + if (this._needsRefresh === x) + return; + + this._needsRefresh = x; + + if (x) { + if (this.visible && !("_refreshTimeout" in this)) + this._refreshTimeout = setTimeout(this.refresh.bind(this), 500); + } else { + if ("_refreshTimeout" in this) { + clearTimeout(this._refreshTimeout); + delete this._refreshTimeout; + } + } + }, + + refreshIfNeeded: function() + { + if (this.needsRefresh) + this.refresh(); + }, + + show: function() + { + WebInspector.Panel.prototype.show.call(this); + + this._updateDividersLabelBarPosition(); + this.refreshIfNeeded(); + }, + + resize: function() + { + this.updateGraphDividersIfNeeded(); + }, + + updateMainViewWidth: function(width) + { + this._containerContentElement.style.left = width + "px"; + this.updateGraphDividersIfNeeded(); + }, + + refresh: function() + { + this.needsRefresh = false; + + var staleItemsLength = this._staleItems.length; + var boundariesChanged = false; + + for (var i = 0; i < staleItemsLength; ++i) { + var item = this._staleItems[i]; + if (!item._itemTreeElement) { + // Create the timeline tree element and graph. + item._itemTreeElement = this.createItemTreeElement(item); + item._itemTreeElement._itemGraph = this.createItemGraph(item); + + this.itemsTreeElement.appendChild(item._itemTreeElement); + this.itemsGraphsElement.appendChild(item._itemTreeElement._itemGraph.graphElement); + } + + if (item._itemTreeElement.refresh) + item._itemTreeElement.refresh(); + + if (this.calculator.updateBoundaries(item)) + boundariesChanged = true; + } + + if (boundariesChanged) { + // The boundaries changed, so all item graphs are stale. + this._staleItems = this._items; + staleItemsLength = this._staleItems.length; + } + + for (var i = 0; i < staleItemsLength; ++i) + this._staleItems[i]._itemTreeElement._itemGraph.refresh(this.calculator); + + this._staleItems = []; + + this.updateGraphDividersIfNeeded(); + }, + + reset: function() + { + this.containerElement.scrollTop = 0; + + if (this._calculator) + this._calculator.reset(); + + if (this._items) { + var itemsLength = this._items.length; + for (var i = 0; i < itemsLength; ++i) { + var item = this._items[i]; + delete item._itemsTreeElement; + } + } + + this._items = []; + this._staleItems = []; + + this.itemsTreeElement.removeChildren(); + this.itemsGraphsElement.removeChildren(); + + this.updateGraphDividersIfNeeded(true); + }, + + get calculator() + { + return this._calculator; + }, + + set calculator(x) + { + if (!x || this._calculator === x) + return; + + this._calculator = x; + this._calculator.reset(); + + this._staleItems = this._items; + this.refresh(); + }, + + addItem: function(item) + { + this._items.push(item); + this.refreshItem(item); + }, + + removeItem: function(item) + { + this._items.remove(item, true); + + if (item._itemTreeElement) { + this.itemsTreeElement.removeChild(resource._itemTreeElement); + this.itemsGraphsElement.removeChild(resource._itemTreeElement._itemGraph.graphElement); + } + + delete item._itemTreeElement; + this.adjustScrollPosition(); + }, + + refreshItem: function(item) + { + this._staleItems.push(item); + this.needsRefresh = true; + }, + + revealAndSelectItem: function(item) + { + if (item._itemsTreeElement) { + item._itemsTreeElement.reveal(); + item._itemsTreeElement.select(true); + } + }, + + sortItems: function(sortingFunction) + { + var sortedElements = [].concat(this.itemsTreeElement.children); + sortedElements.sort(sortingFunction); + + var sortedElementsLength = sortedElements.length; + for (var i = 0; i < sortedElementsLength; ++i) { + var treeElement = sortedElements[i]; + if (treeElement === this.itemsTreeElement.children[i]) + continue; + + var wasSelected = treeElement.selected; + this.itemsTreeElement.removeChild(treeElement); + this.itemsTreeElement.insertChild(treeElement, i); + if (wasSelected) + treeElement.select(true); + + var graphElement = treeElement._itemGraph.graphElement; + this.itemsGraphsElement.insertBefore(graphElement, this.itemsGraphsElement.children[i]); + } + }, + + adjustScrollPosition: function() + { + // Prevent the container from being scrolled off the end. + if ((this.containerElement.scrollTop + this.containerElement.offsetHeight) > this.sidebarElement.offsetHeight) + this.containerElement.scrollTop = (this.sidebarElement.offsetHeight - this.containerElement.offsetHeight); + } +} + +WebInspector.AbstractTimelinePanel.prototype.__proto__ = WebInspector.Panel.prototype; + +WebInspector.AbstractTimelineCalculator = function() +{ +} + +WebInspector.AbstractTimelineCalculator.prototype = { + computeSummaryValues: function(items) + { + var total = 0; + var categoryValues = {}; + + var itemsLength = items.length; + for (var i = 0; i < itemsLength; ++i) { + var item = items[i]; + var value = this._value(item); + if (typeof value === "undefined") + continue; + if (!(item.category.name in categoryValues)) + categoryValues[item.category.name] = 0; + categoryValues[item.category.name] += value; + total += value; + } + + return {categoryValues: categoryValues, total: total}; + }, + + computeBarGraphPercentages: function(item) + { + return {start: 0, middle: 0, end: (this._value(item) / this.boundarySpan) * 100}; + }, + + computeBarGraphLabels: function(item) + { + const label = this.formatValue(this._value(item)); + return {left: label, right: label, tooltip: label}; + }, + + get boundarySpan() + { + return this.maximumBoundary - this.minimumBoundary; + }, + + updateBoundaries: function(item) + { + this.minimumBoundary = 0; + + var value = this._value(item); + if (typeof this.maximumBoundary === "undefined" || value > this.maximumBoundary) { + this.maximumBoundary = value; + return true; + } + return false; + }, + + reset: function() + { + delete this.minimumBoundary; + delete this.maximumBoundary; + }, + + _value: function(item) + { + return 0; + }, + + formatValue: function(value) + { + return value.toString(); + } +} + +WebInspector.AbstractTimelineCategory = function(name, title, color) +{ + this.name = name; + this.title = title; + this.color = color; +} + +WebInspector.AbstractTimelineCategory.prototype = { + toString: function() + { + return this.title; + } +} |