1/*
2 * Copyright (C) 2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import { BaseElement, element } from '../../../../../base-ui/BaseElement';
17import { LitTable } from '../../../../../base-ui/table/lit-table';
18import '../../../../../base-ui/table/lit-table';
19import { type ConstructorItem, FileInfo } from '../../../../../js-heap/model/UiStruct';
20import { HeapDataInterface } from '../../../../../js-heap/HeapDataInterface';
21import '../../../../../base-ui/table/lit-table-column';
22import { TabPaneJsMemoryFilter } from '../TabPaneJsMemoryFilter';
23import '../TabPaneJsMemoryFilter';
24import { LitProgressBar } from '../../../../../base-ui/progress-bar/LitProgressBar';
25import '../../../../../base-ui/progress-bar/LitProgressBar';
26import '../../../../../base-ui/slicer/lit-slicer';
27import { HeapSnapshotStruct } from '../../../../database/ui-worker/ProcedureWorkerHeapSnapshot';
28import { HeapTraceFunctionInfo } from '../../../../../js-heap/model/DatabaseStruct';
29import { TabPaneSummaryHtml } from './TabPaneSummary.html';
30
31@element('tabpane-summary')
32export class TabPaneSummary extends BaseElement {
33  private tblSummary: LitTable | null | undefined;
34  private tbs: LitTable | null | undefined;
35  private stackTable: LitTable | null | undefined;
36  private summary: Array<ConstructorItem> = [];
37  private retainsData: Array<ConstructorItem> = [];
38  private stackData: Array<HeapTraceFunctionInfo> = [];
39  private stackText: HTMLElement | undefined;
40  private fileSize: number = 0;
41  private tabFilter: TabPaneJsMemoryFilter | undefined | null;
42  private progressEL: LitProgressBar | null | undefined;
43  private summaryFilter: Array<ConstructorItem> = [];
44  private summaryData: Array<ConstructorItem> = [];
45  private search: HTMLInputElement | null | undefined;
46  private tbsTable: HTMLDivElement | null | undefined;
47  private tblTable: HTMLDivElement | null | undefined;
48  private rightTheadTable: HTMLDivElement | null | undefined;
49  private leftTheadTable: HTMLDivElement | null | undefined;
50  private leftArray: ConstructorItem[] = [];
51  private rightArray: ConstructorItem[] = [];
52  private stack: HTMLLIElement | null | undefined;
53  private retainers: HTMLLIElement | null | undefined;
54  private file: FileInfo | undefined | null;
55  private leftTable: HTMLDivElement | null | undefined;
56
57  initElements(): void {
58    this.tblSummary = this.shadowRoot?.querySelector<LitTable>('#summary_left');
59    this.tbs = this.shadowRoot?.querySelector<LitTable>('#summary_right');
60    this.stackTable = this.shadowRoot?.querySelector<LitTable>('#stackTable');
61    this.stackText = this.shadowRoot?.querySelector('.stackText') as HTMLElement;
62    this.tabFilter = this.shadowRoot?.querySelector('#filter') as TabPaneJsMemoryFilter;
63    this.progressEL = this.shadowRoot?.querySelector('.summary_progress') as LitProgressBar;
64    this.search = this.tabFilter?.shadowRoot?.querySelector('#js-memory-filter-input') as HTMLInputElement;
65    this.stack = this.shadowRoot?.querySelector('#stack') as HTMLLIElement;
66    this.retainers = this.shadowRoot?.querySelector('#retainers') as HTMLLIElement;
67    this.tblTable = this.tblSummary!.shadowRoot?.querySelector('.table') as HTMLDivElement;
68    this.rightTheadTable = this.tbs!.shadowRoot?.querySelector('.thead') as HTMLDivElement;
69    this.leftTheadTable = this.tblSummary!.shadowRoot?.querySelector('.thead') as HTMLDivElement;
70    this.tbsTable = this.tbs!.shadowRoot?.querySelector('.table') as HTMLDivElement;
71    this.leftTable = this.shadowRoot?.querySelector('#summary_left_table') as HTMLDivElement;
72    this.classFilter();
73  }
74
75  setSnapshotData(
76    data: HeapSnapshotStruct,
77    dataListCache: Array<HeapSnapshotStruct>,
78    scrollCallback: ((d: HeapSnapshotStruct, ds: Array<HeapSnapshotStruct>) => void) | undefined
79  ): void {
80    if (scrollCallback) {
81      scrollCallback(data, dataListCache);
82    }
83    this.summary = [];
84    this.initSummaryData(data);
85  }
86
87  initSummaryData(file: FileInfo | HeapSnapshotStruct, minNodeId?: number, maxNodeId?: number): void {
88    this.file = file as FileInfo;
89    this.clear();
90    this.summary = [];
91    this.progressEL!.loading = true;
92    this.summary = HeapDataInterface.getInstance().getClassesListForSummary(file.id, minNodeId, maxNodeId);
93    this.fileSize = file.size;
94    this.summary.forEach((summaryEl: unknown) => {
95      // @ts-ignore
96      if (summaryEl.childCount > 1) {
97        // @ts-ignore
98        let count = `${summaryEl.nodeName} ×${summaryEl.childCount}`; // @ts-ignore
99        summaryEl.objectName = count; // @ts-ignore
100        summaryEl.count = ` ×${summaryEl.childCount}`; // @ts-ignore
101      } else {
102        // @ts-ignore
103        summaryEl.objectName = summaryEl.nodeName; // @ts-ignore
104      } // @ts-ignore
105      let shallow = `${Math.round((summaryEl.shallowSize / file.size) * 100)}%`; // @ts-ignore
106      let retained = `${Math.round((summaryEl.retainedSize / file.size) * 100)}%`; // @ts-ignore
107      summaryEl.shallowPercent = shallow; // @ts-ignore
108      summaryEl.retainedPercent = retained; // @ts-ignore
109      if (summaryEl.distance >= 100000000 || summaryEl.distance === -5) {
110        // @ts-ignore
111        summaryEl.distance = '-';
112      }
113    });
114    if (this.summary.length > 0) {
115      this.summaryData = this.summary;
116      this.tblSummary!.snapshotDataSource = this.summary;
117      this.progressEL!.loading = false;
118    } else {
119      this.tblSummary!.snapshotDataSource = [];
120      this.progressEL!.loading = false;
121    }
122    new ResizeObserver(() => {
123      if (this.parentElement?.clientHeight !== 0) {
124        this.tblSummary!.style.height = '100%';
125        this.tblSummary!.reMeauseHeight();
126      }
127    }).observe(this.parentElement!);
128    if (this.file!.name.includes('Timeline')) {
129      this.retainers!.classList.add('active');
130      this.stack!.style.display = 'flex';
131      this.retainers!.style.pointerEvents = 'auto';
132    } else {
133      this.stack!.style.display = 'none';
134      this.retainers!.classList.remove('active');
135      this.retainers!.style.pointerEvents = 'none';
136    }
137    this.clickToggleTable();
138  }
139
140  private retainsTableByDistance(currentLeftItem: ConstructorItem, sort: number): void {
141    const getList = function (list: Array<ConstructorItem>): void {
142      list.sort((leftA, rightB) => {
143        return sort === 1 ? leftA.distance - rightB.distance : rightB.distance - leftA.distance;
144      });
145      list.forEach(function (row) {
146        if (row.children.length > 0) {
147          getList(row.children);
148        }
149      });
150    };
151    getList(currentLeftItem.children);
152  }
153
154  private retainsTableByShallowSize(currentLeftItem: ConstructorItem, sort: number): void {
155    const getList = function (list: Array<ConstructorItem>): void {
156      list.sort((leftA, rightB) => {
157        return sort === 1 ? leftA.shallowSize - rightB.shallowSize : rightB.shallowSize - leftA.shallowSize;
158      });
159      list.forEach(function (row) {
160        if (row.children.length > 0) {
161          getList(row.children);
162        }
163      });
164    };
165    getList(currentLeftItem.children);
166  }
167
168  private retainsTableByRetainedSize(currentLeftItem: ConstructorItem, sort: number): void {
169    const getList = function (list: Array<ConstructorItem>): void {
170      list.sort((leftA, rightB) => {
171        return sort === 1 ? leftA.retainedSize - rightB.retainedSize : rightB.retainedSize - leftA.retainedSize;
172      });
173      list.forEach(function (row) {
174        if (row.children.length > 0) {
175          getList(row.children);
176        }
177      });
178    };
179    getList(currentLeftItem.children);
180  }
181
182  private retainsTableByObjectName(currentLeftItem: ConstructorItem, sort: number): void {
183    const getList = function (list: Array<ConstructorItem>): void {
184      list.sort((leftA, rightB) => {
185        return sort === 1
186          ? `${leftA.objectName}`.localeCompare(`${rightB.objectName}`)
187          : `${rightB.objectName}`.localeCompare(`${leftA.objectName}`);
188      });
189      list.forEach(function (row) {
190        if (row.children.length > 0) {
191          getList(row.children);
192        }
193      });
194    };
195    getList(currentLeftItem.children);
196  }
197
198  sortByLeftTable(column: string, sort: number): void {
199    switch (sort) {
200      case 0:
201        if (this.search!.value === '') {
202          this.tblSummary!.snapshotDataSource = this.summary;
203        } else {
204          this.tblSummary!.snapshotDataSource = this.summaryFilter;
205        }
206        break;
207      default:
208        if (this.search!.value === '') {
209          this.leftArray = [...this.summary];
210        } else {
211          this.leftArray = [...this.summaryFilter];
212        }
213        switch (column) {
214          case 'distance':
215            this.sortLeftByDistanceColum(sort);
216            break;
217          case 'shallowSize':
218            this.sortLeftByShallowSizeColum(sort);
219            break;
220          case 'retainedSize':
221            this.sortLeftByRetainedSizeColum(sort);
222            break;
223          case 'objectName':
224            this.sortLeftByObjectNameColum(sort);
225            break;
226        }
227        break;
228    }
229  }
230  private sortLeftByObjectNameColum(sort: number): void {
231    this.tblSummary!.snapshotDataSource = this.leftArray.sort((leftData, rightData) => {
232      return sort === 1
233        ? `${leftData.objectName}`.localeCompare(`${rightData.objectName}`)
234        : `${rightData.objectName}`.localeCompare(`${leftData.objectName}`);
235    });
236    this.leftArray.forEach((currentLeftItem) => {
237      this.retainsTableByObjectName(currentLeftItem, sort);
238    });
239    this.tblSummary!.snapshotDataSource = this.leftArray;
240  }
241
242  private sortLeftByRetainedSizeColum(sort: number): void {
243    this.tblSummary!.snapshotDataSource = this.leftArray.sort((leftData, rightData) => {
244      return sort === 1
245        ? leftData.retainedSize - rightData.retainedSize
246        : rightData.retainedSize - leftData.retainedSize;
247    });
248    this.leftArray.forEach((currentLeftItem) => {
249      this.retainsTableByRetainedSize(currentLeftItem, sort);
250    });
251    this.tblSummary!.snapshotDataSource = this.leftArray;
252  }
253
254  private sortLeftByShallowSizeColum(sort: number): void {
255    this.tblSummary!.snapshotDataSource = this.leftArray.sort((leftData, rightData) => {
256      return sort === 1 ? leftData.shallowSize - rightData.shallowSize : rightData.shallowSize - leftData.shallowSize;
257    });
258    this.leftArray.forEach((currentLeftItem) => {
259      this.retainsTableByShallowSize(currentLeftItem, sort);
260    });
261    this.tblSummary!.snapshotDataSource = this.leftArray;
262  }
263
264  private sortLeftByDistanceColum(sort: number): void {
265    this.tblSummary!.snapshotDataSource = this.leftArray.sort((leftData, rightData) => {
266      return sort === 1 ? leftData.distance - rightData.distance : rightData.distance - leftData.distance;
267    });
268    this.leftArray.forEach((currentLeftItem) => {
269      this.retainsTableByDistance(currentLeftItem, sort);
270    });
271    this.tblSummary!.snapshotDataSource = this.leftArray;
272  }
273
274  sortByRightTable(column: string, sort: number): void {
275    switch (sort) {
276      case 0:
277        this.tbs!.snapshotDataSource = this.retainsData;
278        break;
279      default:
280        this.rightArray = [...this.retainsData];
281        switch (column) {
282          case 'distance':
283            this.tbs!.snapshotDataSource = this.rightArray.sort((a, b) => {
284              return sort === 1 ? a.distance - b.distance : b.distance - a.distance;
285            });
286            this.rightArray.forEach((list) => {
287              this.retainsTableByDistance(list, sort);
288            });
289            this.tbs!.snapshotDataSource = this.rightArray;
290            break;
291          case 'shallowSize':
292            this.tbs!.snapshotDataSource = this.rightArray.sort((a, b) => {
293              return sort === 1 ? a.shallowSize - b.shallowSize : b.shallowSize - a.shallowSize;
294            });
295            this.rightArray.forEach((list) => {
296              this.retainsTableByShallowSize(list, sort);
297            });
298            this.tbs!.snapshotDataSource = this.rightArray;
299            break;
300          case 'retainedSize':
301            this.tbs!.snapshotDataSource = this.rightArray.sort((a, b) => {
302              return sort === 1 ? a.retainedSize - b.retainedSize : b.retainedSize - a.retainedSize;
303            });
304            this.rightArray.forEach((list) => {
305              this.retainsTableByRetainedSize(list, sort);
306            });
307            this.tbs!.snapshotDataSource = this.rightArray;
308            break;
309          case 'objectName':
310            this.tbs!.snapshotDataSource = this.rightArray.sort((a, b) => {
311              return sort === 1
312                ? `${a.objectName}`.localeCompare(`${b.objectName}`)
313                : `${b.objectName}`.localeCompare(`${a.objectName}`);
314            });
315            this.rightArray.forEach((list) => {
316              this.retainsTableByObjectName(list, sort);
317            });
318            this.tbs!.snapshotDataSource = this.rightArray;
319            break;
320        }
321        break;
322    }
323  }
324
325  clickToggleTable(): void {
326    let lis = this.shadowRoot?.querySelectorAll<HTMLElement>('li')!;
327    for (const item of lis) {
328      item.onclick = (): void => {
329        item.className = '';
330        switch (item.textContent) {
331          case 'Retainers':
332            this.stackTable!.style.display = 'none';
333            this.stackText!.style.display = 'none';
334            this.tbs!.style.display = 'flex';
335            this.tbs!.snapshotDataSource = this.retainsData;
336            break;
337          case 'Allocation stack':
338            if (this.stackData.length > 0) {
339              this.stackText!.style.display = 'none';
340              this.stackTable!.style.display = 'flex';
341              this.stackTable!.recycleDataSource = this.stackData;
342            } else {
343              this.stackText!.style.display = 'flex';
344              if (this.retainsData === undefined || this.retainsData.length === 0) {
345                this.stackText!.textContent = '';
346              } else {
347                this.stackText!.textContent =
348                  'Stack was not recorded for this object because it had been allocated before ' +
349                  'this profile recording started.';
350              }
351            }
352            this.tbs!.style.display = 'none';
353            break;
354        }
355        // @ts-ignore
356        this.className = 'active';
357      };
358    }
359  }
360
361  classFilter(): void {
362    this.search!.addEventListener('keyup', () => {
363      this.summaryFilter = [];
364      this.summaryData.forEach((a) => {
365        if (a.objectName.toLowerCase().includes(this.search!.value.toLowerCase())) {
366          this.summaryFilter.push(a);
367        } else {
368        }
369      });
370      this.tblSummary!.snapshotDataSource = this.summaryFilter;
371      let summaryTable = this.tblSummary!.shadowRoot?.querySelector('.table') as HTMLDivElement;
372      summaryTable.scrollTop = 0;
373      this.tblSummary!.reMeauseHeight();
374    });
375  }
376
377  clear(): void {
378    this.tbs!.snapshotDataSource = [];
379    this.stackTable!.recycleDataSource = [];
380    this.retainsData = [];
381    this.stackText!.textContent = '';
382    this.search!.value = '';
383    this.stack!.classList.remove('active');
384    this.tblTable!.scrollTop = 0;
385    this.stackTable!.style.display = 'none';
386    this.stackText!.style.display = 'none';
387    this.tbs!.style.display = 'flex';
388    this.rightTheadTable!.removeAttribute('sort');
389    this.leftTheadTable!.removeAttribute('sort');
390  }
391
392  connectedCallback(): void {
393    super.connectedCallback();
394    let filterHeight = 0;
395    let parentWidth = `${this.parentElement!.clientWidth}px`;
396    let system = document
397      .querySelector('body > sp-application')
398      ?.shadowRoot?.querySelector('#app-content > sp-system-trace');
399    new ResizeObserver(() => {
400      let summaryPaneFilter = this.shadowRoot!.querySelector('#filter') as HTMLElement;
401      if (summaryPaneFilter.clientHeight > 0) {
402        filterHeight = summaryPaneFilter.clientHeight;
403      }
404      if (this.parentElement!.clientHeight > filterHeight) {
405        summaryPaneFilter.style.display = 'flex';
406      } else {
407        summaryPaneFilter.style.display = 'none';
408      }
409      parentWidth = `${this.parentElement!.clientWidth}px`;
410      this.tbs!.style.height = 'calc(100% - 30px)';
411      this.tbsTable!.style.width = `calc(${parentWidth} - ${this.leftTable!.style.width} - 5px)`;
412      this.tbs!.reMeauseHeight();
413      this.tblSummary!.reMeauseHeight();
414    }).observe(this.parentElement!);
415    new ResizeObserver(() => {
416      this.parentElement!.style.width = `${system!.clientWidth}px`;
417      this.style.width = `${system!.clientWidth}px`;
418    }).observe(system!);
419    new ResizeObserver(() => {
420      this.tbsTable!.style.width = `calc(${parentWidth} - ${this.leftTable!.style.width} - 5px)`;
421    }).observe(this.leftTable!);
422
423    this.tblSummary!.addEventListener('row-click', this.tblSummaryRowClick);
424    this.tbs!.addEventListener('row-click', this.tbsRowClick);
425    this.tblSummary!.addEventListener('icon-click', this.tblSummaryIconClick);
426    this.tbs!.addEventListener('icon-click', this.tbsIconClick);
427    this.tblSummary!.addEventListener('column-click', this.tblSummaryColumnClick);
428    this.tbs!.addEventListener('column-click', this.tblColumnClick);
429  }
430
431  disconnectedCallback(): void {
432    super.disconnectedCallback();
433    this.tblSummary!.removeEventListener('row-click', this.tblSummaryRowClick);
434    this.tbs!.removeEventListener('row-click', this.tbsRowClick);
435    this.tblSummary!.removeEventListener('icon-click', this.tblSummaryIconClick);
436    this.tbs!.removeEventListener('icon-click', this.tbsIconClick);
437    this.tblSummary!.removeEventListener('column-click', this.tblSummaryColumnClick);
438    this.tbs!.removeEventListener('column-click', this.tblColumnClick);
439  }
440
441  tblColumnClick = (evt: Event): void => {
442    // @ts-ignore
443    this.sortByRightTable(evt.detail.key, evt.detail.sort);
444    this.tbs!.reMeauseHeight();
445  };
446
447  tblSummaryColumnClick = (evt: Event): void => {
448    // @ts-ignore
449    this.sortByLeftTable(evt.detail.key, evt.detail.sort);
450    this.tblSummary!.reMeauseHeight();
451  };
452
453  tbsIconClick = (evt: Event): void => {
454    // @ts-ignore
455    let data = evt.detail.data;
456    if (data.status) {
457      data.getChildren();
458      let i = 0;
459      let retainsTable = (): void => {
460        const getList = (list: Array<ConstructorItem>): void => {
461          list.forEach((currentRow: ConstructorItem) => {
462            let shallow = `${Math.round((currentRow.shallowSize / this.fileSize) * 100)}%`;
463            let retained = `${Math.round((currentRow.retainedSize / this.fileSize) * 100)}%`;
464            currentRow.shallowPercent = shallow;
465            currentRow.retainedPercent = retained;
466            let nodeId = `${currentRow.nodeName} @${currentRow.id}`;
467            currentRow.objectName = `${currentRow.edgeName}\xa0` + 'in' + `\xa0${nodeId}`;
468            if (currentRow.distance >= 100000000 || currentRow.distance === -5) {
469              // @ts-ignore
470              currentRow.distance = '-';
471            }
472            i++;
473            // @ts-ignore
474            if (i < evt.detail.data.distance - 1 && list[0].distance !== '-') {
475              list[0].getChildren();
476              list[0].expanded = false;
477              if (currentRow.hasNext) {
478                getList(currentRow.children);
479              }
480            } else {
481              return;
482            }
483          });
484        };
485        getList(data.children);
486      };
487      retainsTable();
488    } else {
489      data.status = true;
490    }
491    if (this.rightTheadTable!.hasAttribute('sort')) {
492      this.tbs!.snapshotDataSource = this.rightArray;
493    } else {
494      this.tbs!.snapshotDataSource = this.retainsData;
495    }
496    new ResizeObserver(() => {
497      if (this.parentElement?.clientHeight !== 0) {
498        this.tbs!.style.height = 'calc(100% - 30px)';
499        this.tbs!.reMeauseHeight();
500      }
501    }).observe(this.parentElement!);
502  };
503
504  tblSummaryIconClick = (evt: Event): void => {
505    // @ts-ignore
506    let data = evt.detail.data;
507    if (data.status) {
508      data.getChildren();
509      if (data.children.length > 0) {
510        data.children.sort(function (a: ConstructorItem, b: ConstructorItem) {
511          return b.retainedSize - a.retainedSize;
512        });
513        data.children.forEach((summaryDataEl: any) => {
514          let shallow = `${Math.round((summaryDataEl.shallowSize / this.fileSize) * 100)}%`;
515          let retained = `${Math.round((summaryDataEl.retainedSize / this.fileSize) * 100)}%`;
516          summaryDataEl.shallowPercent = shallow;
517          summaryDataEl.retainedPercent = retained;
518          if (summaryDataEl.distance >= 100000000 || summaryDataEl.distance === -5) {
519            summaryDataEl.distance = '-';
520          }
521          let nodeId = `${summaryDataEl.nodeName} @${summaryDataEl.id}`;
522          summaryDataEl.nodeId = ` @${summaryDataEl.id}`;
523          if (data.isString()) {
524            summaryDataEl.objectName = `"${summaryDataEl.nodeName}"` + ` @${summaryDataEl.id}`;
525          } else {
526            summaryDataEl.objectName = nodeId;
527          }
528          if (summaryDataEl.edgeName !== '') {
529            summaryDataEl.objectName = `${summaryDataEl.edgeName}\xa0` + '::' + `\xa0${nodeId}`;
530          }
531        });
532      } else {
533        this.tblSummary!.snapshotDataSource = [];
534      }
535    } else {
536      data.status = true;
537    }
538    this.tblSummaryIconClickExtend();
539  };
540
541  tblSummaryIconClickExtend(): void {
542    if (this.search!.value !== '') {
543      if (this.leftTheadTable!.hasAttribute('sort')) {
544        this.tblSummary!.snapshotDataSource = this.leftArray;
545      } else {
546        this.tblSummary!.snapshotDataSource = this.summaryFilter;
547      }
548    } else {
549      if (this.leftTheadTable!.hasAttribute('sort')) {
550        this.tblSummary!.snapshotDataSource = this.leftArray;
551      } else {
552        this.tblSummary!.snapshotDataSource = this.summary;
553      }
554    }
555    new ResizeObserver(() => {
556      if (this.parentElement?.clientHeight !== 0) {
557        this.tblSummary!.style.height = '100%';
558        this.tblSummary!.reMeauseHeight();
559      }
560    }).observe(this.parentElement!);
561  }
562
563  tbsRowClick = (rowEvent: Event): void => {
564    // @ts-ignore
565    let data = rowEvent.detail.data as ConstructorItem;
566    (data as any).isSelected = true;
567    // @ts-ignore
568    if ((rowEvent.detail as any).callBack) {
569      // @ts-ignore
570      (rowEvent.detail as any).callBack(true);
571    }
572  };
573
574  tblSummaryRowClick = (evt: Event): void => {
575    this.rightTheadTable!.removeAttribute('sort');
576    this.tbsTable!.scrollTop = 0;
577    //@ts-ignore
578    let data = evt.detail.data as ConstructorItem;
579    (data as any).isSelected = true;
580    this.initRetainsData(data);
581    if (this.retainsData.length > 0) {
582      if (this.retainsData[0].distance > 1) {
583        this.retainsData[0].getChildren();
584        this.retainsData[0].expanded = false;
585      }
586      let i = 0;
587      let retainsTable = (): void => {
588        const getList = (list: Array<ConstructorItem>): void => {
589          list.forEach((summaryRow: ConstructorItem) => {
590            let retainsShallow = `${Math.round((summaryRow.shallowSize / this.fileSize) * 100)}%`;
591            let retained = `${Math.round((summaryRow.retainedSize / this.fileSize) * 100)}%`;
592            summaryRow.shallowPercent = retainsShallow;
593            summaryRow.retainedPercent = retained;
594            let nodeId = `${summaryRow.nodeName} @${summaryRow.id}`;
595            summaryRow.objectName = `${summaryRow.edgeName}\xa0` + 'in' + `\xa0${nodeId}`;
596            if (summaryRow.distance >= 100000000 || summaryRow.distance === -5) {
597              //@ts-ignore
598              summaryRow.distance = '-';
599            }
600            i++;
601            //@ts-ignore
602            if (i < this.retainsData[0].distance - 1 && list[0].distance !== '-') {
603              list[0].getChildren();
604              list[0].expanded = false;
605              if (summaryRow.hasNext) {
606                getList(summaryRow.children);
607              }
608            } else {
609              return;
610            }
611          });
612        };
613        getList(this.retainsData[0].children);
614      };
615      retainsTable();
616      this.tbs!.snapshotDataSource = this.retainsData;
617    } else {
618      this.tbs!.snapshotDataSource = [];
619    }
620    //@ts-ignore
621    this.tblSummaryRowClickExtend(evt.detail);
622  };
623
624  private initRetainsData(data: ConstructorItem): void {
625    this.retainsData = [];
626    this.retainsData = HeapDataInterface.getInstance().getRetains(data);
627    this.retainsData.forEach((element) => {
628      let shallow = `${Math.round((element.shallowSize / this.fileSize) * 100)}%`;
629      let retained = `${Math.round((element.retainedSize / this.fileSize) * 100)}%`;
630      element.shallowPercent = shallow;
631      element.retainedPercent = retained;
632      if (element.distance >= 100000000 || element.distance === -5) {
633        //@ts-ignore
634        element.distance = '-';
635      }
636      let nodeId = `${element.nodeName} @${element.id}`;
637      element.objectName = `${element.edgeName}\xa0` + 'in' + `\xa0${nodeId}`;
638    });
639  }
640
641  private tblSummaryRowClickExtend(detail: any): void {
642    if (this.file!.name.includes('Timeline')) {
643      this.stackData = HeapDataInterface.getInstance().getAllocationStackData(detail.data);
644      if (this.stackData.length > 0) {
645        this.stackTable!.recycleDataSource = this.stackData;
646        this.stackText!.textContent = '';
647        this.stackText!.style.display = 'none';
648        if (this.stack!.className === 'active') {
649          this.stackTable!.style.display = 'grid';
650          this.tbs!.style.display = 'none';
651        }
652      } else {
653        this.stackText!.style.display = 'flex';
654        this.stackTable!.recycleDataSource = [];
655        this.stackTable!.style.display = 'none';
656        if (this.retainers!.className === 'active') {
657          this.stackText!.style.display = 'none';
658        }
659        if (this.retainsData === undefined || this.retainsData.length === 0) {
660          this.stackText!.textContent = '';
661        } else {
662          this.stackText!.textContent =
663            'Stack was not recorded for this object because it had been allocated before ' +
664            'this profile recording started.';
665        }
666      }
667    }
668    new ResizeObserver(() => {
669      this.tbs!.style.height = 'calc(100% - 30px)';
670      this.tbs!.reMeauseHeight();
671      this.stackTable!.style.height = 'calc(100% - 30px)';
672      this.stackTable!.reMeauseHeight();
673    }).observe(this.parentElement!);
674    if (detail.callBack) {
675      detail.callBack(true);
676    }
677  }
678
679  initHtml(): string {
680    return TabPaneSummaryHtml;
681  }
682}
683