blob: 25da14ff70a15abb5b3617ff559b7925f89ce8c7 [file] [log] [blame]
// Copyright 2014 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.TargetManager.Observer}
* @unrestricted
*/
Sources.ThreadsSidebarPane = class extends UI.VBox {
constructor() {
super();
/** @type {?Sources.UIList.Item} */
this._selectedListItem = null;
/** @type {!Map<!SDK.PendingTarget, !Sources.UIList.Item>} */
this._pendingToListItem = new Map();
/** @type {!Map<!SDK.Target, !SDK.PendingTarget>} */
this._targetToPending = new Map();
/** @type {?SDK.PendingTarget} */
this._mainTargetPending = null;
this.threadList = new Sources.UIList();
this.threadList.show(this.element);
SDK.targetManager.addModelListener(
SDK.DebuggerModel, SDK.DebuggerModel.Events.DebuggerPaused, this._onDebuggerStateChanged,
this);
SDK.targetManager.addModelListener(
SDK.DebuggerModel, SDK.DebuggerModel.Events.DebuggerResumed, this._onDebuggerStateChanged,
this);
SDK.targetManager.addModelListener(
SDK.RuntimeModel, SDK.RuntimeModel.Events.ExecutionContextChanged,
this._onExecutionContextChanged, this);
UI.context.addFlavorChangeListener(SDK.Target, this._targetChanged, this);
SDK.targetManager.addEventListener(
SDK.TargetManager.Events.NameChanged, this._targetNameChanged, this);
SDK.targetManager.addModelListener(SDK.SubTargetsManager, SDK.SubTargetsManager.Events.PendingTargetAdded, this._addTargetItem, this);
SDK.targetManager.addModelListener(SDK.SubTargetsManager, SDK.SubTargetsManager.Events.PendingTargetRemoved, this._pendingTargetRemoved, this);
SDK.targetManager.addModelListener(SDK.SubTargetsManager, SDK.SubTargetsManager.Events.PendingTargetAttached, this._addTargetItem, this);
SDK.targetManager.addModelListener(SDK.SubTargetsManager, SDK.SubTargetsManager.Events.PendingTargetDetached, this._targetDetached, this);
SDK.targetManager.observeTargets(this);
var pendingTargets = [];
for (var target of SDK.targetManager.targets(SDK.Target.Capability.Target))
pendingTargets = pendingTargets.concat(SDK.SubTargetsManager.fromTarget(target).pendingTargets());
pendingTargets
.sort(Sources.ThreadsSidebarPane._pendingTargetsComparator)
.forEach(pendingTarget => this._addListItem(pendingTarget));
}
/**
* @return {boolean}
*/
static shouldBeShown() {
if (SDK.targetManager.targets(SDK.Target.Capability.JS).length > 1)
return true;
for (var target of SDK.targetManager.targets(SDK.Target.Capability.Target)) {
var pendingTargets = SDK.SubTargetsManager.fromTarget(target).pendingTargets();
if (pendingTargets.some(pendingTarget => pendingTarget.canConnect()))
return true;
}
return false;
}
/**
* Sorts show tha connected targets appear first, followed by pending subtargets.
*
* @param {!SDK.PendingTarget} c1
* @param {!SDK.PendingTarget} c2
* @return {number}
*/
static _pendingTargetsComparator(c1, c2)
{
var t1 = c1.target();
var t2 = c2.target();
var name1 = t1 ? t1.name() : c1.name();
var name2 = t2 ? t2.name() : c2.name();
if (!!t1 === !!t2) { // Either both are connected or disconnected
return name1.toLowerCase().localeCompare(name2.toLowerCase());
} else if (t1) {
return -1;
}
return 1;
}
/**
* @param {!Common.Event} event
*/
_addTargetItem(event) {
this._addListItem(/** @type {!SDK.PendingTarget} */ (event.data));
}
/**
* @param {!Common.Event} event
*/
_pendingTargetRemoved(event) {
this._removeListItem(/** @type {!SDK.PendingTarget} */ (event.data));
}
/**
* @param {!Common.Event} event
*/
_targetDetached(event) {
this._targetRemoved(/** @type {!SDK.PendingTarget} */ (event.data));
}
/**
* @param {!SDK.PendingTarget} pendingTarget
*/
_addListItem(pendingTarget) {
var target = pendingTarget.target();
if (!pendingTarget.canConnect() && !(target && target.hasJSCapability()))
return;
var listItem = this._pendingToListItem.get(pendingTarget);
if (!listItem) {
listItem = new Sources.UIList.Item('', '', false);
listItem[Sources.ThreadsSidebarPane._pendingTargetSymbol] = pendingTarget;
listItem[Sources.ThreadsSidebarPane._targetSymbol] = target;
this._pendingToListItem.set(pendingTarget, listItem);
this.threadList.addItem(listItem);
listItem.element.addEventListener('click', this._onListItemClick.bind(this, listItem), false);
}
this._updateListItem(listItem, pendingTarget);
this._updateDebuggerState(pendingTarget);
var currentTarget = UI.context.flavor(SDK.Target);
if (currentTarget === target)
this._selectListItem(listItem);
if (target)
this._targetToPending.set(target, pendingTarget);
}
/**
* @override
* @param {!SDK.Target} target
*/
targetAdded(target) {
if (target !== SDK.targetManager.mainTarget())
return;
console.assert(!this._mainTargetPending);
this._mainTargetPending = new Sources.ThreadsSidebarPane.MainTargetConnection(target);
this._addListItem(this._mainTargetPending);
}
/**
* @override
* @param {!SDK.Target} target
*/
targetRemoved(target) {
var subtargetManager = SDK.SubTargetsManager.fromTarget(target);
var pendingTargets = subtargetManager ? subtargetManager.pendingTargets() : [];
for (var pendingTarget of pendingTargets) {
if (pendingTarget.target())
this._targetRemoved(pendingTarget);
}
if (target === SDK.targetManager.mainTarget() && this._mainTargetPending) {
this._targetRemoved(this._mainTargetPending);
this._mainTargetPending = null;
}
}
/**
* @param {!Common.Event} event
*/
_targetNameChanged(event) {
var target = /** @type {!SDK.Target} */ (event.data);
var listItem = this._listItemForTarget(target);
if (listItem)
listItem.setTitle(this._titleForPending(this._targetToPending.get(target)));
}
/**
* @param {!Common.Event} event
*/
_targetChanged(event) {
var listItem = this._listItemForTarget(/** @type {!SDK.Target} */ (event.data));
if (listItem)
this._selectListItem(listItem);
}
/**
* @param {!SDK.Target} target
* @return {?Sources.UIList.Item}
*/
_listItemForTarget(target) {
var pendingTarget = this._targetToPending.get(target);
return this._pendingToListItem.get(pendingTarget) || null;
}
/**
* @param {!SDK.PendingTarget} pendingTarget
* @return {string}
*/
_titleForPending(pendingTarget) {
var target = pendingTarget.target();
if (!target)
return pendingTarget.name();
var executionContext = target.runtimeModel.defaultExecutionContext();
return executionContext && executionContext.label() ? executionContext.label() : target.name();
}
/**
* @param {!Common.Event} event
*/
_onDebuggerStateChanged(event) {
var debuggerModel = /** @type {!SDK.DebuggerModel} */ (event.target);
var pendingTarget = this._targetToPending.get(debuggerModel.target());
this._updateDebuggerState(pendingTarget);
}
/**
* @param {!Common.Event} event
*/
_onExecutionContextChanged(event) {
var executionContext = /** @type {!SDK.ExecutionContext} */ (event.data);
if (!executionContext.isDefault)
return;
var pendingTarget = this._targetToPending.get(executionContext.target());
var listItem = this._pendingToListItem.get(pendingTarget);
if (listItem && executionContext.label())
listItem.setTitle(executionContext.label());
}
/**
* @param {!SDK.PendingTarget} pendingTarget
*/
_updateDebuggerState(pendingTarget) {
var listItem = this._pendingToListItem.get(pendingTarget);
var target = pendingTarget.target();
var debuggerModel = target && SDK.DebuggerModel.fromTarget(target);
var isPaused = !!debuggerModel && debuggerModel.isPaused();
listItem.setSubtitle(Common.UIString(isPaused ? 'paused' : ''));
}
/**
* @param {!Sources.UIList.Item} listItem
*/
_selectListItem(listItem) {
if (listItem === this._selectedListItem)
return;
if (this._selectedListItem)
this._selectedListItem.setSelected(false);
this._selectedListItem = listItem;
listItem.setSelected(true);
}
/**
* @param {!Sources.UIList.Item} listItem
*/
_onListItemClick(listItem) {
var pendingTarget = listItem[Sources.ThreadsSidebarPane._pendingTargetSymbol];
var target = pendingTarget.target();
if (!target)
return;
UI.context.setFlavor(SDK.Target, target);
listItem.element.scrollIntoViewIfNeeded();
}
/**
* @param {!Sources.UIList.Item} item
* @param {!SDK.PendingTarget} pendingTarget
*/
_updateListItem(item, pendingTarget) {
item.setTitle(this._titleForPending(pendingTarget));
item.setSubtitle('');
var target = pendingTarget.target();
var action = null;
var actionLabel = null;
if (pendingTarget.canConnect()) {
actionLabel = target ? 'Disconnect' : 'Connect';
action = this._toggleConnection.bind(this, pendingTarget);
}
item.setAction(actionLabel, action);
item.setDimmed(!target);
}
/**
* @param {!SDK.Target} target
*/
_selectNewlyAddedTarget(target) {
setTimeout(() => UI.context.setFlavor(SDK.Target, target));
}
/**
* @param {!SDK.PendingTarget} pendingTarget
* @return {!Promise}
*/
_toggleConnection(pendingTarget) {
var target = pendingTarget.target();
if (target)
return pendingTarget.detach();
else
return pendingTarget.attach().then(target => this._selectNewlyAddedTarget(target));
}
/**
* @param {!SDK.PendingTarget} pendingTarget
*/
_targetRemoved(pendingTarget) {
var item = this._pendingToListItem.get(pendingTarget);
if (!item) // Not all targets are represented in the UI.
return;
var target = item[Sources.ThreadsSidebarPane._targetSymbol];
item[Sources.ThreadsSidebarPane._targetSymbol] = null;
this._targetToPending.remove(target);
if (pendingTarget.canConnect())
this._updateListItem(item, pendingTarget);
else
this._removeListItem(pendingTarget);
}
/**
* @param {!SDK.PendingTarget} pendingTarget
*/
_removeListItem(pendingTarget) {
var item = this._pendingToListItem.get(pendingTarget);
if (!item)
return;
this.threadList.removeItem(item);
this._pendingToListItem.delete(pendingTarget);
}
};
Sources.ThreadsSidebarPane._pendingTargetSymbol = Symbol('_subtargetSymbol');
Sources.ThreadsSidebarPane._targetSymbol = Symbol('_targetSymbol');
/**
* @unrestricted
*/
Sources.ThreadsSidebarPane.MainTargetConnection = class extends SDK.PendingTarget {
/**
* @param {!SDK.Target} target
*/
constructor(target) {
super('main-target-list-node-' + target.id(), target.title, false, null);
this._target = target;
}
/**
* @override
* @return {!SDK.Target}
*/
target() {
return this._target;
}
/**
* @override
* @return {string}
*/
name() {
return this._target.name();
}
};