From 8b8175d8005cdcea9caeca38af36a6da414a5290 Mon Sep 17 00:00:00 2001 From: "timothy@apple.com" Date: Tue, 21 Jan 2014 02:54:54 +0000 Subject: [PATCH] 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 --- Source/WebInspectorUI/ChangeLog | 58 ++++ Source/WebInspectorUI/UserInterface/Main.html | 4 +- .../UserInterface/OverviewTimelineView.css | 8 +- .../UserInterface/OverviewTimelineView.js | 9 +- .../UserInterface/TimelineDecorations.js | 226 ------------- ...elineDecorations.css => TimelineRuler.css} | 38 +-- .../UserInterface/TimelineRuler.js | 314 ++++++++++++++++++ 7 files changed, 400 insertions(+), 257 deletions(-) delete mode 100644 Source/WebInspectorUI/UserInterface/TimelineDecorations.js rename Source/WebInspectorUI/UserInterface/{TimelineDecorations.css => TimelineRuler.css} (69%) create mode 100644 Source/WebInspectorUI/UserInterface/TimelineRuler.js diff --git a/Source/WebInspectorUI/ChangeLog b/Source/WebInspectorUI/ChangeLog index 6e999260bf1..98ddb3d2088 100644 --- a/Source/WebInspectorUI/ChangeLog +++ b/Source/WebInspectorUI/ChangeLog @@ -1,3 +1,61 @@ +2013-12-13 Timothy Hatcher + + 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 Improve scroll performance of WebInspector.TreeOutlineDataGridSynchronizer. diff --git a/Source/WebInspectorUI/UserInterface/Main.html b/Source/WebInspectorUI/UserInterface/Main.html index 55d7f4e69ba..04aaac0883f 100644 --- a/Source/WebInspectorUI/UserInterface/Main.html +++ b/Source/WebInspectorUI/UserInterface/Main.html @@ -85,7 +85,7 @@ - + @@ -343,7 +343,7 @@ - + diff --git a/Source/WebInspectorUI/UserInterface/OverviewTimelineView.css b/Source/WebInspectorUI/UserInterface/OverviewTimelineView.css index 3672cdb5bf5..11d1083eb85 100644 --- a/Source/WebInspectorUI/UserInterface/OverviewTimelineView.css +++ b/Source/WebInspectorUI/UserInterface/OverviewTimelineView.css @@ -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; } diff --git a/Source/WebInspectorUI/UserInterface/OverviewTimelineView.js b/Source/WebInspectorUI/UserInterface/OverviewTimelineView.js index a73b7a7cd4c..a59c00b5a36 100644 --- a/Source/WebInspectorUI/UserInterface/OverviewTimelineView.js +++ b/Source/WebInspectorUI/UserInterface/OverviewTimelineView.js @@ -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) diff --git a/Source/WebInspectorUI/UserInterface/TimelineDecorations.js b/Source/WebInspectorUI/UserInterface/TimelineDecorations.js deleted file mode 100644 index a65870d023a..00000000000 --- a/Source/WebInspectorUI/UserInterface/TimelineDecorations.js +++ /dev/null @@ -1,226 +0,0 @@ -/* - * Copyright (C) 2013 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.TimelineDecorations = function() -{ - WebInspector.Object.call(this); - - this._element = document.createElement("div"); - this._element.className = WebInspector.TimelineDecorations.StyleClassName; - - this._headerElement = document.createElement("div"); - this._headerElement.className = WebInspector.TimelineDecorations.HeaderElementStyleClassName; - this._element.appendChild(this._headerElement); - - this._markersElement = document.createElement("div"); - this._markersElement.className = WebInspector.TimelineDecorations.EventMarkersElementStyleClassName; - this._element.appendChild(this._markersElement); - - this.clear(); -} - -WebInspector.TimelineDecorations.MinimumDividerSpacing = 64; - -WebInspector.TimelineDecorations.StyleClassName = "timeline-decorations"; -WebInspector.TimelineDecorations.HeaderElementStyleClassName = "header"; -WebInspector.TimelineDecorations.DividerElementStyleClassName = "divider"; -WebInspector.TimelineDecorations.DividerLabelElementStyleClassName = "label"; - -WebInspector.TimelineDecorations.EventMarkersElementStyleClassName = "event-markers"; -WebInspector.TimelineDecorations.EventMarkerTooltipElementStyleClassName = "event-marker-tooltip"; -WebInspector.TimelineDecorations.BaseEventMarkerElementStyleClassName = "event-marker"; - -WebInspector.TimelineDecorations.prototype = { - constructor: WebInspector.TimelineDecorations, - - // Public - - get element() - { - return this._element; - }, - - get headerElement() - { - return this._headerElement; - }, - - clear: function() - { - this._eventMarkers = []; - this._markersElement.removeChildren(); - }, - - updateHeaderTimes: function(timeSpan, leftPadding, rightPadding, force) - { - if (!this.isShowingHeaderDividers()) - return; - - if (isNaN(timeSpan)) - return; - - leftPadding = leftPadding || 0; - rightPadding = rightPadding || 0; - - var clientWidth = this._headerElement.clientWidth; - - var leftVisibleEdge = leftPadding; - var rightVisibleEdge = clientWidth - rightPadding; - var visibleWidth = rightVisibleEdge - leftVisibleEdge; - if (visibleWidth <= 0) - return; - - var pixelsPerSecond = clientWidth / timeSpan; - - if (!force && this._currentPixelsPerSecond === pixelsPerSecond) - return; - - this._currentPixelsPerSecond = pixelsPerSecond; - - // Calculate a divider count based on the maximum allowed divider density. - var dividerCount = Math.round(visibleWidth / WebInspector.TimelineDecorations.MinimumDividerSpacing); - - // Calculate the slice time based on the rough divider count and the time span. - var sliceTime = timeSpan / 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) - sliceTime = sliceTime / 5; - if (sliceTime * pixelsPerSecond >= 2 * WebInspector.TimelineDecorations.MinimumDividerSpacing) - sliceTime = sliceTime / 2; - - var firstDividerTime = sliceTime; - var lastDividerTime = timeSpan; - - // Calculate the divider count now based on the final slice time. - dividerCount = Math.ceil((lastDividerTime - firstDividerTime) / sliceTime); - - var dividerElement = this._headerElement.firstChild; - for (var i = 0; i <= dividerCount; ++i) { - if (!dividerElement) { - dividerElement = document.createElement("div"); - dividerElement.className = WebInspector.TimelineDecorations.DividerElementStyleClassName; - this._headerElement.appendChild(dividerElement); - - var labelElement = document.createElement("div"); - labelElement.className = WebInspector.TimelineDecorations.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 currentPercentLeft = parseFloat(dividerElement.style.left); - if (isNaN(currentPercentLeft) || Math.abs(currentPercentLeft - percentLeft) >= 0.1) - dividerElement.style.left = percentLeft + "%"; - - dividerElement._labelElement.textContent = isNaN(time) ? "" : Number.secondsToString(time); - dividerElement = dividerElement.nextSibling; - } - - // Remove extra dividers. - while (dividerElement) { - var nextDividerElement = dividerElement.nextSibling; - this._headerElement.removeChild(dividerElement); - dividerElement = nextDividerElement; - } - }, - - updateEventMarkers: function(minimumBoundary, maximumBoundary) - { - this._markersElement.removeChildren(); - - if (!this._eventMarkers.length) - return; - - var timeSpan = maximumBoundary - minimumBoundary; - if (isNaN(timeSpan)) - 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); - } - }, - - isShowingHeaderDividers: function() - { - return !this._headerElement.classList.contains("hidden"); - }, - - showHeaderDividers: function() - { - this._headerElement.classList.remove("hidden"); - }, - - hideHeaderDividers: function() - { - this._headerElement.classList.add("hidden"); - }, - - addTimelineEventMarker: function(eventMarker) - { - this._eventMarkers.push(eventMarker); - }, -} - -WebInspector.TimelineDecorations.prototype.__proto__ = WebInspector.Object.prototype; diff --git a/Source/WebInspectorUI/UserInterface/TimelineDecorations.css b/Source/WebInspectorUI/UserInterface/TimelineRuler.css similarity index 69% rename from Source/WebInspectorUI/UserInterface/TimelineDecorations.css rename to Source/WebInspectorUI/UserInterface/TimelineRuler.css index 01c465c0528..8b6122dd3b8 100644 --- a/Source/WebInspectorUI/UserInterface/TimelineDecorations.css +++ b/Source/WebInspectorUI/UserInterface/TimelineRuler.css @@ -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); } diff --git a/Source/WebInspectorUI/UserInterface/TimelineRuler.js b/Source/WebInspectorUI/UserInterface/TimelineRuler.js new file mode 100644 index 00000000000..7238a4e7d9e --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/TimelineRuler.js @@ -0,0 +1,314 @@ +/* + * Copyright (C) 2013 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.TimelineRuler = function() +{ + WebInspector.Object.call(this); + + this._element = document.createElement("div"); + this._element.className = WebInspector.TimelineRuler.StyleClassName; + + this._headerElement = document.createElement("div"); + this._headerElement.className = WebInspector.TimelineRuler.HeaderElementStyleClassName; + this._element.appendChild(this._headerElement); + + this._markersElement = document.createElement("div"); + this._markersElement.className = WebInspector.TimelineRuler.EventMarkersElementStyleClassName; + this._element.appendChild(this._markersElement); + + this._zeroTime = 0; + this._startTime = 0; + this._endTime = 0; + this._duration = NaN; + this._secondsPerPixel = 0; + this._endTimePinned = false; + this._allowsClippedLabels = false; +} + +WebInspector.TimelineRuler.MinimumLeftDividerSpacing = 48; +WebInspector.TimelineRuler.MinimumDividerSpacing = 64; + +WebInspector.TimelineRuler.StyleClassName = "timeline-ruler"; +WebInspector.TimelineRuler.HeaderElementStyleClassName = "header"; +WebInspector.TimelineRuler.DividerElementStyleClassName = "divider"; +WebInspector.TimelineRuler.DividerLabelElementStyleClassName = "label"; + +WebInspector.TimelineRuler.EventMarkersElementStyleClassName = "event-markers"; +WebInspector.TimelineRuler.EventMarkerTooltipElementStyleClassName = "event-marker-tooltip"; +WebInspector.TimelineRuler.BaseEventMarkerElementStyleClassName = "event-marker"; + +WebInspector.TimelineRuler.prototype = { + constructor: WebInspector.TimelineRuler, + + // Public + + get element() + { + return this._element; + }, + + get headerElement() + { + return this._headerElement; + }, + + get allowsClippedLabels() + { + return this._allowsClippedLabels + }, + + set allowsClippedLabels(x) + { + if (this._allowsClippedLabels === x) + return; + + this._allowsClippedLabels = x || false; + + this._needsLayout(); + }, + + get zeroTime() + { + return this._zeroTime; + }, + + set zeroTime(x) + { + if (this._zeroTime === x) + return; + + this._zeroTime = x || 0; + + this._needsLayout(); + }, + + get startTime() + { + return this._startTime; + }, + + set startTime(x) + { + if (this._startTime === x) + return; + + 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; + } + + var visibleWidth = this._recalculate(); + if (visibleWidth <= 0) + return; + + 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.TimelineRuler.MinimumDividerSpacing); + + if (this._endTimePinned || !this._currentSliceTime) { + // Calculate the slice time based on the rough divider count and the time span. + 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.TimelineRuler.MinimumDividerSpacing) + sliceTime = sliceTime / 5; + if (sliceTime * pixelsPerSecond >= 2 * WebInspector.TimelineRuler.MinimumDividerSpacing) + sliceTime = sliceTime / 2; + + 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.TimelineRuler.DividerElementStyleClassName; + this._headerElement.appendChild(dividerElement); + + var labelElement = document.createElement("div"); + labelElement.className = WebInspector.TimelineRuler.DividerLabelElementStyleClassName; + dividerElement._labelElement = labelElement; + dividerElement.appendChild(labelElement); + } + + 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 currentLeftPosition = parseFloat(dividerElement.style.left).toFixed(2); + if (currentLeftPosition !== newLeftPosition) + dividerElement.style.left = newLeftPosition + (this._endTimePinned ? "%" : "px"); + + dividerElement._labelElement.textContent = isNaN(dividerTime) ? "" : Number.secondsToString(dividerTime, true); + dividerElement = dividerElement.nextSibling; + } + + // Remove extra dividers. + while (dividerElement) { + var nextDividerElement = dividerElement.nextSibling; + dividerElement.remove(); + dividerElement = nextDividerElement; + } + }, + + // Private + + _needsLayout: function() + { + if (this._scheduledLayoutUpdateIdentifier) + return; + this._scheduledLayoutUpdateIdentifier = requestAnimationFrame(this.updateLayout.bind(this)); + }, + + _recalculate: function() + { + var visibleWidth = this._headerElement.clientWidth; + if (visibleWidth <= 0) + return 0; + + if (this._endTimePinned) + var duration = this._endTime - this._startTime; + else + var duration = visibleWidth * this._secondsPerPixel; + + this._secondsPerPixel = duration / visibleWidth; + + if (!this._endTimePinned) + this._endTime = this._startTime + (visibleWidth * this._secondsPerPixel); + + return visibleWidth; + } +} + +WebInspector.TimelineRuler.prototype.__proto__ = WebInspector.Object.prototype; -- GitLab