blob: 268d46f72b2fb95ae23f49819e67b3c637008a91 [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 {!Multimap<symbol, !{modelClass: !Function, thisObject: (!Object|undefined), listener: function(!Common.Event)}>} */
this._modelListeners = new Multimap();
/** @type {!Multimap<function(new:SDK.SDKModel, !SDK.Target), !SDK.SDKModelObserver>} */
this._modelObservers = new Multimap();
this._isSuspended = false;
}
/**
* @return {!Promise}
*/
suspendAllTargets() {
if (this._isSuspended)
return Promise.resolve();
this._isSuspended = true;
this.dispatchEventToListeners(SDK.TargetManager.Events.SuspendStateChanged);
return Promise.all(this._targets.map(target => target.suspend()));
}
/**
* @return {!Promise}
*/
resumeAllTargets() {
if (!this._isSuspended)
return Promise.resolve();
this._isSuspended = false;
this.dispatchEventToListeners(SDK.TargetManager.Events.SuspendStateChanged);
return Promise.all(this._targets.map(target => target.resume()));
}
/**
* @return {boolean}
*/
allTargetsSuspended() {
return this._isSuspended;
}
/**
* @param {function(new:T,!SDK.Target)} modelClass
* @return {!Array<!T>}
* @template T
*/
models(modelClass) {
const result = [];
for (let i = 0; i < this._targets.length; ++i) {
const 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) {
const models = this.models(modelClass);
this._modelObservers.set(modelClass, observer);
for (const model of models)
observer.modelAdded(model);
}
/**
* @param {function(new:T,!SDK.Target)} modelClass
* @param {!SDK.SDKModelObserver<T>} observer
* @template T
*/
unobserveModels(modelClass, observer) {
this._modelObservers.delete(modelClass, observer);
}
/**
* @param {!SDK.Target} target
* @param {function(new:SDK.SDKModel,!SDK.Target)} modelClass
* @param {!SDK.SDKModel} model
*/
modelAdded(target, modelClass, model) {
for (const observer of this._modelObservers.get(modelClass).valuesArray())
observer.modelAdded(model);
}
/**
* @param {!SDK.Target} target
* @param {function(new:SDK.SDKModel,!SDK.Target)} modelClass
* @param {!SDK.SDKModel} model
*/
_modelRemoved(target, modelClass, model) {
for (const observer of this._modelObservers.get(modelClass).valuesArray())
observer.modelRemoved(model);
}
/**
* @param {!Function} modelClass
* @param {symbol} eventType
* @param {function(!Common.Event)} listener
* @param {!Object=} thisObject
*/
addModelListener(modelClass, eventType, listener, thisObject) {
for (let i = 0; i < this._targets.length; ++i) {
const model = this._targets[i].model(modelClass);
if (model)
model.addEventListener(eventType, listener, thisObject);
}
this._modelListeners.set(eventType, {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 (let i = 0; i < this._targets.length; ++i) {
const model = this._targets[i].model(modelClass);
if (model)
model.removeEventListener(eventType, listener, thisObject);
}
for (const info of this._modelListeners.get(eventType)) {
if (info.modelClass === modelClass && info.listener === listener && info.thisObject === thisObject)
this._modelListeners.delete(eventType, info);
}
}
/**
* @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) {
const target = new SDK.Target(this, id, name, capabilitiesMask, connectionFactory, parentTarget, this._isSuspended);
target.createModels(new Set(this._modelObservers.keysArray()));
this._targets.push(target);
const copy = this._observersForTarget(target);
for (let i = 0; i < copy.length; ++i)
copy[i].targetAdded(target);
for (const modelClass of target.models().keys())
this.modelAdded(target, modelClass, target.models().get(modelClass));
for (const key of this._modelListeners.keysArray()) {
for (const info of this._modelListeners.get(key)) {
const model = target.model(info.modelClass);
if (model)
model.addEventListener(key, info.listener, info.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;
this._targets.remove(target);
for (const modelClass of target.models().keys())
this._modelRemoved(target, modelClass, target.models().get(modelClass));
const copy = this._observersForTarget(target);
for (let i = 0; i < copy.length; ++i)
copy[i].targetRemoved(target);
for (const key of this._modelListeners.keysArray()) {
for (const info of this._modelListeners.get(key)) {
const model = target.model(info.modelClass);
if (model)
model.removeEventListener(key, info.listener, info.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 (let 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;
}
};
/** @enum {symbol} */
SDK.TargetManager.Events = {
AvailableTargetsChanged: Symbol('AvailableTargetsChanged'),
InspectedURLChanged: Symbol('InspectedURLChanged'),
NameChanged: Symbol('NameChanged'),
SuspendStateChanged: Symbol('SuspendStateChanged')
};
/**
* @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();