blob: 00a05b0670e536db39bac8f4030d5d2d5335bb28 [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.
*/
SDK.TargetManager = class extends Common.Object {
constructor() {
super();
/** @type {!Array.<!SDK.Target>} */
this._targets = [];
/** @type {!Array.<!SDK.TargetManager.Observer>} */
this._observers = [];
this._observerCapabiliesMaskSymbol = Symbol('observerCapabilitiesMask');
/** @type {!Map<symbol, !Array<{modelClass: !Function, thisObject: (!Object|undefined), listener: function(!Common.Event)}>>} */
this._modelListeners = new Map();
/** @type {!Map<function(new:SDK.SDKModel, !SDK.Target), !Array<!SDK.SDKModelObserver>>} */
this._modelObservers = new Map();
this._isSuspended = false;
this._lastAnonymousTargetId = 0;
/** @type {!Map<!SDK.Target, !SDK.ChildTargetManager>} */
this._childTargetManagers = new Map();
/** @type {!Set<string>} */
this._nodeTargetIds = new Set();
/** @type {!Protocol.InspectorBackend.Connection} */
this._mainConnection;
/** @type {function()} */
this._webSocketConnectionLostCallback;
}
/**
* @return {!Promise}
*/
suspendAllTargets() {
if (this._isSuspended)
return Promise.resolve();
this._isSuspended = true;
this.dispatchEventToListeners(SDK.TargetManager.Events.SuspendStateChanged);
var promises = [];
for (var target of this._targets) {
var childTargetManager = this._childTargetManagers.get(target);
if (childTargetManager)
promises.push(childTargetManager.suspend());
for (var model of target.models().values())
promises.push(model.suspendModel());
}
return Promise.all(promises);
}
/**
* @return {!Promise}
*/
resumeAllTargets() {
if (!this._isSuspended)
return Promise.resolve();
this._isSuspended = false;
this.dispatchEventToListeners(SDK.TargetManager.Events.SuspendStateChanged);
var promises = [];
for (var target of this._targets) {
var childTargetManager = this._childTargetManagers.get(target);
if (childTargetManager)
promises.push(childTargetManager.resume());
for (var model of target.models().values())
promises.push(model.resumeModel());
}
return Promise.all(promises);
}
/**
* @return {boolean}
*/
allTargetsSuspended() {
return this._isSuspended;
}
/**
* @param {function(new:T,!SDK.Target)} modelClass
* @return {!Array<!T>}
* @template T
*/
models(modelClass) {
var result = [];
for (var i = 0; i < this._targets.length; ++i) {
var model = this._targets[i].model(modelClass);
if (model)
result.push(model);
}
return result;
}
/**
* @return {string}
*/
inspectedURL() {
return this._targets[0] ? this._targets[0].inspectedURL() : '';
}
/**
* @param {function(new:T,!SDK.Target)} modelClass
* @param {!SDK.SDKModelObserver<T>} observer
* @template T
*/
observeModels(modelClass, observer) {
if (!this._modelObservers.has(modelClass))
this._modelObservers.set(modelClass, []);
this._modelObservers.get(modelClass).push(observer);
for (var model of this.models(modelClass))
observer.modelAdded(model);
}
/**
* @param {function(new:T,!SDK.Target)} modelClass
* @param {!SDK.SDKModelObserver<T>} observer
* @template T
*/
unobserveModels(modelClass, observer) {
if (!this._modelObservers.has(modelClass))
return;
var observers = this._modelObservers.get(modelClass);
observers.remove(observer);
if (!observers.length)
this._modelObservers.delete(modelClass);
}
/**
* @param {!SDK.Target} target
* @param {function(new:SDK.SDKModel,!SDK.Target)} modelClass
* @param {!SDK.SDKModel} model
*/
modelAdded(target, modelClass, model) {
if (!this._modelObservers.has(modelClass))
return;
for (var observer of this._modelObservers.get(modelClass).slice())
observer.modelAdded(model);
}
/**
* @param {!SDK.Target} target
* @param {function(new:SDK.SDKModel,!SDK.Target)} modelClass
* @param {!SDK.SDKModel} model
*/
_modelRemoved(target, modelClass, model) {
if (!this._modelObservers.has(modelClass))
return;
for (var observer of this._modelObservers.get(modelClass).slice())
observer.modelRemoved(model);
}
/**
* @param {!Function} modelClass
* @param {symbol} eventType
* @param {function(!Common.Event)} listener
* @param {!Object=} thisObject
*/
addModelListener(modelClass, eventType, listener, thisObject) {
for (var i = 0; i < this._targets.length; ++i) {
var model = this._targets[i].model(modelClass);
if (model)
model.addEventListener(eventType, listener, thisObject);
}
if (!this._modelListeners.has(eventType))
this._modelListeners.set(eventType, []);
this._modelListeners.get(eventType).push({modelClass: modelClass, thisObject: thisObject, listener: listener});
}
/**
* @param {!Function} modelClass
* @param {symbol} eventType
* @param {function(!Common.Event)} listener
* @param {!Object=} thisObject
*/
removeModelListener(modelClass, eventType, listener, thisObject) {
if (!this._modelListeners.has(eventType))
return;
for (var i = 0; i < this._targets.length; ++i) {
var model = this._targets[i].model(modelClass);
if (model)
model.removeEventListener(eventType, listener, thisObject);
}
var listeners = this._modelListeners.get(eventType);
for (var i = 0; i < listeners.length; ++i) {
if (listeners[i].modelClass === modelClass && listeners[i].listener === listener &&
listeners[i].thisObject === thisObject)
listeners.splice(i--, 1);
}
if (!listeners.length)
this._modelListeners.delete(eventType);
}
/**
* @param {!SDK.TargetManager.Observer} targetObserver
* @param {number=} capabilitiesMask
*/
observeTargets(targetObserver, capabilitiesMask) {
if (this._observerCapabiliesMaskSymbol in targetObserver)
throw new Error('Observer can only be registered once');
targetObserver[this._observerCapabiliesMaskSymbol] = capabilitiesMask || 0;
this.targets(capabilitiesMask).forEach(targetObserver.targetAdded.bind(targetObserver));
this._observers.push(targetObserver);
}
/**
* @param {!SDK.TargetManager.Observer} targetObserver
*/
unobserveTargets(targetObserver) {
delete targetObserver[this._observerCapabiliesMaskSymbol];
this._observers.remove(targetObserver);
}
/**
* @param {string} id
* @param {string} name
* @param {number} capabilitiesMask
* @param {!Protocol.InspectorBackend.Connection.Factory} connectionFactory
* @param {?SDK.Target} parentTarget
* @return {!SDK.Target}
*/
createTarget(id, name, capabilitiesMask, connectionFactory, parentTarget) {
var target = new SDK.Target(this, id, name, capabilitiesMask, connectionFactory, parentTarget);
target.createModels(new Set(this._modelObservers.keys()));
if (target.hasTargetCapability())
this._childTargetManagers.set(target, new SDK.ChildTargetManager(this, target));
this._targets.push(target);
var copy = this._observersForTarget(target);
for (var i = 0; i < copy.length; ++i)
copy[i].targetAdded(target);
for (var modelClass of target.models().keys())
this.modelAdded(target, modelClass, target.models().get(modelClass));
for (var pair of this._modelListeners) {
var listeners = pair[1];
for (var i = 0; i < listeners.length; ++i) {
var model = target.model(listeners[i].modelClass);
if (model)
model.addEventListener(/** @type {symbol} */ (pair[0]), listeners[i].listener, listeners[i].thisObject);
}
}
return target;
}
/**
* @param {!SDK.Target} target
* @return {!Array<!SDK.TargetManager.Observer>}
*/
_observersForTarget(target) {
return this._observers.filter(
observer => target.hasAllCapabilities(observer[this._observerCapabiliesMaskSymbol] || 0));
}
/**
* @param {!SDK.Target} target
*/
removeTarget(target) {
if (!this._targets.includes(target))
return;
var childTargetManager = this._childTargetManagers.get(target);
this._childTargetManagers.delete(target);
if (childTargetManager)
childTargetManager.dispose();
this._targets.remove(target);
for (var modelClass of target.models().keys())
this._modelRemoved(target, modelClass, target.models().get(modelClass));
var copy = this._observersForTarget(target);
for (var i = 0; i < copy.length; ++i)
copy[i].targetRemoved(target);
for (var pair of this._modelListeners) {
var listeners = pair[1];
for (var i = 0; i < listeners.length; ++i) {
var model = target.model(listeners[i].modelClass);
if (model)
model.removeEventListener(/** @type {symbol} */ (pair[0]), listeners[i].listener, listeners[i].thisObject);
}
}
}
/**
* @param {number=} capabilitiesMask
* @return {!Array.<!SDK.Target>}
*/
targets(capabilitiesMask) {
if (!capabilitiesMask)
return this._targets.slice();
else
return this._targets.filter(target => target.hasAllCapabilities(capabilitiesMask || 0));
}
/**
*
* @param {string} id
* @return {?SDK.Target}
*/
targetById(id) {
// TODO(dgozman): add a map id -> target.
for (var i = 0; i < this._targets.length; ++i) {
if (this._targets[i].id() === id)
return this._targets[i];
}
return null;
}
/**
* @return {?SDK.Target}
*/
mainTarget() {
return this._targets[0] || null;
}
/**
* @param {function()} webSocketConnectionLostCallback
*/
connectToMainTarget(webSocketConnectionLostCallback) {
this._webSocketConnectionLostCallback = webSocketConnectionLostCallback;
this._connectAndCreateMainTarget();
}
_connectAndCreateMainTarget() {
if (Runtime.queryParam('nodeFrontend')) {
var target = new SDK.Target(
this, 'main', Common.UIString('Node.js'), SDK.Target.Capability.Target, this._createMainConnection.bind(this),
null);
target.setInspectedURL('Node.js');
this._childTargetManagers.set(target, new SDK.ChildTargetManager(this, target));
Host.userMetrics.actionTaken(Host.UserMetrics.Action.ConnectToNodeJSFromFrontend);
return;
}
var capabilities = SDK.Target.Capability.Browser | SDK.Target.Capability.DOM | SDK.Target.Capability.JS |
SDK.Target.Capability.Log | SDK.Target.Capability.Network | SDK.Target.Capability.Target |
SDK.Target.Capability.ScreenCapture | SDK.Target.Capability.Tracing | SDK.Target.Capability.Emulation |
SDK.Target.Capability.Security | SDK.Target.Capability.Input | SDK.Target.Capability.Inspector |
SDK.Target.Capability.DeviceEmulation;
if (Runtime.queryParam('isSharedWorker')) {
capabilities = SDK.Target.Capability.Browser | SDK.Target.Capability.Log | SDK.Target.Capability.Network |
SDK.Target.Capability.Target | SDK.Target.Capability.Inspector;
} else if (Runtime.queryParam('v8only')) {
capabilities = SDK.Target.Capability.JS;
Host.userMetrics.actionTaken(Host.UserMetrics.Action.ConnectToNodeJSDirectly);
}
var target =
this.createTarget('main', Common.UIString('Main'), capabilities, this._createMainConnection.bind(this), null);
target.runtimeAgent().runIfWaitingForDebugger();
}
/**
* @param {!Protocol.InspectorBackend.Connection.Params} params
* @return {!Protocol.InspectorBackend.Connection}
*/
_createMainConnection(params) {
var wsParam = Runtime.queryParam('ws');
var wssParam = Runtime.queryParam('wss');
if (wsParam || wssParam) {
var ws = wsParam ? `ws://${wsParam}` : `wss://${wssParam}`;
this._mainConnection = new SDK.WebSocketConnection(ws, this._webSocketConnectionLostCallback, params);
} else if (InspectorFrontendHost.isHostedMode()) {
this._mainConnection = new SDK.StubConnection(params);
} else {
this._mainConnection = new SDK.MainConnection(params);
}
return this._mainConnection;
}
/**
* @return {number}
*/
availableNodeTargetsCount() {
return this._nodeTargetIds.size;
}
/**
* @param {function(string)} onMessage
* @return {!Promise<!Protocol.InspectorBackend.Connection>}
*/
interceptMainConnection(onMessage) {
var params = {onMessage: onMessage, onDisconnect: this._connectAndCreateMainTarget.bind(this)};
return this._mainConnection.disconnect().then(this._createMainConnection.bind(this, params));
}
};
/**
* @implements {Protocol.TargetDispatcher}
*/
SDK.ChildTargetManager = class {
/**
* @param {!SDK.TargetManager} targetManager
* @param {!SDK.Target} parentTarget
*/
constructor(targetManager, parentTarget) {
this._targetManager = targetManager;
this._parentTarget = parentTarget;
this._targetAgent = parentTarget.targetAgent();
/** @type {!Map<string, !SDK.ChildConnection>} */
this._childConnections = new Map();
parentTarget.registerTargetDispatcher(this);
this._targetAgent.invoke_setAutoAttach({autoAttach: true, waitForDebuggerOnStart: true});
if (Runtime.experiments.isEnabled('autoAttachToCrossProcessSubframes'))
this._targetAgent.setAttachToFrames(true);
if (!parentTarget.parentTarget()) {
this._targetAgent.setDiscoverTargets(true);
if (Runtime.queryParam('nodeFrontend')) {
InspectorFrontendHost.setDevicesUpdatesEnabled(true);
InspectorFrontendHost.events.addEventListener(
InspectorFrontendHostAPI.Events.DevicesDiscoveryConfigChanged, this._devicesDiscoveryConfigChanged, this);
} else {
this._targetAgent.setRemoteLocations([{host: 'localhost', port: 9229}]);
}
}
}
/**
* @param {!Common.Event} event
*/
_devicesDiscoveryConfigChanged(event) {
var config = /** @type {!Adb.Config} */ (event.data);
var locations = [];
for (var address of config.networkDiscoveryConfig) {
var parts = address.split(':');
var port = parseInt(parts[1], 10);
if (parts[0] && port)
locations.push({host: parts[0], port: port});
}
this._targetAgent.setRemoteLocations(locations);
}
/**
* @return {!Promise}
*/
suspend() {
return this._targetAgent.invoke_setAutoAttach({autoAttach: true, waitForDebuggerOnStart: false});
}
/**
* @return {!Promise}
*/
resume() {
return this._targetAgent.invoke_setAutoAttach({autoAttach: true, waitForDebuggerOnStart: true});
}
dispose() {
if (Runtime.queryParam('nodeFrontend') && !this._parentTarget.parentTarget()) {
InspectorFrontendHost.events.removeEventListener(
InspectorFrontendHostAPI.Events.DevicesDiscoveryConfigChanged, this._devicesDiscoveryConfigChanged, this);
}
// TODO(dgozman): this is O(n^2) when removing main target.
var childTargets = this._targetManager._targets.filter(child => child.parentTarget() === this._parentTarget);
for (var child of childTargets)
this.detachedFromTarget(child.id());
}
/**
* @param {string} type
* @return {number}
*/
_capabilitiesForType(type) {
if (type === 'worker') {
if (Runtime.experiments.isEnabled('networkInWorkers'))
return SDK.Target.Capability.JS | SDK.Target.Capability.Log | SDK.Target.Capability.Network;
else
return SDK.Target.Capability.JS | SDK.Target.Capability.Log;
}
if (type === 'service_worker')
return SDK.Target.Capability.Log | SDK.Target.Capability.Network | SDK.Target.Capability.Target;
if (type === 'iframe') {
return SDK.Target.Capability.Browser | SDK.Target.Capability.DOM | SDK.Target.Capability.JS |
SDK.Target.Capability.Log | SDK.Target.Capability.Network | SDK.Target.Capability.Target |
SDK.Target.Capability.Tracing | SDK.Target.Capability.Emulation | SDK.Target.Capability.Input;
}
if (type === 'node')
return SDK.Target.Capability.JS;
return 0;
}
/**
* @override
* @param {!Protocol.Target.TargetInfo} targetInfo
*/
targetCreated(targetInfo) {
if (targetInfo.type !== 'node')
return;
if (Runtime.queryParam('nodeFrontend')) {
if (!targetInfo.attached)
this._targetAgent.attachToTarget(targetInfo.targetId);
return;
}
if (targetInfo.attached)
return;
this._targetManager._nodeTargetIds.add(targetInfo.targetId);
this._targetManager.dispatchEventToListeners(SDK.TargetManager.Events.AvailableNodeTargetsChanged);
}
/**
* @override
* @param {!Protocol.Target.TargetInfo} targetInfo
*/
targetInfoChanged(targetInfo) {
if (targetInfo.type !== 'node' || Runtime.queryParam('nodeFrontend'))
return;
var availableIds = this._targetManager._nodeTargetIds;
if (!availableIds.has(targetInfo.targetId) && !targetInfo.attached) {
availableIds.add(targetInfo.targetId);
this._targetManager.dispatchEventToListeners(SDK.TargetManager.Events.AvailableNodeTargetsChanged);
} else if (availableIds.has(targetInfo.targetId) && targetInfo.attached) {
availableIds.delete(targetInfo.targetId);
this._targetManager.dispatchEventToListeners(SDK.TargetManager.Events.AvailableNodeTargetsChanged);
}
}
/**
* @override
* @param {string} targetId
*/
targetDestroyed(targetId) {
if (Runtime.queryParam('nodeFrontend') || !this._targetManager._nodeTargetIds.has(targetId))
return;
this._targetManager._nodeTargetIds.delete(targetId);
this._targetManager.dispatchEventToListeners(SDK.TargetManager.Events.AvailableNodeTargetsChanged);
}
/**
* @override
* @param {!Protocol.Target.TargetInfo} targetInfo
* @param {boolean} waitingForDebugger
*/
attachedToTarget(targetInfo, waitingForDebugger) {
var targetName = '';
if (targetInfo.type === 'node') {
targetName = Common.UIString('Node.js: %s', targetInfo.url);
} else if (targetInfo.type !== 'iframe') {
var parsedURL = targetInfo.url.asParsedURL();
targetName =
parsedURL ? parsedURL.lastPathComponentWithFragment() : '#' + (++this._targetManager._lastAnonymousTargetId);
}
var target = this._targetManager.createTarget(
targetInfo.targetId, targetName, this._capabilitiesForType(targetInfo.type),
this._createChildConnection.bind(this, this._targetAgent, targetInfo.targetId), this._parentTarget);
// Only pause the new worker if debugging SW - we are going through the pause on start checkbox.
if (!this._parentTarget.parentTarget() && Runtime.queryParam('isSharedWorker') && waitingForDebugger) {
var debuggerModel = target.model(SDK.DebuggerModel);
if (debuggerModel)
debuggerModel.pause();
}
target.runtimeAgent().runIfWaitingForDebugger();
if (Runtime.queryParam('nodeFrontend'))
InspectorFrontendHost.bringToFront();
}
/**
* @override
* @param {string} childTargetId
*/
detachedFromTarget(childTargetId) {
this._childConnections.get(childTargetId)._onDisconnect.call(null, 'target terminated');
this._childConnections.delete(childTargetId);
}
/**
* @override
* @param {string} childTargetId
* @param {string} message
*/
receivedMessageFromTarget(childTargetId, message) {
var connection = this._childConnections.get(childTargetId);
if (connection)
connection._onMessage.call(null, message);
}
/**
* @param {!Protocol.TargetAgent} agent
* @param {string} childTargetId
* @param {!Protocol.InspectorBackend.Connection.Params} params
* @return {!Protocol.InspectorBackend.Connection}
*/
_createChildConnection(agent, childTargetId, params) {
var connection = new SDK.ChildConnection(agent, childTargetId, params);
this._childConnections.set(childTargetId, connection);
return connection;
}
};
/**
* @implements {Protocol.InspectorBackend.Connection}
*/
SDK.ChildConnection = class {
/**
* @param {!Protocol.TargetAgent} agent
* @param {string} targetId
* @param {!Protocol.InspectorBackend.Connection.Params} params
*/
constructor(agent, targetId, params) {
this._agent = agent;
this._targetId = targetId;
this._onMessage = params.onMessage;
this._onDisconnect = params.onDisconnect;
}
/**
* @override
* @param {string} message
*/
sendMessage(message) {
this._agent.sendMessageToTarget(this._targetId, message);
}
/**
* @override
* @return {!Promise}
*/
disconnect() {
throw 'Not implemented';
}
};
/** @enum {symbol} */
SDK.TargetManager.Events = {
InspectedURLChanged: Symbol('InspectedURLChanged'),
NameChanged: Symbol('NameChanged'),
SuspendStateChanged: Symbol('SuspendStateChanged'),
AvailableNodeTargetsChanged: Symbol('AvailableNodeTargetsChanged')
};
/**
* @interface
*/
SDK.TargetManager.Observer = function() {};
SDK.TargetManager.Observer.prototype = {
/**
* @param {!SDK.Target} target
*/
targetAdded(target) {},
/**
* @param {!SDK.Target} target
*/
targetRemoved(target) {},
};
/**
* @interface
* @template T
*/
SDK.SDKModelObserver = function() {};
SDK.SDKModelObserver.prototype = {
/**
* @param {!T} model
*/
modelAdded(model) {},
/**
* @param {!T} model
*/
modelRemoved(model) {},
};
/**
* @type {!SDK.TargetManager}
*/
SDK.targetManager = new SDK.TargetManager();