Web Inspector: Redesign summary view / retaining tree contents
[WebKit-https.git] / Source / WebCore / inspector / front-end / DetailedHeapshotGridNodes.js
1 /*
2  * Copyright (C) 2011 Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *     * Neither the name of Google Inc. nor the names of its
15  * contributors may be used to endorse or promote products derived from
16  * this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 WebInspector.HeapSnapshotGridNode = function(tree, hasChildren)
32 {
33     WebInspector.DataGridNode.call(this, null, hasChildren);
34     this._defaultPopulateCount = tree._defaultPopulateCount;
35     this._provider = null;
36     this.addEventListener("populate", this._populate, this);
37 }
38
39 WebInspector.HeapSnapshotGridNode.prototype = {
40     createCell: function(columnIdentifier)
41     {
42         var cell = WebInspector.DataGridNode.prototype.createCell.call(this, columnIdentifier);
43         if (this._searchMatched)
44             cell.addStyleClass("highlight");
45         return cell;
46     },
47
48     dispose: function()
49     {
50         if (this._provider)
51             this._provider.dispose();
52         for (var node = this.children[0]; node; node = node.traverseNextNode(true, this, true))
53             if (node.dispose)
54                 node.dispose();
55     },
56
57     hasHoverMessage: false,
58
59     queryObjectContent: function(callback)
60     {
61     },
62
63     _toPercentString: function(num)
64     {
65         return num.toFixed(2) + "%";
66     },
67
68     _createValueCell: function(columnIdentifier)
69     {
70         var cell = document.createElement("td");
71         cell.className = columnIdentifier + "-column";
72         var div = document.createElement("div");
73         var valueSpan = document.createElement("span");
74         valueSpan.textContent = this.data[columnIdentifier];
75         var percentColumn = columnIdentifier + "-percent";
76         if (percentColumn in this.data) {
77             var percentSpan = document.createElement("span");
78             percentSpan.className = "percent-column";
79             percentSpan.textContent = this.data[percentColumn];
80             div.appendChild(percentSpan);
81         }
82         div.appendChild(valueSpan);
83         cell.appendChild(div);
84         return cell;
85     },
86
87     _populate: function(event)
88     {
89         this.removeEventListener("populate", this._populate, this);
90         function sorted(ignored)
91         {
92             this.populateChildren();
93         }
94         this._provider.sortAndRewind(this.comparator(), sorted.bind(this));
95     },
96
97     populateChildren: function(provider, howMany, atIndex, afterPopulate, suppressNotifyAboutCompletion)
98     {
99         if (!howMany && provider) {
100             howMany = provider.instanceCount;
101             provider.instanceCount = 0;
102         }
103         provider = provider || this._provider;
104         if (!("instanceCount" in provider))
105             provider.instanceCount = 0;
106         howMany = howMany || this._defaultPopulateCount;
107         atIndex = atIndex || this.children.length;
108         var haveSavedChildren = !!this._savedChildren;
109         if (haveSavedChildren) {
110             haveSavedChildren = false;
111             for (var c in this._savedChildren) {
112                 haveSavedChildren = true;
113                 break;
114             }
115         }
116
117         var part = 0;
118         function callSerialize()
119         {
120             if (part >= howMany)
121                 return;
122             part += this._defaultPopulateCount;
123             provider.serializeNextItems(this._defaultPopulateCount, childrenRetrieved.bind(this));
124         }
125         function childrenRetrieved(items)
126         {
127             var length = items.totalLength;
128             for (var i = 0, l = items.length; i < l; ++i) {
129                 var item = items[i];
130                 if (haveSavedChildren) {
131                     var hash = this._childHashForEntity(item);
132                     if (hash in this._savedChildren) {
133                         this.insertChild(this._savedChildren[hash], atIndex++);
134                         continue;
135                     }
136                 }
137                 this.insertChild(this._createChildNode(item, provider, this), atIndex++);
138             }
139             provider.instanceCount += items.length;
140             if (part < howMany) {
141                 setTimeout(callSerialize.bind(this), 0);
142                 return;
143             }
144
145             if (items.hasNext)
146                 this.insertChild(new WebInspector.ShowMoreDataGridNode(this.populateChildren.bind(this, provider), this._defaultPopulateCount, length), atIndex++);
147             if (afterPopulate)
148                 afterPopulate();
149             if (!suppressNotifyAboutCompletion) {
150                 function notify()
151                 {
152                     this.dispatchEventToListeners("populate complete");
153                 }
154                 setTimeout(notify.bind(this), 0);
155             }
156         }
157         setTimeout(callSerialize.bind(this), 0);
158     },
159
160     _saveChildren: function()
161     {
162         this._savedChildren = {};
163         for (var i = 0, childrenCount = this.children.length; i < childrenCount; ++i) {
164             var child = this.children[i];
165             if (child.expanded)
166                 this._savedChildren[this._childHashForNode(child)] = child;
167         }
168     },
169
170     sort: function()
171     {
172         this.dataGrid.recursiveSortingEnter();
173         function afterSort(sorted)
174         {
175             if (!sorted) {
176                 this.dataGrid.recursiveSortingLeave();
177                 return;
178             }
179             this._saveChildren();
180             this.removeChildren();
181
182             function afterPopulate()
183             {
184                 for (var i = 0, l = this.children.length; i < l; ++i) {
185                     var child = this.children[i];
186                     if (child.expanded)
187                         child.sort();
188                 }
189                 this.dataGrid.recursiveSortingLeave();
190             }
191             this.populateChildren(this._provider, null, null, afterPopulate.bind(this));
192         }
193         this._provider.sortAndRewind(this.comparator(), afterSort.bind(this));
194     }
195 };
196
197 WebInspector.HeapSnapshotGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype;
198
199 WebInspector.HeapSnapshotGenericObjectNode = function(tree, node)
200 {
201     WebInspector.HeapSnapshotGridNode.call(this, tree, false);
202     this._name = node.name;
203     this._type = node.type;
204     this._shallowSize = node.selfSize;
205     this._retainedSize = node.retainedSize;
206     this.snapshotNodeId = node.id;
207     this.snapshotNodeIndex = node.nodeIndex;
208     if (this._type === "string")
209         this.hasHoverMessage = true;
210     else if (this._type === "object" && this.isDOMWindow(this._name)) {
211         this._name = this.shortenWindowURL(this._name, false);
212         this.hasHoverMessage = true;
213     } else if (node.flags & tree.snapshot.nodeFlags.canBeQueried)
214         this.hasHoverMessage = true;
215     if (node.flags & tree.snapshot.nodeFlags.detachedDOMTreeNode)
216         this.detachedDOMTreeNode = true;
217 };
218
219 WebInspector.HeapSnapshotGenericObjectNode.prototype = {
220     createCell: function(columnIdentifier)
221     {
222         var cell = columnIdentifier !== "object" ? this._createValueCell(columnIdentifier) : this._createObjectCell();
223         if (this._searchMatched)
224             cell.addStyleClass("highlight");
225         return cell;
226     },
227
228     _createObjectCell: function()
229     {
230         var cell = document.createElement("td");
231         cell.className = "object-column";
232         var div = document.createElement("div");
233         div.className = "source-code event-properties";
234         div.style.overflow = "visible";
235         var data = this.data["object"];
236         if (this._prefixObjectCell)
237             this._prefixObjectCell(div, data);
238         var valueSpan = document.createElement("span");
239         valueSpan.className = "value console-formatted-" + data.valueStyle;
240         valueSpan.textContent = data.value;
241         div.appendChild(valueSpan);
242         var idSpan = document.createElement("span");
243         idSpan.className = "console-formatted-id";
244         idSpan.textContent = " @" + data["nodeId"];
245         div.appendChild(idSpan);
246         if (this._postfixObjectCell)
247             this._postfixObjectCell(div, data);
248         cell.appendChild(div);
249         cell.addStyleClass("disclosure");
250         if (this.depth)
251             cell.style.setProperty("padding-left", (this.depth * this.dataGrid.indentWidth) + "px");
252         return cell;
253     },
254
255     get _countPercent()
256     {
257         return this._count / this.dataGrid.snapshot.nodeCount * 100.0;
258     },
259
260     get data()
261     {
262         var data = this._emptyData();
263
264         var value = this._name;
265         var valueStyle = "object";
266         switch (this._type) {
267         case "string":
268             value = "\"" + value + "\"";
269             valueStyle = "string";
270             break;
271         case "regexp":
272             value = "/" + value + "/";
273             valueStyle = "string";
274             break;
275         case "closure":
276             value = "function" + (value ? " " : "") + value + "()";
277             valueStyle = "function";
278             break;
279         case "number":
280             valueStyle = "number";
281             break;
282         case "hidden":
283             valueStyle = "null";
284             break;
285         case "array":
286             if (!value)
287                 value = "[]";
288             else
289                 value += "[]";
290             break;
291         };
292         if (this.hasHoverMessage)
293             valueStyle += " highlight";
294         if (value === "Object")
295             value = "";
296         if (this.detachedDOMTreeNode)
297             valueStyle += " detached-dom-tree-node";
298         data["object"] = { valueStyle: valueStyle, value: value, nodeId: this.snapshotNodeId };
299
300         var view = this.dataGrid.snapshotView;
301         data["shallowSize"] = Number.withThousandsSeparator(this._shallowSize);
302         data["retainedSize"] = Number.withThousandsSeparator(this._retainedSize);
303         if (view._showPercentage) {
304             data["shallowSize-percent"] = this._toPercentString(this._shallowSizePercent);
305             data["retainedSize-percent"] = this._toPercentString(this._retainedSizePercent);
306         }
307
308         return this._enhanceData ? this._enhanceData(data) : data;
309     },
310
311     queryObjectContent: function(callback)
312     {
313         if (this._type === "string")
314             callback(WebInspector.RemoteObject.fromPrimitiveValue(this._name));
315         else {
316             function formatResult(error, object)
317             {
318                 if (!error && object.type)
319                     callback(WebInspector.RemoteObject.fromPayload(object), !!error);
320                 else
321                     callback(WebInspector.RemoteObject.fromPrimitiveValue(WebInspector.UIString("Not available")));
322             }
323             ProfilerAgent.getObjectByHeapObjectId(this.snapshotNodeId, formatResult);
324         }
325     },
326
327     get _retainedSizePercent()
328     {
329         return this._retainedSize / this.dataGrid.snapshot.totalSize * 100.0;
330     },
331
332     get _shallowSizePercent()
333     {
334         return this._shallowSize / this.dataGrid.snapshot.totalSize * 100.0;
335     },
336
337     updateHasChildren: function()
338     {
339         function isEmptyCallback(isEmpty)
340         {
341             this.hasChildren = !isEmpty;
342         }
343         this._provider.isEmpty(isEmptyCallback.bind(this));
344     },
345
346     isDOMWindow: function(fullName)
347     {
348         return fullName.substr(0, 9) === "DOMWindow";
349     },
350
351     shortenWindowURL: function(fullName, hasObjectId)
352     {
353         var startPos = fullName.indexOf("/");
354         var endPos = hasObjectId ? fullName.indexOf("@") : fullName.length;
355         if (startPos !== -1 && endPos !== -1) {
356             var fullURL = fullName.substring(startPos + 1, endPos).trimLeft();
357             var url = fullURL.trimURL();
358             if (url.length > 40)
359                 url = url.trimMiddle(40);
360             return fullName.substr(0, startPos + 2) + url + fullName.substr(endPos);
361         } else
362             return fullName;
363     }
364 }
365
366 WebInspector.HeapSnapshotGenericObjectNode.prototype.__proto__ = WebInspector.HeapSnapshotGridNode.prototype;
367
368 WebInspector.HeapSnapshotObjectNode = function(tree, isFromBaseSnapshot, edge, parentGridNode)
369 {
370     WebInspector.HeapSnapshotGenericObjectNode.call(this, tree, edge.node);
371     this._referenceName = edge.name;
372     this._referenceType = edge.type;
373     this._propertyAccessor = edge.propertyAccessor;
374     this.showRetainingEdges = tree.showRetainingEdges;
375     this._isFromBaseSnapshot = isFromBaseSnapshot;
376     this._provider = this._createProvider(!isFromBaseSnapshot ? tree.snapshot : tree.baseSnapshot, edge.nodeIndex, tree);
377     this.updateHasChildren(parentGridNode);
378 }
379
380 WebInspector.HeapSnapshotObjectNode.prototype = {
381     updateHasChildren: function(parentGridNode)
382     {
383         if (this.showRetainingEdges) {
384             this._parentGridNode = parentGridNode;
385             var ancestor = parentGridNode;
386             while (ancestor) {
387                 if (ancestor.snapshotNodeId === this.snapshotNodeId) {
388                     this._cycledWithAncestorGridNode = ancestor;
389                     return;
390                 }
391                 ancestor = ancestor._parentGridNode;
392             }
393         }
394         WebInspector.HeapSnapshotGenericObjectNode.prototype.updateHasChildren.call(this);
395     },
396
397     _createChildNode: function(item)
398     {
399         return new WebInspector.HeapSnapshotObjectNode(this.dataGrid, this._isFromBaseSnapshot, item, this);
400     },
401
402     _createProvider: function(snapshot, nodeIndex, tree)
403     {
404         var showHiddenData = WebInspector.settings.showHeapSnapshotObjectsHiddenProperties.get();
405         var filter = "function(edge) {\n" +
406             "    return !edge.isInvisible\n" +
407             "        && (" + !this.showRetainingEdges + " || (edge.node.id !== 1 && !edge.node.isArtificial))\n" +
408             "        && (" + showHiddenData + " || (!edge.isHidden && !edge.node.isHidden));\n" +
409             "}\n";
410         if (tree.showRetainingEdges)
411             return snapshot.createRetainingEdgesProvider(nodeIndex, filter);
412         else
413             return snapshot.createEdgesProvider(nodeIndex, filter);
414     },
415
416     _childHashForEntity: function(edge)
417     {
418         return edge.type + "#" + edge.name;
419     },
420
421     _childHashForNode: function(childNode)
422     {
423         return childNode._referenceType + "#" + childNode._referenceName;
424     },
425
426     comparator: function()
427     {
428         var sortAscending = this.dataGrid.sortOrder === "ascending";
429         var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier;
430         var sortFields = {
431             object: ["!edgeName", sortAscending, "retainedSize", false],
432             count: ["!edgeName", true, "retainedSize", false],
433             shallowSize: ["selfSize", sortAscending, "!edgeName", true],
434             retainedSize: ["retainedSize", sortAscending, "!edgeName", true]
435         }[sortColumnIdentifier] || ["!edgeName", true, "retainedSize", false];
436         return WebInspector.HeapSnapshotFilteredOrderedIterator.prototype.createComparator(sortFields);
437     },
438
439     _emptyData: function()
440     {
441         return { count: "", addedCount: "", removedCount: "", countDelta: "", addedSize: "", removedSize: "", sizeDelta: "" };
442     },
443
444     _enhanceData: function(data)
445     {
446         var name = this._referenceName;
447         if (name === "") name = "(empty)";
448         var nameClass = "name";
449         switch (this._referenceType) {
450         case "context":
451             nameClass = "console-formatted-number";
452             break;
453         case "internal":
454         case "hidden":
455             nameClass = "console-formatted-null";
456             break;
457         case "element":
458             name = "[" + name + "]";
459             break;
460         }
461         data["object"].nameClass = nameClass;
462         data["object"].name = name;
463         return data;
464     },
465
466     _prefixObjectCell: function(div, data)
467     {
468         if (this.showRetainingEdges && this._cycledWithAncestorGridNode)
469             div.className += " cycled-ancessor-node";
470
471         var nameSpan = document.createElement("span");
472         nameSpan.className = data.nameClass;
473         nameSpan.textContent = data.name;
474         div.appendChild(nameSpan);
475
476         var separatorSpan = document.createElement("span");
477         separatorSpan.className = "grayed";
478         separatorSpan.textContent = this.showRetainingEdges ? " in " : " :: ";
479         div.appendChild(separatorSpan);
480     }
481 }
482
483 WebInspector.HeapSnapshotObjectNode.prototype.__proto__ = WebInspector.HeapSnapshotGenericObjectNode.prototype;
484
485 WebInspector.HeapSnapshotInstanceNode = function(tree, baseSnapshot, snapshot, node)
486 {
487     WebInspector.HeapSnapshotGenericObjectNode.call(this, tree, node);
488     this._isDeletedNode = !!baseSnapshot;
489     this._provider = this._createProvider(baseSnapshot || snapshot, node.nodeIndex);
490     this.updateHasChildren();
491 };
492
493 WebInspector.HeapSnapshotInstanceNode.prototype = {
494     _createChildNode: function(item)
495     {
496         return new WebInspector.HeapSnapshotObjectNode(this.dataGrid, this._isDeletedNode, item);
497     },
498
499     _createProvider: function(snapshot, nodeIndex)
500     {
501         var showHiddenData = WebInspector.DetailedHeapshotView.prototype.showHiddenData;
502         return snapshot.createEdgesProvider(
503             nodeIndex,
504             "function(edge) {" +
505             "    return !edge.isInvisible" +
506             "        && (" + showHiddenData + " || (!edge.isHidden && !edge.node.isHidden));" +
507             "}");
508     },
509
510     _childHashForEntity: function(edge)
511     {
512         return edge.type + "#" + edge.name;
513     },
514
515     _childHashForNode: function(childNode)
516     {
517         return childNode._referenceType + "#" + childNode._referenceName;
518     },
519
520     comparator: function()
521     {
522         var sortAscending = this.dataGrid.sortOrder === "ascending";
523         var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier;
524         var sortFields = {
525             object: ["!edgeName", sortAscending, "retainedSize", false],
526             count: ["!edgeName", true, "retainedSize", false],
527             addedSize: ["selfSize", sortAscending, "!edgeName", true],
528             removedSize: ["selfSize", sortAscending, "!edgeName", true],
529             shallowSize: ["selfSize", sortAscending, "!edgeName", true],
530             retainedSize: ["retainedSize", sortAscending, "!edgeName", true]
531         }[sortColumnIdentifier] || ["!edgeName", true, "retainedSize", false];
532         return WebInspector.HeapSnapshotFilteredOrderedIterator.prototype.createComparator(sortFields);
533     },
534
535     _emptyData: function()
536     {
537         return {count:"", countDelta:"", sizeDelta: ""};
538     },
539
540     _enhanceData: function(data)
541     {
542         if (this._isDeletedNode) {
543             data["addedCount"] = "";
544             data["addedSize"] = "";
545             data["removedCount"] = "\u2022";
546             data["removedSize"] = Number.withThousandsSeparator(this._shallowSize);
547         } else {
548             data["addedCount"] = "\u2022";
549             data["addedSize"] = Number.withThousandsSeparator(this._shallowSize);
550             data["removedCount"] = "";
551             data["removedSize"] = "";
552         }
553         return data;
554     },
555
556     get isDeletedNode()
557     {
558         return this._isDeletedNode;
559     }
560 }
561
562 WebInspector.HeapSnapshotInstanceNode.prototype.__proto__ = WebInspector.HeapSnapshotGenericObjectNode.prototype;
563
564 WebInspector.HeapSnapshotConstructorNode = function(tree, className, aggregate, aggregatesKey)
565 {
566     WebInspector.HeapSnapshotGridNode.call(this, tree, aggregate.count > 0);
567     this._name = className;
568     this._count = aggregate.count;
569     this._shallowSize = aggregate.self;
570     this._retainedSize = aggregate.maxRet;
571     this._provider = this._createNodesProvider(tree.snapshot, className, aggregatesKey);
572 }
573
574 WebInspector.HeapSnapshotConstructorNode.prototype = {
575     createCell: function(columnIdentifier)
576     {
577         var cell = columnIdentifier !== "object" ? this._createValueCell(columnIdentifier) : WebInspector.HeapSnapshotGridNode.prototype.createCell.call(this, columnIdentifier);
578         if (this._searchMatched)
579             cell.addStyleClass("highlight");
580         return cell;
581     },
582
583     _createChildNode: function(item)
584     {
585         return new WebInspector.HeapSnapshotInstanceNode(this.dataGrid, null, this.dataGrid.snapshot, item);
586     },
587
588     _createNodesProvider: function(snapshot, className, aggregatesKey)
589     {
590         return snapshot.createNodesProviderForClass(className, aggregatesKey);
591     },
592
593     comparator: function()
594     {
595         var sortAscending = this.dataGrid.sortOrder === "ascending";
596         var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier;
597         var sortFields = {
598             object: ["id", sortAscending, "retainedSize", false],
599             count: ["id", true, "retainedSize", false],
600             shallowSize: ["selfSize", sortAscending, "id", true],
601             retainedSize: ["retainedSize", sortAscending, "id", true]
602         }[sortColumnIdentifier];
603         return WebInspector.HeapSnapshotFilteredOrderedIterator.prototype.createComparator(sortFields);
604     },
605
606     _childHashForEntity: function(node)
607     {
608         return node.id;
609     },
610
611     _childHashForNode: function(childNode)
612     {
613         return childNode.snapshotNodeId;
614     },
615
616     get data()
617     {
618         var data = { object: this._name };
619         var view = this.dataGrid.snapshotView;
620         data["count"] =  Number.withThousandsSeparator(this._count);
621         data["shallowSize"] = Number.withThousandsSeparator(this._shallowSize);
622         data["retainedSize"] = Number.withThousandsSeparator(this._retainedSize);
623         if (view._showPercentage) {
624             data["shallowSize-percent"] = this._toPercentString(this._shallowSizePercent);
625             data["retainedSize-percent"] = this._toPercentString(this._retainedSizePercent);
626         }
627         return data;
628     },
629
630     get _countPercent()
631     {
632         return this._count / this.dataGrid.snapshot.nodeCount * 100.0;
633     },
634
635     get _retainedSizePercent()
636     {
637         return this._retainedSize / this.dataGrid.snapshot.totalSize * 100.0;
638     },
639
640     get _shallowSizePercent()
641     {
642         return this._shallowSize / this.dataGrid.snapshot.totalSize * 100.0;
643     }
644 };
645
646 WebInspector.HeapSnapshotConstructorNode.prototype.__proto__ = WebInspector.HeapSnapshotGridNode.prototype;
647
648 WebInspector.HeapSnapshotIteratorsTuple = function(it1, it2)
649 {
650     this._it1 = it1;
651     this._it2 = it2;
652 }
653
654 WebInspector.HeapSnapshotIteratorsTuple.prototype = {
655     dispose: function()
656     {
657         this._it1.dispose();
658         this._it2.dispose();
659     },
660
661     sortAndRewind: function(comparator, callback)
662     {
663         function afterSort(ignored)
664         {
665             this._it2.sortAndRewind(comparator, callback);
666         }
667         this._it1.sortAndRewind(comparator, afterSort.bind(this));
668     }
669 };
670
671 WebInspector.HeapSnapshotDiffNode = function(tree, className, baseAggregate, aggregate)
672 {
673     WebInspector.HeapSnapshotGridNode.call(this, tree, true);
674     this._name = className;
675     this._baseIndexes = baseAggregate ? baseAggregate.idxs : [];
676     this._indexes = aggregate ? aggregate.idxs : [];
677     this._provider = this._createNodesProvider(tree.baseSnapshot, tree.snapshot, aggregate ? aggregate.type : baseAggregate.type, className);
678 }
679
680 WebInspector.HeapSnapshotDiffNode.prototype = {
681     calculateDiff: function(dataGrid, callback)
682     {
683         var diff = dataGrid.snapshot.createDiff(this._name);
684
685         function diffCalculated(diffResult)
686         {
687             diff.dispose();
688             this._addedCount = diffResult.addedCount;
689             this._removedCount = diffResult.removedCount;
690             this._countDelta = diffResult.countDelta;
691             this._addedSize = diffResult.addedSize;
692             this._removedSize = diffResult.removedSize;
693             this._sizeDelta = diffResult.sizeDelta;
694             this._baseIndexes = null;
695             this._indexes = null;
696             callback(this._addedSize === 0 && this._removedSize === 0);
697         }
698         function baseSelfSizesReceived(baseSelfSizes)
699         {
700             diff.pushBaseSelfSizes(baseSelfSizes);
701             diff.calculate(diffCalculated.bind(this));
702         }
703         function baseIdsReceived(baseIds)
704         {
705             diff.pushBaseIds(baseIds);
706             dataGrid.snapshot.pushBaseIds(dataGrid.baseSnapshot.uid, this._name, baseIds);
707             dataGrid.baseSnapshot.nodeFieldValuesByIndex("selfSize", this._baseIndexes, baseSelfSizesReceived.bind(this));
708         }
709         function idsReceived(ids)
710         {
711             dataGrid.baseSnapshot.pushBaseIds(dataGrid.snapshot.uid, this._name, ids);
712         }
713         dataGrid.baseSnapshot.nodeFieldValuesByIndex("id", this._baseIndexes, baseIdsReceived.bind(this));
714         dataGrid.snapshot.nodeFieldValuesByIndex("id", this._indexes, idsReceived.bind(this));
715     },
716
717     _createChildNode: function(item, provider)
718     {
719         if (provider === this._provider._it1)
720             return new WebInspector.HeapSnapshotInstanceNode(this.dataGrid, null, provider.snapshot, item);
721         else
722             return new WebInspector.HeapSnapshotInstanceNode(this.dataGrid, provider.snapshot, null, item);
723     },
724
725     _createNodesProvider: function(baseSnapshot, snapshot, nodeType, nodeClassName)
726     {
727         var className = this._name;
728         return new WebInspector.HeapSnapshotIteratorsTuple(
729             createProvider(snapshot, baseSnapshot), createProvider(baseSnapshot, snapshot));
730
731         function createProvider(snapshot, otherSnapshot)
732         {
733             var otherSnapshotId = otherSnapshot.uid;
734             var provider = snapshot.createNodesProvider(
735                 "function (node) {" +
736                 "     return node.type === \"" + nodeType + "\" " +
737                 (nodeClassName !== null ? "&& node.className === \"" + nodeClassName + "\"" : "") +
738                 "         && !this.baseSnapshotHasNode(" + otherSnapshotId + ", \"" + className + "\", node.id);" +
739                 "}");
740             provider.snapshot = snapshot;
741             return provider;
742         }
743     },
744
745     _childHashForEntity: function(node)
746     {
747         return node.id;
748     },
749
750     _childHashForNode: function(childNode)
751     {
752         return childNode.snapshotNodeId;
753     },
754
755     comparator: function()
756     {
757         var sortAscending = this.dataGrid.sortOrder === "ascending";
758         var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier;
759         var sortFields = {
760             object: ["id", sortAscending, "selfSize", false],
761             addedCount: ["selfSize", sortAscending, "id", true],
762             removedCount: ["selfSize", sortAscending, "id", true],
763             countDelta: ["selfSize", sortAscending, "id", true],
764             addedSize: ["selfSize", sortAscending, "id", true],
765             removedSize: ["selfSize", sortAscending, "id", true],
766             sizeDelta: ["selfSize", sortAscending, "id", true]
767         }[sortColumnIdentifier];
768         return WebInspector.HeapSnapshotFilteredOrderedIterator.prototype.createComparator(sortFields);
769     },
770
771     populateChildren: function(provider, howMany, atIndex, afterPopulate)
772     {
773         if (!provider && !howMany) {
774             var firstProviderPopulated = function()
775             {
776                 WebInspector.HeapSnapshotGridNode.prototype.populateChildren.call(this, this._provider._it2, this._defaultPopulateCount, atIndex, afterPopulate);
777             };
778             WebInspector.HeapSnapshotGridNode.prototype.populateChildren.call(this, this._provider._it1, this._defaultPopulateCount, atIndex, firstProviderPopulated.bind(this), true);
779         } else if (!howMany) {
780             var firstProviderPopulated = function()
781             {
782                 WebInspector.HeapSnapshotGridNode.prototype.populateChildren.call(this, this._provider._it2, null, atIndex, afterPopulate);
783             };
784             WebInspector.HeapSnapshotGridNode.prototype.populateChildren.call(this, this._provider._it1, null, atIndex, firstProviderPopulated.bind(this), true);
785         } else
786             WebInspector.HeapSnapshotGridNode.prototype.populateChildren.call(this, provider, howMany, atIndex, afterPopulate);
787     },
788
789     _signForDelta: function(delta)
790     {
791         if (delta === 0)
792             return "";
793         if (delta > 0)
794             return "+";
795         else
796             return "\u2212";  // Math minus sign, same width as plus.
797     },
798
799     get data()
800     {
801         var data = {object: this._name};
802
803         data["addedCount"] = Number.withThousandsSeparator(this._addedCount);
804         data["removedCount"] = Number.withThousandsSeparator(this._removedCount);
805         data["countDelta"] = this._signForDelta(this._countDelta) + Number.withThousandsSeparator(Math.abs(this._countDelta));
806         data["addedSize"] = Number.withThousandsSeparator(this._addedSize);
807         data["removedSize"] = Number.withThousandsSeparator(this._removedSize);
808         data["sizeDelta"] = this._signForDelta(this._sizeDelta) + Number.withThousandsSeparator(Math.abs(this._sizeDelta));
809
810         return data;
811     }
812 };
813
814 WebInspector.HeapSnapshotDiffNode.prototype.__proto__ = WebInspector.HeapSnapshotGridNode.prototype;
815
816 WebInspector.HeapSnapshotDominatorObjectNode = function(tree, node)
817 {
818     WebInspector.HeapSnapshotGenericObjectNode.call(this, tree, node);
819     this._provider = this._createProvider(tree.snapshot, node.nodeIndex);
820     this.updateHasChildren();
821 };
822
823 WebInspector.HeapSnapshotDominatorObjectNode.prototype = {
824     _createChildNode: function(item)
825     {
826         return new WebInspector.HeapSnapshotDominatorObjectNode(this.dataGrid, item);
827     },
828
829     _createProvider: function(snapshot, nodeIndex)
830     {
831         var showHiddenData = WebInspector.DetailedHeapshotView.prototype.showHiddenData;
832         return snapshot.createNodesProviderForDominator(nodeIndex,
833             "function (node) {" +
834             "     return " + showHiddenData + " || !node.isHidden;" +
835             "}");
836     },
837
838     _childHashForEntity: function(node)
839     {
840         return node.id;
841     },
842
843     _childHashForNode: function(childNode)
844     {
845         return childNode.snapshotNodeId;
846     },
847
848     comparator: function()
849     {
850         var sortAscending = this.dataGrid.sortOrder === "ascending";
851         var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier;
852         var sortFields = {
853             object: ["id", sortAscending, "retainedSize", false],
854             shallowSize: ["selfSize", sortAscending, "id", true],
855             retainedSize: ["retainedSize", sortAscending, "id", true]
856         }[sortColumnIdentifier];
857         return WebInspector.HeapSnapshotFilteredOrderedIterator.prototype.createComparator(sortFields);
858     },
859
860     _emptyData: function()
861     {
862         return {};
863     }
864 };
865
866 WebInspector.HeapSnapshotDominatorObjectNode.prototype.__proto__ = WebInspector.HeapSnapshotGenericObjectNode.prototype;
867
868 function MixInSnapshotNodeFunctions(sourcePrototype, targetPrototype)
869 {
870     targetPrototype._childHashForEntity = sourcePrototype._childHashForEntity;
871     targetPrototype._childHashForNode = sourcePrototype._childHashForNode;
872     targetPrototype.comparator = sourcePrototype.comparator;
873     targetPrototype._createChildNode = sourcePrototype._createChildNode;
874     targetPrototype._createProvider = sourcePrototype._createProvider;
875     targetPrototype.dispose = sourcePrototype.dispose;
876     targetPrototype.populateChildren = sourcePrototype.populateChildren;
877     targetPrototype._saveChildren = sourcePrototype._saveChildren;
878     targetPrototype.sort = sourcePrototype.sort;
879 }