| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| /** |
| * @implements {SDK.SDKModelObserver<!SDK.RuntimeModel>} |
| * @implements {UI.ListDelegate<!Profiler.IsolateSelector.ListItem>} |
| */ |
| Profiler.IsolateSelector = class extends UI.VBox { |
| constructor() { |
| super(true); |
| |
| /** @type {!UI.ListModel<!Profiler.IsolateSelector.ListItem>} */ |
| this._items = new UI.ListModel(); |
| /** @type {!UI.ListControl<!Profiler.IsolateSelector.ListItem>} */ |
| this._list = new UI.ListControl(this._items, this, UI.ListMode.NonViewport); |
| this.contentElement.appendChild(this._list.element); |
| |
| this.registerRequiredCSS('profiler/profileLauncherView.css'); |
| /** @type {!Map<!SDK.RuntimeModel, !Promise<string>>} */ |
| this._isolateByModel = new Map(); |
| /** @type {!Map<string, !Profiler.IsolateSelector.ListItem>} */ |
| this._itemByIsolate = new Map(); |
| |
| SDK.targetManager.observeModels(SDK.RuntimeModel, this); |
| SDK.targetManager.addEventListener(SDK.TargetManager.Events.NameChanged, this._targetChanged, this); |
| SDK.targetManager.addEventListener(SDK.TargetManager.Events.InspectedURLChanged, this._targetChanged, this); |
| } |
| |
| /** |
| * @override |
| * @param {!SDK.RuntimeModel} model |
| */ |
| modelAdded(model) { |
| this._modelAdded(model); |
| } |
| |
| /** |
| * @param {!SDK.RuntimeModel} model |
| */ |
| async _modelAdded(model) { |
| const isolatePromise = model.isolateId(); |
| this._isolateByModel.set(model, isolatePromise); |
| const isolate = await isolatePromise; |
| let item = this._itemByIsolate.get(isolate); |
| if (!item) { |
| item = new Profiler.IsolateSelector.ListItem(model); |
| const index = model.target() === SDK.targetManager.mainTarget() ? 0 : this._items.length; |
| this._items.insert(index, item); |
| this._itemByIsolate.set(isolate, item); |
| if (this._items.length === 1) |
| this._list.selectItem(item); |
| } else { |
| item.addModel(model); |
| } |
| this._update(); |
| } |
| |
| /** |
| * @override |
| * @param {!SDK.RuntimeModel} model |
| */ |
| modelRemoved(model) { |
| this._modelRemoved(model); |
| } |
| |
| /** |
| * @param {!SDK.RuntimeModel} model |
| */ |
| async _modelRemoved(model) { |
| const isolate = await this._isolateByModel.get(model); |
| this._isolateByModel.delete(model); |
| const item = this._itemByIsolate.get(isolate); |
| item.removeModel(model); |
| if (!item.models().length) { |
| this._items.remove(this._items.indexOf(item)); |
| this._itemByIsolate.delete(isolate); |
| } |
| this._update(); |
| } |
| |
| /** |
| * @param {!Common.Event} event |
| */ |
| async _targetChanged(event) { |
| const target = /** @type {!SDK.Target} */ (event.data); |
| const model = target.model(SDK.RuntimeModel); |
| const isolate = model && await this._isolateByModel.get(model); |
| const item = isolate && this._itemByIsolate.get(isolate); |
| if (item) |
| item.updateTitle(); |
| } |
| |
| /** |
| * @override |
| * @param {!Profiler.IsolateSelector.ListItem} item |
| * @return {!Element} |
| */ |
| createElementForItem(item) { |
| return item.element; |
| } |
| |
| /** |
| * @override |
| * @param {!Profiler.IsolateSelector.ListItem} item |
| * @return {number} |
| */ |
| heightForItem(item) { |
| } |
| |
| /** |
| * @override |
| * @param {!Profiler.IsolateSelector.ListItem} item |
| * @return {boolean} |
| */ |
| isItemSelectable(item) { |
| return true; |
| } |
| |
| /** |
| * @override |
| * @param {?Profiler.IsolateSelector.ListItem} from |
| * @param {?Profiler.IsolateSelector.ListItem} to |
| * @param {?Element} fromElement |
| * @param {?Element} toElement |
| */ |
| selectedItemChanged(from, to, fromElement, toElement) { |
| if (fromElement) |
| fromElement.classList.remove('selected'); |
| if (toElement) |
| toElement.classList.add('selected'); |
| const model = to && to.models()[0]; |
| UI.context.setFlavor(SDK.HeapProfilerModel, model && model.heapProfilerModel()); |
| UI.context.setFlavor(SDK.CPUProfilerModel, model && model.target().model(SDK.CPUProfilerModel)); |
| } |
| |
| _update() { |
| this._list.invalidateRange(0, this._items.length); |
| } |
| }; |
| |
| Profiler.IsolateSelector.ListItem = class { |
| /** |
| * @param {!SDK.RuntimeModel} model |
| */ |
| constructor(model) { |
| /** @type {!Set<!SDK.RuntimeModel>} */ |
| this._models = new Set([model]); |
| this.element = createElementWithClass('div', 'profile-isolate-item hbox'); |
| this._heapDiv = this.element.createChild('div', 'profile-isolate-item-heap'); |
| this._nameDiv = this.element.createChild('div', 'profile-isolate-item-name'); |
| this._updateTimer = null; |
| this.updateTitle(); |
| this._updateStats(); |
| } |
| |
| /** |
| * @param {!SDK.RuntimeModel} model |
| */ |
| addModel(model) { |
| this._models.add(model); |
| this.updateTitle(); |
| } |
| |
| /** |
| * @param {!SDK.RuntimeModel} model |
| */ |
| removeModel(model) { |
| this._models.delete(model); |
| this.updateTitle(); |
| if (this._models.size) |
| return; |
| clearTimeout(this._updateTimer); |
| } |
| |
| /** |
| * @return {!Array<!SDK.RuntimeModel>} |
| */ |
| models() { |
| return Array.from(this._models); |
| } |
| |
| async _updateStats() { |
| const heapStats = await this._models.values().next().value.heapUsage(); |
| if (!heapStats) |
| return; |
| const usedTitle = ls`Heap size in use by live JS objects.`; |
| const totalTitle = ls`Total JS heap size including live objects, garbage, and reserved space.`; |
| this._heapDiv.removeChildren(); |
| this._heapDiv.append(UI.html` |
| <span title="${usedTitle}">${Number.bytesToString(heapStats.usedSize)}</span> |
| <span> / </span> |
| <span title="${totalTitle}">${Number.bytesToString(heapStats.totalSize)}</span>`); |
| } |
| |
| updateTitle() { |
| /** @type {!Map<string, number>} */ |
| const modelCountByName = new Map(); |
| for (const model of this._models.values()) { |
| const target = model.target(); |
| const name = SDK.targetManager.mainTarget() !== target ? target.name() : ''; |
| const parsedURL = new Common.ParsedURL(target.inspectedURL()); |
| const domain = parsedURL.isValid ? parsedURL.domain() : ''; |
| const title = target.decorateLabel(domain && name ? `${domain}: ${name}` : name || domain || ls`(empty)`); |
| modelCountByName.set(title, (modelCountByName.get(title) || 0) + 1); |
| } |
| this._nameDiv.removeChildren(); |
| for (const [name, count] of modelCountByName) { |
| const lineDiv = this._nameDiv.createChild('div'); |
| const title = count > 1 ? `${name} (${count})` : name; |
| lineDiv.textContent = title; |
| lineDiv.setAttribute('title', title); |
| } |
| } |
| }; |