"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Frame = void 0;
const events_1 = require("events");
const exception_js_1 = require("../exception.js");
const cdp_util_js_1 = require("./cdp-util.js");
const execution_context_js_1 = require("./execution-context.js");
const index_js_1 = require("./inject/index.js");
class Frame extends events_1.EventEmitter {
    constructor(page, frameId, parentFrame) {
        super();
        this.page = page;
        this.frameId = frameId;
        this.parentFrame = parentFrame;
        this.childFrames = new Set();
        this.loaded = false;
        this.ready = false;
        this.failed = false;
        this.url = '';
        this.securityOrigin = '';
        this.mimeType = '';
        this._isolatedWorld = null;
        this._defaultExecCtx = null;
        if (parentFrame) {
            parentFrame.childFrames.add(this);
        }
        this.clearExecutionContexts();
    }
    get logger() {
        return this.page.logger;
    }
    isMainFrame() {
        return !this.parentFrame;
    }
    async getCurrentExecutionContext() {
        if (this._isolatedWorld && this._isolatedWorld.isAlive) {
            return this._isolatedWorld;
        }
        return await this.initIsolatedWorld();
    }
    async initIsolatedWorld() {
        const { executionContextId } = await this.page.target.send('Page.createIsolatedWorld', {
            frameId: this.frameId,
            worldName: 'Autopilot',
            grantUniveralAccess: true,
        });
        this._isolatedWorld = new execution_context_js_1.ExecutionContext(this, executionContextId);
        this._isolatedWorld.initContentScripts(index_js_1.runtimeScripts);
        return this._isolatedWorld;
    }
    async getDefaultExecutionContext() {
        if (!this._defaultExecCtx) {
            throw new exception_js_1.Exception({
                name: 'NoExecutionContext',
                message: 'Default execution context is not ready',
                retry: true,
            });
        }
        return this._defaultExecCtx;
    }
    clearExecutionContexts() {
        this._defaultExecCtx = null;
        this._isolatedWorld = null;
    }
    async evaluate(pageFn, ...args) {
        const execContext = await this.getCurrentExecutionContext();
        return await execContext.evaluate(pageFn, ...args);
    }
    async evaluateElement(pageFn, ...args) {
        const execContext = await this.getCurrentExecutionContext();
        return await execContext.evaluateElement(pageFn, ...args);
    }
    async evaluateJson(pageFn, ...args) {
        const execContext = await this.getCurrentExecutionContext();
        return await execContext.evaluateJson(pageFn, ...args);
    }
    async document() {
        try {
            return (await this.evaluateElement(() => document));
        }
        catch (err) {
            throw new exception_js_1.Exception({
                name: 'PageLoadingFailed',
                message: 'Failed to obtain top frame document',
                retry: true,
                details: {
                    cause: {
                        message: err.message,
                        code: err.code,
                        details: err.details,
                    },
                },
            });
        }
    }
    async querySelectorAll(selector) {
        return (await this.document()).querySelectorAll(selector);
    }
    async querySelector(selector) {
        return (await this.document()).querySelector(selector);
    }
    onNavigated(cdpFrame) {
        this.loaded = false;
        this.ready = false;
        this.failed = !!cdpFrame.unreachableUrl;
        this.url = cdpFrame.unreachableUrl || cdpFrame.url;
        this.securityOrigin = cdpFrame.securityOrigin;
        this.mimeType = cdpFrame.mimeType;
        this.emit('navigate');
        if (['iframe', 'page'].includes(this.page.target.type)) {
            this.logger.debug(`Navigate (${this.page.target.type}) ${this.url}`, {
                frameId: this.frameId,
                url: this.url,
            });
        }
    }
    onStoppedLoading() {
        this.ready = true;
        this.loaded = true;
        this.emit('ready');
        this.emit('loaded');
    }
    onExecutionContextCreated(context) {
        const { id, auxData = {} } = context;
        if (auxData.isDefault) {
            const exCtx = new execution_context_js_1.ExecutionContext(this, id);
            exCtx.initContentScripts(index_js_1.stubScripts);
            this._defaultExecCtx = exCtx;
        }
    }
    onExecutionContextDestroyed(executionContextId) {
        if (this._defaultExecCtx && this._defaultExecCtx.executionContextId === executionContextId) {
            this.clearExecutionContexts();
        }
    }
    onExecutionContextsCleared() {
        this.clearExecutionContexts();
    }
    async captureHtmlSnapshot() {
        await this.page.waitForReady({ timeout: 5000, rejectHttpErrors: false }).catch(_err => { });
        await this.htmlSnapshotPopulateFrameIds();
        const snapshot = await this.htmlSnapshotEvalWithFrameRefs();
        return await this.htmlSnapshotResolveFrameRefs(snapshot);
    }
    async htmlSnapshotPopulateFrameIds() {
        const frames = await this.querySelectorAll('iframe, frame');
        const nodeInfosPromises = frames.map(frame => this.page.send('DOM.describeNode', { objectId: frame.objectId }));
        const nodeInfos = await Promise.all(nodeInfosPromises);
        const frameIds = nodeInfos.map(nodeInfo => nodeInfo.node.frameId);
        await this.evaluateJson((frameIds, ...frames) => {
            for (let i = 0; i < frames.length; i++) {
                frames[i].__ubioFrameId = frameIds[i];
            }
        }, frameIds, ...frames);
    }
    async htmlSnapshotEvalWithFrameRefs() {
        try {
            return await this.evaluateJson(toolkitBinding => {
                return window[toolkitBinding].htmlSnapshot();
            }, this.page.toolkitBinding);
        }
        catch (err) {
            return await this.evaluateJson(() => document.documentElement.outerHTML);
        }
    }
    async htmlSnapshotResolveFrameRefs(snapshot) {
        const frameMarkerRegex = /------@@(.*?):\d+@@------/g;
        const result = await (0, cdp_util_js_1.asyncRegexpReplace)(snapshot, frameMarkerRegex, async (m) => {
            const frameId = m[1];
            const frame = await this.page.resolveFrameById(frameId);
            if (!frame) {
                return 'data:text;frame content unavailable';
            }
            const html = await frame.captureHtmlSnapshot();
            const htmlBase64 = Buffer.from(html).toString('base64');
            return `data:text/html;base64,${htmlBase64}"`;
        });
        return result;
    }
}
exports.Frame = Frame;
//# sourceMappingURL=frame.js.map