blob: 7a068398475557f209a12544b1454f691277c4b5 [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.
*/
export default class TargetManager extends Common.Object {
constructor() {
super();
/** @type {!Array.<!SDK.Target>} */
this._targets = [];
/** @type {!Array.<!Observer>} */
this._observers = [];
/** @type {!Platform.Multimap<symbol, !{modelClass: !Function, thisObject: (!Object|undefined), listener: function(!Common.Event)}>} */
this._modelListeners = new Platform.Multimap();
/** @type {!Platform.Multimap<function(new:SDK.SDKModel, !SDK.Target), !SDKModelObserver>} */
this._modelObservers = new Platform.Multimap();
this._isSuspended = false;
}
/**
* @param {string=} reason - optionally provide a reason, so targets can respond accordingly
* @return {!Promise}
*/
suspendAllTargets(reason) {
if (this._isSuspended) {
return Promise.resolve();
}
this._isSuspended = true;
this.dispatchEventToListeners(Events.SuspendStateChanged);
return Promise.all(this._targets.map(target => target.suspend(reason)));
}
/**
* @return {!Promise}
*/
resumeAllTargets() {
if (!this._isSuspended) {
return Promise.resolve();
}
this._isSuspended = false;
this.dispatchEventToListeners(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 {!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 {!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 {!Observer} targetObserver
*/
observeTargets(targetObserver) {
if (this._observers.indexOf(targetObserver) !== -1) {
throw new Error('Observer can only be registered once');
}
for (const target of this._targets) {
targetObserver.targetAdded(target);
}
this._observers.push(targetObserver);
}
/**
* @param {!Observer} targetObserver
*/
unobserveTargets(targetObserver) {
this._observers.remove(targetObserver);
}
/**
* @param {string} id
* @param {string} name
* @param {!SDK.Target.Type} type
* @param {?SDK.Target} parentTarget
* @param {string=} sessionId
* @param {boolean=} waitForDebuggerInPage
* @param {!Protocol.Connection=} connection
* @return {!SDK.Target}
*/
createTarget(id, name, type, parentTarget, sessionId, waitForDebuggerInPage, connection) {
const target =
new SDK.Target(this, id, name, type, parentTarget, sessionId || '', this._isSuspended, connection || null);
if (waitForDebuggerInPage) {
target.pageAgent().waitForDebugger();
}
target.createModels(new Set(this._modelObservers.keysArray()));
this._targets.push(target);
const copy = this._observers.slice(0);
for (const observer of copy) {
observer.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
*/
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._observers.slice(0);
for (const observer of copy) {
observer.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);
}
}
}
}
/**
* @return {!Array.<!SDK.Target>}
*/
targets() {
return this._targets.slice();
}
/**
* @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} */
export const Events = {
AvailableTargetsChanged: Symbol('AvailableTargetsChanged'),
InspectedURLChanged: Symbol('InspectedURLChanged'),
NameChanged: Symbol('NameChanged'),
SuspendStateChanged: Symbol('SuspendStateChanged')
};
/**
* @interface
*/
export class Observer {
/**
* @param {!SDK.Target} target
*/
targetAdded(target) {
}
/**
* @param {!SDK.Target} target
*/
targetRemoved(target) {
}
}
/**
* @interface
* @template T
*/
export class SDKModelObserver {
/**
* @param {!T} model
*/
modelAdded(model) {
}
/**
* @param {!T} model
*/
modelRemoved(model) {
}
}
/* Legacy exported object */
self.SDK = self.SDK || {};
/* Legacy exported object */
SDK = SDK || {};
/** @constructor */
SDK.TargetManager = TargetManager;
/** @enum {symbol} */
SDK.TargetManager.Events = Events;
/** @interface */
SDK.TargetManager.Observer = Observer;
/** @interface */
SDK.SDKModelObserver = SDKModelObserver;
/**
* @type {!TargetManager}
*/
SDK.targetManager = new TargetManager();