"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CdpTargetManager = void 0;
const log_js_1 = require("../../../utils/log.js");
const BrowsingContextImpl_js_1 = require("../context/BrowsingContextImpl.js");
const WorkerRealm_js_1 = require("../script/WorkerRealm.js");
const CdpTarget_js_1 = require("./CdpTarget.js");
const cdpToBidiTargetTypes = {
    service_worker: 'service-worker',
    shared_worker: 'shared-worker',
    worker: 'dedicated-worker',
};
class CdpTargetManager {
    #browserCdpClient;
    #cdpConnection;
    #selfTargetId;
    #eventManager;
    #browsingContextStorage;
    #networkStorage;
    #acceptInsecureCerts;
    #preloadScriptStorage;
    #realmStorage;
    #defaultUserContextId;
    #logger;
    constructor(cdpConnection, browserCdpClient, selfTargetId, eventManager, browsingContextStorage, realmStorage, networkStorage, preloadScriptStorage, acceptInsecureCerts, defaultUserContextId, logger) {
        this.#acceptInsecureCerts = acceptInsecureCerts;
        this.#cdpConnection = cdpConnection;
        this.#browserCdpClient = browserCdpClient;
        this.#selfTargetId = selfTargetId;
        this.#eventManager = eventManager;
        this.#browsingContextStorage = browsingContextStorage;
        this.#preloadScriptStorage = preloadScriptStorage;
        this.#networkStorage = networkStorage;
        this.#realmStorage = realmStorage;
        this.#defaultUserContextId = defaultUserContextId;
        this.#logger = logger;
        this.#setEventListeners(browserCdpClient);
    }
    /**
     * This method is called for each CDP session, since this class is responsible
     * for creating and destroying all targets and browsing contexts.
     */
    #setEventListeners(cdpClient) {
        cdpClient.on('Target.attachedToTarget', (params) => {
            this.#handleAttachedToTargetEvent(params, cdpClient);
        });
        cdpClient.on('Target.detachedFromTarget', this.#handleDetachedFromTargetEvent.bind(this));
        cdpClient.on('Target.targetInfoChanged', this.#handleTargetInfoChangedEvent.bind(this));
        cdpClient.on('Inspector.targetCrashed', () => {
            this.#handleTargetCrashedEvent(cdpClient);
        });
        cdpClient.on('Page.frameAttached', this.#handleFrameAttachedEvent.bind(this));
        cdpClient.on('Page.frameDetached', this.#handleFrameDetachedEvent.bind(this));
    }
    #handleFrameAttachedEvent(params) {
        const parentBrowsingContext = this.#browsingContextStorage.findContext(params.parentFrameId);
        if (parentBrowsingContext !== undefined) {
            BrowsingContextImpl_js_1.BrowsingContextImpl.create(params.frameId, params.parentFrameId, parentBrowsingContext.userContext, parentBrowsingContext.cdpTarget, this.#eventManager, this.#browsingContextStorage, this.#realmStorage, this.#logger);
        }
    }
    #handleFrameDetachedEvent(params) {
        // In case of OOPiF no need in deleting BrowsingContext.
        if (params.reason === 'swap') {
            return;
        }
        this.#browsingContextStorage.findContext(params.frameId)?.dispose();
    }
    #handleAttachedToTargetEvent(params, parentSessionCdpClient) {
        const { sessionId, targetInfo } = params;
        const targetCdpClient = this.#cdpConnection.getCdpClient(sessionId);
        switch (targetInfo.type) {
            case 'page':
            case 'iframe': {
                if (targetInfo.targetId === this.#selfTargetId) {
                    break;
                }
                const cdpTarget = this.#createCdpTarget(targetCdpClient, targetInfo);
                const maybeContext = this.#browsingContextStorage.findContext(targetInfo.targetId);
                if (maybeContext) {
                    // OOPiF.
                    maybeContext.updateCdpTarget(cdpTarget);
                }
                else {
                    const userContext = targetInfo.browserContextId &&
                        targetInfo.browserContextId !== this.#defaultUserContextId
                        ? targetInfo.browserContextId
                        : 'default';
                    // New context.
                    BrowsingContextImpl_js_1.BrowsingContextImpl.create(targetInfo.targetId, null, userContext, cdpTarget, this.#eventManager, this.#browsingContextStorage, this.#realmStorage, this.#logger);
                }
                return;
            }
            case 'service_worker':
            case 'worker': {
                const realm = this.#realmStorage.findRealm({
                    cdpSessionId: parentSessionCdpClient.sessionId,
                });
                // If there is no browsing context, this worker is already terminated.
                if (!realm) {
                    break;
                }
                const cdpTarget = this.#createCdpTarget(targetCdpClient, targetInfo);
                this.#handleWorkerTarget(cdpToBidiTargetTypes[targetInfo.type], cdpTarget, realm);
                return;
            }
            // In CDP, we only emit shared workers on the browser and not the set of
            // frames that use the shared worker. If we change this in the future to
            // behave like service workers (emits on both browser and frame targets),
            // we can remove this block and merge service workers with the above one.
            case 'shared_worker': {
                const cdpTarget = this.#createCdpTarget(targetCdpClient, targetInfo);
                this.#handleWorkerTarget(cdpToBidiTargetTypes[targetInfo.type], cdpTarget);
                return;
            }
        }
        // DevTools or some other not supported by BiDi target. Just release
        // debugger and ignore them.
        targetCdpClient
            .sendCommand('Runtime.runIfWaitingForDebugger')
            .then(() => parentSessionCdpClient.sendCommand('Target.detachFromTarget', params))
            .catch((error) => this.#logger?.(log_js_1.LogType.debugError, error));
    }
    #createCdpTarget(targetCdpClient, targetInfo) {
        this.#setEventListeners(targetCdpClient);
        const target = CdpTarget_js_1.CdpTarget.create(targetInfo.targetId, targetCdpClient, this.#browserCdpClient, this.#realmStorage, this.#eventManager, this.#preloadScriptStorage, this.#browsingContextStorage, this.#networkStorage, this.#acceptInsecureCerts, this.#logger);
        this.#networkStorage.onCdpTargetCreated(target);
        return target;
    }
    #workers = new Map();
    #handleWorkerTarget(realmType, cdpTarget, ownerRealm) {
        cdpTarget.cdpClient.on('Runtime.executionContextCreated', (params) => {
            const { uniqueId, id, origin } = params.context;
            const workerRealm = new WorkerRealm_js_1.WorkerRealm(cdpTarget.cdpClient, this.#eventManager, id, this.#logger, (0, BrowsingContextImpl_js_1.serializeOrigin)(origin), ownerRealm ? [ownerRealm] : [], uniqueId, this.#realmStorage, realmType);
            this.#workers.set(cdpTarget.cdpSessionId, workerRealm);
        });
    }
    #handleDetachedFromTargetEvent({ sessionId, targetId, }) {
        if (targetId) {
            this.#preloadScriptStorage.find({ targetId }).map((preloadScript) => {
                preloadScript.dispose(targetId);
            });
        }
        const context = this.#browsingContextStorage.findContextBySession(sessionId);
        if (context) {
            context.dispose();
            return;
        }
        const worker = this.#workers.get(sessionId);
        if (worker) {
            this.#realmStorage.deleteRealms({
                cdpSessionId: worker.cdpClient.sessionId,
            });
        }
    }
    #handleTargetInfoChangedEvent(params) {
        const context = this.#browsingContextStorage.findContext(params.targetInfo.targetId);
        if (context) {
            context.onTargetInfoChanged(params);
        }
    }
    #handleTargetCrashedEvent(cdpClient) {
        // This is primarily used for service and shared workers. CDP tends to not
        // signal they closed gracefully and instead says they crashed to signal
        // they are closed.
        const realms = this.#realmStorage.findRealms({
            cdpSessionId: cdpClient.sessionId,
        });
        for (const realm of realms) {
            realm.dispose();
        }
    }
}
exports.CdpTargetManager = CdpTargetManager;
//# sourceMappingURL=CdpTargetManager.js.map