"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExecutionContext = void 0;
const exception_js_1 = require("../exception.js");
const remote_element_js_1 = require("./remote-element.js");
const remote_object_js_1 = require("./remote-object.js");
class ExecutionContext {
    constructor(frame, executionContextId) {
        this.frame = frame;
        this.executionContextId = executionContextId;
        this.isAlive = true;
        this.listeners = {
            onExecutionContextsCleared: this.onExecutionContextsCleared.bind(this),
            onExecutionContextDestroyed: this.onExecutionContextDestroyed.bind(this),
        };
    }
    get page() {
        return this.frame.page;
    }
    get target() {
        return this.page.target;
    }
    async evaluate(pageFn, ...args) {
        const { executionContextId } = this;
        const functionDeclaration = pageFn.toString();
        const argv = args.map(arg => this.convertArgument(arg));
        const { result, exceptionDetails } = await this.page.send('Runtime.callFunctionOn', {
            functionDeclaration,
            executionContextId,
            arguments: argv,
            returnByValue: false,
            userGesture: true,
            awaitPromise: true,
        }).catch((err) => this.handleEvaluateError(err));
        if (exceptionDetails) {
            const err = this.convertException(exceptionDetails);
            this.frame.emit('evaluateError', err);
            throw err;
        }
        return this.createRemoteObject(result);
    }
    async evaluateJson(pageFn, ...args) {
        const { executionContextId } = this;
        const functionDeclaration = pageFn.toString();
        const argv = args.map(arg => this.convertArgument(arg));
        const { result, exceptionDetails } = await this.page.send('Runtime.callFunctionOn', {
            functionDeclaration,
            executionContextId,
            arguments: argv,
            returnByValue: true,
            userGesture: true,
            awaitPromise: true,
        }).catch((err) => this.handleEvaluateError(err));
        if (exceptionDetails) {
            const err = this.convertException(exceptionDetails);
            this.frame.emit('evaluateError', err);
            throw err;
        }
        return result.value;
    }
    async evaluateElement(pageFn, ...args) {
        const res = await this.evaluate(pageFn, ...args);
        if (!(res instanceof remote_element_js_1.RemoteElement) || res.isNull()) {
            return null;
        }
        return res;
    }
    async evaluateElementList(pageFn, ...args) {
        const nodeList = await this.evaluate(pageFn, ...args);
        return this.nodeListToElements(nodeList);
    }
    async nodeListToElements(nodeList) {
        const properties = await nodeList.getOwnProperties();
        await nodeList.release();
        const result = [];
        for (let i = 0; i < properties.size; i++) {
            result.push(properties.get(i.toString()));
        }
        return result;
    }
    createRemoteObject(cdpRemoteObject) {
        const { subtype } = cdpRemoteObject;
        if (subtype === 'node') {
            return new remote_element_js_1.RemoteElement(this, cdpRemoteObject);
        }
        return new remote_object_js_1.RemoteObject(this, cdpRemoteObject);
    }
    convertArgument(arg) {
        if (arg == null) {
            return { value: null };
        }
        if (arg instanceof remote_object_js_1.RemoteObject) {
            if (arg.cdpRemoteObject.objectId) {
                return { objectId: arg.cdpRemoteObject.objectId };
            }
            return { value: arg.cdpRemoteObject.value };
        }
        return { value: arg };
    }
    convertException(exceptionDetails) {
        const { exception, text, stackTrace } = exceptionDetails;
        let msg = text;
        if (exception && exception.description) {
            msg = exception.description;
        }
        else if (stackTrace) {
            for (const cf of stackTrace.callFrames) {
                const location = cf.url + ':' + cf.lineNumber + ':' + cf.columnNumber;
                const functionName = cf.functionName || '<anonymous>';
                msg += `\n    at ${functionName} (${location})`;
            }
        }
        msg = msg.replace(/^Uncaught \(in promise\)\s+/, '');
        return new exception_js_1.Exception({
            name: 'EvaluateFailed',
            message: msg,
            retry: true,
        });
    }
    handleEvaluateError(err) {
        if (/Cannot find context with specified id/.test(err.message)) {
            this.isAlive = false;
        }
        throw err;
    }
    async initContentScripts(scripts) {
        const options = {
            toolkitBinding: this.page.toolkitBinding,
        };
        const promises = scripts.map(async ({ fn, filename }) => {
            const source = `(${fn.toString()})(${JSON.stringify(options)})\n//# sourceURL=${filename}\n`;
            await this.page.send('Runtime.evaluate', {
                contextId: this.executionContextId,
                expression: source,
            });
        });
        await Promise.allSettled(promises);
    }
    onExecutionContextsCleared() {
        this.onDestroyed();
    }
    onExecutionContextDestroyed(ev) {
        if (ev.executionContextId === this.executionContextId) {
            this.onDestroyed();
        }
    }
    onDestroyed() {
        this.isAlive = false;
    }
}
exports.ExecutionContext = ExecutionContext;
//# sourceMappingURL=execution-context.js.map