Web Inspector: HeapSnapshots are slow and use too much memory
authorjoepeck@webkit.org <joepeck@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 17 Mar 2016 21:02:07 +0000 (21:02 +0000)
committerjoepeck@webkit.org <joepeck@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 17 Mar 2016 21:02:07 +0000 (21:02 +0000)
https://bugs.webkit.org/show_bug.cgi?id=155571

Reviewed by Timothy Hatcher.

Source/WebInspectorUI:

This is the first inclusion of Workers into Web Inspector. In this case
the Main side merely needs to make requests of the Worker and get back
objects that it can interact with more.

New file heirarchies:

    UserInterface/Proxies
        - new Proxy classes in the Main page.
        - treat like Model classes, but not quite model.

    UserInterface/Workers/HeapSnapshotWorker
        - new Worker classes for Workers. No WebInspector namespace.
        - no minification of these resources, they are simply copied.

Remote procedure call interface between the Main/Worker page happens
through the WorkerProxy and Worker classes. There are simple ways
to perform factory style methods and call methods on objects, and
get the result in a callback. Similiar to frontend <-> backend agent
communication:

    HeapSnapshotWorkerProxy: (Main world)
        - creates the worker
        - performAction("actionName", arguments, callback)
        - callMethod(objectId, "methodName", arguments, callback)
        - handle message => dispatch event or invoke callback

    HeapSnapshotWorker: (Worker world)
        - sendEvent("eventName", eventData)
        - handle message => dispatch action or method on object

Proxy object methods are boilerplate calls to performAction/callMethod
with deserialization of responses. The rest of the frontend can just
treat Proxy objects as Model objects with some data and async methods.

Because the Node/Edge data is so small, objects are cheaply created
when needed and not cached. This means that there may be duplicate
HeapSnapshotNode's for the same node. For example if different Views
both request instancesWithClassName("Foo"). This is fine, as none
of our Views really care about object uniqueness, they are only
interested in the data or querying for more data.

* Scripts/combine-resources.pl:
* Scripts/copy-user-interface-resources.pl:
Copy the Workers directory to the resources directory.
Its code is only meant to be loaded by Workers, so it
shouldn't be included in the Main page.

* UserInterface/Main.html:
* UserInterface/Test.html:
* UserInterface/Models/HeapSnapshot.js: Removed.
* UserInterface/Models/HeapSnapshotDiff.js: Removed.
* UserInterface/Models/HeapSnapshotEdge.js: Removed.
* UserInterface/Models/HeapSnapshotNode.js: Removed.
Replace the old simple Model classes with Proxy classes that interact
with the Worker.

* UserInterface/Models/HeapAllocationsInstrument.js:
(WebInspector.HeapAllocationsInstrument.prototype._takeHeapSnapshot):
(WebInspector.HeapAllocationsInstrument):
* UserInterface/Models/HeapAllocationsTimelineRecord.js:
(WebInspector.HeapAllocationsTimelineRecord):
* UserInterface/Models/HeapSnapshotRootPath.js:
(WebInspector.HeapSnapshotRootPath):
(WebInspector.HeapSnapshotRootPath.prototype.appendEdge):
* UserInterface/Protocol/HeapObserver.js:
(WebInspector.HeapObserver.prototype.trackingStart):
(WebInspector.HeapObserver.prototype.trackingComplete):
* UserInterface/Views/ContentView.js:
(WebInspector.ContentView.createFromRepresentedObject):
(WebInspector.ContentView.isViewable):
* UserInterface/Views/HeapAllocationsTimelineView.js:
(WebInspector.HeapAllocationsTimelineView.prototype.showHeapSnapshotDiff):
(WebInspector.HeapAllocationsTimelineView.prototype._takeHeapSnapshotClicked):
(WebInspector.HeapAllocationsTimelineView.prototype._dataGridNodeSelected):
(WebInspector.HeapAllocationsTimelineView):
* UserInterface/Views/HeapSnapshotClassDataGridNode.js:
(WebInspector.HeapSnapshotClassDataGridNode.prototype._populate):
* UserInterface/Views/HeapSnapshotClusterContentView.js:
* UserInterface/Views/HeapSnapshotInstanceDataGridNode.js:
(WebInspector.HeapSnapshotInstanceDataGridNode):
(WebInspector.HeapSnapshotInstanceDataGridNode.logHeapSnapshotNode.node.shortestGCRootPath.):
(WebInspector.HeapSnapshotInstanceDataGridNode.logHeapSnapshotNode):
(WebInspector.HeapSnapshotInstanceDataGridNode.prototype._mouseoverHandler.appendPath):
(WebInspector.HeapSnapshotInstanceDataGridNode.prototype._mouseoverHandler.stringifyEdge):
(WebInspector.HeapSnapshotInstanceDataGridNode.prototype._mouseoverHandler):
* UserInterface/Views/HeapSnapshotInstancesContentView.js:
(WebInspector.HeapSnapshotInstancesContentView):
* UserInterface/Views/HeapSnapshotInstancesDataGridTree.js:
(WebInspector.HeapSnapshotInstancesDataGridTree):
* UserInterface/Views/HeapSnapshotSummaryContentView.js:
(WebInspector.HeapSnapshotSummaryContentView):
Update existing code to expect the new Proxy objects or create
the new HeapSnapshot using workers.

* UserInterface/Proxies/HeapSnapshotDiffProxy.js: Added.
(WebInspector.HeapSnapshotDiffProxy):
(WebInspector.HeapSnapshotDiffProxy.deserialize):
(WebInspector.HeapSnapshotDiffProxy.prototype.get snapshot1):
(WebInspector.HeapSnapshotDiffProxy.prototype.get snapshot2):
(WebInspector.HeapSnapshotDiffProxy.prototype.get totalSize):
(WebInspector.HeapSnapshotDiffProxy.prototype.get totalObjectCount):
(WebInspector.HeapSnapshotDiffProxy.prototype.get categories):
(WebInspector.HeapSnapshotDiffProxy.prototype.allocationBucketCounts):
(WebInspector.HeapSnapshotDiffProxy.prototype.instancesWithClassName):
(WebInspector.HeapSnapshotDiffProxy.prototype.nodeWithIdentifier):
A HeapSnapshotDiffProxy looks like a HeapSnapshotProxy and responds to
the same methods, but has the extra snapshot1/2 pointers.

* UserInterface/Proxies/HeapSnapshotEdgeProxy.js:
(WebInspector.HeapSnapshotEdgeProxy):
(WebInspector.HeapSnapshotEdgeProxy.deserialize):
Edge data. No methods are proxied at this point.

* UserInterface/Proxies/HeapSnapshotNodeProxy.js: Added.
(WebInspector.HeapSnapshotNodeProxy):
(WebInspector.HeapSnapshotNodeProxy.deserialize):
(WebInspector.HeapSnapshotNodeProxy.prototype.shortestGCRootPath):
(WebInspector.HeapSnapshotNodeProxy.prototype.dominatedNodes):
(WebInspector.HeapSnapshotNodeProxy.prototype.retainedNodes):
(WebInspector.HeapSnapshotNodeProxy.prototype.retainers):
Node data and methods to query for node relationships.

* UserInterface/Proxies/HeapSnapshotProxy.js: Added.
(WebInspector.HeapSnapshotProxy):
(WebInspector.HeapSnapshotProxy.deserialize):
(WebInspector.HeapSnapshotProxy.prototype.get proxyObjectId):
(WebInspector.HeapSnapshotProxy.prototype.get identifier):
(WebInspector.HeapSnapshotProxy.prototype.get totalSize):
(WebInspector.HeapSnapshotProxy.prototype.get totalObjectCount):
(WebInspector.HeapSnapshotProxy.prototype.get categories):
(WebInspector.HeapSnapshotProxy.prototype.allocationBucketCounts):
(WebInspector.HeapSnapshotProxy.prototype.instancesWithClassName):
(WebInspector.HeapSnapshotProxy.prototype.nodeWithIdentifier):
Snapshot data and methods to query for nodes.

* UserInterface/Proxies/HeapSnapshotWorkerProxy.js: Added.
(WebInspector.HeapSnapshotWorkerProxy):
(WebInspector.HeapSnapshotWorkerProxy.singleton):
(WebInspector.HeapSnapshotWorkerProxy.prototype.createSnapshot):
(WebInspector.HeapSnapshotWorkerProxy.prototype.createSnapshotDiff):
(WebInspector.HeapSnapshotWorkerProxy.prototype.performAction):
(WebInspector.HeapSnapshotWorkerProxy.prototype.callMethod):
(WebInspector.HeapSnapshotWorkerProxy.prototype._postMessage):
(WebInspector.HeapSnapshotWorkerProxy.prototype._handleMessage):
Singleton factory for the worker and proxied communication with the worker.
Provide means for invoking "factory actions" and "object methods".

* UserInterface/Workers/HeapSnapshot/HeapSnapshotWorker.js: Added.
(HeapSnapshotWorker):
(HeapSnapshotWorker.prototype.createSnapshot):
(HeapSnapshotWorker.prototype.createSnapshotDiff):
(HeapSnapshotWorker.prototype.sendEvent):
(HeapSnapshotWorker.prototype._handleMessage):
Main worker code. Handle dispatching actions and methods.

* UserInterface/Workers/HeapSnapshot/HeapSnapshot.js: Added.
(HeapSnapshot):
(HeapSnapshot.buildCategories):
(HeapSnapshot.allocationBucketCounts):
(HeapSnapshot.instancesWithClassName):
(HeapSnapshot.prototype.allocationBucketCounts):
(HeapSnapshot.prototype.instancesWithClassName):
(HeapSnapshot.prototype.nodeWithIdentifier):
(HeapSnapshot.prototype.shortestGCRootPath):
(HeapSnapshot.prototype.dominatedNodes):
(HeapSnapshot.prototype.retainedNodes):
(HeapSnapshot.prototype.retainers):
(HeapSnapshot.prototype.serialize):
(HeapSnapshot.prototype.serializeNode):
(HeapSnapshot.prototype.serializeEdge):
(HeapSnapshot.prototype._buildOutgoingEdges):
(HeapSnapshot.prototype._buildIncomingEdges):
(HeapSnapshot.prototype._buildPostOrderIndexes):
(HeapSnapshot.prototype._buildDominatorIndexes):
(HeapSnapshot.prototype._buildRetainedSizes):
(HeapSnapshot.prototype._gcRootPathes.visitNode):
(HeapSnapshot.prototype._gcRootPathes):
(HeapSnapshotDiff):
(HeapSnapshotDiff.prototype.allocationBucketCounts):
(HeapSnapshotDiff.prototype.instancesWithClassName):
(HeapSnapshotDiff.prototype.nodeWithIdentifier):
(HeapSnapshotDiff.prototype.shortestGCRootPath):
(HeapSnapshotDiff.prototype.dominatedNodes):
(HeapSnapshotDiff.prototype.retainedNodes):
(HeapSnapshotDiff.prototype.retainers):
(HeapSnapshotDiff.prototype.serialize):
New HeapSnapshot data processing implementation. Instead of creating
a new object per Node or per Edge create data arrays containing data
per-Node. Operate on these lists of data instead of creating many objects.

LayoutTests:

* inspector/heap/getPreview.html:
* inspector/heap/getRemoteObject.html:
* inspector/heap/snapshot.html:
Update tests to use the new HeapSnapshotWorker frontend code.

* inspector/unit-tests/heap-snapshot-expected.txt: Added.
* inspector/unit-tests/heap-snapshot.html: Added.
Verify the data processing in and worker communication work HeapSnapshotWorker
produces expected values when compared with the simple HeapSnapshot/Node/Edge
implentation.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@198353 268f45cc-cd09-0410-ab3c-d52691b4dbfc

33 files changed:
LayoutTests/ChangeLog
LayoutTests/inspector/heap/getPreview.html
LayoutTests/inspector/heap/getRemoteObject.html
LayoutTests/inspector/heap/snapshot.html
LayoutTests/inspector/unit-tests/heap-snapshot-expected.txt [new file with mode: 0644]
LayoutTests/inspector/unit-tests/heap-snapshot.html [new file with mode: 0644]
Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/Scripts/combine-resources.pl
Source/WebInspectorUI/Scripts/copy-user-interface-resources.pl
Source/WebInspectorUI/UserInterface/Main.html
Source/WebInspectorUI/UserInterface/Models/HeapAllocationsInstrument.js
Source/WebInspectorUI/UserInterface/Models/HeapAllocationsTimelineRecord.js
Source/WebInspectorUI/UserInterface/Models/HeapSnapshot.js [deleted file]
Source/WebInspectorUI/UserInterface/Models/HeapSnapshotDiff.js [deleted file]
Source/WebInspectorUI/UserInterface/Models/HeapSnapshotNode.js [deleted file]
Source/WebInspectorUI/UserInterface/Models/HeapSnapshotRootPath.js
Source/WebInspectorUI/UserInterface/Protocol/HeapObserver.js
Source/WebInspectorUI/UserInterface/Proxies/HeapSnapshotDiffProxy.js [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Proxies/HeapSnapshotEdgeProxy.js [moved from Source/WebInspectorUI/UserInterface/Models/HeapSnapshotEdge.js with 72% similarity]
Source/WebInspectorUI/UserInterface/Proxies/HeapSnapshotNodeProxy.js [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Proxies/HeapSnapshotProxy.js [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Proxies/HeapSnapshotWorkerProxy.js [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Test.html
Source/WebInspectorUI/UserInterface/Views/ContentView.js
Source/WebInspectorUI/UserInterface/Views/HeapAllocationsTimelineView.js
Source/WebInspectorUI/UserInterface/Views/HeapSnapshotClassDataGridNode.js
Source/WebInspectorUI/UserInterface/Views/HeapSnapshotClusterContentView.js
Source/WebInspectorUI/UserInterface/Views/HeapSnapshotInstanceDataGridNode.js
Source/WebInspectorUI/UserInterface/Views/HeapSnapshotInstancesContentView.js
Source/WebInspectorUI/UserInterface/Views/HeapSnapshotInstancesDataGridTree.js
Source/WebInspectorUI/UserInterface/Views/HeapSnapshotSummaryContentView.js
Source/WebInspectorUI/UserInterface/Workers/HeapSnapshot/HeapSnapshot.js [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Workers/HeapSnapshot/HeapSnapshotWorker.js [new file with mode: 0644]

index 0496453..77f3140 100644 (file)
@@ -1,3 +1,21 @@
+2016-03-17  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Web Inspector: HeapSnapshots are slow and use too much memory
+        https://bugs.webkit.org/show_bug.cgi?id=155571
+
+        Reviewed by Timothy Hatcher.
+
+        * inspector/heap/getPreview.html:
+        * inspector/heap/getRemoteObject.html:
+        * inspector/heap/snapshot.html:
+        Update tests to use the new HeapSnapshotWorker frontend code.
+
+        * inspector/unit-tests/heap-snapshot-expected.txt: Added.
+        * inspector/unit-tests/heap-snapshot.html: Added.
+        Verify the data processing in and worker communication work HeapSnapshotWorker
+        produces expected values when compared with the simple HeapSnapshot/Node/Edge
+        implentation.
+
 2016-03-17  Ryan Haddad  <ryanhaddad@apple.com>
 
         Marking http/tests/security/aboutBlank/window-open-self-about-blank.html as flaky on ios-sim-debug
index d9fa296..1061386 100644 (file)
@@ -52,15 +52,17 @@ function test()
                 InspectorTest.evaluateInPage("triggerCreateStringObject()");
                 HeapAgent.snapshot((error, timestamp, snapshotStringData) => { // Newly created objects.
                     InspectorTest.expectThat(!error, "Should not have an error creating a snapshot.");
-                    let payload = JSON.parse(snapshotStringData);
-                    let snapshot = WebInspector.HeapSnapshot.fromPayload(payload);
-
-                    let strings = snapshot.instancesWithClassName("string");
-                    let heapSnapshotNode = strings.reduce((result, x) => result.id < x.id ? x : result, strings[0]);
-                    HeapAgent.getPreview(heapSnapshotNode.id, (error, string, functionDetails, objectPreviewPayload) => {
-                        InspectorTest.expectThat(!error, "Should not have an error getting preview.");
-                        InspectorTest.log("STRING: " + string);
-                        resolve();
+                    let workerProxy = WebInspector.HeapSnapshotWorkerProxy.singleton();
+                    workerProxy.createSnapshot(snapshotStringData, ({objectId, snapshot: serializedSnapshot}) => {
+                        let snapshot = WebInspector.HeapSnapshotProxy.deserialize(objectId, serializedSnapshot);
+                        snapshot.instancesWithClassName("string", (strings) => {
+                            let heapSnapshotNode = strings.reduce((result, x) => result.id < x.id ? x : result, strings[0]);
+                            HeapAgent.getPreview(heapSnapshotNode.id, (error, string, functionDetails, objectPreviewPayload) => {
+                                InspectorTest.expectThat(!error, "Should not have an error getting preview.");
+                                InspectorTest.log("STRING: " + string);
+                                resolve();
+                            });
+                        });
                     });
                 });
             });
@@ -75,15 +77,17 @@ function test()
                 InspectorTest.evaluateInPage("triggerCreateFunctionObject()");
                 HeapAgent.snapshot((error, timestamp, snapshotStringData) => { // Newly created objects.
                     InspectorTest.expectThat(!error, "Should not have an error creating a snapshot.");
-                    let payload = JSON.parse(snapshotStringData);
-                    let snapshot = WebInspector.HeapSnapshot.fromPayload(payload);
-
-                    let functions = snapshot.instancesWithClassName("Function");
-                    let heapSnapshotNode = functions.reduce((result, x) => result.id < x.id ? x : result, functions[0]);
-                    HeapAgent.getPreview(heapSnapshotNode.id, (error, string, functionDetails, objectPreviewPayload) => {
-                        InspectorTest.expectThat(!error, "Should not have an error getting preview.");
-                        InspectorTest.log("FUNCTION DETAILS: " + JSON.stringify(functionDetails, jsonFilter, 4));
-                        resolve();
+                    let workerProxy = WebInspector.HeapSnapshotWorkerProxy.singleton();
+                    workerProxy.createSnapshot(snapshotStringData, ({objectId, snapshot: serializedSnapshot}) => {
+                        let snapshot = WebInspector.HeapSnapshotProxy.deserialize(objectId, serializedSnapshot);
+                        snapshot.instancesWithClassName("Function", (functions) => {
+                            let heapSnapshotNode = functions.reduce((result, x) => result.id < x.id ? x : result, functions[0]);
+                            HeapAgent.getPreview(heapSnapshotNode.id, (error, string, functionDetails, objectPreviewPayload) => {
+                                InspectorTest.expectThat(!error, "Should not have an error getting preview.");
+                                InspectorTest.log("FUNCTION DETAILS: " + JSON.stringify(functionDetails, jsonFilter, 4));
+                                resolve();
+                            });
+                        });
                     });
                 });
             });
@@ -98,15 +102,17 @@ function test()
                 InspectorTest.evaluateInPage("triggerCreateMapObject()");
                 HeapAgent.snapshot((error, timestamp, snapshotStringData) => { // Newly created objects.
                     InspectorTest.expectThat(!error, "Should not have an error creating a snapshot.");
-                    let payload = JSON.parse(snapshotStringData);
-                    let snapshot = WebInspector.HeapSnapshot.fromPayload(payload);
-
-                    let maps = snapshot.instancesWithClassName("Map");
-                    let heapSnapshotNode = maps.reduce((result, x) => result.id < x.id ? x : result, maps[0]);
-                    HeapAgent.getPreview(heapSnapshotNode.id, (error, string, functionDetails, objectPreviewPayload) => {
-                        InspectorTest.expectThat(!error, "Should not have an error getting preview.");
-                        InspectorTest.log("OBJECT PREVIEW: " + JSON.stringify(objectPreviewPayload, jsonFilter, 4));
-                        resolve();
+                    let workerProxy = WebInspector.HeapSnapshotWorkerProxy.singleton();
+                    workerProxy.createSnapshot(snapshotStringData, ({objectId, snapshot: serializedSnapshot}) => {
+                        let snapshot = WebInspector.HeapSnapshotProxy.deserialize(objectId, serializedSnapshot);
+                        snapshot.instancesWithClassName("Map", (maps) => {
+                            let heapSnapshotNode = maps.reduce((result, x) => result.id < x.id ? x : result, maps[0]);
+                            HeapAgent.getPreview(heapSnapshotNode.id, (error, string, functionDetails, objectPreviewPayload) => {
+                                InspectorTest.expectThat(!error, "Should not have an error getting preview.");
+                                InspectorTest.log("OBJECT PREVIEW: " + JSON.stringify(objectPreviewPayload, jsonFilter, 4));
+                                resolve();
+                            });
+                        });
                     });
                 });
             });
@@ -136,18 +142,21 @@ function test()
                 InspectorTest.evaluateInPage("triggerCreateMapObject()");
                 HeapAgent.snapshot((error, timestamp, snapshotStringData) => { // Newly created objects.
                     InspectorTest.expectThat(!error, "Should not have an error creating a snapshot.");
-                    let payload = JSON.parse(snapshotStringData);
-                    let snapshot = WebInspector.HeapSnapshot.fromPayload(payload);
-
-                    InspectorTest.evaluateInPage("triggerDeleteMapObject()");
-                    HeapAgent.gc();
-
-                    let maps = snapshot.instancesWithClassName("Map");
-                    let heapSnapshotNode = maps.reduce((result, x) => result.id < x.id ? x : result, maps[0]);
-                    HeapAgent.getPreview(heapSnapshotNode.id, (error, string, functionDetails, objectPreviewPayload) => {
-                        InspectorTest.expectThat(error, "Should get an error when object has been collected.");
-                        InspectorTest.pass(error);
-                        resolve();
+                    let workerProxy = WebInspector.HeapSnapshotWorkerProxy.singleton();
+                    workerProxy.createSnapshot(snapshotStringData, ({objectId, snapshot: serializedSnapshot}) => {
+                        let snapshot = WebInspector.HeapSnapshotProxy.deserialize(objectId, serializedSnapshot);
+
+                        InspectorTest.evaluateInPage("triggerDeleteMapObject()");
+                        HeapAgent.gc();
+
+                        snapshot.instancesWithClassName("Map", (maps) => {
+                            let heapSnapshotNode = maps.reduce((result, x) => result.id < x.id ? x : result, maps[0]);
+                            HeapAgent.getPreview(heapSnapshotNode.id, (error, string, functionDetails, objectPreviewPayload) => {
+                                InspectorTest.expectThat(error, "Should get an error when object has been collected.");
+                                InspectorTest.pass(error);
+                                resolve();
+                            });
+                        });
                     });
                 });
             });
index ddd7ba0..1edd1f8 100644 (file)
@@ -33,16 +33,20 @@ function test()
         test: (resolve, reject) => {
             HeapAgent.snapshot((error, timestamp, snapshotStringData) => {
                 InspectorTest.expectThat(!error, "Should not have an error creating a snapshot.");
-                let payload = JSON.parse(snapshotStringData);
-                let snapshot = WebInspector.HeapSnapshot.fromPayload(payload);
-                let heapSnapshotNode = snapshot.instancesWithClassName("Window")[0];
-                InspectorTest.expectThat(heapSnapshotNode, "Should should include at least one 'Window' instance.");
+                let workerProxy = WebInspector.HeapSnapshotWorkerProxy.singleton();
+                workerProxy.createSnapshot(snapshotStringData, ({objectId, snapshot: serializedSnapshot}) => {
+                    let snapshot = WebInspector.HeapSnapshotProxy.deserialize(objectId, serializedSnapshot);
+                    snapshot.instancesWithClassName("Window", (windows) => {
+                        let heapSnapshotNode = windows[0];
+                        InspectorTest.expectThat(heapSnapshotNode, "Should should include at least one 'Window' instance.");
 
-                HeapAgent.getRemoteObject(heapSnapshotNode.id, "test", (error, remoteObjectPayload) => {
-                    InspectorTest.expectThat(!error, "Should not have an error getting remote object.");
-                    let remoteObject = WebInspector.RemoteObject.fromPayload(remoteObjectPayload);
-                    InspectorTest.log(remoteObject.description);
-                    resolve();
+                        HeapAgent.getRemoteObject(heapSnapshotNode.id, "test", (error, remoteObjectPayload) => {
+                            InspectorTest.expectThat(!error, "Should not have an error getting remote object.");
+                            let remoteObject = WebInspector.RemoteObject.fromPayload(remoteObjectPayload);
+                            InspectorTest.log(remoteObject.description);
+                            resolve();
+                        });
+                    });
                 });
             });
         }
@@ -71,19 +75,22 @@ function test()
                 InspectorTest.evaluateInPage("triggerCreateMapObject()");
                 HeapAgent.snapshot((error, timestamp, snapshotStringData) => { // Newly created objects.
                     InspectorTest.expectThat(!error, "Should not have an error creating a snapshot.");
-                    let payload = JSON.parse(snapshotStringData);
-                    let snapshot = WebInspector.HeapSnapshot.fromPayload(payload);
-                    let maps = snapshot.instancesWithClassName("Map");
-                    InspectorTest.expectThat(maps.length, "Should should include at least one 'Map' instance.");
+                    let workerProxy = WebInspector.HeapSnapshotWorkerProxy.singleton();
+                    workerProxy.createSnapshot(snapshotStringData, ({objectId, snapshot: serializedSnapshot}) => {
+                        let snapshot = WebInspector.HeapSnapshotProxy.deserialize(objectId, serializedSnapshot);
+                        snapshot.instancesWithClassName("Map", (maps) => {
+                            InspectorTest.expectThat(maps.length, "Should should include at least one 'Map' instance.");
 
-                    InspectorTest.evaluateInPage("triggerDeleteMapObject()");
-                    HeapAgent.gc();
+                            InspectorTest.evaluateInPage("triggerDeleteMapObject()");
+                            HeapAgent.gc();
 
-                    let heapSnapshotNode = maps.reduce((result, x) => result.id < x.id ? x : result, maps[0]);
-                    HeapAgent.getRemoteObject(heapSnapshotNode.id, "test", (error, remoteObjectPayload) => {
-                        InspectorTest.expectThat(error, "Should get an error when object has been collected.");
-                        InspectorTest.pass(error);
-                        resolve();
+                            let heapSnapshotNode = maps.reduce((result, x) => result.id < x.id ? x : result, maps[0]);
+                            HeapAgent.getRemoteObject(heapSnapshotNode.id, "test", (error, remoteObjectPayload) => {
+                                InspectorTest.expectThat(error, "Should get an error when object has been collected.");
+                                InspectorTest.pass(error);
+                                resolve();
+                            });
+                        });
                     });
                 });
             });
index 3916d2a..2248913 100644 (file)
@@ -13,13 +13,17 @@ function test()
         test: (resolve, reject) => {
             HeapAgent.snapshot((error, timestamp, snapshotStringData) => {
                 InspectorTest.expectThat(!error, "Should not have an error creating a snapshot.");
-                let payload = JSON.parse(snapshotStringData);
-                let snapshot = WebInspector.HeapSnapshot.fromPayload(payload);
-                InspectorTest.expectThat(snapshot.totalSize > 1024, "Snapshot size should be greater than 1kb.");
-                InspectorTest.expectThat(snapshot.totalObjectCount > 100, "Snapshot object count should be greater than 100.");
-                InspectorTest.expectThat(snapshot.categories.get("Window"), "Snapshot should include a class category for 'Window'.");
-                InspectorTest.expectThat(snapshot.instancesWithClassName("Window").length > 0, "Snapshot should include at least one 'Window' instance.");
-                resolve();
+                let workerProxy = WebInspector.HeapSnapshotWorkerProxy.singleton();
+                workerProxy.createSnapshot(snapshotStringData, ({objectId, snapshot: serializedSnapshot}) => {
+                    let snapshot = WebInspector.HeapSnapshotProxy.deserialize(objectId, serializedSnapshot);
+                    InspectorTest.expectThat(snapshot.totalSize > 1024, "Snapshot size should be greater than 1kb.");
+                    InspectorTest.expectThat(snapshot.totalObjectCount > 100, "Snapshot object count should be greater than 100.");
+                    InspectorTest.expectThat(snapshot.categories.get("Window"), "Snapshot should include a class category for 'Window'.");
+                    snapshot.instancesWithClassName("Window", (windows) => {
+                        InspectorTest.expectThat(windows.length > 0, "Snapshot should include at least one 'Window' instance.");
+                        resolve();
+                    });
+                });
             });
         }
     });
diff --git a/LayoutTests/inspector/unit-tests/heap-snapshot-expected.txt b/LayoutTests/inspector/unit-tests/heap-snapshot-expected.txt
new file mode 100644 (file)
index 0000000..586c981
--- /dev/null
@@ -0,0 +1,42 @@
+Testing HeapSnapshot Worker and Proxy objects.
+
+
+== Running test suite: HeapSnapshot
+-- Running test case: HeapSnapshotProxy data
+PASS: Should not have an error creating a snapshot.
+PASS: Snapshots totalSize should match.
+PASS: Snapshots totalObjectCount should match.
+
+-- Running test case: HeapSnapshotProxy.prototype.instancesWithClassName
+PASS: Should be at least 1 Window.
+PASS: Window object count is expected.
+PASS: Every className should be 'Window'.
+PASS: Should be at least 1 Function.
+PASS: Function object count is expected.
+PASS: Every className should be 'Function'.
+PASS: Should be at least 1 string.
+PASS: string count is expected.
+PASS: Every className should be 'string'.
+
+-- Running test case: HeapSnapshotProxy.prototype.nodeWithIdentifier and HeapSnapshotNodeProxy data
+PASS: Node className should be 'Window'.
+PASS: Node identifier should match.
+PASS: Node size should match.
+PASS: Node internal state should match.
+PASS: Node gcRoot state should match.
+PASS: Node retainedSize should at least be the size.
+
+-- Running test case: HeapSnapshotProxy.prototype.allocationBucketCounts
+PASS: Result should have 3 buckets, for small/medium/large.
+PASS: Small count should match.
+PASS: Medium count should match.
+PASS: Large count should match.
+
+-- Running test case: HeapSnapshotNodeProxy.prototype.retainedNodes
+PASS: Number of retained nodes should match.
+PASS: Node values should match.
+
+-- Running test case: HeapSnapshotNodeProxy.prototype.retainers
+PASS: Number of retainer nodes should match.
+PASS: Node values should match.
+
diff --git a/LayoutTests/inspector/unit-tests/heap-snapshot.html b/LayoutTests/inspector/unit-tests/heap-snapshot.html
new file mode 100644 (file)
index 0000000..258bb9a
--- /dev/null
@@ -0,0 +1,262 @@
+<!doctype html>
+<html>
+<head>
+<script src="../../http/tests/inspector/resources/inspector-test.js"></script>
+<script>
+function test()
+{
+    // Simple HeapSnapshot representation.
+
+    WebInspector.TestHeapSnapshotNode = class TestHeapSnapshotNode
+    {
+        constructor(identifier, className, size, internal)
+        {
+            this.id = identifier;
+            this.className = className;
+            this.size = size; 
+            this.internal = internal;
+            this.gcRoot = false;
+            this.outgoingEdges = [];
+            this.incomingEdges = [];
+        }
+    }
+
+    WebInspector.TestHeapSnapshotEdge = class TestHeapSnapshotEdge
+    {
+        constructor(from, to, type, data)
+        {
+            this.from = from;
+            this.to = to;
+            this.type = type;
+            this.data = data;
+        }
+    };
+
+    WebInspector.TestHeapSnapshot = class TestHeapSnapshot
+    {
+        constructor(rootNode, nodes, nodeMap)
+        {
+            console.assert(rootNode instanceof WebInspector.TestHeapSnapshotNode);
+            console.assert(nodes.every((n) => n instanceof WebInspector.TestHeapSnapshotNode));
+
+            this.rootNode = rootNode;
+            this.nodes = nodes;
+            this.nodeMap = nodeMap;
+            this.totalSize = nodes.reduce((sum, node) => sum += node.size, 0);
+            this.totalObjectCount = nodes.length;
+        }
+
+        static fromPayload(payload)
+        {
+            let {version, nodes, nodeClassNames, edges, edgeTypes, edgeNames} = payload;
+            console.assert(version === 1, "Only know how to handle JavaScriptCore Heap Snapshot Format Version 1");
+
+            let nodeMap = new Map;
+
+            // Turn nodes into real nodes.
+            let processedNodes = [];
+            for (let i = 0, length = nodes.length; i < length;) {
+                let id = nodes[i++];
+                let size = nodes[i++];
+                let classNameIndex = nodes[i++];
+                let internal = nodes[i++];
+
+                let node = new WebInspector.TestHeapSnapshotNode(id, nodeClassNames[classNameIndex], size, !!internal);
+                nodeMap.set(id, node);
+                processedNodes.push(node);
+            }
+
+            // Turn edges into real edges and set them on the nodes.
+            for (let i = 0, length = edges.length; i < length;) {
+                let fromIdentifier = edges[i++];
+                let toIdentifier = edges[i++];
+                let edgeTypeIndex = edges[i++];
+                let data = edges[i++];
+
+                let from = nodeMap.get(fromIdentifier);
+                let to = nodeMap.get(toIdentifier);
+                let type = edgeTypes[edgeTypeIndex];
+                if (type === "Property" || type === "Variable")
+                    data = edgeNames[data];
+
+                let edge = new WebInspector.TestHeapSnapshotEdge(from, to, type, data);
+                from.outgoingEdges.push(edge);
+                to.incomingEdges.push(edge);
+            }
+
+            // Root node.
+            let rootNode = nodeMap.get(0);
+            console.assert(rootNode, "Node with identifier 0 is the synthetic <root> node.");
+            console.assert(rootNode.outgoingEdges.length > 0, "This had better have children!");
+            console.assert(rootNode.incomingEdges.length === 0, "This had better not have back references!");
+
+            // Mark GC roots.
+            let rootNodeEdges = rootNode.outgoingEdges;
+            for (let i = 0, length = rootNodeEdges.length; i < length; ++i)
+                rootNodeEdges[i].to.gcRoot = true;
+
+            return new WebInspector.TestHeapSnapshot(rootNode, processedNodes, nodeMap);
+        }
+
+        // Public
+
+        instancesWithClassName(className)
+        {
+            let results = [];
+            for (let i = 0; i < this.nodes.length; ++i) {
+                let node = this.nodes[i];
+                if (node.className === className)
+                    results.push(node);
+            }
+            return results;
+        }
+    };
+
+    // ------
+
+    let suite = InspectorTest.createAsyncSuite("HeapSnapshot");
+
+    let snapshot = null;
+    let snapshotNodeForWindowObject = null;
+    let testSnapshot = null;
+    let testSnapshotNodeForWindowObject = null;
+
+    function compareNodes(node1, node2) {
+        return node1.id === node2.id
+            && node1.size === node2.size
+            && node1.className === node2.className
+            && node1.internal === node2.internal
+            && node1.gcRoot === node2.gcRoot;
+    }
+
+    suite.addTestCase({
+        name: "HeapSnapshotProxy data",
+        test: (resolve, reject) => {
+            HeapAgent.snapshot((error, timestamp, snapshotStringData) => {
+                InspectorTest.expectThat(!error, "Should not have an error creating a snapshot.");
+                testSnapshot = WebInspector.TestHeapSnapshot.fromPayload(JSON.parse(snapshotStringData));
+                let workerProxy = WebInspector.HeapSnapshotWorkerProxy.singleton();
+                workerProxy.createSnapshot(snapshotStringData, ({objectId, snapshot: serializedSnapshot}) => {
+                    snapshot = WebInspector.HeapSnapshotProxy.deserialize(objectId, serializedSnapshot);
+                    InspectorTest.assert(testSnapshot, "Created TestHeapSnapshot");
+                    InspectorTest.assert(snapshot, "Created HeapSnapshotProxy");
+                    InspectorTest.expectThat(snapshot.totalSize === testSnapshot.totalSize, "Snapshots totalSize should match.");
+                    InspectorTest.expectThat(snapshot.totalObjectCount === testSnapshot.totalObjectCount, "Snapshots totalObjectCount should match.");
+                    resolve();
+                });
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: "HeapSnapshotProxy.prototype.instancesWithClassName",
+        test: (resolve, reject) => {
+            let windowObjects = testSnapshot.instancesWithClassName("Window")
+            let windowObjectCount = windowObjects.length;
+            let functionObjectCount = testSnapshot.instancesWithClassName("Function").length;
+            let stringCount = testSnapshot.instancesWithClassName("string").length;
+
+            snapshot.instancesWithClassName("Window", (windows) => {
+                testSnapshotNodeForWindowObject = windowObjects[0]; // Used by later tests.
+                snapshotNodeForWindowObject = windows[0]; // Used by later tests.
+
+                InspectorTest.expectThat(windows.length > 0, "Should be at least 1 Window.");
+                InspectorTest.expectThat(windows.length === windowObjectCount, "Window object count is expected.");
+                InspectorTest.expectThat(windows.every((node) => node.className === "Window"), "Every className should be 'Window'.");
+            });
+
+            snapshot.instancesWithClassName("Function", (functions) => {
+                InspectorTest.expectThat(functions.length > 0, "Should be at least 1 Function.");
+                InspectorTest.expectThat(functions.length === functionObjectCount, "Function object count is expected.");
+                InspectorTest.expectThat(functions.every((node) => node.className === "Function"), "Every className should be 'Function'.");
+            });
+
+            snapshot.instancesWithClassName("string", (strings) => {
+                InspectorTest.expectThat(strings.length > 0, "Should be at least 1 string.");
+                InspectorTest.expectThat(strings.length === stringCount, "string count is expected.");
+                InspectorTest.expectThat(strings.every((node) => node.className === "string"), "Every className should be 'string'.");
+                resolve();
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: "HeapSnapshotProxy.prototype.nodeWithIdentifier and HeapSnapshotNodeProxy data",
+        test: (resolve, reject) => {
+            snapshot.nodeWithIdentifier(testSnapshotNodeForWindowObject.id, (heapSnapshotNode) => {
+                InspectorTest.expectThat(heapSnapshotNode.className === "Window", "Node className should be 'Window'.");
+                InspectorTest.expectThat(heapSnapshotNode.id === testSnapshotNodeForWindowObject.id, "Node identifier should match.")
+                InspectorTest.expectThat(heapSnapshotNode.size === testSnapshotNodeForWindowObject.size, "Node size should match.");
+                InspectorTest.expectThat(heapSnapshotNode.internal === testSnapshotNodeForWindowObject.internal, "Node internal state should match.");
+                InspectorTest.expectThat(heapSnapshotNode.gcRoot === testSnapshotNodeForWindowObject.gcRoot, "Node gcRoot state should match.");
+                InspectorTest.expectThat(heapSnapshotNode.retainedSize >= heapSnapshotNode.size, "Node retainedSize should at least be the size.");
+                resolve();
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: "HeapSnapshotProxy.prototype.allocationBucketCounts",
+        test: (resolve, reject) => {
+            let testSmall = 0, testMedium = 0, testLarge = 0;
+            const smallSize = 32, mediumSize = 128;
+            for (let {size} of testSnapshot.nodes) {
+                if (size < smallSize)
+                    testSmall++;
+                else if (size < mediumSize)
+                    testMedium++;
+                else
+                    testLarge++;
+            }
+
+            snapshot.allocationBucketCounts([smallSize, mediumSize], (results) => {
+                let [small, medium, large] = results;
+                InspectorTest.expectThat(results.length === 3, "Result should have 3 buckets, for small/medium/large.");
+                InspectorTest.expectThat(small === testSmall, "Small count should match.");
+                InspectorTest.expectThat(medium === testMedium, "Medium count should match.");
+                InspectorTest.expectThat(large === testLarge, "Large count should match.");
+                resolve();
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: "HeapSnapshotNodeProxy.prototype.retainedNodes",
+        test: (resolve, reject) => {
+            let expectedNodes = testSnapshotNodeForWindowObject.outgoingEdges.map((edge) => edge.to);
+            expectedNodes.sort((a, b) => a.id - b.id);
+
+            snapshotNodeForWindowObject.retainedNodes((nodes) => {
+                nodes.sort((a, b) => a.id - b.id);
+                InspectorTest.assert(nodes.length > 0, "Test only makes since if there are retained nodes");
+                InspectorTest.expectThat(nodes.length === expectedNodes.length, "Number of retained nodes should match.");
+                InspectorTest.expectThat(nodes.every((node, i) => compareNodes(node, expectedNodes[i])), "Node values should match.");
+                resolve();
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: "HeapSnapshotNodeProxy.prototype.retainers",
+        test: (resolve, reject) => {
+            let expectedNodes = testSnapshotNodeForWindowObject.incomingEdges.map((edge) => edge.from);
+            expectedNodes.sort((a, b) => a.id - b.id);
+
+            snapshotNodeForWindowObject.retainers((nodes) => {
+                nodes.sort((a, b) => a.id - b.id);
+                InspectorTest.assert(nodes.length > 0, "Test only makes since if there are retainer nodes");
+                InspectorTest.expectThat(nodes.length === expectedNodes.length, "Number of retainer nodes should match.");
+                InspectorTest.expectThat(nodes.every((node, i) => compareNodes(node, expectedNodes[i])), "Node values should match.");
+                resolve();
+            });
+        }
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onload="runTest()">
+    <p>Testing HeapSnapshot Worker and Proxy objects.</p>
+</body>
+</html>
index ea425e9..62cc6a3 100644 (file)
@@ -1,3 +1,200 @@
+2016-03-17  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Web Inspector: HeapSnapshots are slow and use too much memory
+        https://bugs.webkit.org/show_bug.cgi?id=155571
+
+        Reviewed by Timothy Hatcher.
+
+        This is the first inclusion of Workers into Web Inspector. In this case
+        the Main side merely needs to make requests of the Worker and get back
+        objects that it can interact with more.
+        
+        New file heirarchies:
+
+            UserInterface/Proxies
+                - new Proxy classes in the Main page.
+                - treat like Model classes, but not quite model.
+
+            UserInterface/Workers/HeapSnapshotWorker
+                - new Worker classes for Workers. No WebInspector namespace.
+                - no minification of these resources, they are simply copied.
+
+        Remote procedure call interface between the Main/Worker page happens
+        through the WorkerProxy and Worker classes. There are simple ways
+        to perform factory style methods and call methods on objects, and
+        get the result in a callback. Similiar to frontend <-> backend agent
+        communication:
+
+            HeapSnapshotWorkerProxy: (Main world)
+                - creates the worker
+                - performAction("actionName", arguments, callback)
+                - callMethod(objectId, "methodName", arguments, callback)
+                - handle message => dispatch event or invoke callback
+
+            HeapSnapshotWorker: (Worker world)
+                - sendEvent("eventName", eventData)
+                - handle message => dispatch action or method on object
+        
+        Proxy object methods are boilerplate calls to performAction/callMethod
+        with deserialization of responses. The rest of the frontend can just
+        treat Proxy objects as Model objects with some data and async methods.
+
+        Because the Node/Edge data is so small, objects are cheaply created
+        when needed and not cached. This means that there may be duplicate
+        HeapSnapshotNode's for the same node. For example if different Views
+        both request instancesWithClassName("Foo"). This is fine, as none
+        of our Views really care about object uniqueness, they are only
+        interested in the data or querying for more data.
+
+        * Scripts/combine-resources.pl:
+        * Scripts/copy-user-interface-resources.pl:
+        Copy the Workers directory to the resources directory.
+        Its code is only meant to be loaded by Workers, so it
+        shouldn't be included in the Main page.
+
+        * UserInterface/Main.html:
+        * UserInterface/Test.html:
+        * UserInterface/Models/HeapSnapshot.js: Removed.
+        * UserInterface/Models/HeapSnapshotDiff.js: Removed.
+        * UserInterface/Models/HeapSnapshotEdge.js: Removed.
+        * UserInterface/Models/HeapSnapshotNode.js: Removed.
+        Replace the old simple Model classes with Proxy classes that interact
+        with the Worker.
+
+        * UserInterface/Models/HeapAllocationsInstrument.js:
+        (WebInspector.HeapAllocationsInstrument.prototype._takeHeapSnapshot):
+        (WebInspector.HeapAllocationsInstrument):
+        * UserInterface/Models/HeapAllocationsTimelineRecord.js:
+        (WebInspector.HeapAllocationsTimelineRecord):
+        * UserInterface/Models/HeapSnapshotRootPath.js:
+        (WebInspector.HeapSnapshotRootPath):
+        (WebInspector.HeapSnapshotRootPath.prototype.appendEdge):
+        * UserInterface/Protocol/HeapObserver.js:
+        (WebInspector.HeapObserver.prototype.trackingStart):
+        (WebInspector.HeapObserver.prototype.trackingComplete):
+        * UserInterface/Views/ContentView.js:
+        (WebInspector.ContentView.createFromRepresentedObject):
+        (WebInspector.ContentView.isViewable):
+        * UserInterface/Views/HeapAllocationsTimelineView.js:
+        (WebInspector.HeapAllocationsTimelineView.prototype.showHeapSnapshotDiff):
+        (WebInspector.HeapAllocationsTimelineView.prototype._takeHeapSnapshotClicked):
+        (WebInspector.HeapAllocationsTimelineView.prototype._dataGridNodeSelected):
+        (WebInspector.HeapAllocationsTimelineView):
+        * UserInterface/Views/HeapSnapshotClassDataGridNode.js:
+        (WebInspector.HeapSnapshotClassDataGridNode.prototype._populate):
+        * UserInterface/Views/HeapSnapshotClusterContentView.js:
+        * UserInterface/Views/HeapSnapshotInstanceDataGridNode.js:
+        (WebInspector.HeapSnapshotInstanceDataGridNode):
+        (WebInspector.HeapSnapshotInstanceDataGridNode.logHeapSnapshotNode.node.shortestGCRootPath.):
+        (WebInspector.HeapSnapshotInstanceDataGridNode.logHeapSnapshotNode):
+        (WebInspector.HeapSnapshotInstanceDataGridNode.prototype._mouseoverHandler.appendPath):
+        (WebInspector.HeapSnapshotInstanceDataGridNode.prototype._mouseoverHandler.stringifyEdge):
+        (WebInspector.HeapSnapshotInstanceDataGridNode.prototype._mouseoverHandler):
+        * UserInterface/Views/HeapSnapshotInstancesContentView.js:
+        (WebInspector.HeapSnapshotInstancesContentView):
+        * UserInterface/Views/HeapSnapshotInstancesDataGridTree.js:
+        (WebInspector.HeapSnapshotInstancesDataGridTree):
+        * UserInterface/Views/HeapSnapshotSummaryContentView.js:
+        (WebInspector.HeapSnapshotSummaryContentView):
+        Update existing code to expect the new Proxy objects or create
+        the new HeapSnapshot using workers.
+
+        * UserInterface/Proxies/HeapSnapshotDiffProxy.js: Added.
+        (WebInspector.HeapSnapshotDiffProxy):
+        (WebInspector.HeapSnapshotDiffProxy.deserialize):
+        (WebInspector.HeapSnapshotDiffProxy.prototype.get snapshot1):
+        (WebInspector.HeapSnapshotDiffProxy.prototype.get snapshot2):
+        (WebInspector.HeapSnapshotDiffProxy.prototype.get totalSize):
+        (WebInspector.HeapSnapshotDiffProxy.prototype.get totalObjectCount):
+        (WebInspector.HeapSnapshotDiffProxy.prototype.get categories):
+        (WebInspector.HeapSnapshotDiffProxy.prototype.allocationBucketCounts):
+        (WebInspector.HeapSnapshotDiffProxy.prototype.instancesWithClassName):
+        (WebInspector.HeapSnapshotDiffProxy.prototype.nodeWithIdentifier):
+        A HeapSnapshotDiffProxy looks like a HeapSnapshotProxy and responds to
+        the same methods, but has the extra snapshot1/2 pointers.
+
+        * UserInterface/Proxies/HeapSnapshotEdgeProxy.js:
+        (WebInspector.HeapSnapshotEdgeProxy):
+        (WebInspector.HeapSnapshotEdgeProxy.deserialize):
+        Edge data. No methods are proxied at this point.
+
+        * UserInterface/Proxies/HeapSnapshotNodeProxy.js: Added.
+        (WebInspector.HeapSnapshotNodeProxy):
+        (WebInspector.HeapSnapshotNodeProxy.deserialize):
+        (WebInspector.HeapSnapshotNodeProxy.prototype.shortestGCRootPath):
+        (WebInspector.HeapSnapshotNodeProxy.prototype.dominatedNodes):
+        (WebInspector.HeapSnapshotNodeProxy.prototype.retainedNodes):
+        (WebInspector.HeapSnapshotNodeProxy.prototype.retainers):
+        Node data and methods to query for node relationships.
+
+        * UserInterface/Proxies/HeapSnapshotProxy.js: Added.
+        (WebInspector.HeapSnapshotProxy):
+        (WebInspector.HeapSnapshotProxy.deserialize):
+        (WebInspector.HeapSnapshotProxy.prototype.get proxyObjectId):
+        (WebInspector.HeapSnapshotProxy.prototype.get identifier):
+        (WebInspector.HeapSnapshotProxy.prototype.get totalSize):
+        (WebInspector.HeapSnapshotProxy.prototype.get totalObjectCount):
+        (WebInspector.HeapSnapshotProxy.prototype.get categories):
+        (WebInspector.HeapSnapshotProxy.prototype.allocationBucketCounts):
+        (WebInspector.HeapSnapshotProxy.prototype.instancesWithClassName):
+        (WebInspector.HeapSnapshotProxy.prototype.nodeWithIdentifier):
+        Snapshot data and methods to query for nodes.
+
+        * UserInterface/Proxies/HeapSnapshotWorkerProxy.js: Added.
+        (WebInspector.HeapSnapshotWorkerProxy):
+        (WebInspector.HeapSnapshotWorkerProxy.singleton):
+        (WebInspector.HeapSnapshotWorkerProxy.prototype.createSnapshot):
+        (WebInspector.HeapSnapshotWorkerProxy.prototype.createSnapshotDiff):
+        (WebInspector.HeapSnapshotWorkerProxy.prototype.performAction):
+        (WebInspector.HeapSnapshotWorkerProxy.prototype.callMethod):
+        (WebInspector.HeapSnapshotWorkerProxy.prototype._postMessage):
+        (WebInspector.HeapSnapshotWorkerProxy.prototype._handleMessage):
+        Singleton factory for the worker and proxied communication with the worker.
+        Provide means for invoking "factory actions" and "object methods".
+
+        * UserInterface/Workers/HeapSnapshot/HeapSnapshotWorker.js: Added.
+        (HeapSnapshotWorker):
+        (HeapSnapshotWorker.prototype.createSnapshot):
+        (HeapSnapshotWorker.prototype.createSnapshotDiff):
+        (HeapSnapshotWorker.prototype.sendEvent):
+        (HeapSnapshotWorker.prototype._handleMessage):
+        Main worker code. Handle dispatching actions and methods.
+
+        * UserInterface/Workers/HeapSnapshot/HeapSnapshot.js: Added.
+        (HeapSnapshot):
+        (HeapSnapshot.buildCategories):
+        (HeapSnapshot.allocationBucketCounts):
+        (HeapSnapshot.instancesWithClassName):
+        (HeapSnapshot.prototype.allocationBucketCounts):
+        (HeapSnapshot.prototype.instancesWithClassName):
+        (HeapSnapshot.prototype.nodeWithIdentifier):
+        (HeapSnapshot.prototype.shortestGCRootPath):
+        (HeapSnapshot.prototype.dominatedNodes):
+        (HeapSnapshot.prototype.retainedNodes):
+        (HeapSnapshot.prototype.retainers):
+        (HeapSnapshot.prototype.serialize):
+        (HeapSnapshot.prototype.serializeNode):
+        (HeapSnapshot.prototype.serializeEdge):
+        (HeapSnapshot.prototype._buildOutgoingEdges):
+        (HeapSnapshot.prototype._buildIncomingEdges):
+        (HeapSnapshot.prototype._buildPostOrderIndexes):
+        (HeapSnapshot.prototype._buildDominatorIndexes):
+        (HeapSnapshot.prototype._buildRetainedSizes):
+        (HeapSnapshot.prototype._gcRootPathes.visitNode):
+        (HeapSnapshot.prototype._gcRootPathes):
+        (HeapSnapshotDiff):
+        (HeapSnapshotDiff.prototype.allocationBucketCounts):
+        (HeapSnapshotDiff.prototype.instancesWithClassName):
+        (HeapSnapshotDiff.prototype.nodeWithIdentifier):
+        (HeapSnapshotDiff.prototype.shortestGCRootPath):
+        (HeapSnapshotDiff.prototype.dominatedNodes):
+        (HeapSnapshotDiff.prototype.retainedNodes):
+        (HeapSnapshotDiff.prototype.retainers):
+        (HeapSnapshotDiff.prototype.serialize):
+        New HeapSnapshot data processing implementation. Instead of creating
+        a new object per Node or per Edge create data arrays containing data
+        per-Node. Operate on these lists of data instead of creating many objects.
+
 2016-03-17  Nikita Vasilyev  <nvasilyev@apple.com>
 
         Web Inspector: Large repaints when typing any character in console
index bc1a231..6d2cf44 100755 (executable)
@@ -103,7 +103,7 @@ sub concatenateFiles($$$)
     }
 }
 
-my $inputDirectoryPattern = "(?!External\/)[^\"]*";
+my $inputDirectoryPattern = "(?!External\/)(?!Workers\/)[^\"]*";
 $inputDirectoryPattern = $inputDirectory . "\/[^\"]*" if $inputDirectory;
 
 concatenateFiles($outputStylesheetName, "<link rel=\"stylesheet\" href=\"($inputDirectoryPattern)\">", "<link rel=\"stylesheet\" href=\"$outputStylesheetName\">") if defined $outputStylesheetName;
index c3108b8..60f5822 100755 (executable)
@@ -125,6 +125,7 @@ my $scriptsRoot = File::Spec->catdir($ENV{'SRCROOT'}, 'Scripts');
 my $uiRoot = File::Spec->catdir($ENV{'SRCROOT'}, 'UserInterface');
 my $targetResourcePath = File::Spec->catdir($ENV{'TARGET_BUILD_DIR'}, $ENV{'UNLOCALIZED_RESOURCES_FOLDER_PATH'});
 my $protocolDir = File::Spec->catdir($targetResourcePath, 'Protocol');
+my $workersDir = File::Spec->catdir($targetResourcePath, 'Workers');
 my $codeMirrorPath = File::Spec->catdir($uiRoot, 'External', 'CodeMirror');
 my $esprimaPath = File::Spec->catdir($uiRoot, 'External', 'Esprima');
 my $eslintPath = File::Spec->catdir($uiRoot, 'External', 'ESLint');
@@ -238,8 +239,9 @@ if ($shouldCombineMain) {
     # Remove ESLint until needed: <https://webkit.org/b/136515> Web Inspector: JavaScript source text editor should have a linter
     unlink $targetESLintJS;
 
-    # Copy the Legacy directory.
+    # Copy the Protocol/Legacy and Workers directories.
     ditto(File::Spec->catfile($uiRoot, 'Protocol', 'Legacy'), File::Spec->catfile($protocolDir, 'Legacy'));
+    ditto(File::Spec->catfile($uiRoot, 'Workers'), $workersDir);
 } else {
     # Keep the files separate for engineering builds.
     ditto($uiRoot, $targetResourcePath);
index 22dcde7..33f52fc 100644 (file)
     <script src="Models/Gradient.js"></script>
     <script src="Models/HeapAllocationsInstrument.js"></script>
     <script src="Models/HeapAllocationsTimelineRecord.js"></script>
-    <script src="Models/HeapSnapshot.js"></script>
-    <script src="Models/HeapSnapshotDiff.js"></script>
-    <script src="Models/HeapSnapshotEdge.js"></script>
-    <script src="Models/HeapSnapshotNode.js"></script>
     <script src="Models/HeapSnapshotRootPath.js"></script>
     <script src="Models/IndexedDatabase.js"></script>
     <script src="Models/IndexedDatabaseObjectStore.js"></script>
     <script src="Models/TypeSet.js"></script>
     <script src="Models/WrappedPromise.js"></script>
 
+    <script src="Proxies/HeapSnapshotDiffProxy.js"></script>
+    <script src="Proxies/HeapSnapshotEdgeProxy.js"></script>
+    <script src="Proxies/HeapSnapshotNodeProxy.js"></script>
+    <script src="Proxies/HeapSnapshotProxy.js"></script>
+    <script src="Proxies/HeapSnapshotWorkerProxy.js"></script>
+
     <script src="Views/View.js"></script>
     <script src="Views/Dialog.js"></script>
 
index 4cd4178..15bd1eb 100644 (file)
@@ -74,9 +74,11 @@ WebInspector.HeapAllocationsInstrument = class HeapAllocationsInstrument extends
     _takeHeapSnapshot()
     {
         HeapAgent.snapshot(function(error, timestamp, snapshotStringData) {
-            let payload = JSON.parse(snapshotStringData);
-            let snapshot = WebInspector.HeapSnapshot.fromPayload(payload);
-            WebInspector.timelineManager.heapSnapshotAdded(timestamp, snapshot);
+            let workerProxy = WebInspector.HeapSnapshotWorkerProxy.singleton();
+            workerProxy.createSnapshot(snapshotStringData, ({objectId, snapshot: serializedSnapshot}) => {
+                let snapshot = WebInspector.HeapSnapshotProxy.deserialize(objectId, serializedSnapshot);
+                WebInspector.timelineManager.heapSnapshotAdded(timestamp, snapshot);
+            });
         });
     }
 };
index c696044..853fe3e 100644 (file)
@@ -30,7 +30,7 @@ WebInspector.HeapAllocationsTimelineRecord = class HeapAllocationsTimelineRecord
         super(WebInspector.TimelineRecord.Type.HeapAllocations, timestamp, timestamp);
 
         console.assert(typeof timestamp === "number");
-        console.assert(heapSnapshot instanceof WebInspector.HeapSnapshot);
+        console.assert(heapSnapshot instanceof WebInspector.HeapSnapshotProxy);
 
         this._timestamp = timestamp;
         this._heapSnapshot = heapSnapshot;
diff --git a/Source/WebInspectorUI/UserInterface/Models/HeapSnapshot.js b/Source/WebInspectorUI/UserInterface/Models/HeapSnapshot.js
deleted file mode 100644 (file)
index 712ed07..0000000
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Copyright (C) 2016 Apple Inc. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
- * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
- * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-WebInspector.HeapSnapshotClassCategory = class HeapSnapshotClassCategory
-{
-    constructor(className)
-    {
-        this.className = className;
-        this.size = 0;
-        this.count = 0;
-        this.internalCount = 0;
-    }
-};
-
-WebInspector.HeapSnapshot = class HeapSnapshot extends WebInspector.Object
-{
-    constructor(rootNode, nodes, nodeMap)
-    {
-        super();
-
-        console.assert(!rootNode || rootNode instanceof WebInspector.HeapSnapshotNode);
-        console.assert(nodes instanceof Array);
-
-        this._rootNode = rootNode;
-        this._nodes = nodes;
-        this._nodeMap = nodeMap;
-
-        this._identifier = WebInspector.HeapSnapshot._nextAvailableSnapshotIdentifier++;
-        this._instances = nodes;
-
-        let categories = {};
-        for (let i = 0; i < nodes.length; ++i) {
-            let {className, size, internal} = nodes[i];
-
-            let category = categories[className];
-            if (!category)
-                category = categories[className] = new WebInspector.HeapSnapshotClassCategory(className);
-
-            category.size += size;
-            category.count++;
-            if (internal)
-                category.internalCount++;
-        }
-        this._categories = Map.fromObject(categories);
-
-        this._totalSize = 0;
-        this._totalObjectCount = 0;
-        for (let {count, size} of this._categories.values()) {
-            this._totalSize += size;
-            this._totalObjectCount += count;
-        }
-    }
-
-    // Static
-
-    static fromPayload(payload)
-    {
-        let {version, nodes, nodeClassNames, edges, edgeTypes, edgeNames} = payload;
-        console.assert(version === 1, "Only know how to handle JavaScriptCore Heap Snapshot Format Version 1");
-        console.assert(edgeTypes.every((type) => type in WebInspector.HeapSnapshotEdge.EdgeType), "Unexpected edge type", edgeTypes);
-
-        let nodeMap = new Map;
-
-        // Turn nodes into real nodes.
-        let processedNodes = [];
-        for (let i = 0, length = nodes.length; i < length;) {
-            let id = nodes[i++];
-            let size = nodes[i++];
-            let classNameIndex = nodes[i++];
-            let internal = nodes[i++];
-
-            let node = new WebInspector.HeapSnapshotNode(id, nodeClassNames[classNameIndex], size, !!internal);
-            nodeMap.set(id, node);
-            processedNodes.push(node);
-        }
-
-        // Turn edges into real edges and set them on the nodes.
-        for (let i = 0, length = edges.length; i < length;) {
-            let fromIdentifier = edges[i++];
-            let toIdentifier = edges[i++];
-            let edgeTypeIndex = edges[i++];
-            let data = edges[i++];
-
-            let from = nodeMap.get(fromIdentifier);
-            let to = nodeMap.get(toIdentifier);
-            let type = edgeTypes[edgeTypeIndex];
-            if (type === WebInspector.HeapSnapshotEdge.EdgeType.Property || type === WebInspector.HeapSnapshotEdge.EdgeType.Variable)
-                data = edgeNames[data];
-
-            let edge = new WebInspector.HeapSnapshotEdge(from, to, type, data);
-            from.outgoingEdges.push(edge);
-            to.incomingEdges.push(edge);
-        }
-
-        // Root node.
-        let rootNode = nodeMap.get(0);
-        console.assert(rootNode, "Node with identifier 0 is the synthetic <root> node.");
-        console.assert(rootNode.outgoingEdges.length > 0, "This had better have children!");
-        console.assert(rootNode.incomingEdges.length === 0, "This had better not have back references!");
-
-        // Mark GC roots.
-        let rootNodeEdges = rootNode.outgoingEdges;
-        for (let i = 0, length = rootNodeEdges.length; i < length; ++i)
-            rootNodeEdges[i].to.gcRoot = true;
-
-        return new WebInspector.HeapSnapshot(rootNode, processedNodes, nodeMap);
-    }
-
-    // Public
-
-    get rootNode() { return this._rootNode; }
-    get nodes() { return this._nodes; }
-    get identifier() { return this._identifier; }
-    get instances() { return this._instances; }
-    get categories() { return this._categories; }
-    get totalSize() { return this._totalSize; }
-    get totalObjectCount() { return this._totalObjectCount; }
-
-    instancesWithClassName(className)
-    {
-        let results = [];
-        for (let i = 0; i < this._instances.length; ++i) {
-            let cell = this._instances[i];
-            if (cell.className === className)
-                results.push(cell);
-        }
-        return results;
-    }
-
-    nodeWithObjectIdentifier(id)
-    {
-        return this._nodeMap.get(id);
-    }
-};
-
-WebInspector.HeapSnapshot._nextAvailableSnapshotIdentifier = 1;
diff --git a/Source/WebInspectorUI/UserInterface/Models/HeapSnapshotDiff.js b/Source/WebInspectorUI/UserInterface/Models/HeapSnapshotDiff.js
deleted file mode 100644 (file)
index 255b38e..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2016 Apple Inc. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
- * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
- * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-WebInspector.HeapSnapshotDiff = class HeapSnapshotDiff extends WebInspector.Object
-{
-    constructor(snapshot1, snapshot2)
-    {
-        super();
-
-        console.assert(snapshot1 instanceof WebInspector.HeapSnapshot);
-        console.assert(snapshot2 instanceof WebInspector.HeapSnapshot);
-
-        this._snapshot1 = snapshot1;
-        this._snapshot2 = snapshot2;
-
-        let known = new Map;
-        for (let instance of snapshot1.instances)
-            known.set(instance.id, instance);
-
-        let added = [];
-        for (let instance of snapshot2.instances) {
-            if (known.has(instance.id))
-                known.delete(instance.id);
-            else
-                added.push(instance);
-        }
-
-        let removed = [...known.values()];
-
-        this._addedInstances = added;
-        this._removedInstances = removed;
-    }
-
-    // Public
-
-    get snapshot1() { return this._snapshot1; }
-    get snapshot2() { return this._snapshot2; }
-    get addedInstances() { return this._addedInstances; }
-    get removedInstances() { return this._removedInstances; }
-
-    get sizeDifference()
-    {
-        return this._snapshot2.totalSize - this._snapshot1.totalSize;
-    }
-
-    get growth()
-    {
-        return this._addedInstances.reduce((sum, x) => sum += x.size, 0);
-    }
-
-    snapshotForDiff()
-    {
-        // FIXME: This only includes the newly added instances. Should we do anything with the removed instances?
-        return new WebInspector.HeapSnapshot(null, this._addedInstances, this._snapshot2.nodeMap);
-    }
-};
diff --git a/Source/WebInspectorUI/UserInterface/Models/HeapSnapshotNode.js b/Source/WebInspectorUI/UserInterface/Models/HeapSnapshotNode.js
deleted file mode 100644 (file)
index e9c20a2..0000000
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2016 Apple Inc. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
- * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
- * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-WebInspector.HeapSnapshotNode = class HeapSnapshotNode
-{
-    constructor(identifier, className, size, internal)
-    {
-        this.id = identifier;
-        this.className = className;
-        this.size = size; 
-        this.internal = internal;
-        this.gcRoot = false;
-        this.outgoingEdges = [];
-        this.incomingEdges = [];
-    }
-
-    // Public
-
-    get shortestGCRootPath()
-    {
-        // Returns an array from this node to a gcRoot node.
-        // E.g. [Node, Edge, Node, Edge, Node].
-        // Internal nodes are avoided, so if the path is empty this
-        // node is either a gcRoot or only reachable via Internal nodes.
-
-        if (this._shortestGCRootPath !== undefined)
-            return this._shortestGCRootPath;
-
-        let paths = this._gcRootPaths();
-        paths.sort((a, b) => a.length - b.length);
-        this._shortestGCRootPath = paths[0] || null;
-
-        return this._shortestGCRootPath;
-    }
-
-    // Private
-
-    _gcRootPaths()
-    {
-        if (this.gcRoot)
-            return [];
-
-        let paths = [];
-        let currentPath = [];
-        let visitedSet = new Set;
-
-        function visitNode(node) {
-            if (node.gcRoot) {
-                let fullPath = currentPath.slice();
-                fullPath.push(node);
-                paths.push(fullPath);
-                return;
-            }
-
-            if (visitedSet.has(node))
-                return;
-            visitedSet.add(node);
-
-            currentPath.push(node);
-            for (let parentEdge of node.incomingEdges) {
-                if (parentEdge.from.internal)
-                    continue;
-                currentPath.push(parentEdge);
-                visitNode(parentEdge.from);
-                currentPath.pop();
-            }
-            currentPath.pop();
-        }
-
-        visitNode(this);
-
-        return paths;
-    }
-};
index 403d2dd..58d2f4b 100644 (file)
@@ -29,7 +29,7 @@ WebInspector.HeapSnapshotRootPath = class HeapSnapshotRootPath extends WebInspec
     {
         super();
 
-        console.assert(!node || node instanceof WebInspector.HeapSnapshotNode);
+        console.assert(!node || node instanceof WebInspector.HeapSnapshotNodeProxy);
         console.assert(!pathComponent || typeof pathComponent === "string");
         console.assert(!parent || parent instanceof WebInspector.HeapSnapshotRootPath);
 
@@ -136,16 +136,16 @@ WebInspector.HeapSnapshotRootPath = class HeapSnapshotRootPath extends WebInspec
 
     appendEdge(edge)
     {
-        console.assert(edge instanceof WebInspector.HeapSnapshotEdge);
+        console.assert(edge instanceof WebInspector.HeapSnapshotEdgeProxy);
 
         switch (edge.type) {
-        case WebInspector.HeapSnapshotEdge.EdgeType.Internal:
+        case WebInspector.HeapSnapshotEdgeProxy.EdgeType.Internal:
             return this.appendInternal(edge.to);
-        case WebInspector.HeapSnapshotEdge.EdgeType.Index:
+        case WebInspector.HeapSnapshotEdgeProxy.EdgeType.Index:
             return this.appendArrayIndex(edge.to, edge.data);
-        case WebInspector.HeapSnapshotEdge.EdgeType.Property:
+        case WebInspector.HeapSnapshotEdgeProxy.EdgeType.Property:
             return this.appendPropertyName(edge.to, edge.data);
-        case WebInspector.HeapSnapshotEdge.EdgeType.Variable:
+        case WebInspector.HeapSnapshotEdgeProxy.EdgeType.Variable:
             return this.appendVariableName(edge.to, edge.data);
         }
 
index 2fa1608..3fd41e5 100644 (file)
@@ -32,17 +32,21 @@ WebInspector.HeapObserver = class HeapObserver
         WebInspector.heapManager.garbageCollected(collection);
     }
 
-    trackingStart(timestamp, snapshotData)
+    trackingStart(timestamp, snapshotStringData)
     {
-        let payload = JSON.parse(snapshotData);
-        let snapshot = WebInspector.HeapSnapshot.fromPayload(payload);
-        WebInspector.timelineManager.heapTrackingStarted(timestamp, snapshot);
+        let workerProxy = WebInspector.HeapSnapshotWorkerProxy.singleton();
+        workerProxy.createSnapshot(snapshotStringData, ({objectId, snapshot: serializedSnapshot}) => {
+            let snapshot = WebInspector.HeapSnapshotProxy.deserialize(objectId, serializedSnapshot);
+            WebInspector.timelineManager.heapTrackingStarted(timestamp, snapshot);
+        });
     }
 
-    trackingComplete(timestamp, snapshotData)
+    trackingComplete(timestamp, snapshotStringData)
     {
-        let payload = JSON.parse(snapshotData);
-        let snapshot = WebInspector.HeapSnapshot.fromPayload(payload);
-        WebInspector.timelineManager.heapTrackingCompleted(timestamp, snapshot);
+        let workerProxy = WebInspector.HeapSnapshotWorkerProxy.singleton();
+        workerProxy.createSnapshot(snapshotStringData, ({objectId, snapshot: serializedSnapshot}) => {
+            let snapshot = WebInspector.HeapSnapshotProxy.deserialize(objectId, serializedSnapshot);
+            WebInspector.timelineManager.heapTrackingCompleted(timestamp, snapshot);
+        });
     }
 };
diff --git a/Source/WebInspectorUI/UserInterface/Proxies/HeapSnapshotDiffProxy.js b/Source/WebInspectorUI/UserInterface/Proxies/HeapSnapshotDiffProxy.js
new file mode 100644 (file)
index 0000000..34d3be0
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WebInspector.HeapSnapshotDiffProxy = class HeapSnapshotDiffProxy extends WebInspector.Object
+{
+    constructor(snapshotDiffObjectId, snapshot1, snapshot2, totalSize, totalObjectCount, categories)
+    {
+        super();
+
+        this._proxyObjectId = snapshotDiffObjectId;
+
+        console.assert(snapshot1 instanceof WebInspector.HeapSnapshotProxy);
+        console.assert(snapshot2 instanceof WebInspector.HeapSnapshotProxy);
+
+        this._snapshot1 = snapshot1;
+        this._snapshot2 = snapshot2;
+        this._totalSize = totalSize;
+        this._totalObjectCount = totalObjectCount;
+        this._categories = Map.fromObject(categories);
+    }
+
+    // Static
+
+    static deserialize(objectId, serializedSnapshotDiff)
+    {
+        let {snapshot1: serializedSnapshot1, snapshot2: serializedSnapshot2, totalSize, totalObjectCount, categories} = serializedSnapshotDiff;
+        // FIXME: The objectId for these snapshots is the snapshotDiff's objectId. Currently these
+        // snapshots are only used for static data so the proxing doesn't matter. However,
+        // should we serialize the objectId with the snapshot so we have the right objectId?
+        let snapshot1 = WebInspector.HeapSnapshotProxy.deserialize(objectId, serializedSnapshot1);
+        let snapshot2 = WebInspector.HeapSnapshotProxy.deserialize(objectId, serializedSnapshot2);
+        return new WebInspector.HeapSnapshotDiffProxy(objectId, snapshot1, snapshot2, totalSize, totalObjectCount, categories);
+    }
+
+    // Public
+
+    get snapshot1() { return this._snapshot1; }
+    get snapshot2() { return this._snapshot2; }
+    get totalSize() { return this._totalSize; }
+    get totalObjectCount() { return this._totalObjectCount; }
+    get categories() { return this._categories; }
+
+    allocationBucketCounts(bucketSizes, callback)
+    {
+        WebInspector.HeapSnapshotWorkerProxy.singleton().callMethod(this._proxyObjectId, "allocationBucketCounts", bucketSizes, callback);
+    }
+
+    instancesWithClassName(className, callback)
+    {
+        WebInspector.HeapSnapshotWorkerProxy.singleton().callMethod(this._proxyObjectId, "instancesWithClassName", className, (serializedNodes) => {
+            callback(serializedNodes.map(WebInspector.HeapSnapshotNodeProxy.deserialize.bind(null, this._proxyObjectId)));
+        });
+    }
+
+    nodeWithIdentifier(nodeIdentifier, callback)
+    {
+        WebInspector.HeapSnapshotWorkerProxy.singleton().callMethod(this._proxyObjectId, "nodeWithIdentifier", nodeIdentifier, (serializedNode) => {
+            callback(WebInspector.HeapSnapshotNodeProxy.deserialize(this._proxyObjectId, serializedNode));
+        });
+    }
+};
 
 // Directed edge between two HeapSnapshotNodes 'from' and 'to'.
 
-WebInspector.HeapSnapshotEdge = class HeapSnapshotEdge
+WebInspector.HeapSnapshotEdgeProxy = class HeapSnapshotEdgeProxy
 {
-    constructor(from, to, type, data)
+    constructor(objectId, fromIdentifier, toIdentifier, type, data)
     {
-        this.from = from;
-        this.to = to;
+        this._proxyObjectId = objectId;
+
+        console.assert(type in WebInspector.HeapSnapshotEdgeProxy.EdgeType);
+
+        this.fromIdentifier = fromIdentifier;
+        this.toIdentifier = toIdentifier;
         this.type = type;
         this.data = data;
+
+        this.from = null;
+        this.to = null;
+    }
+
+    // Static
+
+    static deserialize(objectId, serializedEdge)
+    {
+        let {from, to, type, data} = serializedEdge;
+        return new WebInspector.HeapSnapshotEdgeProxy(objectId, from, to, type, data);
     }
 };
 
-WebInspector.HeapSnapshotEdge.EdgeType = {
+WebInspector.HeapSnapshotEdgeProxy.EdgeType = {
     Internal: "Internal",       // No data.
     Property: "Property",       // data is string property name.
     Index: "Index",             // data is numeric index.
diff --git a/Source/WebInspectorUI/UserInterface/Proxies/HeapSnapshotNodeProxy.js b/Source/WebInspectorUI/UserInterface/Proxies/HeapSnapshotNodeProxy.js
new file mode 100644 (file)
index 0000000..2d097b3
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WebInspector.HeapSnapshotNodeProxy = class HeapSnapshotNodeProxy
+{
+    constructor(snapshotObjectId, identifier, className, size, retainedSize, internal, gcRoot)
+    {
+        this._proxyObjectId = snapshotObjectId;
+
+        this.id = identifier;
+        this.className = className;
+        this.size = size;
+        this.retainedSize = retainedSize;
+        this.internal = internal;
+        this.gcRoot = gcRoot;
+    }
+
+    // Static
+
+    static deserialize(objectId, serializedNode)
+    {
+        let {id, className, size, retainedSize, internal, gcRoot} = serializedNode;
+        return new WebInspector.HeapSnapshotNodeProxy(objectId, id, className, size, retainedSize, internal, gcRoot);
+    }
+
+    // Proxied
+
+    shortestGCRootPath(callback)
+    {
+        WebInspector.HeapSnapshotWorkerProxy.singleton().callMethod(this._proxyObjectId, "shortestGCRootPath", this.id, (serializedPath) => {
+            let isNode = false;
+            let path = serializedPath.map((component) => {
+                isNode = !isNode;
+                if (isNode)
+                    return WebInspector.HeapSnapshotNodeProxy.deserialize(this._proxyObjectId, component);
+                return WebInspector.HeapSnapshotEdgeProxy.deserialize(this._proxyObjectId, component);
+            });
+
+            for (let i = 1; i < path.length; i += 2) {
+                console.assert(path[i] instanceof WebInspector.HeapSnapshotEdgeProxy);
+                let edge = path[i];
+                edge.from = path[i - 1];
+                edge.to = path[i + 1];
+            }
+
+            callback(path);
+        });
+    }
+
+    dominatedNodes(callback)
+    {
+        WebInspector.HeapSnapshotWorkerProxy.singleton().callMethod(this._proxyObjectId, "dominatedNodes", this.id, (serializedNodes) => {
+            callback(serializedNodes.map(WebInspector.HeapSnapshotNodeProxy.deserialize.bind(null, this._proxyObjectId)));
+        });
+    }
+
+    retainedNodes(callback)
+    {
+        WebInspector.HeapSnapshotWorkerProxy.singleton().callMethod(this._proxyObjectId, "retainedNodes", this.id, (serializedNodes) => {
+            callback(serializedNodes.map(WebInspector.HeapSnapshotNodeProxy.deserialize.bind(null, this._proxyObjectId)));
+        });
+    }
+
+    retainers(callback)
+    {
+        WebInspector.HeapSnapshotWorkerProxy.singleton().callMethod(this._proxyObjectId, "retainers", this.id, (serializedNodes) => {
+            callback(serializedNodes.map(WebInspector.HeapSnapshotNodeProxy.deserialize.bind(null, this._proxyObjectId)));
+        });
+    }
+};
diff --git a/Source/WebInspectorUI/UserInterface/Proxies/HeapSnapshotProxy.js b/Source/WebInspectorUI/UserInterface/Proxies/HeapSnapshotProxy.js
new file mode 100644 (file)
index 0000000..8ee6633
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WebInspector.HeapSnapshotProxy = class HeapSnapshotProxy extends WebInspector.Object
+{
+    constructor(snapshotObjectId, identifier, totalSize, totalObjectCount, categories)
+    {
+        super();
+
+        this._proxyObjectId = snapshotObjectId;
+
+        this._identifier = identifier;
+        this._totalSize = totalSize;
+        this._totalObjectCount = totalObjectCount;
+        this._categories = Map.fromObject(categories);
+    }
+
+    // Static
+
+    static deserialize(objectId, serializedSnapshot)
+    {
+        let {identifier, totalSize, totalObjectCount, categories} = serializedSnapshot;
+        return new WebInspector.HeapSnapshotProxy(objectId, identifier, totalSize, totalObjectCount, categories);
+    }
+
+    // Public
+
+    get proxyObjectId() { return this._proxyObjectId; }
+    get identifier() { return this._identifier; }
+    get totalSize() { return this._totalSize; }
+    get totalObjectCount() { return this._totalObjectCount; }
+    get categories() { return this._categories; }
+
+    allocationBucketCounts(bucketSizes, callback)
+    {
+        WebInspector.HeapSnapshotWorkerProxy.singleton().callMethod(this._proxyObjectId, "allocationBucketCounts", bucketSizes, callback);
+    }
+
+    instancesWithClassName(className, callback)
+    {
+        WebInspector.HeapSnapshotWorkerProxy.singleton().callMethod(this._proxyObjectId, "instancesWithClassName", className, (serializedNodes) => {
+            callback(serializedNodes.map(WebInspector.HeapSnapshotNodeProxy.deserialize.bind(null, this._proxyObjectId)));
+        });
+    }
+
+    nodeWithIdentifier(nodeIdentifier, callback)
+    {
+        WebInspector.HeapSnapshotWorkerProxy.singleton().callMethod(this._proxyObjectId, "nodeWithIdentifier", nodeIdentifier, (serializedNode) => {
+            callback(WebInspector.HeapSnapshotNodeProxy.deserialize(this._proxyObjectId, serializedNode));
+        });
+    }
+};
diff --git a/Source/WebInspectorUI/UserInterface/Proxies/HeapSnapshotWorkerProxy.js b/Source/WebInspectorUI/UserInterface/Proxies/HeapSnapshotWorkerProxy.js
new file mode 100644 (file)
index 0000000..3b30cf6
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WebInspector.HeapSnapshotWorkerProxy = class HeapSnapshotWorkerProxy extends WebInspector.Object
+{
+    constructor()
+    {
+        super();
+
+        this._heapSnapshotWorker = new Worker("Workers/HeapSnapshot/HeapSnapshotWorker.js");
+        this._heapSnapshotWorker.addEventListener("message", this._handleMessage.bind(this));
+
+        this._nextCallId = 1;
+        this._callbacks = new Map;
+    }
+
+    // Static
+
+    static singleton()
+    {
+        if (!HeapSnapshotWorkerProxy.instance)
+            HeapSnapshotWorkerProxy.instance = new HeapSnapshotWorkerProxy;
+        return HeapSnapshotWorkerProxy.instance;
+    }
+
+    // Actions
+
+    createSnapshot(snapshotStringData, callback)
+    {
+        this.performAction("createSnapshot", ...arguments);
+    }
+
+    createSnapshotDiff(objectId1, objectId2, callback)
+    {
+        this.performAction("createSnapshotDiff", ...arguments);
+    }
+
+    // Public
+
+    performAction(actionName)
+    {
+        let callId = this._nextCallId++;
+        let callback = arguments[arguments.length - 1];
+        let actionArguments = Array.prototype.slice.call(arguments, 1, arguments.length - 1);
+
+        console.assert(typeof actionName === "string", "performAction should always have an actionName");
+        console.assert(typeof callback === "function", "performAction should always have a callback");
+
+        this._callbacks.set(callId, callback);
+        this._postMessage({callId, actionName, actionArguments});
+    }
+
+    callMethod(objectId, methodName)
+    {
+        let callId = this._nextCallId++;
+        let callback = arguments[arguments.length - 1];
+        let methodArguments = Array.prototype.slice.call(arguments, 2, arguments.length - 1);
+
+        console.assert(typeof objectId === "number", "callMethod should always have an objectId");
+        console.assert(typeof methodName === "string", "callMethod should always have a methodName");
+        console.assert(typeof callback === "function", "callMethod should always have a callback");
+
+        this._callbacks.set(callId, callback);
+        this._postMessage({callId, objectId, methodName, methodArguments});
+    }
+
+    // Private
+
+    _postMessage()
+    {
+        this._heapSnapshotWorker.postMessage(...arguments);
+    }
+
+    _handleMessage(event)
+    {
+        let data = event.data;
+
+        // Event.
+        if (data.eventName) {
+            this.dispatchEventToListeners(data.eventName, data.eventData);
+            return;
+        }
+
+        // Action or Method Response.
+        if (data.callId) {
+            let callback = this._callbacks.get(data.callId);
+            this._callbacks.delete(data.callId);
+            callback(data.result);
+            return;
+        }
+
+        console.error("Unexpected HeapSnapshotWorker message", data);
+    }
+};
index 113bfbe..23114fb 100644 (file)
     <script src="Models/Geometry.js"></script>
     <script src="Models/HeapAllocationsInstrument.js"></script>
     <script src="Models/HeapAllocationsTimelineRecord.js"></script>
-    <script src="Models/HeapSnapshot.js"></script>
-    <script src="Models/HeapSnapshotDiff.js"></script>
-    <script src="Models/HeapSnapshotEdge.js"></script>
-    <script src="Models/HeapSnapshotNode.js"></script>
     <script src="Models/IndexedDatabase.js"></script>
     <script src="Models/IndexedDatabaseObjectStore.js"></script>
     <script src="Models/IndexedDatabaseObjectStoreIndex.js"></script>
     <script src="Models/TimelineMarker.js"></script>
     <script src="Models/TimelineRecording.js"></script>
 
+    <script src="Proxies/HeapSnapshotDiffProxy.js"></script>
+    <script src="Proxies/HeapSnapshotEdgeProxy.js"></script>
+    <script src="Proxies/HeapSnapshotNodeProxy.js"></script>
+    <script src="Proxies/HeapSnapshotProxy.js"></script>
+    <script src="Proxies/HeapSnapshotWorkerProxy.js"></script>
+
     <script src="Controllers/CSSStyleManager.js"></script>
     <script src="Controllers/DOMTreeManager.js"></script>
     <script src="Controllers/DebuggerManager.js"></script>
index 413fa68..04ae9b3 100644 (file)
@@ -138,7 +138,7 @@ WebInspector.ContentView = class ContentView extends WebInspector.View
         if (representedObject instanceof WebInspector.CallingContextTree)
             return new WebInspector.ProfileView(representedObject, extraArguments);
 
-        if (representedObject instanceof WebInspector.HeapSnapshot)
+        if (representedObject instanceof WebInspector.HeapSnapshotProxy || representedObject instanceof WebInspector.HeapSnapshotDiffProxy)
             return new WebInspector.HeapSnapshotClusterContentView(representedObject, extraArguments);
 
         if (typeof representedObject === "string" || representedObject instanceof String)
@@ -237,7 +237,7 @@ WebInspector.ContentView = class ContentView extends WebInspector.View
             return true;
         if (representedObject instanceof WebInspector.CallingContextTree)
             return true;
-        if (representedObject instanceof WebInspector.HeapSnapshot)
+        if (representedObject instanceof WebInspector.HeapSnapshotProxy || representedObject instanceof WebInspector.HeapSnapshotDiffProxy)
             return true;
         if (typeof representedObject === "string" || representedObject instanceof String)
             return true;
index 2dafccb..c377255 100644 (file)
@@ -147,7 +147,7 @@ WebInspector.HeapAllocationsTimelineView = class HeapAllocationsTimelineView ext
         this._showingSnapshotList = false;
         this._heapSnapshotDiff = heapSnapshotDiff;
 
-        this._contentViewContainer.showContentViewForRepresentedObject(heapSnapshotDiff.snapshotForDiff());
+        this._contentViewContainer.showContentViewForRepresentedObject(heapSnapshotDiff);
     }
 
     // Protected
@@ -293,9 +293,11 @@ WebInspector.HeapAllocationsTimelineView = class HeapAllocationsTimelineView ext
     _takeHeapSnapshotClicked()
     {
         HeapAgent.snapshot(function(error, timestamp, snapshotStringData) {
-            let payload = JSON.parse(snapshotStringData);
-            let snapshot = WebInspector.HeapSnapshot.fromPayload(payload);
-            WebInspector.timelineManager.heapSnapshotAdded(timestamp, snapshot);
+            let workerProxy = WebInspector.HeapSnapshotWorkerProxy.singleton();
+            workerProxy.createSnapshot(snapshotStringData, ({objectId, snapshot: serializedSnapshot}) => {
+                let snapshot = WebInspector.HeapSnapshotProxy.deserialize(objectId, serializedSnapshot);
+                WebInspector.timelineManager.heapSnapshotAdded(timestamp, snapshot);
+            });
         });
     }
 
@@ -363,8 +365,13 @@ WebInspector.HeapAllocationsTimelineView = class HeapAllocationsTimelineView ext
         }
 
         // Selected Comparison.
-        let diff = new WebInspector.HeapSnapshotDiff(this._baselineHeapSnapshotTimelineRecord.heapSnapshot, heapAllocationsTimelineRecord.heapSnapshot);
-        this.showHeapSnapshotDiff(diff);
+        let snapshot1 = this._baselineHeapSnapshotTimelineRecord.heapSnapshot;
+        let snapshot2 = heapAllocationsTimelineRecord.heapSnapshot;
+        let workerProxy = WebInspector.HeapSnapshotWorkerProxy.singleton();
+        workerProxy.createSnapshotDiff(snapshot1.proxyObjectId, snapshot2.proxyObjectId, ({objectId, snapshotDiff: serializedSnapshotDiff}) => {
+            let diff = WebInspector.HeapSnapshotDiffProxy.deserialize(objectId, serializedSnapshotDiff);
+            this.showHeapSnapshotDiff(diff);
+        });
 
         this._baselineDataGridNode.clearBaseline();
         this._selectingComparisonHeapSnapshots = false;
index 0b27de4..55b336f 100644 (file)
@@ -95,25 +95,25 @@ WebInspector.HeapSnapshotClassDataGridNode = class HeapSnapshotClassDataGridNode
     {
         this.removeEventListener("populate", this._populate, this);
 
-        let instances = this._tree.heapSnapshot.instancesWithClassName(this._data.className);
-
-        // Batch.
-        if (instances.length > WebInspector.HeapSnapshotClassDataGridNode.ChildrenBatchLimit) {
-            // FIXME: This should respect the this._tree.includeInternalObjects setting.
-            this._instances = instances;
-            this._batched = true;
-            this._updateBatchedSort();
-            this._fetchBatch(WebInspector.HeapSnapshotClassDataGridNode.ChildrenBatchLimit);
-            return;
-        }
-
-        for (let instance of instances) {
-            if (instance.internal && !this._tree.includeInternalObjects)
-                continue;
-            this.appendChild(new WebInspector.HeapSnapshotInstanceDataGridNode(instance, this._tree));
-        }
-
-        this.sort();
+        this._tree.heapSnapshot.instancesWithClassName(this._data.className, (instances) => {
+            // Batch.
+            if (instances.length > WebInspector.HeapSnapshotClassDataGridNode.ChildrenBatchLimit) {
+                // FIXME: This should respect the this._tree.includeInternalObjects setting.
+                this._instances = instances;
+                this._batched = true;
+                this._updateBatchedSort();
+                this._fetchBatch(WebInspector.HeapSnapshotClassDataGridNode.ChildrenBatchLimit);
+                return;
+            }
+
+            for (let instance of instances) {
+                if (instance.internal && !this._tree.includeInternalObjects)
+                    continue;
+                this.appendChild(new WebInspector.HeapSnapshotInstanceDataGridNode(instance, this._tree));
+            }
+
+            this.sort();
+        });
     }
 
     _fetchBatch(batchSize)
index 57c3071..5190481 100644 (file)
@@ -29,7 +29,7 @@ WebInspector.HeapSnapshotClusterContentView = class HeapSnapshotClusterContentVi
     {
         super(heapSnapshot);
 
-        console.assert(heapSnapshot instanceof WebInspector.HeapSnapshot);
+        console.assert(heapSnapshot instanceof WebInspector.HeapSnapshotProxy || heapSnapshot instanceof WebInspector.HeapSnapshotDiffProxy);
 
         this._heapSnapshot = heapSnapshot;
 
index b997528..3f7c827 100644 (file)
@@ -29,7 +29,7 @@ WebInspector.HeapSnapshotInstanceDataGridNode = class HeapSnapshotInstanceDataGr
     {
         super(node, false);
 
-        console.assert(node instanceof WebInspector.HeapSnapshotNode);
+        console.assert(node instanceof WebInspector.HeapSnapshotNodeProxy);
 
         this._node = node;
         this._tree = tree;
@@ -45,44 +45,44 @@ WebInspector.HeapSnapshotInstanceDataGridNode = class HeapSnapshotInstanceDataGr
     static logHeapSnapshotNode(node)
     {
         let heapObjectIdentifier = node.id;
-
         let synthetic = true;
         let text = WebInspector.UIString("Heap Snapshot Object (@%d)").format(heapObjectIdentifier);
 
-        let gcRootPath = node.shortestGCRootPath;
-        if (gcRootPath) {
-            gcRootPath = gcRootPath.slice().reverse();
-            let windowIndex = gcRootPath.findIndex((x) => {
-                return x instanceof WebInspector.HeapSnapshotNode && x.className === "Window";
-            });
+        node.shortestGCRootPath((gcRootPath) => {
+            if (gcRootPath.length) {
+                gcRootPath = gcRootPath.slice().reverse();
+                let windowIndex = gcRootPath.findIndex((x) => {
+                    return x instanceof WebInspector.HeapSnapshotNodeProxy && x.className === "Window";
+                });
+
+                let heapSnapshotRootPath = WebInspector.HeapSnapshotRootPath.emptyPath();
+                for (let i = windowIndex === -1 ? 0 : windowIndex; i < gcRootPath.length; ++i) {
+                    let component = gcRootPath[i];
+                    if (component instanceof WebInspector.HeapSnapshotNodeProxy) {
+                        if (component.className === "Window")
+                            heapSnapshotRootPath = heapSnapshotRootPath.appendGlobalScopeName(component, "window");
+                    } else if (component instanceof WebInspector.HeapSnapshotEdgeProxy)
+                        heapSnapshotRootPath = heapSnapshotRootPath.appendEdge(component);
+                }
 
-            let heapSnapshotRootPath = WebInspector.HeapSnapshotRootPath.emptyPath();
-            for (let i = windowIndex === -1 ? 0 : windowIndex; i < gcRootPath.length; ++i) {
-                let component = gcRootPath[i];
-                if (component instanceof WebInspector.HeapSnapshotNode) {
-                    if (component.className === "Window")
-                        heapSnapshotRootPath = heapSnapshotRootPath.appendGlobalScopeName(component, "window");
-                } else if (component instanceof WebInspector.HeapSnapshotEdge)
-                    heapSnapshotRootPath = heapSnapshotRootPath.appendEdge(component);
+                if (!heapSnapshotRootPath.isFullPathImpossible()) {
+                    synthetic = false;
+                    text = heapSnapshotRootPath.fullPath;
+                }
             }
 
-            if (!heapSnapshotRootPath.isFullPathImpossible()) {
-                synthetic = false;
-                text = heapSnapshotRootPath.fullPath;
+            if (node.className === "string") {
+                HeapAgent.getPreview(heapObjectIdentifier, function(error, string, functionDetails, objectPreviewPayload) {
+                    let remoteObject = error ? WebInspector.RemoteObject.fromPrimitiveValue(undefined) : WebInspector.RemoteObject.fromPrimitiveValue(string);
+                    WebInspector.consoleLogViewController.appendImmediateExecutionWithResult(text, remoteObject, synthetic);                
+                });
+            } else {
+                HeapAgent.getRemoteObject(heapObjectIdentifier, WebInspector.RuntimeManager.ConsoleObjectGroup, function(error, remoteObjectPayload) {
+                    let remoteObject = error ? WebInspector.RemoteObject.fromPrimitiveValue(undefined) : WebInspector.RemoteObject.fromPayload(remoteObjectPayload);
+                    WebInspector.consoleLogViewController.appendImmediateExecutionWithResult(text, remoteObject, synthetic);
+                });
             }
-        }
-
-        if (node.className === "string") {
-            HeapAgent.getPreview(heapObjectIdentifier, function(error, string, functionDetails, objectPreviewPayload) {
-                let remoteObject = error ? WebInspector.RemoteObject.fromPrimitiveValue(undefined) : WebInspector.RemoteObject.fromPrimitiveValue(string);
-                WebInspector.consoleLogViewController.appendImmediateExecutionWithResult(text, remoteObject, synthetic);                
-            });
-        } else {
-            HeapAgent.getRemoteObject(heapObjectIdentifier, WebInspector.RuntimeManager.ConsoleObjectGroup, function(error, remoteObjectPayload) {
-                let remoteObject = error ? WebInspector.RemoteObject.fromPrimitiveValue(undefined) : WebInspector.RemoteObject.fromPayload(remoteObjectPayload);
-                WebInspector.consoleLogViewController.appendImmediateExecutionWithResult(text, remoteObject, synthetic);
-            });
-        }
+        });
     }
 
     // Protected
@@ -211,13 +211,13 @@ WebInspector.HeapSnapshotInstanceDataGridNode = class HeapSnapshotInstanceDataGr
 
             path = path.slice().reverse();
             let windowIndex = path.findIndex((x) => {
-                return x instanceof WebInspector.HeapSnapshotNode && x.className === "Window";
+                return x instanceof WebInspector.HeapSnapshotNodeProxy && x.className === "Window";
             });
 
             let edge = null;
             for (let i = windowIndex === -1 ? 0 : windowIndex; i < path.length; ++i) {
                 let component = path[i];
-                if (component instanceof WebInspector.HeapSnapshotEdge) {
+                if (component instanceof WebInspector.HeapSnapshotEdgeProxy) {
                     edge = component;
                     continue;
                 }
@@ -271,32 +271,31 @@ WebInspector.HeapSnapshotInstanceDataGridNode = class HeapSnapshotInstanceDataGr
 
         function stringifyEdge(edge) {
             switch(edge.type) {
-            case WebInspector.HeapSnapshotEdge.EdgeType.Property:
-            case WebInspector.HeapSnapshotEdge.EdgeType.Variable:
+            case WebInspector.HeapSnapshotEdgeProxy.EdgeType.Property:
+            case WebInspector.HeapSnapshotEdgeProxy.EdgeType.Variable:
                 if (/^(?![0-9])\w+$/.test(edge.data))
                     return edge.data;
                 return "[" + doubleQuotedString(edge.data) + "]";
-            case WebInspector.HeapSnapshotEdge.EdgeType.Index:
+            case WebInspector.HeapSnapshotEdgeProxy.EdgeType.Index:
                 return "[" + edge.data + "]";
-            case WebInspector.HeapSnapshotEdge.EdgeType.Internal:
+            case WebInspector.HeapSnapshotEdgeProxy.EdgeType.Internal:
             default:
                 return null;
             }
         }
 
-        if (this._node.gcRoot) {
-            let textElement = popoverContentElement.appendChild(document.createElement("div"));
-            textElement.textContent = WebInspector.UIString("This object is a root");
-        } else {
-            let path = this._node.shortestGCRootPath;
-            if (path)
+        this._node.shortestGCRootPath((path) => {
+            if (path.length)
                 appendPath(path);
-            else {
+            else if (this._node.gcRoot) {
+                let textElement = popoverContentElement.appendChild(document.createElement("div"));
+                textElement.textContent = WebInspector.UIString("This object is a root");
+            } else {
                 let emptyElement = popoverContentElement.appendChild(document.createElement("div"));
                 emptyElement.textContent = WebInspector.UIString("This object is referenced by internal objects");
             }
-        }
-
-        this._tree.popover.presentNewContentWithFrame(popoverContentElement, targetFrame.pad(2), [WebInspector.RectEdge.MAX_Y, WebInspector.RectEdge.MIN_Y, WebInspector.RectEdge.MAX_X]);
+            
+            this._tree.popover.presentNewContentWithFrame(popoverContentElement, targetFrame.pad(2), [WebInspector.RectEdge.MAX_Y, WebInspector.RectEdge.MIN_Y, WebInspector.RectEdge.MAX_X]);
+        });
     }
 };
index 8c70e58..977b93e 100644 (file)
@@ -27,7 +27,7 @@ WebInspector.HeapSnapshotInstancesContentView = class HeapSnapshotInstancesConte
 {
     constructor(representedObject, extraArguments)
     {
-        console.assert(representedObject instanceof WebInspector.HeapSnapshot);
+        console.assert(representedObject instanceof WebInspector.HeapSnapshotProxy || representedObject instanceof WebInspector.HeapSnapshotDiffProxy);
 
         super(representedObject);
 
index 50134c5..3614532 100644 (file)
@@ -29,7 +29,7 @@ WebInspector.HeapSnapshotInstancesDataGridTree = class HeapSnapshotInstancesData
     {
         super();
 
-        console.assert(heapSnapshot instanceof WebInspector.HeapSnapshot);
+        console.assert(heapSnapshot instanceof WebInspector.HeapSnapshotProxy || heapSnapshot instanceof WebInspector.HeapSnapshotDiffProxy);
 
         this._heapSnapshot = heapSnapshot;
 
index 0ae7802..7a06e48 100644 (file)
@@ -27,7 +27,7 @@ WebInspector.HeapSnapshotSummaryContentView = class HeapSnapshotSummaryContentVi
 {
     constructor(heapSnapshot, extraArguments)
     {
-        console.assert(heapSnapshot instanceof WebInspector.HeapSnapshot);
+        console.assert(heapSnapshot instanceof WebInspector.HeapSnapshotProxy || heapSnapshot instanceof WebInspector.HeapSnapshotDiffProxy);
 
         super(heapSnapshot);
 
@@ -187,42 +187,30 @@ WebInspector.HeapSnapshotSummaryContentView = class HeapSnapshotSummaryContentVi
             appendEmptyMessage.call(this, this._classCountBreakdownLegendElement, WebInspector.UIString("No objects"));
 
         // Allocation size groups.
-        let small = 0;
-        let medium = 0;
-        let large = 0;
-        let veryLarge = 0;
-
         const smallAllocationSize = 48;
         const mediumAllocationSize = 128;
         const largeAllocationSize = 512;
 
-        this._heapSnapshot.instances.forEach(({size}) => {
-            if (size < smallAllocationSize)
-                small++;
-            else if (size < mediumAllocationSize)
-                medium++;
-            else if (size < largeAllocationSize)
-                large++;
-            else
-                veryLarge++;
+        this._heapSnapshot.allocationBucketCounts([smallAllocationSize, mediumAllocationSize, largeAllocationSize], (results) => {
+            let [small, medium, large, veryLarge] = results;
+
+            if (small + medium + large + veryLarge) {
+                appendLegendRow.call(this, this._allocationSizeBreakdownLegendElement, "small", WebInspector.UIString("Small %s").format(Number.bytesToString(smallAllocationSize)), small);
+                appendLegendRow.call(this, this._allocationSizeBreakdownLegendElement, "medium", WebInspector.UIString("Medium %s").format(Number.bytesToString(mediumAllocationSize)), medium);
+                appendLegendRow.call(this, this._allocationSizeBreakdownLegendElement, "large", WebInspector.UIString("Large %s").format(Number.bytesToString(largeAllocationSize)), large);
+                appendLegendRow.call(this, this._allocationSizeBreakdownLegendElement, "very-large", WebInspector.UIString("Very Large"), veryLarge);
+
+                this._allocationSizeBreakdownCircleChart.segments = ["small", "medium", "large", "very-large"];
+                this._allocationSizeBreakdownCircleChart.values = [small, medium, large, veryLarge];
+                this._allocationSizeBreakdownCircleChart.updateLayout();
+
+                let averageAllocationSizeElement = this._allocationSizeBreakdownCircleChart.centerElement.appendChild(document.createElement("div"));
+                averageAllocationSizeElement.classList.add("average-allocation-size");
+                averageAllocationSizeElement.textContent = Number.bytesToString(this._heapSnapshot.totalSize / this._heapSnapshot.totalObjectCount);
+                averageAllocationSizeElement.title = WebInspector.UIString("Average allocation size");
+            } else
+                appendEmptyMessage.call(this, this._allocationSizeBreakdownLegendElement, WebInspector.UIString("No objects"));
         });
-
-        if (small + medium + large + veryLarge) {
-            appendLegendRow.call(this, this._allocationSizeBreakdownLegendElement, "small", WebInspector.UIString("Small %s").format(Number.bytesToString(smallAllocationSize)), small);
-            appendLegendRow.call(this, this._allocationSizeBreakdownLegendElement, "medium", WebInspector.UIString("Medium %s").format(Number.bytesToString(mediumAllocationSize)), medium);
-            appendLegendRow.call(this, this._allocationSizeBreakdownLegendElement, "large", WebInspector.UIString("Large %s").format(Number.bytesToString(largeAllocationSize)), large);
-            appendLegendRow.call(this, this._allocationSizeBreakdownLegendElement, "very-large", WebInspector.UIString("Very Large"), veryLarge);
-
-            this._allocationSizeBreakdownCircleChart.segments = ["small", "medium", "large", "very-large"];
-            this._allocationSizeBreakdownCircleChart.values = [small, medium, large, veryLarge];
-            this._allocationSizeBreakdownCircleChart.updateLayout();
-
-            let averageAllocationSizeElement = this._allocationSizeBreakdownCircleChart.centerElement.appendChild(document.createElement("div"));
-            averageAllocationSizeElement.classList.add("average-allocation-size");
-            averageAllocationSizeElement.textContent = Number.bytesToString(this._heapSnapshot.totalSize / this._heapSnapshot.totalObjectCount);
-            averageAllocationSizeElement.title = WebInspector.UIString("Average allocation size");
-        } else
-            appendEmptyMessage.call(this, this._allocationSizeBreakdownLegendElement, WebInspector.UIString("No objects"));
     }
 
     // Protected
diff --git a/Source/WebInspectorUI/UserInterface/Workers/HeapSnapshot/HeapSnapshot.js b/Source/WebInspectorUI/UserInterface/Workers/HeapSnapshot/HeapSnapshot.js
new file mode 100644 (file)
index 0000000..a03a429
--- /dev/null
@@ -0,0 +1,708 @@
+/*
+ * Copyright (C) 2011 Google Inc. All rights reserved.
+ * Copyright (C) 2016 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:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT
+ * OWNER OR 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.
+ */
+
+// nodes
+// [<0:id>, <1:size>, <2:classNameTableIndex>, <3:internal>];
+const nodeFieldCount = 4;
+const nodeIdOffset = 0;
+const nodeSizeOffset = 1;
+const nodeClassNameOffset = 2;
+const nodeInternalOffset = 3;
+
+// edges
+// [<0:fromId>, <1:toId>, <2:typeTableIndex>, <3:edgeDataIndexOrEdgeNameIndex>]
+const edgeFieldCount = 4;
+const edgeFromIdOffset = 0;
+const edgeToIdOffset = 1;
+const edgeTypeOffset = 2;
+const edgeDataOffset = 3;
+
+// Other constants.
+const rootNodeIndex = 0;
+const rootNodeOrdinal = 0;
+const rootNodeIdentifier = 0;
+
+// Terminology:
+//   - `nodeIndex` is an index into the `nodes` list.
+//   - `nodeOrdinal` is the order of the node in the `nodes` list. (nodeIndex / nodeFieldCount)
+//   - `nodeIdentifier` is the node's id value. (nodes[nodeIndex + nodeIdOffset]).
+//   - `edgeIndex` is an index into the `edges` list.
+//
+// Lists:
+//   - _nodeOrdinalToFirstOutgoingEdge - `nodeOrdinal` to `edgeIndex` in `edges`.
+//     Iterate edges by walking `edges` (edgeFieldCount) and checking if fromIdentifier is current.
+//   - _nodeOrdinalToFirstIncomingEdge - `nodeOrdinal` to `incomingEdgeIndex` in `incomingEdges`.
+//     Iterate edges by walking `incomingEdges` until `nodeOrdinal+1`'s first incoming edge index.
+//   - _nodeOrdinalToDominatorNodeOrdinal - `nodeOrdinal` to `nodeOrdinal` of dominator
+//   - _nodeOrdinalToRetainedSizes - `nodeOrdinal` to retain size value
+//
+// Temporary Lists:
+//   - nodeOrdinalToPostOrderIndex - `nodeOrdinal` to a `postOrderIndex`.
+//   - postOrderIndexToNodeOrdinal - `postOrderIndex` to a `nodeOrdinal`.
+
+let nextSnapshotIdentifier = 1;
+
+HeapSnapshot = class HeapSnapshot
+{
+    constructor(objectId, snapshotDataString)
+    {
+        this._identifier = nextSnapshotIdentifier++;
+        this._objectId = objectId;
+
+        let json = JSON.parse(snapshotDataString);
+        snapshotDataString = null;
+
+        let {version, nodes, nodeClassNames, edges, edgeTypes, edgeNames} = json;
+        console.assert(version === 1, "Expect JavaScriptCore Heap Snapshot version 1");
+
+        this._nodes = nodes;
+        this._nodeCount = nodes.length / nodeFieldCount;
+
+        this._edges = edges;
+        this._edgeCount = edges.length / edgeFieldCount;
+
+        this._edgeTypesTable = edgeTypes;
+        this._edgeNamesTable = edgeNames;
+        this._nodeClassNamesTable = nodeClassNames;
+
+        this._totalSize = 0;
+        this._nodeIdentifierToOrdinal = new Map; // <node identifier> => nodeOrdinal
+        for (let nodeIndex = 0; nodeIndex < nodes.length; nodeIndex += nodeFieldCount) {
+            let nodeOrdinal = nodeIndex / nodeFieldCount;
+            this._nodeIdentifierToOrdinal.set(nodes[nodeIndex + nodeIdOffset], nodeOrdinal);
+            this._totalSize += nodes[nodeIndex + nodeSizeOffset];
+        }
+
+        // FIXME: Replace toIdentifier and fromIdentifier in edges with nodeIndex to reduce hash lookups?
+
+        this._nodeOrdinalToFirstOutgoingEdge = new Uint32Array(this._nodeCount); // nodeOrdinal => edgeIndex
+        this._buildOutgoingEdges();
+
+        this._nodeOrdinalToFirstIncomingEdge = new Uint32Array(this._nodeCount + 1); // nodeOrdinal => incomingNodes/incomingEdges index
+        this._incomingNodes = new Uint32Array(this._edgeCount); // from nodeOrdinals.
+        this._incomingEdges = new Uint32Array(this._edgeCount); // edgeIndex.
+        this._buildIncomingEdges();
+
+        let {nodeOrdinalToPostOrderIndex, postOrderIndexToNodeOrdinal} = this._buildPostOrderIndexes();
+
+        this._nodeOrdinalToDominatorNodeOrdinal = new Uint32Array(this._nodeCount);
+        this._nodeOrdinalIsGCRoot = new Uint8Array(this._nodeCount);
+        this._buildDominatorIndexes(nodeOrdinalToPostOrderIndex, postOrderIndexToNodeOrdinal);
+
+        nodeOrdinalToPostOrderIndex = null;
+
+        this._nodeOrdinalToRetainedSizes = new Uint32Array(this._nodeCount);
+        this._buildRetainedSizes(postOrderIndexToNodeOrdinal);
+
+        postOrderIndexToNodeOrdinal = null;
+
+        this._categories = HeapSnapshot.buildCategories(this);
+    }
+
+    // Static
+
+    static buildCategories(snapshot, allowNodeIdentifierCallback)
+    {
+        let categories = {};
+
+        let nodes = snapshot._nodes;
+        let nodeClassNamesTable = snapshot._nodeClassNamesTable;
+        let nodeOrdinalToRetainedSizes = snapshot._nodeOrdinalToRetainedSizes;
+
+        // Skip the <root> node.
+        let firstNodeIndex = nodeFieldCount;
+        let firstNodeOrdinal = 1;
+        for (let nodeIndex = firstNodeIndex, nodeOrdinal = firstNodeOrdinal; nodeIndex < nodes.length; nodeIndex += nodeFieldCount, nodeOrdinal++) {
+            if (allowNodeIdentifierCallback && !allowNodeIdentifierCallback(nodes[nodeIndex + nodeIdOffset]))
+                continue;
+
+            let classNameTableIndex = nodes[nodeIndex + nodeClassNameOffset];
+            let className = nodeClassNamesTable[classNameTableIndex];
+            let size = nodes[nodeIndex + nodeSizeOffset];
+            let retainedSize = nodeOrdinalToRetainedSizes[nodeOrdinal];
+            let internal = nodes[nodeIndex + nodeInternalOffset] ? true : false;
+
+            let category = categories[className];
+            if (!category)
+                category = categories[className] = {className, size: 0, retainedSize: 0, count: 0, internalCount: 0};
+
+            category.size += size;
+            category.retainedSize += retainedSize;
+            category.count += 1;
+            if (internal)
+                category.internalCount += 1;
+        }
+
+        return categories;
+    }
+
+    static allocationBucketCounts(snapshot, bucketSizes, allowNodeIdentifierCallback)
+    {
+        let counts = new Array(bucketSizes.length + 1);
+        let remainderBucket = counts.length - 1;
+        counts.fill(0);
+
+        let nodes = snapshot._nodes;
+
+        // Skip the <root> node.
+        let firstNodeIndex = nodeFieldCount;
+
+    outer:
+        for (let nodeIndex = firstNodeIndex; nodeIndex < nodes.length; nodeIndex += nodeFieldCount) {
+            if (allowNodeIdentifierCallback && !allowNodeIdentifierCallback(nodes[nodeIndex + nodeIdOffset]))
+                continue;
+
+            let size = nodes[nodeIndex + nodeSizeOffset];
+            for (let i = 0; i < bucketSizes.length; ++i) {
+                if (size < bucketSizes[i]) {
+                    counts[i]++;
+                    continue outer;
+                }
+            }
+            counts[remainderBucket]++;
+        }
+
+        return counts;
+    }
+
+    static instancesWithClassName(snapshot, className, allowNodeIdentifierCallback)
+    {
+        let instances = [];
+
+        let nodes = snapshot._nodes;
+        let nodeClassNamesTable = snapshot._nodeClassNamesTable;
+
+        // Skip the <root> node.
+        let firstNodeIndex = nodeFieldCount;
+        let firstNodeOrdinal = 1;
+        for (let nodeIndex = firstNodeIndex, nodeOrdinal = firstNodeOrdinal; nodeIndex < nodes.length; nodeIndex += nodeFieldCount, nodeOrdinal++) {
+            if (allowNodeIdentifierCallback && !allowNodeIdentifierCallback(nodes[nodeIndex + nodeIdOffset]))
+                continue;
+
+            let classNameTableIndex = nodes[nodeIndex + nodeClassNameOffset];
+            if (nodeClassNamesTable[classNameTableIndex] === className)
+                instances.push(nodeIndex);
+        }
+
+        return instances.map(snapshot.serializeNode.bind(snapshot));
+    }
+
+    // Worker Methods
+
+    allocationBucketCounts(bucketSizes)
+    {
+        return HeapSnapshot.allocationBucketCounts(this, bucketSizes);
+    }
+
+    instancesWithClassName(className)
+    {
+        return HeapSnapshot.instancesWithClassName(this, className);
+    }
+
+    nodeWithIdentifier(nodeIdentifier)
+    {
+        let nodeOrdinal = this._nodeIdentifierToOrdinal.get(nodeIdentifier);
+        let nodeIndex = nodeOrdinal * nodeFieldCount;
+        return this.serializeNode(nodeIndex);
+    }
+
+    shortestGCRootPath(nodeIdentifier)
+    {
+        // Returns an array from this node to a gcRoot node.
+        // E.g. [Node, Edge, Node, Edge, Node].
+        // Internal nodes are avoided, so if the path is empty this
+        // node is either a gcRoot or only reachable via Internal nodes.
+
+        let paths = this._gcRootPathes(nodeIdentifier);
+        if (!paths.length)
+            return [];
+
+        paths.sort((a, b) => a.length - b.length);
+        let shortestPath = paths[0];
+
+        console.assert("node" in shortestPath[0], "Path should start with a node");
+        console.assert("node" in shortestPath[shortestPath.length - 1], "Path should end with a node");
+
+        return shortestPath.map((component) => {
+            if (component.node)
+                return this.serializeNode(component.node);
+            return this.serializeEdge(component.edge);
+        });
+    }
+
+    dominatedNodes(nodeIdentifier)
+    {
+        let dominatedNodes = [];
+
+        let targetNodeOrdinal = this._nodeIdentifierToOrdinal.get(nodeIdentifier);
+        for (let nodeOrdinal = 0; nodeOrdinal < this._nodeCount; ++nodeOrdinal) {
+            if (this._nodeOrdinalToDominatorNodeOrdinal[nodeOrdinal] === targetNodeOrdinal)
+                dominatedNodes.push(nodeOrdinal * nodeFieldCount);
+        }
+
+        return dominatedNodes.map(this.serializeNode.bind(this));
+    }
+
+    retainedNodes(nodeIdentifier)
+    {
+        let retainedNodes = [];
+
+        let nodeOrdinal = this._nodeIdentifierToOrdinal.get(nodeIdentifier);
+        let edgeIndex = this._nodeOrdinalToFirstOutgoingEdge[nodeOrdinal];
+        for (; this._edges[edgeIndex + edgeFromIdOffset] === nodeIdentifier; edgeIndex += edgeFieldCount) {
+            let toNodeIdentifier = this._edges[edgeIndex + edgeToIdOffset];
+            let toNodeOrdinal = this._nodeIdentifierToOrdinal.get(toNodeIdentifier);
+            let toNodeIndex = toNodeOrdinal * nodeFieldCount;
+            retainedNodes.push(toNodeIndex);
+        }
+
+        return retainedNodes.map(this.serializeNode.bind(this));
+    }
+
+    retainers(nodeIdentifier)
+    {
+        let retainers = [];
+
+        let nodeOrdinal = this._nodeIdentifierToOrdinal.get(nodeIdentifier);
+        let incomingEdgeIndex = this._nodeOrdinalToFirstIncomingEdge[nodeOrdinal];
+        let incomingEdgeIndexEnd = this._nodeOrdinalToFirstIncomingEdge[nodeOrdinal + 1];
+        for (let edgeIndex = incomingEdgeIndex; edgeIndex < incomingEdgeIndexEnd; ++edgeIndex) {
+            let fromNodeOrdinal = this._incomingNodes[edgeIndex];
+            let fromNodeIndex = fromNodeOrdinal * nodeFieldCount;
+            retainers.push(fromNodeIndex);
+        }
+
+        return retainers.map(this.serializeNode.bind(this));
+    }
+
+    // Public
+
+    serialize()
+    {
+        return {
+            identifier: this._identifier,
+            totalSize: this._totalSize,
+            totalObjectCount: this._nodeCount - 1, // <root>.
+            categories: this._categories,
+        };
+    }
+
+    serializeNode(nodeIndex)
+    {
+        console.assert((nodeIndex % nodeFieldCount) === 0, "Invalid nodeIndex to serialize");
+
+        let nodeOrdinal = nodeIndex / nodeFieldCount;
+
+        return {
+            id: this._nodes[nodeIndex + nodeIdOffset],
+            className: this._nodeClassNamesTable[this._nodes[nodeIndex + nodeClassNameOffset]],
+            size: this._nodes[nodeIndex + nodeSizeOffset],
+            retainedSize: this._nodeOrdinalToRetainedSizes[nodeOrdinal],
+            internal: this._nodes[nodeIndex + nodeInternalOffset] ? true : false,
+            gcRoot: this._nodeOrdinalIsGCRoot[nodeOrdinal] ? true : false,
+        };
+    }
+
+    serializeEdge(edgeIndex)
+    {
+        console.assert((edgeIndex % edgeFieldCount) === 0, "Invalid edgeIndex to serialize");
+
+        let edgeType = this._edgeTypesTable[this._edges[edgeIndex + edgeTypeOffset]];
+        let edgeData = this._edges[edgeIndex + edgeDataOffset];
+        switch (edgeType) {
+        case "Internal":
+            // edgeData can be ignored.
+            break;
+        case "Property":
+        case "Variable":
+            // edgeData is a table index.
+            edgeData = this._edgeNamesTable[edgeData];
+            break;
+        case "Index":
+            // edgeData is the index.
+            break;
+        default:
+            console.error("Unexpected edge type: " + edgeType);
+            break;
+        }
+
+        return {
+            from: this._edges[edgeIndex + edgeFromIdOffset],
+            to: this._edges[edgeIndex + edgeToIdOffset],
+            type: edgeType,
+            data: edgeData,
+        };
+    }
+
+    // Private
+
+    _buildOutgoingEdges()
+    {
+        let lastFromIdentifier = -1;
+        for (let edgeIndex = 0; edgeIndex < this._edges.length; edgeIndex += edgeFieldCount) {
+            let fromIdentifier = this._edges[edgeIndex + edgeFromIdOffset];
+            console.assert(lastFromIdentifier <= fromIdentifier, "Edge list should be ordered by from node identifier");
+            if (fromIdentifier !== lastFromIdentifier) {
+                let nodeOrdinal = this._nodeIdentifierToOrdinal.get(fromIdentifier);
+                this._nodeOrdinalToFirstOutgoingEdge[nodeOrdinal] = edgeIndex;
+                lastFromIdentifier = fromIdentifier;
+            }
+        }
+    }
+
+    _buildIncomingEdges()
+    {
+        // First calculate the count of incoming edges for each node.
+        for (let edgeIndex = 0; edgeIndex < this._edges.length; edgeIndex += edgeFieldCount) {
+            let toIdentifier = this._edges[edgeIndex + edgeToIdOffset];
+            let toNodeOrdinal = this._nodeIdentifierToOrdinal.get(toIdentifier);
+            this._nodeOrdinalToFirstIncomingEdge[toNodeOrdinal]++;
+        }
+
+        // Replace the counts with what will be the resulting index by running up the counts.
+        // Store the counts in what will be the edges list to use when placing edges in the list.
+        let runningFirstIndex = 0;
+        for (let nodeOrdinal = 0; nodeOrdinal < this._nodeCount; ++nodeOrdinal) {
+            let count = this._nodeOrdinalToFirstIncomingEdge[nodeOrdinal];
+            this._nodeOrdinalToFirstIncomingEdge[nodeOrdinal] = runningFirstIndex;
+            this._incomingNodes[runningFirstIndex] = count;
+            runningFirstIndex += count;            
+        }
+
+        // Fill in the incoming edges list. Use the count as an offset when placing edges in the list.
+        for (let edgeIndex = 0; edgeIndex < this._edges.length; edgeIndex += edgeFieldCount) {
+            let fromIdentifier = this._edges[edgeIndex + edgeFromIdOffset];
+            let fromNodeOrdinal = this._nodeIdentifierToOrdinal.get(fromIdentifier);
+            let toIdentifier = this._edges[edgeIndex + edgeToIdOffset];
+            let toNodeOrdinal = this._nodeIdentifierToOrdinal.get(toIdentifier);
+
+            let firstIncomingEdgeIndex = this._nodeOrdinalToFirstIncomingEdge[toNodeOrdinal];
+            console.assert(this._incomingNodes[firstIncomingEdgeIndex] > 0, "Should be expecting edges for this node");
+            let countAsOffset = this._incomingNodes[firstIncomingEdgeIndex]--;
+            let index = firstIncomingEdgeIndex + countAsOffset - 1;
+            this._incomingNodes[index] = fromNodeOrdinal;
+            this._incomingEdges[index] = edgeIndex;
+        }
+
+        // Duplicate value on the end. Incoming edge iteration walks firstIncomingEdge(ordinal) to firstIncomingEdge(ordinal+1).
+        this._nodeOrdinalToFirstIncomingEdge[this._nodeCount] = this._nodeOrdinalToFirstIncomingEdge[this._nodeCount - 1];
+    }
+
+    _buildPostOrderIndexes()
+    {
+        let postOrderIndex = 0;
+        let nodeOrdinalToPostOrderIndex = new Uint32Array(this._nodeCount);
+        let postOrderIndexToNodeOrdinal = new Uint32Array(this._nodeCount);
+
+        let stackNodes = new Uint32Array(this._nodeCount); // nodeOrdinal.
+        let stackEdges = new Uint32Array(this._nodeCount); // edgeIndex.
+        let visited = new Uint8Array(this._nodeCount);
+
+        let stackTop = 0;
+        stackNodes[stackTop] = rootNodeOrdinal;
+        stackEdges[stackTop] = this._nodeOrdinalToFirstOutgoingEdge[rootNodeOrdinal];
+
+        while (stackTop >= 0) {
+            let nodeOrdinal = stackNodes[stackTop];
+            let nodeIdentifier = this._nodes[(nodeOrdinal * nodeFieldCount) + nodeIdOffset];
+            let edgeIndex = stackEdges[stackTop];
+
+            if (this._edges[edgeIndex + edgeFromIdOffset] === nodeIdentifier) {
+                // Prepare the next child for the current node.
+                stackEdges[stackTop] += edgeFieldCount;
+
+                let toIdentifier = this._edges[edgeIndex + edgeToIdOffset];
+                let toNodeOrdinal = this._nodeIdentifierToOrdinal.get(toIdentifier);
+                if (visited[toNodeOrdinal])
+                    continue;
+
+                // Child.
+                stackTop++;
+                stackNodes[stackTop] = toNodeOrdinal;
+                stackEdges[stackTop] = this._nodeOrdinalToFirstOutgoingEdge[toNodeOrdinal];
+                visited[toNodeOrdinal] = 1;
+            } else {
+                // Self.
+                nodeOrdinalToPostOrderIndex[nodeOrdinal] = postOrderIndex;
+                postOrderIndexToNodeOrdinal[postOrderIndex] = nodeOrdinal;
+                postOrderIndex++;
+                stackTop--;
+            }
+        }
+
+        // Unvisited nodes.
+        // This can happen if the parent node was disallowed on the backend, but other nodes
+        // that were only referenced from that disallowed node were eventually allowed because
+        // they may be generic system objects. Give these nodes a postOrderIndex anyways.
+        if (postOrderIndex !== this._nodeCount) {
+            // Root was the last node visited. Revert assigning it an index, add it back at the end.
+            postOrderIndex--;
+
+            // Visit unvisited nodes.
+            for (let nodeOrdinal = 1; nodeOrdinal < this._nodeCount; ++nodeOrdinal) {
+                if (visited[nodeOrdinal])
+                    continue;
+                nodeOrdinalToPostOrderIndex[nodeOrdinal] = postOrderIndex;
+                postOrderIndexToNodeOrdinal[postOrderIndex] = nodeOrdinal;
+                postOrderIndex++;
+            }
+
+            // Visit root again.
+            nodeOrdinalToPostOrderIndex[rootNodeOrdinal] = postOrderIndex;
+            postOrderIndexToNodeOrdinal[postOrderIndex] = rootNodeOrdinal;
+            postOrderIndex++;
+        }
+
+        console.assert(postOrderIndex === this._nodeCount, "All nodes were visited");
+        console.assert(nodeOrdinalToPostOrderIndex[rootNodeOrdinal] === this._nodeCount - 1, "Root node should have the last possible postOrderIndex");
+
+        return {nodeOrdinalToPostOrderIndex, postOrderIndexToNodeOrdinal};
+    }
+
+    _buildDominatorIndexes(nodeOrdinalToPostOrderIndex, postOrderIndexToNodeOrdinal)
+    {
+        // The algorithm is based on the article:
+        // K. Cooper, T. Harvey and K. Kennedy "A Simple, Fast Dominance Algorithm"
+
+        let rootPostOrderIndex = this._nodeCount - 1;
+        let noEntry = this._nodeCount;
+
+        let affected = new Uint8Array(this._nodeCount);
+        let dominators = new Uint32Array(this._nodeCount);
+
+        // Initialize with unset value.
+        dominators.fill(noEntry);
+
+        // Mark the root's dominator value.
+        dominators[rootPostOrderIndex] = rootPostOrderIndex;        
+
+        // Affect the root's children. Also use this opportunity to mark them as GC roots.
+        let rootEdgeIndex = this._nodeOrdinalToFirstOutgoingEdge[rootNodeOrdinal];
+        for (let edgeIndex = rootEdgeIndex; this._edges[edgeIndex + edgeFromIdOffset] === rootNodeIdentifier; edgeIndex += edgeFieldCount) {
+            let toIdentifier = this._edges[edgeIndex + edgeToIdOffset];
+            let toNodeOrdinal = this._nodeIdentifierToOrdinal.get(toIdentifier);
+            let toPostOrderIndex = nodeOrdinalToPostOrderIndex[toNodeOrdinal];
+            affected[toPostOrderIndex] = 1;
+            this._nodeOrdinalIsGCRoot[toNodeOrdinal] = 1;
+        }
+
+        let changed = true;
+        while (changed) {
+            changed = false;
+
+            for (let postOrderIndex = rootPostOrderIndex - 1; postOrderIndex >= 0; --postOrderIndex) {
+                if (!affected[postOrderIndex])
+                    continue;
+                affected[postOrderIndex] = 0;
+
+                // The dominator is already the root, nothing to do.
+                if (dominators[postOrderIndex] === rootPostOrderIndex)
+                    continue;
+
+                let newDominatorIndex = noEntry;
+                let nodeOrdinal = postOrderIndexToNodeOrdinal[postOrderIndex];
+                let incomingEdgeIndex = this._nodeOrdinalToFirstIncomingEdge[nodeOrdinal];
+                let incomingEdgeIndexEnd = this._nodeOrdinalToFirstIncomingEdge[nodeOrdinal + 1];
+                for (let edgeIndex = incomingEdgeIndex; edgeIndex < incomingEdgeIndexEnd; ++edgeIndex) {
+                    let fromNodeOrdinal = this._incomingNodes[edgeIndex];
+                    let fromPostOrderIndex = nodeOrdinalToPostOrderIndex[fromNodeOrdinal];
+                    if (dominators[fromPostOrderIndex] !== noEntry) {
+                        if (newDominatorIndex === noEntry)
+                            newDominatorIndex = fromPostOrderIndex;
+                        else {
+                            while (fromPostOrderIndex !== newDominatorIndex) {
+                                while (fromPostOrderIndex < newDominatorIndex)
+                                    fromPostOrderIndex = dominators[fromPostOrderIndex];
+                                while (newDominatorIndex < fromPostOrderIndex)
+                                    newDominatorIndex = dominators[newDominatorIndex];
+                            }
+                        }
+                    }
+                    if (newDominatorIndex === rootPostOrderIndex)
+                        break;
+                }
+
+                // Changed. Affect children.
+                if (newDominatorIndex !== noEntry && dominators[postOrderIndex] !== newDominatorIndex) {
+                    dominators[postOrderIndex] = newDominatorIndex;
+                    changed = true;
+
+                    let outgoingEdgeIndex = this._nodeOrdinalToFirstOutgoingEdge[nodeOrdinal];
+                    let nodeIdentifier = this._nodes[(nodeOrdinal * 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);
+                        let toNodePostOrder = nodeOrdinalToPostOrderIndex[toNodeOrdinal];
+                        affected[toNodePostOrder] = 1;
+                    }
+                }
+            }
+        }
+
+        for (let postOrderIndex = 0; postOrderIndex < this._nodeCount; ++postOrderIndex) {
+            let nodeOrdinal = postOrderIndexToNodeOrdinal[postOrderIndex];
+            let dominatorNodeOrdinal = postOrderIndexToNodeOrdinal[dominators[postOrderIndex]];
+            this._nodeOrdinalToDominatorNodeOrdinal[nodeOrdinal] = dominatorNodeOrdinal;
+        }
+    }
+
+    _buildRetainedSizes(postOrderIndexToNodeOrdinal)
+    {
+        // Self size.
+        for (let nodeIndex = 0, nodeOrdinal = 0; nodeOrdinal < this._nodeCount; nodeIndex += nodeFieldCount, nodeOrdinal++)
+            this._nodeOrdinalToRetainedSizes[nodeOrdinal] = this._nodes[nodeIndex + nodeSizeOffset];
+
+        // Attribute size to dominator.
+        for (let postOrderIndex = 0; postOrderIndex < this._nodeCount - 1; ++postOrderIndex) {
+            let nodeOrdinal = postOrderIndexToNodeOrdinal[postOrderIndex];
+            let nodeRetainedSize = this._nodeOrdinalToRetainedSizes[nodeOrdinal];
+            let dominatorNodeOrdinal = this._nodeOrdinalToDominatorNodeOrdinal[nodeOrdinal];
+            this._nodeOrdinalToRetainedSizes[dominatorNodeOrdinal] += nodeRetainedSize;
+        }
+    }
+
+    _gcRootPathes(nodeIdentifier)
+    {
+        let targetNodeOrdinal = this._nodeIdentifierToOrdinal.get(nodeIdentifier);
+
+        if (this._nodeOrdinalIsGCRoot[targetNodeOrdinal])
+            return [];
+
+        // FIXME: Array push/pop can affect performance here, but in practice it hasn't been an issue.
+
+        let paths = [];
+        let currentPath = [];
+        let visited = new Uint8Array(this._nodeCount);
+
+        function visitNode(nodeOrdinal)
+        {
+            if (this._nodeOrdinalIsGCRoot[nodeOrdinal]) {
+                let fullPath = currentPath.slice();
+                let nodeIndex = nodeOrdinal * nodeFieldCount;
+                fullPath.push({node: nodeIndex});
+                paths.push(fullPath);
+                return;
+            }
+
+            if (visited[nodeOrdinal])
+                return;
+            visited[nodeOrdinal] = 1;
+
+            let nodeIndex = nodeOrdinal * nodeFieldCount;
+            currentPath.push({node: nodeIndex});
+
+            // Loop in reverse order because edges were added in reverse order.
+            // It doesn't particularly matter other then consistency with previous code.
+            let incomingEdgeIndexStart = this._nodeOrdinalToFirstIncomingEdge[nodeOrdinal];
+            let incomingEdgeIndexEnd = this._nodeOrdinalToFirstIncomingEdge[nodeOrdinal + 1];
+            for (let incomingEdgeIndex = incomingEdgeIndexEnd - 1; incomingEdgeIndex >= incomingEdgeIndexStart; --incomingEdgeIndex) {
+                let fromNodeOrdinal = this._incomingNodes[incomingEdgeIndex];
+                let fromNodeIndex = fromNodeOrdinal * nodeFieldCount;
+                let fromNodeIsInternal = this._nodes[fromNodeIndex + nodeInternalOffset];
+                if (fromNodeIsInternal)
+                    continue;
+
+                let edgeIndex = this._incomingEdges[incomingEdgeIndex];
+                currentPath.push({edge: edgeIndex});
+                visitNode.call(this, fromNodeOrdinal);
+                currentPath.pop();
+            }
+
+            currentPath.pop();
+        }
+
+        visitNode.call(this, targetNodeOrdinal);
+
+        return paths;
+    }
+};
+
+HeapSnapshotDiff = class HeapSnapshotDiff
+{
+    constructor(objectId, snapshot1, snapshot2)
+    {
+        this._objectId = objectId;
+
+        this._snapshot1 = snapshot1;
+        this._snapshot2 = snapshot2;
+
+        this._totalSize = 0;
+        this._addedNodeIdentifiers = new Set;
+
+        let known = new Map;
+        for (let nodeIndex = 0; nodeIndex < this._snapshot1._nodes.length; nodeIndex += nodeFieldCount) {
+            let nodeIdentifier = this._snapshot1._nodes[nodeIndex + nodeIdOffset];
+            known.set(nodeIdentifier, nodeIndex);
+        }
+
+        for (let nodeIndex = 0; nodeIndex < this._snapshot2._nodes.length; nodeIndex += nodeFieldCount) {
+            let nodeIdentifier = this._snapshot2._nodes[nodeIndex + nodeIdOffset];
+            let existed = known.delete(nodeIdentifier);
+            if (!existed) {
+                this._addedNodeIdentifiers.add(nodeIdentifier);
+                this._totalSize += this._snapshot2._nodes[nodeIndex + nodeSizeOffset];
+            }
+        }
+
+        this._categories = HeapSnapshot.buildCategories(this._snapshot2, (nodeIdentifier) => this._addedNodeIdentifiers.has(nodeIdentifier));
+    }
+
+    // Worker Methods
+
+    allocationBucketCounts(bucketSizes)
+    {
+        return HeapSnapshot.allocationBucketCounts(this._snapshot2, bucketSizes, (nodeIdentifier) => this._addedNodeIdentifiers.has(nodeIdentifier));
+    }
+
+    instancesWithClassName(className)
+    {
+        return HeapSnapshot.instancesWithClassName(this._snapshot2, className, (nodeIdentifier) => this._addedNodeIdentifiers.has(nodeIdentifier));
+    }
+
+    nodeWithIdentifier(nodeIdentifier) { return this._snapshot2.nodeWithIdentifier(nodeIdentifier); }
+    shortestGCRootPath(nodeIdentifier) { return this._snapshot2.shortestGCRootPath(nodeIdentifier); }
+    dominatedNodes(nodeIdentifier) { return this._snapshot2.dominatedNodes(nodeIdentifier); }
+    retainedNodes(nodeIdentifier) { return this._snapshot2.retainedNodes(nodeIdentifier); }
+    retainers(nodeIdentifier) { return this._snapshot2.retainers(nodeIdentifier); }
+
+    // Public
+
+    serialize()
+    {
+        return {
+            snapshot1: this._snapshot1.serialize(),
+            snapshot2: this._snapshot2.serialize(),
+            totalSize: this._totalSize,
+            totalObjectCount: this._addedNodeIdentifiers.size,
+            categories: this._categories,
+        };
+    }
+};
diff --git a/Source/WebInspectorUI/UserInterface/Workers/HeapSnapshot/HeapSnapshotWorker.js b/Source/WebInspectorUI/UserInterface/Workers/HeapSnapshot/HeapSnapshotWorker.js
new file mode 100644 (file)
index 0000000..b51b56c
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+importScripts(...[
+    "HeapSnapshot.js"
+]);
+
+HeapSnapshotWorker = class HeapSnapshotWorker
+{
+    constructor()
+    {
+        this._nextObjectId = 1;
+        this._objects = new Map;
+
+        self.addEventListener("message", this._handleMessage.bind(this));
+    }
+
+    // Actions
+
+    createSnapshot(snapshotString)
+    {
+        let objectId = this._nextObjectId++;
+        let snapshot = new HeapSnapshot(objectId, snapshotString);
+        this._objects.set(objectId, snapshot);
+        return {objectId, snapshot: snapshot.serialize()};
+    }
+
+    createSnapshotDiff(objectId1, objectId2)
+    {
+        let snapshot1 = this._objects.get(objectId1);
+        let snapshot2 = this._objects.get(objectId2);
+
+        console.assert(snapshot1 instanceof HeapSnapshot);
+        console.assert(snapshot2 instanceof HeapSnapshot);
+
+        let objectId = this._nextObjectId++;
+        let snapshotDiff = new HeapSnapshotDiff(objectId, snapshot1, snapshot2);
+        this._objects.set(objectId, snapshotDiff);
+        return {objectId, snapshotDiff: snapshotDiff.serialize()};
+    }
+
+    // Public
+
+    sendEvent(eventName, eventData)
+    {
+        self.postMessage({eventName, eventData});
+    }
+
+    // Private
+    
+    _handleMessage(event)
+    {
+        let data = event.data;
+
+        // Action.
+        if (data.actionName) {
+            let result = this[data.actionName](...data.actionArguments);
+            self.postMessage({callId: data.callId, result});
+            return;
+        }
+
+        // Method.
+        if (data.methodName) {
+            console.assert(data.objectId, "Must have an objectId to call the method on");
+            let object = this._objects.get(data.objectId);
+            let result = object[data.methodName](...data.methodArguments);
+            self.postMessage({callId: data.callId, result});
+            return;
+        }
+
+        console.error("Unexpected HeapSnapshotWorker message", data);
+    }
+};
+
+self.heapSnapshotWorker = new HeapSnapshotWorker;