"use strict";
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.DEFAULT_BROWSER_SETTINGS = exports.Launcher = exports.BrowserWrapper = void 0;
const fs = __importStar(require("node:fs"));
const os = __importStar(require("node:os"));
const path = __importStar(require("node:path"));
const url = __importStar(require("node:url"));
const puppeteer = __importStar(require("puppeteer-core"));
const events_js_1 = require("../../conductor/events.js");
const paths_js_1 = require("../../conductor/paths.js");
const test_config_js_1 = require("../../conductor/test_config.js");
class BrowserWrapper {
    browser;
    constructor(b) {
        this.browser = b;
    }
    get connected() {
        return this.browser.connected;
    }
    async createBrowserContext() {
        return await this.browser.createBrowserContext();
    }
    copyCrashDumps() {
        const crashesPath = this.#getCrashpadDir();
        if (!fs.existsSync(crashesPath)) {
            // TODO (liviurau): Determine where exactly does Crashpad store the dumps on
            // Linux and Windows.
            console.error('No crash dumps found at location ', crashesPath);
            return;
        }
        for (const file of fs.readdirSync(crashesPath)) {
            const target = path.join(test_config_js_1.TestConfig.artifactsDir, file);
            if (fs.existsSync(target)) {
                continue;
            }
            console.error('Collecting crash dump:', file);
            fs.copyFileSync(path.join(crashesPath, file), path.join(test_config_js_1.TestConfig.artifactsDir, file));
        }
    }
    #getCrashpadDir() {
        // TODO (liviurau): generate a tmp dir and pass when launching puppeteer
        // instead of parsing it out of args
        const userDataArg = this.browser.process()?.spawnargs.find(arg => arg.startsWith('--user-data-dir='));
        if (userDataArg) {
            const configuredPath = path.join(userDataArg.split('=')[1], 'Crashpad', 'pending');
            // `--user-data-dir` generally does not contain Craspad files on any
            // platform. In the future this might get properly aligned so we search
            // here first.
            if (fs.existsSync(configuredPath)) {
                return configuredPath;
            }
        }
        const homeDir = os.homedir();
        const platform = os.platform();
        switch (platform) {
            case 'darwin':
                return path.join(homeDir, 'Library', 'Application Support', 'Google', 'Chrome for Testing', 'Crashpad', 'pending');
            case 'win32': {
                const localAppData = path.join(process.env.LOCALAPPDATA ?? '', 'Google', 'Chrome for Testing', 'User Data', 'Crashpad', 'pending');
                if (fs.existsSync(localAppData)) {
                    return localAppData;
                }
                return path.join(homeDir, 'AppData', 'Local', 'Google', 'Chrome for Testing', 'User Data', 'Crashpad', 'pending');
            }
            case 'linux':
                return path.join(homeDir, '.config', 'google-chrome-for-testing', 'Crashpad', 'pending');
            default:
                throw new Error(`Unsupported platform: ${platform}`);
        }
    }
}
exports.BrowserWrapper = BrowserWrapper;
class Launcher {
    static async browserSetup(settings, serverPort) {
        const browser = await Launcher.launchChrome(settings, serverPort);
        (0, events_js_1.setupBrowserProcessIO)(browser);
        const wrapper = new BrowserWrapper(browser);
        if (settings.extensions && settings.extensions.length > 0) {
            for (const extPath of settings.extensions) {
                await wrapper.browser.installExtension(extPath, {
                    enabledInIncognito: true,
                });
            }
        }
        return wrapper;
    }
    static launchChrome(settings, serverPort) {
        const frontEndDirectory = url.pathToFileURL(path.join(paths_js_1.GEN_DIR, 'front_end'));
        const disabledFeatures = settings.disabledFeatures?.slice() ?? [];
        const launchArgs = [
            '--remote-allow-origins=*',
            '--remote-debugging-port=0',
            '--enable-experimental-web-platform-features',
            // This fingerprint may be generated from the certificate using
            // openssl x509 -noout -pubkey -in scripts/hosted_mode/cert.pem | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | base64
            '--ignore-certificate-errors-spki-list=KLy6vv6synForXwI6lDIl+D3ZrMV6Y1EMTY6YpOcAos=',
            '--site-per-process', // Default on Desktop anyway, but ensure that we always use out-of-process frames when we intend to.
            `--host-resolver-rules=MAP *.test 127.0.0.1, MAP chromeuxreport.googleapis.com 127.0.0.1:${serverPort}`,
            '--disable-gpu',
            `--disable-features=${disabledFeatures.join(',')}`,
            `--custom-devtools-frontend=${frontEndDirectory}`,
            '--enable-crash-reporter',
            // This has no effect (see https://crbug.com/435638630)
            `--crash-dumps-dir=${test_config_js_1.TestConfig.artifactsDir}`,
            `--privacy-sandbox-enrollment-overrides=https://localhost:${serverPort}`,
        ];
        const headless = test_config_js_1.TestConfig.headless;
        // CDP commands in e2e and interaction should not generally take
        // more than 20 seconds, but performance tests might require more time.
        const protocolTimeout = test_config_js_1.TestConfig.debug ? 0 : test_config_js_1.TestConfig.isPerfTest ? 120_000 : 20_000;
        const executablePath = test_config_js_1.TestConfig.chromeBinary;
        const opts = {
            headless,
            executablePath,
            dumpio: !headless || Boolean(process.env['LUCI_CONTEXT']),
            protocolTimeout,
            networkEnabled: false,
            enableExtensions: true,
            pipe: true,
            ignoreDefaultArgs: ['--disable-crash-reporter', '--disable-breakpad'],
        };
        test_config_js_1.TestConfig.configureChrome(executablePath);
        const viewportWidth = 1280;
        const viewportHeight = 720;
        // Adding some offset to the window size used in the headful mode
        // so to account for the size of the browser UI.
        // Values are chosen by trial and error to make sure that the window
        // size is not much bigger than the viewport but so that the entire
        // viewport is visible.
        const windowWidth = viewportWidth + 50;
        const windowHeight = viewportHeight + 200;
        // Always set the default viewport because setting only the window size for
        // headful mode would result in much smaller actual viewport.
        opts.defaultViewport = { width: viewportWidth, height: viewportHeight };
        // Toggle either viewport or window size depending on headless vs not.
        if (!headless) {
            launchArgs.push(`--window-size=${windowWidth},${windowHeight}`);
        }
        const enabledFeatures = settings.enabledFeatures?.slice() ?? [];
        // TODO: remove
        const envChromeFeatures = process.env['CHROME_FEATURES'];
        if (envChromeFeatures) {
            enabledFeatures.push(envChromeFeatures);
        }
        launchArgs.push(`--enable-features=${enabledFeatures.join(',')}`);
        if (settings.extensions && settings.extensions.length > 0) {
            if (Array.isArray(opts.ignoreDefaultArgs)) {
                opts.ignoreDefaultArgs.push('--disable-extensions');
            }
            else {
                opts.ignoreDefaultArgs = ['--disable-extensions'];
            }
        }
        opts.args = launchArgs;
        return puppeteer.launch(opts);
    }
}
exports.Launcher = Launcher;
exports.DEFAULT_BROWSER_SETTINGS = {
    // LINT.IfChange(features)
    enabledFeatures: [
        'PartitionedCookies',
        'SharedStorageAPI',
        'FencedFrames',
        'PrivacySandboxAdsAPIsOverride',
        'AutofillEnableDevtoolsIssues',
        'DevToolsVeLogging:testing/true',
        'CADisplayLink',
    ],
    disabledFeatures: [
        'PMProcessPriorityPolicy', // crbug.com/361252079
        'MojoChannelAssociatedSendUsesRunOrPostTask', // crbug.com/376228320
        'RasterInducingScroll', // crbug.com/381055647
        'CompositeBackgroundColorAnimation', // crbug.com/381055647
        'ScriptSrcHashesV1', // crbug.com/443216445
        'RenderDocument', // crbug.com/444369637
    ],
    // LINT.ThenChange(/test/conductor/hooks.ts:features)
};
//# sourceMappingURL=browser-helper.js.map