Title: [241219] trunk
Revision
241219
Author
[email protected]
Date
2019-02-08 16:25:46 -0800 (Fri, 08 Feb 2019)

Log Message

Web Inspector: Import / Export Heap Snapshots
https://bugs.webkit.org/show_bug.cgi?id=194448
<rdar://problem/47928093>

Patch by Joseph Pecoraro <[email protected]> on 2019-02-08
Reviewed by Devin Rousso.

Source/WebInspectorUI:

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

* UserInterface/Proxies/HeapSnapshotProxy.js:
(WI.HeapSnapshotProxy):
(WI.HeapSnapshotProxy.deserialize):
(WI.HeapSnapshotProxy.prototype.get imported):
(WI.HeapSnapshotProxy.prototype.get snapshotStringData):
(WI.HeapSnapshotProxy.prototype.set snapshotStringData):
Include an "imported" state on the HeapSnapshot and allow for
stashing the snapshotStringData on the main thread side.

* UserInterface/Proxies/HeapSnapshotWorkerProxy.js:
(WI.HeapSnapshotWorkerProxy.prototype.createImportedSnapshot):
* UserInterface/Workers/HeapSnapshot/HeapSnapshotWorker.js:
(HeapSnapshotWorker.prototype.clearSnapshots):
(HeapSnapshotWorker.prototype.createSnapshot):
Provide a specialized way to create an imported HeapSnapshot.
Track imported snapshots separately since they won't want to
be searched for live/dead objects due to active recording GCs.

* UserInterface/Workers/HeapSnapshot/HeapSnapshot.js:
(HeapSnapshot):
(HeapSnapshot.updateCategoriesAndMetadata):
(HeapSnapshot.allocationBucketCounts):
(HeapSnapshot.instancesWithClassName):
(HeapSnapshot.prototype.nodeWithIdentifier):
(HeapSnapshot.prototype.dominatedNodes):
(HeapSnapshot.prototype.retainedNodes):
(HeapSnapshot.prototype.retainers):
(HeapSnapshot.prototype.updateDeadNodesAndGatherCollectionData):
(HeapSnapshot.prototype.serialize):
(HeapSnapshot.prototype.serializeNode):
(HeapSnapshot.prototype._buildPostOrderIndexes):
(HeapSnapshot.prototype._buildDominatorIndexes):
(HeapSnapshot.prototype._buildRetainedSizes):
(HeapSnapshot.prototype._gcRootPathes.visitNode):
(HeapSnapshot.prototype._gcRootPathes):
Construct a HeapSnapshot knowinng whether or not it is imported.
Imported snapshots may be the "GCDebugging" snapshot type which
differs from "Inspector" by the number of node fields. So keep
the node field count a member instead of a global constant
in order to work with both snapshot types.

* UserInterface/Models/HeapAllocationsInstrument.js:
(WI.HeapAllocationsInstrument.prototype._takeHeapSnapshot):
* UserInterface/Protocol/ConsoleObserver.js:
(WI.ConsoleObserver.prototype.heapSnapshot):
* UserInterface/Protocol/HeapObserver.js:
(WI.HeapObserver.prototype.trackingStart):
(WI.HeapObserver.prototype.trackingComplete):
Stash the original string JSON data on the main thread side
where we already have the data.

* UserInterface/Views/HeapAllocationsTimelineOverviewGraph.js:
(WI.HeapAllocationsTimelineOverviewGraph.prototype.layout):
Don't show [S] icons for imported snapshots with no timestamp.

* UserInterface/Views/HeapAllocationsTimelineView.js:
(WI.HeapAllocationsTimelineView):
(WI.HeapAllocationsTimelineView.prototype.get navigationItems):
(WI.HeapAllocationsTimelineView.prototype._importButtonNavigationItemClicked):
(WI.HeapAllocationsTimelineView.prototype._takeHeapSnapshotClicked):
Import button that just creates a new snapshot.

* UserInterface/Views/HeapSnapshotContentView.js:
(WI.HeapSnapshotContentView):
(WI.HeapSnapshotContentView.prototype.get navigationItems):
(WI.HeapSnapshotContentView.prototype.get supportsSave):
(WI.HeapSnapshotContentView.prototype.get saveData):
(WI.HeapSnapshotContentView.prototype._exportSnapshot):
Export button that saves the original data.

* UserInterface/Views/TimelineTabContentView.js:
(WI.TimelineTabContentView.displayNameForRecord):
Specialized display string for imported snapshots.

LayoutTests:

* inspector/heap/imported-snapshot-expected.txt: Added.
* inspector/heap/imported-snapshot.html: Added.
* platform/mac/TestExpectations:

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (241218 => 241219)


--- trunk/LayoutTests/ChangeLog	2019-02-09 00:24:24 UTC (rev 241218)
+++ trunk/LayoutTests/ChangeLog	2019-02-09 00:25:46 UTC (rev 241219)
@@ -1,3 +1,15 @@
+2019-02-08  Joseph Pecoraro  <[email protected]>
+
+        Web Inspector: Import / Export Heap Snapshots
+        https://bugs.webkit.org/show_bug.cgi?id=194448
+        <rdar://problem/47928093>
+
+        Reviewed by Devin Rousso.
+
+        * inspector/heap/imported-snapshot-expected.txt: Added.
+        * inspector/heap/imported-snapshot.html: Added.
+        * platform/mac/TestExpectations:
+
 2019-02-08  Nikita Vasilyev  <[email protected]>
 
         Web Inspector: Styles: close unbalanced quotes and parenthesis when editing values

Added: trunk/LayoutTests/inspector/heap/imported-snapshot-expected.txt (0 => 241219)


--- trunk/LayoutTests/inspector/heap/imported-snapshot-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/inspector/heap/imported-snapshot-expected.txt	2019-02-09 00:25:46 UTC (rev 241219)
@@ -0,0 +1,11 @@
+Test for an imported HeapSnapshot.
+
+
+== Running test suite: HeapSnapshot.imported
+-- Running test case: HeapSnapshot.imported
+PASS: Should not have an error creating a snapshot.
+PASS: Normal snapshot is not imported.
+PASS: Normal snapshot title should not be set.
+PASS: Imported snapshot is imported.
+PASS: Imported snapshot title should be set.
+

Added: trunk/LayoutTests/inspector/heap/imported-snapshot.html (0 => 241219)


--- trunk/LayoutTests/inspector/heap/imported-snapshot.html	                        (rev 0)
+++ trunk/LayoutTests/inspector/heap/imported-snapshot.html	2019-02-09 00:25:46 UTC (rev 241219)
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src=""
+<script>
+function test()
+{
+    let suite = InspectorTest.createAsyncSuite("HeapSnapshot.imported");
+
+    suite.addTestCase({
+        name: "HeapSnapshot.imported",
+        description: "createSnapshot() and createImportedSnapshot() differences.",
+        test(resolve, reject) {
+            HeapAgent.snapshot((error, timestamp, snapshotStringData) => {
+                InspectorTest.expectThat(!error, "Should not have an error creating a snapshot.");
+
+                const importedTitle = "Imported Snapshot";
+                let workerProxy = WI.HeapSnapshotWorkerProxy.singleton();
+                workerProxy.createSnapshot(snapshotStringData, ({objectId, snapshot: serializedSnapshot}) => {
+                    let snapshot = WI.HeapSnapshotProxy.deserialize(objectId, serializedSnapshot);
+                    snapshot.snapshotStringData = snapshotStringData;
+                    workerProxy.createImportedSnapshot(snapshotStringData, importedTitle, ({objectId, snapshot: serializedSnapshot}) => {
+                        let importedSnapshot = WI.HeapSnapshotProxy.deserialize(objectId, serializedSnapshot);
+                        importedSnapshot.snapshotStringData = snapshotStringData;
+
+                        InspectorTest.expectFalse(snapshot.imported, "Normal snapshot is not imported.");
+                        InspectorTest.expectNull(snapshot.title, "Normal snapshot title should not be set.");
+                        InspectorTest.expectTrue(importedSnapshot.imported, "Imported snapshot is imported.");
+                        InspectorTest.expectEqual(importedSnapshot.title, importedTitle, "Imported snapshot title should be set.");
+                        resolve();
+                    });
+                });
+            });
+        }
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body _onload_="runTest()">
+<p>Test for an imported HeapSnapshot.</p>
+</body>
+</html>

Modified: trunk/LayoutTests/platform/mac/TestExpectations (241218 => 241219)


--- trunk/LayoutTests/platform/mac/TestExpectations	2019-02-09 00:24:24 UTC (rev 241218)
+++ trunk/LayoutTests/platform/mac/TestExpectations	2019-02-09 00:25:46 UTC (rev 241219)
@@ -1086,6 +1086,7 @@
 webkit.org/b/164872 inspector/worker/debugger-multiple-targets-pause.html [ Pass Failure Timeout ]
 webkit.org/b/165582 inspector/worker/debugger-scripts.html [ Pass Failure ]
 webkit.org/b/167203 inspector/worker/debugger-shared-breakpoint.html [ Pass Failure Timeout ]
+webkit.org/b/155607 inspector/heap/imported-snapshot.html [ Pass Timeout ]
 webkit.org/b/155607 inspector/heap/snapshot.html [ Pass Timeout ]
 webkit.org/b/143719 inspector/console/console-api.html [ Pass Timeout ]
 webkit.org/b/156078 inspector/console/heapSnapshot.html [ Pass Timeout ]

Modified: trunk/Source/WebInspectorUI/ChangeLog (241218 => 241219)


--- trunk/Source/WebInspectorUI/ChangeLog	2019-02-09 00:24:24 UTC (rev 241218)
+++ trunk/Source/WebInspectorUI/ChangeLog	2019-02-09 00:25:46 UTC (rev 241219)
@@ -1,5 +1,90 @@
 2019-02-08  Joseph Pecoraro  <[email protected]>
 
+        Web Inspector: Import / Export Heap Snapshots
+        https://bugs.webkit.org/show_bug.cgi?id=194448
+        <rdar://problem/47928093>
+
+        Reviewed by Devin Rousso.
+
+        * Localizations/en.lproj/localizedStrings.js:
+        New strings.
+
+        * UserInterface/Proxies/HeapSnapshotProxy.js:
+        (WI.HeapSnapshotProxy):
+        (WI.HeapSnapshotProxy.deserialize):
+        (WI.HeapSnapshotProxy.prototype.get imported):
+        (WI.HeapSnapshotProxy.prototype.get snapshotStringData):
+        (WI.HeapSnapshotProxy.prototype.set snapshotStringData):
+        Include an "imported" state on the HeapSnapshot and allow for
+        stashing the snapshotStringData on the main thread side.
+
+        * UserInterface/Proxies/HeapSnapshotWorkerProxy.js:
+        (WI.HeapSnapshotWorkerProxy.prototype.createImportedSnapshot):
+        * UserInterface/Workers/HeapSnapshot/HeapSnapshotWorker.js:
+        (HeapSnapshotWorker.prototype.clearSnapshots):
+        (HeapSnapshotWorker.prototype.createSnapshot):
+        Provide a specialized way to create an imported HeapSnapshot.
+        Track imported snapshots separately since they won't want to
+        be searched for live/dead objects due to active recording GCs.
+
+        * UserInterface/Workers/HeapSnapshot/HeapSnapshot.js:
+        (HeapSnapshot):
+        (HeapSnapshot.updateCategoriesAndMetadata):
+        (HeapSnapshot.allocationBucketCounts):
+        (HeapSnapshot.instancesWithClassName):
+        (HeapSnapshot.prototype.nodeWithIdentifier):
+        (HeapSnapshot.prototype.dominatedNodes):
+        (HeapSnapshot.prototype.retainedNodes):
+        (HeapSnapshot.prototype.retainers):
+        (HeapSnapshot.prototype.updateDeadNodesAndGatherCollectionData):
+        (HeapSnapshot.prototype.serialize):
+        (HeapSnapshot.prototype.serializeNode):
+        (HeapSnapshot.prototype._buildPostOrderIndexes):
+        (HeapSnapshot.prototype._buildDominatorIndexes):
+        (HeapSnapshot.prototype._buildRetainedSizes):
+        (HeapSnapshot.prototype._gcRootPathes.visitNode):
+        (HeapSnapshot.prototype._gcRootPathes):
+        Construct a HeapSnapshot knowinng whether or not it is imported.
+        Imported snapshots may be the "GCDebugging" snapshot type which
+        differs from "Inspector" by the number of node fields. So keep
+        the node field count a member instead of a global constant
+        in order to work with both snapshot types.
+
+        * UserInterface/Models/HeapAllocationsInstrument.js:
+        (WI.HeapAllocationsInstrument.prototype._takeHeapSnapshot):
+        * UserInterface/Protocol/ConsoleObserver.js:
+        (WI.ConsoleObserver.prototype.heapSnapshot):
+        * UserInterface/Protocol/HeapObserver.js:
+        (WI.HeapObserver.prototype.trackingStart):
+        (WI.HeapObserver.prototype.trackingComplete):
+        Stash the original string JSON data on the main thread side
+        where we already have the data.
+
+        * UserInterface/Views/HeapAllocationsTimelineOverviewGraph.js:
+        (WI.HeapAllocationsTimelineOverviewGraph.prototype.layout):
+        Don't show [S] icons for imported snapshots with no timestamp.
+
+        * UserInterface/Views/HeapAllocationsTimelineView.js:
+        (WI.HeapAllocationsTimelineView):
+        (WI.HeapAllocationsTimelineView.prototype.get navigationItems):
+        (WI.HeapAllocationsTimelineView.prototype._importButtonNavigationItemClicked):
+        (WI.HeapAllocationsTimelineView.prototype._takeHeapSnapshotClicked):
+        Import button that just creates a new snapshot.
+
+        * UserInterface/Views/HeapSnapshotContentView.js:
+        (WI.HeapSnapshotContentView):
+        (WI.HeapSnapshotContentView.prototype.get navigationItems):
+        (WI.HeapSnapshotContentView.prototype.get supportsSave):
+        (WI.HeapSnapshotContentView.prototype.get saveData):
+        (WI.HeapSnapshotContentView.prototype._exportSnapshot):
+        Export button that saves the original data.
+
+        * UserInterface/Views/TimelineTabContentView.js:
+        (WI.TimelineTabContentView.displayNameForRecord):
+        Specialized display string for imported snapshots.
+
+2019-02-08  Joseph Pecoraro  <[email protected]>
+
         Web Inspector: Add Debug setting to show Internal Object Classes in Heap Snapshot
         https://bugs.webkit.org/show_bug.cgi?id=194445
 

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


--- trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js	2019-02-09 00:24:24 UTC (rev 241218)
+++ trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js	2019-02-09 00:25:46 UTC (rev 241219)
@@ -418,6 +418,7 @@
 localizedStrings["Expanded"] = "Expanded";
 localizedStrings["Experimental"] = "Experimental";
 localizedStrings["Export"] = "Export";
+localizedStrings["Export (%s)"] = "Export (%s)";
 localizedStrings["Export HAR"] = "Export HAR";
 localizedStrings["Export Result"] = "Export Result";
 localizedStrings["Export Test"] = "Export Test";
@@ -488,6 +489,7 @@
 localizedStrings["Headers"] = "Headers";
 localizedStrings["Headers:"] = "Headers:";
 localizedStrings["Heading Level"] = "Heading Level";
+localizedStrings["Heap Snapshot %s-%s-%s at %s.%s.%s"] = "Heap Snapshot %s-%s-%s at %s.%s.%s";
 localizedStrings["Heap Snapshot Object (%s)"] = "Heap Snapshot Object (%s)";
 localizedStrings["Height"] = "Height";
 localizedStrings["Hide Console"] = "Hide Console";
@@ -519,6 +521,7 @@
 localizedStrings["Import"] = "Import";
 localizedStrings["Imported"] = "Imported";
 localizedStrings["Imported Recordings"] = "Imported Recordings";
+localizedStrings["Imported \u2014 %s"] = "Imported \u2014 %s";
 localizedStrings["Incomplete"] = "Incomplete";
 localizedStrings["Indent width:"] = "Indent width:";
 localizedStrings["Index"] = "Index";

Modified: trunk/Source/WebInspectorUI/UserInterface/Models/HeapAllocationsInstrument.js (241218 => 241219)


--- trunk/Source/WebInspectorUI/UserInterface/Models/HeapAllocationsInstrument.js	2019-02-09 00:24:24 UTC (rev 241218)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/HeapAllocationsInstrument.js	2019-02-09 00:25:46 UTC (rev 241219)
@@ -79,6 +79,7 @@
             let workerProxy = WI.HeapSnapshotWorkerProxy.singleton();
             workerProxy.createSnapshot(snapshotStringData, ({objectId, snapshot: serializedSnapshot}) => {
                 let snapshot = WI.HeapSnapshotProxy.deserialize(objectId, serializedSnapshot);
+                snapshot.snapshotStringData = snapshotStringData;
                 WI.timelineManager.heapSnapshotAdded(timestamp, snapshot);
             });
         });

Modified: trunk/Source/WebInspectorUI/UserInterface/Protocol/ConsoleObserver.js (241218 => 241219)


--- trunk/Source/WebInspectorUI/UserInterface/Protocol/ConsoleObserver.js	2019-02-09 00:24:24 UTC (rev 241218)
+++ trunk/Source/WebInspectorUI/UserInterface/Protocol/ConsoleObserver.js	2019-02-09 00:25:46 UTC (rev 241219)
@@ -53,6 +53,7 @@
         let workerProxy = WI.HeapSnapshotWorkerProxy.singleton();
         workerProxy.createSnapshot(snapshotStringData, title || null, ({objectId, snapshot: serializedSnapshot}) => {
             let snapshot = WI.HeapSnapshotProxy.deserialize(objectId, serializedSnapshot);
+            snapshot.snapshotStringData = snapshotStringData;
             WI.timelineManager.heapSnapshotAdded(timestamp, snapshot);
         });
     }

Modified: trunk/Source/WebInspectorUI/UserInterface/Protocol/HeapObserver.js (241218 => 241219)


--- trunk/Source/WebInspectorUI/UserInterface/Protocol/HeapObserver.js	2019-02-09 00:24:24 UTC (rev 241218)
+++ trunk/Source/WebInspectorUI/UserInterface/Protocol/HeapObserver.js	2019-02-09 00:25:46 UTC (rev 241219)
@@ -37,6 +37,7 @@
         let workerProxy = WI.HeapSnapshotWorkerProxy.singleton();
         workerProxy.createSnapshot(snapshotStringData, ({objectId, snapshot: serializedSnapshot}) => {
             let snapshot = WI.HeapSnapshotProxy.deserialize(objectId, serializedSnapshot);
+            snapshot.snapshotStringData = snapshotStringData;
             WI.timelineManager.heapTrackingStarted(timestamp, snapshot);
         });
     }
@@ -46,6 +47,7 @@
         let workerProxy = WI.HeapSnapshotWorkerProxy.singleton();
         workerProxy.createSnapshot(snapshotStringData, ({objectId, snapshot: serializedSnapshot}) => {
             let snapshot = WI.HeapSnapshotProxy.deserialize(objectId, serializedSnapshot);
+            snapshot.snapshotStringData = snapshotStringData;
             WI.timelineManager.heapTrackingCompleted(timestamp, snapshot);
         });
     }

Modified: trunk/Source/WebInspectorUI/UserInterface/Proxies/HeapSnapshotProxy.js (241218 => 241219)


--- trunk/Source/WebInspectorUI/UserInterface/Proxies/HeapSnapshotProxy.js	2019-02-09 00:24:24 UTC (rev 241218)
+++ trunk/Source/WebInspectorUI/UserInterface/Proxies/HeapSnapshotProxy.js	2019-02-09 00:25:46 UTC (rev 241219)
@@ -25,7 +25,7 @@
 
 WI.HeapSnapshotProxy = class HeapSnapshotProxy extends WI.Object
 {
-    constructor(snapshotObjectId, identifier, title, totalSize, totalObjectCount, liveSize, categories)
+    constructor(snapshotObjectId, identifier, title, totalSize, totalObjectCount, liveSize, categories, imported)
     {
         super();
 
@@ -37,6 +37,8 @@
         this._totalObjectCount = totalObjectCount;
         this._liveSize = liveSize;
         this._categories = Map.fromObject(categories);
+        this._imported = imported;
+        this._snapshotStringData = null;
 
         console.assert(!this.invalid);
 
@@ -49,8 +51,8 @@
 
     static deserialize(objectId, serializedSnapshot)
     {
-        let {identifier, title, totalSize, totalObjectCount, liveSize, categories} = serializedSnapshot;
-        return new WI.HeapSnapshotProxy(objectId, identifier, title, totalSize, totalObjectCount, liveSize, categories);
+        let {identifier, title, totalSize, totalObjectCount, liveSize, categories, imported} = serializedSnapshot;
+        return new WI.HeapSnapshotProxy(objectId, identifier, title, totalSize, totalObjectCount, liveSize, categories, imported);
     }
 
     static invalidateSnapshotProxies()
@@ -73,8 +75,19 @@
     get totalObjectCount() { return this._totalObjectCount; }
     get liveSize() { return this._liveSize; }
     get categories() { return this._categories; }
+    get imported() { return this._imported; }
     get invalid() { return this._proxyObjectId === 0; }
 
+    get snapshotStringData()
+    {
+        return this._snapshotStringData;
+    }
+
+    set snapshotStringData(data)
+    {
+        this._snapshotStringData = data;
+    }
+
     updateForCollectionEvent(event)
     {
         console.assert(!this.invalid);

Modified: trunk/Source/WebInspectorUI/UserInterface/Proxies/HeapSnapshotWorkerProxy.js (241218 => 241219)


--- trunk/Source/WebInspectorUI/UserInterface/Proxies/HeapSnapshotWorkerProxy.js	2019-02-09 00:24:24 UTC (rev 241218)
+++ trunk/Source/WebInspectorUI/UserInterface/Proxies/HeapSnapshotWorkerProxy.js	2019-02-09 00:25:46 UTC (rev 241219)
@@ -66,6 +66,12 @@
         this.performAction("createSnapshotDiff", ...arguments);
     }
 
+    createImportedSnapshot(snapshotStringData, title, callback)
+    {
+        const imported = true;
+        this.performAction("createSnapshot", snapshotStringData, title, imported, callback);
+    }
+
     // Public
 
     performAction(actionName)

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/HeapAllocationsTimelineOverviewGraph.js (241218 => 241219)


--- trunk/Source/WebInspectorUI/UserInterface/Views/HeapAllocationsTimelineOverviewGraph.js	2019-02-09 00:24:24 UTC (rev 241218)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/HeapAllocationsTimelineOverviewGraph.js	2019-02-09 00:25:46 UTC (rev 241219)
@@ -73,6 +73,9 @@
         }
 
         for (let record of visibleRecords) {
+            if (isNaN(record.timestamp))
+                continue;
+
             const halfImageWidth = 8;
             let x = xScale(record.timestamp) - halfImageWidth;
             if (x <= 1)

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/HeapAllocationsTimelineView.js (241218 => 241219)


--- trunk/Source/WebInspectorUI/UserInterface/Views/HeapAllocationsTimelineView.js	2019-02-09 00:24:24 UTC (rev 241218)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/HeapAllocationsTimelineView.js	2019-02-09 00:25:46 UTC (rev 241219)
@@ -59,6 +59,11 @@
             },
         };
 
+        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.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._importButtonNavigationItemClicked, this);
+
         let snapshotTooltip = WI.UIString("Take snapshot");
         this._takeHeapSnapshotButtonItem = new WI.ButtonNavigationItem("take-snapshot", snapshotTooltip, "Images/Camera.svg", 16, 16);
         this._takeHeapSnapshotButtonItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._takeHeapSnapshotClicked, this);
@@ -184,7 +189,7 @@
     get navigationItems()
     {
         if (this._showingSnapshotList) {
-            let items = [this._takeHeapSnapshotButtonItem, this._compareHeapSnapshotsButtonItem];
+            let items = [this._importButtonNavigationItem, this._takeHeapSnapshotButtonItem, this._compareHeapSnapshotsButtonItem];
             if (this._selectingComparisonHeapSnapshots)
                 items.push(this._compareHeapSnapshotHelpTextItem);
             return items;
@@ -214,6 +219,22 @@
         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)
@@ -382,6 +403,20 @@
         this._compareHeapSnapshotsButtonItem.enabled = hasAtLeastTwoValidSnapshots;
     }
 
+    _importButtonNavigationItemClicked()
+    {
+        WI.FileUtilities.importText(function(result) {
+            let snapshotStringData = result.text;
+            let workerProxy = WI.HeapSnapshotWorkerProxy.singleton();
+            workerProxy.createImportedSnapshot(snapshotStringData, result.filename, ({objectId, snapshot: serializedSnapshot}) => {
+                let snapshot = WI.HeapSnapshotProxy.deserialize(objectId, serializedSnapshot);
+                snapshot.snapshotStringData = snapshotStringData;
+                const timestamp = NaN;
+                WI.timelineManager.heapSnapshotAdded(timestamp, snapshot);
+            });
+        });
+    }
+
     _takeHeapSnapshotClicked()
     {
         HeapAgent.snapshot(function(error, timestamp, snapshotStringData) {
@@ -388,6 +423,7 @@
             let workerProxy = WI.HeapSnapshotWorkerProxy.singleton();
             workerProxy.createSnapshot(snapshotStringData, ({objectId, snapshot: serializedSnapshot}) => {
                 let snapshot = WI.HeapSnapshotProxy.deserialize(objectId, serializedSnapshot);
+                snapshot.snapshotStringData = snapshotStringData;
                 WI.timelineManager.heapSnapshotAdded(timestamp, snapshot);
             });
         });

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/HeapSnapshotContentView.js (241218 => 241219)


--- trunk/Source/WebInspectorUI/UserInterface/Views/HeapSnapshotContentView.js	2019-02-09 00:24:24 UTC (rev 241218)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/HeapSnapshotContentView.js	2019-02-09 00:25:46 UTC (rev 241219)
@@ -33,6 +33,12 @@
 
         this.element.classList.add("heap-snapshot");
 
+        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.High;
+        this._exportButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, () => { this._exportSnapshot(); });
+
         this._dataGrid = new WI.DataGrid(columns);
         this._dataGrid.sortColumnIdentifier = "retainedSize";
         this._dataGrid.sortOrder = WI.DataGrid.SortOrder.Descending;
@@ -52,6 +58,23 @@
 
     // Protected
 
+    get navigationItems()
+    {
+        if (this.representedObject instanceof WI.HeapSnapshotProxy)
+            return [this._exportButtonNavigationItem];
+        return [];
+    }
+
+    get supportsSave()
+    {
+        return this.representedObject instanceof WI.HeapSnapshotProxy;
+    }
+
+    get saveData()
+    {
+        return {customSaveHandler: () => { this._exportSnapshot(); }};
+    }
+
     shown()
     {
         super.shown();
@@ -73,6 +96,31 @@
 
     // Private
 
+    _exportSnapshot()
+    {
+        if (!this.representedObject.snapshotStringData) {
+            InspectorFrontendHost.beep();
+            return;
+        }
+
+        let date = new Date;
+        let values = [
+            date.getFullYear(),
+            Number.zeroPad(date.getMonth() + 1, 2),
+            Number.zeroPad(date.getDate(), 2),
+            Number.zeroPad(date.getHours(), 2),
+            Number.zeroPad(date.getMinutes(), 2),
+            Number.zeroPad(date.getSeconds(), 2),
+        ];
+        let filename = WI.UIString("Heap Snapshot %s-%s-%s at %s.%s.%s").format(...values);
+        let url = "" + encodeURI(filename) + ".json";
+        WI.FileUtilities.save({
+            url,
+            content: this.representedObject.snapshotStringData,
+            forceSaveAs: true,
+        });
+    }
+
     _sortDataGrid()
     {
         if (!this._heapSnapshotDataGridTree)

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/RecordingContentView.js (241218 => 241219)


--- trunk/Source/WebInspectorUI/UserInterface/Views/RecordingContentView.js	2019-02-09 00:24:24 UTC (rev 241218)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/RecordingContentView.js	2019-02-09 00:25:46 UTC (rev 241219)
@@ -147,8 +147,6 @@
         return {customSaveHandler: () => { this._exportRecording(); }};
     }
 
-    // Protected
-
     initialLayout()
     {
         let previewHeader = this.element.appendChild(document.createElement("header"));

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/TimelineTabContentView.js (241218 => 241219)


--- trunk/Source/WebInspectorUI/UserInterface/Views/TimelineTabContentView.js	2019-02-09 00:24:24 UTC (rev 241218)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/TimelineTabContentView.js	2019-02-09 00:25:46 UTC (rev 241219)
@@ -277,6 +277,8 @@
         case WI.TimelineRecord.Type.RenderingFrame:
             return WI.UIString("Frame %d").format(timelineRecord.frameNumber);
         case WI.TimelineRecord.Type.HeapAllocations:
+            if (timelineRecord.heapSnapshot.imported)
+                return WI.UIString("Imported \u2014 %s").format(timelineRecord.heapSnapshot.title);
             if (timelineRecord.heapSnapshot.title)
                 return WI.UIString("Snapshot %d \u2014 %s").format(timelineRecord.heapSnapshot.identifier, timelineRecord.heapSnapshot.title);
             return WI.UIString("Snapshot %d").format(timelineRecord.heapSnapshot.identifier);

Modified: trunk/Source/WebInspectorUI/UserInterface/Workers/HeapSnapshot/HeapSnapshot.js (241218 => 241219)


--- trunk/Source/WebInspectorUI/UserInterface/Workers/HeapSnapshot/HeapSnapshot.js	2019-02-09 00:24:24 UTC (rev 241218)
+++ trunk/Source/WebInspectorUI/UserInterface/Workers/HeapSnapshot/HeapSnapshot.js	2019-02-09 00:25:46 UTC (rev 241219)
@@ -36,6 +36,7 @@
 const nodeSizeOffset = 1;
 const nodeClassNameOffset = 2;
 const nodeInternalOffset = 3;
+const gcDebuggingNodeFieldCount = 7;
 
 // edges
 // [<0:fromId>, <1:toId>, <2:typeTableIndex>, <3:edgeDataIndexOrEdgeNameIndex>]
@@ -73,11 +74,12 @@
 
 HeapSnapshot = class HeapSnapshot
 {
-    constructor(objectId, snapshotDataString, title = null)
+    constructor(objectId, snapshotDataString, title = null, imported = false)
     {
         this._identifier = nextSnapshotIdentifier++;
         this._objectId = objectId;
         this._title = title;
+        this._imported = imported;
 
         let json = JSON.parse(snapshotDataString);
         snapshotDataString = null;
@@ -84,10 +86,12 @@
 
         let {version, type, nodes, nodeClassNames, edges, edgeTypes, edgeNames} = json;
         console.assert(version === 1, "Expect _javascript_Core Heap Snapshot version 1");
-        console.assert(!type || type === "Inspector", "Expect an Inspector Heap Snapshot");
+        console.assert(!type || (type === "Inspector" || type === "GCDebugging"), "Expect an Inspector / GCDebugging Heap Snapshot");
 
+        this._nodeFieldCount = type === "GCDebugging" ? gcDebuggingNodeFieldCount : nodeFieldCount;
+
         this._nodes = nodes;
-        this._nodeCount = nodes.length / nodeFieldCount;
+        this._nodeCount = nodes.length / this._nodeFieldCount;
 
         this._edges = edges;
         this._edgeCount = edges.length / edgeFieldCount;
@@ -99,8 +103,8 @@
         this._totalSize = 0;
         this._nodeIdentifierToOrdinal = new Map; // <node identifier> => nodeOrdinal
         this._lastNodeIdentifier = 0;
-        for (let nodeIndex = 0; nodeIndex < nodes.length; nodeIndex += nodeFieldCount) {
-            let nodeOrdinal = nodeIndex / nodeFieldCount;
+        for (let nodeIndex = 0; nodeIndex < nodes.length; nodeIndex += this._nodeFieldCount) {
+            let nodeOrdinal = nodeIndex / this._nodeFieldCount;
             let nodeIdentifier = nodes[nodeIndex + nodeIdOffset];
             this._nodeIdentifierToOrdinal.set(nodeIdentifier, nodeOrdinal);
             this._totalSize += nodes[nodeIndex + nodeSizeOffset];
@@ -151,9 +155,9 @@
         let nodeOrdinalIsDead = snapshot._nodeOrdinalIsDead;
 
         // Skip the <root> node.
-        let firstNodeIndex = nodeFieldCount;
+        let firstNodeIndex = snapshot._nodeFieldCount;
         let firstNodeOrdinal = 1;
-        for (let nodeIndex = firstNodeIndex, nodeOrdinal = firstNodeOrdinal; nodeIndex < nodes.length; nodeIndex += nodeFieldCount, nodeOrdinal++) {
+        for (let nodeIndex = firstNodeIndex, nodeOrdinal = firstNodeOrdinal; nodeIndex < nodes.length; nodeIndex += snapshot._nodeFieldCount, nodeOrdinal++) {
             if (allowNodeIdentifierCallback && !allowNodeIdentifierCallback(nodes[nodeIndex + nodeIdOffset]))
                 continue;
 
@@ -191,10 +195,10 @@
         let nodes = snapshot._nodes;
 
         // Skip the <root> node.
-        let firstNodeIndex = nodeFieldCount;
+        let firstNodeIndex = snapshot._nodeFieldCount;
 
     outer:
-        for (let nodeIndex = firstNodeIndex; nodeIndex < nodes.length; nodeIndex += nodeFieldCount) {
+        for (let nodeIndex = firstNodeIndex; nodeIndex < nodes.length; nodeIndex += snapshot._nodeFieldCount) {
             if (allowNodeIdentifierCallback && !allowNodeIdentifierCallback(nodes[nodeIndex + nodeIdOffset]))
                 continue;
 
@@ -219,9 +223,9 @@
         let nodeClassNamesTable = snapshot._nodeClassNamesTable;
 
         // Skip the <root> node.
-        let firstNodeIndex = nodeFieldCount;
+        let firstNodeIndex = snapshot._nodeFieldCount;
         let firstNodeOrdinal = 1;
-        for (let nodeIndex = firstNodeIndex, nodeOrdinal = firstNodeOrdinal; nodeIndex < nodes.length; nodeIndex += nodeFieldCount, nodeOrdinal++) {
+        for (let nodeIndex = firstNodeIndex, nodeOrdinal = firstNodeOrdinal; nodeIndex < nodes.length; nodeIndex += snapshot._nodeFieldCount, nodeOrdinal++) {
             if (allowNodeIdentifierCallback && !allowNodeIdentifierCallback(nodes[nodeIndex + nodeIdOffset]))
                 continue;
 
@@ -253,7 +257,7 @@
     nodeWithIdentifier(nodeIdentifier)
     {
         let nodeOrdinal = this._nodeIdentifierToOrdinal.get(nodeIdentifier);
-        let nodeIndex = nodeOrdinal * nodeFieldCount;
+        let nodeIndex = nodeOrdinal * this._nodeFieldCount;
         return this.serializeNode(nodeIndex);
     }
 
@@ -297,7 +301,7 @@
         let targetNodeOrdinal = this._nodeIdentifierToOrdinal.get(nodeIdentifier);
         for (let nodeOrdinal = 0; nodeOrdinal < this._nodeCount; ++nodeOrdinal) {
             if (this._nodeOrdinalToDominatorNodeOrdinal[nodeOrdinal] === targetNodeOrdinal)
-                dominatedNodes.push(nodeOrdinal * nodeFieldCount);
+                dominatedNodes.push(nodeOrdinal * this._nodeFieldCount);
         }
 
         return dominatedNodes.map(this.serializeNode, this);
@@ -313,7 +317,7 @@
         for (; this._edges[edgeIndex + edgeFromIdOffset] === nodeIdentifier; edgeIndex += edgeFieldCount) {
             let toNodeIdentifier = this._edges[edgeIndex + edgeToIdOffset];
             let toNodeOrdinal = this._nodeIdentifierToOrdinal.get(toNodeIdentifier);
-            let toNodeIndex = toNodeOrdinal * nodeFieldCount;
+            let toNodeIndex = toNodeOrdinal * this._nodeFieldCount;
             retainedNodes.push(toNodeIndex);
             edges.push(edgeIndex);
         }
@@ -334,7 +338,7 @@
         let incomingEdgeIndexEnd = this._nodeOrdinalToFirstIncomingEdge[nodeOrdinal + 1];
         for (let edgeIndex = incomingEdgeIndex; edgeIndex < incomingEdgeIndexEnd; ++edgeIndex) {
             let fromNodeOrdinal = this._incomingNodes[edgeIndex];
-            let fromNodeIndex = fromNodeOrdinal * nodeFieldCount;
+            let fromNodeIndex = fromNodeOrdinal * this._nodeFieldCount;
             retainers.push(fromNodeIndex);
             edges.push(this._incomingEdges[edgeIndex]);
         }
@@ -347,6 +351,9 @@
 
     updateDeadNodesAndGatherCollectionData(snapshots)
     {
+        console.assert(!this._imported, "Should never use an imported snapshot to modify snapshots");
+        console.assert(snapshots.every((x) => !x._imported), "Should never modify nodes of imported snapshots");
+
         let previousSnapshotIndex = snapshots.indexOf(this) - 1;
         let previousSnapshot = snapshots[previousSnapshotIndex];
         if (!previousSnapshot)
@@ -356,7 +363,7 @@
 
         // All of the node identifiers that could have existed prior to this snapshot.
         let known = new Map;
-        for (let nodeIndex = 0; nodeIndex < this._nodes.length; nodeIndex += nodeFieldCount) {
+        for (let nodeIndex = 0; nodeIndex < this._nodes.length; nodeIndex += this._nodeFieldCount) {
             let nodeIdentifier = this._nodes[nodeIndex + nodeIdOffset];
             if (nodeIdentifier > lastNodeIdentifier)
                 continue;
@@ -365,7 +372,7 @@
 
         // Determine which node identifiers have since been deleted.
         let collectedNodesList = [];
-        for (let nodeIndex = 0; nodeIndex < previousSnapshot._nodes.length; nodeIndex += nodeFieldCount) {
+        for (let nodeIndex = 0; nodeIndex < previousSnapshot._nodes.length; nodeIndex += this._nodeFieldCount) {
             let nodeIdentifier = previousSnapshot._nodes[nodeIndex + nodeIdOffset];
             let wasDeleted = !known.has(nodeIdentifier);
             if (wasDeleted)
@@ -403,20 +410,21 @@
             totalObjectCount: this._nodeCount - 1, // <root>.
             liveSize: this._liveSize,
             categories: this._categories,
+            imported: this._imported,
         };
     }
 
     serializeNode(nodeIndex)
     {
-        console.assert((nodeIndex % nodeFieldCount) === 0, "Invalid nodeIndex to serialize");
+        console.assert((nodeIndex % this._nodeFieldCount) === 0, "Invalid nodeIndex to serialize");
 
         let nodeIdentifier = this._nodes[nodeIndex + nodeIdOffset];
-        let nodeOrdinal = nodeIndex / nodeFieldCount;
+        let nodeOrdinal = nodeIndex / this._nodeFieldCount;
         let edgeIndex = this._nodeOrdinalToFirstOutgoingEdge[nodeOrdinal];
         let hasChildren = this._edges[edgeIndex + edgeFromIdOffset] === nodeIdentifier;
 
         let dominatorNodeOrdinal = this._nodeOrdinalToDominatorNodeOrdinal[nodeOrdinal];
-        let dominatorNodeIndex = dominatorNodeOrdinal * nodeFieldCount;
+        let dominatorNodeIndex = dominatorNodeOrdinal * this._nodeFieldCount;
         let dominatorNodeIdentifier = this._nodes[dominatorNodeIndex + nodeIdOffset];
 
         return {
@@ -533,7 +541,7 @@
 
         while (stackTop >= 0) {
             let nodeOrdinal = stackNodes[stackTop];
-            let nodeIdentifier = this._nodes[(nodeOrdinal * nodeFieldCount) + nodeIdOffset];
+            let nodeIdentifier = this._nodes[(nodeOrdinal * this._nodeFieldCount) + nodeIdOffset];
             let edgeIndex = stackEdges[stackTop];
 
             if (this._edges[edgeIndex + edgeFromIdOffset] === nodeIdentifier) {
@@ -657,7 +665,7 @@
                     changed = true;
 
                     let outgoingEdgeIndex = this._nodeOrdinalToFirstOutgoingEdge[nodeOrdinal];
-                    let nodeIdentifier = this._nodes[(nodeOrdinal * nodeFieldCount) + nodeIdOffset];
+                    let nodeIdentifier = this._nodes[(nodeOrdinal * this._nodeFieldCount) + nodeIdOffset];
                     for (let edgeIndex = outgoingEdgeIndex; this._edges[edgeIndex + edgeFromIdOffset] === nodeIdentifier; edgeIndex += edgeFieldCount) {
                         let toNodeIdentifier = this._edges[edgeIndex + edgeToIdOffset];
                         let toNodeOrdinal = this._nodeIdentifierToOrdinal.get(toNodeIdentifier);
@@ -678,7 +686,7 @@
     _buildRetainedSizes(postOrderIndexToNodeOrdinal)
     {
         // Self size.
-        for (let nodeIndex = 0, nodeOrdinal = 0; nodeOrdinal < this._nodeCount; nodeIndex += nodeFieldCount, nodeOrdinal++)
+        for (let nodeIndex = 0, nodeOrdinal = 0; nodeOrdinal < this._nodeCount; nodeIndex += this._nodeFieldCount, nodeOrdinal++)
             this._nodeOrdinalToRetainedSizes[nodeOrdinal] = this._nodes[nodeIndex + nodeSizeOffset];
 
         // Attribute size to dominator.
@@ -731,7 +739,7 @@
         {
             if (this._nodeOrdinalIsGCRoot[nodeOrdinal]) {
                 let fullPath = currentPath.slice();
-                let nodeIndex = nodeOrdinal * nodeFieldCount;
+                let nodeIndex = nodeOrdinal * this._nodeFieldCount;
                 fullPath.push({node: nodeIndex});
                 paths.push(fullPath);
                 return;
@@ -741,7 +749,7 @@
                 return;
             visited[nodeOrdinal] = 1;
 
-            let nodeIndex = nodeOrdinal * nodeFieldCount;
+            let nodeIndex = nodeOrdinal * this._nodeFieldCount;
             currentPath.push({node: nodeIndex});
 
             // Loop in reverse order because edges were added in reverse order.
@@ -750,7 +758,7 @@
             let incomingEdgeIndexEnd = this._nodeOrdinalToFirstIncomingEdge[nodeOrdinal + 1];
             for (let incomingEdgeIndex = incomingEdgeIndexEnd - 1; incomingEdgeIndex >= incomingEdgeIndexStart; --incomingEdgeIndex) {
                 let fromNodeOrdinal = this._incomingNodes[incomingEdgeIndex];
-                let fromNodeIndex = fromNodeOrdinal * nodeFieldCount;
+                let fromNodeIndex = fromNodeOrdinal * this._nodeFieldCount;
                 let fromNodeIsInternal = this._nodes[fromNodeIndex + nodeInternalOffset];
                 if (fromNodeIsInternal)
                     continue;

Modified: trunk/Source/WebInspectorUI/UserInterface/Workers/HeapSnapshot/HeapSnapshotWorker.js (241218 => 241219)


--- trunk/Source/WebInspectorUI/UserInterface/Workers/HeapSnapshot/HeapSnapshotWorker.js	2019-02-09 00:24:24 UTC (rev 241218)
+++ trunk/Source/WebInspectorUI/UserInterface/Workers/HeapSnapshot/HeapSnapshotWorker.js	2019-02-09 00:25:46 UTC (rev 241219)
@@ -47,20 +47,23 @@
         this._snapshots = [];
     }
 
-    createSnapshot(snapshotString, title)
+    createSnapshot(snapshotString, title, imported)
     {
         let objectId = this._nextObjectId++;
-        let snapshot = new HeapSnapshot(objectId, snapshotString, title);
-        this._snapshots.push(snapshot);
+        let snapshot = new HeapSnapshot(objectId, snapshotString, title, imported);
         this._objects.set(objectId, snapshot);
 
-        if (this._snapshots.length > 1) {
-            setTimeout(() => {
-                let collectionData = snapshot.updateDeadNodesAndGatherCollectionData(this._snapshots);
-                if (!collectionData || !collectionData.affectedSnapshots.length)
-                    return;
-                this.sendEvent("HeapSnapshot.CollectionEvent", collectionData);
-            }, 0);
+        if (!imported) {
+            this._snapshots.push(snapshot);
+
+            if (this._snapshots.length > 1) {
+                setTimeout(() => {
+                    let collectionData = snapshot.updateDeadNodesAndGatherCollectionData(this._snapshots);
+                    if (!collectionData || !collectionData.affectedSnapshots.length)
+                        return;
+                    this.sendEvent("HeapSnapshot.CollectionEvent", collectionData);
+                }, 0);
+            }            
         }
 
         return {objectId, snapshot: snapshot.serialize()};
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to