blob: 5a05b58234edbc04686259eae79e072eaa13ffbc [file] [log] [blame]
// 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.
let _lastAnonymousTargetId = 0;
/**
* @implements {Protocol.TargetDispatcher}
*/
export default class ChildTargetManager extends SDK.SDKModel {
/**
* @param {!SDK.Target} parentTarget
*/
constructor(parentTarget) {
super(parentTarget);
this._targetManager = parentTarget.targetManager();
this._parentTarget = parentTarget;
this._targetAgent = parentTarget.targetAgent();
/** @type {!Map<string, !Protocol.Target.TargetInfo>} */
this._targetInfos = new Map();
/** @type {!Map<string, !SDK.Target>} */
this._childTargets = new Map();
/** @type {!Map<string, !Protocol.Connection>} */
this._parallelConnections = new Map();
/** @type {string | null} */
this._parentTargetId = null;
parentTarget.registerTargetDispatcher(this);
this._targetAgent.invoke_setAutoAttach({autoAttach: true, waitForDebuggerOnStart: true, flatten: true});
if (!parentTarget.parentTarget() && !Host.isUnderTest()) {
this._targetAgent.setDiscoverTargets(true);
this._targetAgent.setRemoteLocations([{host: 'localhost', port: 9229}]);
}
}
/**
* @param {function({target: !SDK.Target, waitingForDebugger: boolean}):!Promise=} attachCallback
*/
static install(attachCallback) {
SDK.ChildTargetManager._attachCallback = attachCallback;
SDK.SDKModel.register(SDK.ChildTargetManager, SDK.Target.Capability.Target, true);
}
/**
* @override
* @return {!Promise}
*/
suspendModel() {
return this._targetAgent.invoke_setAutoAttach({autoAttach: true, waitForDebuggerOnStart: false, flatten: true});
}
/**
* @override
* @return {!Promise}
*/
resumeModel() {
return this._targetAgent.invoke_setAutoAttach({autoAttach: true, waitForDebuggerOnStart: true, flatten: true});
}
/**
* @override
*/
dispose() {
for (const sessionId of this._childTargets.keys()) {
this.detachedFromTarget(sessionId, undefined);
}
}
/**
* @override
* @param {!Protocol.Target.TargetInfo} targetInfo
*/
targetCreated(targetInfo) {
this._targetInfos.set(targetInfo.targetId, targetInfo);
this._fireAvailableTargetsChanged();
}
/**
* @override
* @param {!Protocol.Target.TargetInfo} targetInfo
*/
targetInfoChanged(targetInfo) {
this._targetInfos.set(targetInfo.targetId, targetInfo);
this._fireAvailableTargetsChanged();
}
/**
* @override
* @param {string} targetId
*/
targetDestroyed(targetId) {
this._targetInfos.delete(targetId);
this._fireAvailableTargetsChanged();
}
/**
* @override
* @param {string} targetId
* @param {string} status
* @param {number} errorCode
*/
targetCrashed(targetId, status, errorCode) {
}
_fireAvailableTargetsChanged() {
SDK.targetManager.dispatchEventToListeners(
SDK.TargetManager.Events.AvailableTargetsChanged, this._targetInfos.valuesArray());
}
/**
* @return {!Promise<string>}
*/
async _getParentTargetId() {
if (!this._parentTargetId) {
this._parentTargetId = (await this._parentTarget.targetAgent().getTargetInfo()).targetId;
}
return this._parentTargetId;
}
/**
* @override
* @param {string} sessionId
* @param {!Protocol.Target.TargetInfo} targetInfo
* @param {boolean} waitingForDebugger
*/
attachedToTarget(sessionId, targetInfo, waitingForDebugger) {
if (this._parentTargetId === targetInfo.targetId) {
return;
}
let targetName = '';
if (targetInfo.type === 'worker' && targetInfo.title && targetInfo.title !== targetInfo.url) {
targetName = targetInfo.title;
} else if (targetInfo.type !== 'iframe') {
const parsedURL = Common.ParsedURL.fromString(targetInfo.url);
targetName = parsedURL ? parsedURL.lastPathComponentWithFragment() : '#' + (++_lastAnonymousTargetId);
}
let type = SDK.Target.Type.Browser;
if (targetInfo.type === 'iframe') {
type = SDK.Target.Type.Frame;
}
// TODO(lfg): ensure proper capabilities for child pages (e.g. portals).
else if (targetInfo.type === 'page') {
type = SDK.Target.Type.Frame;
} else if (targetInfo.type === 'worker') {
type = SDK.Target.Type.Worker;
} else if (targetInfo.type === 'service_worker') {
type = SDK.Target.Type.ServiceWorker;
}
const target =
this._targetManager.createTarget(targetInfo.targetId, targetName, type, this._parentTarget, sessionId);
this._childTargets.set(sessionId, target);
if (SDK.ChildTargetManager._attachCallback) {
SDK.ChildTargetManager._attachCallback({target, waitingForDebugger}).then(() => {
target.runtimeAgent().runIfWaitingForDebugger();
});
} else {
target.runtimeAgent().runIfWaitingForDebugger();
}
}
/**
* @override
* @param {string} sessionId
* @param {string=} childTargetId
*/
detachedFromTarget(sessionId, childTargetId) {
if (this._parallelConnections.has(sessionId)) {
this._parallelConnections.delete(sessionId);
} else {
this._childTargets.get(sessionId).dispose('target terminated');
this._childTargets.delete(sessionId);
}
}
/**
* @override
* @param {string} sessionId
* @param {string} message
* @param {string=} childTargetId
*/
receivedMessageFromTarget(sessionId, message, childTargetId) {
// We use flatten protocol.
}
/**
* @param {function((!Object|string))} onMessage
* @return {!Promise<!Protocol.Connection>}
*/
async createParallelConnection(onMessage) {
// The main SDK.Target id is actually just `main`, instead of the real targetId.
// Get the real id (requires an async operation) so that it can be used synchronously later.
const targetId = await this._getParentTargetId();
const {connection, sessionId} =
await this._createParallelConnectionAndSessionForTarget(this._parentTarget, targetId);
connection.setOnMessage(onMessage);
this._parallelConnections.set(sessionId, connection);
return connection;
}
/**
* @param {!SDK.Target} target
* @param {string} targetId
* @return {!Promise<!{connection: !Protocol.Connection, sessionId: string}>}
*/
async _createParallelConnectionAndSessionForTarget(target, targetId) {
const targetAgent = target.targetAgent();
const targetRouter = target.router();
const sessionId = /** @type {string} */ (await targetAgent.attachToTarget(targetId, true /* flatten */));
const connection = new SDK.ParallelConnection(targetRouter.connection(), sessionId);
targetRouter.registerSession(target, sessionId, connection);
connection.setOnDisconnect(() => {
targetAgent.detachFromTarget(sessionId);
targetRouter.unregisterSession(sessionId);
});
return {connection, sessionId};
}
}
/* Legacy exported object */
self.SDK = self.SDK || {};
/* Legacy exported object */
SDK = SDK || {};
/** @constructor */
SDK.ChildTargetManager = ChildTargetManager;
/** @type {function({target: !SDK.Target, waitingForDebugger: boolean})|undefined} */
SDK.ChildTargetManager._attachCallback;