/* * 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: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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. * * Neither the name of Google Inc. 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 THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT * OWNER OR 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.TimelinePanel = function() { WebInspector.AbstractTimelinePanel.call(this); this.element.addStyleClass("timeline"); this.createInterface(); this.summaryBar.element.id = "timeline-summary"; this.itemsGraphsElement.id = "timeline-graphs"; this._createStatusbarButtons(); this.calculator = new WebInspector.TimelineCalculator(); this.filter(this.filterAllElement, false); } WebInspector.TimelinePanel.prototype = { toolbarItemClass: "timeline", get toolbarItemLabel() { return WebInspector.UIString("Timeline"); }, get statusBarItems() { return [this.toggleTimelineButton.element, this.clearButton.element]; }, get categories() { if (!this._categories) { this._categories = { loading: new WebInspector.TimelineCategory("loading", WebInspector.UIString("Loading"), "rgb(47,102,236)"), scripting: new WebInspector.TimelineCategory("scripting", WebInspector.UIString("Scripting"), "rgb(157,231,119)"), rendering: new WebInspector.TimelineCategory("rendering", WebInspector.UIString("Rendering"), "rgb(164,60,255)"), other: new WebInspector.TimelineCategory("other", WebInspector.UIString("Other"), "rgb(186,186,186)") }; } return this._categories; }, populateSidebar: function() { this.itemsTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("RECORDS"), {}, true); this.itemsTreeElement.expanded = true; this.sidebarTree.appendChild(this.itemsTreeElement); }, _createStatusbarButtons: function() { this.toggleTimelineButton = new WebInspector.StatusBarButton("", "record-profile-status-bar-item"); this.toggleTimelineButton.addEventListener("click", this._toggleTimelineButtonClicked.bind(this), false); this.clearButton = new WebInspector.StatusBarButton("", "timeline-clear-status-bar-item"); this.clearButton.addEventListener("click", this.reset.bind(this), false); }, timelineWasStarted: function() { this.toggleTimelineButton.toggled = true; }, timelineWasStopped: function() { this.toggleTimelineButton.toggled = false; }, addRecordToTimeline: function(record) { var formattedRecord = this._formatRecord(record); // Glue subsequent records with same category and title together if they are closer than 100ms to each other. if (this._lastRecord && (!record.children || !record.children.length) && this._lastRecord.category == formattedRecord.category && this._lastRecord.title == formattedRecord.title && this._lastRecord.details == formattedRecord.details && formattedRecord.startTime - this._lastRecord.endTime < 0.1) { this._lastRecord.endTime = formattedRecord.endTime; this._lastRecord.count++; this.refreshItem(this._lastRecord); } else { this.addItem(formattedRecord); for (var i = 0; record.children && i < record.children.length; ++i) this.addRecordToTimeline(record.children[i]); this._lastRecord = record.children && record.children.length ? null : formattedRecord; } }, createItemTreeElement: function(item) { return new WebInspector.TimelineRecordTreeElement(item); }, createItemGraph: function(item) { return new WebInspector.TimelineGraph(item); }, _toggleTimelineButtonClicked: function() { if (InspectorController.timelineProfilerEnabled()) InspectorController.stopTimelineProfiler(); else InspectorController.startTimelineProfiler(); }, _formatRecord: function(record) { if (!this._recordStyles) { this._recordStyles = {}; var recordTypes = WebInspector.TimelineAgent.RecordType; this._recordStyles[recordTypes.DOMDispatch] = { title: WebInspector.UIString("DOM Event"), category: this.categories.scripting }; this._recordStyles[recordTypes.Layout] = { title: WebInspector.UIString("Layout"), category: this.categories.rendering }; this._recordStyles[recordTypes.RecalculateStyles] = { title: WebInspector.UIString("Recalculate Style"), category: this.categories.rendering }; this._recordStyles[recordTypes.Paint] = { title: WebInspector.UIString("Paint"), category: this.categories.rendering }; this._recordStyles[recordTypes.ParseHTML] = { title: WebInspector.UIString("Parse"), category: this.categories.loading }; this._recordStyles[recordTypes.TimerInstall] = { title: WebInspector.UIString("Install Timer"), category: this.categories.scripting }; this._recordStyles[recordTypes.TimerRemove] = { title: WebInspector.UIString("Remove Timer"), category: this.categories.scripting }; this._recordStyles[recordTypes.TimerFire] = { title: WebInspector.UIString("Timer Fired"), category: this.categories.scripting }; this._recordStyles[recordTypes.XHRReadyStateChange] = { title: WebInspector.UIString("XHR Ready State Change"), category: this.categories.scripting }; this._recordStyles[recordTypes.XHRLoad] = { title: WebInspector.UIString("XHR Load"), category: this.categories.scripting }; this._recordStyles[recordTypes.EvaluateScriptTag] = { title: WebInspector.UIString("Evaluate Script"), category: this.categories.scripting }; this._recordStyles["Other"] = { title: WebInspector.UIString("Other"), icon: 0, category: this.categories.other }; } var style = this._recordStyles[record.type]; if (!style) style = this._recordStyles["Other"]; var formattedRecord = {}; formattedRecord.category = style.category; formattedRecord.title = style.title; formattedRecord.startTime = record.startTime / 1000; formattedRecord.data = record.data; formattedRecord.count = 1; formattedRecord.type = record.type; formattedRecord.details = this._getRecordDetails(record); formattedRecord.endTime = (typeof record.endTime !== "undefined") ? record.endTime / 1000 : formattedRecord.startTime; return formattedRecord; }, _getRecordDetails: function(record) { switch (record.type) { case WebInspector.TimelineAgent.RecordType.DOMDispatch: return record.data.type; case WebInspector.TimelineAgent.RecordType.TimerInstall: case WebInspector.TimelineAgent.RecordType.TimerRemove: case WebInspector.TimelineAgent.RecordType.TimerFire: return record.data.timerId; case WebInspector.TimelineAgent.RecordType.XHRReadyStateChange: case WebInspector.TimelineAgent.RecordType.XHRLoad: case WebInspector.TimelineAgent.RecordType.EvaluateScriptTag: return record.data.url; default: return ""; } }, reset: function() { WebInspector.AbstractTimelinePanel.prototype.reset.call(this); this._lastRecord = null; } } WebInspector.TimelinePanel.prototype.__proto__ = WebInspector.AbstractTimelinePanel.prototype; WebInspector.TimelineCategory = function(name, title, color) { WebInspector.AbstractTimelineCategory.call(this, name, title, color); } WebInspector.TimelineCategory.prototype = { } WebInspector.TimelineCategory.prototype.__proto__ = WebInspector.AbstractTimelineCategory.prototype; WebInspector.TimelineRecordTreeElement = function(record) { this._record = record; // Pass an empty title, the title gets made later in onattach. TreeElement.call(this, "", null, false); } WebInspector.TimelineRecordTreeElement.prototype = { onattach: function() { this.listItemElement.removeChildren(); this.listItemElement.addStyleClass("timeline-tree-item"); this.listItemElement.addStyleClass("timeline-category-" + this._record.category.name); var iconElement = document.createElement("span"); iconElement.className = "timeline-tree-icon"; this.listItemElement.appendChild(iconElement); this.typeElement = document.createElement("span"); this.typeElement.className = "type"; this.typeElement.textContent = this._record.title; this.listItemElement.appendChild(this.typeElement); if (this._record.details) { var separatorElement = document.createElement("span"); separatorElement.className = "separator"; separatorElement.textContent = " "; var dataElement = document.createElement("span"); dataElement.className = "data"; dataElement.textContent = "(" + this._record.details + ")"; dataElement.addStyleClass("dimmed"); this.listItemElement.appendChild(separatorElement); this.listItemElement.appendChild(dataElement); } }, refresh: function() { if (this._record.count > 1) this.typeElement.textContent = this._record.title + " x " + this._record.count; } } WebInspector.TimelineRecordTreeElement.prototype.__proto__ = TreeElement.prototype; WebInspector.TimelineCalculator = function() { WebInspector.AbstractTimelineCalculator.call(this); } WebInspector.TimelineCalculator.prototype = { computeBarGraphPercentages: function(record) { var start = ((record.startTime - this.minimumBoundary) / this.boundarySpan) * 100; var end = ((record.endTime - this.minimumBoundary) / this.boundarySpan) * 100; return {start: start, end: end}; }, computePercentageFromEventTime: function(eventTime) { return ((eventTime - this.minimumBoundary) / this.boundarySpan) * 100; }, computeBarGraphLabels: function(record) { return {tooltip: record.title}; }, updateBoundaries: function(record) { var didChange = false; var lowerBound = record.startTime; if (typeof this.minimumBoundary === "undefined" || lowerBound < this.minimumBoundary) { this.minimumBoundary = lowerBound; didChange = true; } var upperBound = record.endTime; if (typeof this.maximumBoundary === "undefined" || upperBound > this.maximumBoundary) { this.maximumBoundary = upperBound; didChange = true; } return didChange; }, formatValue: function(value) { return Number.secondsToString(value, WebInspector.UIString.bind(WebInspector)); }, } WebInspector.TimelineCalculator.prototype.__proto__ = WebInspector.AbstractTimelineCalculator.prototype; WebInspector.TimelineGraph = function(record) { this.record = record; this._graphElement = document.createElement("div"); this._graphElement.className = "timeline-graph-side"; this._barAreaElement = document.createElement("div"); this._barAreaElement.className = "timeline-graph-bar-area hidden"; this._graphElement.appendChild(this._barAreaElement); this._barElement = document.createElement("div"); this._barElement.className = "timeline-graph-bar"; this._barAreaElement.appendChild(this._barElement); this._graphElement.addStyleClass("timeline-category-" + record.category.name); } WebInspector.TimelineGraph.prototype = { get graphElement() { return this._graphElement; }, refreshLabelPositions: function() { }, refresh: function(calculator) { var percentages = calculator.computeBarGraphPercentages(this.record); var labels = calculator.computeBarGraphLabels(this.record); this._percentages = percentages; this._barAreaElement.removeStyleClass("hidden"); if (!this._graphElement.hasStyleClass("timeline-category-" + this.record.category.name)) { this._graphElement.removeMatchingStyleClasses("timeline-category-\\w+"); this._graphElement.addStyleClass("timeline-category-" + this.record.category.name); } this._barElement.style.setProperty("left", percentages.start + "%"); this._barElement.style.setProperty("right", (100 - percentages.end) + "%"); var tooltip = (labels.tooltip || ""); this._barElement.title = tooltip; } }