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 { HeapDataInterface } from '../../../../../js-heap/HeapDataInterface'; 19import { ConstructorComparison, ConstructorItem, ConstructorType } from '../../../../../js-heap/model/UiStruct'; 20import '../../../../../base-ui/table/lit-table-column'; 21import { TabPaneJsMemoryFilter } from '../TabPaneJsMemoryFilter'; 22import '../TabPaneJsMemoryFilter'; 23import { HeapSnapshotStruct } from '../../../../database/ui-worker/ProcedureWorkerHeapSnapshot'; 24import { LitSelectOption } from '../../../../../base-ui/select/LitSelectOption'; 25import { LitSelect } from '../../../../../base-ui/select/LitSelect'; 26import { TabPaneComparisonHtml } from './TabPaneComparison.html'; 27 28@element('tabpane-comparison') 29export class TabPaneComparison extends BaseElement { 30 private comparisonTableEl: LitTable | undefined | null; 31 private retainerTableEl: LitTable | undefined | null; 32 private comparisonsData: Array<ConstructorComparison> = []; 33 private retainsData: Array<ConstructorItem> = []; 34 private baseFileId: number | undefined | null; 35 private targetFileId: number | undefined | null; 36 private filterEl: TabPaneJsMemoryFilter | undefined | null; 37 private selectEl: LitSelect | undefined | null; 38 private search: HTMLInputElement | undefined | null; 39 private comparisonData: Array<ConstructorItem> = []; 40 private comparisonFilter: Array<ConstructorComparison> = []; 41 private leftArray: Array<ConstructorComparison> = []; 42 private rightArray: Array<ConstructorItem> = []; 43 private rightTheadTable: HTMLDivElement | undefined | null; 44 private leftTheadTable: HTMLDivElement | undefined | null; 45 private comparisonTable: HTMLDivElement | undefined | null; 46 private fileSize: number = 0; 47 48 initElements(): void { 49 this.comparisonTableEl = this.shadowRoot!.querySelector<LitTable>('#tb-comparison') as LitTable; 50 this.retainerTableEl = this.shadowRoot!.querySelector<LitTable>('#tb-retainer') as LitTable; 51 this.filterEl = this.shadowRoot!.querySelector<TabPaneJsMemoryFilter>('#filter'); 52 this.selectEl = this.filterEl?.shadowRoot?.querySelector<LitSelect>('lit-select'); 53 this.search = this.filterEl?.shadowRoot?.querySelector('#js-memory-filter-input') as HTMLInputElement; 54 this.rightTheadTable = this.retainerTableEl!.shadowRoot?.querySelector('.thead') as HTMLDivElement; 55 this.leftTheadTable = this.comparisonTableEl!.shadowRoot?.querySelector('.thead') as HTMLDivElement; 56 this.comparisonTable = this.comparisonTableEl.shadowRoot?.querySelector('.table') as HTMLDivElement; 57 this.classFilter(); 58 } 59 60 initComparison(data: HeapSnapshotStruct, dataListCache: Array<HeapSnapshotStruct>): void { 61 this.clear(); 62 this.retainerTableEl!.snapshotDataSource = []; 63 let fileArr: HeapSnapshotStruct[] = []; 64 for (let file of dataListCache) { 65 if (file.id !== data.id) { 66 fileArr.push(file); 67 } 68 } 69 fileArr = fileArr.sort(); 70 this.fileSize = data.size; 71 this.initSelect(data.id, fileArr); 72 this.baseFileId = data.id; 73 this.targetFileId = fileArr[0].id; 74 this.updateComparisonData(data.id, fileArr[0].id); 75 new ResizeObserver((): void => { 76 this.comparisonTableEl!.style.height = '100%'; 77 this.comparisonTableEl!.reMeauseHeight(); 78 }).observe(this.parentElement!); 79 } 80 81 updateComparisonData(baseFileId: number, targetFileId: number): void { 82 this.comparisonsData = HeapDataInterface.getInstance().getClassListForComparison(baseFileId, targetFileId); 83 this.comparisonsData.forEach((dataList): void => { 84 dataList.objectName = dataList.nodeName; 85 }); 86 if (this.comparisonsData.length > 0) { 87 this.comparisonData = this.comparisonsData; 88 this.comparisonTableEl!.snapshotDataSource = this.comparisonsData; 89 } else { 90 this.comparisonTableEl!.snapshotDataSource = []; 91 } 92 this.comparisonTableEl!.reMeauseHeight(); 93 } 94 95 initSelect(fileId: number, comFileArr: Array<HeapSnapshotStruct>): void { 96 let input = this.selectEl!.shadowRoot?.querySelector('input') as HTMLInputElement; 97 this.selectEl!.innerHTML = ''; 98 this.selectEl!.defaultValue = comFileArr[0].name || ''; 99 this.selectEl!.placeholder = comFileArr[0].name || ''; 100 this.selectEl!.dataSource = comFileArr; 101 let option = new LitSelectOption(); 102 option.innerHTML = 'File Name'; 103 option.setAttribute('disabled', 'disabled'); 104 this.selectEl?.prepend(option); 105 if (comFileArr[0].name) { 106 option.setAttribute('value', comFileArr[0].name); 107 } 108 let selectOption = this.selectEl!.querySelectorAll('lit-select-option'); 109 for (const item of selectOption) { 110 item.addEventListener('onSelected', (e): void => { 111 this.comparisonTable!.scrollTop = 0; 112 this.retainerTableEl!.snapshotDataSource = []; 113 for (let f of comFileArr) { 114 if (input.value === f.name) { 115 this.updateComparisonData(fileId, f.id); 116 } 117 } 118 e.stopPropagation(); 119 }); 120 } 121 } 122 123 sortComprisonByColumn(column: string, sort: number): void { 124 switch (sort) { 125 case 0: 126 if (this.search!.value === '') { 127 this.comparisonTableEl!.snapshotDataSource = this.comparisonsData; 128 } else { 129 this.comparisonTableEl!.snapshotDataSource = this.comparisonFilter; 130 } 131 break; 132 default: 133 if (this.search!.value === '') { 134 this.leftArray = [...this.comparisonsData]; 135 } else { 136 this.leftArray = [...this.comparisonFilter]; 137 } 138 this.sortComprisonByColumnExtend(column, sort); 139 break; 140 } 141 } 142 143 private sortComprisonByColumnExtend(column: string, sort: number): void { 144 switch (column) { 145 case 'addedCount': 146 this.comparisonTableEl!.snapshotDataSource = this.leftArray.sort((a, b): number => { 147 return sort === 1 ? a.addedCount - b.addedCount : b.addedCount - a.addedCount; 148 }); 149 break; 150 case 'removedCount': 151 this.comparisonTableEl!.snapshotDataSource = this.leftArray.sort((a, b): number => { 152 return sort === 1 ? a.removedCount - b.removedCount : b.removedCount - a.removedCount; 153 }); 154 break; 155 case 'deltaCount': 156 this.comparisonTableEl!.snapshotDataSource = this.leftArray.sort((a, b): number => { 157 return sort === 1 ? a.deltaCount - b.deltaCount : b.deltaCount - a.deltaCount; 158 }); 159 break; 160 case 'objectName': 161 this.comparisonTableEl!.snapshotDataSource = this.leftArray.sort((a, b): number => { 162 return sort === 1 163 ? `${a.objectName}`.localeCompare(`${b.objectName}`) 164 : `${b.objectName}`.localeCompare(`${a.objectName}`); 165 }); 166 break; 167 case 'addedSize': 168 this.comparisonTableEl!.snapshotDataSource = this.leftArray.sort((a, b): number => { 169 return sort === 1 ? a.addedSize - b.addedSize : b.addedSize - a.addedSize; 170 }); 171 break; 172 case 'removedSize': 173 this.comparisonTableEl!.snapshotDataSource = this.leftArray.sort((a, b): number => { 174 return sort === 1 ? a.removedSize - b.removedSize : b.removedSize - a.removedSize; 175 }); 176 break; 177 case 'deltaSize': 178 this.comparisonTableEl!.snapshotDataSource = this.leftArray.sort((a, b): number => { 179 return sort === 1 ? a.deltaSize - b.deltaSize : b.deltaSize - a.deltaSize; 180 }); 181 break; 182 } 183 } 184 185 sortRetainerByColumn(column: string, sort: number): void { 186 switch (sort) { 187 case 0: 188 this.retainerTableEl!.snapshotDataSource = this.retainsData; 189 break; 190 default: 191 this.rightArray = [...this.retainsData]; 192 switch (column) { 193 case 'distance': 194 this.sortRetainerByDistanceType(sort); 195 break; 196 case 'shallowSize': 197 this.sortRetainerByShallowSizeType(sort); 198 break; 199 case 'retainedSize': 200 this.sortRetainerByRetainedSizeType(sort); 201 break; 202 case 'objectName': 203 this.sortRetainerByObjectNameType(sort); 204 break; 205 } 206 break; 207 } 208 } 209 210 private sortRetainerByObjectNameType(sort: number): void { 211 this.retainerTableEl!.snapshotDataSource = this.rightArray.sort((rightArrA, rightArrB): number => { 212 return sort === 1 213 ? `${rightArrA.objectName}`.localeCompare(`${rightArrB.objectName}`) 214 : `${rightArrB.objectName}`.localeCompare(`${rightArrA.objectName}`); 215 }); 216 this.rightArray.forEach((list): void => { 217 let retainsTable = (): void => { 218 const getList = (listArr: Array<ConstructorItem>): void => { 219 listArr.sort((listArrA, listArrB): number => { 220 return sort === 1 221 ? `${listArrA.objectName}`.localeCompare(`${listArrB.objectName}`) 222 : `${listArrB.objectName}`.localeCompare(`${listArrA.objectName}`); 223 }); 224 listArr.forEach(function (currentRow): void { 225 if (currentRow.children.length > 0) { 226 getList(currentRow.children); 227 } 228 }); 229 }; 230 getList(list.children); 231 }; 232 retainsTable(); 233 }); 234 this.retainerTableEl!.snapshotDataSource = this.rightArray; 235 } 236 237 private sortRetainerByRetainedSizeType(sort: number): void { 238 this.retainerTableEl!.snapshotDataSource = this.rightArray.sort((rightArrA, rightArrB): number => { 239 return sort === 1 240 ? rightArrA.retainedSize - rightArrB.retainedSize 241 : rightArrB.retainedSize - rightArrA.retainedSize; 242 }); 243 this.rightArray.forEach((list): void => { 244 let retainsTable = (): void => { 245 const getList = (listArr: Array<ConstructorItem>): void => { 246 listArr.sort((listArrA, listArrB): number => { 247 return sort === 1 248 ? listArrA.retainedSize - listArrB.retainedSize 249 : listArrB.retainedSize - listArrA.retainedSize; 250 }); 251 listArr.forEach(function (row): void { 252 if (row.children.length > 0) { 253 getList(row.children); 254 } 255 }); 256 }; 257 getList(list.children); 258 }; 259 retainsTable(); 260 }); 261 this.retainerTableEl!.snapshotDataSource = this.rightArray; 262 } 263 264 private sortRetainerByShallowSizeType(sort: number): void { 265 this.retainerTableEl!.snapshotDataSource = this.rightArray.sort((rightArrA, rightArrB): number => { 266 return sort === 1 ? rightArrA.shallowSize - rightArrB.shallowSize : rightArrB.shallowSize - rightArrA.shallowSize; 267 }); 268 this.rightArray.forEach((list): void => { 269 let retainsTable = (): void => { 270 const getList = (listArr: Array<ConstructorItem>): void => { 271 listArr.sort((listArrA, listArrB): number => { 272 return sort === 1 273 ? listArrA.shallowSize - listArrB.shallowSize 274 : listArrB.shallowSize - listArrA.shallowSize; 275 }); 276 listArr.forEach(function (rowEl): void { 277 if (rowEl.children.length > 0) { 278 getList(rowEl.children); 279 } 280 }); 281 }; 282 getList(list.children); 283 }; 284 retainsTable(); 285 }); 286 this.retainerTableEl!.snapshotDataSource = this.rightArray; 287 } 288 289 private sortRetainerByDistanceType(sort: number): void { 290 this.retainerTableEl!.snapshotDataSource = this.rightArray.sort((a, b): number => { 291 return sort === 1 ? a.distance - b.distance : b.distance - a.distance; 292 }); 293 this.rightArray.forEach((list): void => { 294 let retainsTable = (): void => { 295 const getList = (currentList: Array<ConstructorItem>): void => { 296 currentList.sort((a, b): number => { 297 return sort === 1 ? a.distance - b.distance : b.distance - a.distance; 298 }); 299 currentList.forEach(function (currentRow): void { 300 if (currentRow.children.length > 0) { 301 getList(currentRow.children); 302 } 303 }); 304 }; 305 getList(list.children); 306 }; 307 retainsTable(); 308 }); 309 this.retainerTableEl!.snapshotDataSource = this.rightArray; 310 } 311 312 classFilter(): void { 313 this.search!.addEventListener('keyup', (): void => { 314 this.comparisonFilter = []; 315 this.comparisonData.forEach((a: unknown) => { 316 // @ts-ignore 317 if (a.objectName.toLowerCase().includes(this.search!.value.toLowerCase())) { 318 // @ts-ignore 319 this.comparisonFilter.push(a); 320 } else { 321 } 322 }); 323 this.comparisonTableEl!.snapshotDataSource = this.comparisonFilter; 324 let summaryTable = this.comparisonTableEl!.shadowRoot?.querySelector('.table') as HTMLDivElement; 325 summaryTable.scrollTop = 0; 326 this.comparisonTableEl!.reMeauseHeight(); 327 }); 328 } 329 330 clear(): void { 331 this.search!.value = ''; 332 this.rightTheadTable!.removeAttribute('sort'); 333 this.leftTheadTable!.removeAttribute('sort'); 334 this.comparisonTable!.scrollTop = 0; 335 } 336 337 connectedCallback(): void { 338 super.connectedCallback(); 339 let filterHeight = 0; 340 new ResizeObserver((): void => { 341 let comparisonPanelFilter = this.shadowRoot!.querySelector('#filter') as HTMLElement; 342 if (comparisonPanelFilter.clientHeight > 0) { 343 filterHeight = comparisonPanelFilter.clientHeight; 344 } 345 if (this.parentElement!.clientHeight > filterHeight) { 346 comparisonPanelFilter.style.display = 'flex'; 347 } else { 348 comparisonPanelFilter.style.display = 'none'; 349 } 350 }).observe(this.parentElement!); 351 this.comparisonTableEl!.addEventListener('icon-click', this.comparisonTblIconClickHandler); 352 this.retainerTableEl!.addEventListener('icon-click', this.retainerTblIconClickHandler); 353 this.comparisonTableEl!.addEventListener('column-click', this.comparisonTblColumnClickHandler); 354 this.retainerTableEl!.addEventListener('column-click', this.retainerTblColumnClickHandler); 355 this.comparisonTableEl!.addEventListener('row-click', this.comparisonTblRowClickHandler); 356 this.retainerTableEl!.addEventListener('row-click', this.retainerTblRowClickHandler); 357 } 358 359 disconnectedCallback(): void { 360 super.disconnectedCallback(); 361 this.comparisonTableEl!.removeEventListener('icon-click', this.comparisonTblIconClickHandler); 362 this.retainerTableEl!.removeEventListener('icon-click', this.retainerTblIconClickHandler); 363 this.comparisonTableEl!.removeEventListener('column-click', this.comparisonTblColumnClickHandler); 364 this.retainerTableEl!.removeEventListener('column-click', this.retainerTblColumnClickHandler); 365 this.comparisonTableEl!.removeEventListener('row-click', this.comparisonTblRowClickHandler); 366 this.retainerTableEl!.removeEventListener('row-click', this.retainerTblRowClickHandler); 367 } 368 369 private comparisonTblRowClickHandler = (e: Event): void => { 370 this.rightTheadTable!.removeAttribute('sort'); 371 // @ts-ignore 372 let item = e.detail.data as ConstructorItem; // @ts-ignore 373 (item as unknown).isSelected = true; 374 this.retainsData = HeapDataInterface.getInstance().getRetains(item); 375 if (this.retainsData && this.retainsData.length > 0) { 376 this.retainsDataInit(); 377 let i = 0; 378 if (this.retainsData[0].distance > 1) { 379 this.retainsData[0].getChildren(); 380 this.retainsData[0].expanded = false; 381 } 382 let retainsTable = (): void => { 383 const getList = (list: Array<ConstructorItem>): void => { 384 for (const item of list) { 385 let shallow = `${Math.round((item.shallowSize / this.fileSize) * 100)}%`; 386 let retained = `${Math.round((item.retainedSize / this.fileSize) * 100)}%`; 387 item.shallowPercent = shallow; 388 item.retainedPercent = retained; 389 let nodeId = `${item.nodeName} @${item.id}`; 390 item.objectName = `${item.edgeName}\xa0` + 'in' + `\xa0${nodeId}`; 391 if (item.distance >= 100000000 || item.distance === -5) { 392 // @ts-ignore 393 item.distance = '-'; 394 } 395 i++; 396 // @ts-ignore 397 if (i < this.retainsData[0].distance - 1 && list[0].distance !== '-') { 398 list[0].getChildren(); 399 list[0].expanded = false; 400 if (item.hasNext) { 401 getList(item.children); 402 } 403 } else { 404 return; 405 } 406 } 407 }; 408 getList(this.retainsData[0].children); 409 }; 410 retainsTable(); 411 this.retainerTableEl!.snapshotDataSource = this.retainsData; 412 } else { 413 this.retainerTableEl!.snapshotDataSource = []; 414 } 415 this.resizeObserverObserve(); 416 // @ts-ignore 417 if ((e.detail as unknown).callBack) { 418 // @ts-ignore 419 (e.detail as unknown).callBack(true); 420 } 421 }; 422 423 private resizeObserverObserve(): void { 424 new ResizeObserver((): void => { 425 this.retainerTableEl!.style.height = 'calc(100% - 21px)'; 426 this.retainerTableEl!.reMeauseHeight(); 427 }).observe(this.parentElement!); 428 } 429 430 private retainsDataInit(): void { 431 this.retainsData.forEach((comparisonRetainEl): void => { 432 let shallow = `${Math.round((comparisonRetainEl.shallowSize / this.fileSize) * 100)}%`; 433 let retained = `${Math.round((comparisonRetainEl.retainedSize / this.fileSize) * 100)}%`; 434 comparisonRetainEl.shallowPercent = shallow; 435 comparisonRetainEl.retainedPercent = retained; 436 if (comparisonRetainEl.distance >= 100000000 || comparisonRetainEl.distance === -5) { 437 // @ts-ignore 438 comparisonRetainEl.distance = '-'; 439 } 440 let nodeId = `${comparisonRetainEl.nodeName} @${comparisonRetainEl.id}`; 441 comparisonRetainEl.objectName = `${comparisonRetainEl.edgeName}\xa0` + 'in' + `\xa0${nodeId}`; 442 }); 443 } 444 445 private retainerTblRowClickHandler = (evt: Event): void => { 446 // @ts-ignore 447 let data = evt.detail.data as ConstructorItem; // @ts-ignore 448 (data as unknown).isSelected = true; 449 // @ts-ignore 450 if ((evt.detail as unknown).callBack) { 451 // @ts-ignore 452 (evt.detail as unknown).callBack(true); 453 } 454 }; 455 456 private comparisonTblColumnClickHandler = (e: Event): void => { 457 // @ts-ignore 458 this.sortComprisonByColumn(e.detail.key, e.detail.sort); 459 this.comparisonTableEl!.reMeauseHeight(); 460 }; 461 462 private retainerTblColumnClickHandler = (e: Event): void => { 463 // @ts-ignore 464 this.sortRetainerByColumn(e.detail.key, e.detail.sort); 465 this.retainerTableEl!.reMeauseHeight(); 466 }; 467 468 retainerTblIconClickHandler = (e: Event): void => { 469 // @ts-ignore 470 let retainerNext = e.detail.data as ConstructorItem; 471 if (retainerNext) { 472 if (this.retainsData.length > 0) { 473 if (retainerNext.status) { 474 retainerNext.getChildren(); 475 let i = 0; 476 let retainsTable = (): void => { 477 const getList = (comList: Array<ConstructorItem>): void => { 478 comList.forEach((row): void => { 479 let shallow = `${Math.round((row.shallowSize / this.fileSize) * 100)}%`; 480 let retained = `${Math.round((row.retainedSize / this.fileSize) * 100)}%`; 481 row.shallowPercent = shallow; 482 row.retainedPercent = retained; 483 let nodeId = `${row.nodeName} @${row.id}`; 484 row.objectName = `${row.edgeName}\xa0` + 'in' + `\xa0${nodeId}`; 485 if (row.distance >= 100000000 || row.distance === -5) { 486 // @ts-ignore 487 row.distance = '-'; 488 } 489 i++; 490 // @ts-ignore 491 if (i < this.retainsData[0].distance - 1 && comList[0].distance !== '-') { 492 comList[0].getChildren(); 493 comList[0].expanded = false; 494 if (row.hasNext) { 495 getList(row.children); 496 } 497 } else { 498 return; 499 } 500 }); 501 }; 502 getList(retainerNext.children); 503 }; 504 retainsTable(); 505 } else { 506 retainerNext.status = true; 507 } 508 if (this.rightTheadTable!.hasAttribute('sort')) { 509 this.retainerTableEl!.snapshotDataSource = this.rightArray; 510 } else { 511 this.retainerTableEl!.snapshotDataSource = this.retainsData; 512 } 513 } else { 514 this.retainerTableEl!.snapshotDataSource = []; 515 } 516 this.resizeObserverObserve(); 517 } 518 }; 519 520 comparisonTblIconClickHandler = (e: Event): void => { 521 // @ts-ignore 522 let clickRow = e.detail.data; 523 if (clickRow.status) { 524 clickRow.targetFileId = this.targetFileId; 525 clickRow.children = HeapDataInterface.getInstance().getNextForComparison(clickRow); 526 if (clickRow.children.length > 0) { 527 for (let item of clickRow.children) { 528 let nodeName = `${item.nodeName} @${item.id}`; 529 item.nodeId = ` @${item.id}`; 530 if (item.isString()) { 531 item.objectName = `"${item.nodeName}"` + ` @${item.id}`; 532 } else { 533 item.objectName = nodeName; 534 } 535 item.deltaCount = '-'; 536 item.deltaSize = '-'; 537 if (item.edgeName !== '') { 538 item.objectName = `${item.edgeName}\xa0` + '::' + `\xa0${nodeName}`; 539 } else { 540 if (item.fileId === this.baseFileId) { 541 item.addedCount = '•'; 542 item.addedSize = item.shallowSize; 543 item.removedCount = '-'; 544 item.removedSize = '-'; 545 } else if (item.fileId) { 546 item.removedCount = '•'; 547 item.removedSize = item.shallowSize; 548 item.addedCount = '-'; 549 item.addedSize = '-'; 550 } 551 } 552 if (item.type === ConstructorType.FiledType) { 553 item.removedCount = '-'; 554 item.removedSize = '-'; 555 item.addedCount = '-'; 556 item.addedSize = '-'; 557 } 558 } 559 } else { 560 this.comparisonTableEl!.snapshotDataSource = []; 561 } 562 } else { 563 clickRow.status = true; 564 } 565 this.comparisonTblIconClickData(); 566 }; 567 568 private comparisonTblIconClickData(): void { 569 if (this.search!.value !== '') { 570 if (this.leftTheadTable!.hasAttribute('sort')) { 571 this.comparisonTableEl!.snapshotDataSource = this.leftArray; 572 } else { 573 this.comparisonTableEl!.snapshotDataSource = this.comparisonFilter; 574 } 575 } else { 576 if (this.leftTheadTable!.hasAttribute('sort')) { 577 this.comparisonTableEl!.snapshotDataSource = this.leftArray; 578 } else { 579 this.comparisonTableEl!.snapshotDataSource = this.comparisonsData; 580 } 581 } 582 new ResizeObserver((): void => { 583 this.comparisonTableEl!.style.height = '100%'; 584 this.comparisonTableEl!.reMeauseHeight(); 585 }).observe(this.parentElement!); 586 } 587 588 initHtml(): string { 589 return TabPaneComparisonHtml; 590 } 591} 592