/**
 * Copyright (c) Microsoft Corporation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { serializeAsCallArgument } from '@isomorphic/utilityScriptSerializers';
import { LongStandingScope } from '@isomorphic/manualPromise';
import { isUnderTest } from '@utils/debug';
import { SdkObject } from './instrumentation';
import * as rawUtilityScriptSource from '../generated/utilityScriptSource';
export class ExecutionContext extends SdkObject {
    delegate;
    _utilityScriptPromise;
    _contextDestroyedScope = new LongStandingScope();
    worldNameForTest;
    constructor(parent, delegate, worldNameForTest) {
        super(parent, 'execution-context');
        this.worldNameForTest = worldNameForTest;
        this.delegate = delegate;
    }
    contextDestroyed(reason) {
        this._contextDestroyedScope.close(new Error(reason));
    }
    async raceAgainstContextDestroyed(promise) {
        return this._contextDestroyedScope.race(promise);
    }
    rawEvaluateJSON(expression) {
        return this.raceAgainstContextDestroyed(this.delegate.rawEvaluateJSON(expression));
    }
    rawEvaluateHandle(expression) {
        return this.raceAgainstContextDestroyed(this.delegate.rawEvaluateHandle(this, expression));
    }
    async _evaluateWithArguments(expression, returnByValue, values, handles) {
        const utilityScript = await this._utilityScript();
        return this.raceAgainstContextDestroyed(this.delegate.evaluateWithArguments(expression, returnByValue, utilityScript, values, handles));
    }
    getProperties(object) {
        return this.raceAgainstContextDestroyed(this.delegate.getProperties(object));
    }
    _releaseHandle(handle) {
        return this.delegate.releaseHandle(handle);
    }
    adoptIfNeeded(handle) {
        return null;
    }
    _utilityScript() {
        if (!this._utilityScriptPromise) {
            const source = `
      (() => {
        const module = {};
        ${rawUtilityScriptSource.source}
        return new (module.exports.UtilityScript())(globalThis, ${isUnderTest()});
      })();`;
            this._utilityScriptPromise = this.raceAgainstContextDestroyed(this.delegate.rawEvaluateHandle(this, source))
                .then(handle => {
                handle._setPreview('UtilityScript');
                return handle;
            });
        }
        return this._utilityScriptPromise;
    }
}
export class JSHandle extends SdkObject {
    __jshandle = true;
    _context;
    _disposed = false;
    _objectId;
    _value;
    _objectType;
    _preview;
    _previewCallback;
    constructor(context, type, preview, objectId, value) {
        super(context, 'handle');
        this._context = context;
        this._objectId = objectId;
        this._value = value;
        this._objectType = type;
        this._preview = this._objectId ? preview || `JSHandle@${this._objectType}` : String(value);
        if (this._objectId && globalThis.leakedJSHandles)
            globalThis.leakedJSHandles.set(this, new Error('Leaked JSHandle'));
    }
    async evaluateExpression(progress, expression, options, arg) {
        return await progress.race(this.internalEvaluateExpression(expression, options, arg));
    }
    async evaluateExpressionHandle(progress, expression, options, arg) {
        return await progress.race(this._evaluateExpressionHandle(expression, options, arg));
    }
    async getProperty(progress, propertyName) {
        return await progress.race(this._getProperty(propertyName));
    }
    async getProperties(progress) {
        return await progress.race(this.internalGetProperties());
    }
    async jsonValue(progress) {
        return await progress.race(this._jsonValue());
    }
    async evaluate(pageFunction, arg) {
        return evaluate(this._context, true /* returnByValue */, pageFunction, this, arg);
    }
    async evaluateHandle(pageFunction, arg) {
        return evaluate(this._context, false /* returnByValue */, pageFunction, this, arg);
    }
    async internalEvaluateExpression(expression, options, arg) {
        return await evaluateExpression(this._context, expression, { ...options, returnByValue: true }, this, arg);
    }
    async _evaluateExpressionHandle(expression, options, arg) {
        return await evaluateExpression(this._context, expression, { ...options, returnByValue: false }, this, arg);
    }
    async _getProperty(propertyName) {
        const objectHandle = await this.evaluateHandle((object, propertyName) => {
            const result = { __proto__: null };
            result[propertyName] = object[propertyName];
            return result;
        }, propertyName);
        const properties = await objectHandle.internalGetProperties();
        const result = properties.get(propertyName);
        objectHandle.dispose();
        return result;
    }
    async internalGetProperties() {
        if (!this._objectId)
            return new Map();
        return this._context.getProperties(this);
    }
    rawValue() {
        return this._value;
    }
    async _jsonValue() {
        if (!this._objectId)
            return this._value;
        const script = `(utilityScript, ...args) => utilityScript.jsonValue(...args)`;
        return this._context._evaluateWithArguments(script, true, [true], [this]);
    }
    asElement() {
        return null;
    }
    dispose() {
        if (this._disposed)
            return;
        this._disposed = true;
        if (this._objectId) {
            this._context._releaseHandle(this).catch(e => { });
            if (globalThis.leakedJSHandles)
                globalThis.leakedJSHandles.delete(this);
        }
    }
    toString() {
        return this._preview;
    }
    _setPreviewCallback(callback) {
        this._previewCallback = callback;
    }
    preview() {
        return this._preview;
    }
    worldNameForTest() {
        return this._context.worldNameForTest;
    }
    _setPreview(preview) {
        this._preview = preview;
        if (this._previewCallback)
            this._previewCallback(preview);
    }
}
export async function evaluate(context, returnByValue, pageFunction, ...args) {
    return evaluateExpression(context, String(pageFunction), { returnByValue, isFunction: typeof pageFunction === 'function' }, ...args);
}
export async function evaluateExpression(context, expression, options, ...args) {
    expression = normalizeEvaluationExpression(expression, options.isFunction);
    const handles = [];
    const toDispose = [];
    const pushHandle = (handle) => {
        handles.push(handle);
        return handles.length - 1;
    };
    args = args.map(arg => serializeAsCallArgument(arg, handle => {
        if (handle instanceof JSHandle) {
            if (!handle._objectId)
                return { fallThrough: handle._value };
            if (handle._disposed)
                throw new JavaScriptErrorInEvaluate('JSHandle is disposed!');
            const adopted = context.adoptIfNeeded(handle);
            if (adopted === null)
                return { h: pushHandle(Promise.resolve(handle)) };
            toDispose.push(adopted);
            return { h: pushHandle(adopted) };
        }
        return { fallThrough: handle };
    }));
    const utilityScriptObjects = [];
    for (const handle of await Promise.all(handles)) {
        if (handle._context !== context)
            throw new JavaScriptErrorInEvaluate('JSHandles can be evaluated only in the context they were created!');
        utilityScriptObjects.push(handle);
    }
    // See UtilityScript for arguments.
    const utilityScriptValues = [options.isFunction, options.returnByValue, expression, args.length, ...args];
    const script = `(utilityScript, ...args) => utilityScript.evaluate(...args)`;
    try {
        return await context._evaluateWithArguments(script, options.returnByValue || false, utilityScriptValues, utilityScriptObjects);
    }
    finally {
        toDispose.map(handlePromise => handlePromise.then(handle => handle.dispose()));
    }
}
export function parseUnserializableValue(unserializableValue) {
    if (unserializableValue === 'NaN')
        return NaN;
    if (unserializableValue === 'Infinity')
        return Infinity;
    if (unserializableValue === '-Infinity')
        return -Infinity;
    if (unserializableValue === '-0')
        return -0;
}
export function normalizeEvaluationExpression(expression, isFunction) {
    expression = expression.trim();
    if (isFunction) {
        try {
            new Function('(' + expression + ')');
        }
        catch (e1) {
            // This means we might have a function shorthand. Try another
            // time prefixing 'function '.
            if (expression.startsWith('async '))
                expression = 'async function ' + expression.substring('async '.length);
            else
                expression = 'function ' + expression;
            try {
                new Function('(' + expression + ')');
            }
            catch (e2) {
                // We tried hard to serialize, but there's a weird beast here.
                throw new Error('Passed function is not well-serializable!');
            }
        }
    }
    if (/^(async)?\s*function(\s|\()/.test(expression))
        expression = '(' + expression + ')';
    return expression;
}
// Error inside the expression evaluation as opposed to a protocol error.
export class JavaScriptErrorInEvaluate extends Error {
}
export function isJavaScriptErrorInEvaluate(error) {
    return error instanceof JavaScriptErrorInEvaluate;
}
export function sparseArrayToString(entries) {
    const arrayEntries = [];
    for (const { name, value } of entries) {
        const index = +name;
        if (isNaN(index) || index < 0)
            continue;
        arrayEntries.push({ index, value });
    }
    arrayEntries.sort((a, b) => a.index - b.index);
    let lastIndex = -1;
    const tokens = [];
    for (const { index, value } of arrayEntries) {
        const emptyItems = index - lastIndex - 1;
        if (emptyItems === 1)
            tokens.push(`empty`);
        else if (emptyItems > 1)
            tokens.push(`empty x ${emptyItems}`);
        tokens.push(String(value));
        lastIndex = index;
    }
    return '[' + tokens.join(', ') + ']';
}
//# sourceMappingURL=javascript.js.map