Title: [243024] trunk
Revision
243024
Author
joep...@webkit.org
Date
2019-03-15 16:45:51 -0700 (Fri, 15 Mar 2019)

Log Message

Web Inspector: Timelines - Import / Export Timeline Recordings
https://bugs.webkit.org/show_bug.cgi?id=195709
<rdar://problem/23188921>

Reviewed by Devin Rousso.

Source/WebInspectorUI:

Timeline exporting saves TimelineRecording and TimelineOverview state.
The TimelineRecording includes all kinds of model objects, such as
records, markers, memory pressure events, etc. It also includes raw
protocol data, such as script profiler samples. TimelineOverview
includes some of the view state to restore, such as the selected
time range, zoom level, and selected timeline.

Timeline importing constructs a new TimelineRecording by replaying
the records, markers, and other events, as well as re-initializing
more state. To finally display the imported recording, the content
view will immediately initialize start/current/end times and the
overview will restore the view state.

* Localizations/en.lproj/localizedStrings.js:
New strings.

* UserInterface/Controllers/TimelineManager.js:
(WI.TimelineManager.synthesizeImportError):
(WI.TimelineManager.prototype.importRecording):
Import API.

(WI.TimelineManager.prototype.scriptProfilerTrackingCompleted):
Initialize the samples on the recording via a different path
so that the data can be saved for exporting.

* UserInterface/Models/TimelineRecording.js:
(WI.TimelineRecording):
(WI.TimelineRecording.import):
(WI.TimelineRecording.prototype.exportData):
(WI.TimelineRecording.prototype.get capturing):
(WI.TimelineRecording.prototype.get imported):
(WI.TimelineRecording.prototype.unloaded):
(WI.TimelineRecording.prototype.reset):
(WI.TimelineRecording.prototype.addEventMarker):
(WI.TimelineRecording.prototype.addRecord):
(WI.TimelineRecording.prototype.addMemoryPressureEvent):
(WI.TimelineRecording.prototype.initializeCallingContextTrees):
(WI.TimelineRecording.prototype.canExport):
Save data at the TimelineRecording level that can be used for export.
We only allow exporting a TimelineRecording that has started/stopped
at least once and is not currently capturing.

* UserInterface/Views/TimelineRecordingContentView.js:
(WI.TimelineRecordingContentView):
(WI.TimelineRecordingContentView.prototype.get navigationItems):
(WI.TimelineRecordingContentView.prototype.get supportsSave):
(WI.TimelineRecordingContentView.prototype.get saveData):
(WI.TimelineRecordingContentView.prototype.shown):
(WI.TimelineRecordingContentView.prototype._capturingStarted):
(WI.TimelineRecordingContentView.prototype._capturingStopped):
(WI.TimelineRecordingContentView.prototype._initializeImportedRecording):
(WI.TimelineRecordingContentView.prototype._exportTimelineRecording):
(WI.TimelineRecordingContentView.prototype._importButtonNavigationItemClicked):
(WI.TimelineRecordingContentView.prototype._recordingReset):
Add Import and Export buttons in the Timeline navigation bar.

* UserInterface/Views/TimelineOverview.js:
(WI.TimelineOverview):
(WI.TimelineOverview.prototype.exportData):
(WI.TimelineOverview.prototype._instrumentAdded):
(WI.TimelineOverview.prototype._recordingImported):
When importing a recording update the TimelineOverview state
soon afterwards.

* UserInterface/Models/CPUTimelineRecord.js:
(WI.CPUTimelineRecord.fromJSON):
(WI.CPUTimelineRecord.prototype.toJSON):
* UserInterface/Models/GarbageCollection.js:
(WI.GarbageCollection.fromJSON):
(WI.GarbageCollection.prototype.toJSON):
* UserInterface/Models/Geometry.js:
(WI.Quad.fromJSON):
(WI.Quad.prototype.toJSON):
* UserInterface/Models/HeapAllocationsTimelineRecord.js:
(WI.HeapAllocationsTimelineRecord.fromJSON):
(WI.HeapAllocationsTimelineRecord.prototype.toJSON):
* UserInterface/Models/LayoutTimelineRecord.js:
(WI.LayoutTimelineRecord.fromJSON):
(WI.LayoutTimelineRecord.prototype.toJSON):
* UserInterface/Models/MediaTimelineRecord.js:
(WI.MediaTimelineRecord.fromJSON):
(WI.MediaTimelineRecord.prototype.toJSON):
* UserInterface/Models/MemoryPressureEvent.js:
(WI.MemoryPressureEvent.fromJSON):
(WI.MemoryPressureEvent.prototype.toJSON):
* UserInterface/Models/MemoryTimelineRecord.js:
(WI.MemoryTimelineRecord):
(WI.MemoryTimelineRecord.fromJSON):
(WI.MemoryTimelineRecord.prototype.toJSON):
* UserInterface/Models/RenderingFrameTimelineRecord.js:
(WI.RenderingFrameTimelineRecord.fromJSON):
(WI.RenderingFrameTimelineRecord.prototype.toJSON):
* UserInterface/Models/ResourceTimelineRecord.js:
(WI.ResourceTimelineRecord.fromJSON):
(WI.ResourceTimelineRecord.prototype.toJSON):
* UserInterface/Models/ScriptTimelineRecord.js:
(WI.ScriptTimelineRecord.fromJSON):
(WI.ScriptTimelineRecord.prototype.toJSON):
* UserInterface/Models/TimelineMarker.js:
(WI.TimelineMarker.fromJSON):
(WI.TimelineMarker.prototype.toJSON):
(WI.TimelineMarker.prototype.get type):
(WI.TimelineMarker.prototype.get details):
(WI.TimelineMarker.prototype.set time):
(WI.TimelineMarker):
* UserInterface/Models/TimelineRecord.js:
(WI.TimelineRecord.fromJSON):
(WI.TimelineRecord.prototype.toJSON):
Import / Export toJSON / fromJSON implementations.

* UserInterface/Views/CPUTimelineOverviewGraph.js:
(WI.CPUTimelineOverviewGraph):
(WI.CPUTimelineOverviewGraph.prototype._cpuTimelineRecordAdded):
(WI.CPUTimelineOverviewGraph.prototype._processRecord):
* UserInterface/Views/LayoutTimelineOverviewGraph.js:
(WI.LayoutTimelineOverviewGraph):
(WI.LayoutTimelineOverviewGraph.prototype._layoutTimelineRecordAdded):
(WI.LayoutTimelineOverviewGraph.prototype._processRecord):
* UserInterface/Views/LayoutTimelineView.js:
(WI.LayoutTimelineView):
(WI.LayoutTimelineView.prototype._layoutTimelineRecordAdded):
(WI.LayoutTimelineView.prototype._processRecord):
* UserInterface/Views/MediaTimelineView.js:
(WI.MediaTimelineView):
(WI.MediaTimelineView.prototype._handleRecordAdded):
(WI.MediaTimelineView.prototype._processRecord):
* UserInterface/Views/MemoryTimelineOverviewGraph.js:
(WI.MemoryTimelineOverviewGraph):
(WI.MemoryTimelineOverviewGraph.prototype._memoryTimelineRecordAdded):
(WI.MemoryTimelineOverviewGraph.prototype._processRecord):
* UserInterface/Views/MemoryTimelineView.js:
(WI.MemoryTimelineView):
(WI.MemoryTimelineView.prototype._memoryTimelineRecordAdded):
(WI.MemoryTimelineView.prototype._processRecord):
* UserInterface/Views/NetworkTimelineOverviewGraph.js:
(WI.NetworkTimelineOverviewGraph):
(WI.NetworkTimelineOverviewGraph.prototype.reset):
(WI.NetworkTimelineOverviewGraph.prototype._networkTimelineRecordAdded):
(WI.NetworkTimelineOverviewGraph.prototype._processRecord):
(WI.NetworkTimelineOverviewGraph.prototype._networkTimelineRecordAdded.compareByStartTime): Deleted.
* UserInterface/Views/NetworkTimelineView.js:
(WI.NetworkTimelineView):
(WI.NetworkTimelineView.prototype._networkTimelineRecordAdded):
(WI.NetworkTimelineView.prototype._processRecord):
* UserInterface/Views/RenderingFrameTimelineView.js:
(WI.RenderingFrameTimelineView):
(WI.RenderingFrameTimelineView.prototype._renderingFrameTimelineRecordAdded):
(WI.RenderingFrameTimelineView.prototype._processRecord):
* UserInterface/Views/ScriptDetailsTimelineView.js:
(WI.ScriptDetailsTimelineView):
(WI.ScriptDetailsTimelineView.prototype._scriptTimelineRecordAdded):
(WI.ScriptDetailsTimelineView.prototype._processRecord):
Add common _processRecord path to each timeline OverviewGraph and TimelineView.
By calling this in construction we populate graphs with TimelineRecords that
may have already existed. This is necessary for imports, but this also fixes
the case where you enable a timeline that had data and it didn't show data.

* UserInterface/Views/LayoutTimelineOverviewGraph.css:
(.timeline-overview-graph.layout-overview > .graph-row):
(.timeline-overview-graph.layout-overview > .graph-row > .timeline-record-bar):
(.timeline-overview-graph.layout-overview > .graph-row > .timeline-record-bar > .segment):
(.timeline-overview-graph.layout > .graph-row): Deleted.
(.timeline-overview-graph.layout > .graph-row > .timeline-record-bar): Deleted.
(.timeline-overview-graph.layout > .graph-row > .timeline-record-bar > .segment): Deleted.
* UserInterface/Views/TimelineRecordBar.css:
(.timeline-record-bar.timeline-record-type-layout.paint > .segment,):
(.timeline-record-bar.timeline-record-type-layout.layout-timeline-record-paint > .segment,): Deleted.
We simplified some of the sub-record type enum strings. To do this we needed to change
"layout" to "layout-overview" to avoid a conflict.

LayoutTests:

* inspector/timeline/timeline-recording-expected.txt: Added.
* inspector/timeline/timeline-recording.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (243023 => 243024)


--- trunk/LayoutTests/ChangeLog	2019-03-15 23:33:16 UTC (rev 243023)
+++ trunk/LayoutTests/ChangeLog	2019-03-15 23:45:51 UTC (rev 243024)
@@ -1,3 +1,14 @@
+2019-03-15  Joseph Pecoraro  <pecor...@apple.com>
+
+        Web Inspector: Timelines - Import / Export Timeline Recordings
+        https://bugs.webkit.org/show_bug.cgi?id=195709
+        <rdar://problem/23188921>
+
+        Reviewed by Devin Rousso.
+
+        * inspector/timeline/timeline-recording-expected.txt: Added.
+        * inspector/timeline/timeline-recording.html: Added.
+
 2019-03-15  Zalan Bujtas  <za...@apple.com>
 
         [ContentChangeObserver] HTMLImageElement::willRespondToMouseClickEvents returns quirk value.

Added: trunk/LayoutTests/inspector/timeline/timeline-recording-expected.txt (0 => 243024)


--- trunk/LayoutTests/inspector/timeline/timeline-recording-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/inspector/timeline/timeline-recording-expected.txt	2019-03-15 23:45:51 UTC (rev 243024)
@@ -0,0 +1,70 @@
+Tests for timeline recording.
+
+
+== Running test suite: TimelineRecording
+-- Running test case: TimelineRecording.Basic
+Loaded
+PASS: TimelineManager should not be capturing.
+PASS: TimelineRecording should not be capturing.
+PASS: TimelineRecording should not be readonly.
+PASS: TimelineRecording should not be imported.
+PASS: TimelineRecording should not have a startTime.
+PASS: TimelineRecording should not have a endTime.
+Start
+PASS: TimelineManager should be capturing.
+PASS: TimelineManager active recording should not have changed.
+PASS: TimelineRecording should be capturing.
+PASS: TimelineRecording should not be readonly.
+Reload
+Stop
+PASS: TimelineManager should not be capturing.
+PASS: TimelineRecording should not be capturing.
+PASS: TimelineRecording should not be readonly.
+PASS: TimelineRecording should not be imported.
+PASS: TimelineRecording should have a startTime.
+PASS: TimelineRecording should have a endTime.
+
+-- Running test case: TimelineRecording.prototype.exportData
+PASS: TimelineRecording should be able to export.
+PASS: TimelineRecording should be able to produce export data.
+PASS: TimelineRecording should have at least 10 Timeline Records.
+Export Data:
+{
+  "displayName": "<filtered>",
+  "startTime": "<filtered>",
+  "endTime": "<filtered>",
+  "discontinuities": [],
+  "instrumentTypes": [
+    "timeline-record-type-network",
+    "timeline-record-type-layout",
+    "timeline-record-type-script",
+    "timeline-record-type-cpu",
+    "timeline-record-type-rendering-frame"
+  ],
+  "records": "<filtered>",
+  "markers": [
+    {
+      "time": "<filtered>",
+      "type": "dom-content-event"
+    },
+    {
+      "time": "<filtered>",
+      "type": "load-event"
+    }
+  ],
+  "memoryPressureEvents": [],
+  "sampleStackTraces": [],
+  "sampleDurations": []
+}
+
+-- Running test case: TimelineRecording.import
+PASS: TimelineManager active recording is not this imported recording.
+PASS: TimelineRecording should not be capturing.
+PASS: TimelineRecording should be readonly.
+PASS: TimelineRecording should be imported.
+PASS: TimelineRecording should have a startTime.
+PASS: TimelineRecording should have a endTime.
+PASS: TimelineRecording identifier should be 999.
+Display Name:
+Imported - TEST
+

Added: trunk/LayoutTests/inspector/timeline/timeline-recording.html (0 => 243024)


--- trunk/LayoutTests/inspector/timeline/timeline-recording.html	                        (rev 0)
+++ trunk/LayoutTests/inspector/timeline/timeline-recording.html	2019-03-15 23:45:51 UTC (rev 243024)
@@ -0,0 +1,108 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src=""
+<script>
+function test()
+{
+    let suite = InspectorTest.createAsyncSuite("TimelineRecording");
+
+    let exportData = null;
+
+    suite.addTestCase({
+        name: "TimelineRecording.Basic",
+        description: "Make a basic Timeline recording.",
+        async test() {
+            let recording = WI.timelineManager.activeRecording;
+
+            InspectorTest.log("Loaded");
+            InspectorTest.expectFalse(WI.timelineManager.isCapturing(), "TimelineManager should not be capturing.");
+            InspectorTest.expectFalse(recording.capturing, "TimelineRecording should not be capturing.");
+            InspectorTest.expectFalse(recording.readonly, "TimelineRecording should not be readonly.");
+            InspectorTest.expectFalse(recording.imported, "TimelineRecording should not be imported.");
+            InspectorTest.expectThat(isNaN(recording.startTime), "TimelineRecording should not have a startTime.");
+            InspectorTest.expectThat(isNaN(recording.endTime), "TimelineRecording should not have a endTime.");
+
+            InspectorTest.log("Start");
+            WI.timelineManager.startCapturing();
+            await WI.timelineManager.awaitEvent(WI.TimelineManager.Event.CapturingStarted);
+            InspectorTest.expectTrue(WI.timelineManager.isCapturing(), "TimelineManager should be capturing.");
+            InspectorTest.expectEqual(WI.timelineManager.activeRecording, recording, "TimelineManager active recording should not have changed.");
+            InspectorTest.expectTrue(recording.capturing, "TimelineRecording should be capturing.");
+            InspectorTest.expectFalse(recording.readonly, "TimelineRecording should not be readonly.");
+
+            InspectorTest.log("Reload");
+            await Promise.all([
+                InspectorTest.awaitEvent(FrontendTestHarness.Event.TestPageDidLoad),
+                InspectorTest.reloadPage(),
+            ]);
+
+            InspectorTest.log("Stop");
+            WI.timelineManager.stopCapturing();
+            InspectorTest.expectFalse(WI.timelineManager.isCapturing(), "TimelineManager should not be capturing.");
+            InspectorTest.expectFalse(recording.capturing, "TimelineRecording should not be capturing.");
+            InspectorTest.expectFalse(recording.readonly, "TimelineRecording should not be readonly.");
+            InspectorTest.expectFalse(recording.imported, "TimelineRecording should not be imported.");
+            InspectorTest.expectThat(!isNaN(recording.startTime), "TimelineRecording should have a startTime.");
+            InspectorTest.expectThat(!isNaN(recording.endTime), "TimelineRecording should have a endTime.");
+        }
+    });
+
+    suite.addTestCase({
+        name: "TimelineRecording.prototype.exportData",
+        description: "Test for a recording export.",
+        async test() {
+            let recording = WI.timelineManager.activeRecording;
+            InspectorTest.assert(!isNaN(recording.startTime), "FAIL: Previous test loading a recording failed.");
+            InspectorTest.assert(!isNaN(recording.endTime), "FAIL: Previous test loading a recording failed.");
+
+            InspectorTest.expectTrue(recording.canExport(), "TimelineRecording should be able to export.");
+
+            exportData = recording.exportData();
+            InspectorTest.expectThat(exportData, "TimelineRecording should be able to produce export data.");
+            InspectorTest.expectThat(exportData.records.length > 10, "TimelineRecording should have at least 10 Timeline Records.");
+
+            InspectorTest.log("Export Data:");
+            let filterKeys = new Set(["startTime", "endTime", "time", "records", "displayName"]);
+            InspectorTest.json(exportData, (key, value) => {
+                if (filterKeys.has(key))
+                    return "<filtered>";
+                return value;
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: "TimelineRecording.import",
+        description: "Test for a recording import.",
+        async test() {
+            InspectorTest.assert(exportData, "FAIL: Previous test exporting a recording failed.");
+
+            // NOTE: This runs the toJSON handlers on the timeline records and other model objects,
+            // which is important because importing expects the serialized form of the objects, not
+            // actual model objects.
+            let jsonData = JSON.parse(JSON.stringify(exportData));
+
+            const identifier = 999;
+            let recording = WI.TimelineRecording.import(identifier, jsonData, "TEST");
+            InspectorTest.expectNotEqual(WI.timelineManager.activeRecording, recording, "TimelineManager active recording is not this imported recording.");
+            InspectorTest.expectFalse(recording.capturing, "TimelineRecording should not be capturing.");
+            InspectorTest.expectTrue(recording.readonly, "TimelineRecording should be readonly.");
+            InspectorTest.expectTrue(recording.imported, "TimelineRecording should be imported.");
+            InspectorTest.expectThat(!isNaN(recording.startTime), "TimelineRecording should have a startTime.");
+            InspectorTest.expectThat(!isNaN(recording.endTime), "TimelineRecording should have a endTime.");
+            InspectorTest.expectEqual(recording.identifier, identifier, `TimelineRecording identifier should be ${identifier}.`);
+
+            InspectorTest.log("Display Name:");
+            InspectorTest.log(recording.displayName);
+        }
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body _onload_="runTest()">
+<p>Tests for timeline recording.</p>
+</body>
+</html>

Modified: trunk/Source/WebInspectorUI/ChangeLog (243023 => 243024)


--- trunk/Source/WebInspectorUI/ChangeLog	2019-03-15 23:33:16 UTC (rev 243023)
+++ trunk/Source/WebInspectorUI/ChangeLog	2019-03-15 23:45:51 UTC (rev 243024)
@@ -1,5 +1,183 @@
 2019-03-15  Joseph Pecoraro  <pecor...@apple.com>
 
+        Web Inspector: Timelines - Import / Export Timeline Recordings
+        https://bugs.webkit.org/show_bug.cgi?id=195709
+        <rdar://problem/23188921>
+
+        Reviewed by Devin Rousso.
+
+        Timeline exporting saves TimelineRecording and TimelineOverview state.
+        The TimelineRecording includes all kinds of model objects, such as
+        records, markers, memory pressure events, etc. It also includes raw
+        protocol data, such as script profiler samples. TimelineOverview
+        includes some of the view state to restore, such as the selected
+        time range, zoom level, and selected timeline.
+
+        Timeline importing constructs a new TimelineRecording by replaying
+        the records, markers, and other events, as well as re-initializing
+        more state. To finally display the imported recording, the content
+        view will immediately initialize start/current/end times and the
+        overview will restore the view state.
+
+        * Localizations/en.lproj/localizedStrings.js:
+        New strings.
+
+        * UserInterface/Controllers/TimelineManager.js:
+        (WI.TimelineManager.synthesizeImportError):
+        (WI.TimelineManager.prototype.importRecording):
+        Import API.
+
+        (WI.TimelineManager.prototype.scriptProfilerTrackingCompleted):
+        Initialize the samples on the recording via a different path
+        so that the data can be saved for exporting.
+
+        * UserInterface/Models/TimelineRecording.js:
+        (WI.TimelineRecording):
+        (WI.TimelineRecording.import):
+        (WI.TimelineRecording.prototype.exportData):
+        (WI.TimelineRecording.prototype.get capturing):
+        (WI.TimelineRecording.prototype.get imported):
+        (WI.TimelineRecording.prototype.unloaded):
+        (WI.TimelineRecording.prototype.reset):
+        (WI.TimelineRecording.prototype.addEventMarker):
+        (WI.TimelineRecording.prototype.addRecord):
+        (WI.TimelineRecording.prototype.addMemoryPressureEvent):
+        (WI.TimelineRecording.prototype.initializeCallingContextTrees):
+        (WI.TimelineRecording.prototype.canExport):
+        Save data at the TimelineRecording level that can be used for export.
+        We only allow exporting a TimelineRecording that has started/stopped
+        at least once and is not currently capturing.
+
+        * UserInterface/Views/TimelineRecordingContentView.js:
+        (WI.TimelineRecordingContentView):
+        (WI.TimelineRecordingContentView.prototype.get navigationItems):
+        (WI.TimelineRecordingContentView.prototype.get supportsSave):
+        (WI.TimelineRecordingContentView.prototype.get saveData):
+        (WI.TimelineRecordingContentView.prototype.shown):
+        (WI.TimelineRecordingContentView.prototype._capturingStarted):
+        (WI.TimelineRecordingContentView.prototype._capturingStopped):
+        (WI.TimelineRecordingContentView.prototype._initializeImportedRecording):
+        (WI.TimelineRecordingContentView.prototype._exportTimelineRecording):
+        (WI.TimelineRecordingContentView.prototype._importButtonNavigationItemClicked):
+        (WI.TimelineRecordingContentView.prototype._recordingReset):
+        Add Import and Export buttons in the Timeline navigation bar.
+
+        * UserInterface/Views/TimelineOverview.js:
+        (WI.TimelineOverview):
+        (WI.TimelineOverview.prototype.exportData):
+        (WI.TimelineOverview.prototype._instrumentAdded):
+        (WI.TimelineOverview.prototype._recordingImported):
+        When importing a recording update the TimelineOverview state
+        soon afterwards.
+
+        * UserInterface/Models/CPUTimelineRecord.js:
+        (WI.CPUTimelineRecord.fromJSON):
+        (WI.CPUTimelineRecord.prototype.toJSON):
+        * UserInterface/Models/GarbageCollection.js:
+        (WI.GarbageCollection.fromJSON):
+        (WI.GarbageCollection.prototype.toJSON):
+        * UserInterface/Models/Geometry.js:
+        (WI.Quad.fromJSON):
+        (WI.Quad.prototype.toJSON):
+        * UserInterface/Models/HeapAllocationsTimelineRecord.js:
+        (WI.HeapAllocationsTimelineRecord.fromJSON):
+        (WI.HeapAllocationsTimelineRecord.prototype.toJSON):
+        * UserInterface/Models/LayoutTimelineRecord.js:
+        (WI.LayoutTimelineRecord.fromJSON):
+        (WI.LayoutTimelineRecord.prototype.toJSON):
+        * UserInterface/Models/MediaTimelineRecord.js:
+        (WI.MediaTimelineRecord.fromJSON):
+        (WI.MediaTimelineRecord.prototype.toJSON):
+        * UserInterface/Models/MemoryPressureEvent.js:
+        (WI.MemoryPressureEvent.fromJSON):
+        (WI.MemoryPressureEvent.prototype.toJSON):
+        * UserInterface/Models/MemoryTimelineRecord.js:
+        (WI.MemoryTimelineRecord):
+        (WI.MemoryTimelineRecord.fromJSON):
+        (WI.MemoryTimelineRecord.prototype.toJSON):
+        * UserInterface/Models/RenderingFrameTimelineRecord.js:
+        (WI.RenderingFrameTimelineRecord.fromJSON):
+        (WI.RenderingFrameTimelineRecord.prototype.toJSON):
+        * UserInterface/Models/ResourceTimelineRecord.js:
+        (WI.ResourceTimelineRecord.fromJSON):
+        (WI.ResourceTimelineRecord.prototype.toJSON):
+        * UserInterface/Models/ScriptTimelineRecord.js:
+        (WI.ScriptTimelineRecord.fromJSON):
+        (WI.ScriptTimelineRecord.prototype.toJSON):
+        * UserInterface/Models/TimelineMarker.js:
+        (WI.TimelineMarker.fromJSON):
+        (WI.TimelineMarker.prototype.toJSON):
+        (WI.TimelineMarker.prototype.get type):
+        (WI.TimelineMarker.prototype.get details):
+        (WI.TimelineMarker.prototype.set time):
+        (WI.TimelineMarker):
+        * UserInterface/Models/TimelineRecord.js:
+        (WI.TimelineRecord.fromJSON):
+        (WI.TimelineRecord.prototype.toJSON):
+        Import / Export toJSON / fromJSON implementations.
+        
+        * UserInterface/Views/CPUTimelineOverviewGraph.js:
+        (WI.CPUTimelineOverviewGraph):
+        (WI.CPUTimelineOverviewGraph.prototype._cpuTimelineRecordAdded):
+        (WI.CPUTimelineOverviewGraph.prototype._processRecord):
+        * UserInterface/Views/LayoutTimelineOverviewGraph.js:
+        (WI.LayoutTimelineOverviewGraph):
+        (WI.LayoutTimelineOverviewGraph.prototype._layoutTimelineRecordAdded):
+        (WI.LayoutTimelineOverviewGraph.prototype._processRecord):
+        * UserInterface/Views/LayoutTimelineView.js:
+        (WI.LayoutTimelineView):
+        (WI.LayoutTimelineView.prototype._layoutTimelineRecordAdded):
+        (WI.LayoutTimelineView.prototype._processRecord):
+        * UserInterface/Views/MediaTimelineView.js:
+        (WI.MediaTimelineView):
+        (WI.MediaTimelineView.prototype._handleRecordAdded):
+        (WI.MediaTimelineView.prototype._processRecord):
+        * UserInterface/Views/MemoryTimelineOverviewGraph.js:
+        (WI.MemoryTimelineOverviewGraph):
+        (WI.MemoryTimelineOverviewGraph.prototype._memoryTimelineRecordAdded):
+        (WI.MemoryTimelineOverviewGraph.prototype._processRecord):
+        * UserInterface/Views/MemoryTimelineView.js:
+        (WI.MemoryTimelineView):
+        (WI.MemoryTimelineView.prototype._memoryTimelineRecordAdded):
+        (WI.MemoryTimelineView.prototype._processRecord):
+        * UserInterface/Views/NetworkTimelineOverviewGraph.js:
+        (WI.NetworkTimelineOverviewGraph):
+        (WI.NetworkTimelineOverviewGraph.prototype.reset):
+        (WI.NetworkTimelineOverviewGraph.prototype._networkTimelineRecordAdded):
+        (WI.NetworkTimelineOverviewGraph.prototype._processRecord):
+        (WI.NetworkTimelineOverviewGraph.prototype._networkTimelineRecordAdded.compareByStartTime): Deleted.
+        * UserInterface/Views/NetworkTimelineView.js:
+        (WI.NetworkTimelineView):
+        (WI.NetworkTimelineView.prototype._networkTimelineRecordAdded):
+        (WI.NetworkTimelineView.prototype._processRecord):
+        * UserInterface/Views/RenderingFrameTimelineView.js:
+        (WI.RenderingFrameTimelineView):
+        (WI.RenderingFrameTimelineView.prototype._renderingFrameTimelineRecordAdded):
+        (WI.RenderingFrameTimelineView.prototype._processRecord):
+        * UserInterface/Views/ScriptDetailsTimelineView.js:
+        (WI.ScriptDetailsTimelineView):
+        (WI.ScriptDetailsTimelineView.prototype._scriptTimelineRecordAdded):
+        (WI.ScriptDetailsTimelineView.prototype._processRecord):
+        Add common _processRecord path to each timeline OverviewGraph and TimelineView.
+        By calling this in construction we populate graphs with TimelineRecords that
+        may have already existed. This is necessary for imports, but this also fixes
+        the case where you enable a timeline that had data and it didn't show data.
+
+        * UserInterface/Views/LayoutTimelineOverviewGraph.css:
+        (.timeline-overview-graph.layout-overview > .graph-row):
+        (.timeline-overview-graph.layout-overview > .graph-row > .timeline-record-bar):
+        (.timeline-overview-graph.layout-overview > .graph-row > .timeline-record-bar > .segment):
+        (.timeline-overview-graph.layout > .graph-row): Deleted.
+        (.timeline-overview-graph.layout > .graph-row > .timeline-record-bar): Deleted.
+        (.timeline-overview-graph.layout > .graph-row > .timeline-record-bar > .segment): Deleted.
+        * UserInterface/Views/TimelineRecordBar.css:
+        (.timeline-record-bar.timeline-record-type-layout.paint > .segment,):
+        (.timeline-record-bar.timeline-record-type-layout.layout-timeline-record-paint > .segment,): Deleted.
+        We simplified some of the sub-record type enum strings. To do this we needed to change
+        "layout" to "layout-overview" to avoid a conflict.
+
+2019-03-15  Joseph Pecoraro  <pecor...@apple.com>
+
         Web Inspector: Network - Toggle Between Live Activity and Imported HAR resource collections
         https://bugs.webkit.org/show_bug.cgi?id=195734
 

Modified: trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js (243023 => 243024)


--- trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js	2019-03-15 23:33:16 UTC (rev 243023)
+++ trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js	2019-03-15 23:45:51 UTC (rev 243024)
@@ -554,6 +554,7 @@
 localizedStrings["Imported"] = "Imported";
 localizedStrings["Imported - %s"] = "Imported - %s";
 localizedStrings["Imported Recordings"] = "Imported Recordings";
+localizedStrings["Imported Timeline Recording"] = "Imported Timeline Recording";
 localizedStrings["Imported \u2014 %s"] = "Imported \u2014 %s";
 localizedStrings["Incomplete"] = "Incomplete";
 localizedStrings["Indent width:"] = "Indent width:";
@@ -1055,6 +1056,7 @@
 localizedStrings["Time to First Byte"] = "Time to First Byte";
 localizedStrings["Timeline"] = "Timeline";
 localizedStrings["Timeline Recording %d"] = "Timeline Recording %d";
+localizedStrings["Timeline Recording Import Error: %s"] = "Timeline Recording Import Error: %s";
 localizedStrings["Timelines"] = "Timelines";
 localizedStrings["Timer %d Fired"] = "Timer %d Fired";
 localizedStrings["Timer %d Installed"] = "Timer %d Installed";
@@ -1189,4 +1191,5 @@
 localizedStrings["unknown %s \u0022%s\u0022"] = "unknown %s \u0022%s\u0022";
 localizedStrings["unsupported %s"] = "unsupported %s";
 localizedStrings["unsupported HAR version"] = "unsupported HAR version";
+localizedStrings["unsupported version"] = "unsupported version";
 localizedStrings["value"] = "value";

Modified: trunk/Source/WebInspectorUI/UserInterface/Base/Main.js (243023 => 243024)


--- trunk/Source/WebInspectorUI/UserInterface/Base/Main.js	2019-03-15 23:33:16 UTC (rev 243023)
+++ trunk/Source/WebInspectorUI/UserInterface/Base/Main.js	2019-03-15 23:45:51 UTC (rev 243024)
@@ -2736,7 +2736,7 @@
 
 WI.createMessageTextView = function(message, isError)
 {
-    var messageElement = document.createElement("div");
+    let messageElement = document.createElement("div");
     messageElement.className = "message-text-view";
     if (isError)
         messageElement.classList.add("error");

Modified: trunk/Source/WebInspectorUI/UserInterface/Controllers/TimelineManager.js (243023 => 243024)


--- trunk/Source/WebInspectorUI/UserInterface/Controllers/TimelineManager.js	2019-03-15 23:33:16 UTC (rev 243023)
+++ trunk/Source/WebInspectorUI/UserInterface/Controllers/TimelineManager.js	2019-03-15 23:45:51 UTC (rev 243024)
@@ -136,6 +136,21 @@
         return types;
     }
 
+    static synthesizeImportError(message)
+    {
+        message = WI.UIString("Timeline Recording Import Error: %s").format(message);
+
+        if (window.InspectorTest) {
+            console.error(message);
+            return;
+        }
+
+        let consoleMessage = new WI.ConsoleMessage(WI.mainTarget, WI.ConsoleMessage.MessageSource.Other, WI.ConsoleMessage.MessageLevel.Error, message);
+        consoleMessage.shouldRevealConsole = true;
+
+        WI.consoleLogViewController.appendConsoleMessage(consoleMessage);
+    }
+
     // Public
 
     reset()
@@ -259,6 +274,52 @@
         this._activeRecording = null;
     }
 
+    processJSON({filename, json, error})
+    {
+        if (error) {
+            WI.TimelineManager.synthesizeImportError(error);
+            return;
+        }
+
+        if (typeof json !== "object" || json === null) {
+            WI.TimelineManager.synthesizeImportError(WI.UIString("invalid JSON"));
+            return;
+        }
+
+        if (!json.recording  || typeof json.recording !== "object" || !json.overview || typeof json.overview !== "object" || typeof json.version !== "number") {
+            WI.TimelineManager.synthesizeImportError(WI.UIString("invalid JSON"));
+            return;
+        }
+
+        if (json.version !== WI.TimelineRecording.SerializationVersion) {
+            WI.NetworkManager.synthesizeImportError(WI.UIString("unsupported version"));
+            return;
+        }
+
+        let recordingData = json.recording;
+        let overviewData = json.overview;
+
+        let identifier = this._nextRecordingIdentifier++;
+        let newRecording = WI.TimelineRecording.import(identifier, recordingData, filename);
+        this._recordings.push(newRecording);
+
+        this.dispatchEventToListeners(WI.TimelineManager.Event.RecordingCreated, {recording: newRecording});
+
+        if (this._isCapturing)
+            this.stopCapturing();
+
+        let oldRecording = this._activeRecording;
+        if (oldRecording) {
+            const importing = true;
+            oldRecording.unloaded(importing);
+        }
+
+        this._activeRecording = newRecording;
+
+        this.dispatchEventToListeners(WI.TimelineManager.Event.RecordingLoaded, {oldRecording});
+        this.dispatchEventToListeners(WI.TimelineManager.Event.RecordingImported, {overviewData});
+    }
+
     computeElapsedTime(timestamp)
     {
         if (!this._activeRecording)
@@ -1026,9 +1087,6 @@
         if (samples) {
             let {stackTraces} = samples;
             let topDownCallingContextTree = this.activeRecording.topDownCallingContextTree;
-            let bottomUpCallingContextTree = this.activeRecording.bottomUpCallingContextTree;
-            let topFunctionsTopDownCallingContextTree = this.activeRecording.topFunctionsTopDownCallingContextTree;
-            let topFunctionsBottomUpCallingContextTree = this.activeRecording.topFunctionsBottomUpCallingContextTree;
 
             // Calculate a per-sample duration.
             let timestampIndex = 0;
@@ -1062,12 +1120,7 @@
             if (timestampIndex < timestampCount)
                 sampleDurations.fill(defaultDuration, sampleDurationIndex);
 
-            for (let i = 0; i < stackTraces.length; i++) {
-                topDownCallingContextTree.updateTreeWithStackTrace(stackTraces[i], sampleDurations[i]);
-                bottomUpCallingContextTree.updateTreeWithStackTrace(stackTraces[i], sampleDurations[i]);
-                topFunctionsTopDownCallingContextTree.updateTreeWithStackTrace(stackTraces[i], sampleDurations[i]);
-                topFunctionsBottomUpCallingContextTree.updateTreeWithStackTrace(stackTraces[i], sampleDurations[i]);
-            }
+            this.activeRecording.initializeCallingContextTrees(stackTraces, sampleDurations);
 
             // FIXME: This transformation should not be needed after introducing ProfileView.
             // Once we eliminate ProfileNodeTreeElements and ProfileNodeDataGridNodes.
@@ -1213,6 +1266,7 @@
 WI.TimelineManager.Event = {
     RecordingCreated: "timeline-manager-recording-created",
     RecordingLoaded: "timeline-manager-recording-loaded",
+    RecordingImported: "timeline-manager-recording-imported",
     CapturingWillStart: "timeline-manager-capturing-will-start",
     CapturingStarted: "timeline-manager-capturing-started",
     CapturingStopped: "timeline-manager-capturing-stopped"

Modified: trunk/Source/WebInspectorUI/UserInterface/Main.html (243023 => 243024)


--- trunk/Source/WebInspectorUI/UserInterface/Main.html	2019-03-15 23:33:16 UTC (rev 243023)
+++ trunk/Source/WebInspectorUI/UserInterface/Main.html	2019-03-15 23:45:51 UTC (rev 243024)
@@ -222,6 +222,7 @@
     <link rel="stylesheet" href=""
     <link rel="stylesheet" href=""
     <link rel="stylesheet" href=""
+    <link rel="stylesheet" href=""
     <link rel="stylesheet" href=""
     <link rel="stylesheet" href=""
     <link rel="stylesheet" href=""
@@ -826,6 +827,7 @@
     <script src=""
     <script src=""
     <script src=""
+    <script src=""
     <script src=""
     <script src=""
     <script src=""

Modified: trunk/Source/WebInspectorUI/UserInterface/Models/CPUTimelineRecord.js (243023 => 243024)


--- trunk/Source/WebInspectorUI/UserInterface/Models/CPUTimelineRecord.js	2019-03-15 23:33:16 UTC (rev 243023)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/CPUTimelineRecord.js	2019-03-15 23:45:51 UTC (rev 243024)
@@ -36,9 +36,8 @@
 
         this._timestamp = timestamp;
         this._usage = usage;
+        this._threads = threads || [];
 
-        threads = threads || [];
-
         this._mainThreadUsage = 0;
         this._webkitThreadUsage = 0;
         this._workerThreadUsage = 0;
@@ -45,7 +44,7 @@
         this._unknownThreadUsage = 0;
         this._workersData = null;
 
-        for (let thread of threads) {
+        for (let thread of this._threads) {
             if (thread.type === InspectorBackend.domains.CPUProfiler.ThreadInfoType.Main) {
                 console.assert(!this._mainThreadUsage, "There should only be one main thread.");
                 this._mainThreadUsage += thread.usage;
@@ -69,6 +68,23 @@
         }
     }
 
+    // Import / Export
+
+    static fromJSON(json)
+    {
+        return new WI.CPUTimelineRecord(json);
+    }
+
+    toJSON()
+    {
+        return {
+            type: this.type,
+            timestamp: this._timestamp,
+            usage: this._usage,
+            threads: this._threads,
+        };
+    }
+
     // Public
 
     get timestamp() { return this._timestamp; }

Modified: trunk/Source/WebInspectorUI/UserInterface/Models/GarbageCollection.js (243023 => 243024)


--- trunk/Source/WebInspectorUI/UserInterface/Models/GarbageCollection.js	2019-03-15 23:33:16 UTC (rev 243023)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/GarbageCollection.js	2019-03-15 23:45:51 UTC (rev 243024)
@@ -45,6 +45,24 @@
         return new WI.GarbageCollection(type, payload.startTime, payload.endTime);
     }
 
+    // Import / Export
+
+    static fromJSON(json)
+    {
+        let {type, startTime, endTime} = json;
+        return new WI.GarbageCollection(type, startTime, endTime);
+    }
+
+    toJSON()
+    {
+        return {
+            __type: "GarbageCollection",
+            type: this.type,
+            startTime: this.startTime,
+            endTime: this.endTime,
+        };
+    }
+
     // Public
 
     get type() { return this._type; }
@@ -58,6 +76,6 @@
 };
 
 WI.GarbageCollection.Type = {
-    Partial: Symbol("Partial"),
-    Full: Symbol("Full")
+    Partial: "partial",
+    Full: "full",
 };

Modified: trunk/Source/WebInspectorUI/UserInterface/Models/Geometry.js (243023 => 243024)


--- trunk/Source/WebInspectorUI/UserInterface/Models/Geometry.js	2019-03-15 23:33:16 UTC (rev 243023)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/Geometry.js	2019-03-15 23:45:51 UTC (rev 243024)
@@ -284,6 +284,18 @@
         this.height = Math.round(Math.sqrt(Math.pow(quad[0] - quad[6], 2) + Math.pow(quad[1] - quad[7], 2)));
     }
 
+    // Import / Export
+
+    static fromJSON(json)
+    {
+        return new WI.Quad(json);
+    }
+
+    toJSON()
+    {
+        return this.toProtocol();
+    }
+
     // Public
 
     toProtocol()

Modified: trunk/Source/WebInspectorUI/UserInterface/Models/HeapAllocationsTimelineRecord.js (243023 => 243024)


--- trunk/Source/WebInspectorUI/UserInterface/Models/HeapAllocationsTimelineRecord.js	2019-03-15 23:33:16 UTC (rev 243023)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/HeapAllocationsTimelineRecord.js	2019-03-15 23:45:51 UTC (rev 243024)
@@ -36,6 +36,36 @@
         this._heapSnapshot = heapSnapshot;
     }
 
+    // Import / Export
+
+    static fromJSON(json)
+    {
+        // NOTE: This just goes through and generates a new heap snapshot,
+        // it is not perfect but does what we want. It asynchronously creates
+        // a heap snapshot at the right time, and insert it into the active
+        // recording, which on an import should be the newly imported recording.
+        let {timestamp, title, snapshotStringData} = json;
+
+        let workerProxy = WI.HeapSnapshotWorkerProxy.singleton();
+        workerProxy.createImportedSnapshot(snapshotStringData, title, ({objectId, snapshot: serializedSnapshot}) => {
+            let snapshot = WI.HeapSnapshotProxy.deserialize(objectId, serializedSnapshot);
+            snapshot.snapshotStringData = snapshotStringData;
+            WI.timelineManager.heapSnapshotAdded(timestamp, snapshot);
+        });
+
+        return null;
+    }
+
+    toJSON()
+    {
+        return {
+            type: this.type,
+            timestamp: this._timestamp,
+            title: WI.TimelineTabContentView.displayNameForRecord(this),
+            snapshotStringData: this._heapSnapshot.snapshotStringData,
+        };
+    }
+
     // Public
 
     get timestamp() { return this._timestamp; }

Modified: trunk/Source/WebInspectorUI/UserInterface/Models/LayoutTimelineRecord.js (243023 => 243024)


--- trunk/Source/WebInspectorUI/UserInterface/Models/LayoutTimelineRecord.js	2019-03-15 23:33:16 UTC (rev 243023)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/LayoutTimelineRecord.js	2019-03-15 23:45:51 UTC (rev 243024)
@@ -61,6 +61,29 @@
         }
     }
 
+    // Import / Export
+
+    static fromJSON(json)
+    {
+        let {eventType, startTime, endTime, callFrames, sourceCodeLocation, quad} = json;
+        quad = quad ? WI.Quad.fromJSON(quad) : null;
+        return new WI.LayoutTimelineRecord(eventType, startTime, endTime, callFrames, sourceCodeLocation, quad);
+    }
+
+    toJSON()
+    {
+        // FIXME: CallFrames
+        // FIXME: SourceCodeLocation
+
+        return {
+            type: this.type,
+            eventType: this._eventType,
+            startTime: this.startTime,
+            endTime: this.endTime,
+            quad: this._quad || undefined,
+        }
+    }
+
     // Public
 
     get eventType()
@@ -97,13 +120,13 @@
 };
 
 WI.LayoutTimelineRecord.EventType = {
-    InvalidateStyles: "layout-timeline-record-invalidate-styles",
-    RecalculateStyles: "layout-timeline-record-recalculate-styles",
-    InvalidateLayout: "layout-timeline-record-invalidate-layout",
-    ForcedLayout: "layout-timeline-record-forced-layout",
-    Layout: "layout-timeline-record-layout",
-    Paint: "layout-timeline-record-paint",
-    Composite: "layout-timeline-record-composite"
+    InvalidateStyles: "invalidate-styles",
+    RecalculateStyles: "recalculate-styles",
+    InvalidateLayout: "invalidate-layout",
+    ForcedLayout: "forced-layout",
+    Layout: "layout",
+    Paint: "paint",
+    Composite: "composite"
 };
 
 WI.LayoutTimelineRecord.TypeIdentifier = "layout-timeline-record";

Modified: trunk/Source/WebInspectorUI/UserInterface/Models/MediaTimelineRecord.js (243023 => 243024)


--- trunk/Source/WebInspectorUI/UserInterface/Models/MediaTimelineRecord.js	2019-03-15 23:33:16 UTC (rev 243023)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/MediaTimelineRecord.js	2019-03-15 23:45:51 UTC (rev 243024)
@@ -37,6 +37,27 @@
         this._isLowPower = isLowPower || false;
     }
 
+    // Import / Export
+
+    static fromJSON(json)
+    {
+        let {eventType, timestamp} = json;
+        return new WI.MediaTimelineRecord(eventType, timestamp, json);
+    }
+
+    toJSON()
+    {
+        // FIXME: DOMNode
+
+        return {
+            type: this.type,
+            eventType: this._eventType,
+            timestamp: this.startTime,
+            domEvent: this._domEvent,
+            isLowPower: this._isLowPower,
+        };
+    }
+
     // Public
 
     get eventType() { return this._eventType; }

Modified: trunk/Source/WebInspectorUI/UserInterface/Models/MemoryPressureEvent.js (243023 => 243024)


--- trunk/Source/WebInspectorUI/UserInterface/Models/MemoryPressureEvent.js	2019-03-15 23:33:16 UTC (rev 243023)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/MemoryPressureEvent.js	2019-03-15 23:45:51 UTC (rev 243024)
@@ -52,6 +52,23 @@
         return new WI.MemoryPressureEvent(timestamp, severity);
     }
 
+    // Import / Export
+
+
+    static fromJSON(json)
+    {
+        let {timestamp, severity} = json;
+        return new WI.MemoryPressureEvent(timestamp, severity);
+    }
+
+    toJSON()
+    {
+        return {
+            timestamp: this._timestamp,
+            severity: this._severity,
+        };
+    }
+
     // Public
 
     get timestamp() { return this._timestamp; }
@@ -59,6 +76,6 @@
 };
 
 WI.MemoryPressureEvent.Severity = {
-    Critical: Symbol("Critical"),
-    NonCritical: Symbol("NonCritical"),
+    Critical: "critical",
+    NonCritical: "non-critical",
 };

Modified: trunk/Source/WebInspectorUI/UserInterface/Models/MemoryTimelineRecord.js (243023 => 243024)


--- trunk/Source/WebInspectorUI/UserInterface/Models/MemoryTimelineRecord.js	2019-03-15 23:33:16 UTC (rev 243023)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/MemoryTimelineRecord.js	2019-03-15 23:45:51 UTC (rev 243024)
@@ -34,6 +34,7 @@
 
         this._timestamp = timestamp;
         this._categories = WI.MemoryTimelineRecord.memoryCategoriesFromProtocol(categories);
+        this._exportCategories = categories;
 
         this._totalSize = 0;
         for (let {size} of categories)
@@ -79,6 +80,23 @@
         ];
     }
 
+    // Import / Export
+
+    static fromJSON(json)
+    {
+        let {timestamp, categories} = json;
+        return new WI.MemoryTimelineRecord(timestamp, categories);
+    }
+
+    toJSON()
+    {
+        return {
+            type: this.type,
+            timestamp: this.startTime,
+            categories: this._exportCategories,
+        };
+    }
+
     // Public
 
     get timestamp() { return this._timestamp; }

Modified: trunk/Source/WebInspectorUI/UserInterface/Models/RenderingFrameTimelineRecord.js (243023 => 243024)


--- trunk/Source/WebInspectorUI/UserInterface/Models/RenderingFrameTimelineRecord.js	2019-03-15 23:33:16 UTC (rev 243023)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/RenderingFrameTimelineRecord.js	2019-03-15 23:45:51 UTC (rev 243024)
@@ -69,6 +69,27 @@
         }
     }
 
+    // Import / Export
+
+    static fromJSON(json)
+    {
+        let {startTime, endTime} = json;
+        let record = new WI.RenderingFrameTimelineRecord(startTime, endTime);
+        record.setupFrameIndex();
+        return record;
+    }
+
+    toJSON()
+    {
+        // FIXME: durationByTaskType data cannot be calculated if this does not have children.
+
+        return {
+            type: this.type,
+            startTime: this.startTime,
+            endTime: this.endTime,
+        };
+    }
+
     // Public
 
     get frameIndex()

Modified: trunk/Source/WebInspectorUI/UserInterface/Models/Resource.js (243023 => 243024)


--- trunk/Source/WebInspectorUI/UserInterface/Models/Resource.js	2019-03-15 23:33:16 UTC (rev 243023)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/Resource.js	2019-03-15 23:45:51 UTC (rev 243024)
@@ -66,6 +66,7 @@
         this._statusText = null;
         this._cached = false;
         this._canceled = false;
+        this._finished = false;
         this._failed = false;
         this._failureReasonText = null;
         this._receivedNetworkLoadMetrics = false;

Modified: trunk/Source/WebInspectorUI/UserInterface/Models/ResourceTimelineRecord.js (243023 => 243024)


--- trunk/Source/WebInspectorUI/UserInterface/Models/ResourceTimelineRecord.js	2019-03-15 23:33:16 UTC (rev 243023)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/ResourceTimelineRecord.js	2019-03-15 23:45:51 UTC (rev 243024)
@@ -33,6 +33,26 @@
         this._resource.addEventListener(WI.Resource.Event.TimestampsDidChange, this._dispatchUpdatedEvent, this);
     }
 
+    // Import / Export
+
+    static fromJSON(json)
+    {
+        let {entry, archiveStartTime} = json;
+        let localResource = WI.LocalResource.fromHAREntry(entry, archiveStartTime);
+        return new WI.ResourceTimelineRecord(localResource);
+    }
+
+    toJSON()
+    {
+        const content = "";
+
+        return {
+            type: this.type,
+            archiveStartTime: this._resource.requestSentWalltime - this.startTime,
+            entry: WI.HARBuilder.entry(this._resource, content),
+        };
+    }
+
     // Public
 
     get resource()

Modified: trunk/Source/WebInspectorUI/UserInterface/Models/ScriptTimelineRecord.js (243023 => 243024)


--- trunk/Source/WebInspectorUI/UserInterface/Models/ScriptTimelineRecord.js	2019-03-15 23:33:16 UTC (rev 243023)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/ScriptTimelineRecord.js	2019-03-15 23:45:51 UTC (rev 243024)
@@ -48,6 +48,32 @@
         }
     }
 
+    // Import / Export
+
+    static fromJSON(json)
+    {
+        let {eventType, startTime, endTime, callFrames, sourceCodeLocation, details, profilePayload} = json;
+
+        if (typeof details === "object" && details.__type === "GarbageCollection")
+            details = WI.GarbageCollection.fromJSON(details);
+
+        return new WI.ScriptTimelineRecord(eventType, startTime, endTime, callFrames, sourceCodeLocation, details, profilePayload);
+    }
+
+    toJSON()
+    {
+        // FIXME: CallFrames
+        // FIXME: SourceCodeLocation
+
+        return {
+            type: this.type,
+            eventType: this._eventType,
+            startTime: this.startTime,
+            endTime: this.endTime,
+            details: this._details,
+        };
+    }
+
     // Public
 
     get eventType()
@@ -183,20 +209,20 @@
 };
 
 WI.ScriptTimelineRecord.EventType = {
-    ScriptEvaluated: "script-timeline-record-script-evaluated",
-    APIScriptEvaluated: "script-timeline-record-api-script-evaluated",
-    MicrotaskDispatched: "script-timeline-record-microtask-dispatched",
-    EventDispatched: "script-timeline-record-event-dispatched",
-    ProbeSampleRecorded: "script-timeline-record-probe-sample-recorded",
-    TimerFired: "script-timeline-record-timer-fired",
-    TimerInstalled: "script-timeline-record-timer-installed",
-    TimerRemoved: "script-timeline-record-timer-removed",
-    AnimationFrameFired: "script-timeline-record-animation-frame-fired",
-    AnimationFrameRequested: "script-timeline-record-animation-frame-requested",
-    AnimationFrameCanceled: "script-timeline-record-animation-frame-canceled",
-    ObserverCallback: "script-timeline-record-observer-callback",
-    ConsoleProfileRecorded: "script-timeline-record-console-profile-recorded",
-    GarbageCollected: "script-timeline-record-garbage-collected",
+    ScriptEvaluated: "script-evaluated",
+    APIScriptEvaluated: "api-script-evaluated",
+    MicrotaskDispatched: "microtask-dispatched",
+    EventDispatched: "event-dispatched",
+    ProbeSampleRecorded: "probe-sample-recorded",
+    TimerFired: "timer-fired",
+    TimerInstalled: "timer-installed",
+    TimerRemoved: "timer-removed",
+    AnimationFrameFired: "animation-frame-fired",
+    AnimationFrameRequested: "animation-frame-requested",
+    AnimationFrameCanceled: "animation-frame-canceled",
+    ObserverCallback: "observer-callback",
+    ConsoleProfileRecorded: "console-profile-recorded",
+    GarbageCollected: "garbage-collected",
 };
 
 WI.ScriptTimelineRecord.EventType.displayName = function(eventType, details, includeDetailsInMainTitle)

Modified: trunk/Source/WebInspectorUI/UserInterface/Models/SourceMapResource.js (243023 => 243024)


--- trunk/Source/WebInspectorUI/UserInterface/Models/SourceMapResource.js	2019-03-15 23:33:16 UTC (rev 243023)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/SourceMapResource.js	2019-03-15 23:45:51 UTC (rev 243024)
@@ -74,7 +74,7 @@
         return resourceURLComponents.path.substring(sourceMappingBasePathURLComponents.path.length, resourceURLComponents.length);
     }
 
-    requestContentFromBackend(callback)
+    requestContentFromBackend()
     {
         // Revert the markAsFinished that was done in the constructor.
         this.revertMarkAsFinished();

Modified: trunk/Source/WebInspectorUI/UserInterface/Models/TimelineMarker.js (243023 => 243024)


--- trunk/Source/WebInspectorUI/UserInterface/Models/TimelineMarker.js	2019-03-15 23:33:16 UTC (rev 243023)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/TimelineMarker.js	2019-03-15 23:45:51 UTC (rev 243024)
@@ -36,8 +36,28 @@
         this._details = details || null;
     }
 
+    // Import / Export
+
+    static fromJSON(json)
+    {
+        let {time, type, details} = json;
+        return new WI.TimelineMarker(time, type, details);
+    }
+
+    toJSON()
+    {
+        return {
+            time: this._time,
+            type: this._type,
+            details: this._details || undefined,
+        };
+    }
+
     // Public
 
+    get type() { return this._type; }
+    get details() { return this._details; }
+
     get time()
     {
         return this._time;
@@ -56,16 +76,6 @@
 
         this.dispatchEventToListeners(WI.TimelineMarker.Event.TimeChanged);
     }
-
-    get type()
-    {
-        return this._type;
-    }
-
-    get details()
-    {
-        return this._details;
-    }
 };
 
 WI.TimelineMarker.Event = {

Modified: trunk/Source/WebInspectorUI/UserInterface/Models/TimelineRecord.js (243023 => 243024)


--- trunk/Source/WebInspectorUI/UserInterface/Models/TimelineRecord.js	2019-03-15 23:33:16 UTC (rev 243023)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/TimelineRecord.js	2019-03-15 23:45:51 UTC (rev 243024)
@@ -42,6 +42,38 @@
         this._children = [];
     }
 
+    // Import / Export
+
+    static fromJSON(json)
+    {
+        switch (json.type) {
+        case WI.TimelineRecord.Type.Network:
+            return WI.ResourceTimelineRecord.fromJSON(json);
+        case WI.TimelineRecord.Type.Layout:
+            return WI.LayoutTimelineRecord.fromJSON(json);
+        case WI.TimelineRecord.Type.Script:
+            return WI.ScriptTimelineRecord.fromJSON(json);
+        case WI.TimelineRecord.Type.RenderingFrame:
+            return WI.RenderingFrameTimelineRecord.fromJSON(json);
+        case WI.TimelineRecord.Type.CPU:
+            return WI.CPUTimelineRecord.fromJSON(json);
+        case WI.TimelineRecord.Type.Memory:
+            return WI.MemoryTimelineRecord.fromJSON(json);
+        case WI.TimelineRecord.Type.HeapAllocations:
+            return WI.HeapAllocationsTimelineRecord.fromJSON(json);
+        case WI.TimelineRecord.Type.Media:
+            return WI.MediaTimelineRecord.fromJSON(json);
+        default:
+            console.error("Unknown TimelineRecord.Type: " + json.type, json);
+            return null;
+        }
+    }
+
+    toJSON()
+    {
+        throw WI.NotImplementedError.subclassMustOverride();
+    }
+
     // Public
 
     get type()

Modified: trunk/Source/WebInspectorUI/UserInterface/Models/TimelineRecording.js (243023 => 243024)


--- trunk/Source/WebInspectorUI/UserInterface/Models/TimelineRecording.js	2019-03-15 23:33:16 UTC (rev 243023)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/TimelineRecording.js	2019-03-15 23:45:51 UTC (rev 243024)
@@ -34,7 +34,19 @@
         this._displayName = displayName;
         this._capturing = false;
         this._readonly = false;
+        this._imported = false;
         this._instruments = instruments || [];
+
+        this._startTime = NaN;
+        this._endTime = NaN;
+        this._discontinuities = null;
+
+        this._exportDataRecords = null;
+        this._exportDataMarkers = null;
+        this._exportDataMemoryPressureEvents = null;
+        this._exportDataSampleStackTraces = null;
+        this._exportDataSampleDurations = null;
+
         this._topDownCallingContextTree = new WI.CallingContextTree(WI.CallingContextTree.Type.TopDown);
         this._bottomUpCallingContextTree = new WI.CallingContextTree(WI.CallingContextTree.Type.BottomUp);
         this._topFunctionsTopDownCallingContextTree = new WI.CallingContextTree(WI.CallingContextTree.Type.TopFunctionsTopDown);
@@ -60,6 +72,76 @@
         return WI.sharedApp.debuggableType === WI.DebuggableType.Web;
     }
 
+    // Import / Export
+
+    static import(identifier, json, displayName)
+    {
+        let {startTime, endTime, discontinuities, instrumentTypes, records, markers, memoryPressureEvents, sampleStackTraces, sampleDurations} = json;
+        let importedDisplayName = WI.UIString("Imported - %s").format(displayName);
+        let instruments = instrumentTypes.map((type) => WI.Instrument.createForTimelineType(type));
+        let recording = new WI.TimelineRecording(identifier, importedDisplayName, instruments);
+
+        recording._readonly = true;
+        recording._imported = true;
+        recording._startTime = startTime;
+        recording._endTime = endTime;
+        recording._discontinuities = discontinuities;
+
+        recording.initializeCallingContextTrees(sampleStackTraces, sampleDurations);
+
+        for (let recordJSON of records) {
+            let record = WI.TimelineRecord.fromJSON(recordJSON);
+            if (record) {
+                recording.addRecord(record);
+
+                if (record instanceof WI.ScriptTimelineRecord)
+                    record.profilePayload = recording._topDownCallingContextTree.toCPUProfilePayload(record.startTime, record.endTime);
+            }
+        }
+
+        for (let memoryPressureJSON of memoryPressureEvents) {
+            let memoryPressureEvent = WI.MemoryPressureEvent.fromJSON(memoryPressureJSON);
+            if (memoryPressureEvent)
+                recording.addMemoryPressureEvent(memoryPressureEvent);
+        }
+
+        // Add markers once we've transitioned the active recording.
+        setTimeout(() => {
+            recording.__importing = true;
+
+            for (let markerJSON of markers) {
+                let marker = WI.TimelineMarker.fromJSON(markerJSON);
+                if (marker)
+                    recording.addEventMarker(marker);
+            }
+
+            recording.__importing = false;
+        });
+
+        return recording;
+    }
+
+    exportData()
+    {
+        console.assert(this.canExport(), "Attempted to export a recording which should not be exportable.");
+
+        // FIXME: Overview data (sourceCodeTimelinesMap).
+        // FIXME: Record hierarchy (parent / child relationship) is lost.
+
+        return {
+            displayName: this._displayName,
+            startTime: this._startTime,
+            endTime: this._endTime,
+            discontinuities: this._discontinuities,
+            instrumentTypes: this._instruments.map((instrument) => instrument.timelineRecordType),
+            records: this._exportDataRecords,
+            markers: this._exportDataMarkers,
+            memoryPressureEvents: this._exportDataMemoryPressureEvents,
+            sampleStackTraces: this._exportDataSampleStackTraces,
+            sampleDurations: this._exportDataSampleDurations,
+        };
+    }
+
     // Public
 
     get displayName() { return this._displayName; }
@@ -66,7 +148,9 @@
     get identifier() { return this._identifier; }
     get timelines() { return this._timelines; }
     get instruments() { return this._instruments; }
+    get capturing() { return this._capturing; }
     get readonly() { return this._readonly; }
+    get imported() { return this._imported; }
     get startTime() { return this._startTime; }
     get endTime() { return this._endTime; }
 
@@ -113,9 +197,9 @@
         return true;
     }
 
-    unloaded()
+    unloaded(importing)
     {
-        console.assert(!this.isEmpty(), "Shouldn't unload an empty recording; it should be reused instead.");
+        console.assert(importing || !this.isEmpty(), "Shouldn't unload an empty recording; it should be reused instead.");
 
         this._readonly = true;
 
@@ -127,10 +211,17 @@
         console.assert(!this._readonly, "Can't reset a read-only recording.");
 
         this._sourceCodeTimelinesMap = new Map;
+
         this._startTime = NaN;
         this._endTime = NaN;
         this._discontinuities = [];
 
+        this._exportDataRecords = [];
+        this._exportDataMarkers = []
+        this._exportDataMemoryPressureEvents = [];
+        this._exportDataSampleStackTraces = [];
+        this._exportDataSampleDurations = [];
+
         this._topDownCallingContextTree.reset();
         this._bottomUpCallingContextTree.reset();
         this._topFunctionsTopDownCallingContextTree.reset();
@@ -192,7 +283,9 @@
 
     addEventMarker(marker)
     {
-        if (!this._capturing)
+        this._exportDataMarkers.push(marker);
+
+        if (!this._capturing && !this.__importing)
             return;
 
         this.dispatchEventToListeners(WI.TimelineRecording.Event.MarkerAdded, {marker});
@@ -200,7 +293,9 @@
 
     addRecord(record)
     {
-        var timeline = this._timelines.get(record.type);
+        this._exportDataRecords.push(record);
+
+        let timeline = this._timelines.get(record.type);
         console.assert(timeline, record, this._timelines);
         if (!timeline)
             return;
@@ -247,6 +342,8 @@
 
     addMemoryPressureEvent(memoryPressureEvent)
     {
+        this._exportDataMemoryPressureEvents.push(memoryPressureEvent);
+
         let memoryTimeline = this._timelines.get(WI.TimelineRecord.Type.Memory);
         console.assert(memoryTimeline, this._timelines);
         if (!memoryTimeline)
@@ -326,6 +423,30 @@
         }
     }
 
+    initializeCallingContextTrees(stackTraces, sampleDurations)
+    {
+        this._exportDataSampleStackTraces.concat(stackTraces);
+        this._exportDataSampleDurations.concat(sampleDurations);
+
+        for (let i = 0; i < stackTraces.length; i++) {
+            this._topDownCallingContextTree.updateTreeWithStackTrace(stackTraces[i], sampleDurations[i]);
+            this._bottomUpCallingContextTree.updateTreeWithStackTrace(stackTraces[i], sampleDurations[i]);
+            this._topFunctionsTopDownCallingContextTree.updateTreeWithStackTrace(stackTraces[i], sampleDurations[i]);
+            this._topFunctionsBottomUpCallingContextTree.updateTreeWithStackTrace(stackTraces[i], sampleDurations[i]);
+        }
+    }
+
+    canExport()
+    {
+        if (this._capturing)
+            return false;
+
+        if (isNaN(this._startTime))
+            return false;
+
+        return true;
+    }
+
     // Private
 
     _keyForRecord(record)
@@ -373,3 +494,5 @@
 WI.TimelineRecording.isLegacy = undefined;
 WI.TimelineRecording.TimestampThresholdForLegacyRecordConversion = 10000000; // Some value not near zero.
 WI.TimelineRecording.TimestampThresholdForLegacyAssumedMilliseconds = 1420099200000; // Date.parse("Jan 1, 2015"). Milliseconds since epoch.
+
+WI.TimelineRecording.SerializationVersion = 1;

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/CPUTimelineOverviewGraph.js (243023 => 243024)


--- trunk/Source/WebInspectorUI/UserInterface/Views/CPUTimelineOverviewGraph.js	2019-03-15 23:33:16 UTC (rev 243023)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/CPUTimelineOverviewGraph.js	2019-03-15 23:45:51 UTC (rev 243024)
@@ -51,6 +51,9 @@
         this._lastSelectedRecordInLayout = null;
 
         this.reset();
+
+        for (let record of this._cpuTimeline.records)
+            this._processRecord(record);
     }
 
     // Static
@@ -212,8 +215,13 @@
     {
         let cpuTimelineRecord = event.data.record;
 
-        this._maxUsage = Math.max(this._maxUsage, cpuTimelineRecord.usage);
+        this._processRecord(cpuTimelineRecord);
 
         this.needsLayout();
     }
+
+    _processRecord(cpuTimelineRecord)
+    {
+        this._maxUsage = Math.max(this._maxUsage, cpuTimelineRecord.usage);
+    }
 };

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/HeapAllocationsTimelineView.js (243023 => 243024)


--- trunk/Source/WebInspectorUI/UserInterface/Views/HeapAllocationsTimelineView.js	2019-03-15 23:33:16 UTC (rev 243023)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/HeapAllocationsTimelineView.js	2019-03-15 23:45:51 UTC (rev 243024)
@@ -219,22 +219,6 @@
         return components.concat(this._contentViewContainer.currentContentView.selectionPathComponents);
     }
 
-    get supportsSave()
-    {
-        if (this._showingSnapshotList)
-            return false;
-
-        if (!this._contentViewContainer.currentContentView)
-            return false;
-
-        return this._contentViewContainer.currentContentView.supportsSave;
-    }
-
-    get saveData()
-    {
-        return this._contentViewContainer.currentContentView.saveData;
-    }
-
     selectRecord(record)
     {
         if (record)

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/HeapSnapshotContentView.js (243023 => 243024)


--- trunk/Source/WebInspectorUI/UserInterface/Views/HeapSnapshotContentView.js	2019-03-15 23:33:16 UTC (rev 243023)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/HeapSnapshotContentView.js	2019-03-15 23:45:51 UTC (rev 243024)
@@ -65,16 +65,6 @@
         return [];
     }
 
-    get supportsSave()
-    {
-        return this.representedObject instanceof WI.HeapSnapshotProxy;
-    }
-
-    get saveData()
-    {
-        return {customSaveHandler: () => { this._exportSnapshot(); }};
-    }
-
     shown()
     {
         super.shown();

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/LayoutTimelineOverviewGraph.css (243023 => 243024)


--- trunk/Source/WebInspectorUI/UserInterface/Views/LayoutTimelineOverviewGraph.css	2019-03-15 23:33:16 UTC (rev 243023)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/LayoutTimelineOverviewGraph.css	2019-03-15 23:45:51 UTC (rev 243024)
@@ -23,15 +23,15 @@
  * THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-.timeline-overview-graph.layout > .graph-row {
+.timeline-overview-graph.layout-overview > .graph-row {
     height: 16px;
 }
 
-.timeline-overview-graph.layout > .graph-row > .timeline-record-bar {
+.timeline-overview-graph.layout-overview > .graph-row > .timeline-record-bar {
     height: 12px;
     margin-top: 4px;
 }
 
-.timeline-overview-graph.layout > .graph-row > .timeline-record-bar > .segment {
+.timeline-overview-graph.layout-overview > .graph-row > .timeline-record-bar > .segment {
     border-radius: 2px;
 }

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/LayoutTimelineOverviewGraph.js (243023 => 243024)


--- trunk/Source/WebInspectorUI/UserInterface/Views/LayoutTimelineOverviewGraph.js	2019-03-15 23:33:16 UTC (rev 243023)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/LayoutTimelineOverviewGraph.js	2019-03-15 23:45:51 UTC (rev 243024)
@@ -29,12 +29,15 @@
     {
         super(timelineOverview);
 
-        this.element.classList.add("layout");
+        this.element.classList.add("layout-overview");
 
         this._layoutTimeline = timeline;
         this._layoutTimeline.addEventListener(WI.Timeline.Event.RecordAdded, this._layoutTimelineRecordAdded, this);
 
         this.reset();
+
+        for (let record of this._layoutTimeline.records)
+            this._processRecord(record);
     }
 
     // Public
@@ -118,14 +121,19 @@
 
     _layoutTimelineRecordAdded(event)
     {
-        var layoutTimelineRecord = event.data.record;
+        let layoutTimelineRecord = event.data.record;
         console.assert(layoutTimelineRecord instanceof WI.LayoutTimelineRecord);
 
+        this._processRecord(layoutTimelineRecord);
+
+        this.needsLayout();
+    }
+
+    _processRecord(layoutTimelineRecord)
+    {
         if (layoutTimelineRecord.eventType === WI.LayoutTimelineRecord.EventType.Paint || layoutTimelineRecord.eventType === WI.LayoutTimelineRecord.EventType.Composite)
             this._timelinePaintRecordRow.records.push(layoutTimelineRecord);
         else
             this._timelineLayoutRecordRow.records.push(layoutTimelineRecord);
-
-        this.needsLayout();
     }
 };

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/LayoutTimelineView.js (243023 => 243024)


--- trunk/Source/WebInspectorUI/UserInterface/Views/LayoutTimelineView.js	2019-03-15 23:33:16 UTC (rev 243023)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/LayoutTimelineView.js	2019-03-15 23:45:51 UTC (rev 243024)
@@ -98,6 +98,9 @@
         timeline.addEventListener(WI.Timeline.Event.RecordAdded, this._layoutTimelineRecordAdded, this);
 
         this._pendingRecords = [];
+
+        for (let record of timeline.records)
+            this._processRecord(record);
     }
 
     // Public
@@ -231,16 +234,21 @@
 
     _layoutTimelineRecordAdded(event)
     {
-        var layoutTimelineRecord = event.data.record;
+        let layoutTimelineRecord = event.data.record;
         console.assert(layoutTimelineRecord instanceof WI.LayoutTimelineRecord);
 
+        this._processRecord(layoutTimelineRecord);
+
+        this.needsLayout();
+    }
+
+    _processRecord(layoutTimelineRecord)
+    {
         // Only add top-level records, to avoid processing child records multiple times.
         if (layoutTimelineRecord.parent instanceof WI.LayoutTimelineRecord)
             return;
 
         this._pendingRecords.push(layoutTimelineRecord);
-
-        this.needsLayout();
     }
 
     _updateHighlight()

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/MediaTimelineView.js (243023 => 243024)


--- trunk/Source/WebInspectorUI/UserInterface/Views/MediaTimelineView.js	2019-03-15 23:33:16 UTC (rev 243023)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/MediaTimelineView.js	2019-03-15 23:45:51 UTC (rev 243024)
@@ -79,6 +79,9 @@
         timeline.addEventListener(WI.Timeline.Event.RecordAdded, this._handleRecordAdded, this);
 
         this._pendingRecords = [];
+
+        for (let record of timeline.records)
+            this._processRecord(record);
     }
 
     // Public
@@ -191,14 +194,19 @@
 
     _handleRecordAdded(event)
     {
-        let record = event.data.record;
-        console.assert(record instanceof WI.MediaTimelineRecord);
+        let mediaTimelineRecord = event.data.record;
+        console.assert(mediaTimelineRecord instanceof WI.MediaTimelineRecord);
 
-        this._pendingRecords.push(record);
+        this._processRecord(mediaTimelineRecord);
 
         this.needsLayout();
     }
 
+    _processRecord(mediaTimelineRecord)
+    {
+        this._pendingRecords.push(mediaTimelineRecord);
+    }
+
     _handleSelectionPathComponentSiblingSelected(event)
     {
         let pathComponent = event.data.pathComponent;

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/MemoryTimelineOverviewGraph.js (243023 => 243024)


--- trunk/Source/WebInspectorUI/UserInterface/Views/MemoryTimelineOverviewGraph.js	2019-03-15 23:33:16 UTC (rev 243023)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/MemoryTimelineOverviewGraph.js	2019-03-15 23:45:51 UTC (rev 243024)
@@ -52,6 +52,9 @@
         this._memoryPressureMarkerElements = [];
 
         this.reset();
+
+        for (let record of this._memoryTimeline.records)
+            this._processRecord(record);
     }
 
     // Protected
@@ -244,7 +247,15 @@
     _memoryTimelineRecordAdded(event)
     {
         let memoryTimelineRecord = event.data.record;
+        console.assert(memoryTimelineRecord instanceof WI.MemoryTimelineRecord);
 
+        this._processRecord(memoryTimelineRecord);
+
+        this.needsLayout();
+    }
+
+    _processRecord(memoryTimelineRecord)
+    {
         this._maxSize = Math.max(this._maxSize, memoryTimelineRecord.totalSize);
 
         if (!this._didInitializeCategories) {
@@ -254,8 +265,6 @@
                 types.push(category.type);
             this._chart.initializeSections(types);
         }
-
-        this.needsLayout();
     }
 
     _memoryTimelineMemoryPressureEventAdded(event)

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/MemoryTimelineView.js (243023 => 243024)


--- trunk/Source/WebInspectorUI/UserInterface/Views/MemoryTimelineView.js	2019-03-15 23:33:16 UTC (rev 243023)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/MemoryTimelineView.js	2019-03-15 23:45:51 UTC (rev 243024)
@@ -96,6 +96,9 @@
         timeline.addEventListener(WI.Timeline.Event.RecordAdded, this._memoryTimelineRecordAdded, this);
 
         this.element.addEventListener("mousemove", this._handleGraphMouseMove.bind(this));
+
+        for (let record of timeline.records)
+            this._processRecord(record);
     }
 
     // Static
@@ -452,12 +455,17 @@
         let memoryTimelineRecord = event.data.record;
         console.assert(memoryTimelineRecord instanceof WI.MemoryTimelineRecord);
 
+        this._processRecord(memoryTimelineRecord);
+
+        if (memoryTimelineRecord.startTime >= this.startTime && memoryTimelineRecord.endTime <= this.endTime)
+            this.needsLayout();
+    }
+
+    _processRecord(memoryTimelineRecord)
+    {
         if (!this._didInitializeCategories)
             this._initializeCategoryViews(memoryTimelineRecord);
 
         this._maxSize = Math.max(this._maxSize, memoryTimelineRecord.totalSize);
-
-        if (memoryTimelineRecord.startTime >= this.startTime && memoryTimelineRecord.endTime <= this.endTime)
-            this.needsLayout();
     }
 };

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/NetworkTimelineOverviewGraph.js (243023 => 243024)


--- trunk/Source/WebInspectorUI/UserInterface/Views/NetworkTimelineOverviewGraph.js	2019-03-15 23:33:16 UTC (rev 243023)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/NetworkTimelineOverviewGraph.js	2019-03-15 23:45:51 UTC (rev 243024)
@@ -36,6 +36,9 @@
         timeline.addEventListener(WI.Timeline.Event.TimesUpdated, this.needsLayout, this);
 
         this.reset();
+
+        for (let record of timeline.records)
+            this._processRecord(record);
     }
 
     // Public
@@ -47,12 +50,12 @@
         this._nextDumpRow = 0;
         this._timelineRecordGridRows = [];
 
-        for (var i = 0; i < WI.NetworkTimelineOverviewGraph.MaximumRowCount; ++i)
+        for (let i = 0; i < WI.NetworkTimelineOverviewGraph.MaximumRowCount; ++i)
             this._timelineRecordGridRows.push([]);
 
         this.element.removeChildren();
 
-        for (var rowRecords of this._timelineRecordGridRows) {
+        for (let rowRecords of this._timelineRecordGridRows) {
             rowRecords.__element = document.createElement("div");
             rowRecords.__element.classList.add("graph-row");
             this.element.appendChild(rowRecords.__element);
@@ -106,21 +109,24 @@
 
     _networkTimelineRecordAdded(event)
     {
-        var resourceTimelineRecord = event.data.record;
+        let resourceTimelineRecord = event.data.record;
         console.assert(resourceTimelineRecord instanceof WI.ResourceTimelineRecord);
 
-        function compareByStartTime(a, b)
-        {
-            return a.startTime - b.startTime;
-        }
+        this._processRecord(resourceTimelineRecord);
 
+        this.needsLayout();
+    }
+
+    _processRecord(resourceTimelineRecord)
+    {
+        let compareByStartTime = (a, b) => a.startTime - b.startTime;
         let minimumBarPaddingTime = WI.TimelineOverview.MinimumDurationPerPixel * (WI.TimelineRecordBar.MinimumWidthPixels + WI.TimelineRecordBar.MinimumMarginPixels);
 
         // Try to find a row that has room and does not overlap a previous record.
-        var foundRowForRecord = false;
-        for (var i = 0; i < this._timelineRecordGridRows.length; ++i) {
-            var rowRecords = this._timelineRecordGridRows[i];
-            var lastRecord = rowRecords.lastValue;
+        let foundRowForRecord = false;
+        for (let i = 0; i < this._timelineRecordGridRows.length; ++i) {
+            let rowRecords = this._timelineRecordGridRows[i];
+            let lastRecord = rowRecords.lastValue;
 
             if (!lastRecord || lastRecord.endTime + minimumBarPaddingTime <= resourceTimelineRecord.startTime) {
                 insertObjectIntoSortedArray(resourceTimelineRecord, rowRecords, compareByStartTime);
@@ -132,9 +138,9 @@
 
         if (!foundRowForRecord) {
             // Try to find a row that does not overlap a previous record's active time, but it can overlap the inactive time.
-            for (var i = 0; i < this._timelineRecordGridRows.length; ++i) {
-                var rowRecords = this._timelineRecordGridRows[i];
-                var lastRecord = rowRecords.lastValue;
+            for (let i = 0; i < this._timelineRecordGridRows.length; ++i) {
+                let rowRecords = this._timelineRecordGridRows[i];
+                let lastRecord = rowRecords.lastValue;
                 console.assert(lastRecord);
 
                 if (lastRecord.activeStartTime + minimumBarPaddingTime <= resourceTimelineRecord.startTime) {
@@ -152,8 +158,6 @@
                 this._nextDumpRow = 0;
             insertObjectIntoSortedArray(resourceTimelineRecord, this._timelineRecordGridRows[this._nextDumpRow++], compareByStartTime);
         }
-
-        this.needsLayout();
     }
 };
 

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/NetworkTimelineView.js (243023 => 243024)


--- trunk/Source/WebInspectorUI/UserInterface/Views/NetworkTimelineView.js	2019-03-15 23:33:16 UTC (rev 243023)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/NetworkTimelineView.js	2019-03-15 23:45:51 UTC (rev 243024)
@@ -136,6 +136,9 @@
 
         this._pendingRecords = [];
         this._resourceDataGridNodeMap = new Map;
+
+        for (let record of timeline.records)
+            this._processRecord(record);
     }
 
     // Public
@@ -267,11 +270,16 @@
 
     _networkTimelineRecordAdded(event)
     {
-        var resourceTimelineRecord = event.data.record;
+        let resourceTimelineRecord = event.data.record;
         console.assert(resourceTimelineRecord instanceof WI.ResourceTimelineRecord);
 
-        this._pendingRecords.push(resourceTimelineRecord);
+        this._processRecord(resourceTimelineRecord);
 
         this.needsLayout();
     }
+
+    _processRecord(resourceTimelineRecord)
+    {
+        this._pendingRecords.push(resourceTimelineRecord);
+    }
 };

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/OverviewTimelineView.js (243023 => 243024)


--- trunk/Source/WebInspectorUI/UserInterface/Views/OverviewTimelineView.js	2019-03-15 23:33:16 UTC (rev 243023)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/OverviewTimelineView.js	2019-03-15 23:45:51 UTC (rev 243024)
@@ -52,7 +52,8 @@
         this._timelineRuler.addMarker(this._currentTimeMarker);
 
         this.element.classList.add("overview");
-        this.addSubview(this._dataGrid);
+        if (!this._recording.imported)
+            this.addSubview(this._dataGrid);
 
         this._networkTimeline = recording.timelines.get(WI.TimelineRecord.Type.Network);
         if (this._networkTimeline)
@@ -125,6 +126,11 @@
 
     // Protected
 
+    get showsImportedRecordingMessage()
+    {
+        return true;
+    }
+
     dataGridNodePathComponentSelected(event)
     {
         let dataGridNode = event.data.pathComponent.timelineDataGridNode;
@@ -135,6 +141,9 @@
 
     layout()
     {
+        if (this._recording.imported)
+            return;
+
         let oldZeroTime = this._timelineRuler.zeroTime;
         let oldStartTime = this._timelineRuler.startTime;
         let oldEndTime = this._timelineRuler.endTime;

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/RenderingFrameTimelineView.js (243023 => 243024)


--- trunk/Source/WebInspectorUI/UserInterface/Views/RenderingFrameTimelineView.js	2019-03-15 23:33:16 UTC (rev 243023)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/RenderingFrameTimelineView.js	2019-03-15 23:45:51 UTC (rev 243024)
@@ -90,6 +90,9 @@
         timeline.addEventListener(WI.Timeline.Event.RecordAdded, this._renderingFrameTimelineRecordAdded, this);
 
         this._pendingRecords = [];
+
+        for (let record of timeline.records)
+            this._processRecord(record);
     }
 
     static displayNameForDurationFilter(filter)
@@ -277,15 +280,20 @@
 
     _renderingFrameTimelineRecordAdded(event)
     {
-        var renderingFrameTimelineRecord = event.data.record;
+        let renderingFrameTimelineRecord = event.data.record;
         console.assert(renderingFrameTimelineRecord instanceof WI.RenderingFrameTimelineRecord);
         console.assert(renderingFrameTimelineRecord.children.length, "Missing child records for rendering frame.");
 
-        this._pendingRecords.push(renderingFrameTimelineRecord);
+        this._processRecord(renderingFrameTimelineRecord);
 
         this.needsLayout();
     }
 
+    _processRecord(renderingFrameTimelineRecord)
+    {
+        this._pendingRecords.push(renderingFrameTimelineRecord);
+    }
+
     _scopeBarSelectionDidChange()
     {
         this._dataGrid.filterDidChange();

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/ScriptDetailsTimelineView.js (243023 => 243024)


--- trunk/Source/WebInspectorUI/UserInterface/Views/ScriptDetailsTimelineView.js	2019-03-15 23:33:16 UTC (rev 243023)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/ScriptDetailsTimelineView.js	2019-03-15 23:45:51 UTC (rev 243024)
@@ -87,6 +87,9 @@
         timeline.addEventListener(WI.Timeline.Event.Refreshed, this._scriptTimelineRecordRefreshed, this);
 
         this._pendingRecords = [];
+
+        for (let record of timeline.records)
+            this._processRecord(record);
     }
 
     // Public
@@ -224,14 +227,19 @@
 
     _scriptTimelineRecordAdded(event)
     {
-        var scriptTimelineRecord = event.data.record;
+        let scriptTimelineRecord = event.data.record;
         console.assert(scriptTimelineRecord instanceof WI.ScriptTimelineRecord);
 
-        this._pendingRecords.push(scriptTimelineRecord);
+        this._processRecord(scriptTimelineRecord);
 
         this.needsLayout();
     }
 
+    _processRecord(scriptTimelineRecord)
+    {
+        this._pendingRecords.push(scriptTimelineRecord);
+    }
+
     _scriptTimelineRecordRefreshed(event)
     {
         this.needsLayout();

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/TimelineOverview.js (243023 => 243024)


--- trunk/Source/WebInspectorUI/UserInterface/Views/TimelineOverview.js	2019-03-15 23:33:16 UTC (rev 243023)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/TimelineOverview.js	2019-03-15 23:45:51 UTC (rev 243024)
@@ -116,10 +116,28 @@
 
         this._viewModeDidChange();
 
+        WI.timelineManager.addEventListener(WI.TimelineManager.Event.RecordingImported, this._recordingImported, this);
         WI.timelineManager.addEventListener(WI.TimelineManager.Event.CapturingStarted, this._capturingStarted, this);
         WI.timelineManager.addEventListener(WI.TimelineManager.Event.CapturingStopped, this._capturingStopped, this);
     }
 
+    // Import / Export
+
+    exportData()
+    {
+        let json = {
+            secondsPerPixel: this.secondsPerPixel,
+            scrollStartTime: this.scrollStartTime,
+            selectionStartTime: this.selectionStartTime,
+            selectionDuration: this.selectionDuration,
+        };
+
+        if (this._selectedTimeline)
+            json.selectedTimelineType = this._selectedTimeline.type;
+
+        return json;
+    }
+
     // Public
 
     get selectedTimeline()
@@ -657,6 +675,8 @@
             overviewGraph.hidden();
             treeElement.hidden = true;
         }
+
+        this.needsLayout();
     }
 
     _instrumentRemoved(event)
@@ -1012,13 +1032,36 @@
         this._editingInstrumentsDidChange();
     }
 
-    _capturingStarted()
+    _recordingImported(event)
     {
+        let {overviewData} = event.data;
+
+        if (overviewData.secondsPerPixel !== undefined)
+            this.secondsPerPixel = overviewData.secondsPerPixel;
+        if (overviewData.scrollStartTime !== undefined)
+            this.scrollStartTime = overviewData.scrollStartTime;
+        if (overviewData.selectionStartTime !== undefined)
+            this.selectionStartTime = overviewData.selectionStartTime;
+        if (overviewData.selectionDuration !== undefined) {
+            if (overviewData.selectionDuration === Number.MAX_VALUE)
+                this._timelineRuler.selectEntireRange();
+            else
+                this.selectionDuration = overviewData.selectionDuration;
+        }
+        if (overviewData.selectedTimelineType !== undefined) {
+            let timeline = this._recording.timelineForRecordType(overviewData.selectedTimelineType);
+            if (timeline)
+                this.selectedTimeline = timeline;
+        }
+    }
+
+    _capturingStarted(event)
+    {
         this._editInstrumentsButton.enabled = false;
         this._stopEditingInstruments();
     }
 
-    _capturingStopped()
+    _capturingStopped(event)
     {
         this._editInstrumentsButton.enabled = true;
     }

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/TimelineRecordBar.css (243023 => 243024)


--- trunk/Source/WebInspectorUI/UserInterface/Views/TimelineRecordBar.css	2019-03-15 23:33:16 UTC (rev 243023)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/TimelineRecordBar.css	2019-03-15 23:45:51 UTC (rev 243024)
@@ -111,8 +111,8 @@
 }
 
 
-.timeline-record-bar.timeline-record-type-layout.layout-timeline-record-paint > .segment,
-.timeline-record-bar.timeline-record-type-layout.layout-timeline-record-composite > .segment {
+.timeline-record-bar.timeline-record-type-layout.paint > .segment,
+.timeline-record-bar.timeline-record-type-layout.composite > .segment {
     background-color: hsl(76, 49%, 60%);
     border-color: hsl(79, 45%, 51%);
 }
@@ -122,7 +122,7 @@
     border-color: hsl(273, 33%, 58%);
 }
 
-.timeline-record-bar.timeline-record-type-script.script-timeline-record-garbage-collected > .segment {
+.timeline-record-bar.timeline-record-type-script.garbage-collected > .segment {
     background-color: hsl(23, 69%, 73%);
     border-color: hsl(11, 54%, 62%);    
 }

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/TimelineRecordingContentView.css (243023 => 243024)


--- trunk/Source/WebInspectorUI/UserInterface/Views/TimelineRecordingContentView.css	2019-03-15 23:33:16 UTC (rev 243023)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/TimelineRecordingContentView.css	2019-03-15 23:45:51 UTC (rev 243024)
@@ -59,7 +59,7 @@
     --scope-bar-border-color-override: transparent;
 }
 
-.content-view.timeline-recording > .content-browser .recording-progress {
+.content-view.timeline-recording > .content-browser :matches(.recording-progress, .recording-imported) {
     position: absolute;
     left: 0;
     right: 0;
@@ -69,7 +69,7 @@
     background-color: var(--panel-background-color-light);
 }
 
-.content-view.timeline-recording > .content-browser .recording-progress > .status {
+.content-view.timeline-recording > .content-browser :matches(.recording-progress, .recording-imported) > .status {
     margin-top: 40px;
     margin-bottom: 10px;
     text-align: center;

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/TimelineRecordingContentView.js (243023 => 243024)


--- trunk/Source/WebInspectorUI/UserInterface/Views/TimelineRecordingContentView.js	2019-03-15 23:33:16 UTC (rev 243023)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/TimelineRecordingContentView.js	2019-03-15 23:45:51 UTC (rev 243024)
@@ -64,6 +64,19 @@
             WI.settings.timelinesAutoStop.addEventListener(WI.Setting.Event.Changed, this._handleTimelinesAutoStopSettingChanged, this);
         }
 
+        this._exportButtonNavigationItem = new WI.ButtonNavigationItem("export", WI.UIString("Export"), "Images/Export.svg", 15, 15);
+        this._exportButtonNavigationItem.toolTip = WI.UIString("Export (%s)").format(WI.saveKeyboardShortcut.displayName);
+        this._exportButtonNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
+        this._exportButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
+        this._exportButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._exportButtonNavigationItemClicked, this);
+        this._exportButtonNavigationItem.enabled = false;
+
+        this._importButtonNavigationItem = new WI.ButtonNavigationItem("import", WI.UIString("Import"), "Images/Import.svg", 15, 15);
+        this._importButtonNavigationItem.toolTip = WI.UIString("Import");
+        this._importButtonNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
+        this._importButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
+        this._importButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._importButtonNavigationItemClicked, this);
+
         this._clearTimelineNavigationItem = new WI.ButtonNavigationItem("clear-timeline", WI.UIString("Clear Timeline (%s)").format(WI.clearKeyboardShortcut.displayName), "Images/NavigationItemTrash.svg", 15, 15);
         this._clearTimelineNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
         this._clearTimelineNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._clearTimeline, this);
@@ -74,6 +87,9 @@
         this._progressView = new WI.TimelineRecordingProgressView;
         this._timelineContentBrowser.addSubview(this._progressView);
 
+        this._importedView = new WI.TimelineRecordingImportedView;
+        this._timelineContentBrowser.addSubview(this._importedView);
+
         this._timelineViewMap = new Map;
         this._pathComponentMap = new Map;
 
@@ -109,6 +125,11 @@
             this._instrumentAdded(instrument);
 
         this.showOverviewTimelineView();
+
+        if (this._recording.imported) {
+            let {startTime, endTime} = this._recording;
+            this._updateTimes(startTime, endTime, endTime);
+        }
     }
 
     // Public
@@ -167,6 +188,9 @@
         if (this._autoStopCheckboxNavigationItem)
             navigationItems.push(this._autoStopCheckboxNavigationItem);
         navigationItems.push(new WI.DividerNavigationItem);
+        navigationItems.push(this._importButtonNavigationItem);
+        navigationItems.push(this._exportButtonNavigationItem);
+        navigationItems.push(new WI.DividerNavigationItem);
         navigationItems.push(this._clearTimelineNavigationItem);
         return navigationItems;
     }
@@ -179,14 +203,12 @@
 
     get supportsSave()
     {
-        let currentContentView = this._timelineContentBrowser.currentContentView;
-        return currentContentView && currentContentView.supportsSave;
+        return this._recording.canExport();
     }
 
     get saveData()
     {
-        let currentContentView = this._timelineContentBrowser.currentContentView;
-        return currentContentView && currentContentView.saveData || null;
+        return {customSaveHandler: () => { this._exportTimelineRecording(); }};
     }
 
     get currentTimelineView()
@@ -200,7 +222,9 @@
 
         this._timelineOverview.shown();
         this._timelineContentBrowser.shown();
+
         this._clearTimelineNavigationItem.enabled = !this._recording.readonly && !isNaN(this._recording.startTime);
+        this._exportButtonNavigationItem.enabled = this._recording.canExport();
 
         this._currentContentViewDidChange();
 
@@ -292,6 +316,7 @@
         this._timelineOverview.viewMode = newViewMode;
         this._updateTimelineOverviewHeight();
         this._updateProgressView();
+        this._updateImportedView();
         this._updateFilterBar();
 
         if (timelineView) {
@@ -497,6 +522,7 @@
         if (!this._updating)
             this._startUpdatingCurrentTime(startTime);
         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
@@ -518,6 +544,8 @@
             this._updateTimelineViewTimes(this.currentTimelineView);
 
         this._discontinuityStartTime = event.data.endTime || this._currentTime;
+
+        this._exportButtonNavigationItem.enabled = this._recording.canExport();
     }
 
     _debuggerPaused(event)
@@ -562,6 +590,42 @@
         this._autoStopCheckboxNavigationItem.checked = WI.settings.timelinesAutoStop.value;
     }
 
+    _exportTimelineRecording()
+    {
+        let json = {
+            version: WI.TimelineRecording.SerializationVersion,
+            recording: this._recording.exportData(),
+            overview: this._timelineOverview.exportData(),
+        };
+        if (!json.recording || !json.overview) {
+            InspectorFrontendHost.beep();
+            return;
+        }
+
+        let frameName = null;
+        let mainFrame = WI.networkManager.mainFrame;
+        if (mainFrame)
+            frameName = mainFrame.mainResource.urlComponents.host || mainFrame.mainResource.displayName;
+
+        let filename = frameName ? `${frameName}-recording` : this._recording.displayName;
+        let url = "" + encodeURI(filename) + ".json";
+        WI.FileUtilities.save({
+            url,
+            content: JSON.stringify(json),
+            forceSaveAs: true,
+        });
+    }
+
+    _exportButtonNavigationItemClicked(event)
+    {
+        this._exportTimelineRecording();
+    }
+
+    _importButtonNavigationItemClicked(event)
+    {
+        WI.FileUtilities.importJSON((result) => WI.timelineManager.processJSON(result));
+    }
+
     _clearTimeline(event)
     {
         if (this._recording.readonly)
@@ -664,6 +728,7 @@
         this._timelineOverview.reset();
         this._overviewTimelineView.reset();
         this._clearTimelineNavigationItem.enabled = false;
+        this._exportButtonNavigationItem.enabled = false;
     }
 
     _recordingUnloaded(event)
@@ -877,6 +942,11 @@
         this._progressView.visible = isCapturing && this.currentTimelineView && !this.currentTimelineView.showsLiveRecordingData;
     }
 
+    _updateImportedView()
+    {
+        this._importedView.visible = this._recording.imported && this.currentTimelineView && this.currentTimelineView.showsImportedRecordingMessage;
+    }
+
     _updateFilterBar()
     {
         this._filterBarNavigationItem.hidden = !this.currentTimelineView || !this.currentTimelineView.showsFilterBar;

Copied: trunk/Source/WebInspectorUI/UserInterface/Views/TimelineRecordingImportedView.css (from rev 243022, trunk/Source/WebInspectorUI/UserInterface/Views/LayoutTimelineOverviewGraph.css) (0 => 243024)


--- trunk/Source/WebInspectorUI/UserInterface/Views/TimelineRecordingImportedView.css	                        (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/TimelineRecordingImportedView.css	2019-03-15 23:45:51 UTC (rev 243024)
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+.content-view.timeline-recording > .content-browser .recording-imported .message-text-view > .message {
+    font-size: var(--message-text-view-large-font-size);
+    font-weight: 600;
+    letter-spacing: 0.02em;
+}

Copied: trunk/Source/WebInspectorUI/UserInterface/Views/TimelineRecordingImportedView.js (from rev 243022, trunk/Source/WebInspectorUI/UserInterface/Models/HeapAllocationsTimelineRecord.js) (0 => 243024)


--- trunk/Source/WebInspectorUI/UserInterface/Views/TimelineRecordingImportedView.js	                        (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/TimelineRecordingImportedView.js	2019-03-15 23:45:51 UTC (rev 243024)
@@ -0,0 +1,52 @@
+/*
+ * 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.TimelineRecordingImportedView = class TimelineRecordingImportedView extends WI.View
+{
+    constructor()
+    {
+        super();
+
+        this.element.classList.add("recording-imported");
+        this.element.appendChild(WI.createMessageTextView(WI.UIString("Imported Timeline Recording")));
+    }
+
+    // Public
+
+    get visible()
+    {
+        return this._visible;
+    }
+
+    set visible(x)
+    {
+        if (this._visible === x)
+            return;
+
+        // FIXME: remove once <https://webkit.org/b/150741> is fixed.
+        this._visible = x;
+        this.element.classList.toggle("hidden", !this._visible);
+    }
+};

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/TimelineRecordingProgressView.js (243023 => 243024)


--- trunk/Source/WebInspectorUI/UserInterface/Views/TimelineRecordingProgressView.js	2019-03-15 23:33:16 UTC (rev 243023)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/TimelineRecordingProgressView.js	2019-03-15 23:45:51 UTC (rev 243024)
@@ -47,7 +47,10 @@
 
     // Public
 
-    get visible() { return this._visible; }
+    get visible()
+    {
+        return this._visible;
+    }
 
     set visible(x)
     {

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/TimelineView.js (243023 => 243024)


--- trunk/Source/WebInspectorUI/UserInterface/Views/TimelineView.js	2019-03-15 23:33:16 UTC (rev 243023)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/TimelineView.js	2019-03-15 23:45:51 UTC (rev 243024)
@@ -56,6 +56,12 @@
         return true;
     }
 
+    get showsImportedRecordingMessage()
+    {
+        // Implemented by sub-classes if needed.
+        return false;
+    }
+
     get showsFilterBar()
     {
         // Implemented by sub-classes if needed.
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to