home *** CD-ROM | disk | FTP | other *** search
/ Computer Active Guide 2009 July / CAG7.ISO / Internetas / SafariSetup.exe / AppleApplicationSupport.msi / WebKit.resources_inspector_TimelinePanel.js < prev    next >
Encoding:
Text File  |  2010-06-03  |  46.4 KB  |  1,051 lines

  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.Panel.call(this);
  34.     this.element.addStyleClass("timeline");
  35.  
  36.     this._overviewPane = new WebInspector.TimelineOverviewPane(this.categories);
  37.     this._overviewPane.addEventListener("window changed", this._windowChanged, this);
  38.     this._overviewPane.addEventListener("filter changed", this._refresh, this);
  39.     this.element.appendChild(this._overviewPane.element);
  40.     this.element.tabIndex = 0;
  41.  
  42.     this._sidebarBackgroundElement = document.createElement("div");
  43.     this._sidebarBackgroundElement.className = "sidebar timeline-sidebar-background";
  44.     this.element.appendChild(this._sidebarBackgroundElement);
  45.  
  46.     this._containerElement = document.createElement("div");
  47.     this._containerElement.id = "timeline-container";
  48.     this._containerElement.addEventListener("scroll", this._onScroll.bind(this), false);
  49.     this.element.appendChild(this._containerElement);
  50.  
  51.     this.createSidebar(this._containerElement, this._containerElement);
  52.     var itemsTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("RECORDS"), {}, true);
  53.     itemsTreeElement.expanded = true;
  54.     this.sidebarTree.appendChild(itemsTreeElement);
  55.  
  56.     this._sidebarListElement = document.createElement("div");
  57.     this.sidebarElement.appendChild(this._sidebarListElement);
  58.  
  59.     this._containerContentElement = document.createElement("div");
  60.     this._containerContentElement.id = "resources-container-content";
  61.     this._containerElement.appendChild(this._containerContentElement);
  62.  
  63.     this._timelineGrid = new WebInspector.TimelineGrid();
  64.     this._itemsGraphsElement = this._timelineGrid.itemsGraphsElement;
  65.     this._itemsGraphsElement.id = "timeline-graphs";
  66.     this._containerContentElement.appendChild(this._timelineGrid.element);
  67.  
  68.     this._topGapElement = document.createElement("div");
  69.     this._topGapElement.className = "timeline-gap";
  70.     this._itemsGraphsElement.appendChild(this._topGapElement);
  71.  
  72.     this._graphRowsElement = document.createElement("div");
  73.     this._itemsGraphsElement.appendChild(this._graphRowsElement);
  74.  
  75.     this._bottomGapElement = document.createElement("div");
  76.     this._bottomGapElement.className = "timeline-gap";
  77.     this._itemsGraphsElement.appendChild(this._bottomGapElement);
  78.  
  79.     this._rootRecord = this._createRootRecord();
  80.     this._sendRequestRecords = {};
  81.     this._timerRecords = {};
  82.  
  83.     this._calculator = new WebInspector.TimelineCalculator();
  84.     this._calculator._showShortEvents = false;
  85.     var shortRecordThresholdTitle = Number.secondsToString(WebInspector.TimelinePanel.shortRecordThreshold, WebInspector.UIString.bind(WebInspector));
  86.     this._showShortRecordsTitleText = WebInspector.UIString("Show the records that are shorter than %s", shortRecordThresholdTitle);
  87.     this._hideShortRecordsTitleText = WebInspector.UIString("Hide the records that are shorter than %s", shortRecordThresholdTitle);
  88.     this._createStatusbarButtons();
  89.  
  90.     this._boundariesAreValid = true;
  91.     this._scrollTop = 0;
  92.  
  93.     this._popoverHelper = new WebInspector.PopoverHelper(this._containerElement, this._getPopoverAnchor.bind(this), this._showPopover.bind(this), true);
  94.  
  95.     // Disable short events filter by default.
  96.     this.toggleFilterButton.toggled = true;
  97.     this._calculator._showShortEvents = this.toggleFilterButton.toggled;
  98.     this._markTimelineRecords = [];
  99.     this._expandOffset = 15;
  100. }
  101.  
  102. WebInspector.TimelinePanel.shortRecordThreshold = 0.015;
  103.  
  104. WebInspector.TimelinePanel.prototype = {
  105.     toolbarItemClass: "timeline",
  106.  
  107.     get toolbarItemLabel()
  108.     {
  109.         return WebInspector.UIString("Timeline");
  110.     },
  111.  
  112.     get statusBarItems()
  113.     {
  114.         return [this.toggleFilterButton.element, this.toggleTimelineButton.element, this.clearButton.element, this.recordsCounter];
  115.     },
  116.  
  117.     get categories()
  118.     {
  119.         if (!this._categories) {
  120.             this._categories = {
  121.                 loading: new WebInspector.TimelineCategory("loading", WebInspector.UIString("Loading"), "rgb(47,102,236)"),
  122.                 scripting: new WebInspector.TimelineCategory("scripting", WebInspector.UIString("Scripting"), "rgb(157,231,119)"),
  123.                 rendering: new WebInspector.TimelineCategory("rendering", WebInspector.UIString("Rendering"), "rgb(164,60,255)")
  124.             };
  125.         }
  126.         return this._categories;
  127.     },
  128.  
  129.     get defaultFocusedElement()
  130.     {
  131.         return this.element;
  132.     },
  133.  
  134.     get _recordStyles()
  135.     {
  136.         if (!this._recordStylesArray) {
  137.             var recordTypes = WebInspector.TimelineAgent.RecordType;
  138.             var recordStyles = {};
  139.             recordStyles[recordTypes.EventDispatch] = { title: WebInspector.UIString("Event"), category: this.categories.scripting };
  140.             recordStyles[recordTypes.Layout] = { title: WebInspector.UIString("Layout"), category: this.categories.rendering };
  141.             recordStyles[recordTypes.RecalculateStyles] = { title: WebInspector.UIString("Recalculate Style"), category: this.categories.rendering };
  142.             recordStyles[recordTypes.Paint] = { title: WebInspector.UIString("Paint"), category: this.categories.rendering };
  143.             recordStyles[recordTypes.ParseHTML] = { title: WebInspector.UIString("Parse"), category: this.categories.loading };
  144.             recordStyles[recordTypes.TimerInstall] = { title: WebInspector.UIString("Install Timer"), category: this.categories.scripting };
  145.             recordStyles[recordTypes.TimerRemove] = { title: WebInspector.UIString("Remove Timer"), category: this.categories.scripting };
  146.             recordStyles[recordTypes.TimerFire] = { title: WebInspector.UIString("Timer Fired"), category: this.categories.scripting };
  147.             recordStyles[recordTypes.XHRReadyStateChange] = { title: WebInspector.UIString("XHR Ready State Change"), category: this.categories.scripting };
  148.             recordStyles[recordTypes.XHRLoad] = { title: WebInspector.UIString("XHR Load"), category: this.categories.scripting };
  149.             recordStyles[recordTypes.EvaluateScript] = { title: WebInspector.UIString("Evaluate Script"), category: this.categories.scripting };
  150.             recordStyles[recordTypes.MarkTimeline] = { title: WebInspector.UIString("Mark"), category: this.categories.scripting };
  151.             recordStyles[recordTypes.ResourceSendRequest] = { title: WebInspector.UIString("Send Request"), category: this.categories.loading };
  152.             recordStyles[recordTypes.ResourceReceiveResponse] = { title: WebInspector.UIString("Receive Response"), category: this.categories.loading };
  153.             recordStyles[recordTypes.ResourceFinish] = { title: WebInspector.UIString("Finish Loading"), category: this.categories.loading };
  154.             recordStyles[recordTypes.FunctionCall] = { title: WebInspector.UIString("Function Call"), category: this.categories.scripting };
  155.             recordStyles[recordTypes.ResourceReceiveData] = { title: WebInspector.UIString("Receive Data"), category: this.categories.loading };
  156.             recordStyles[recordTypes.GCEvent] = { title: WebInspector.UIString("GC Event"), category: this.categories.scripting };
  157.             recordStyles[recordTypes.MarkDOMContentEventType] = { title: WebInspector.UIString("DOMContent event"), category: this.categories.scripting };
  158.             recordStyles[recordTypes.MarkLoadEventType] = { title: WebInspector.UIString("Load event"), category: this.categories.scripting };
  159.             this._recordStylesArray = recordStyles;
  160.         }
  161.         return this._recordStylesArray;
  162.     },
  163.  
  164.     _createStatusbarButtons: function()
  165.     {
  166.         this.toggleTimelineButton = new WebInspector.StatusBarButton(WebInspector.UIString("Record"), "record-profile-status-bar-item");
  167.         this.toggleTimelineButton.addEventListener("click", this._toggleTimelineButtonClicked.bind(this), false);
  168.  
  169.         this.clearButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear"), "timeline-clear-status-bar-item");
  170.         this.clearButton.addEventListener("click", this._clearPanel.bind(this), false);
  171.  
  172.         this.toggleFilterButton = new WebInspector.StatusBarButton(this._hideShortRecordsTitleText, "timeline-filter-status-bar-item");
  173.         this.toggleFilterButton.addEventListener("click", this._toggleFilterButtonClicked.bind(this), false);
  174.  
  175.         this.recordsCounter = document.createElement("span");
  176.         this.recordsCounter.className = "timeline-records-counter";
  177.     },
  178.  
  179.     _updateRecordsCounter: function()
  180.     {
  181.         this.recordsCounter.textContent = WebInspector.UIString("%d of %d captured records are visible", this._rootRecord._visibleRecordsCount, this._rootRecord._allRecordsCount);
  182.     },
  183.  
  184.     _updateEventDividers: function()
  185.     {
  186.         this._timelineGrid.removeEventDividers();
  187.         var clientWidth = this._graphRowsElement.offsetWidth - this._expandOffset;
  188.         var dividers = [];
  189.         for (var i = 0; i < this._markTimelineRecords.length; ++i) {
  190.             var record = this._markTimelineRecords[i];
  191.             var positions = this._calculator.computeBarGraphWindowPosition(record, clientWidth);
  192.             var dividerPosition = Math.round(positions.left);
  193.             if (dividerPosition < 0 || dividerPosition >= clientWidth || dividers[dividerPosition])
  194.                 continue;
  195.             var divider = this._createEventDivider(record);
  196.             divider.style.left = (dividerPosition + this._expandOffset) + "px";
  197.             dividers[dividerPosition] = divider;
  198.         }
  199.         this._timelineGrid.addEventDividers(dividers);
  200.         this._overviewPane.updateEventDividers(this._markTimelineRecords, this._createEventDivider.bind(this));
  201.     },
  202.  
  203.     _createEventDivider: function(record)
  204.     {
  205.         var eventDivider = document.createElement("div");
  206.         eventDivider.className = "resources-event-divider";
  207.         var recordTypes = WebInspector.TimelineAgent.RecordType;
  208.  
  209.         var eventDividerPadding = document.createElement("div");
  210.         eventDividerPadding.className = "resources-event-divider-padding";
  211.         eventDividerPadding.title = record.title;
  212.  
  213.         if (record.type === recordTypes.MarkDOMContentEventType)
  214.             eventDivider.className += " resources-blue-divider";
  215.         else if (record.type === recordTypes.MarkLoadEventType)
  216.             eventDivider.className += " resources-red-divider";
  217.         else if (record.type === recordTypes.MarkTimeline) {
  218.             eventDivider.className += " resources-orange-divider";
  219.             eventDividerPadding.title = record.data.message;
  220.         }
  221.         eventDividerPadding.appendChild(eventDivider);
  222.         return eventDividerPadding;
  223.     },
  224.  
  225.     _toggleTimelineButtonClicked: function()
  226.     {
  227.         if (this.toggleTimelineButton.toggled)
  228.             InspectorBackend.stopTimelineProfiler();
  229.         else {
  230.             this._clearPanel();
  231.             InspectorBackend.startTimelineProfiler();
  232.         }
  233.     },
  234.  
  235.     _toggleFilterButtonClicked: function()
  236.     {
  237.         this.toggleFilterButton.toggled = !this.toggleFilterButton.toggled;
  238.         this._calculator._showShortEvents = this.toggleFilterButton.toggled;
  239.         this.toggleFilterButton.element.title = this._calculator._showShortEvents ? this._hideShortRecordsTitleText : this._showShortRecordsTitleText;
  240.         this._scheduleRefresh(true);
  241.     },
  242.  
  243.     timelineWasStarted: function()
  244.     {
  245.         this.toggleTimelineButton.toggled = true;
  246.     },
  247.  
  248.     timelineWasStopped: function()
  249.     {
  250.         this.toggleTimelineButton.toggled = false;
  251.     },
  252.  
  253.     addRecordToTimeline: function(record)
  254.     {
  255.         if (record.type == WebInspector.TimelineAgent.RecordType.ResourceSendRequest && record.data.isMainResource) {
  256.             if (this._mainResourceIdentifier != record.data.identifier) {
  257.                 // We are loading new main resource -> clear the panel. Check above is necessary since
  258.                 // there may be several resource loads with main resource marker upon redirects, redirects are reported with
  259.                 // the original identifier.
  260.                 this._mainResourceIdentifier = record.data.identifier;
  261.                 this._clearPanel();
  262.             }
  263.         }
  264.         this._innerAddRecordToTimeline(record, this._rootRecord);
  265.         this._scheduleRefresh();
  266.     },
  267.  
  268.     _findParentRecord: function(record)
  269.     {
  270.         var recordTypes = WebInspector.TimelineAgent.RecordType;
  271.         var parentRecord;
  272.         if (record.type === recordTypes.ResourceReceiveResponse ||
  273.             record.type === recordTypes.ResourceFinish ||
  274.             record.type === recordTypes.ResourceReceiveData)
  275.             parentRecord = this._sendRequestRecords[record.data.identifier];
  276.         else if (record.type === recordTypes.TimerFire)
  277.             parentRecord = this._timerRecords[record.data.timerId];
  278.         return parentRecord;
  279.     },
  280.  
  281.     _innerAddRecordToTimeline: function(record, parentRecord)
  282.     {
  283.         var connectedToOldRecord = false;
  284.         var recordTypes = WebInspector.TimelineAgent.RecordType;
  285.         if (record.type === recordTypes.MarkDOMContentEventType || record.type === recordTypes.MarkLoadEventType)
  286.             parentRecord = null; // No bar entry for load events.
  287.         else if (parentRecord === this._rootRecord) {
  288.             var newParentRecord = this._findParentRecord(record);
  289.             if (newParentRecord) {
  290.                 parentRecord = newParentRecord;
  291.                 connectedToOldRecord = true;
  292.             }
  293.         }
  294.  
  295.         if (record.type == recordTypes.TimerFire && record.children && record.children.length === 1) {
  296.             var childRecord = record.children[0];
  297.             if ( childRecord.type === recordTypes.FunctionCall) {
  298.                 record.data.scriptName = childRecord.data.scriptName;
  299.                 record.data.scriptLine = childRecord.data.scriptLine;
  300.                 record.children = childRecord.children;
  301.             }
  302.         }
  303.  
  304.         var formattedRecord = new WebInspector.TimelinePanel.FormattedRecord(record, parentRecord, this._recordStyles, this._sendRequestRecords, this._timerRecords);
  305.  
  306.         if (record.type === recordTypes.MarkDOMContentEventType || record.type === recordTypes.MarkLoadEventType) {
  307.             this._markTimelineRecords.push(formattedRecord);
  308.             return;
  309.         }
  310.  
  311.         ++this._rootRecord._allRecordsCount;
  312.         formattedRecord.collapsed = (parentRecord === this._rootRecord);
  313.  
  314.         var childrenCount = record.children ? record.children.length : 0;
  315.         for (var i = 0; i < childrenCount; ++i)
  316.             this._innerAddRecordToTimeline(record.children[i], formattedRecord);
  317.  
  318.         formattedRecord._calculateAggregatedStats(this.categories);
  319.  
  320.         if (connectedToOldRecord) {
  321.             var record = formattedRecord;
  322.             do {
  323.                 var parent = record.parent;
  324.                 parent._cpuTime += formattedRecord._cpuTime;
  325.                 if (parent._lastChildEndTime < record._lastChildEndTime)
  326.                     parent._lastChildEndTime = record._lastChildEndTime;
  327.                 for (var category in formattedRecord._aggregatedStats)
  328.                     parent._aggregatedStats[category] += formattedRecord._aggregatedStats[category];
  329.                 record = parent;
  330.             } while (record.parent);
  331.         } else
  332.             if (parentRecord !== this._rootRecord)
  333.                 parentRecord._selfTime -= formattedRecord.endTime - formattedRecord.startTime;
  334.  
  335.         // Keep bar entry for mark timeline since nesting might be interesting to the user.
  336.         if (record.type === recordTypes.MarkTimeline)
  337.             this._markTimelineRecords.push(formattedRecord);
  338.     },
  339.  
  340.     setSidebarWidth: function(width)
  341.     {
  342.         WebInspector.Panel.prototype.setSidebarWidth.call(this, width);
  343.         this._sidebarBackgroundElement.style.width = width + "px";
  344.         this._overviewPane.setSidebarWidth(width);
  345.     },
  346.  
  347.     updateMainViewWidth: function(width)
  348.     {
  349.         this._containerContentElement.style.left = width + "px";
  350.         this._scheduleRefresh();
  351.         this._overviewPane.updateMainViewWidth(width);
  352.     },
  353.  
  354.     resize: function()
  355.     {
  356.         this._closeRecordDetails();
  357.         this._scheduleRefresh();
  358.     },
  359.  
  360.     _createRootRecord: function()
  361.     {
  362.         var rootRecord = {};
  363.         rootRecord.children = [];
  364.         rootRecord._visibleRecordsCount = 0;
  365.         rootRecord._allRecordsCount = 0;
  366.         rootRecord._aggregatedStats = {};
  367.         return rootRecord;
  368.     },
  369.  
  370.     _clearPanel: function()
  371.     {
  372.         this._markTimelineRecords = [];
  373.         this._sendRequestRecords = {};
  374.         this._timerRecords = {};
  375.         this._rootRecord = this._createRootRecord();
  376.         this._boundariesAreValid = false;
  377.         this._overviewPane.reset();
  378.         this._adjustScrollPosition(0);
  379.         this._refresh();
  380.         this._closeRecordDetails();
  381.     },
  382.  
  383.     show: function()
  384.     {
  385.         WebInspector.Panel.prototype.show.call(this);
  386.         if (typeof this._scrollTop === "number")
  387.             this._containerElement.scrollTop = this._scrollTop;
  388.         this._refresh();
  389.     },
  390.  
  391.     hide: function()
  392.     {
  393.         WebInspector.Panel.prototype.hide.call(this);
  394.         this._closeRecordDetails();
  395.     },
  396.  
  397.     _onScroll: function(event)
  398.     {
  399.         this._closeRecordDetails();
  400.         var scrollTop = this._containerElement.scrollTop;
  401.         var dividersTop = Math.max(0, scrollTop);
  402.         this._timelineGrid.setScrollAndDividerTop(scrollTop, dividersTop);
  403.         this._scheduleRefresh(true);
  404.     },
  405.  
  406.     _windowChanged: function()
  407.     {
  408.         this._closeRecordDetails();
  409.         this._scheduleRefresh();
  410.     },
  411.  
  412.     _scheduleRefresh: function(preserveBoundaries)
  413.     {
  414.         this._closeRecordDetails();
  415.         this._boundariesAreValid &= preserveBoundaries;
  416.  
  417.         if (!this.visible)
  418.             return;
  419.  
  420.         if (preserveBoundaries)
  421.             this._refresh();
  422.         else
  423.             if (!this._refreshTimeout)
  424.                 this._refreshTimeout = setTimeout(this._refresh.bind(this), 100);
  425.     },
  426.  
  427.     _refresh: function()
  428.     {
  429.         if (this._refreshTimeout) {
  430.             clearTimeout(this._refreshTimeout);
  431.             delete this._refreshTimeout;
  432.         }
  433.  
  434.         this._overviewPane.update(this._rootRecord.children, this._calculator._showShortEvents);
  435.         this._refreshRecords(!this._boundariesAreValid);
  436.         this._updateRecordsCounter();
  437.         if(!this._boundariesAreValid)
  438.             this._updateEventDividers();
  439.         this._boundariesAreValid = true;
  440.     },
  441.  
  442.     _updateBoundaries: function()
  443.     {
  444.         this._calculator.reset();
  445.         this._calculator.windowLeft = this._overviewPane.windowLeft;
  446.         this._calculator.windowRight = this._overviewPane.windowRight;
  447.  
  448.         for (var i = 0; i < this._rootRecord.children.length; ++i)
  449.             this._calculator.updateBoundaries(this._rootRecord.children[i]);
  450.  
  451.         this._calculator.calculateWindow();
  452.     },
  453.  
  454.     _addToRecordsWindow: function(record, recordsWindow, parentIsCollapsed)
  455.     {
  456.         if (!this._calculator._showShortEvents && !record.isLong())
  457.             return;
  458.         var percentages = this._calculator.computeBarGraphPercentages(record);
  459.         if (percentages.start < 100 && percentages.endWithChildren >= 0 && !record.category.hidden) {
  460.             ++this._rootRecord._visibleRecordsCount;
  461.             ++record.parent._invisibleChildrenCount;
  462.             if (!parentIsCollapsed)
  463.                 recordsWindow.push(record);
  464.         }
  465.  
  466.         var index = recordsWindow.length;
  467.         record._invisibleChildrenCount = 0;
  468.         for (var i = 0; i < record.children.length; ++i)
  469.             this._addToRecordsWindow(record.children[i], recordsWindow, parentIsCollapsed || record.collapsed);
  470.         record._visibleChildrenCount = recordsWindow.length - index;
  471.     },
  472.  
  473.     _filterRecords: function()
  474.     {
  475.         var recordsInWindow = [];
  476.         this._rootRecord._visibleRecordsCount = 0;
  477.         for (var i = 0; i < this._rootRecord.children.length; ++i)
  478.             this._addToRecordsWindow(this._rootRecord.children[i], recordsInWindow);
  479.         return recordsInWindow;
  480.     },
  481.  
  482.     _refreshRecords: function(updateBoundaries)
  483.     {
  484.         if (updateBoundaries)
  485.             this._updateBoundaries();
  486.  
  487.         var recordsInWindow = this._filterRecords();
  488.  
  489.         // Calculate the visible area.
  490.         this._scrollTop = this._containerElement.scrollTop;
  491.         var visibleTop = this._scrollTop;
  492.         var visibleBottom = visibleTop + this._containerElement.clientHeight;
  493.  
  494.         // Define row height, should be in sync with styles for timeline graphs.
  495.         const rowHeight = 18;
  496.  
  497.         // Convert visible area to visible indexes. Always include top-level record for a visible nested record.
  498.         var startIndex = Math.max(0, Math.min(Math.floor(visibleTop / rowHeight) - 1, recordsInWindow.length - 1));
  499.         var endIndex = Math.min(recordsInWindow.length, Math.ceil(visibleBottom / rowHeight));
  500.  
  501.         // Resize gaps first.
  502.         const top = (startIndex * rowHeight) + "px";
  503.         this._topGapElement.style.height = top;
  504.         this.sidebarElement.style.top = top;
  505.         this.sidebarResizeElement.style.top = top;
  506.         this._bottomGapElement.style.height = (recordsInWindow.length - endIndex) * rowHeight + "px";
  507.  
  508.         // Update visible rows.
  509.         var listRowElement = this._sidebarListElement.firstChild;
  510.         var width = this._graphRowsElement.offsetWidth;
  511.         this._itemsGraphsElement.removeChild(this._graphRowsElement);
  512.         var graphRowElement = this._graphRowsElement.firstChild;
  513.         var scheduleRefreshCallback = this._scheduleRefresh.bind(this, true);
  514.  
  515.         for (var i = startIndex; i < endIndex; ++i) {
  516.             var record = recordsInWindow[i];
  517.             var isEven = !(i % 2);
  518.  
  519.             if (!listRowElement) {
  520.                 listRowElement = new WebInspector.TimelineRecordListRow().element;
  521.                 this._sidebarListElement.appendChild(listRowElement);
  522.             }
  523.             if (!graphRowElement) {
  524.                 graphRowElement = new WebInspector.TimelineRecordGraphRow(this._itemsGraphsElement, scheduleRefreshCallback, rowHeight).element;
  525.                 this._graphRowsElement.appendChild(graphRowElement);
  526.             }
  527.  
  528.             listRowElement.row.update(record, isEven, this._calculator, visibleTop);
  529.             graphRowElement.row.update(record, isEven, this._calculator, width, this._expandOffset, i);
  530.  
  531.             listRowElement = listRowElement.nextSibling;
  532.             graphRowElement = graphRowElement.nextSibling;
  533.         }
  534.  
  535.         // Remove extra rows.
  536.         while (listRowElement) {
  537.             var nextElement = listRowElement.nextSibling;
  538.             listRowElement.row.dispose();
  539.             listRowElement = nextElement;
  540.         }
  541.         while (graphRowElement) {
  542.             var nextElement = graphRowElement.nextSibling;
  543.             graphRowElement.row.dispose();
  544.             graphRowElement = nextElement;
  545.         }
  546.  
  547.         this._itemsGraphsElement.insertBefore(this._graphRowsElement, this._bottomGapElement);
  548.         this.sidebarResizeElement.style.height = this.sidebarElement.clientHeight + "px";
  549.         // Reserve some room for expand / collapse controls to the left for records that start at 0ms.
  550.         var timelinePaddingLeft = this._calculator.windowLeft === 0 ? this._expandOffset : 0;
  551.         if (updateBoundaries)
  552.             this._timelineGrid.updateDividers(true, this._calculator, timelinePaddingLeft);
  553.         this._adjustScrollPosition((recordsInWindow.length + 1) * rowHeight);
  554.     },
  555.  
  556.     _adjustScrollPosition: function(totalHeight)
  557.     {
  558.         // Prevent the container from being scrolled off the end.
  559.         if ((this._containerElement.scrollTop + this._containerElement.offsetHeight) > totalHeight + 1)
  560.             this._containerElement.scrollTop = (totalHeight - this._containerElement.offsetHeight);
  561.     },
  562.  
  563.     _getPopoverAnchor: function(element)
  564.     {
  565.         return element.enclosingNodeOrSelfWithClass("timeline-graph-bar") || element.enclosingNodeOrSelfWithClass("timeline-tree-item");
  566.     },
  567.  
  568.     _showPopover: function(anchor)
  569.     {
  570.         var record = anchor.row._record;
  571.         var popover = new WebInspector.Popover(record._generatePopupContent(this._calculator, this.categories));
  572.         popover.show(anchor);
  573.         return popover;
  574.     },
  575.  
  576.     _closeRecordDetails: function()
  577.     {
  578.         this._popoverHelper.hidePopup();
  579.     }
  580. }
  581.  
  582. WebInspector.TimelinePanel.prototype.__proto__ = WebInspector.Panel.prototype;
  583.  
  584. WebInspector.TimelineCategory = function(name, title, color)
  585. {
  586.     this.name = name;
  587.     this.title = title;
  588.     this.color = color;
  589. }
  590.  
  591. WebInspector.TimelineCalculator = function()
  592. {
  593.     this.reset();
  594.     this.windowLeft = 0.0;
  595.     this.windowRight = 1.0;
  596.     this._uiString = WebInspector.UIString.bind(WebInspector);
  597. }
  598.  
  599. WebInspector.TimelineCalculator.prototype = {
  600.     computeBarGraphPercentages: function(record)
  601.     {
  602.         var start = (record.startTime - this.minimumBoundary) / this.boundarySpan * 100;
  603.         var end = (record.startTime + record._selfTime - this.minimumBoundary) / this.boundarySpan * 100;
  604.         var endWithChildren = (record._lastChildEndTime - this.minimumBoundary) / this.boundarySpan * 100;
  605.         var cpuWidth = record._cpuTime / this.boundarySpan * 100;
  606.         return {start: start, end: end, endWithChildren: endWithChildren, cpuWidth: cpuWidth};
  607.     },
  608.  
  609.     computeBarGraphWindowPosition: function(record, clientWidth)
  610.     {
  611.         const minWidth = 5;
  612.         const borderWidth = 4;
  613.         var workingArea = clientWidth - minWidth - borderWidth;
  614.         var percentages = this.computeBarGraphPercentages(record);
  615.         var left = percentages.start / 100 * workingArea;
  616.         var width = (percentages.end - percentages.start) / 100 * workingArea + minWidth;
  617.         var widthWithChildren =  (percentages.endWithChildren - percentages.start) / 100 * workingArea;
  618.         var cpuWidth = percentages.cpuWidth / 100 * workingArea + minWidth;
  619.         if (percentages.endWithChildren > percentages.end)
  620.             widthWithChildren += borderWidth + minWidth;
  621.         return {left: left, width: width, widthWithChildren: widthWithChildren, cpuWidth: cpuWidth};
  622.     },
  623.  
  624.     calculateWindow: function()
  625.     {
  626.         this.minimumBoundary = this._absoluteMinimumBoundary + this.windowLeft * (this._absoluteMaximumBoundary - this._absoluteMinimumBoundary);
  627.         this.maximumBoundary = this._absoluteMinimumBoundary + this.windowRight * (this._absoluteMaximumBoundary - this._absoluteMinimumBoundary);
  628.         this.boundarySpan = this.maximumBoundary - this.minimumBoundary;
  629.     },
  630.  
  631.     reset: function()
  632.     {
  633.         this._absoluteMinimumBoundary = -1;
  634.         this._absoluteMaximumBoundary = -1;
  635.     },
  636.  
  637.     updateBoundaries: function(record)
  638.     {
  639.         var lowerBound = record.startTime;
  640.         if (this._absoluteMinimumBoundary === -1 || lowerBound < this._absoluteMinimumBoundary)
  641.             this._absoluteMinimumBoundary = lowerBound;
  642.  
  643.         const minimumTimeFrame = 0.1;
  644.         const minimumDeltaForZeroSizeEvents = 0.01;
  645.         var upperBound = Math.max(record._lastChildEndTime + minimumDeltaForZeroSizeEvents, lowerBound + minimumTimeFrame);
  646.         if (this._absoluteMaximumBoundary === -1 || upperBound > this._absoluteMaximumBoundary)
  647.             this._absoluteMaximumBoundary = upperBound;
  648.     },
  649.  
  650.     formatValue: function(value)
  651.     {
  652.         return Number.secondsToString(value + this.minimumBoundary - this._absoluteMinimumBoundary, this._uiString);
  653.     }
  654. }
  655.  
  656.  
  657. WebInspector.TimelineRecordListRow = function()
  658. {
  659.     this.element = document.createElement("div");
  660.     this.element.row = this;
  661.     this.element.style.cursor = "pointer";
  662.     var iconElement = document.createElement("span");
  663.     iconElement.className = "timeline-tree-icon";
  664.     this.element.appendChild(iconElement);
  665.  
  666.     this._typeElement = document.createElement("span");
  667.     this._typeElement.className = "type";
  668.     this.element.appendChild(this._typeElement);
  669.  
  670.     var separatorElement = document.createElement("span");
  671.     separatorElement.className = "separator";
  672.     separatorElement.textContent = " ";
  673.  
  674.     this._dataElement = document.createElement("span");
  675.     this._dataElement.className = "data dimmed";
  676.  
  677.     this.element.appendChild(separatorElement);
  678.     this.element.appendChild(this._dataElement);
  679. }
  680.  
  681. WebInspector.TimelineRecordListRow.prototype = {
  682.     update: function(record, isEven, calculator, offset)
  683.     {
  684.         this._record = record;
  685.         this._calculator = calculator;
  686.         this._offset = offset;
  687.  
  688.         this.element.className = "timeline-tree-item timeline-category-" + record.category.name + (isEven ? " even" : "");
  689.         this._typeElement.textContent = record.title;
  690.  
  691.         if (this._dataElement.firstChild)
  692.             this._dataElement.removeChildren();
  693.         if (record.details) {
  694.             var detailsContainer = document.createElement("span");
  695.             if (typeof record.details === "object") {
  696.                 detailsContainer.appendChild(document.createTextNode("("));
  697.                 detailsContainer.appendChild(record.details);
  698.                 detailsContainer.appendChild(document.createTextNode(")"));
  699.             } else
  700.                 detailsContainer.textContent = "(" + record.details + ")";
  701.             this._dataElement.appendChild(detailsContainer);
  702.         }
  703.     },
  704.  
  705.     dispose: function()
  706.     {
  707.         this.element.parentElement.removeChild(this.element);
  708.     }
  709. }
  710.  
  711. WebInspector.TimelineRecordGraphRow = function(graphContainer, scheduleRefresh, rowHeight)
  712. {
  713.     this.element = document.createElement("div");
  714.     this.element.row = this;
  715.  
  716.     this._barAreaElement = document.createElement("div");
  717.     this._barAreaElement.className = "timeline-graph-bar-area";
  718.     this.element.appendChild(this._barAreaElement);
  719.  
  720.     this._barWithChildrenElement = document.createElement("div");
  721.     this._barWithChildrenElement.className = "timeline-graph-bar with-children";
  722.     this._barWithChildrenElement.row = this;
  723.     this._barAreaElement.appendChild(this._barWithChildrenElement);
  724.  
  725.     this._barCpuElement = document.createElement("div");
  726.     this._barCpuElement.className = "timeline-graph-bar cpu"
  727.     this._barCpuElement.row = this;
  728.     this._barAreaElement.appendChild(this._barCpuElement);
  729.  
  730.     this._barElement = document.createElement("div");
  731.     this._barElement.className = "timeline-graph-bar";
  732.     this._barElement.row = this;
  733.     this._barAreaElement.appendChild(this._barElement);
  734.  
  735.     this._expandElement = document.createElement("div");
  736.     this._expandElement.className = "timeline-expandable";
  737.     graphContainer.appendChild(this._expandElement);
  738.  
  739.     var leftBorder = document.createElement("div");
  740.     leftBorder.className = "timeline-expandable-left";
  741.     this._expandElement.appendChild(leftBorder);
  742.  
  743.     this._expandElement.addEventListener("click", this._onClick.bind(this));
  744.     this._rowHeight = rowHeight;
  745.  
  746.     this._scheduleRefresh = scheduleRefresh;
  747. }
  748.  
  749. WebInspector.TimelineRecordGraphRow.prototype = {
  750.     update: function(record, isEven, calculator, clientWidth, expandOffset, index)
  751.     {
  752.         this._record = record;
  753.         this.element.className = "timeline-graph-side timeline-category-" + record.category.name + (isEven ? " even" : "");
  754.         var barPosition = calculator.computeBarGraphWindowPosition(record, clientWidth - expandOffset);
  755.         this._barWithChildrenElement.style.left = barPosition.left + expandOffset + "px";
  756.         this._barWithChildrenElement.style.width = barPosition.widthWithChildren + "px";
  757.         this._barElement.style.left = barPosition.left + expandOffset + "px";
  758.         this._barElement.style.width =  barPosition.width + "px";
  759.         this._barCpuElement.style.left = barPosition.left + expandOffset + "px";
  760.         this._barCpuElement.style.width = barPosition.cpuWidth + "px";
  761.  
  762.         if (record._visibleChildrenCount || record._invisibleChildrenCount) {
  763.             this._expandElement.style.top = index * this._rowHeight + "px";
  764.             this._expandElement.style.left = barPosition.left + "px";
  765.             this._expandElement.style.width = Math.max(12, barPosition.width + 25) + "px";
  766.             if (!record.collapsed) {
  767.                 this._expandElement.style.height = (record._visibleChildrenCount + 1) * this._rowHeight + "px";
  768.                 this._expandElement.addStyleClass("timeline-expandable-expanded");
  769.                 this._expandElement.removeStyleClass("timeline-expandable-collapsed");
  770.             } else {
  771.                 this._expandElement.style.height = this._rowHeight + "px";
  772.                 this._expandElement.addStyleClass("timeline-expandable-collapsed");
  773.                 this._expandElement.removeStyleClass("timeline-expandable-expanded");
  774.             }
  775.             this._expandElement.removeStyleClass("hidden");
  776.         } else {
  777.             this._expandElement.addStyleClass("hidden");
  778.         }
  779.     },
  780.  
  781.     _onClick: function(event)
  782.     {
  783.         this._record.collapsed = !this._record.collapsed;
  784.         this._scheduleRefresh();
  785.     },
  786.  
  787.     dispose: function()
  788.     {
  789.         this.element.parentElement.removeChild(this.element);
  790.         this._expandElement.parentElement.removeChild(this._expandElement);
  791.     }
  792. }
  793.  
  794. WebInspector.TimelinePanel.FormattedRecord = function(record, parentRecord, recordStyles, sendRequestRecords, timerRecords)
  795. {
  796.     var recordTypes = WebInspector.TimelineAgent.RecordType;
  797.     var style = recordStyles[record.type];
  798.  
  799.     this.parent = parentRecord;
  800.     if (parentRecord)
  801.         parentRecord.children.push(this);
  802.     this.category = style.category;
  803.     this.title = style.title;
  804.     this.startTime = record.startTime / 1000;
  805.     this.data = record.data;
  806.     this.type = record.type;
  807.     this.endTime = (typeof record.endTime !== "undefined") ? record.endTime / 1000 : this.startTime;
  808.     this._selfTime = this.endTime - this.startTime;
  809.     this._lastChildEndTime = this.endTime;
  810.     this.originalRecordForTests = record;
  811.     this.callerScriptName = record.callerScriptName;
  812.     this.callerScriptLine = record.callerScriptLine;
  813.     this.totalHeapSize = record.totalHeapSize;
  814.     this.usedHeapSize = record.usedHeapSize;
  815.  
  816.     // Make resource receive record last since request was sent; make finish record last since response received.
  817.     if (record.type === recordTypes.ResourceSendRequest) {
  818.         sendRequestRecords[record.data.identifier] = this;
  819.     } else if (record.type === recordTypes.ResourceReceiveResponse) {
  820.         var sendRequestRecord = sendRequestRecords[record.data.identifier];
  821.         if (sendRequestRecord) { // False if we started instrumentation in the middle of request.
  822.             record.data.url = sendRequestRecord.data.url;
  823.             // Now that we have resource in the collection, recalculate details in order to display short url.
  824.             sendRequestRecord.details = this._getRecordDetails(sendRequestRecord, sendRequestRecords);
  825.         }
  826.     } else if (record.type === recordTypes.ResourceReceiveData) {
  827.         var sendRequestRecord = sendRequestRecords[record.data.identifier];
  828.         if (sendRequestRecord) // False for main resource.
  829.             record.data.url = sendRequestRecord.data.url;
  830.     } else if (record.type === recordTypes.ResourceFinish) {
  831.         var sendRequestRecord = sendRequestRecords[record.data.identifier];
  832.         if (sendRequestRecord) // False for main resource.
  833.             record.data.url = sendRequestRecord.data.url;
  834.     } else if (record.type === recordTypes.TimerInstall) {
  835.         this.timeout = record.data.timeout;
  836.         this.singleShot = record.data.singleShot;
  837.         timerRecords[record.data.timerId] = this;
  838.     } else if (record.type === recordTypes.TimerFire) {
  839.         var timerInstalledRecord = timerRecords[record.data.timerId];
  840.         if (timerInstalledRecord) {
  841.             this.callSiteScriptName = timerInstalledRecord.callerScriptName;
  842.             this.callSiteScriptLine = timerInstalledRecord.callerScriptLine;
  843.             this.timeout = timerInstalledRecord.timeout;
  844.             this.singleShot = timerInstalledRecord.singleShot;
  845.         }
  846.     }
  847.     this.details = this._getRecordDetails(record, sendRequestRecords);
  848. }
  849.  
  850. WebInspector.TimelinePanel.FormattedRecord.prototype = {
  851.     isLong: function()
  852.     {
  853.         return (this._lastChildEndTime - this.startTime) > WebInspector.TimelinePanel.shortRecordThreshold;
  854.     },
  855.  
  856.     get children()
  857.     {
  858.         if (!this._children)
  859.             this._children = [];
  860.         return this._children;
  861.     },
  862.  
  863.     _generateAggregatedInfo: function()
  864.     {
  865.         var cell = document.createElement("span");
  866.         cell.className = "timeline-aggregated-info";
  867.         for (var index in this._aggregatedStats) {
  868.             var label = document.createElement("div");
  869.             label.className = "timeline-aggregated-category timeline-" + index;
  870.             cell.appendChild(label);
  871.             var text = document.createElement("span");
  872.             text.textContent = Number.secondsToString(this._aggregatedStats[index] + 0.0001);
  873.             cell.appendChild(text);
  874.         }
  875.         return cell;
  876.     },
  877.  
  878.     _generatePopupContent: function(calculator, categories)
  879.     {
  880.         var contentHelper = new WebInspector.TimelinePanel.PopupContentHelper(this.title);
  881.  
  882.         if (this._children && this._children.length) {
  883.             contentHelper._appendTextRow("Self Time", Number.secondsToString(this._selfTime + 0.0001));
  884.             contentHelper._appendElementRow("Aggregated Time", this._generateAggregatedInfo());
  885.         }
  886.         var text = Number.secondsToString(this._lastChildEndTime - this.startTime) + " (@" +
  887.             calculator.formatValue(this.startTime - calculator.minimumBoundary) + ")";
  888.         contentHelper._appendTextRow("Duration", text);
  889.  
  890.         const recordTypes = WebInspector.TimelineAgent.RecordType;
  891.  
  892.         switch (this.type) {
  893.             case recordTypes.GCEvent:
  894.                 contentHelper._appendTextRow("Collected", Number.bytesToString(this.data.usedHeapSizeDelta));
  895.                 break;
  896.             case recordTypes.TimerInstall:
  897.             case recordTypes.TimerFire:
  898.             case recordTypes.TimerRemove:
  899.                 contentHelper._appendTextRow("Timer Id", this.data.timerId);
  900.                 if (typeof this.timeout === "number") {
  901.                     contentHelper._appendTextRow("Timeout", this.timeout);
  902.                     contentHelper._appendTextRow("Repeats", !this.singleShot);
  903.                 }
  904.                 if (typeof this.callSiteScriptLine === "number")
  905.                     contentHelper._appendLinkRow("Call Site", this.callSiteScriptName, this.callSiteScriptLine);
  906.                 break;
  907.             case recordTypes.FunctionCall:
  908.                 contentHelper._appendLinkRow("Location", this.data.scriptName, this.data.scriptLine);
  909.                 break;
  910.             case recordTypes.ResourceSendRequest:
  911.             case recordTypes.ResourceReceiveResponse:
  912.             case recordTypes.ResourceReceiveData:
  913.             case recordTypes.ResourceFinish:
  914.                 contentHelper._appendLinkRow("Resource", this.data.url);
  915.                 if (this.data.requestMethod)
  916.                     contentHelper._appendTextRow("Request Method", this.data.requestMethod);
  917.                 if (typeof this.data.statusCode === "number")
  918.                     contentHelper._appendTextRow("Status Code", this.data.statusCode);
  919.                 if (this.data.mimeType)
  920.                     contentHelper._appendTextRow("Mime Type", this.data.mimeType);
  921.                 if (typeof this.data.expectedContentLength === "number" && this.data.expectedContentLength !== -1)
  922.                     contentHelper._appendTextRow("Expected Content Length", this.data.expectedContentLength);
  923.                 break;
  924.             case recordTypes.EvaluateScript:
  925.                 if (this.data && this.data.url)
  926.                     contentHelper._appendLinkRow("Script", this.data.url, this.data.lineNumber);
  927.                 break;
  928.             case recordTypes.Paint:
  929.                 contentHelper._appendTextRow("Location", this.data.x + "\u2009\u00d7\u2009" + this.data.y);
  930.                 contentHelper._appendTextRow("Dimensions", this.data.width + "\u2009\u00d7\u2009" + this.data.height);
  931.             default:
  932.                 if (this.details)
  933.                     contentHelper._appendTextRow("Details", this.details);
  934.                 break;
  935.         }
  936.  
  937.         if (this.data.scriptName && this.type !== recordTypes.FunctionCall)
  938.             contentHelper._appendLinkRow("Function Call", this.data.scriptName, this.data.scriptLine);
  939.  
  940.         if (this.callerScriptName && this.type !== recordTypes.GCEvent)
  941.             contentHelper._appendLinkRow("Caller", this.callerScriptName, this.callerScriptLine);
  942.  
  943.         if (this.usedHeapSize)
  944.             contentHelper._appendTextRow("Used Heap Size", WebInspector.UIString("%s of %s", Number.bytesToString(this.usedHeapSize), Number.bytesToString(this.totalHeapSize)));
  945.  
  946.         return contentHelper._contentTable;
  947.     },
  948.  
  949.     _getRecordDetails: function(record, sendRequestRecords)
  950.     {
  951.         switch (record.type) {
  952.             case WebInspector.TimelineAgent.RecordType.GCEvent:
  953.                 return WebInspector.UIString("%s collected", Number.bytesToString(record.data.usedHeapSizeDelta));
  954.             case WebInspector.TimelineAgent.RecordType.TimerFire:
  955.                 return record.data.scriptName ? WebInspector.linkifyResourceAsNode(record.data.scriptName, "scripts", record.data.scriptLine) : record.data.timerId;
  956.             case WebInspector.TimelineAgent.RecordType.FunctionCall:
  957.                 return record.data.scriptName ? WebInspector.linkifyResourceAsNode(record.data.scriptName, "scripts", record.data.scriptLine) : null;
  958.             case WebInspector.TimelineAgent.RecordType.EventDispatch:
  959.                 return record.data ? record.data.type : null;
  960.             case WebInspector.TimelineAgent.RecordType.Paint:
  961.                 return record.data.width + "\u2009\u00d7\u2009" + record.data.height;
  962.             case WebInspector.TimelineAgent.RecordType.TimerInstall:
  963.             case WebInspector.TimelineAgent.RecordType.TimerRemove:
  964.                 return this.callerScriptName ? WebInspector.linkifyResourceAsNode(this.callerScriptName, "scripts", this.callerScriptLine) : record.data.timerId;
  965.             case WebInspector.TimelineAgent.RecordType.ParseHTML:
  966.             case WebInspector.TimelineAgent.RecordType.RecalculateStyles:
  967.                 return this.callerScriptName ? WebInspector.linkifyResourceAsNode(this.callerScriptName, "scripts", this.callerScriptLine) : null;
  968.             case WebInspector.TimelineAgent.RecordType.EvaluateScript:
  969.                 return record.data.url ? WebInspector.linkifyResourceAsNode(record.data.url, "scripts", record.data.lineNumber) : null;
  970.             case WebInspector.TimelineAgent.RecordType.XHRReadyStateChange:
  971.             case WebInspector.TimelineAgent.RecordType.XHRLoad:
  972.             case WebInspector.TimelineAgent.RecordType.ResourceSendRequest:
  973.             case WebInspector.TimelineAgent.RecordType.ResourceReceiveData:
  974.             case WebInspector.TimelineAgent.RecordType.ResourceReceiveResponse:
  975.             case WebInspector.TimelineAgent.RecordType.ResourceFinish:
  976.                 return WebInspector.displayNameForURL(record.data.url);
  977.             case WebInspector.TimelineAgent.RecordType.MarkTimeline:
  978.                 return record.data.message;
  979.             default:
  980.                 return null;
  981.         }
  982.     },
  983.  
  984.     _calculateAggregatedStats: function(categories)
  985.     {
  986.         this._aggregatedStats = {};
  987.         for (var category in categories)
  988.             this._aggregatedStats[category] = 0;
  989.         this._cpuTime = this._selfTime;
  990.  
  991.         if (this._children) {
  992.             for (var index = this._children.length; index; --index) {
  993.                 var child = this._children[index - 1];
  994.                 this._aggregatedStats[child.category.name] += child._selfTime;
  995.                 for (var category in categories)
  996.                     this._aggregatedStats[category] += child._aggregatedStats[category];
  997.             }
  998.             for (var category in this._aggregatedStats)
  999.                 this._cpuTime += this._aggregatedStats[category];
  1000.         }
  1001.     }
  1002. }
  1003.  
  1004. WebInspector.TimelinePanel.PopupContentHelper = function(title)
  1005. {
  1006.     this._contentTable = document.createElement("table");;
  1007.     var titleCell = this._createCell(WebInspector.UIString("%s - Details", title), "timeline-details-title");
  1008.     titleCell.colSpan = 2;
  1009.     var titleRow = document.createElement("tr");
  1010.     titleRow.appendChild(titleCell);
  1011.     this._contentTable.appendChild(titleRow);
  1012. }
  1013.  
  1014. WebInspector.TimelinePanel.PopupContentHelper.prototype = {
  1015.     _createCell: function(content, styleName)
  1016.     {
  1017.         var text = document.createElement("label");
  1018.         text.appendChild(document.createTextNode(content));
  1019.         var cell = document.createElement("td");
  1020.         cell.className = "timeline-details";
  1021.         if (styleName)
  1022.             cell.className += " " + styleName;
  1023.         cell.textContent = content;
  1024.         return cell;
  1025.     },
  1026.  
  1027.     _appendTextRow: function(title, content)
  1028.     {
  1029.         var row = document.createElement("tr");
  1030.         row.appendChild(this._createCell(WebInspector.UIString(title), "timeline-details-row-title"));
  1031.         row.appendChild(this._createCell(content, "timeline-details-row-data"));
  1032.         this._contentTable.appendChild(row);
  1033.     },
  1034.  
  1035.     _appendElementRow: function(title, content)
  1036.     {
  1037.         var row = document.createElement("tr");
  1038.         row.appendChild(this._createCell(WebInspector.UIString(title), "timeline-details-row-title"));
  1039.         var cell = document.createElement("td");
  1040.         cell.appendChild(content);
  1041.         row.appendChild(cell);
  1042.         this._contentTable.appendChild(row);
  1043.     },
  1044.  
  1045.     _appendLinkRow: function(title, scriptName, scriptLine)
  1046.     {
  1047.         var link = WebInspector.linkifyResourceAsNode(scriptName, "scripts", scriptLine, "timeline-details");
  1048.         this._appendElementRow(title, link);
  1049.     }
  1050. }
  1051.