Web Inspector: Timelines: CPU/memory timeline bars sometimes don't draw correctly and jump around on scrolling
https://bugs.webkit.org/show_bug.cgi?id=197440
<rdar://problem/46886315>

Reviewed by Joseph Pecoraro.

When drawing the Memory/CPU graphs, we need to know about the record immediately before/after
what's overlapping the visible range so that the graph correctly slopes off the screen.

* UserInterface/Models/Timeline.js:
(WI.Timeline.prototype.recordsInTimeRange):
(WI.Timeline.prototype.recordsOverlappingTimeRange): Deleted.
Merge `recordsOverlappingTimeRange` into `recordsInTimeRange` by accepting an options object
that determines whether to include the record before/after the first/last record that are
at all overlapping the range.

* UserInterface/Models/CPUTimelineRecord.js:
(WI.CPUTimelineRecord):
(WI.CPUTimelineRecord.get samplingRatePerSecond): Added.
(WI.CPUTimelineRecord.prototype.adjustStartTimeToLastRecord): Added.
* UserInterface/Models/MemoryTimelineRecord.js:
(WI.MemoryTimelineRecord):
(WI.MemoryTimelineRecord.get samplingRatePerSecond): Added.
(WI.MemoryTimelineRecord.prototype.adjustStartTimeToLastRecord): Added.
Adjust the `startTime` of the record by the sampling rate (which is 500ms).

* UserInterface/Models/CPUTimeline.js: Added.
(WI.CPUTimeline.prototype.addRecord):
* UserInterface/Models/MemoryTimeline.js:
(WI.MemoryTimeline.prototype.addRecord): Added.
Adjust the `startTime` of the new record to be equal to the `endTime` of the last record.

* UserInterface/Views/TimelineOverview.js:
(WI.TimelineOverview.prototype._recordSelected):
* UserInterface/Views/CPUTimelineView.js:
(WI.CPUTimelineView.prototype.layout):
(WI.CPUTimelineView.prototype._computeStatisticsData):
(WI.CPUTimelineView.prototype._attemptSelectIndicatatorTimelineRecord):
* UserInterface/Views/CPUTimelineOverviewGraph.js:
(WI.CPUTimelineOverviewGraph.prototype.layout):
(WI.CPUTimelineOverviewGraph.prototype._handleChartClick):
(WI.CPUTimelineOverviewGraph.prototype.get samplingRatePerSecond): Deleted.
(WI.CPUTimelineOverviewGraph.prototype.layout.yScaleForRecord): Deleted.

* UserInterface/Views/MemoryTimelineView.js:
(WI.MemoryTimelineView.prototype.layout):
* UserInterface/Views/MemoryTimelineOverviewGraph.js:
(WI.MemoryTimelineOverviewGraph.prototype.layout):

* UserInterface/Main.html:
* UserInterface/Test.html:


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@245498 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/WebInspectorUI/ChangeLog b/Source/WebInspectorUI/ChangeLog
index 26470e0..d76efb6 100644
--- a/Source/WebInspectorUI/ChangeLog
+++ b/Source/WebInspectorUI/ChangeLog
@@ -1,5 +1,59 @@
 2019-05-17  Devin Rousso  <drousso@apple.com>
 
+        Web Inspector: Timelines: CPU/memory timeline bars sometimes don't draw correctly and jump around on scrolling
+        https://bugs.webkit.org/show_bug.cgi?id=197440
+        <rdar://problem/46886315>
+
+        Reviewed by Joseph Pecoraro.
+
+        When drawing the Memory/CPU graphs, we need to know about the record immediately before/after
+        what's overlapping the visible range so that the graph correctly slopes off the screen.
+
+        * UserInterface/Models/Timeline.js:
+        (WI.Timeline.prototype.recordsInTimeRange):
+        (WI.Timeline.prototype.recordsOverlappingTimeRange): Deleted.
+        Merge `recordsOverlappingTimeRange` into `recordsInTimeRange` by accepting an options object
+        that determines whether to include the record before/after the first/last record that are
+        at all overlapping the range.
+
+        * UserInterface/Models/CPUTimelineRecord.js:
+        (WI.CPUTimelineRecord):
+        (WI.CPUTimelineRecord.get samplingRatePerSecond): Added.
+        (WI.CPUTimelineRecord.prototype.adjustStartTimeToLastRecord): Added.
+        * UserInterface/Models/MemoryTimelineRecord.js:
+        (WI.MemoryTimelineRecord):
+        (WI.MemoryTimelineRecord.get samplingRatePerSecond): Added.
+        (WI.MemoryTimelineRecord.prototype.adjustStartTimeToLastRecord): Added.
+        Adjust the `startTime` of the record by the sampling rate (which is 500ms).
+
+        * UserInterface/Models/CPUTimeline.js: Added.
+        (WI.CPUTimeline.prototype.addRecord):
+        * UserInterface/Models/MemoryTimeline.js:
+        (WI.MemoryTimeline.prototype.addRecord): Added.
+        Adjust the `startTime` of the new record to be equal to the `endTime` of the last record.
+
+        * UserInterface/Views/TimelineOverview.js:
+        (WI.TimelineOverview.prototype._recordSelected):
+        * UserInterface/Views/CPUTimelineView.js:
+        (WI.CPUTimelineView.prototype.layout):
+        (WI.CPUTimelineView.prototype._computeStatisticsData):
+        (WI.CPUTimelineView.prototype._attemptSelectIndicatatorTimelineRecord):
+        * UserInterface/Views/CPUTimelineOverviewGraph.js:
+        (WI.CPUTimelineOverviewGraph.prototype.layout):
+        (WI.CPUTimelineOverviewGraph.prototype._handleChartClick):
+        (WI.CPUTimelineOverviewGraph.prototype.get samplingRatePerSecond): Deleted.
+        (WI.CPUTimelineOverviewGraph.prototype.layout.yScaleForRecord): Deleted.
+
+        * UserInterface/Views/MemoryTimelineView.js:
+        (WI.MemoryTimelineView.prototype.layout):
+        * UserInterface/Views/MemoryTimelineOverviewGraph.js:
+        (WI.MemoryTimelineOverviewGraph.prototype.layout):
+
+        * UserInterface/Main.html:
+        * UserInterface/Test.html:
+
+2019-05-17  Devin Rousso  <drousso@apple.com>
+
         Web Inspector: Audit: make it easier to jump to any returned DOM node in the Elements tab
         https://bugs.webkit.org/show_bug.cgi?id=197470
         <rdar://problem/50466774>
diff --git a/Source/WebInspectorUI/UserInterface/Main.html b/Source/WebInspectorUI/UserInterface/Main.html
index 981655e..6b4d613 100644
--- a/Source/WebInspectorUI/UserInterface/Main.html
+++ b/Source/WebInspectorUI/UserInterface/Main.html
@@ -356,6 +356,7 @@
     <script src="Models/Branch.js"></script>
     <script src="Models/Breakpoint.js"></script>
     <script src="Models/CPUInstrument.js"></script>
+    <script src="Models/CPUTimeline.js"></script>
     <script src="Models/CPUTimelineRecord.js"></script>
     <script src="Models/CSSCompletions.js"></script>
     <script src="Models/CSSKeywordCompletions.js"></script>
diff --git a/Source/WebInspectorUI/UserInterface/Models/CPUTimeline.js b/Source/WebInspectorUI/UserInterface/Models/CPUTimeline.js
new file mode 100644
index 0000000..128022f
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Models/CPUTimeline.js
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+WI.CPUTimeline = class CPUTimeline extends WI.Timeline
+{
+    // Public
+
+    addRecord(record)
+    {
+        let lastRecord = this.records.lastValue;
+        if (lastRecord)
+            record.adjustStartTimeToLastRecord(lastRecord);
+
+        super.addRecord(record);
+    }
+};
diff --git a/Source/WebInspectorUI/UserInterface/Models/CPUTimelineRecord.js b/Source/WebInspectorUI/UserInterface/Models/CPUTimelineRecord.js
index 2166891..112b713 100644
--- a/Source/WebInspectorUI/UserInterface/Models/CPUTimelineRecord.js
+++ b/Source/WebInspectorUI/UserInterface/Models/CPUTimelineRecord.js
@@ -27,7 +27,7 @@
 {
     constructor({timestamp, usage, threads})
     {
-        super(WI.TimelineRecord.Type.CPU, timestamp, timestamp);
+        super(WI.TimelineRecord.Type.CPU, timestamp - CPUTimelineRecord.samplingRatePerSecond, timestamp);
 
         console.assert(typeof timestamp === "number");
         console.assert(typeof usage === "number");
@@ -68,6 +68,14 @@
         }
     }
 
+    // Static
+
+    static get samplingRatePerSecond()
+    {
+        // 500ms. This matches the ResourceUsageThread sampling frequency in the backend.
+        return 0.5;
+    }
+
     // Import / Export
 
     static fromJSON(json)
@@ -95,4 +103,11 @@
     get workerThreadUsage() { return this._workerThreadUsage; }
     get unknownThreadUsage() { return this._unknownThreadUsage; }
     get workersData() { return this._workersData; }
+
+    adjustStartTimeToLastRecord(lastRecord)
+    {
+        console.assert(lastRecord instanceof CPUTimelineRecord);
+        console.assert(this._startTime >= lastRecord.endTime);
+        this._startTime = lastRecord.endTime;
+    }
 };
diff --git a/Source/WebInspectorUI/UserInterface/Models/MemoryTimeline.js b/Source/WebInspectorUI/UserInterface/Models/MemoryTimeline.js
index a2f109b..4b4f060 100644
--- a/Source/WebInspectorUI/UserInterface/Models/MemoryTimeline.js
+++ b/Source/WebInspectorUI/UserInterface/Models/MemoryTimeline.js
@@ -46,6 +46,15 @@
 
         this._pressureEvents = [];
     }
+
+    addRecord(record)
+    {
+        let lastRecord = this.records.lastValue;
+        if (lastRecord)
+            record.adjustStartTimeToLastRecord(lastRecord);
+
+        super.addRecord(record);
+    }
 };
 
 WI.MemoryTimeline.Event = {
diff --git a/Source/WebInspectorUI/UserInterface/Models/MemoryTimelineRecord.js b/Source/WebInspectorUI/UserInterface/Models/MemoryTimelineRecord.js
index 1e37b9a..787bedc 100644
--- a/Source/WebInspectorUI/UserInterface/Models/MemoryTimelineRecord.js
+++ b/Source/WebInspectorUI/UserInterface/Models/MemoryTimelineRecord.js
@@ -27,7 +27,7 @@
 {
     constructor(timestamp, categories)
     {
-        super(WI.TimelineRecord.Type.Memory, timestamp, timestamp);
+        super(WI.TimelineRecord.Type.Memory, timestamp - MemoryTimelineRecord.samplingRatePerSecond, timestamp);
 
         console.assert(typeof timestamp === "number");
         console.assert(categories instanceof Array);
@@ -43,6 +43,12 @@
 
     // Static
 
+    static get samplingRatePerSecond()
+    {
+        // 500ms. This matches the ResourceUsageThread sampling frequency in the backend.
+        return 0.5;
+    }
+
     static memoryCategoriesFromProtocol(categories)
     {
         let javascriptSize = 0;
@@ -102,4 +108,11 @@
     get timestamp() { return this._timestamp; }
     get categories() { return this._categories; }
     get totalSize() { return this._totalSize; }
+
+    adjustStartTimeToLastRecord(lastRecord)
+    {
+        console.assert(lastRecord instanceof MemoryTimelineRecord);
+        console.assert(this._startTime >= lastRecord.endTime);
+        this._startTime = lastRecord.endTime;
+    }
 };
diff --git a/Source/WebInspectorUI/UserInterface/Models/Timeline.js b/Source/WebInspectorUI/UserInterface/Models/Timeline.js
index 0be7ec5..28f4252 100644
--- a/Source/WebInspectorUI/UserInterface/Models/Timeline.js
+++ b/Source/WebInspectorUI/UserInterface/Models/Timeline.js
@@ -41,6 +41,9 @@
         if (type === WI.TimelineRecord.Type.Network)
             return new WI.NetworkTimeline(type);
 
+        if (type === WI.TimelineRecord.Type.CPU)
+            return new WI.CPUTimeline(type);
+
         if (type === WI.TimelineRecord.Type.Memory)
             return new WI.MemoryTimeline(type);
 
@@ -111,20 +114,9 @@
         return (before < after) ? recordBefore : recordAfter;
     }
 
-    recordsOverlappingTimeRange(startTime, endTime)
+    recordsInTimeRange(startTime, endTime, {includeRecordBeforeStart, includeRecordAfterEnd} = {})
     {
         let lowerIndex = this._records.lowerBound(startTime, (time, record) => time - record.endTime);
-        let upperIndex = this._records.upperBound(endTime, (time, record) => time - record.startTime);
-
-        return this._records.slice(lowerIndex, upperIndex);
-    }
-
-    recordsInTimeRange(startTime, endTime, includeRecordBeforeStart)
-    {
-        let lowerIndex = this._records.lowerBound(startTime, (time, record) => time - record.startTime);
-        let upperIndex = this._records.upperBound(endTime, (time, record) => time - record.startTime);
-
-        // Include the record right before the start time.
         if (includeRecordBeforeStart && lowerIndex > 0) {
             lowerIndex--;
 
@@ -137,6 +129,10 @@
             }
         }
 
+        let upperIndex = this._records.upperBound(endTime, (time, record) => time - record.startTime);
+        if (includeRecordAfterEnd && upperIndex < this._records.length)
+            ++upperIndex;
+
         return this._records.slice(lowerIndex, upperIndex);
     }
 
diff --git a/Source/WebInspectorUI/UserInterface/Test.html b/Source/WebInspectorUI/UserInterface/Test.html
index 06f0be2..e710ec6 100644
--- a/Source/WebInspectorUI/UserInterface/Test.html
+++ b/Source/WebInspectorUI/UserInterface/Test.html
@@ -118,6 +118,7 @@
 
     <script src="Models/Breakpoint.js"></script>
     <script src="Models/CPUInstrument.js"></script>
+    <script src="Models/CPUTimeline.js"></script>
     <script src="Models/CPUTimelineRecord.js"></script>
     <script src="Models/CSSCompletions.js"></script>
     <script src="Models/CSSKeywordCompletions.js"></script>
diff --git a/Source/WebInspectorUI/UserInterface/Views/CPUTimelineOverviewGraph.js b/Source/WebInspectorUI/UserInterface/Views/CPUTimelineOverviewGraph.js
index 087a4a6..024314c 100644
--- a/Source/WebInspectorUI/UserInterface/Views/CPUTimelineOverviewGraph.js
+++ b/Source/WebInspectorUI/UserInterface/Views/CPUTimelineOverviewGraph.js
@@ -56,14 +56,6 @@
             this._processRecord(record);
     }
 
-    // Static
-
-    static get samplingRatePerSecond()
-    {
-        // 500ms. This matches the ResourceUsageThread sampling frequency in the backend.
-        return 0.5;
-    }
-
     // Protected
 
     get height()
@@ -115,22 +107,18 @@
             return (size / maxCapacity) * height;
         }
 
-        const includeRecordBeforeStart = true;
-        let visibleRecords = this._cpuTimeline.recordsInTimeRange(graphStartTime, visibleEndTime, includeRecordBeforeStart);
+        let visibleRecords = this._cpuTimeline.recordsInTimeRange(graphStartTime, visibleEndTime, {
+            includeRecordBeforeStart: true,
+        });
         if (!visibleRecords.length)
             return;
 
-        function yScaleForRecord(record) {
-            return yScale(record.usage);
-        }
-
-        let intervalWidth = CPUTimelineOverviewGraph.samplingRatePerSecond / secondsPerPixel;
         const minimumDisplayHeight = 4;
 
         for (let record of visibleRecords) {
             let additionalClass = record === this.selectedRecord ? "selected" : undefined;
-            let w = intervalWidth;
-            let x = xScale(record.startTime - CPUTimelineOverviewGraph.samplingRatePerSecond);
+            let w = (record.endTime - record.startTime) / secondsPerPixel;
+            let x = xScale(record.startTime);
             let h1 = Math.max(minimumDisplayHeight, yScale(record.mainThreadUsage));
             let h2 = Math.max(minimumDisplayHeight, yScale(record.mainThreadUsage + record.workerThreadUsage));
             let h3 = Math.max(minimumDisplayHeight, yScale(record.usage));
@@ -199,7 +187,7 @@
         let graphStartTime = this.startTime;
 
         let clickTime = graphStartTime + graphClickTime;
-        let record = this._cpuTimeline.closestRecordTo(clickTime + (CPUTimelineOverviewGraph.samplingRatePerSecond / 2));
+        let record = this._cpuTimeline.closestRecordTo(clickTime);
         if (!record)
             return;
 
diff --git a/Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.js b/Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.js
index 1513d4e..365421d 100644
--- a/Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.js
+++ b/Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.js
@@ -445,9 +445,10 @@
         let discontinuities = this._recording.discontinuitiesInTimeRange(graphStartTime, visibleEndTime);
         let originalDiscontinuities = discontinuities.slice();
 
-        // Don't include the record before the graph start if the graph start is within a gap.
-        let includeRecordBeforeStart = !discontinuities.length || discontinuities[0].startTime > graphStartTime;
-        let visibleRecords = this.representedObject.recordsInTimeRange(graphStartTime, visibleEndTime, includeRecordBeforeStart);
+        let visibleRecords = this.representedObject.recordsInTimeRange(graphStartTime, visibleEndTime, {
+            includeRecordBeforeStart: !discontinuities.length || discontinuities[0].startTime > graphStartTime,
+            includeRecordAfterEnd: true,
+        });
         if (!visibleRecords.length || (visibleRecords.length === 1 && visibleRecords[0].endTime < graphStartTime)) {
             this.clear();
             return;
@@ -1161,8 +1162,6 @@
         // with the data available to the frontend and is quite accurate for most
         // Main Thread activity.
 
-        const includeRecordBeforeStart = true;
-
         function incrementTypeCount(map, key) {
             let entry = map.get(key);
             if (entry)
@@ -1183,7 +1182,7 @@
         let possibleRepeatingTimers = new Set;
 
         let scriptTimeline = this._recording.timelineForRecordType(WI.TimelineRecord.Type.Script);
-        let scriptRecords = scriptTimeline ? scriptTimeline.recordsInTimeRange(startTime, endTime, includeRecordBeforeStart) : [];
+        let scriptRecords = scriptTimeline ? scriptTimeline.recordsInTimeRange(startTime, endTime, {includeRecordBeforeStart: true}) : [];
         scriptRecords = scriptRecords.filter((record) => {
             // Return true for event types that define script entries/exits.
             // Return false for events with no time ranges or if they are contained in other events.
@@ -1249,7 +1248,7 @@
         });
 
         let layoutTimeline = this._recording.timelineForRecordType(WI.TimelineRecord.Type.Layout);
-        let layoutRecords = layoutTimeline ? layoutTimeline.recordsInTimeRange(startTime, endTime, includeRecordBeforeStart) : [];
+        let layoutRecords = layoutTimeline ? layoutTimeline.recordsInTimeRange(startTime, endTime, {includeRecordBeforeStart: true}) : [];
         layoutRecords = layoutRecords.filter((record) => {
             switch (record.eventType) {
             case WI.LayoutTimelineRecord.EventType.RecalculateStyles:
@@ -1573,7 +1572,7 @@
     _attemptSelectIndicatatorTimelineRecord(startTime, endTime)
     {
         let layoutTimeline = this._recording.timelineForRecordType(WI.TimelineRecord.Type.Layout);
-        let layoutRecords = layoutTimeline ? layoutTimeline.recordsOverlappingTimeRange(startTime, endTime) : [];
+        let layoutRecords = layoutTimeline ? layoutTimeline.recordsInTimeRange(startTime, endTime, {includeRecordBeforeStart: true}) : [];
         layoutRecords = layoutRecords.filter((record) => {
             switch (record.eventType) {
             case WI.LayoutTimelineRecord.EventType.RecalculateStyles:
@@ -1597,7 +1596,7 @@
         }
 
         let scriptTimeline = this._recording.timelineForRecordType(WI.TimelineRecord.Type.Script);
-        let scriptRecords = scriptTimeline ? scriptTimeline.recordsOverlappingTimeRange(startTime, endTime) : [];
+        let scriptRecords = scriptTimeline ? scriptTimeline.recordsInTimeRange(startTime, endTime, {includeRecordBeforeStart: true}) : [];
         scriptRecords = scriptRecords.filter((record) => {
             switch (record.eventType) {
             case WI.ScriptTimelineRecord.EventType.ScriptEvaluated:
diff --git a/Source/WebInspectorUI/UserInterface/Views/MemoryTimelineOverviewGraph.js b/Source/WebInspectorUI/UserInterface/Views/MemoryTimelineOverviewGraph.js
index 496092f..f272287 100644
--- a/Source/WebInspectorUI/UserInterface/Views/MemoryTimelineOverviewGraph.js
+++ b/Source/WebInspectorUI/UserInterface/Views/MemoryTimelineOverviewGraph.js
@@ -138,11 +138,10 @@
 
         let discontinuities = this.timelineOverview.discontinuitiesInTimeRange(graphStartTime, visibleEndTime);
 
-        // Don't include the record before the graph start if the graph start is within a gap.
-        let includeRecordBeforeStart = !discontinuities.length || discontinuities[0].startTime > graphStartTime;
-
-        // FIXME: <https://webkit.org/b/153759> Web Inspector: Memory Timelines should better extend to future data
-        let visibleRecords = this._memoryTimeline.recordsInTimeRange(graphStartTime, visibleEndTime, includeRecordBeforeStart);
+        let visibleRecords = this._memoryTimeline.recordsInTimeRange(graphStartTime, visibleEndTime, {
+            includeRecordBeforeStart: !discontinuities.length || discontinuities[0].startTime > graphStartTime,
+            includeRecordAfterEnd: true,
+        });
         if (!visibleRecords.length)
             return;
 
diff --git a/Source/WebInspectorUI/UserInterface/Views/MemoryTimelineView.js b/Source/WebInspectorUI/UserInterface/Views/MemoryTimelineView.js
index 5172770..31e3dbb 100644
--- a/Source/WebInspectorUI/UserInterface/Views/MemoryTimelineView.js
+++ b/Source/WebInspectorUI/UserInterface/Views/MemoryTimelineView.js
@@ -197,11 +197,10 @@
 
         let discontinuities = this._recording.discontinuitiesInTimeRange(graphStartTime, visibleEndTime);
 
-        // Don't include the record before the graph start if the graph start is within a gap.
-        let includeRecordBeforeStart = !discontinuities.length || discontinuities[0].startTime > graphStartTime;
-
-        // FIXME: <https://webkit.org/b/153759> Web Inspector: Memory Timelines should better extend to future data
-        let visibleRecords = this.representedObject.recordsInTimeRange(graphStartTime, visibleEndTime, includeRecordBeforeStart);
+        let visibleRecords = this.representedObject.recordsInTimeRange(graphStartTime, visibleEndTime, {
+            includeRecordBeforeStart: !discontinuities.length || discontinuities[0].startTime > graphStartTime,
+            includeRecordAfterEnd: true,
+        });
         if (!visibleRecords.length || (visibleRecords.length === 1 && visibleRecords[0].endTime < graphStartTime)) {
             this.clear();
             return;
diff --git a/Source/WebInspectorUI/UserInterface/Views/TimelineOverview.js b/Source/WebInspectorUI/UserInterface/Views/TimelineOverview.js
index 3cec4fb..f11cff7 100644
--- a/Source/WebInspectorUI/UserInterface/Views/TimelineOverview.js
+++ b/Source/WebInspectorUI/UserInterface/Views/TimelineOverview.js
@@ -784,12 +784,7 @@
 
             let startTime = firstRecord instanceof WI.RenderingFrameTimelineRecord ? firstRecord.frameIndex : firstRecord.startTime;
             let endTime = lastRecord instanceof WI.RenderingFrameTimelineRecord ? lastRecord.frameIndex : lastRecord.endTime;
-
-            if (firstRecord instanceof WI.CPUTimelineRecord) {
-                let selectionPadding = WI.CPUTimelineOverviewGraph.samplingRatePerSecond * 2.25;
-                this.selectionStartTime = startTime - selectionPadding - (WI.CPUTimelineOverviewGraph.samplingRatePerSecond / 2);
-                this.selectionDuration = endTime - startTime + (selectionPadding * 2);
-            } else if (startTime < this.selectionStartTime || endTime > this.selectionStartTime + this.selectionDuration) {
+            if (startTime < this.selectionStartTime || (endTime > this.selectionStartTime + this.selectionDuration) || firstRecord instanceof WI.CPUTimelineRecord) {
                 let selectionPadding = this.secondsPerPixel * 10;
                 this.selectionStartTime = startTime - selectionPadding;
                 this.selectionDuration = endTime - startTime + (selectionPadding * 2);