Web Inspector: REGRESSION(r245498): Timelines: CPU: discontinuities are filled in by the next record
https://bugs.webkit.org/show_bug.cgi?id=198927

Reviewed by Matt Baker.

Source/WebInspectorUI:

* UserInterface/Controllers/TimelineManager.js:
(WI.TimelineManager.prototype.capturingStarted):
(WI.TimelineManager.prototype.capturingStopped):
* UserInterface/Models/TimelineRecording.js:
(WI.TimelineRecording):
(WI.TimelineRecording.prototype.start):
(WI.TimelineRecording.prototype.capturingStarted): Added.
(WI.TimelineRecording.prototype.capturingStopped): Added.
(WI.TimelineRecording.prototype.reset):
(WI.TimelineRecording.prototype.addRecord):
(WI.TimelineRecording.prototype.discontinuitiesInTimeRange):
(WI.TimelineRecording.prototype.addDiscontinuity): Deleted.
Notify the `TimelineRecording` when capturing has started/stopped.
Adjust the first record after a discontinuity to have it's `startTime` match the `endTime`
of the most recent discontinuity.

* UserInterface/Models/Timeline.js:
(WI.Timeline.prototype.addRecord):
* UserInterface/Models/CPUTimeline.js:
(WI.CPUTimeline.prototype.addRecord):
* UserInterface/Models/CPUTimelineRecord.js:
(WI.CPUTimelineRecord.prototype.adjustStartTime): Added.
(WI.CPUTimelineRecord.prototype.adjustStartTimeToLastRecord): Deleted.
* UserInterface/Models/MemoryTimeline.js:
(WI.MemoryTimeline.prototype.addRecord):
* UserInterface/Models/MemoryTimelineRecord.js:
(WI.MemoryTimelineRecord.prototype.adjustStartTime): Added.
(WI.MemoryTimelineRecord.prototype.adjustStartTimeToLastRecord): Deleted.
* UserInterface/Models/NetworkTimeline.js:
(WI.NetworkTimeline.prototype.addRecord):

* UserInterface/Views/CPUTimelineView.js:
(WI.CPUTimelineView.prototype.layout):
* UserInterface/Views/MemoryTimelineOverviewGraph.js:
(WI.MemoryTimelineOverviewGraph.prototype.layout):
* UserInterface/Views/MemoryTimelineView.js:
(WI.MemoryTimelineView.prototype.layout):
Include discontinuities that exactly match the start/end time of the record immediately
before/after the discontinuity.

* UserInterface/Views/TimelineRecordingContentView.js:
(WI.TimelineRecordingContentView):
(WI.TimelineRecordingContentView.prototype._handleTimelineCapturingStateChanged):
(WI.TimelineRecordingContentView.prototype._recordingReset):
Move the logic for handling discontinuity start/end times to the `TimelineRecording`.

* UserInterface/Base/Utilities.js:

LayoutTests:

* inspector/unit-tests/set-utilities.html:
* inspector/unit-tests/set-utilities-expected.txt:


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@247033 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog
index fdfbcf9..81897b5 100644
--- a/LayoutTests/ChangeLog
+++ b/LayoutTests/ChangeLog
@@ -1,3 +1,13 @@
+2019-07-01  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: REGRESSION(r245498): Timelines: CPU: discontinuities are filled in by the next record
+        https://bugs.webkit.org/show_bug.cgi?id=198927
+
+        Reviewed by Matt Baker.
+
+        * inspector/unit-tests/set-utilities.html:
+        * inspector/unit-tests/set-utilities-expected.txt:
+
 2019-07-01  Russell Epstein  <russell_e@apple.com>
 
         Layout Test imported/blink/fast/multicol/span/overflow-on-viewport.html is flaky.
diff --git a/LayoutTests/inspector/unit-tests/set-utilities-expected.txt b/LayoutTests/inspector/unit-tests/set-utilities-expected.txt
index d2e7d22..c127acb 100644
--- a/LayoutTests/inspector/unit-tests/set-utilities-expected.txt
+++ b/LayoutTests/inspector/unit-tests/set-utilities-expected.txt
@@ -1,5 +1,11 @@
 
 == Running test suite: Set
+-- Running test case: Set.prototype.take
+PASS: Set can take `key`.
+PASS: Set no longer has `key`.
+PASS: Set can NOT take `key`.
+PASS: Set can NOT take `DNE`, as it does NOT exist.
+
 -- Running test case: Set.prototype.intersects
 PASS: an empty set should not intersect another empty set.
 PASS: a non-empty set should not intersect an empty set.
diff --git a/LayoutTests/inspector/unit-tests/set-utilities.html b/LayoutTests/inspector/unit-tests/set-utilities.html
index 5905e2f..c708aa0 100644
--- a/LayoutTests/inspector/unit-tests/set-utilities.html
+++ b/LayoutTests/inspector/unit-tests/set-utilities.html
@@ -8,6 +8,20 @@
     let suite = InspectorTest.createSyncSuite("Set");
 
     suite.addTestCase({
+        name: "Set.prototype.take",
+        test() {
+            const key = "key";
+
+            let set = new Set;
+            set.add(key);
+            InspectorTest.expectTrue(set.take(key), "Set can take `key`.");
+            InspectorTest.expectFalse(set.has(key), "Set no longer has `key`.");
+            InspectorTest.expectFalse(set.take(key), "Set can NOT take `key`.");
+            InspectorTest.expectFalse(set.take("DNE"), "Set can NOT take `DNE`, as it does NOT exist.");
+        }
+    });
+
+    suite.addTestCase({
         name: "Set.prototype.intersects",
         test() {
             function testTrue(a, b, message) {
diff --git a/Source/WebInspectorUI/ChangeLog b/Source/WebInspectorUI/ChangeLog
index 70ac1bb..d187121 100644
--- a/Source/WebInspectorUI/ChangeLog
+++ b/Source/WebInspectorUI/ChangeLog
@@ -1,3 +1,58 @@
+2019-07-01  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: REGRESSION(r245498): Timelines: CPU: discontinuities are filled in by the next record
+        https://bugs.webkit.org/show_bug.cgi?id=198927
+
+        Reviewed by Matt Baker.
+
+        * UserInterface/Controllers/TimelineManager.js:
+        (WI.TimelineManager.prototype.capturingStarted):
+        (WI.TimelineManager.prototype.capturingStopped):
+        * UserInterface/Models/TimelineRecording.js:
+        (WI.TimelineRecording):
+        (WI.TimelineRecording.prototype.start):
+        (WI.TimelineRecording.prototype.capturingStarted): Added.
+        (WI.TimelineRecording.prototype.capturingStopped): Added.
+        (WI.TimelineRecording.prototype.reset):
+        (WI.TimelineRecording.prototype.addRecord):
+        (WI.TimelineRecording.prototype.discontinuitiesInTimeRange):
+        (WI.TimelineRecording.prototype.addDiscontinuity): Deleted.
+        Notify the `TimelineRecording` when capturing has started/stopped.
+        Adjust the first record after a discontinuity to have it's `startTime` match the `endTime`
+        of the most recent discontinuity.
+
+        * UserInterface/Models/Timeline.js:
+        (WI.Timeline.prototype.addRecord):
+        * UserInterface/Models/CPUTimeline.js:
+        (WI.CPUTimeline.prototype.addRecord):
+        * UserInterface/Models/CPUTimelineRecord.js:
+        (WI.CPUTimelineRecord.prototype.adjustStartTime): Added.
+        (WI.CPUTimelineRecord.prototype.adjustStartTimeToLastRecord): Deleted.
+        * UserInterface/Models/MemoryTimeline.js:
+        (WI.MemoryTimeline.prototype.addRecord):
+        * UserInterface/Models/MemoryTimelineRecord.js:
+        (WI.MemoryTimelineRecord.prototype.adjustStartTime): Added.
+        (WI.MemoryTimelineRecord.prototype.adjustStartTimeToLastRecord): Deleted.
+        * UserInterface/Models/NetworkTimeline.js:
+        (WI.NetworkTimeline.prototype.addRecord):
+
+        * UserInterface/Views/CPUTimelineView.js:
+        (WI.CPUTimelineView.prototype.layout):
+        * UserInterface/Views/MemoryTimelineOverviewGraph.js:
+        (WI.MemoryTimelineOverviewGraph.prototype.layout):
+        * UserInterface/Views/MemoryTimelineView.js:
+        (WI.MemoryTimelineView.prototype.layout):
+        Include discontinuities that exactly match the start/end time of the record immediately
+        before/after the discontinuity.
+
+        * UserInterface/Views/TimelineRecordingContentView.js:
+        (WI.TimelineRecordingContentView):
+        (WI.TimelineRecordingContentView.prototype._handleTimelineCapturingStateChanged):
+        (WI.TimelineRecordingContentView.prototype._recordingReset):
+        Move the logic for handling discontinuity start/end times to the `TimelineRecording`.
+
+        * UserInterface/Base/Utilities.js:
+
 2019-06-29  Nikita Vasilyev  <nvasilyev@apple.com>
 
         Web Inspector: Remove trailing white-space
diff --git a/Source/WebInspectorUI/UserInterface/Base/Utilities.js b/Source/WebInspectorUI/UserInterface/Base/Utilities.js
index 673e2f2..56744fb 100644
--- a/Source/WebInspectorUI/UserInterface/Base/Utilities.js
+++ b/Source/WebInspectorUI/UserInterface/Base/Utilities.js
@@ -146,6 +146,17 @@
     }
 });
 
+Object.defineProperty(Set.prototype, "take",
+{
+    value(key)
+    {
+        let exists = this.has(key);
+        if (exists)
+            this.delete(key);
+        return exists;
+    }
+});
+
 Object.defineProperty(Set.prototype, "equals",
 {
     value(other)
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/TimelineManager.js b/Source/WebInspectorUI/UserInterface/Controllers/TimelineManager.js
index a04386e..874efba 100644
--- a/Source/WebInspectorUI/UserInterface/Controllers/TimelineManager.js
+++ b/Source/WebInspectorUI/UserInterface/Controllers/TimelineManager.js
@@ -355,6 +355,8 @@
 
         this._webTimelineScriptRecordsExpectingScriptProfilerEvents = [];
 
+        this._activeRecording.capturingStarted(this._capturingStartTime);
+
         WI.settings.timelinesAutoStop.addEventListener(WI.Setting.Event.Changed, this._handleTimelinesAutoStopSettingChanged, this);
 
         WI.Frame.addEventListener(WI.Frame.Event.ResourceWasAdded, this._resourceWasAdded, this);
@@ -400,6 +402,8 @@
         WI.Frame.removeEventListener(WI.Frame.Event.ResourceWasAdded, this._resourceWasAdded, this);
         WI.settings.timelinesAutoStop.removeEventListener(null, null, this);
 
+        this._activeRecording.capturingStopped(this._capturingEndTime);
+
         this.relaxAutoStop();
 
         this._isCapturingPageReload = false;
diff --git a/Source/WebInspectorUI/UserInterface/Models/CPUTimeline.js b/Source/WebInspectorUI/UserInterface/Models/CPUTimeline.js
index 128022f..88cfa75 100644
--- a/Source/WebInspectorUI/UserInterface/Models/CPUTimeline.js
+++ b/Source/WebInspectorUI/UserInterface/Models/CPUTimeline.js
@@ -27,12 +27,16 @@
 {
     // Public
 
-    addRecord(record)
+    addRecord(record, options = {})
     {
         let lastRecord = this.records.lastValue;
-        if (lastRecord)
-            record.adjustStartTimeToLastRecord(lastRecord);
+        if (lastRecord) {
+            let startTime = lastRecord.endTime;
+            if (options.discontinuity)
+                startTime = options.discontinuity.endTime;
+            record.adjustStartTime(startTime);
+        }
 
-        super.addRecord(record);
+        super.addRecord(record, options);
     }
 };
diff --git a/Source/WebInspectorUI/UserInterface/Models/CPUTimelineRecord.js b/Source/WebInspectorUI/UserInterface/Models/CPUTimelineRecord.js
index 112b713..c26bdce 100644
--- a/Source/WebInspectorUI/UserInterface/Models/CPUTimelineRecord.js
+++ b/Source/WebInspectorUI/UserInterface/Models/CPUTimelineRecord.js
@@ -104,10 +104,9 @@
     get unknownThreadUsage() { return this._unknownThreadUsage; }
     get workersData() { return this._workersData; }
 
-    adjustStartTimeToLastRecord(lastRecord)
+    adjustStartTime(startTime)
     {
-        console.assert(lastRecord instanceof CPUTimelineRecord);
-        console.assert(this._startTime >= lastRecord.endTime);
-        this._startTime = lastRecord.endTime;
+        console.assert(startTime < this._endTime);
+        this._startTime = startTime;
     }
 };
diff --git a/Source/WebInspectorUI/UserInterface/Models/MemoryTimeline.js b/Source/WebInspectorUI/UserInterface/Models/MemoryTimeline.js
index 4b4f060..f91ef35 100644
--- a/Source/WebInspectorUI/UserInterface/Models/MemoryTimeline.js
+++ b/Source/WebInspectorUI/UserInterface/Models/MemoryTimeline.js
@@ -47,13 +47,17 @@
         this._pressureEvents = [];
     }
 
-    addRecord(record)
+    addRecord(record, options = {})
     {
         let lastRecord = this.records.lastValue;
-        if (lastRecord)
-            record.adjustStartTimeToLastRecord(lastRecord);
+        if (lastRecord) {
+            let startTime = lastRecord.endTime;
+            if (options.discontinuity)
+                startTime = options.discontinuity.endTime;
+            record.adjustStartTime(startTime);
+        }
 
-        super.addRecord(record);
+        super.addRecord(record, options);
     }
 };
 
diff --git a/Source/WebInspectorUI/UserInterface/Models/MemoryTimelineRecord.js b/Source/WebInspectorUI/UserInterface/Models/MemoryTimelineRecord.js
index 787bedc..c2d09a3 100644
--- a/Source/WebInspectorUI/UserInterface/Models/MemoryTimelineRecord.js
+++ b/Source/WebInspectorUI/UserInterface/Models/MemoryTimelineRecord.js
@@ -109,10 +109,9 @@
     get categories() { return this._categories; }
     get totalSize() { return this._totalSize; }
 
-    adjustStartTimeToLastRecord(lastRecord)
+    adjustStartTime(startTime)
     {
-        console.assert(lastRecord instanceof MemoryTimelineRecord);
-        console.assert(this._startTime >= lastRecord.endTime);
-        this._startTime = lastRecord.endTime;
+        console.assert(startTime < this._endTime);
+        this._startTime = startTime;
     }
 };
diff --git a/Source/WebInspectorUI/UserInterface/Models/NetworkTimeline.js b/Source/WebInspectorUI/UserInterface/Models/NetworkTimeline.js
index c772ae0..f0ac7a1 100644
--- a/Source/WebInspectorUI/UserInterface/Models/NetworkTimeline.js
+++ b/Source/WebInspectorUI/UserInterface/Models/NetworkTimeline.js
@@ -41,7 +41,7 @@
         super.reset(suppressEvents);
     }
 
-    addRecord(record)
+    addRecord(record, options = {})
     {
         console.assert(record instanceof WI.ResourceTimelineRecord);
 
@@ -51,6 +51,6 @@
 
         this._resourceRecordMap.set(record.resource, record);
 
-        super.addRecord(record);
+        super.addRecord(record, options);
     }
 };
diff --git a/Source/WebInspectorUI/UserInterface/Models/Timeline.js b/Source/WebInspectorUI/UserInterface/Models/Timeline.js
index 28f4252..cb88637 100644
--- a/Source/WebInspectorUI/UserInterface/Models/Timeline.js
+++ b/Source/WebInspectorUI/UserInterface/Models/Timeline.js
@@ -69,7 +69,7 @@
         }
     }
 
-    addRecord(record)
+    addRecord(record, options = {})
     {
         if (record.updatesDynamically)
             record.addEventListener(WI.TimelineRecord.Event.Updated, this._recordUpdated, this);
diff --git a/Source/WebInspectorUI/UserInterface/Models/TimelineRecording.js b/Source/WebInspectorUI/UserInterface/Models/TimelineRecording.js
index 0b4bbc4..f744f94 100644
--- a/Source/WebInspectorUI/UserInterface/Models/TimelineRecording.js
+++ b/Source/WebInspectorUI/UserInterface/Models/TimelineRecording.js
@@ -39,7 +39,10 @@
 
         this._startTime = NaN;
         this._endTime = NaN;
+
+        this._discontinuityStartTime = NaN;
         this._discontinuities = null;
+        this._firstRecordOfTypeAfterDiscontinuity = new Set;
 
         this._exportDataRecords = null;
         this._exportDataMarkers = null;
@@ -168,6 +171,11 @@
 
         for (let instrument of this._instruments)
             instrument.startInstrumentation(initiatedByBackend);
+
+        if (!isNaN(this._discontinuityStartTime)) {
+            for (let instrument of this._instruments)
+                this._firstRecordOfTypeAfterDiscontinuity.add(instrument.timelineRecordType);
+        }
     }
 
     stop(initiatedByBackend)
@@ -181,6 +189,25 @@
             instrument.stopInstrumentation(initiatedByBackend);
     }
 
+    capturingStarted(startTime)
+    {
+        // A discontinuity occurs when the recording is stopped and resumed at
+        // a future time. Capturing started signals the end of the current
+        // discontinuity, if one exists.
+        if (!isNaN(this._discontinuityStartTime)) {
+            this._discontinuities.push({
+                startTime: this._discontinuityStartTime,
+                endTime: startTime,
+            });
+            this._discontinuityStartTime = NaN;
+        }
+    }
+
+    capturingStopped(endTime)
+    {
+        this._discontinuityStartTime = endTime;
+    }
+
     saveIdentityToCookie()
     {
         // Do nothing. Timeline recordings are not persisted when the inspector is
@@ -214,7 +241,10 @@
 
         this._startTime = NaN;
         this._endTime = NaN;
+
+        this._discontinuityStartTime = NaN;
         this._discontinuities = [];
+        this._firstRecordOfTypeAfterDiscontinuity.clear();
 
         this._exportDataRecords = [];
         this._exportDataMarkers = []
@@ -300,8 +330,12 @@
         if (!timeline)
             return;
 
+        let discontinuity = null;
+        if (this._firstRecordOfTypeAfterDiscontinuity.take(record.type))
+            discontinuity = this._discontinuities.lastValue;
+
         // Add the record to the global timeline by type.
-        timeline.addRecord(record);
+        timeline.addRecord(record, {discontinuity});
 
         // Some records don't have source code timelines.
         if (record.type === WI.TimelineRecord.Type.Network
@@ -358,14 +392,9 @@
         memoryTimeline.addMemoryPressureEvent(memoryPressureEvent);
     }
 
-    addDiscontinuity(startTime, endTime)
-    {
-        this._discontinuities.push({startTime, endTime});
-    }
-
     discontinuitiesInTimeRange(startTime, endTime)
     {
-        return this._discontinuities.filter((item) => item.startTime < endTime && item.endTime > startTime);
+        return this._discontinuities.filter((item) => item.startTime <= endTime && item.endTime >= startTime);
     }
 
     addScriptInstrumentForProgrammaticCapture()
diff --git a/Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.js b/Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.js
index 53f1b62..0c6b549 100644
--- a/Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.js
+++ b/Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.js
@@ -486,10 +486,10 @@
             let time = record.startTime;
             let {usage, mainThreadUsage, workerThreadUsage, webkitThreadUsage, unknownThreadUsage} = record;
 
-            if (discontinuities.length && discontinuities[0].endTime < time) {
+            if (discontinuities.length && discontinuities[0].endTime <= time) {
                 let startDiscontinuity = discontinuities.shift();
                 let endDiscontinuity = startDiscontinuity;
-                while (discontinuities.length && discontinuities[0].endTime < time)
+                while (discontinuities.length && discontinuities[0].endTime <= time)
                     endDiscontinuity = discontinuities.shift();
 
                 if (dataPoints.length) {
diff --git a/Source/WebInspectorUI/UserInterface/Views/MemoryTimelineOverviewGraph.js b/Source/WebInspectorUI/UserInterface/Views/MemoryTimelineOverviewGraph.js
index f272287..40bc1b2 100644
--- a/Source/WebInspectorUI/UserInterface/Views/MemoryTimelineOverviewGraph.js
+++ b/Source/WebInspectorUI/UserInterface/Views/MemoryTimelineOverviewGraph.js
@@ -188,10 +188,10 @@
         // Points for visible records.
         let previousRecord = null;
         for (let record of visibleRecords) {
-            if (discontinuities.length && discontinuities[0].endTime < record.startTime) {
+            if (discontinuities.length && discontinuities[0].endTime <= record.startTime) {
                 let startDiscontinuity = discontinuities.shift();
                 let endDiscontinuity = startDiscontinuity;
-                while (discontinuities.length && discontinuities[0].endTime < record.startTime)
+                while (discontinuities.length && discontinuities[0].endTime <= record.startTime)
                     endDiscontinuity = discontinuities.shift();
                 insertDiscontinuity.call(this, previousRecord, startDiscontinuity, endDiscontinuity, record);
             }
diff --git a/Source/WebInspectorUI/UserInterface/Views/MemoryTimelineView.js b/Source/WebInspectorUI/UserInterface/Views/MemoryTimelineView.js
index 31e3dbb..908c93d 100644
--- a/Source/WebInspectorUI/UserInterface/Views/MemoryTimelineView.js
+++ b/Source/WebInspectorUI/UserInterface/Views/MemoryTimelineView.js
@@ -228,10 +228,10 @@
             let time = record.startTime;
             let startDiscontinuity = null;
             let endDiscontinuity = null;
-            if (discontinuities.length && discontinuities[0].endTime < time) {
+            if (discontinuities.length && discontinuities[0].endTime <= time) {
                 startDiscontinuity = discontinuities.shift();
                 endDiscontinuity = startDiscontinuity;
-                while (discontinuities.length && discontinuities[0].endTime < time)
+                while (discontinuities.length && discontinuities[0].endTime <= time)
                     endDiscontinuity = discontinuities.shift();
             }
 
diff --git a/Source/WebInspectorUI/UserInterface/Views/TimelineRecordingContentView.js b/Source/WebInspectorUI/UserInterface/Views/TimelineRecordingContentView.js
index 40f6c73..fa1dc21 100644
--- a/Source/WebInspectorUI/UserInterface/Views/TimelineRecordingContentView.js
+++ b/Source/WebInspectorUI/UserInterface/Views/TimelineRecordingContentView.js
@@ -92,7 +92,6 @@
 
         this._updating = false;
         this._currentTime = NaN;
-        this._discontinuityStartTime = NaN;
         this._lastUpdateTimestamp = NaN;
         this._startTimeNeedsReset = true;
         this._renderingFrameTimeline = null;
@@ -523,14 +522,6 @@
 
             this._clearTimelineNavigationItem.enabled = !this._recording.readonly;
             this._exportButtonNavigationItem.enabled = false;
-
-            // A discontinuity occurs when the recording is stopped and resumed at
-            // a future time. Capturing started signals the end of the current
-            // discontinuity, if one exists.
-            if (!isNaN(this._discontinuityStartTime)) {
-                this._recording.addDiscontinuity(this._discontinuityStartTime, startTime);
-                this._discontinuityStartTime = NaN;
-            }
             break;
 
         case WI.TimelineManager.CapturingState.Inactive:
@@ -540,8 +531,6 @@
             if (this.currentTimelineView)
                 this._updateTimelineViewTimes(this.currentTimelineView);
 
-            this._discontinuityStartTime = endTime || this._currentTime;
-
             this._exportButtonNavigationItem.enabled = this._recording.canExport();
             break;
         }
@@ -710,7 +699,6 @@
             timelineView.reset();
 
         this._currentTime = NaN;
-        this._discontinuityStartTime = NaN;
 
         if (!this._updating) {
             // Force the time ruler and views to reset to 0.