Commit 8b8175d8 authored by timothy@apple.com's avatar timothy@apple.com

Clean up and refactor TimelineDecorations into TimelineRuler.

https://bugs.webkit.org/show_bug.cgi?id=125709

Reviewed by Joseph Pecoraro.

* UserInterface/Main.html:
* UserInterface/OverviewTimelineView.css:
(.timeline-view.overview > .timeline-ruler):
(.timeline-view.overview > .timeline-ruler > .header):
(.timeline-view.overview > .timeline-ruler > .event-markers):
Updated class names and border sides.

* UserInterface/OverviewTimelineView.js:
(WebInspector.OverviewTimelineView):
(WebInspector.OverviewTimelineView.prototype.updateLayout):
Call updateLayout on the TimelineRuler.

* UserInterface/TimelineRuler.css: Renamed from Source/WebInspectorUI/UserInterface/TimelineDecorations.css.
(.timeline-ruler):
(.timeline-ruler > .header):
(.timeline-ruler > .header > .divider):
(.timeline-ruler > .header > .divider > .label):
(.timeline-ruler > .event-markers):
(.timeline-ruler > .event-markers > .event-marker-tooltip):
(.timeline-ruler > .event-markers > .event-marker):
(.timeline-ruler > .event-markers > .event-marker.load-event):
(.timeline-ruler > .event-markers > .event-marker.dom-content-event):
(.timeline-ruler > .event-markers > .event-marker.timestamp):
Updated class names and border sides.

* UserInterface/TimelineRuler.js: Renamed from Source/WebInspectorUI/UserInterface/TimelineDecorations.js.
(WebInspector.TimelineRuler):
(WebInspector.TimelineRuler.prototype.get element):
(WebInspector.TimelineRuler.prototype.get headerElement):
(WebInspector.TimelineRuler.prototype.get allowsClippedLabels):
(WebInspector.TimelineRuler.prototype.set allowsClippedLabels):
(WebInspector.TimelineRuler.prototype.get zeroTime):
(WebInspector.TimelineRuler.prototype.set zeroTime):
(WebInspector.TimelineRuler.prototype.get startTime):
(WebInspector.TimelineRuler.prototype.set startTime):
(WebInspector.TimelineRuler.prototype.get duration):
(WebInspector.TimelineRuler.prototype.set duration):
(WebInspector.TimelineRuler.prototype.get endTime):
(WebInspector.TimelineRuler.prototype.set endTime):
(WebInspector.TimelineRuler.prototype.get secondsPerPixel):
(WebInspector.TimelineRuler.prototype.set secondsPerPixel):
(WebInspector.TimelineRuler.prototype.updateLayout.removeDividerAndSelectNext):
(WebInspector.TimelineRuler.prototype.updateLayout):
(WebInspector.TimelineRuler.prototype._needsLayout):
(WebInspector.TimelineRuler.prototype._recalculate):
Refactor so the times are stored on TimelineRuler and not passed in each time an update is needed.
Support dividers that don't always start at the zero position, allowing a sliding time ruler.
Support for a non-pinned ruler where the end time is not fixed and the scale is specified in
seconds-per-pixel.

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@162407 268f45cc-cd09-0410-ab3c-d52691b4dbfc
parent 50cb8c9a
2013-12-13 Timothy Hatcher <timothy@apple.com>
Clean up and refactor TimelineDecorations into TimelineRuler.
https://bugs.webkit.org/show_bug.cgi?id=125709
Reviewed by Joseph Pecoraro.
* UserInterface/Main.html:
* UserInterface/OverviewTimelineView.css:
(.timeline-view.overview > .timeline-ruler):
(.timeline-view.overview > .timeline-ruler > .header):
(.timeline-view.overview > .timeline-ruler > .event-markers):
Updated class names and border sides.
* UserInterface/OverviewTimelineView.js:
(WebInspector.OverviewTimelineView):
(WebInspector.OverviewTimelineView.prototype.updateLayout):
Call updateLayout on the TimelineRuler.
* UserInterface/TimelineRuler.css: Renamed from Source/WebInspectorUI/UserInterface/TimelineDecorations.css.
(.timeline-ruler):
(.timeline-ruler > .header):
(.timeline-ruler > .header > .divider):
(.timeline-ruler > .header > .divider > .label):
(.timeline-ruler > .event-markers):
(.timeline-ruler > .event-markers > .event-marker-tooltip):
(.timeline-ruler > .event-markers > .event-marker):
(.timeline-ruler > .event-markers > .event-marker.load-event):
(.timeline-ruler > .event-markers > .event-marker.dom-content-event):
(.timeline-ruler > .event-markers > .event-marker.timestamp):
Updated class names and border sides.
* UserInterface/TimelineRuler.js: Renamed from Source/WebInspectorUI/UserInterface/TimelineDecorations.js.
(WebInspector.TimelineRuler):
(WebInspector.TimelineRuler.prototype.get element):
(WebInspector.TimelineRuler.prototype.get headerElement):
(WebInspector.TimelineRuler.prototype.get allowsClippedLabels):
(WebInspector.TimelineRuler.prototype.set allowsClippedLabels):
(WebInspector.TimelineRuler.prototype.get zeroTime):
(WebInspector.TimelineRuler.prototype.set zeroTime):
(WebInspector.TimelineRuler.prototype.get startTime):
(WebInspector.TimelineRuler.prototype.set startTime):
(WebInspector.TimelineRuler.prototype.get duration):
(WebInspector.TimelineRuler.prototype.set duration):
(WebInspector.TimelineRuler.prototype.get endTime):
(WebInspector.TimelineRuler.prototype.set endTime):
(WebInspector.TimelineRuler.prototype.get secondsPerPixel):
(WebInspector.TimelineRuler.prototype.set secondsPerPixel):
(WebInspector.TimelineRuler.prototype.updateLayout.removeDividerAndSelectNext):
(WebInspector.TimelineRuler.prototype.updateLayout):
(WebInspector.TimelineRuler.prototype._needsLayout):
(WebInspector.TimelineRuler.prototype._recalculate):
Refactor so the times are stored on TimelineRuler and not passed in each time an update is needed.
Support dividers that don't always start at the zero position, allowing a sliding time ruler.
Support for a non-pinned ruler where the end time is not fixed and the scale is specified in
seconds-per-pixel.
2014-01-08 Timothy Hatcher <timothy@apple.com>
Improve scroll performance of WebInspector.TreeOutlineDataGridSynchronizer.
......
......@@ -85,7 +85,7 @@
<link rel="stylesheet" href="TimelineContentView.css">
<link rel="stylesheet" href="OverviewTimelineView.css">
<link rel="stylesheet" href="TimelineIcons.css">
<link rel="stylesheet" href="TimelineDecorations.css">
<link rel="stylesheet" href="TimelineRuler.css">
<link rel="stylesheet" href="TimelineDataGrid.css">
<link rel="stylesheet" href="TimelineOverview.css">
<link rel="stylesheet" href="ProfileView.css">
......@@ -343,7 +343,7 @@
<script src="IndeterminateProgressSpinner.js"></script>
<script src="TimelineSidebarPanel.js"></script>
<script src="SourceCodeTimelineTreeElement.js"></script>
<script src="TimelineDecorations.js"></script>
<script src="TimelineRuler.js"></script>
<script src="TimelineDataGrid.js"></script>
<script src="LayoutTimelineDataGrid.js"></script>
<script src="ScriptTimelineDataGrid.js"></script>
......
......@@ -23,7 +23,7 @@
* THE POSSIBILITY OF SUCH DAMAGE.
*/
.timeline-view.overview > .timeline-decorations {
.timeline-view.overview > .timeline-ruler {
position: absolute;
top: 0;
left: 0;
......@@ -31,12 +31,12 @@
bottom: 0;
}
.timeline-view.overview > .timeline-decorations > .header {
border-bottom: 1px solid rgb(200, 200, 200);
.timeline-view.overview > .timeline-ruler > .header {
border-top: 1px solid rgb(200, 200, 200);
height: 23px;
}
.timeline-view.overview > .timeline-decorations > .event-markers {
.timeline-view.overview > .timeline-ruler > .event-markers {
top: 23px;
}
......
......@@ -34,8 +34,8 @@ WebInspector.OverviewTimelineView = function()
this._treeOutlineDataGridSynchronizer = new WebInspector.TreeOutlineDataGridSynchronizer(this._contentTreeOutline, this._dataGrid);
this._timelineDecorations = new WebInspector.TimelineDecorations;
this.element.appendChild(this._timelineDecorations.element);
this._timelineRuler = new WebInspector.TimelineRuler;
this.element.appendChild(this._timelineRuler.element);
this.element.classList.add(WebInspector.OverviewTimelineView.StyleClassName);
this.element.appendChild(this._dataGrid.element);
......@@ -71,6 +71,11 @@ WebInspector.OverviewTimelineView.prototype = {
this._treeOutlineDataGridSynchronizer.synchronize();
},
updateLayout: function()
{
this._timelineRuler.updateLayout();
},
// Private
_compareTreeElementsByDetails: function(a, b)
......
......@@ -23,47 +23,39 @@
* THE POSSIBILITY OF SUCH DAMAGE.
*/
.timeline-decorations {
.timeline-ruler {
position: relative;
pointer-events: none;
}
.timeline-decorations > .header {
border-top: 1px solid rgb(200, 200, 200);
.timeline-ruler > .header {
border-bottom: 1px solid rgb(200, 200, 200);
height: 22px;
position: relative;
}
.timeline-decorations > .header > .divider {
.timeline-ruler > .header > .divider {
position: absolute;
width: 1px;
top: 0;
bottom: 0;
background-image: -webkit-linear-gradient(top, rgba(200, 200, 200, 1), rgba(200, 200, 200, 0) 85%);
-webkit-transform: translateX(-1px);
background-image: -webkit-linear-gradient(bottom, rgba(200, 200, 200, 1), rgba(200, 200, 200, 0) 85%);
}
.timeline-decorations > .header > .divider:first-child,
.timeline-decorations > .header > .divider:last-child {
background-image: none;
}
.timeline-decorations > .header > .divider > .label {
.timeline-ruler > .header > .divider > .label {
position: absolute;
top: 3px;
right: 4px;
top: 5px;
right: 5px;
font-size: 9px;
font-family: "Lucida Grande", sans-serif;
color: rgb(128, 128, 128);
white-space: nowrap;
}
.timeline-decorations > .header > .divider:first-child > .label {
display: none;
}
.timeline-decorations > .event-markers {
.timeline-ruler > .event-markers {
position: absolute;
top: 22px;
left: 0;
......@@ -71,7 +63,7 @@
bottom: 0;
}
.timeline-decorations > .event-markers > .event-marker-tooltip {
.timeline-ruler > .event-markers > .event-marker-tooltip {
position: absolute;
top: 0;
bottom: 0;
......@@ -81,7 +73,7 @@
pointer-events: auto;
}
.timeline-decorations > .event-markers > .event-marker {
.timeline-ruler > .event-markers > .event-marker {
position: absolute;
top: 0;
bottom: 0;
......@@ -93,14 +85,14 @@
border-left-color: rgba(128, 128, 128, 0.5);
}
.timeline-decorations > .event-markers > .event-marker.load-event {
.timeline-ruler > .event-markers > .event-marker.load-event {
border-left-color: rgba(255, 0, 0, 0.5);
}
.timeline-decorations > .event-markers > .event-marker.dom-content-event {
.timeline-ruler > .event-markers > .event-marker.dom-content-event {
border-left-color: rgba(0, 0, 255, 0.5);
}
.timeline-decorations > .event-markers > .event-marker.timestamp {
.timeline-ruler > .event-markers > .event-marker.timestamp {
border-left-color: rgba(0, 110, 0, 0.5);
}
......@@ -23,37 +23,44 @@
* THE POSSIBILITY OF SUCH DAMAGE.
*/
WebInspector.TimelineDecorations = function()
WebInspector.TimelineRuler = function()
{
WebInspector.Object.call(this);
this._element = document.createElement("div");
this._element.className = WebInspector.TimelineDecorations.StyleClassName;
this._element.className = WebInspector.TimelineRuler.StyleClassName;
this._headerElement = document.createElement("div");
this._headerElement.className = WebInspector.TimelineDecorations.HeaderElementStyleClassName;
this._headerElement.className = WebInspector.TimelineRuler.HeaderElementStyleClassName;
this._element.appendChild(this._headerElement);
this._markersElement = document.createElement("div");
this._markersElement.className = WebInspector.TimelineDecorations.EventMarkersElementStyleClassName;
this._markersElement.className = WebInspector.TimelineRuler.EventMarkersElementStyleClassName;
this._element.appendChild(this._markersElement);
this.clear();
this._zeroTime = 0;
this._startTime = 0;
this._endTime = 0;
this._duration = NaN;
this._secondsPerPixel = 0;
this._endTimePinned = false;
this._allowsClippedLabels = false;
}
WebInspector.TimelineDecorations.MinimumDividerSpacing = 64;
WebInspector.TimelineRuler.MinimumLeftDividerSpacing = 48;
WebInspector.TimelineRuler.MinimumDividerSpacing = 64;
WebInspector.TimelineDecorations.StyleClassName = "timeline-decorations";
WebInspector.TimelineDecorations.HeaderElementStyleClassName = "header";
WebInspector.TimelineDecorations.DividerElementStyleClassName = "divider";
WebInspector.TimelineDecorations.DividerLabelElementStyleClassName = "label";
WebInspector.TimelineRuler.StyleClassName = "timeline-ruler";
WebInspector.TimelineRuler.HeaderElementStyleClassName = "header";
WebInspector.TimelineRuler.DividerElementStyleClassName = "divider";
WebInspector.TimelineRuler.DividerLabelElementStyleClassName = "label";
WebInspector.TimelineDecorations.EventMarkersElementStyleClassName = "event-markers";
WebInspector.TimelineDecorations.EventMarkerTooltipElementStyleClassName = "event-marker-tooltip";
WebInspector.TimelineDecorations.BaseEventMarkerElementStyleClassName = "event-marker";
WebInspector.TimelineRuler.EventMarkersElementStyleClassName = "event-markers";
WebInspector.TimelineRuler.EventMarkerTooltipElementStyleClassName = "event-marker-tooltip";
WebInspector.TimelineRuler.BaseEventMarkerElementStyleClassName = "event-marker";
WebInspector.TimelineDecorations.prototype = {
constructor: WebInspector.TimelineDecorations,
WebInspector.TimelineRuler.prototype = {
constructor: WebInspector.TimelineRuler,
// Public
......@@ -67,160 +74,241 @@ WebInspector.TimelineDecorations.prototype = {
return this._headerElement;
},
clear: function()
get allowsClippedLabels()
{
this._eventMarkers = [];
this._markersElement.removeChildren();
return this._allowsClippedLabels
},
updateHeaderTimes: function(timeSpan, leftPadding, rightPadding, force)
set allowsClippedLabels(x)
{
if (!this.isShowingHeaderDividers())
if (this._allowsClippedLabels === x)
return;
if (isNaN(timeSpan))
this._allowsClippedLabels = x || false;
this._needsLayout();
},
get zeroTime()
{
return this._zeroTime;
},
set zeroTime(x)
{
if (this._zeroTime === x)
return;
leftPadding = leftPadding || 0;
rightPadding = rightPadding || 0;
this._zeroTime = x || 0;
var clientWidth = this._headerElement.clientWidth;
this._needsLayout();
},
var leftVisibleEdge = leftPadding;
var rightVisibleEdge = clientWidth - rightPadding;
var visibleWidth = rightVisibleEdge - leftVisibleEdge;
if (visibleWidth <= 0)
get startTime()
{
return this._startTime;
},
set startTime(x)
{
if (this._startTime === x)
return;
var pixelsPerSecond = clientWidth / timeSpan;
this._startTime = x || 0;
if (!isNaN(this._duration))
this._endTime = this._startTime + this._duration;
this._needsLayout();
},
get duration()
{
if (!isNaN(this._duration))
return this._duration;
return this.endTime - this.startTime;
},
set duration(x)
{
if (this._duration === x)
return;
this._duration = x || NaN;
if (!isNaN(this._duration)) {
this._endTime = this._startTime + this._duration;
this._endTimePinned = true;
} else
this._endTimePinned = false;
this._needsLayout();
},
get endTime()
{
if (!this._endTimePinned && this._scheduledLayoutUpdateIdentifier)
this._recalculate();
return this._endTime;
},
set endTime(x)
{
if (this._endTime === x)
return;
this._endTime = x || 0;
this._endTimePinned = true;
this._needsLayout();
},
get secondsPerPixel()
{
if (this._scheduledLayoutUpdateIdentifier)
this._recalculate();
return this._secondsPerPixel;
},
set secondsPerPixel(x)
{
if (this._secondsPerPixel === x)
return;
this._secondsPerPixel = x || 0;
this._endTimePinned = false;
this._currentSliceTime = 0;
this._needsLayout();
},
updateLayout: function()
{
if (this._scheduledLayoutUpdateIdentifier) {
cancelAnimationFrame(this._scheduledLayoutUpdateIdentifier);
delete this._scheduledLayoutUpdateIdentifier;
}
if (!force && this._currentPixelsPerSecond === pixelsPerSecond)
var visibleWidth = this._recalculate();
if (visibleWidth <= 0)
return;
this._currentPixelsPerSecond = pixelsPerSecond;
var duration = this.duration;
var pixelsPerSecond = visibleWidth / duration;
// Calculate a divider count based on the maximum allowed divider density.
var dividerCount = Math.round(visibleWidth / WebInspector.TimelineDecorations.MinimumDividerSpacing);
var dividerCount = Math.round(visibleWidth / WebInspector.TimelineRuler.MinimumDividerSpacing);
if (this._endTimePinned || !this._currentSliceTime) {
// Calculate the slice time based on the rough divider count and the time span.
var sliceTime = timeSpan / dividerCount;
var sliceTime = duration / dividerCount;
// Snap the slice time to a nearest number (e.g. 0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 50, etc.)
sliceTime = Math.pow(10, Math.ceil(Math.log(sliceTime) / Math.LN10));
if (sliceTime * pixelsPerSecond >= 5 * WebInspector.TimelineDecorations.MinimumDividerSpacing)
if (sliceTime * pixelsPerSecond >= 5 * WebInspector.TimelineRuler.MinimumDividerSpacing)
sliceTime = sliceTime / 5;
if (sliceTime * pixelsPerSecond >= 2 * WebInspector.TimelineDecorations.MinimumDividerSpacing)
if (sliceTime * pixelsPerSecond >= 2 * WebInspector.TimelineRuler.MinimumDividerSpacing)
sliceTime = sliceTime / 2;
var firstDividerTime = sliceTime;
var lastDividerTime = timeSpan;
this._currentSliceTime = sliceTime;
} else {
// Reuse the last slice time since the time duration does not scale to fit when the end time isn't pinned.
var sliceTime = this._currentSliceTime;
}
var firstDividerTime = (Math.ceil((this._startTime - this._zeroTime) / sliceTime) * sliceTime) + this._zeroTime;
var lastDividerTime = this._endTime;
// Calculate the divider count now based on the final slice time.
dividerCount = Math.ceil((lastDividerTime - firstDividerTime) / sliceTime);
// Make an extra divider in case the last one is partially visible.
if (!this._endTimePinned)
++dividerCount;
var dividerElement = this._headerElement.firstChild;
for (var i = 0; i <= dividerCount; ++i) {
if (!dividerElement) {
dividerElement = document.createElement("div");
dividerElement.className = WebInspector.TimelineDecorations.DividerElementStyleClassName;
dividerElement.className = WebInspector.TimelineRuler.DividerElementStyleClassName;
this._headerElement.appendChild(dividerElement);
var labelElement = document.createElement("div");
labelElement.className = WebInspector.TimelineDecorations.DividerLabelElementStyleClassName;
labelElement.className = WebInspector.TimelineRuler.DividerLabelElementStyleClassName;
dividerElement._labelElement = labelElement;
dividerElement.appendChild(labelElement);
}
var left = visibleWidth * (i / dividerCount);
var totalLeft = left + leftVisibleEdge;
var fractionLeft = totalLeft / clientWidth;
var percentLeft = 100 * fractionLeft;
var time = firstDividerTime + (sliceTime * i);
var dividerTime = firstDividerTime + (sliceTime * i);
var newLeftPosition = (dividerTime - this._startTime) / duration;
if (!this._allowsClippedLabels) {
// Don't allow dividers under 0% where they will be completely hidden.
if (newLeftPosition < 0)
continue;
// When over 100% it is time to stop making/updating dividers.
if (newLeftPosition > 1)
break;
// Don't allow the left-most divider spacing to be so tight it clips.
if ((newLeftPosition * visibleWidth) < WebInspector.TimelineRuler.MinimumLeftDividerSpacing)
continue;
}
if (this._endTimePinned)
newLeftPosition *= 100;
else
newLeftPosition *= visibleWidth;
newLeftPosition = newLeftPosition.toFixed(2);
var currentPercentLeft = parseFloat(dividerElement.style.left);
if (isNaN(currentPercentLeft) || Math.abs(currentPercentLeft - percentLeft) >= 0.1)
dividerElement.style.left = percentLeft + "%";
var currentLeftPosition = parseFloat(dividerElement.style.left).toFixed(2);
if (currentLeftPosition !== newLeftPosition)
dividerElement.style.left = newLeftPosition + (this._endTimePinned ? "%" : "px");
dividerElement._labelElement.textContent = isNaN(time) ? "" : Number.secondsToString(time);
dividerElement._labelElement.textContent = isNaN(dividerTime) ? "" : Number.secondsToString(dividerTime, true);
dividerElement = dividerElement.nextSibling;
}
// Remove extra dividers.
while (dividerElement) {
var nextDividerElement = dividerElement.nextSibling;
this._headerElement.removeChild(dividerElement);
dividerElement.remove();
dividerElement = nextDividerElement;
}
},
updateEventMarkers: function(minimumBoundary, maximumBoundary)
{
this._markersElement.removeChildren();
if (!this._eventMarkers.length)
return;
// Private
var timeSpan = maximumBoundary - minimumBoundary;
if (isNaN(timeSpan))
_needsLayout: function()
{
if (this._scheduledLayoutUpdateIdentifier)
return;
function toolTipForEventMarker(eventMarker, time)
{
switch (eventMarker.type) {
case WebInspector.TimelineEventMarker.Type.LoadEvent:
return WebInspector.UIString("Load event fired at %s").format(Number.secondsToString(time));
case WebInspector.TimelineEventMarker.Type.DOMContentEvent:
return WebInspector.UIString("DOMContent event fired at %s").format(Number.secondsToString(time));
case WebInspector.TimelineEventMarker.Type.TimeStamp:
return WebInspector.UIString("Timestamp marker at %s").format(Number.secondsToString(time));
default:
console.assert(false);
return "";
}
}
for (var i = 0; i < this._eventMarkers.length; ++i) {
var eventMarker = this._eventMarkers[i];
if (eventMarker.timestamp < minimumBoundary || eventMarker.timestamp > maximumBoundary)
continue;
var time = eventMarker.timestamp - minimumBoundary;
var percentLeft = (100 * time) / timeSpan;
var tooltipElement = document.createElement("div");
tooltipElement.className = WebInspector.TimelineDecorations.EventMarkerTooltipElementStyleClassName;
tooltipElement.title = toolTipForEventMarker(eventMarker, time);
tooltipElement.style.left = percentLeft + "%";
var markerElement = document.createElement("div");
markerElement.className = WebInspector.TimelineDecorations.BaseEventMarkerElementStyleClassName;
markerElement.classList.add(eventMarker.type);
markerElement.style.left = percentLeft + "%";
this._markersElement.appendChild(markerElement);
this._markersElement.appendChild(tooltipElement);
}
this._scheduledLayoutUpdateIdentifier = requestAnimationFrame(this.updateLayout.bind(this));
},
isShowingHeaderDividers: function()
_recalculate: function()
{
return !this._headerElement.classList.contains("hidden");
},
var visibleWidth = this._headerElement.clientWidth;
if (visibleWidth <= 0)
return 0;
showHeaderDividers: function()
{
this._headerElement.classList.remove("hidden");
},
if (this._endTimePinned)
var duration = this._endTime - this._startTime;
else
var duration = visibleWidth * this._secondsPerPixel;
hideHeaderDividers: function()
{
this._headerElement.classList.add("hidden");
},
this._secondsPerPixel = duration / visibleWidth;
ad