summaryrefslogtreecommitdiff
path: root/Source/WebInspectorUI/UserInterface/Views/TimelineRecordFrame.js
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@lorry>2017-06-27 06:07:23 +0000
committerLorry Tar Creator <lorry-tar-importer@lorry>2017-06-27 06:07:23 +0000
commit1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch)
tree46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebInspectorUI/UserInterface/Views/TimelineRecordFrame.js
parent32761a6cee1d0dee366b885b7b9c777e67885688 (diff)
downloadWebKitGtk-tarball-master.tar.gz
Diffstat (limited to 'Source/WebInspectorUI/UserInterface/Views/TimelineRecordFrame.js')
-rw-r--r--Source/WebInspectorUI/UserInterface/Views/TimelineRecordFrame.js285
1 files changed, 285 insertions, 0 deletions
diff --git a/Source/WebInspectorUI/UserInterface/Views/TimelineRecordFrame.js b/Source/WebInspectorUI/UserInterface/Views/TimelineRecordFrame.js
new file mode 100644
index 000000000..15430b133
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Views/TimelineRecordFrame.js
@@ -0,0 +1,285 @@
+/*
+ * 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 = class TimelineRecordFrame extends WebInspector.Object
+{
+ constructor(graphDataSource, record)
+ {
+ super();
+
+ this._element = document.createElement("div");
+ this._element.classList.add("timeline-record-frame");
+
+ this._graphDataSource = graphDataSource;
+ this._record = record || null;
+ this._filtered = false;
+ }
+
+ // Public
+
+ get element()
+ {
+ return this._element;
+ }
+
+ get record()
+ {
+ return this._record;
+ }
+
+ set record(record)
+ {
+ this._record = record;
+ }
+
+ get selected()
+ {
+ return this._element.classList.contains("selected");
+ }
+
+ set selected(x)
+ {
+ if (this.selected === x)
+ return;
+
+ this._element.classList.toggle("selected");
+ }
+
+ get filtered()
+ {
+ return this._filtered;
+ }
+
+ set filtered(x)
+ {
+ if (this._filtered === x)
+ return;
+
+ this._filtered = x;
+ this._element.classList.toggle("filtered");
+ }
+
+ refresh(graphDataSource)
+ {
+ if (!this._record)
+ return false;
+
+ var frameIndex = this._record.frameIndex;
+ var graphStartFrameIndex = Math.floor(graphDataSource.startTime);
+ var graphEndFrameIndex = graphDataSource.endTime;
+
+ // If this frame is completely before or after the bounds of the graph, return early.
+ if (frameIndex < graphStartFrameIndex || frameIndex > graphEndFrameIndex)
+ return false;
+
+ this._element.style.width = (1 / graphDataSource.timelineOverview.secondsPerPixel) + "px";
+
+ var graphDuration = graphDataSource.endTime - graphDataSource.startTime;
+ var recordLeftPosition = (frameIndex - graphDataSource.startTime) / graphDuration;
+ this._updateElementPosition(this._element, recordLeftPosition, "left");
+ this._updateChildElements(graphDataSource);
+
+ return true;
+ }
+
+ // Private
+
+ _calculateFrameDisplayData(graphDataSource)
+ {
+ var secondsPerBlock = (graphDataSource.graphHeightSeconds / graphDataSource.height) * WebInspector.TimelineRecordFrame.MinimumHeightPixels;
+ var segments = [];
+ var invisibleSegments = [];
+ var currentSegment = null;
+
+ function updateDurationRemainder(segment)
+ {
+ if (segment.duration <= secondsPerBlock) {
+ segment.remainder = 0;
+ return;
+ }
+
+ var roundedDuration = Math.roundTo(segment.duration, secondsPerBlock);
+ segment.remainder = Math.max(segment.duration - roundedDuration, 0);
+ }
+
+ function pushCurrentSegment()
+ {
+ updateDurationRemainder(currentSegment);
+ segments.push(currentSegment);
+ if (currentSegment.duration < secondsPerBlock)
+ invisibleSegments.push({segment: currentSegment, index: segments.length - 1});
+
+ currentSegment = null;
+ }
+
+ // Frame segments aren't shown at arbitrary pixel heights, but are divided into blocks of pixels. One block
+ // represents the minimum displayable duration of a rendering frame, in seconds. Contiguous tasks less than a
+ // block high are grouped until the minimum is met, or a task meeting the minimum is found. The group is then
+ // added to the list of segment candidates. Large tasks (one block or more) are not grouped with other tasks
+ // and are simply added to the candidate list.
+ for (var key in WebInspector.RenderingFrameTimelineRecord.TaskType) {
+ var taskType = WebInspector.RenderingFrameTimelineRecord.TaskType[key];
+ var duration = this._record.durationForTask(taskType);
+ if (duration === 0)
+ continue;
+
+ if (currentSegment && duration >= secondsPerBlock)
+ pushCurrentSegment();
+
+ if (!currentSegment)
+ currentSegment = {taskType: null, longestTaskDuration: 0, duration: 0, remainder: 0};
+
+ currentSegment.duration += duration;
+ if (duration > currentSegment.longestTaskDuration) {
+ currentSegment.taskType = taskType;
+ currentSegment.longestTaskDuration = duration;
+ }
+
+ if (currentSegment.duration >= secondsPerBlock)
+ pushCurrentSegment();
+ }
+
+ if (currentSegment)
+ pushCurrentSegment();
+
+ // A frame consisting of a single segment is always visible.
+ if (segments.length === 1) {
+ segments[0].duration = Math.max(segments[0].duration, secondsPerBlock);
+ invisibleSegments = [];
+ }
+
+ // After grouping sub-block tasks, a second pass is needed to handle those groups that are still beneath the
+ // minimum displayable duration. Each sub-block task has one or two adjacent display segments greater than one
+ // block. The rounded-off time from these tasks is added to the sub-block, if it's sufficient to create a full
+ // block. Failing that, the task is merged with an adjacent segment.
+ invisibleSegments.sort(function(a, b) { return a.segment.duration - b.segment.duration; });
+
+ for (var item of invisibleSegments) {
+ var segment = item.segment;
+ var previousSegment = item.index > 0 ? segments[item.index - 1] : null;
+ var nextSegment = item.index < segments.length - 1 ? segments[item.index + 1] : null;
+ console.assert(previousSegment || nextSegment, "Invisible segment should have at least one adjacent visible segment.");
+
+ // Try to increase the segment's size to exactly one block, by taking subblock time from neighboring segments.
+ // If there are two neighbors, the one with greater subblock duration is borrowed from first.
+ var adjacentSegments;
+ var availableDuration;
+ if (previousSegment && nextSegment) {
+ adjacentSegments = previousSegment.remainder > nextSegment.remainder ? [previousSegment, nextSegment] : [nextSegment, previousSegment];
+ availableDuration = previousSegment.remainder + nextSegment.remainder;
+ } else {
+ adjacentSegments = [previousSegment || nextSegment];
+ availableDuration = adjacentSegments[0].remainder;
+ }
+
+ if (availableDuration < (secondsPerBlock - segment.duration)) {
+ // Merge with largest adjacent segment.
+ var targetSegment;
+ if (previousSegment && nextSegment)
+ targetSegment = previousSegment.duration > nextSegment.duration ? previousSegment : nextSegment;
+ else
+ targetSegment = previousSegment || nextSegment;
+
+ targetSegment.duration += segment.duration;
+ updateDurationRemainder(targetSegment);
+ continue;
+ }
+
+ adjacentSegments.forEach(function(adjacentSegment) {
+ if (segment.duration >= secondsPerBlock)
+ return;
+ var remainder = Math.min(secondsPerBlock - segment.duration, adjacentSegment.remainder);
+ segment.duration += remainder;
+ adjacentSegment.remainder -= remainder;
+ });
+ }
+
+ // Round visible segments to the nearest block, and compute the rounded frame duration.
+ var frameDuration = 0;
+ segments = segments.filter(function(segment) {
+ if (segment.duration < secondsPerBlock)
+ return false;
+ segment.duration = Math.roundTo(segment.duration, secondsPerBlock);
+ frameDuration += segment.duration;
+ return true;
+ });
+
+ return {frameDuration, segments};
+ }
+
+ _updateChildElements(graphDataSource)
+ {
+ this._element.removeChildren();
+
+ console.assert(this._record);
+ if (!this._record)
+ return;
+
+ if (graphDataSource.graphHeightSeconds === 0)
+ return;
+
+ var frameElement = document.createElement("div");
+ frameElement.classList.add("frame");
+ this._element.appendChild(frameElement);
+
+ // Display data must be recalculated when the overview graph's vertical axis changes.
+ if (this._record.__displayData && this._record.__displayData.graphHeightSeconds !== graphDataSource.graphHeightSeconds)
+ this._record.__displayData = null;
+
+ if (!this._record.__displayData) {
+ this._record.__displayData = this._calculateFrameDisplayData(graphDataSource);
+ this._record.__displayData.graphHeightSeconds = graphDataSource.graphHeightSeconds;
+ }
+
+ var frameHeight = this._record.__displayData.frameDuration / graphDataSource.graphHeightSeconds;
+ if (frameHeight >= 0.95)
+ this._element.classList.add("tall");
+ else
+ this._element.classList.remove("tall");
+
+ this._updateElementPosition(frameElement, frameHeight, "height");
+
+ for (var segment of this._record.__displayData.segments) {
+ var element = document.createElement("div");
+ this._updateElementPosition(element, segment.duration / this._record.__displayData.frameDuration, "height");
+ element.classList.add("duration", segment.taskType);
+ frameElement.insertBefore(element, frameElement.firstChild);
+ }
+ }
+
+ _updateElementPosition(element, newPosition, property)
+ {
+ newPosition *= 100;
+
+ let newPositionAprox = Math.round(newPosition * 100);
+ let currentPositionAprox = Math.round(parseFloat(element.style[property]) * 100);
+ if (currentPositionAprox !== newPositionAprox)
+ element.style[property] = (newPositionAprox / 100) + "%";
+ }
+};
+
+WebInspector.TimelineRecordFrame.MinimumHeightPixels = 3;
+WebInspector.TimelineRecordFrame.MaximumWidthPixels = 14;
+WebInspector.TimelineRecordFrame.MinimumWidthPixels = 4;