| // 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; |