/**
 * Copyright 2017 Google Inc. All rights reserved.
 *
 * 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 { assert } from './assert.js' ; import { helper } from './helper.js' ; import { Target } from './Target.js' ; import { EventEmitter } from './EventEmitter.js' ; import { Connection , ConnectionEmittedEvents } from './Connection.js' ; import { Protocol } from 'devtools-protocol' ; import { Page } from './Page.js' ; import { TaskQueue } from './TaskQueue.js' ; import { ChildProcess } from 'child_process' ; import { Viewport } from './PuppeteerViewport.js' ; public export interface BrowserContextOptions { /**
     * Proxy server with optional port to use for all requests.
     * Username and password can be set in `Page.authenticate`.
     */ proxyServer ? : string ; /**
     * Bypass the proxy for the given semi-colon-separated list of hosts.
     */ proxyBypassList ? : string [ ] ; } internal export type BrowserCloseCallback = ( ) => Promise < void > | void ; public export type TargetFilterCallback = ( target : Protocol . Target . TargetInfo ) => boolean ; const WEB_PERMISSION_TO_PROTOCOL_PERMISSION = new Map < Permission , Protocol . Browser . PermissionType > ( [ [ 'geolocation' , 'geolocation' ] , [ 'midi' , 'midi' ] , [ 'notifications' , 'notifications' ] , [ 'camera' , 'videoCapture' ] , [ 'microphone' , 'audioCapture' ] , [ 'background-sync' , 'backgroundSync' ] , [ 'ambient-light-sensor' , 'sensors' ] , [ 'accelerometer' , 'sensors' ] , [ 'gyroscope' , 'sensors' ] , [ 'magnetometer' , 'sensors' ] , [ 'accessibility-events' , 'accessibilityEvents' ] , [ 'clipboard-read' , 'clipboardReadWrite' ] , [ 'clipboard-write' , 'clipboardReadWrite' ] , [ 'payment-handler' , 'paymentHandler' ] , [ 'persistent-storage' , 'durableStorage' ] , [ 'idle-detection' , 'idleDetection' ] , [ 'midi-sysex' , 'midiSysex' ] , ] ) ; public export type Permission = 'geolocation' | 'midi' | 'notifications' | 'camera' | 'microphone' | 'background-sync' | 'ambient-light-sensor' | 'accelerometer' | 'gyroscope' | 'magnetometer' | 'accessibility-events' | 'clipboard-read' | 'clipboard-write' | 'payment-handler' | 'persistent-storage' | 'idle-detection' | 'midi-sysex' ; public export interface WaitForTargetOptions { defaultValue timeout ? : number ; } /**
 * All the events a  Browser may emit.
 *
 *  public export const enum BrowserEmittedEvents { /**
     * Emitted when Puppeteer gets disconnected from the Chromium instance. This
     * might happen because of one of the following:
     *
     * - Chromium is closed or crashed
     *
     * - The  Browser . disconnect method was called.
      Disconnected = 'disconnected' , /**
     * Emitted when the url of a target changes. Contains a  Target instance.
     *
     *  remarks TargetChanged = 'targetchanged' , /**
     * Emitted when a target is created, for example when a new page is opened by
     *  https * or by  Browser . newPage *
     * Contains a  Target instance.
     *
     *  remarks TargetCreated = 'targetcreated' , /**
     * Emitted when a target is destroyed, for example when a page is closed.
     * Contains a  Target instance.
     *
     *  remarks TargetDestroyed = 'targetdestroyed' } /**
 * A Browser is created when Puppeteer connects to a Chromium instance, either through
 *  PuppeteerNode . launch or  Puppeteer . connect .
 *
 *  remarks The Browser class extends from Puppeteer's  EventEmitter class and will
 * emit various events which are documented in the  BrowserEmittedEvents enum.
 *
 *  example An example of using a  Browser to create a  Page :
 * ```js
 * const puppeteer = require('puppeteer');
 *
 * (async () => {
 *   const browser = await puppeteer.launch();
 *   const page = await browser.newPage();
 *   await page.goto('https://example.com');
 *   await browser.close();
 * })();
 * ```
 *
 *  example An example of disconnecting from and reconnecting to a  Browser :
 * ```js
 * const puppeteer = require('puppeteer');
 *
 * (async () => {
 *   const browser = await puppeteer.launch();
 *   // Store the endpoint to be able to reconnect to Chromium
 *   const browserWSEndpoint = browser.wsEndpoint();
 *   // Disconnect puppeteer from Chromium
 *   browser.disconnect();
 *
 *   // Use the endpoint to reestablish a connection
 *   const browser2 = await puppeteer.connect({browserWSEndpoint});
 *   // Close Chromium
 *   await browser2.close();
 * })();
 * ```
 *
 *  public export class Browser extends EventEmitter { internal static async create ( connection , contextIds , ignoreHTTPSErrors , defaultViewport ? , process ? , closeCallback ? , targetFilterCallback ? ) { const browser = new Browser ( connection , contextIds , ignoreHTTPSErrors , defaultViewport , process , closeCallback , targetFilterCallback ) ; await connection . send ( 'Target.setDiscoverTargets' , { discover : true } ) ; return browser ; } private _ignoreHTTPSErrors : boolean ; private _defaultViewport ? : Viewport | null ; private _process ? : ChildProcess ; private _connection : Connection ; private _closeCallback : BrowserCloseCallback ; private _targetFilterCallback : TargetFilterCallback ; private _defaultContext : BrowserContext ; private _contexts : Map < string , BrowserContext > ; private _screenshotTaskQueue : TaskQueue ; private _ignoredTargets = new Set < string > ( ) ; internal _targets : Map < string , Target > ; internal constructor ( connection , contextIds , ignoreHTTPSErrors , defaultViewport ? , process ? , closeCallback ? , targetFilterCallback ? ) { super ( ) ; this . _ignoreHTTPSErrors = ignoreHTTPSErrors ; this . _defaultViewport = defaultViewport ; this . _process = process ; this . _screenshotTaskQueue = new TaskQueue ( ) ; this . _connection = connection ; this . _closeCallback = closeCallback || function ( ) : void { } ; this . _targetFilterCallback = targetFilterCallback || ( ( ) : boolean => true ) ; this . _defaultContext = new BrowserContext ( this . _connection , this , null ) ; this . _contexts = new Map ( ) ; for ( const contextId of contextIds ) this . _contexts . set ( contextId , new BrowserContext ( this . _connection , this , contextId ) ) ; this . _targets = new Map ( ) ; this . _connection . on ( ConnectionEmittedEvents . Disconnected , ( ) => this . emit ( BrowserEmittedEvents . Disconnected ) ) ; this . _connection . on ( 'Target.targetCreated' , this . _targetCreated . bind ( this ) ) ; this . _connection . on ( 'Target.targetDestroyed' , this . _targetDestroyed . bind ( this ) ) ; this . _connection . on ( 'Target.targetInfoChanged' , this . _targetInfoChanged . bind ( this ) ) ; } /**
     * The spawned browser process. Returns `null` if the browser instance was created with
     *  Puppeteer . connect .
      process ( ) { return this . _process ; } example async createIncognitoBrowserContext ( options = { } ) { const { proxyServer = '' , proxyBypassList = [ ] } = options ; const { browserContextId } = await this . _connection . send ( 'Target.createBrowserContext' , { proxyServer , proxyBypassList : proxyBypassList && proxyBypassList . join ( ',' ) , } ) ; const context = new BrowserContext ( this . _connection , this , browserContextId ) ; this . _contexts . set ( browserContextId , context ) ; return context ; } /**
     * Returns an array of all open browser contexts. In a newly created browser, this will
     * return a single instance of  BrowserContext .
      browserContexts ( ) { return [ this . _defaultContext , ... Array . from ( this . _contexts . values ( ) ) ] ; } /**
     * Returns the default browser context. The default browser context cannot be closed.
     */ defaultBrowserContext ( ) { return this . _defaultContext ; } internal async _disposeContext ( contextId ? ) { await this . _connection . send ( 'Target.disposeBrowserContext' , { browserContextId : contextId || undefined , } ) ; this . _contexts . delete ( contextId ) ; } private async _targetCreated ( event ) { const targetInfo = event . targetInfo ; const { browserContextId } = targetInfo ; const context = browserContextId && this . _contexts . has ( browserContextId ) ? this . _contexts . get ( browserContextId ) : this . _defaultContext ; const shouldAttachToTarget = this . _targetFilterCallback ( targetInfo ) ; if ( ! shouldAttachToTarget ) { this . _ignoredTargets . add ( targetInfo . targetId ) ; return ; } const target = new Target ( targetInfo , context , ( ) => this . _connection . createSession ( targetInfo ) , this . _ignoreHTTPSErrors , this . _defaultViewport , this . _screenshotTaskQueue ) ; assert ( ! this . _targets . has ( event . targetInfo . targetId ) , 'Target should not exist before targetCreated' ) ; this . _targets . set ( event . targetInfo . targetId , target ) ; if ( await target . _initializedPromise ) { this . emit ( BrowserEmittedEvents . TargetCreated , target ) ; context . emit ( BrowserContextEmittedEvents . TargetCreated , target ) ; } } private async _targetDestroyed ( event ) { if ( this . _ignoredTargets . has ( event . targetId ) ) return ; const target = this . _targets . get ( event . targetId ) ; target . _initializedCallback ( false ) ; this . _targets . delete ( event . targetId ) ; target . _closedCallback ( ) ; if ( await target . _initializedPromise ) { this . emit ( BrowserEmittedEvents . TargetDestroyed , target ) ; target . browserContext ( ) . emit ( BrowserContextEmittedEvents . TargetDestroyed , target ) ; } } private _targetInfoChanged ( event ) { if ( this . _ignoredTargets . has ( event . targetInfo . targetId ) ) return ; const target = this . _targets . get ( event . targetInfo . targetId ) ; assert ( target , 'target should exist before targetInfoChanged' ) ; const previousURL = target . url ( ) ; const wasInitialized = target . _isInitialized ; target . _targetInfoChanged ( event . targetInfo ) ; if ( wasInitialized && previousURL !== target . url ( ) ) { this . emit ( BrowserEmittedEvents . TargetChanged , target ) ; target . browserContext ( ) . emit ( BrowserContextEmittedEvents . TargetChanged , target ) ; } } /**
     * The browser websocket endpoint which can be used as an argument to
     *  Puppeteer . connect .
     *
     *  returns remarks The format is `ws://${host}:${port}/devtools/browser/<id>`.
     *
     * You can find the `webSocketDebuggerUrl` from `http://${host}:${port}/json/version`.
     * Learn more about the
     *  https and
     * the  {@link
     * https://chromedevtools.github.io/devtools-protocol/#how-do-i-access-the-browser-target
 * | browser endpoint}.
      wsEndpoint ( ) { return this . _connection . url ( ) ; } /**
     * Promise which resolves to a new  Page object. The Page is created in
     * a default browser context.
      async newPage ( ) { return this . _defaultContext . newPage ( ) ; } internal async _createPageInContext ( contextId ? ) { const { targetId } = await this . _connection . send ( 'Target.createTarget' , { url : 'about:blank' , browserContextId : contextId || undefined , } ) ; const target = this . _targets . get ( targetId ) ; assert ( await target . _initializedPromise , 'Failed to create target for page' ) ; const page = await target . page ( ) ; return page ; } /**
     * All active targets inside the Browser. In case of multiple browser contexts, returns
     * an array with all the targets in all browser contexts.
     */ targets ( ) { return Array . from ( this . _targets . values ( ) ) . filter ( ( target ) => target . _isInitialized ) ; } /**
     * The target associated with the browser.
     */ target ( ) { return this . targets ( ) . find ( ( target ) => target . type ( ) === 'browser' ) ; } param predicate returns example async waitForTarget ( predicate , options = { } ) { const { timeout = 30000 } = options ; const existingTarget = this . targets ( ) . find ( predicate ) ; if ( existingTarget ) return existingTarget ; let resolve ; const targetPromise = new Promise < Target > ( ( x ) => ( resolve = x ) ) ; this . on ( BrowserEmittedEvents . TargetCreated , check ) ; this . on ( BrowserEmittedEvents . TargetChanged , check ) ; try { if ( ! timeout ) return await targetPromise ; return await helper . waitWithTimeout < Target > ( targetPromise , 'target' , timeout ) ; } finally { this . removeListener ( BrowserEmittedEvents . TargetCreated , check ) ; this . removeListener ( BrowserEmittedEvents . TargetChanged , check ) ; } function check ( target ) { if ( predicate ( target ) ) resolve ( target ) ; } } remarks In case of multiple browser contexts, returns an array with all the pages in all
     * browser contexts. Non-visible pages, such as `"background_page"`, will not be listed
     * here. You can find them using  Target . page .
      async pages ( ) { const contextPages = await Promise . all ( this . browserContexts ( ) . map ( ( context ) => context . pages ( ) ) ) ; return contextPages . reduce ( ( acc , x ) => acc . concat ( x ) , [ ] ) ; } remarks async version ( ) { const version = await this . _getVersion ( ) ; return version . product ; } /**
     * The browser's original user agent. Pages can override the browser user agent with
     *  Page . setUserAgent .
      async userAgent ( ) { const version = await this . _getVersion ( ) ; return version . userAgent ; } /**
     * Closes Chromium and all of its pages (if any were opened). The  Browser object
     * itself is considered to be disposed and cannot be used anymore.
      async close ( ) { await this . _closeCallback . call ( null ) ; this . disconnect ( ) ; } /**
     * Disconnects Puppeteer from the browser, but leaves the Chromium process running.
     * After calling `disconnect`, the  Browser object is considered disposed and
     * cannot be used anymore.
      disconnect ( ) { this . _connection . dispose ( ) ; } /**
     * Indicates that the browser is connected.
     */ isConnected ( ) { return ! this . _connection . _closed ; } private _getVersion ( ) { return this . _connection . send ( 'Browser.getVersion' ) ; } } public export const enum BrowserContextEmittedEvents { /**
     * Emitted when the url of a target inside the browser context changes.
     * Contains a  Target instance.
      TargetChanged = 'targetchanged' , /**
     * Emitted when a target is created within the browser context, for example
     * when a new page is opened by
     *  https * or by  BrowserContext . newPage *
     * Contains a  Target instance.
      TargetCreated = 'targetcreated' , /**
     * Emitted when a target is destroyed within the browser context, for example
     * when a page is closed. Contains a  Target instance.
      TargetDestroyed = 'targetdestroyed' } /**
 * BrowserContexts provide a way to operate multiple independent browser
 * sessions. When a browser is launched, it has a single BrowserContext used by
 * default. The method  Browser . newPage creates a page
 * in the default browser context.
 *
 *  remarks The Browser class extends from Puppeteer's  EventEmitter class and
 * will emit various events which are documented in the
 *  BrowserContextEmittedEvents enum.
 *
 * If a page opens another page, e.g. with a `window.open` call, the popup will
 * belong to the parent page's browser context.
 *
 * Puppeteer allows creation of "incognito" browser contexts with
 *  Browser . createIncognitoBrowserContext * method. "Incognito" browser contexts don't write any browsing data to disk.
 *
 *  example public export class BrowserContext extends EventEmitter { private _connection : Connection ; private _browser : Browser ; private _id ? : string ; internal constructor ( connection , browser , contextId ? ) { super ( ) ; this . _connection = connection ; this . _browser = browser ; this . _id = contextId ; } /**
     * An array of all active targets inside the browser context.
     */ targets ( ) { return this . _browser . targets ( ) . filter ( ( target ) => target . browserContext ( ) === this ) ; } example param predicate param options returns waitForTarget ( predicate , options = { } ) { return this . _browser . waitForTarget ( ( target ) => target . browserContext ( ) === this && predicate ( target ) , options ) ; } returns Promise which resolves to an array of all open pages.
     * Non visible pages, such as `"background_page"`, will not be listed here.
     * You can find them using  Target . page .
      async pages ( ) { const pages = await Promise . all ( this . targets ( ) . filter ( ( target ) => target . type ( ) === 'page' ) . map ( ( target ) => target . page ( ) ) ) ; return pages . filter ( ( page ) => ! ! page ) ; } remarks isIncognito ( ) { return ! ! this . _id ; } example param origin param permissions async overridePermissions ( origin , permissions ) { const protocolPermissions = permissions . map ( ( permission ) => { const protocolPermission = WEB_PERMISSION_TO_PROTOCOL_PERMISSION . get ( permission ) ; if ( ! protocolPermission ) throw new Error ( 'Unknown permission: ' + permission ) ; return protocolPermission ; } ) ; await this . _connection . send ( 'Browser.grantPermissions' , { origin , browserContextId : this . _id || undefined , permissions : protocolPermissions , } ) ; } example async clearPermissionOverrides ( ) { await this . _connection . send ( 'Browser.resetPermissions' , { browserContextId : this . _id || undefined , } ) ; } /**
     * Creates a new page in the browser context.
     */ newPage ( ) { return this . _browser . _createPageInContext ( this . _id ) ; } /**
     * The browser this browser context belongs to.
     */ browser ( ) { return this . _browser ; } remarks async close ( ) { assert ( this . _id , 'Non-incognito profiles cannot be closed!' ) ; await this . _browser . _disposeContext ( this . _id ) ; } }