2019-12-10 15:13:56 -08:00
/ * *
* Copyright 2017 Google Inc . All rights reserved .
* Modifications 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 .
* /
2021-04-30 00:02:48 +02:00
import * as os from 'os' ;
2022-04-07 13:36:13 -08:00
import { TimeoutSettings } from '../common/timeoutSettings' ;
2022-06-28 15:09:36 -07:00
import { createGuid , debugMode } from '../utils' ;
2022-04-07 19:18:22 -08:00
import { mkdirIfNeeded } from '../utils/fileUtils' ;
2022-04-06 13:57:14 -08:00
import type { Browser , BrowserOptions } from './browser' ;
import type { Download } from './download' ;
import type * as frames from './frames' ;
2020-08-17 16:19:21 -07:00
import { helper } from './helper' ;
2020-03-05 17:22:57 -08:00
import * as network from './network' ;
2022-04-06 13:57:14 -08:00
import type { PageDelegate } from './page' ;
import { Page , PageBinding } from './page' ;
2022-11-22 15:21:20 -08:00
import type { Progress , ProgressController } from './progress' ;
2022-04-06 13:57:14 -08:00
import type { Selectors } from './selectors' ;
import type * as types from './types' ;
2022-09-20 18:41:51 -07:00
import type * as channels from '@protocol/channels' ;
2021-02-11 06:36:15 -08:00
import path from 'path' ;
2022-03-18 09:00:52 -07:00
import fs from 'fs' ;
2022-04-06 13:57:14 -08:00
import type { CallMetadata } from './instrumentation' ;
import { serverSideCallMetadata , SdkObject } from './instrumentation' ;
2022-04-08 11:52:40 -08:00
import { Debugger } from './debugger' ;
2021-04-27 11:07:07 -07:00
import { Tracing } from './trace/recorder/tracing' ;
2022-04-08 11:52:40 -08:00
import { HarRecorder } from './har/harRecorder' ;
import { Recorder } from './recorder' ;
2021-04-23 20:39:09 -07:00
import * as consoleApiSource from '../generated/consoleApiSource' ;
2021-11-05 16:27:49 +01:00
import { BrowserContextAPIRequestContext } from './fetch' ;
2022-06-28 15:09:36 -07:00
import type { Artifact } from './artifact' ;
2019-12-11 07:18:43 -08:00
2021-02-09 09:00:00 -08:00
export abstract class BrowserContext extends SdkObject {
2020-08-21 16:26:33 -07:00
static Events = {
2023-05-04 15:11:46 -07:00
Console : 'console' ,
2020-08-21 16:26:33 -07:00
Close : 'close' ,
2023-05-04 15:11:46 -07:00
Dialog : 'dialog' ,
2020-08-21 16:26:33 -07:00
Page : 'page' ,
2023-08-17 09:10:03 -07:00
// Can't use just 'error' due to node.js special treatment of error events.
// @see https://nodejs.org/api/events.html#events_error_events
PageError : 'pageerror' ,
2021-05-13 10:29:14 -07:00
Request : 'request' ,
Response : 'response' ,
RequestFailed : 'requestfailed' ,
RequestFinished : 'requestfinished' ,
2023-06-02 13:00:27 -07:00
RequestAborted : 'requestaborted' ,
RequestFulfilled : 'requestfulfilled' ,
RequestContinued : 'requestcontinued' ,
2021-01-25 14:49:26 -08:00
BeforeClose : 'beforeclose' ,
2021-03-31 10:38:05 -07:00
VideoStarted : 'videostarted' ,
2020-08-21 16:26:33 -07:00
} ;
2020-03-05 17:22:57 -08:00
readonly _timeoutSettings = new TimeoutSettings ( ) ;
readonly _pageBindings = new Map < string , PageBinding > ( ) ;
2022-11-22 15:21:20 -08:00
readonly _activeProgressControllers = new Set < ProgressController > ( ) ;
2022-06-14 22:02:15 -07:00
readonly _options : channels.BrowserNewContextParams ;
2020-08-18 17:34:04 -07:00
_requestInterceptor? : network.RouteHandler ;
2020-06-29 16:26:32 -07:00
private _isPersistentContext : boolean ;
2020-07-08 21:36:03 -07:00
private _closedStatus : 'open' | 'closing' | 'closed' = 'open' ;
2020-06-10 15:12:50 -07:00
readonly _closePromise : Promise < Error > ;
2020-03-05 17:22:57 -08:00
private _closePromiseFulfill : ( ( error : Error ) = > void ) | undefined ;
2020-03-20 19:45:35 -07:00
readonly _permissions = new Map < string , string [ ] > ( ) ;
2020-04-02 17:56:14 -07:00
readonly _downloads = new Set < Download > ( ) ;
2020-08-19 10:31:59 -07:00
readonly _browser : Browser ;
2020-08-25 16:33:16 -07:00
readonly _browserContextId : string | undefined ;
2020-09-02 16:15:43 -07:00
private _selectors? : Selectors ;
2020-11-13 14:24:53 -08:00
private _origins = new Set < string > ( ) ;
2022-06-28 15:09:36 -07:00
readonly _harRecorders = new Map < string , HarRecorder > ( ) ;
2021-04-27 11:07:07 -07:00
readonly tracing : Tracing ;
2021-11-05 16:27:49 +01:00
readonly fetchRequest : BrowserContextAPIRequestContext ;
2021-12-08 17:34:50 -08:00
private _customCloseHandler ? : ( ) = > Promise < any > ;
2022-03-18 09:00:52 -07:00
readonly _tempDirs : string [ ] = [ ] ;
2022-03-18 17:17:37 -08:00
private _settingStorageState = false ;
2022-04-02 18:02:27 -08:00
readonly initScripts : string [ ] = [ ] ;
2022-07-07 12:07:09 -08:00
private _routesInFlight = new Set < network.Route > ( ) ;
2022-11-22 11:06:45 -08:00
private _debugger ! : Debugger ;
2023-10-16 20:32:13 -07:00
_closeReason : string | undefined ;
2020-03-05 17:22:57 -08:00
2022-06-14 22:02:15 -07:00
constructor ( browser : Browser , options : channels.BrowserNewContextParams , browserContextId : string | undefined ) {
2021-04-20 23:03:56 -07:00
super ( browser , 'browser-context' ) ;
2021-02-09 09:00:00 -08:00
this . attribution . context = this ;
2020-08-19 10:31:59 -07:00
this . _browser = browser ;
2020-03-05 17:22:57 -08:00
this . _options = options ;
2020-08-25 16:33:16 -07:00
this . _browserContextId = browserContextId ;
this . _isPersistentContext = ! browserContextId ;
2020-03-05 17:22:57 -08:00
this . _closePromise = new Promise ( fulfill = > this . _closePromiseFulfill = fulfill ) ;
2021-04-23 18:34:52 -07:00
2021-12-02 15:53:47 -08:00
this . fetchRequest = new BrowserContextAPIRequestContext ( this ) ;
2021-04-23 18:34:52 -07:00
if ( this . _options . recordHar )
2022-06-28 15:09:36 -07:00
this . _harRecorders . set ( '' , new HarRecorder ( this , null , this . _options . recordHar ) ) ;
2021-08-25 13:32:56 -07:00
2022-01-22 11:25:13 -08:00
this . tracing = new Tracing ( this , browser . options . tracesDir ) ;
2020-03-05 17:22:57 -08:00
}
2021-10-19 14:36:17 -08:00
isPersistentContext ( ) : boolean {
return this . _isPersistentContext ;
}
2022-04-02 18:02:27 -08:00
setSelectors ( selectors : Selectors ) {
2020-09-02 16:15:43 -07:00
this . _selectors = selectors ;
}
2021-02-09 09:00:00 -08:00
selectors ( ) : Selectors {
2023-06-01 17:54:43 -07:00
return this . _selectors || this . attribution . playwright . selectors ;
2020-09-02 16:15:43 -07:00
}
2020-05-27 22:16:54 -07:00
async _initialize() {
2023-06-01 17:54:43 -07:00
if ( this . attribution . playwright . options . isInternalPlaywright )
2021-04-23 18:34:52 -07:00
return ;
// Debugger will pause execution upon page.pause in headed mode.
2022-11-22 11:06:45 -08:00
this . _debugger = new Debugger ( this ) ;
2021-04-23 18:34:52 -07:00
// When PWDEBUG=1, show inspector for each context.
if ( debugMode ( ) === 'inspector' )
2022-04-08 11:52:40 -08:00
await Recorder . show ( this , { pauseOnNextStatement : true } ) ;
2021-04-23 18:34:52 -07:00
// When paused, show inspector.
2022-11-22 11:06:45 -08:00
if ( this . _debugger . isPaused ( ) )
2022-04-08 11:52:40 -08:00
Recorder . showInspector ( this ) ;
2022-11-22 11:06:45 -08:00
this . _debugger . on ( Debugger . Events . PausedStateChanged , ( ) = > {
2022-04-08 11:52:40 -08:00
Recorder . showInspector ( this ) ;
2021-04-23 18:34:52 -07:00
} ) ;
2021-04-23 20:39:09 -07:00
if ( debugMode ( ) === 'console' )
2021-09-08 14:27:05 -07:00
await this . extendInjectedScript ( consoleApiSource . source ) ;
2022-06-08 18:27:51 -04:00
if ( this . _options . serviceWorkers === 'block' )
2023-01-12 19:47:45 +01:00
await this . addInitScript ( ` \ nnavigator.serviceWorker.register = async () => { console.warn('Service Worker registration blocked by Playwright'); }; \ n ` ) ;
2022-07-07 10:39:05 -08:00
if ( this . _options . permissions )
await this . grantPermissions ( this . _options . permissions ) ;
2020-08-28 10:51:55 -07:00
}
2022-11-22 11:06:45 -08:00
debugger ( ) : Debugger {
return this . _debugger ;
}
2020-10-01 11:06:19 -07:00
async _ensureVideosPath() {
2020-11-02 19:42:05 -08:00
if ( this . _options . recordVideo )
await mkdirIfNeeded ( path . join ( this . _options . recordVideo . dir , 'dummy' ) ) ;
2020-09-18 17:36:43 -07:00
}
2022-07-11 12:10:08 -08:00
canResetForReuse ( ) : boolean {
if ( this . _closedStatus !== 'open' )
return false ;
return true ;
}
2023-10-09 19:55:24 -07:00
async stopPendingOperations ( reason : string ) {
// When using context reuse, stop pending operations to gracefully terminate all the actions
// with a user-friendly error message containing operation log.
2022-11-22 15:21:20 -08:00
for ( const controller of this . _activeProgressControllers )
2023-10-09 19:55:24 -07:00
controller . abort ( new Error ( reason ) ) ;
// Let rejections in microtask generate events before returning.
await new Promise ( f = > setTimeout ( f , 0 ) ) ;
2022-11-22 15:21:20 -08:00
}
2022-07-12 13:30:24 -08:00
static reusableContextHash ( params : channels.BrowserNewContextForReuseParams ) : string {
const paramsCopy = { . . . params } ;
2022-08-03 16:14:28 -07:00
for ( const k of Object . keys ( paramsCopy ) ) {
const key = k as keyof channels . BrowserNewContextForReuseParams ;
if ( paramsCopy [ key ] === defaultNewContextParamValues [ key ] )
delete paramsCopy [ key ] ;
}
2022-07-12 13:30:24 -08:00
for ( const key of paramsThatAllowContextReuse )
delete paramsCopy [ key ] ;
return JSON . stringify ( paramsCopy ) ;
}
2022-08-10 10:57:28 -07:00
async resetForReuse ( metadata : CallMetadata , params : channels.BrowserNewContextForReuseParams | null ) {
2022-07-11 12:10:08 -08:00
this . setDefaultNavigationTimeout ( undefined ) ;
this . setDefaultTimeout ( undefined ) ;
2023-01-04 13:19:05 -08:00
this . tracing . resetForReuse ( ) ;
2022-07-11 12:10:08 -08:00
2022-08-10 10:57:28 -07:00
if ( params ) {
for ( const key of paramsThatAllowContextReuse )
( this . _options as any ) [ key ] = params [ key ] ;
}
2022-07-12 13:30:24 -08:00
2022-07-11 12:10:08 -08:00
await this . _cancelAllRoutesInFlight ( ) ;
2022-08-03 16:14:28 -07:00
// Close extra pages early.
2022-07-31 14:31:17 -07:00
let page : Page | undefined = this . pages ( ) [ 0 ] ;
const [ , . . . otherPages ] = this . pages ( ) ;
for ( const p of otherPages )
await p . close ( metadata ) ;
2023-09-27 14:09:56 -07:00
if ( page && page . hasCrashed ( ) ) {
2022-07-11 12:10:08 -08:00
await page . close ( metadata ) ;
2022-07-31 14:31:17 -07:00
page = undefined ;
}
2022-10-26 15:17:40 -07:00
// Unless dialogs are dismissed, setting extra http headers below does not respond.
page ? . _frameManager . setCloseAllOpeningDialogs ( true ) ;
2022-07-12 13:30:24 -08:00
await page ? . _frameManager . closeOpenDialogs ( ) ;
2022-08-04 16:39:18 -07:00
// Navigate to about:blank first to ensure no page scripts are running after this point.
2022-07-12 13:30:24 -08:00
await page ? . mainFrame ( ) . goto ( metadata , 'about:blank' , { timeout : 0 } ) ;
2022-10-26 15:17:40 -07:00
page ? . _frameManager . setCloseAllOpeningDialogs ( false ) ;
2022-09-07 19:15:04 -07:00
await this . _resetStorage ( ) ;
2022-07-12 13:30:24 -08:00
await this . _removeExposedBindings ( ) ;
await this . _removeInitScripts ( ) ;
2022-07-11 12:10:08 -08:00
// TODO: following can be optimized to not perform noops.
if ( this . _options . permissions )
await this . grantPermissions ( this . _options . permissions ) ;
else
await this . clearPermissions ( ) ;
await this . setExtraHTTPHeaders ( this . _options . extraHTTPHeaders || [ ] ) ;
await this . setGeolocation ( this . _options . geolocation ) ;
await this . setOffline ( ! ! this . _options . offline ) ;
2022-08-03 16:14:28 -07:00
await this . setUserAgent ( this . _options . userAgent ) ;
2023-03-03 12:37:44 +01:00
await this . clearCache ( ) ;
2022-09-07 19:15:04 -07:00
await this . _resetCookies ( ) ;
2022-07-11 12:10:08 -08:00
2022-07-12 13:30:24 -08:00
await page ? . resetForReuse ( metadata ) ;
2022-07-11 12:10:08 -08:00
}
2020-03-05 17:22:57 -08:00
_browserClosed() {
2020-03-13 11:33:33 -07:00
for ( const page of this . pages ( ) )
2020-03-05 17:22:57 -08:00
page . _didClose ( ) ;
2020-06-29 16:26:32 -07:00
this . _didCloseInternal ( ) ;
2020-03-05 17:22:57 -08:00
}
2020-06-29 16:26:32 -07:00
private _didCloseInternal() {
2020-07-08 21:36:03 -07:00
if ( this . _closedStatus === 'closed' ) {
// We can come here twice if we close browser context and browser
// at the same time.
return ;
}
2023-11-01 20:17:10 -07:00
this . tracing . abort ( ) ;
2021-06-14 21:55:55 -07:00
if ( this . _isPersistentContext )
2022-04-02 18:02:27 -08:00
this . onClosePersistent ( ) ;
2020-06-29 16:26:32 -07:00
this . _closePromiseFulfill ! ( new Error ( 'Context closed' ) ) ;
2020-08-21 16:26:33 -07:00
this . emit ( BrowserContext . Events . Close ) ;
2020-03-05 17:22:57 -08:00
}
// BrowserContext methods.
2020-03-13 11:33:33 -07:00
abstract pages ( ) : Page [ ] ;
2020-11-12 12:41:23 -08:00
abstract newPageDelegate ( ) : Promise < PageDelegate > ;
2022-06-14 22:02:15 -07:00
abstract addCookies ( cookies : channels.SetNetworkCookie [ ] ) : Promise < void > ;
2020-03-05 17:22:57 -08:00
abstract clearCookies ( ) : Promise < void > ;
2020-08-17 16:19:21 -07:00
abstract setGeolocation ( geolocation? : types.Geolocation ) : Promise < void > ;
2020-08-18 15:38:29 -07:00
abstract setExtraHTTPHeaders ( headers : types.HeadersArray ) : Promise < void > ;
2022-08-03 16:14:28 -07:00
abstract setUserAgent ( userAgent : string | undefined ) : Promise < void > ;
2020-03-05 17:22:57 -08:00
abstract setOffline ( offline : boolean ) : Promise < void > ;
2022-04-02 18:02:27 -08:00
abstract cancelDownload ( uuid : string ) : Promise < void > ;
2023-03-03 12:37:44 +01:00
abstract clearCache ( ) : Promise < void > ;
2022-06-14 22:02:15 -07:00
protected abstract doGetCookies ( urls : string [ ] ) : Promise < channels.NetworkCookie [ ] > ;
2022-04-02 18:02:27 -08:00
protected abstract doGrantPermissions ( origin : string , permissions : string [ ] ) : Promise < void > ;
protected abstract doClearPermissions ( ) : Promise < void > ;
protected abstract doSetHTTPCredentials ( httpCredentials? : types.Credentials ) : Promise < void > ;
protected abstract doAddInitScript ( expression : string ) : Promise < void > ;
2022-04-04 11:39:43 -08:00
protected abstract doRemoveInitScripts ( ) : Promise < void > ;
2022-04-02 18:02:27 -08:00
protected abstract doExposeBinding ( binding : PageBinding ) : Promise < void > ;
2022-04-04 11:39:43 -08:00
protected abstract doRemoveExposedBindings ( ) : Promise < void > ;
2022-04-02 18:02:27 -08:00
protected abstract doUpdateRequestInterception ( ) : Promise < void > ;
2023-10-16 20:32:13 -07:00
protected abstract doClose ( reason : string | undefined ) : Promise < void > ;
2022-04-02 18:02:27 -08:00
protected abstract onClosePersistent ( ) : void ;
2020-03-05 17:22:57 -08:00
2022-06-14 22:02:15 -07:00
async cookies ( urls : string | string [ ] | undefined = [ ] ) : Promise < channels.NetworkCookie [ ] > {
2020-06-25 08:30:56 -07:00
if ( urls && ! Array . isArray ( urls ) )
2022-08-18 20:12:33 +02:00
urls = [ urls ] ;
2022-04-02 18:02:27 -08:00
return await this . doGetCookies ( urls as string [ ] ) ;
2020-06-25 08:30:56 -07:00
}
2020-08-17 16:19:21 -07:00
setHTTPCredentials ( httpCredentials? : types.Credentials ) : Promise < void > {
2022-04-02 18:02:27 -08:00
return this . doSetHTTPCredentials ( httpCredentials ) ;
2020-06-30 11:02:12 -07:00
}
2021-09-08 14:27:05 -07:00
async exposeBinding ( name : string , needsHandle : boolean , playwrightBinding : frames.FunctionWithSource ) : Promise < void > {
if ( this . _pageBindings . has ( name ) )
2020-12-02 13:43:16 -08:00
throw new Error ( ` Function " ${ name } " has been already registered ` ) ;
2020-05-18 14:28:06 -07:00
for ( const page of this . pages ( ) ) {
2021-09-08 14:27:05 -07:00
if ( page . getBinding ( name ) )
2020-05-18 14:28:06 -07:00
throw new Error ( ` Function " ${ name } " has been already registered in one of the pages ` ) ;
}
2021-09-08 14:27:05 -07:00
const binding = new PageBinding ( name , playwrightBinding , needsHandle ) ;
this . _pageBindings . set ( name , binding ) ;
2022-04-02 18:02:27 -08:00
await this . doExposeBinding ( binding ) ;
2020-05-18 14:28:06 -07:00
}
2022-07-12 13:30:24 -08:00
async _removeExposedBindings() {
2022-05-09 06:44:20 -08:00
for ( const key of this . _pageBindings . keys ( ) ) {
if ( ! key . startsWith ( '__pw' ) )
this . _pageBindings . delete ( key ) ;
}
2022-04-04 11:39:43 -08:00
await this . doRemoveExposedBindings ( ) ;
}
2020-08-20 14:19:27 -07:00
async grantPermissions ( permissions : string [ ] , origin? : string ) {
let resolvedOrigin = '*' ;
if ( origin ) {
const url = new URL ( origin ) ;
resolvedOrigin = url . origin ;
2020-03-17 15:32:50 -07:00
}
2020-08-20 14:19:27 -07:00
const existing = new Set ( this . _permissions . get ( resolvedOrigin ) || [ ] ) ;
2020-03-17 15:32:50 -07:00
permissions . forEach ( p = > existing . add ( p ) ) ;
const list = [ . . . existing . values ( ) ] ;
2020-08-20 14:19:27 -07:00
this . _permissions . set ( resolvedOrigin , list ) ;
2022-04-02 18:02:27 -08:00
await this . doGrantPermissions ( resolvedOrigin , list ) ;
2020-03-17 15:32:50 -07:00
}
async clearPermissions() {
this . _permissions . clear ( ) ;
2022-04-02 18:02:27 -08:00
await this . doClearPermissions ( ) ;
2020-03-17 15:32:50 -07:00
}
2021-10-28 07:31:30 -08:00
setDefaultNavigationTimeout ( timeout : number | undefined ) {
2020-03-05 17:22:57 -08:00
this . _timeoutSettings . setDefaultNavigationTimeout ( timeout ) ;
}
2021-10-28 07:31:30 -08:00
setDefaultTimeout ( timeout : number | undefined ) {
2020-03-05 17:22:57 -08:00
this . _timeoutSettings . setDefaultTimeout ( timeout ) ;
}
2020-04-20 07:52:26 -07:00
2020-12-10 16:37:18 -08:00
async _loadDefaultContextAsIs ( progress : Progress ) : Promise < Page [ ] > {
2020-08-17 14:36:51 -07:00
if ( ! this . pages ( ) . length ) {
2020-08-21 16:26:33 -07:00
const waitForEvent = helper . waitForEvent ( progress , this , BrowserContext . Events . Page ) ;
2020-08-17 14:36:51 -07:00
progress . cleanupWhenAborted ( ( ) = > waitForEvent . dispose ) ;
2021-03-18 23:14:57 +08:00
const page = ( await waitForEvent . promise ) as Page ;
if ( page . _pageIsError )
throw page . _pageIsError ;
2020-08-17 14:36:51 -07:00
}
2020-05-10 15:23:53 -07:00
const pages = this . pages ( ) ;
2021-03-18 23:14:57 +08:00
if ( pages [ 0 ] . _pageIsError )
throw pages [ 0 ] . _pageIsError ;
2020-09-14 16:43:17 -07:00
await pages [ 0 ] . mainFrame ( ) . _waitForLoadState ( progress , 'load' ) ;
2020-12-10 16:37:18 -08:00
return pages ;
}
async _loadDefaultContext ( progress : Progress ) {
const pages = await this . _loadDefaultContextAsIs ( progress ) ;
2022-07-27 22:23:22 +03:00
const browserName = this . _browser . options . name ;
if ( ( this . _options . isMobile && browserName === 'chromium' ) || ( this . _options . locale && browserName === 'webkit' ) ) {
2020-05-21 15:13:16 -07:00
// Workaround for:
// - chromium fails to change isMobile for existing page;
// - webkit fails to change locale for existing page.
const oldPage = pages [ 0 ] ;
2021-02-19 09:33:24 -08:00
await this . newPage ( progress . metadata ) ;
await oldPage . close ( progress . metadata ) ;
2020-05-10 15:23:53 -07:00
}
}
2020-06-05 13:50:15 -07:00
protected _authenticateProxyViaHeader() {
2021-01-29 16:00:56 -08:00
const proxy = this . _options . proxy || this . _browser . options . proxy || { username : undefined , password : undefined } ;
2020-06-05 13:50:15 -07:00
const { username , password } = proxy ;
if ( username ) {
this . _options . httpCredentials = { username , password : password ! } ;
const token = Buffer . from ( ` ${ username } : ${ password } ` ) . toString ( 'base64' ) ;
2020-08-18 15:38:29 -07:00
this . _options . extraHTTPHeaders = network . mergeHeaders ( [
this . _options . extraHTTPHeaders ,
network . singleHeader ( 'Proxy-Authorization' , ` Basic ${ token } ` ) ,
] ) ;
2020-06-05 13:50:15 -07:00
}
}
protected _authenticateProxyViaCredentials() {
2021-01-29 16:00:56 -08:00
const proxy = this . _options . proxy || this . _browser . options . proxy ;
2020-06-05 13:50:15 -07:00
if ( ! proxy )
return ;
const { username , password } = proxy ;
2020-12-21 11:47:13 -08:00
if ( username )
this . _options . httpCredentials = { username , password : password || '' } ;
2020-06-05 13:50:15 -07:00
}
2020-06-29 16:26:32 -07:00
2022-04-02 18:02:27 -08:00
async addInitScript ( script : string ) {
this . initScripts . push ( script ) ;
await this . doAddInitScript ( script ) ;
}
2022-07-12 13:30:24 -08:00
async _removeInitScripts ( ) : Promise < void > {
2022-04-04 11:39:43 -08:00
this . initScripts . splice ( 0 , this . initScripts . length ) ;
await this . doRemoveInitScripts ( ) ;
}
2022-04-02 18:02:27 -08:00
async setRequestInterceptor ( handler : network.RouteHandler | undefined ) : Promise < void > {
2020-08-18 17:34:04 -07:00
this . _requestInterceptor = handler ;
2022-04-02 18:02:27 -08:00
await this . doUpdateRequestInterception ( ) ;
2020-08-18 17:34:04 -07:00
}
2020-11-04 16:23:38 -08:00
isClosingOrClosed() {
return this . _closedStatus !== 'open' ;
}
2021-04-08 18:56:09 -07:00
private async _deleteAllDownloads ( ) : Promise < void > {
await Promise . all ( Array . from ( this . _downloads ) . map ( download = > download . artifact . deleteOnContextClose ( ) ) ) ;
}
2022-03-18 09:00:52 -07:00
private async _deleteAllTempDirs ( ) : Promise < void > {
await Promise . all ( this . _tempDirs . map ( async dir = > await fs . promises . unlink ( dir ) . catch ( e = > { } ) ) ) ;
}
2021-12-08 17:34:50 -08:00
setCustomCloseHandler ( handler : ( ( ) = > Promise < any > ) | undefined ) {
this . _customCloseHandler = handler ;
}
2023-10-16 20:32:13 -07:00
async close ( options : { reason? : string } ) {
2020-07-08 21:36:03 -07:00
if ( this . _closedStatus === 'open' ) {
2023-10-16 20:32:13 -07:00
if ( options . reason )
this . _closeReason = options . reason ;
2021-01-25 14:49:26 -08:00
this . emit ( BrowserContext . Events . BeforeClose ) ;
2020-07-08 21:36:03 -07:00
this . _closedStatus = 'closing' ;
2020-10-04 18:18:05 -07:00
2022-06-28 15:09:36 -07:00
for ( const harRecorder of this . _harRecorders . values ( ) )
await harRecorder . flush ( ) ;
2023-11-01 20:17:10 -07:00
await this . tracing . flush ( ) ;
2020-10-26 14:32:07 -07:00
2021-04-09 14:09:45 -07:00
// Cleanup.
const promises : Promise < void > [ ] = [ ] ;
for ( const { context , artifact } of this . _browser . _idToVideo . values ( ) ) {
// Wait for the videos to finish.
if ( context === this )
promises . push ( artifact . finishedPromise ( ) ) ;
}
2021-12-08 17:34:50 -08:00
if ( this . _customCloseHandler ) {
await this . _customCloseHandler ( ) ;
2020-10-04 18:18:05 -07:00
} else {
// Close the context.
2023-10-16 20:32:13 -07:00
await this . doClose ( options . reason ) ;
2020-10-04 18:18:05 -07:00
}
2021-04-08 18:56:09 -07:00
// We delete downloads after context closure
// so that browser does not write to the download file anymore.
promises . push ( this . _deleteAllDownloads ( ) ) ;
2022-03-18 09:00:52 -07:00
promises . push ( this . _deleteAllTempDirs ( ) ) ;
2020-09-18 17:36:43 -07:00
await Promise . all ( promises ) ;
2020-10-04 18:18:05 -07:00
2021-12-08 17:34:50 -08:00
// Custom handler should trigger didCloseInternal itself.
2022-11-01 14:26:38 -07:00
if ( ! this . _customCloseHandler )
this . _didCloseInternal ( ) ;
2020-06-29 16:26:32 -07:00
}
await this . _closePromise ;
}
2020-11-12 12:41:23 -08:00
2021-02-19 09:33:24 -08:00
async newPage ( metadata : CallMetadata ) : Promise < Page > {
2020-11-12 12:41:23 -08:00
const pageDelegate = await this . newPageDelegate ( ) ;
2022-03-17 17:27:33 -08:00
if ( metadata . isServerSide )
pageDelegate . potentiallyUninitializedPage ( ) . markAsServerSideOnly ( ) ;
2020-11-12 12:41:23 -08:00
const pageOrError = await pageDelegate . pageOrError ( ) ;
if ( pageOrError instanceof Page ) {
if ( pageOrError . isClosed ( ) )
throw new Error ( 'Page has been closed.' ) ;
return pageOrError ;
}
throw pageOrError ;
}
2020-11-13 14:24:53 -08:00
addVisitedOrigin ( origin : string ) {
this . _origins . add ( origin ) ;
}
2022-06-14 22:02:15 -07:00
async storageState ( ) : Promise < channels.BrowserContextStorageStateResult > {
const result : channels.BrowserContextStorageStateResult = {
2021-10-07 15:37:47 -07:00
cookies : await this . cookies ( ) ,
2020-11-13 14:24:53 -08:00
origins : [ ]
} ;
if ( this . _origins . size ) {
2022-03-17 17:27:33 -08:00
const internalMetadata = serverSideCallMetadata ( ) ;
2021-02-19 09:33:24 -08:00
const page = await this . newPage ( internalMetadata ) ;
2020-11-13 14:24:53 -08:00
await page . _setServerRequestInterceptor ( handler = > {
2023-03-23 14:57:03 -07:00
handler . fulfill ( { body : '<html></html>' , requestUrl : handler.request ( ) . url ( ) } ) . catch ( ( ) = > { } ) ;
2023-01-27 10:43:19 -08:00
return true ;
2020-11-13 14:24:53 -08:00
} ) ;
for ( const origin of this . _origins ) {
2022-06-14 22:02:15 -07:00
const originStorage : channels.OriginStorage = { origin , localStorage : [ ] } ;
2020-11-13 14:24:53 -08:00
const frame = page . mainFrame ( ) ;
2021-02-19 09:33:24 -08:00
await frame . goto ( internalMetadata , origin ) ;
2021-07-09 16:19:42 +02:00
const storage = await frame . evaluateExpression ( ` ({
2020-11-13 14:24:53 -08:00
localStorage : Object.keys ( localStorage ) . map ( name = > ( { name , value : localStorage.getItem ( name ) } ) ) ,
2023-05-31 14:08:44 -07:00
} ) ` , { world: 'utility' });
2020-11-13 14:24:53 -08:00
originStorage . localStorage = storage . localStorage ;
2021-03-16 10:03:09 +08:00
if ( storage . localStorage . length )
result . origins . push ( originStorage ) ;
2020-11-13 14:24:53 -08:00
}
2021-02-19 09:33:24 -08:00
await page . close ( internalMetadata ) ;
2020-11-13 14:24:53 -08:00
}
return result ;
}
2022-09-07 19:15:04 -07:00
async _resetStorage() {
const oldOrigins = this . _origins ;
const newOrigins = new Map ( this . _options . storageState ? . origins ? . map ( p = > [ p . origin , p ] ) || [ ] ) ;
if ( ! oldOrigins . size && ! newOrigins . size )
2022-08-02 17:26:59 -07:00
return ;
let page = this . pages ( ) [ 0 ] ;
const internalMetadata = serverSideCallMetadata ( ) ;
2023-08-07 16:26:30 -07:00
page = page || await this . newPage ( {
. . . internalMetadata ,
// Do not mark this page as internal, because we will leave it for later reuse
// as a user-visible page.
isServerSide : false ,
} ) ;
2022-08-02 17:26:59 -07:00
await page . _setServerRequestInterceptor ( handler = > {
2023-03-23 14:57:03 -07:00
handler . fulfill ( { body : '<html></html>' , requestUrl : handler.request ( ) . url ( ) } ) . catch ( ( ) = > { } ) ;
2023-01-27 10:43:19 -08:00
return true ;
2022-08-02 17:26:59 -07:00
} ) ;
2022-09-07 19:15:04 -07:00
for ( const origin of new Set ( [ . . . oldOrigins , . . . newOrigins . keys ( ) ] ) ) {
2022-08-02 17:26:59 -07:00
const frame = page . mainFrame ( ) ;
await frame . goto ( internalMetadata , origin ) ;
2022-09-07 19:15:04 -07:00
await frame . resetStorageForCurrentOriginBestEffort ( newOrigins . get ( origin ) ) ;
2022-08-02 17:26:59 -07:00
}
2022-09-07 19:15:04 -07:00
2022-08-02 17:26:59 -07:00
await page . _setServerRequestInterceptor ( undefined ) ;
2022-09-07 19:15:04 -07:00
this . _origins = new Set ( [ . . . newOrigins . keys ( ) ] ) ;
2022-08-04 16:39:18 -07:00
// It is safe to not restore the URL to about:blank since we are doing it in Page::resetForReuse.
2022-08-02 17:26:59 -07:00
}
2022-09-07 19:15:04 -07:00
async _resetCookies() {
await this . clearCookies ( ) ;
if ( this . _options . storageState ? . cookies )
await this . addCookies ( this . _options . storageState ? . cookies ) ;
}
2022-03-18 17:17:37 -08:00
isSettingStorageState ( ) : boolean {
return this . _settingStorageState ;
}
2022-06-14 22:02:15 -07:00
async setStorageState ( metadata : CallMetadata , state : NonNullable < channels.BrowserNewContextParams [ 'storageState' ] > ) {
2022-03-18 17:17:37 -08:00
this . _settingStorageState = true ;
try {
if ( state . cookies )
await this . addCookies ( state . cookies ) ;
if ( state . origins && state . origins . length ) {
const internalMetadata = serverSideCallMetadata ( ) ;
const page = await this . newPage ( internalMetadata ) ;
await page . _setServerRequestInterceptor ( handler = > {
2023-03-23 14:57:03 -07:00
handler . fulfill ( { body : '<html></html>' , requestUrl : handler.request ( ) . url ( ) } ) . catch ( ( ) = > { } ) ;
2023-01-27 10:43:19 -08:00
return true ;
2022-03-18 17:17:37 -08:00
} ) ;
for ( const originState of state . origins ) {
const frame = page . mainFrame ( ) ;
await frame . goto ( metadata , originState . origin ) ;
await frame . evaluateExpression ( `
originState = > {
for ( const { name , value } of ( originState . localStorage || [ ] ) )
localStorage . setItem ( name , value ) ;
2023-05-31 14:08:44 -07:00
} ` , { isFunction: true, world: 'utility' }, originState);
2022-03-18 17:17:37 -08:00
}
await page . close ( internalMetadata ) ;
2020-11-13 14:24:53 -08:00
}
2022-03-18 17:17:37 -08:00
} finally {
this . _settingStorageState = false ;
2020-11-13 14:24:53 -08:00
}
}
2020-12-23 14:15:16 -08:00
2021-09-08 14:27:05 -07:00
async extendInjectedScript ( source : string , arg? : any ) {
const installInFrame = ( frame : frames.Frame ) = > frame . extendInjectedScript ( source , arg ) . catch ( ( ) = > { } ) ;
2020-12-23 14:15:16 -08:00
const installInPage = ( page : Page ) = > {
2021-01-13 14:25:42 -08:00
page . on ( Page . Events . InternalFrameNavigatedToNewDocument , installInFrame ) ;
2020-12-23 14:15:16 -08:00
return Promise . all ( page . frames ( ) . map ( installInFrame ) ) ;
} ;
this . on ( BrowserContext . Events . Page , installInPage ) ;
return Promise . all ( this . pages ( ) . map ( installInPage ) ) ;
}
2022-06-28 15:09:36 -07:00
async _harStart ( page : Page | null , options : channels.RecordHarOptions ) : Promise < string > {
const harId = createGuid ( ) ;
this . _harRecorders . set ( harId , new HarRecorder ( this , page , options ) ) ;
return harId ;
}
async _harExport ( harId : string | undefined ) : Promise < Artifact > {
const recorder = this . _harRecorders . get ( harId || '' ) ! ;
return recorder . export ( ) ;
}
2022-07-07 12:07:09 -08:00
addRouteInFlight ( route : network.Route ) {
this . _routesInFlight . add ( route ) ;
}
removeRouteInFlight ( route : network.Route ) {
this . _routesInFlight . delete ( route ) ;
}
async _cancelAllRoutesInFlight() {
await Promise . all ( [ . . . this . _routesInFlight ] . map ( r = > r . abort ( ) ) ) . catch ( ( ) = > { } ) ;
this . _routesInFlight . clear ( ) ;
}
2020-02-24 08:53:30 -08:00
}
2020-01-13 17:16:05 -08:00
2020-08-19 10:31:59 -07:00
export function assertBrowserContextIsNotOwned ( context : BrowserContext ) {
2020-03-13 11:33:33 -07:00
for ( const page of context . pages ( ) ) {
2020-02-24 08:53:30 -08:00
if ( page . _ownedContext )
throw new Error ( 'Please use browser.newContext() for multi-page scripts that share the context.' ) ;
2020-01-13 17:16:05 -08:00
}
2020-02-24 08:53:30 -08:00
}
2020-02-11 10:27:19 -08:00
2022-06-14 22:02:15 -07:00
export function validateBrowserContextOptions ( options : channels.BrowserNewContextParams , browserOptions : BrowserOptions ) {
2020-08-18 09:37:40 -07:00
if ( options . noDefaultViewport && options . deviceScaleFactor !== undefined )
2020-05-12 18:31:17 -07:00
throw new Error ( ` "deviceScaleFactor" option is not supported with null "viewport" ` ) ;
2023-02-02 13:11:50 -08:00
if ( options . noDefaultViewport && ! ! options . isMobile )
2020-05-12 18:31:17 -07:00
throw new Error ( ` "isMobile" option is not supported with null "viewport" ` ) ;
2021-12-06 09:25:24 -08:00
if ( options . acceptDownloads === undefined )
2023-08-17 10:57:28 +02:00
options . acceptDownloads = 'accept' ;
2020-08-18 09:37:40 -07:00
if ( ! options . viewport && ! options . noDefaultViewport )
options . viewport = { width : 1280 , height : 720 } ;
2021-02-10 13:37:27 -08:00
if ( options . recordVideo ) {
if ( ! options . recordVideo . size ) {
if ( options . noDefaultViewport ) {
options . recordVideo . size = { width : 800 , height : 600 } ;
} else {
const size = options . viewport ! ;
const scale = Math . min ( 1 , 800 / Math . max ( size . width , size . height ) ) ;
2021-02-08 10:59:48 -08:00
options . recordVideo . size = {
width : Math.floor ( size . width * scale ) ,
height : Math.floor ( size . height * scale )
} ;
}
}
2021-02-10 13:37:27 -08:00
// Make sure both dimensions are odd, this is required for vp8
options . recordVideo . size ! . width &= ~ 1 ;
options . recordVideo . size ! . height &= ~ 1 ;
2021-02-08 10:59:48 -08:00
}
2020-10-29 16:12:30 -07:00
if ( options . proxy ) {
2021-04-30 00:02:48 +02:00
if ( ! browserOptions . proxy && browserOptions . isChromium && os . platform ( ) === 'win32' )
throw new Error ( ` Browser needs to be launched with the global proxy. If all contexts override the proxy, global proxy will be never used and can be any string, for example "launch({ proxy: { server: 'http://per-context' } })" ` ) ;
2020-10-29 16:12:30 -07:00
options . proxy = normalizeProxySettings ( options . proxy ) ;
}
2020-08-18 09:37:40 -07:00
verifyGeolocation ( options . geolocation ) ;
2019-12-10 15:13:56 -08:00
}
2020-01-13 15:39:13 -08:00
2020-08-18 09:37:40 -07:00
export function verifyGeolocation ( geolocation? : types.Geolocation ) {
if ( ! geolocation )
return ;
geolocation . accuracy = geolocation . accuracy || 0 ;
const { longitude , latitude , accuracy } = geolocation ;
2020-07-21 15:25:31 -07:00
if ( longitude < - 180 || longitude > 180 )
throw new Error ( ` geolocation.longitude: precondition -180 <= LONGITUDE <= 180 failed. ` ) ;
if ( latitude < - 90 || latitude > 90 )
throw new Error ( ` geolocation.latitude: precondition -90 <= LATITUDE <= 90 failed. ` ) ;
if ( accuracy < 0 )
throw new Error ( ` geolocation.accuracy: precondition 0 <= ACCURACY failed. ` ) ;
2020-01-13 15:39:13 -08:00
}
2020-06-05 13:50:15 -07:00
2020-08-28 14:17:16 -07:00
export function normalizeProxySettings ( proxy : types.ProxySettings ) : types . ProxySettings {
2020-06-05 13:50:15 -07:00
let { server , bypass } = proxy ;
2020-08-28 14:17:16 -07:00
let url ;
try {
// new URL('127.0.0.1:8080') throws
// new URL('localhost:8080') fails to parse host or protocol
// In both of these cases, we need to try re-parse URL with `http://` prefix.
url = new URL ( server ) ;
if ( ! url . host || ! url . protocol )
url = new URL ( 'http://' + server ) ;
} catch ( e ) {
2020-06-05 13:50:15 -07:00
url = new URL ( 'http://' + server ) ;
}
2021-02-08 12:07:45 -08:00
if ( url . protocol === 'socks4:' && ( proxy . username || proxy . password ) )
throw new Error ( ` Socks4 proxy protocol does not support authentication ` ) ;
if ( url . protocol === 'socks5:' && ( proxy . username || proxy . password ) )
throw new Error ( ` Browser does not support socks5 proxy authentication ` ) ;
2020-08-28 14:17:16 -07:00
server = url . protocol + '//' + url . host ;
2020-06-05 13:50:15 -07:00
if ( bypass )
bypass = bypass . split ( ',' ) . map ( t = > t . trim ( ) ) . join ( ',' ) ;
return { . . . proxy , server , bypass } ;
}
2022-07-12 13:30:24 -08:00
const paramsThatAllowContextReuse : ( keyof channels . BrowserNewContextForReuseParams ) [ ] = [
'colorScheme' ,
'forcedColors' ,
'reducedMotion' ,
'screen' ,
2022-08-03 16:14:28 -07:00
'userAgent' ,
'viewport' ,
2022-07-12 13:30:24 -08:00
] ;
2022-08-03 16:14:28 -07:00
const defaultNewContextParamValues : channels.BrowserNewContextForReuseParams = {
noDefaultViewport : false ,
ignoreHTTPSErrors : false ,
javaScriptEnabled : true ,
bypassCSP : false ,
offline : false ,
isMobile : false ,
hasTouch : false ,
2023-08-17 10:57:28 +02:00
acceptDownloads : 'accept' ,
2022-08-03 16:14:28 -07:00
strictSelectors : false ,
serviceWorkers : 'allow' ,
2022-10-24 19:19:58 -04:00
locale : 'en-US' ,
2022-08-03 16:14:28 -07:00
} ;