blob: 80279261d737aa0df79ad270655ccd62890db4e3 [file] [log] [blame]
/*
* Copyright (C) 2015 Apple 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.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. 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 INC. 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.TimelineRecordFrame = function(graphDataSource, records)
{
// FIXME: Convert this to a WebInspector.Object subclass, and call super().
// WebInspector.Object.call(this);
this._element = document.createElement("div");
this._element.classList.add(WebInspector.TimelineRecordFrame.StyleClassName);
this._graphDataSource = graphDataSource;
this.records = records || [];
};
// FIXME: Move to a WebInspector.Object subclass and we can remove this.
WebInspector.Object.deprecatedAddConstructorFunctions(WebInspector.TimelineRecordFrame);
WebInspector.TimelineRecordFrame.StyleClassName = "timeline-record-frame";
WebInspector.TimelineRecordFrame.SixtyFpsFrameBudget = 0.0166;
WebInspector.TimelineRecordFrame.MinimumWidthPixels = 6;
WebInspector.TimelineRecordFrame.MinimumMarginPixels = 1;
WebInspector.TimelineRecordFrame.createCombinedFrames = function(records, secondsPerPixel, graphDataSource, createFrameCallback)
{
if (!records.length)
return;
var startTime = graphDataSource.startTime;
var currentTime = graphDataSource.currentTime;
var endTime = graphDataSource.endTime;
var visibleRecords = [];
for (var record of records) {
if (isNaN(record.startTime))
continue;
// If this frame is completely before the bounds of the graph, skip this record.
if (record.endTime < startTime)
continue;
// If this record is completely after the current time or end time, break out now.
// Records are sorted, so all records after this will be beyond the current or end time too.
if (record.startTime > currentTime || record.startTime > endTime)
break;
visibleRecords.push(record);
}
if (!visibleRecords.length)
return;
var minimumFrameDuration = secondsPerPixel * WebInspector.TimelineRecordFrame.MinimumWidthPixels;
var minimumMargin = secondsPerPixel * WebInspector.TimelineRecordFrame.MinimumMarginPixels;
var activeStartTime = NaN;
var activeEndTime = NaN;
var activeRecords = [];
for (var record of visibleRecords) {
// Check if the previous record is far enough away to create the frame.
if (!isNaN(activeStartTime) && (activeStartTime + Math.max(activeEndTime - activeStartTime, minimumFrameDuration) + minimumMargin <= record.startTime)) {
createFrameCallback(activeRecords);
activeRecords = [];
activeStartTime = NaN;
activeEndTime = NaN;
}
// If this is a new frame, peg the start time.
if (isNaN(activeStartTime))
activeStartTime = record.startTime;
// Update the end time to be the maximum we encounter. endTime might be NaN, so "|| 0" to prevent Math.max from returning NaN.
if (!isNaN(record.endTime))
activeEndTime = Math.max(activeEndTime || 0, record.endTime);
activeRecords.push(record);
}
// Create the active frame for the last record if needed.
if (!isNaN(activeStartTime))
createFrameCallback(activeRecords);
};
WebInspector.TimelineRecordFrame.prototype = {
constructor: WebInspector.TimelineRecordFrame,
__proto__: WebInspector.Object.prototype,
// Public
get element()
{
return this._element;
},
get duration()
{
if (this.records.length === 0)
return 0;
return this.records.lastValue.endTime - this.records[0].startTime;
},
get records()
{
return this._records;
},
set records(records)
{
records = records || [];
if (!(records instanceof Array))
records = [records];
this._records = records;
},
refresh(graphDataSource)
{
if (!this.records || !this.records.length)
return false;
var firstRecord = this.records[0];
var frameStartTime = firstRecord.startTime;
// If this frame has no time info, return early.
if (isNaN(frameStartTime))
return false;
var graphStartTime = graphDataSource.startTime;
var graphEndTime = graphDataSource.endTime;
var graphCurrentTime = graphDataSource.currentTime;
// If this frame is completely after the current time, return early.
if (frameStartTime > graphCurrentTime)
return false;
var frameEndTime = this.records.lastValue.endTime;
// If this frame is completely before or after the bounds of the graph, return early.
if (frameEndTime < graphStartTime || frameStartTime > graphEndTime)
return false;
var graphDuration = graphEndTime - graphStartTime;
var recordLeftPosition = (frameStartTime - graphStartTime) / graphDuration;
this._updateElementPosition(this._element, recordLeftPosition, "left");
var recordWidth = ((frameEndTime - graphStartTime) / graphDuration) - recordLeftPosition;
this._updateElementPosition(this._element, recordWidth, "width");
this._updateChildElements(graphDataSource);
return true;
},
// Private
_updateChildElements(graphDataSource)
{
this._element.removeChildren();
console.assert(this.records.length);
if (!this.records.length)
return;
if (graphDataSource.graphHeightSeconds === 0)
return;
// When combining multiple records into a frame, display the record with the longest duration rather than averaging.
var displayRecord = this.records.reduce(function(previousValue, currentValue) {
return currentValue.duration >= previousValue.duration ? currentValue : previousValue;
});
var frameElement = document.createElement("div");
frameElement.classList.add("frame");
this._element.appendChild(frameElement);
var frameHeight = displayRecord.duration / graphDataSource.graphHeightSeconds;
this._updateElementPosition(frameElement, frameHeight, "height");
function createDurationElement(duration, recordType)
{
var element = document.createElement("div");
this._updateElementPosition(element, duration / displayRecord.duration, "height");
element.classList.add("duration");
if (recordType)
element.classList.add(recordType);
return element;
}
for (type in WebInspector.TimelineRecord.Type) {
var recordType = WebInspector.TimelineRecord.Type[type];
var duration = displayRecord.durationForRecords(recordType);
if (duration === 0)
continue;
frameElement.appendChild(createDurationElement.call(this, duration, recordType));
}
if (displayRecord.durationRemainder > 0)
frameElement.appendChild(createDurationElement.call(this, displayRecord.durationRemainder));
// Add "includes-dropped" style class if multiple records are being combined in the frame,
// and one of those records exceeds the 60 fps frame budget.
if (this.records.length > 1 && displayRecord.duration > WebInspector.TimelineRecordFrame.SixtyFpsFrameBudget)
frameElement.classList.add("includes-dropped");
// If the display record is the last combined record and also exceeds the 60 fps budget,
// add a "dropped" element to the right of the frame.
var frameWidth = 1;
var secondsPerPixel = this._graphDataSource.timelineOverview.secondsPerPixel;
var minimumRecordDuration = secondsPerPixel * WebInspector.TimelineRecordFrame.MinimumWidthPixels * 2; // Combine minimum widths of the frame element and dropped element.
if (displayRecord === this.records.lastValue && displayRecord.duration > WebInspector.TimelineRecordFrame.SixtyFpsFrameBudget && displayRecord.duration >= minimumRecordDuration) {
var overflowDuration = displayRecord.duration - WebInspector.TimelineRecordFrame.SixtyFpsFrameBudget;
var droppedElementWidth = overflowDuration / displayRecord.duration;
frameWidth -= droppedElementWidth;
var droppedElement = document.createElement("div");
droppedElement.className = "dropped";
this._element.appendChild(droppedElement);
this._updateElementPosition(droppedElement, frameHeight, "height");
this._updateElementPosition(droppedElement, droppedElementWidth, "width");
}
this._updateElementPosition(frameElement, frameWidth, "width");
},
_updateElementPosition(element, newPosition, property)
{
newPosition *= 100;
newPosition = newPosition.toFixed(2);
var currentPosition = parseFloat(element.style[property]).toFixed(2);
if (currentPosition !== newPosition)
element.style[property] = newPosition + "%";
}
};