Web Inspector: get rid of cycles in retaining tree
[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         if (this._postfixObjectCell)
243             this._postfixObjectCell(div, data);
244         cell.appendChild(div);
245         cell.addStyleClass("disclosure");
246         if (this.depth)
247             cell.style.setProperty("padding-left", (this.depth * this.dataGrid.indentWidth) + "px");
248         return cell;
249     },
250
251     get _countPercent()
252     {
253         return this._count / this.dataGrid.snapshot.nodeCount * 100.0;
254     },
255
256     get data()
257     {
258         var data = this._emptyData();
259
260         var value = this._name;
261         var valueStyle = "object";
262         switch (this._type) {
263         case "string":
264             value = "\"" + value + "\"";
265             valueStyle = "string";
266             break;
267         case "regexp":
268             value = "/" + value + "/";
269             valueStyle = "string";
270             break;
271         case "closure":
272             value = "function " + value + "()";
273             valueStyle = "function";
274             break;
275         case "number":
276             valueStyle = "number";
277             break;
278         case "hidden":
279             valueStyle = "null";
280             break;
281         case "array":
282             if (!value)
283                 value = "[]";
284             else
285                 value += " []";
286             break;
287         };
288         if (this.hasHoverMessage)
289             valueStyle += " highlight";
290         if (this.detachedDOMTreeNode)
291             valueStyle += " detached-dom-tree-node";
292         data["object"] = { valueStyle: valueStyle, value: value + ": @" + this.snapshotNodeId };
293
294         var view = this.dataGrid.snapshotView;
295         data["shallowSize"] = Number.withThousandsSeparator(this._shallowSize);
296         data["retainedSize"] = Number.withThousandsSeparator(this._retainedSize);
297         if (view._showPercentage) {
298             data["shallowSize-percent"] = this._toPercentString(this._shallowSizePercent);
299             data["retainedSize-percent"] = this._toPercentString(this._retainedSizePercent);
300         }
301
302         return this._enhanceData ? this._enhanceData(data) : data;
303     },
304
305     queryObjectContent: function(callback)
306     {
307         if (this._type === "string")
308             callback(WebInspector.RemoteObject.fromPrimitiveValue(this._name));
309         else {
310             function formatResult(error, object)
311             {
312                 if (!error && object.type)
313                     callback(WebInspector.RemoteObject.fromPayload(object), !!error);
314                 else
315                     callback(WebInspector.RemoteObject.fromPrimitiveValue(WebInspector.UIString("Not available")));
316             }
317             ProfilerAgent.getObjectByHeapObjectId(this.snapshotNodeId, formatResult);
318         }
319     },
320
321     get _retainedSizePercent()
322     {
323         return this._retainedSize / this.dataGrid.snapshot.totalSize * 100.0;
324     },
325
326     get _shallowSizePercent()
327     {
328         return this._shallowSize / this.dataGrid.snapshot.totalSize * 100.0;
329     },
330
331     updateHasChildren: function()
332     {
333         function isEmptyCallback(isEmpty)
334         {
335             this.hasChildren = !isEmpty;
336         }
337         this._provider.isEmpty(isEmptyCallback.bind(this));
338     },
339
340     isDOMWindow: function(fullName)
341     {
342         return fullName.substr(0, 9) === "DOMWindow";
343     },
344
345     shortenWindowURL: function(fullName, hasObjectId)
346     {
347         var startPos = fullName.indexOf("/");
348         var endPos = hasObjectId ? fullName.indexOf("@") : fullName.length;
349         if (startPos !== -1 && endPos !== -1) {
350             var fullURL = fullName.substring(startPos + 1, endPos).trimLeft();
351             var url = fullURL.trimURL();
352             if (url.length > 40)
353                 url = url.trimMiddle(40);
354             return fullName.substr(0, startPos + 2) + url + fullName.substr(endPos);
355         } else
356             return fullName;
357     }
358 }
359
360 WebInspector.HeapSnapshotGenericObjectNode.prototype.__proto__ = WebInspector.HeapSnapshotGridNode.prototype;
361
362 WebInspector.HeapSnapshotObjectNode = function(tree, isFromBaseSnapshot, edge, parentGridNode)
363 {
364     WebInspector.HeapSnapshotGenericObjectNode.call(this, tree, edge.node);
365     this._referenceName = edge.name;
366     this._referenceType = edge.type;
367     this._propertyAccessor = edge.propertyAccessor;
368     this._retainerNode = tree.showRetainingEdges;
369     this._isFromBaseSnapshot = isFromBaseSnapshot;
370     this._provider = this._createProvider(!isFromBaseSnapshot ? tree.snapshot : tree.baseSnapshot, edge.nodeIndex, tree);
371     this.updateHasChildren(parentGridNode);
372 }
373
374 WebInspector.HeapSnapshotObjectNode.prototype = {
375     updateHasChildren: function(parentGridNode)
376     {
377         if (this._retainerNode) {
378             this._parentGridNode = parentGridNode;
379             var ancestor = parentGridNode;
380             while (ancestor) {
381                 if (ancestor.snapshotNodeId === this.snapshotNodeId) {
382                     this._cycledWithAncestorGridNode = ancestor;
383                     return;
384                 }
385                 ancestor = ancestor._parentGridNode;
386             }
387         }
388         WebInspector.HeapSnapshotGenericObjectNode.prototype.updateHasChildren.call(this);
389     },
390
391     _createChildNode: function(item)
392     {
393         return new WebInspector.HeapSnapshotObjectNode(this.dataGrid, this._isFromBaseSnapshot, item, this);
394     },
395
396     _createProvider: function(snapshot, nodeIndex, tree)
397     {
398         var showHiddenData = WebInspector.settings.showHeapSnapshotObjectsHiddenProperties.get();
399         var filter = "function(edge) {" +
400             "    return !edge.isInvisible" +
401             "        && (" + showHiddenData + " || (!edge.isHidden && !edge.node.isHidden));" +
402             "}";
403         if (tree.showRetainingEdges)
404             return snapshot.createRetainingEdgesProvider(nodeIndex, filter);
405         else
406             return snapshot.createEdgesProvider(nodeIndex, filter);
407     },
408
409     _childHashForEntity: function(edge)
410     {
411         return edge.type + "#" + edge.name;
412     },
413
414     _childHashForNode: function(childNode)
415     {
416         return childNode._referenceType + "#" + childNode._referenceName;
417     },
418
419     comparator: function()
420     {
421         var sortAscending = this.dataGrid.sortOrder === "ascending";
422         var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier;
423         var sortFields = {
424             object: ["!edgeName", sortAscending, "retainedSize", false],
425             count: ["!edgeName", true, "retainedSize", false],
426             shallowSize: ["selfSize", sortAscending, "!edgeName", true],
427             retainedSize: ["retainedSize", sortAscending, "!edgeName", true]
428         }[sortColumnIdentifier] || ["!edgeName", true, "retainedSize", false];
429         return WebInspector.HeapSnapshotFilteredOrderedIterator.prototype.createComparator(sortFields);
430     },
431
432     _emptyData: function()
433     {
434         return {count:"", addedCount: "", removedCount: "", countDelta:"", addedSize: "", removedSize: "", sizeDelta: ""};
435     },
436
437     _enhanceData: function(data)
438     {
439         var name = this._referenceName;
440         if (name === "") name = "(empty)";
441         var nameClass = "name";
442         switch (this._referenceType) {
443         case "context":
444             nameClass = "console-formatted-number";
445             break;
446         case "internal":
447         case "hidden":
448             nameClass = "console-formatted-null";
449             break;
450         }
451         data["object"].nameClass = nameClass;
452         data["object"].name = name;
453         return data;
454     },
455
456     _prefixObjectCell: function(div, data)
457     {
458         if (this._retainerNode) {
459             if (this._cycledWithAncestorGridNode)
460                 div.className += " cycled-ancessor-node";
461             return;
462         }
463
464         var nameSpan = document.createElement("span");
465         nameSpan.className = data.nameClass;
466         nameSpan.textContent = data.name;
467         div.appendChild(nameSpan);
468
469         var separatorSpan = document.createElement("span");
470         separatorSpan.className = "separator";
471         separatorSpan.textContent = ": ";
472         div.appendChild(separatorSpan);
473     },
474
475     _postfixObjectCell: function(div, data)
476     {
477         if (this._retainerNode) {
478             var referenceTypeSpan = document.createElement("span");
479             referenceTypeSpan.className = "console-formatted-object";
480             referenceTypeSpan.textContent = this._propertyAccessor;
481             div.appendChild(referenceTypeSpan);
482         }
483     }
484 }
485
486 WebInspector.HeapSnapshotObjectNode.prototype.__proto__ = WebInspector.HeapSnapshotGenericObjectNode.prototype;
487
488 WebInspector.HeapSnapshotInstanceNode = function(tree, baseSnapshot, snapshot, node)
489 {
490     WebInspector.HeapSnapshotGenericObjectNode.call(this, tree, node);
491     this._isDeletedNode = !!baseSnapshot;
492     this._provider = this._createProvider(baseSnapshot || snapshot, node.nodeIndex);
493     this.updateHasChildren();
494 };
495
496 WebInspector.HeapSnapshotInstanceNode.prototype = {
497     _createChildNode: function(item)
498     {
499         return new WebInspector.HeapSnapshotObjectNode(this.dataGrid, this._isDeletedNode, item);
500     },
501
502     _createProvider: function(snapshot, nodeIndex)
503     {
504         var showHiddenData = WebInspector.DetailedHeapshotView.prototype.showHiddenData;
505         return snapshot.createEdgesProvider(
506             nodeIndex,
507             "function(edge) {" +
508             "    return !edge.isInvisible" +
509             "        && (" + showHiddenData + " || (!edge.isHidden && !edge.node.isHidden));" +
510             "}");
511     },
512
513     _childHashForEntity: function(edge)
514     {
515         return edge.type + "#" + edge.name;
516     },
517
518     _childHashForNode: function(childNode)
519     {
520         return childNode._referenceType + "#" + childNode._referenceName;
521     },
522
523     comparator: function()
524     {
525         var sortAscending = this.dataGrid.sortOrder === "ascending";
526         var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier;
527         var sortFields = {
528             object: ["!edgeName", sortAscending, "retainedSize", false],
529             count: ["!edgeName", true, "retainedSize", false],
530             addedSize: ["selfSize", sortAscending, "!edgeName", true],
531             removedSize: ["selfSize", sortAscending, "!edgeName", true],
532             shallowSize: ["selfSize", sortAscending, "!edgeName", true],
533             retainedSize: ["retainedSize", sortAscending, "!edgeName", true]
534         }[sortColumnIdentifier] || ["!edgeName", true, "retainedSize", false];
535         return WebInspector.HeapSnapshotFilteredOrderedIterator.prototype.createComparator(sortFields);
536     },
537
538     _emptyData: function()
539     {
540         return {count:"", countDelta:"", sizeDelta: ""};
541     },
542
543     _enhanceData: function(data)
544     {
545         if (this._isDeletedNode) {
546             data["addedCount"] = "";
547             data["addedSize"] = "";
548             data["removedCount"] = "\u2022";
549             data["removedSize"] = Number.withThousandsSeparator(this._shallowSize);
550         } else {
551             data["addedCount"] = "\u2022";
552             data["addedSize"] = Number.withThousandsSeparator(this._shallowSize);
553             data["removedCount"] = "";
554             data["removedSize"] = "";
555         }
556         return data;
557     },
558
559     get isDeletedNode()
560     {
561         return this._isDeletedNode;
562     }
563 }
564
565 WebInspector.HeapSnapshotInstanceNode.prototype.__proto__ = WebInspector.HeapSnapshotGenericObjectNode.prototype;
566
567 WebInspector.HeapSnapshotConstructorNode = function(tree, className, aggregate, aggregatesKey)
568 {
569     WebInspector.HeapSnapshotGridNode.call(this, tree, aggregate.count > 0);
570     this._name = className;
571     this._count = aggregate.count;
572     this._shallowSize = aggregate.self;
573     this._retainedSize = aggregate.maxRet;
574     this._provider = this._createNodesProvider(tree.snapshot, className, aggregatesKey);
575 }
576
577 WebInspector.HeapSnapshotConstructorNode.prototype = {
578     createCell: function(columnIdentifier)
579     {
580         var cell = columnIdentifier !== "object" ? this._createValueCell(columnIdentifier) : WebInspector.HeapSnapshotGridNode.prototype.createCell.call(this, columnIdentifier);
581         if (this._searchMatched)
582             cell.addStyleClass("highlight");
583         return cell;
584     },
585
586     _createChildNode: function(item)
587     {
588         return new WebInspector.HeapSnapshotInstanceNode(this.dataGrid, null, this.dataGrid.snapshot, item);
589     },
590
591     _createNodesProvider: function(snapshot, className, aggregatesKey)
592     {
593         return snapshot.createNodesProviderForClass(className, aggregatesKey);
594     },
595
596     comparator: function()
597     {
598         var sortAscending = this.dataGrid.sortOrder === "ascending";
599         var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier;
600         var sortFields = {
601             object: ["id", sortAscending, "retainedSize", false],
602             count: ["id", true, "retainedSize", false],
603             shallowSize: ["selfSize", sortAscending, "id", true],
604             retainedSize: ["retainedSize", sortAscending, "id", true]
605         }[sortColumnIdentifier];
606         return WebInspector.HeapSnapshotFilteredOrderedIterator.prototype.createComparator(sortFields);
607     },
608
609     _childHashForEntity: function(node)
610     {
611         return node.id;
612     },
613
614     _childHashForNode: function(childNode)
615     {
616         return childNode.snapshotNodeId;
617     },
618
619     get data()
620     {
621         var data = {object: this._name};
622         var view = this.dataGrid.snapshotView;
623         data["count"] =  Number.withThousandsSeparator(this._count);
624         data["shallowSize"] = Number.withThousandsSeparator(this._shallowSize);
625         data["retainedSize"] = Number.withThousandsSeparator(this._retainedSize);
626         if (view._showPercentage) {
627             data["shallowSize-percent"] = this._toPercentString(this._shallowSizePercent);
628             data["retainedSize-percent"] = this._toPercentString(this._retainedSizePercent);
629         }
630         return data;
631     },
632
633     get _countPercent()
634     {
635         return this._count / this.dataGrid.snapshot.nodeCount * 100.0;
636     },
637
638     get _retainedSizePercent()
639     {
640         return this._retainedSize / this.dataGrid.snapshot.totalSize * 100.0;
641     },
642
643     get _shallowSizePercent()
644     {
645         return this._shallowSize / this.dataGrid.snapshot.totalSize * 100.0;
646     }
647 };
648
649 WebInspector.HeapSnapshotConstructorNode.prototype.__proto__ = WebInspector.HeapSnapshotGridNode.prototype;
650
651 WebInspector.HeapSnapshotIteratorsTuple = function(it1, it2)
652 {
653     this._it1 = it1;
654     this._it2 = it2;
655 }
656
657 WebInspector.HeapSnapshotIteratorsTuple.prototype = {
658     dispose: function()
659     {
660         this._it1.dispose();
661         this._it2.dispose();
662     },
663
664     sortAndRewind: function(comparator, callback)
665     {
666         function afterSort(ignored)
667         {
668             this._it2.sortAndRewind(comparator, callback);
669         }
670         this._it1.sortAndRewind(comparator, afterSort.bind(this));
671     }
672 };
673
674 WebInspector.HeapSnapshotDiffNode = function(tree, className, baseAggregate, aggregate)
675 {
676     WebInspector.HeapSnapshotGridNode.call(this, tree, true);
677     this._name = className;
678     this._baseIndexes = baseAggregate ? baseAggregate.idxs : [];
679     this._indexes = aggregate ? aggregate.idxs : [];
680     this._provider = this._createNodesProvider(tree.baseSnapshot, tree.snapshot, aggregate ? aggregate.type : baseAggregate.type, className);
681 }
682
683 WebInspector.HeapSnapshotDiffNode.prototype = {
684     calculateDiff: function(dataGrid, callback)
685     {
686         var diff = dataGrid.snapshot.createDiff(this._name);
687
688         function diffCalculated(diffResult)
689         {
690             diff.dispose();
691             this._addedCount = diffResult.addedCount;
692             this._removedCount = diffResult.removedCount;
693             this._countDelta = diffResult.countDelta;
694             this._addedSize = diffResult.addedSize;
695             this._removedSize = diffResult.removedSize;
696             this._sizeDelta = diffResult.sizeDelta;
697             this._baseIndexes = null;
698             this._indexes = null;
699             callback(this._addedSize === 0 && this._removedSize === 0);
700         }
701         function baseSelfSizesReceived(baseSelfSizes)
702         {
703             diff.pushBaseSelfSizes(baseSelfSizes);
704             diff.calculate(diffCalculated.bind(this));
705         }
706         function baseIdsReceived(baseIds)
707         {
708             diff.pushBaseIds(baseIds);
709             dataGrid.snapshot.pushBaseIds(dataGrid.baseSnapshot.uid, this._name, baseIds);
710             dataGrid.baseSnapshot.nodeFieldValuesByIndex("selfSize", this._baseIndexes, baseSelfSizesReceived.bind(this));
711         }
712         function idsReceived(ids)
713         {
714             dataGrid.baseSnapshot.pushBaseIds(dataGrid.snapshot.uid, this._name, ids);
715         }
716         dataGrid.baseSnapshot.nodeFieldValuesByIndex("id", this._baseIndexes, baseIdsReceived.bind(this));
717         dataGrid.snapshot.nodeFieldValuesByIndex("id", this._indexes, idsReceived.bind(this));
718     },
719
720     _createChildNode: function(item, provider)
721     {
722         if (provider === this._provider._it1)
723             return new WebInspector.HeapSnapshotInstanceNode(this.dataGrid, null, provider.snapshot, item);
724         else
725             return new WebInspector.HeapSnapshotInstanceNode(this.dataGrid, provider.snapshot, null, item);
726     },
727
728     _createNodesProvider: function(baseSnapshot, snapshot, nodeType, nodeClassName)
729     {
730         var className = this._name;
731         return new WebInspector.HeapSnapshotIteratorsTuple(
732             createProvider(snapshot, baseSnapshot), createProvider(baseSnapshot, snapshot));
733
734         function createProvider(snapshot, otherSnapshot)
735         {
736             var otherSnapshotId = otherSnapshot.uid;
737             var provider = snapshot.createNodesProvider(
738                 "function (node) {" +
739                 "     return node.type === \"" + nodeType + "\" " +
740                 (nodeClassName !== null ? "&& node.className === \"" + nodeClassName + "\"" : "") +
741                 "         && !this.baseSnapshotHasNode(" + otherSnapshotId + ", \"" + className + "\", node.id);" +
742                 "}");
743             provider.snapshot = snapshot;
744             return provider;
745         }
746     },
747
748     _childHashForEntity: function(node)
749     {
750         return node.id;
751     },
752
753     _childHashForNode: function(childNode)
754     {
755         return childNode.snapshotNodeId;
756     },
757
758     comparator: function()
759     {
760         var sortAscending = this.dataGrid.sortOrder === "ascending";
761         var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier;
762         var sortFields = {
763             object: ["id", sortAscending, "selfSize", false],
764             addedCount: ["selfSize", sortAscending, "id", true],
765             removedCount: ["selfSize", sortAscending, "id", true],
766             countDelta: ["selfSize", sortAscending, "id", true],
767             addedSize: ["selfSize", sortAscending, "id", true],
768             removedSize: ["selfSize", sortAscending, "id", true],
769             sizeDelta: ["selfSize", sortAscending, "id", true]
770         }[sortColumnIdentifier];
771         return WebInspector.HeapSnapshotFilteredOrderedIterator.prototype.createComparator(sortFields);
772     },
773
774     populateChildren: function(provider, howMany, atIndex, afterPopulate)
775     {
776         if (!provider && !howMany) {
777             var firstProviderPopulated = function()
778             {
779                 WebInspector.HeapSnapshotGridNode.prototype.populateChildren.call(this, this._provider._it2, this._defaultPopulateCount, atIndex, afterPopulate);
780             };
781             WebInspector.HeapSnapshotGridNode.prototype.populateChildren.call(this, this._provider._it1, this._defaultPopulateCount, atIndex, firstProviderPopulated.bind(this), true);
782         } else if (!howMany) {
783             var firstProviderPopulated = function()
784             {
785                 WebInspector.HeapSnapshotGridNode.prototype.populateChildren.call(this, this._provider._it2, null, atIndex, afterPopulate);
786             };
787             WebInspector.HeapSnapshotGridNode.prototype.populateChildren.call(this, this._provider._it1, null, atIndex, firstProviderPopulated.bind(this), true);
788         } else
789             WebInspector.HeapSnapshotGridNode.prototype.populateChildren.call(this, provider, howMany, atIndex, afterPopulate);
790     },
791
792     _signForDelta: function(delta)
793     {
794         if (delta === 0)
795             return "";
796         if (delta > 0)
797             return "+";
798         else
799             return "\u2212";  // Math minus sign, same width as plus.
800     },
801
802     get data()
803     {
804         var data = {object: this._name};
805
806         data["addedCount"] = Number.withThousandsSeparator(this._addedCount);
807         data["removedCount"] = Number.withThousandsSeparator(this._removedCount);
808         data["countDelta"] = this._signForDelta(this._countDelta) + Number.withThousandsSeparator(Math.abs(this._countDelta));
809         data["addedSize"] = Number.withThousandsSeparator(this._addedSize);
810         data["removedSize"] = Number.withThousandsSeparator(this._removedSize);
811         data["sizeDelta"] = this._signForDelta(this._sizeDelta) + Number.withThousandsSeparator(Math.abs(this._sizeDelta));
812
813         return data;
814     }
815 };
816
817 WebInspector.HeapSnapshotDiffNode.prototype.__proto__ = WebInspector.HeapSnapshotGridNode.prototype;
818
819 WebInspector.HeapSnapshotDominatorObjectNode = function(tree, node)
820 {
821     WebInspector.HeapSnapshotGenericObjectNode.call(this, tree, node);
822     this._provider = this._createProvider(tree.snapshot, node.nodeIndex);
823     this.updateHasChildren();
824 };
825
826 WebInspector.HeapSnapshotDominatorObjectNode.prototype = {
827     _createChildNode: function(item)
828     {
829         return new WebInspector.HeapSnapshotDominatorObjectNode(this.dataGrid, item);
830     },
831
832     _createProvider: function(snapshot, nodeIndex)
833     {
834         var showHiddenData = WebInspector.DetailedHeapshotView.prototype.showHiddenData;
835         return snapshot.createNodesProviderForDominator(nodeIndex,
836             "function (node) {" +
837             "     return " + showHiddenData + " || !node.isHidden;" +
838             "}");
839     },
840
841     _childHashForEntity: function(node)
842     {
843         return node.id;
844     },
845
846     _childHashForNode: function(childNode)
847     {
848         return childNode.snapshotNodeId;
849     },
850
851     comparator: function()
852     {
853         var sortAscending = this.dataGrid.sortOrder === "ascending";
854         var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier;
855         var sortFields = {
856             object: ["id", sortAscending, "retainedSize", false],
857             shallowSize: ["selfSize", sortAscending, "id", true],
858             retainedSize: ["retainedSize", sortAscending, "id", true]
859         }[sortColumnIdentifier];
860         return WebInspector.HeapSnapshotFilteredOrderedIterator.prototype.createComparator(sortFields);
861     },
862
863     _emptyData: function()
864     {
865         return {};
866     }
867 };
868
869 WebInspector.HeapSnapshotDominatorObjectNode.prototype.__proto__ = WebInspector.HeapSnapshotGenericObjectNode.prototype;
870
871 function MixInSnapshotNodeFunctions(sourcePrototype, targetPrototype)
872 {
873     targetPrototype._childHashForEntity = sourcePrototype._childHashForEntity;
874     targetPrototype._childHashForNode = sourcePrototype._childHashForNode;
875     targetPrototype.comparator = sourcePrototype.comparator;
876     targetPrototype._createChildNode = sourcePrototype._createChildNode;
877     targetPrototype._createProvider = sourcePrototype._createProvider;
878     targetPrototype.dispose = sourcePrototype.dispose;
879     targetPrototype.populateChildren = sourcePrototype.populateChildren;
880     targetPrototype._saveChildren = sourcePrototype._saveChildren;
881     targetPrototype.sort = sourcePrototype.sort;
882 }