/**
 * 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 { Artifact } from './artifact';
import { BrowserContext, prepareBrowserContextParams } from './browserContext';
import { CDPSession } from './cdpSession';
import { ChannelOwner } from './channelOwner';
import { isTargetClosedError } from './errors';
import { Events } from './events';
import { mkdirIfNeeded } from './fileUtils';
export class Browser extends ChannelOwner {
    _contexts = new Set();
    _isConnected = true;
    _closedPromise;
    _shouldCloseConnectionOnClose = false;
    _browserType;
    _options = {};
    _name;
    _browserName;
    _path;
    _closeReason;
    static from(browser) {
        return browser._object;
    }
    constructor(parent, type, guid, initializer) {
        super(parent, type, guid, initializer);
        this._name = initializer.name;
        this._browserName = initializer.browserName;
        this._channel.on('context', ({ context }) => this._didCreateContext(BrowserContext.from(context)));
        this._channel.on('close', () => this._didClose());
        this._closedPromise = new Promise(f => this.once(Events.Browser.Disconnected, f));
    }
    browserType() {
        return this._browserType;
    }
    async newContext(options = {}) {
        return await this._innerNewContext(options, false);
    }
    async _newContextForReuse(options = {}) {
        return await this._innerNewContext(options, true);
    }
    async _disconnectFromReusedContext(reason) {
        const context = [...this._contexts].find(context => context._forReuse);
        if (!context)
            return;
        await this._instrumentation.runBeforeCloseBrowserContext(context);
        for (const page of context.pages())
            page._onClose();
        context._onClose();
        await this._channel.disconnectFromReusedContext({ reason });
    }
    async _innerNewContext(userOptions = {}, forReuse) {
        const options = this._browserType._playwright.selectors._withSelectorOptions(userOptions);
        await this._instrumentation.runBeforeCreateBrowserContext(options);
        const contextOptions = await prepareBrowserContextParams(this._platform, options);
        const response = forReuse ? await this._channel.newContextForReuse(contextOptions) : await this._channel.newContext(contextOptions);
        const context = BrowserContext.from(response.context);
        if (forReuse)
            context._forReuse = true;
        if (options.logger)
            context._logger = options.logger;
        await context._initializeHarFromOptions(options.recordHar);
        await this._instrumentation.runAfterCreateBrowserContext(context);
        return context;
    }
    _connectToBrowserType(browserType, browserOptions, logger) {
        // Note: when using connect(), `browserType` is different from `this._parent`.
        // This is why browser type is not wired up in the constructor,
        // and instead this separate method is called later on.
        this._browserType = browserType;
        this._options = browserOptions;
        this._logger = logger;
        for (const context of this._contexts)
            this._setupBrowserContext(context);
    }
    _didCreateContext(context) {
        context._browser = this;
        this._contexts.add(context);
        // Note: when connecting to a browser, initial contexts arrive before `browserType` is set,
        // and will be configured later in `_connectToBrowserType`.
        if (this._browserType)
            this._setupBrowserContext(context);
        this.emit(Events.Browser.Context, context);
    }
    _setupBrowserContext(context) {
        context._logger = this._logger;
        context.tracing._tracesDir = this._options.tracesDir;
        this._browserType._contexts.add(context);
        this._browserType._playwright.selectors._contextsForSelectors.add(context);
        context.setDefaultTimeout(this._browserType._playwright._defaultContextTimeout);
        context.setDefaultNavigationTimeout(this._browserType._playwright._defaultContextNavigationTimeout);
    }
    contexts() {
        return [...this._contexts];
    }
    version() {
        return this._initializer.version;
    }
    async bind(title, options = {}) {
        const { endpoint } = await this._channel.startServer({ title, ...options });
        return { endpoint };
    }
    async unbind() {
        await this._channel.stopServer();
    }
    async newPage(options = {}) {
        return await this._wrapApiCall(async () => {
            const context = await this.newContext(options);
            const page = await context.newPage();
            page._ownedContext = context;
            context._ownerPage = page;
            return page;
        }, { title: 'Create page' });
    }
    isConnected() {
        return this._isConnected;
    }
    async newBrowserCDPSession() {
        return CDPSession.from((await this._channel.newBrowserCDPSession()).session);
    }
    async startTracing(page, options = {}) {
        this._path = options.path;
        await this._channel.startTracing({ ...options, page: page ? page._channel : undefined });
    }
    async stopTracing() {
        const artifact = Artifact.from((await this._channel.stopTracing()).artifact);
        const buffer = await artifact.readIntoBuffer();
        await artifact.delete();
        if (this._path) {
            await mkdirIfNeeded(this._platform, this._path);
            await this._platform.fs().promises.writeFile(this._path, buffer);
            this._path = undefined;
        }
        return buffer;
    }
    async [Symbol.asyncDispose]() {
        await this.close();
    }
    async close(options = {}) {
        this._closeReason = options.reason;
        try {
            if (this._shouldCloseConnectionOnClose)
                this._connection.close();
            else
                await this._channel.close(options);
            await this._closedPromise;
        }
        catch (e) {
            if (isTargetClosedError(e))
                return;
            throw e;
        }
    }
    _didClose() {
        this._isConnected = false;
        this.emit(Events.Browser.Disconnected, this);
    }
}
//# sourceMappingURL=browser.js.map