blob: 5bfeb97a32783f4c61f3cc71242abb312ceda5c7 [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.
import * as Common from '../common/common.js';
import * as Host from '../host/host.js';
import * as ProtocolClient from '../protocol_client/protocol_client.js'; // eslint-disable-line no-unused-vars
import {ParallelConnection} from './Connections.js';
import {Capability, Events, SDKModel, Target, TargetManager, Type} from './SDKModel.js'; // eslint-disable-line no-unused-vars
let _lastAnonymousTargetId = 0;
/** @type {(function({target: !Target, waitingForDebugger: boolean}):!Promise<?>)|undefined} */
let _attachCallback;
/**
* @implements {ProtocolProxyApiWorkaround_TargetDispatcher}
*/
export class ChildTargetManager extends SDKModel {
/**
* @param {!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, !Target>} */
this._childTargets = new Map();
/** @type {!Map<string, !ProtocolClient.InspectorBackend.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.InspectorFrontendHost.isUnderTest()) {
this._targetAgent.invoke_setDiscoverTargets({discover: true});
this._targetAgent.invoke_setRemoteLocations({locations: [{host: 'localhost', port: 9229}]});
}
}
/**
* @return {!Protocol.UsesObjectNotation}
*/
usesObjectNotation() {
return true;
}
/**
* @param {function({target: !Target, waitingForDebugger: boolean}):!Promise<?>=} attachCallback
*/
static install(attachCallback) {
_attachCallback = attachCallback;
SDKModel.register(ChildTargetManager, 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, targetId: undefined});
}
}
/**
* @override
* @param {!Protocol.Target.TargetCreatedEvent} event
*/
targetCreated({targetInfo}) {
this._targetInfos.set(targetInfo.targetId, targetInfo);
this._fireAvailableTargetsChanged();
}
/**
* @override
* @param {!Protocol.Target.TargetInfoChangedEvent} event
*/
targetInfoChanged({targetInfo}) {
this._targetInfos.set(targetInfo.targetId, targetInfo);
this._fireAvailableTargetsChanged();
}
/**
* @override
* @param {!Protocol.Target.TargetDestroyedEvent} event
*/
targetDestroyed({targetId}) {
this._targetInfos.delete(targetId);
this._fireAvailableTargetsChanged();
}
/**
* @override
* @param {!Protocol.Target.TargetCrashedEvent} event
*/
targetCrashed({targetId, status, errorCode}) {
}
_fireAvailableTargetsChanged() {
TargetManager.instance().dispatchEventToListeners(Events.AvailableTargetsChanged, [...this._targetInfos.values()]);
}
/**
* @return {!Promise<string>}
*/
async _getParentTargetId() {
if (!this._parentTargetId) {
this._parentTargetId = (await this._parentTarget.targetAgent().invoke_getTargetInfo({})).targetInfo.targetId;
}
return this._parentTargetId;
}
/**
* @override
* @param {!Protocol.Target.AttachedToTargetEvent} event
*/
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.ParsedURL.fromString(targetInfo.url);
targetName = parsedURL ? parsedURL.lastPathComponentWithFragment() : '#' + (++_lastAnonymousTargetId);
}
let type = Type.Browser;
if (targetInfo.type === 'iframe') {
type = Type.Frame;
}
// TODO(lfg): ensure proper capabilities for child pages (e.g. portals).
else if (targetInfo.type === 'page') {
type = Type.Frame;
} else if (targetInfo.type === 'worker') {
type = Type.Worker;
} else if (targetInfo.type === 'service_worker') {
type = Type.ServiceWorker;
}
const target =
this._targetManager.createTarget(targetInfo.targetId, targetName, type, this._parentTarget, sessionId);
this._childTargets.set(sessionId, target);
if (_attachCallback) {
_attachCallback({target, waitingForDebugger}).then(() => {
target.runtimeAgent().invoke_runIfWaitingForDebugger();
});
} else {
target.runtimeAgent().invoke_runIfWaitingForDebugger();
}
}
/**
* @override
* @param {!Protocol.Target.DetachedFromTargetEvent} event
*/
detachedFromTarget({sessionId}) {
if (this._parallelConnections.has(sessionId)) {
this._parallelConnections.delete(sessionId);
} else {
const session = this._childTargets.get(sessionId);
if (session) {
session.dispose('target terminated');
this._childTargets.delete(sessionId);
}
}
}
/**
* @override
* @param {!Protocol.Target.ReceivedMessageFromTargetEvent} event
*/
receivedMessageFromTarget({}) {
// We use flatten protocol.
}
/**
* @param {function((!Object|string)):void} onMessage
* @return {!Promise<!ProtocolClient.InspectorBackend.Connection>}
*/
async createParallelConnection(onMessage) {
// The main 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 {!Target} target
* @param {string} targetId
* @return {!Promise<!{connection: !ProtocolClient.InspectorBackend.Connection, sessionId: string}>}
*/
async _createParallelConnectionAndSessionForTarget(target, targetId) {
const targetAgent = target.targetAgent();
const targetRouter = /** @type {!ProtocolClient.InspectorBackend.SessionRouter} */ (target.router());
const sessionId = (await targetAgent.invoke_attachToTarget({targetId, flatten: true})).sessionId;
const connection = new ParallelConnection(targetRouter.connection(), sessionId);
targetRouter.registerSession(target, sessionId, connection);
connection.setOnDisconnect(() => {
targetRouter.unregisterSession(sessionId);
targetAgent.invoke_detachFromTarget({sessionId});
});
return {connection, sessionId};
}
}