@quick-game/cli
Version:
Command line interface for rapid qg development
209 lines • 9.62 kB
JavaScript
// 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 { ParallelConnection } from './Connections.js';
import { Capability, Type } from './Target.js';
import { SDKModel } from './SDKModel.js';
import { Events as TargetManagerEvents, TargetManager } from './TargetManager.js';
import { ResourceTreeModel } from './ResourceTreeModel.js';
export class ChildTargetManager extends SDKModel {
#targetManager;
#parentTarget;
#targetAgent;
#targetInfosInternal = new Map();
#childTargetsBySessionId = new Map();
#childTargetsById = new Map();
#parallelConnections = new Map();
#parentTargetId = null;
constructor(parentTarget) {
super(parentTarget);
this.#targetManager = parentTarget.targetManager();
this.#parentTarget = parentTarget;
this.#targetAgent = parentTarget.targetAgent();
parentTarget.registerTargetDispatcher(this);
const browserTarget = this.#targetManager.browserTarget();
if (browserTarget) {
if (browserTarget !== parentTarget) {
void browserTarget.targetAgent().invoke_autoAttachRelated({ targetId: parentTarget.id(), waitForDebuggerOnStart: true });
}
}
else {
void this.#targetAgent.invoke_setAutoAttach({ autoAttach: true, waitForDebuggerOnStart: true, flatten: true });
}
if (parentTarget.parentTarget()?.type() !== Type.Frame && !Host.InspectorFrontendHost.isUnderTest()) {
void this.#targetAgent.invoke_setDiscoverTargets({ discover: true });
void this.#targetAgent.invoke_setRemoteLocations({ locations: [{ host: 'localhost', port: 9229 }] });
}
}
static install(attachCallback) {
ChildTargetManager.attachCallback = attachCallback;
SDKModel.register(ChildTargetManager, { capabilities: Capability.Target, autostart: true });
}
childTargets() {
return Array.from(this.#childTargetsBySessionId.values());
}
async suspendModel() {
await this.#targetAgent.invoke_setAutoAttach({ autoAttach: true, waitForDebuggerOnStart: false, flatten: true });
}
async resumeModel() {
await this.#targetAgent.invoke_setAutoAttach({ autoAttach: true, waitForDebuggerOnStart: true, flatten: true });
}
dispose() {
for (const sessionId of this.#childTargetsBySessionId.keys()) {
this.detachedFromTarget({ sessionId, targetId: undefined });
}
}
targetCreated({ targetInfo }) {
this.#targetInfosInternal.set(targetInfo.targetId, targetInfo);
this.fireAvailableTargetsChanged();
this.dispatchEventToListeners(Events.TargetCreated, targetInfo);
}
targetInfoChanged({ targetInfo }) {
this.#targetInfosInternal.set(targetInfo.targetId, targetInfo);
const target = this.#childTargetsById.get(targetInfo.targetId);
if (target) {
if (target.targetInfo()?.subtype === 'prerender' && !targetInfo.subtype) {
const resourceTreeModel = target.model(ResourceTreeModel);
target.updateTargetInfo(targetInfo);
if (resourceTreeModel && resourceTreeModel.mainFrame) {
resourceTreeModel.primaryPageChanged(resourceTreeModel.mainFrame, "Activation" /* PrimaryPageChangeType.Activation */);
}
}
else {
target.updateTargetInfo(targetInfo);
}
}
this.fireAvailableTargetsChanged();
this.dispatchEventToListeners(Events.TargetInfoChanged, targetInfo);
}
targetDestroyed({ targetId }) {
this.#targetInfosInternal.delete(targetId);
this.fireAvailableTargetsChanged();
this.dispatchEventToListeners(Events.TargetDestroyed, targetId);
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
targetCrashed({ targetId, status, errorCode }) {
}
fireAvailableTargetsChanged() {
TargetManager.instance().dispatchEventToListeners(TargetManagerEvents.AvailableTargetsChanged, [...this.#targetInfosInternal.values()]);
}
async getParentTargetId() {
if (!this.#parentTargetId) {
this.#parentTargetId = (await this.#parentTarget.targetAgent().invoke_getTargetInfo({})).targetInfo.targetId;
}
return this.#parentTargetId;
}
async attachedToTarget({ sessionId, targetInfo, waitingForDebugger }) {
if (this.#parentTargetId === targetInfo.targetId) {
return;
}
let type = Type.Browser;
let targetName = '';
if (targetInfo.type === 'worker' && targetInfo.title && targetInfo.title !== targetInfo.url) {
targetName = targetInfo.title;
}
else if (!['page', 'iframe', 'webview'].includes(targetInfo.type)) {
const KNOWN_FRAME_PATTERNS = [
'^chrome://print/$',
'^chrome://file-manager/',
'^chrome://feedback/',
'^chrome://.*\\.top-chrome/$',
'^devtools://',
];
if (KNOWN_FRAME_PATTERNS.some(p => targetInfo.url.match(p))) {
type = Type.Frame;
}
else {
const parsedURL = Common.ParsedURL.ParsedURL.fromString(targetInfo.url);
targetName =
parsedURL ? parsedURL.lastPathComponentWithFragment() : '#' + (++ChildTargetManager.lastAnonymousTargetId);
}
}
if (targetInfo.type === 'iframe' || targetInfo.type === 'webview') {
type = Type.Frame;
}
else if (targetInfo.type === 'background_page' || targetInfo.type === 'app' || targetInfo.type === 'popup_page') {
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 === 'shared_worker') {
type = Type.SharedWorker;
}
else if (targetInfo.type === 'service_worker') {
type = Type.ServiceWorker;
}
else if (targetInfo.type === 'auction_worklet') {
type = Type.AuctionWorklet;
}
const target = this.#targetManager.createTarget(targetInfo.targetId, targetName, type, this.#parentTarget, sessionId, undefined, undefined, targetInfo);
this.#childTargetsBySessionId.set(sessionId, target);
this.#childTargetsById.set(target.id(), target);
if (ChildTargetManager.attachCallback) {
await ChildTargetManager.attachCallback({ target, waitingForDebugger });
}
// [crbug/1423096] Invoking this on a worker session that is not waiting for the debugger can force the worker
// to resume even if there is another session waiting for the debugger.
if (waitingForDebugger) {
void target.runtimeAgent().invoke_runIfWaitingForDebugger();
}
}
detachedFromTarget({ sessionId }) {
if (this.#parallelConnections.has(sessionId)) {
this.#parallelConnections.delete(sessionId);
}
else {
const target = this.#childTargetsBySessionId.get(sessionId);
if (target) {
target.dispose('target terminated');
this.#childTargetsBySessionId.delete(sessionId);
this.#childTargetsById.delete(target.id());
}
}
}
receivedMessageFromTarget({}) {
// We use flatten protocol.
}
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, sessionId };
}
async createParallelConnectionAndSessionForTarget(target, targetId) {
const targetAgent = target.targetAgent();
const targetRouter = 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);
void targetAgent.invoke_detachFromTarget({ sessionId });
});
return { connection, sessionId };
}
targetInfos() {
return Array.from(this.#targetInfosInternal.values());
}
static lastAnonymousTargetId = 0;
static attachCallback;
}
// TODO(crbug.com/1167717): Make this a const enum again
// eslint-disable-next-line rulesdir/const_enum
export var Events;
(function (Events) {
Events["TargetCreated"] = "TargetCreated";
Events["TargetDestroyed"] = "TargetDestroyed";
Events["TargetInfoChanged"] = "TargetInfoChanged";
})(Events || (Events = {}));
//# sourceMappingURL=ChildTargetManager.js.map