2020-06-25 16:05:36 -07: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 .
* /
2020-12-26 17:05:57 -08:00
import { Page , BindingCall } from './page' ;
2020-06-25 16:05:36 -07:00
import * as network from './network' ;
2020-08-24 17:05:16 -07:00
import * as channels from '../protocol/channels' ;
2021-02-11 06:36:15 -08:00
import fs from 'fs' ;
2020-06-25 16:05:36 -07:00
import { ChannelOwner } from './channelOwner' ;
2020-08-22 07:07:13 -07:00
import { deprecate , evaluationScript , urlMatches } from './clientHelper' ;
2020-06-25 16:05:36 -07:00
import { Browser } from './browser' ;
2021-04-02 09:47:14 +08:00
import { Worker } from './worker' ;
2020-07-29 17:26:59 -07:00
import { Events } from './events' ;
2020-08-22 15:13:51 -07:00
import { TimeoutSettings } from '../utils/timeoutSettings' ;
2020-07-13 16:03:24 -07:00
import { Waiter } from './waiter' ;
2021-01-25 14:49:26 -08:00
import { URLMatch , Headers , WaitForEventOptions , BrowserContextOptions , StorageState , LaunchOptions } from './types' ;
2020-12-14 16:03:52 -08:00
import { isUnderTest , headersObjectToArray , mkdirIfNeeded } from '../utils/utils' ;
2020-09-30 21:17:30 -07:00
import { isSafeCloseError } from '../utils/errors' ;
2020-12-26 17:05:57 -08:00
import * as api from '../../types/types' ;
import * as structs from '../../types/structs' ;
2021-04-02 09:47:14 +08:00
import { CDPSession } from './cdpSession' ;
2021-04-24 20:39:48 -07:00
import { Tracing } from './tracing' ;
2020-06-25 16:05:36 -07:00
2020-12-26 17:05:57 -08:00
export class BrowserContext extends ChannelOwner < channels.BrowserContextChannel , channels.BrowserContextInitializer > implements api . BrowserContext {
2020-06-25 16:05:36 -07:00
_pages = new Set < Page > ( ) ;
2020-07-29 17:26:59 -07:00
private _routes : { url : URLMatch , handler : network.RouteHandler } [ ] = [ ] ;
2020-09-14 16:50:47 +02:00
readonly _browser : Browser | null = null ;
2020-12-26 17:05:57 -08:00
readonly _bindings = new Map < string , ( source : structs.BindingSource , ...args : any [ ] ) = > any > ( ) ;
2020-06-26 21:22:03 -07:00
_timeoutSettings = new TimeoutSettings ( ) ;
2020-06-29 16:37:38 -07:00
_ownerPage : Page | undefined ;
2020-07-09 15:33:01 -07:00
private _closedPromise : Promise < void > ;
2021-02-11 17:46:54 -08:00
_options : channels.BrowserNewContextParams = {
sdkLanguage : 'javascript'
} ;
2020-06-25 16:05:36 -07:00
2021-05-12 12:21:54 -07:00
readonly tracing : Tracing ;
2021-04-24 20:39:48 -07:00
2021-04-02 09:47:14 +08:00
readonly _backgroundPages = new Set < Page > ( ) ;
readonly _serviceWorkers = new Set < Worker > ( ) ;
readonly _isChromium : boolean ;
2020-08-24 17:05:16 -07:00
static from ( context : channels.BrowserContextChannel ) : BrowserContext {
2020-07-01 18:36:09 -07:00
return ( context as any ) . _object ;
2020-06-25 16:05:36 -07:00
}
2020-08-24 17:05:16 -07:00
static fromNullable ( context : channels.BrowserContextChannel | null ) : BrowserContext | null {
2020-06-25 16:05:36 -07:00
return context ? BrowserContext . from ( context ) : null ;
}
2021-01-13 12:08:14 -08:00
constructor ( parent : ChannelOwner , type : string , guid : string , initializer : channels.BrowserContextInitializer ) {
2020-07-27 10:21:39 -07:00
super ( parent , type , guid , initializer ) ;
2020-07-13 21:46:59 -07:00
if ( parent instanceof Browser )
2020-07-13 15:26:09 -07:00
this . _browser = parent ;
2021-04-02 09:47:14 +08:00
this . _isChromium = this . _browser ? . _name === 'chromium' ;
2021-05-12 12:21:54 -07:00
this . tracing = new Tracing ( this ) ;
2020-07-13 15:26:09 -07:00
2020-07-14 18:26:50 -07:00
this . _channel . on ( 'bindingCall' , ( { binding } ) = > this . _onBinding ( BindingCall . from ( binding ) ) ) ;
2020-06-30 10:55:11 -07:00
this . _channel . on ( 'close' , ( ) = > this . _onClose ( ) ) ;
2020-07-14 18:26:50 -07:00
this . _channel . on ( 'page' , ( { page } ) = > this . _onPage ( Page . from ( page ) ) ) ;
2020-06-30 10:55:11 -07:00
this . _channel . on ( 'route' , ( { route , request } ) = > this . _onRoute ( network . Route . from ( route ) , network . Request . from ( request ) ) ) ;
2021-04-02 09:47:14 +08:00
this . _channel . on ( 'backgroundPage' , ( { page } ) = > {
const backgroundPage = Page . from ( page ) ;
this . _backgroundPages . add ( backgroundPage ) ;
this . emit ( Events . BrowserContext . BackgroundPage , backgroundPage ) ;
} ) ;
this . _channel . on ( 'serviceWorker' , ( { worker } ) = > {
const serviceWorker = Worker . from ( worker ) ;
serviceWorker . _context = this ;
this . _serviceWorkers . add ( serviceWorker ) ;
this . emit ( Events . BrowserContext . ServiceWorker , serviceWorker ) ;
} ) ;
2021-05-13 10:29:14 -07:00
this . _channel . on ( 'request' , ( { request , page } ) = > this . _onRequest ( network . Request . from ( request ) , Page . fromNullable ( page ) ) ) ;
this . _channel . on ( 'requestFailed' , ( { request , failureText , responseEndTiming , page } ) = > this . _onRequestFailed ( network . Request . from ( request ) , responseEndTiming , failureText , Page . fromNullable ( page ) ) ) ;
this . _channel . on ( 'requestFinished' , ( { request , responseEndTiming , page } ) = > this . _onRequestFinished ( network . Request . from ( request ) , responseEndTiming , Page . fromNullable ( page ) ) ) ;
this . _channel . on ( 'response' , ( { response , page } ) = > this . _onResponse ( network . Response . from ( response ) , Page . fromNullable ( page ) ) ) ;
2020-07-09 15:33:01 -07:00
this . _closedPromise = new Promise ( f = > this . once ( Events . BrowserContext . Close , f ) ) ;
2020-06-25 16:05:36 -07:00
}
2020-06-26 12:28:27 -07:00
private _onPage ( page : Page ) : void {
this . _pages . add ( page ) ;
this . emit ( Events . BrowserContext . Page , page ) ;
2021-04-02 11:15:07 -07:00
if ( page . _opener && ! page . _opener . isClosed ( ) )
page . _opener . emit ( Events . Page . Popup , page ) ;
2020-06-26 12:28:27 -07:00
}
2020-06-26 11:51:47 -07:00
2021-05-13 10:29:14 -07:00
private _onRequest ( request : network.Request , page : Page | null ) {
this . emit ( Events . BrowserContext . Request , request ) ;
if ( page )
page . emit ( Events . Page . Request , request ) ;
}
private _onResponse ( response : network.Response , page : Page | null ) {
this . emit ( Events . BrowserContext . Response , response ) ;
if ( page )
page . emit ( Events . Page . Response , response ) ;
}
private _onRequestFailed ( request : network.Request , responseEndTiming : number , failureText : string | undefined , page : Page | null ) {
request . _failureText = failureText || null ;
if ( request . _timing )
request . _timing . responseEnd = responseEndTiming ;
this . emit ( Events . BrowserContext . RequestFailed , request ) ;
if ( page )
page . emit ( Events . Page . RequestFailed , request ) ;
}
private _onRequestFinished ( request : network.Request , responseEndTiming : number , page : Page | null ) {
if ( request . _timing )
request . _timing . responseEnd = responseEndTiming ;
this . emit ( Events . BrowserContext . RequestFinished , request ) ;
if ( page )
page . emit ( Events . Page . RequestFinished , request ) ;
}
2020-06-26 11:51:47 -07:00
_onRoute ( route : network.Route , request : network.Request ) {
for ( const { url , handler } of this . _routes ) {
2021-07-06 21:16:37 +02:00
if ( urlMatches ( this . _options . baseURL , request . url ( ) , url ) ) {
2020-06-26 11:51:47 -07:00
handler ( route , request ) ;
return ;
}
}
2021-03-09 04:53:19 +01:00
// it can race with BrowserContext.close() which then throws since its closed
route . continue ( ) . catch ( ( ) = > { } ) ;
2020-06-26 11:51:47 -07:00
}
async _onBinding ( bindingCall : BindingCall ) {
2020-06-26 12:28:27 -07:00
const func = this . _bindings . get ( bindingCall . _initializer . name ) ;
2020-06-26 11:51:47 -07:00
if ( ! func )
return ;
2021-03-22 09:59:39 -07:00
await bindingCall . call ( func ) ;
2020-06-25 16:05:36 -07:00
}
setDefaultNavigationTimeout ( timeout : number ) {
2020-07-13 16:03:24 -07:00
this . _timeoutSettings . setDefaultNavigationTimeout ( timeout ) ;
2020-06-25 16:05:36 -07:00
this . _channel . setDefaultNavigationTimeoutNoReply ( { timeout } ) ;
}
setDefaultTimeout ( timeout : number ) {
2020-06-26 21:22:03 -07:00
this . _timeoutSettings . setDefaultTimeout ( timeout ) ;
2020-06-25 16:05:36 -07:00
this . _channel . setDefaultTimeoutNoReply ( { timeout } ) ;
}
2020-09-14 16:50:47 +02:00
browser ( ) : Browser | null {
return this . _browser ;
}
2020-06-25 16:05:36 -07:00
pages ( ) : Page [ ] {
return [ . . . this . _pages ] ;
}
async newPage ( ) : Promise < Page > {
2021-06-28 13:27:38 -07:00
return this . _wrapApiCall ( async ( channel : channels.BrowserContextChannel ) = > {
2020-07-16 14:32:21 -07:00
if ( this . _ownerPage )
throw new Error ( 'Please use browser.newContext()' ) ;
2021-02-19 16:21:39 -08:00
return Page . from ( ( await channel . newPage ( ) ) . page ) ;
2020-07-16 14:32:21 -07:00
} ) ;
2020-06-25 16:05:36 -07:00
}
async cookies ( urls? : string | string [ ] ) : Promise < network.NetworkCookie [ ] > {
if ( ! urls )
urls = [ ] ;
if ( urls && typeof urls === 'string' )
urls = [ urls ] ;
2021-06-28 13:27:38 -07:00
return this . _wrapApiCall ( async ( channel : channels.BrowserContextChannel ) = > {
2021-02-19 16:21:39 -08:00
return ( await channel . cookies ( { urls : urls as string [ ] } ) ) . cookies ;
2020-07-16 14:32:21 -07:00
} ) ;
2020-06-25 16:05:36 -07:00
}
async addCookies ( cookies : network.SetNetworkCookieParam [ ] ) : Promise < void > {
2021-06-28 13:27:38 -07:00
return this . _wrapApiCall ( async ( channel : channels.BrowserContextChannel ) = > {
2021-02-19 16:21:39 -08:00
await channel . addCookies ( { cookies } ) ;
2020-07-16 14:32:21 -07:00
} ) ;
2020-06-25 16:05:36 -07:00
}
async clearCookies ( ) : Promise < void > {
2021-06-28 13:27:38 -07:00
return this . _wrapApiCall ( async ( channel : channels.BrowserContextChannel ) = > {
2021-02-19 16:21:39 -08:00
await channel . clearCookies ( ) ;
2020-07-16 14:32:21 -07:00
} ) ;
2020-06-25 16:05:36 -07:00
}
async grantPermissions ( permissions : string [ ] , options ? : { origin? : string } ) : Promise < void > {
2021-06-28 13:27:38 -07:00
return this . _wrapApiCall ( async ( channel : channels.BrowserContextChannel ) = > {
2021-02-19 16:21:39 -08:00
await channel . grantPermissions ( { permissions , . . . options } ) ;
2020-07-16 14:32:21 -07:00
} ) ;
2020-06-25 16:05:36 -07:00
}
async clearPermissions ( ) : Promise < void > {
2021-06-28 13:27:38 -07:00
return this . _wrapApiCall ( async ( channel : channels.BrowserContextChannel ) = > {
2021-02-19 16:21:39 -08:00
await channel . clearPermissions ( ) ;
2020-07-16 14:32:21 -07:00
} ) ;
2020-06-25 16:05:36 -07:00
}
2020-07-29 17:26:59 -07:00
async setGeolocation ( geolocation : { longitude : number , latitude : number , accuracy? : number } | null ) : Promise < void > {
2021-06-28 13:27:38 -07:00
return this . _wrapApiCall ( async ( channel : channels.BrowserContextChannel ) = > {
2021-02-19 16:21:39 -08:00
await channel . setGeolocation ( { geolocation : geolocation || undefined } ) ;
2020-07-16 14:32:21 -07:00
} ) ;
2020-06-25 16:05:36 -07:00
}
2020-07-29 17:26:59 -07:00
async setExtraHTTPHeaders ( headers : Headers ) : Promise < void > {
2021-06-28 13:27:38 -07:00
return this . _wrapApiCall ( async ( channel : channels.BrowserContextChannel ) = > {
2020-08-18 15:38:29 -07:00
network . validateHeaders ( headers ) ;
2021-02-19 16:21:39 -08:00
await channel . setExtraHTTPHeaders ( { headers : headersObjectToArray ( headers ) } ) ;
2020-07-16 14:32:21 -07:00
} ) ;
2020-06-25 16:05:36 -07:00
}
async setOffline ( offline : boolean ) : Promise < void > {
2021-06-28 13:27:38 -07:00
return this . _wrapApiCall ( async ( channel : channels.BrowserContextChannel ) = > {
2021-02-19 16:21:39 -08:00
await channel . setOffline ( { offline } ) ;
2020-07-16 14:32:21 -07:00
} ) ;
2020-06-25 16:05:36 -07:00
}
2020-07-29 17:26:59 -07:00
async setHTTPCredentials ( httpCredentials : { username : string , password : string } | null ) : Promise < void > {
2020-09-06 21:36:22 -07:00
if ( ! isUnderTest ( ) )
2020-08-17 16:19:21 -07:00
deprecate ( ` context.setHTTPCredentials ` , ` warning: method |context.setHTTPCredentials()| is deprecated. Instead of changing credentials, create another browser context with new credentials. ` ) ;
2021-06-28 13:27:38 -07:00
return this . _wrapApiCall ( async ( channel : channels.BrowserContextChannel ) = > {
2021-02-19 16:21:39 -08:00
await channel . setHTTPCredentials ( { httpCredentials : httpCredentials || undefined } ) ;
2020-07-16 14:32:21 -07:00
} ) ;
2020-06-25 16:05:36 -07:00
}
async addInitScript ( script : Function | string | { path? : string , content? : string } , arg? : any ) : Promise < void > {
2021-06-28 13:27:38 -07:00
return this . _wrapApiCall ( async ( channel : channels.BrowserContextChannel ) = > {
2020-08-19 13:27:58 -07:00
const source = await evaluationScript ( script , arg ) ;
2021-02-19 16:21:39 -08:00
await channel . addInitScript ( { source } ) ;
2020-07-16 14:32:21 -07:00
} ) ;
2020-06-25 16:05:36 -07:00
}
2021-01-04 13:50:29 -08:00
async exposeBinding ( name : string , callback : ( source : structs.BindingSource , . . . args : any [ ] ) = > any , options : { handle? : boolean } = { } ) : Promise < void > {
2021-06-28 13:27:38 -07:00
return this . _wrapApiCall ( async ( channel : channels.BrowserContextChannel ) = > {
2021-02-19 16:21:39 -08:00
await channel . exposeBinding ( { name , needsHandle : options.handle } ) ;
2021-01-04 13:50:29 -08:00
this . _bindings . set ( name , callback ) ;
2020-07-16 14:32:21 -07:00
} ) ;
2020-06-25 16:05:36 -07:00
}
2021-01-04 13:50:29 -08:00
async exposeFunction ( name : string , callback : Function ) : Promise < void > {
2021-06-28 13:27:38 -07:00
return this . _wrapApiCall ( async ( channel : channels.BrowserContextChannel ) = > {
2021-02-19 16:21:39 -08:00
await channel . exposeBinding ( { name } ) ;
2021-01-04 13:50:29 -08:00
const binding = ( source : structs.BindingSource , . . . args : any [ ] ) = > callback ( . . . args ) ;
2020-10-01 22:47:31 -07:00
this . _bindings . set ( name , binding ) ;
} ) ;
2020-06-25 16:05:36 -07:00
}
2020-07-29 17:26:59 -07:00
async route ( url : URLMatch , handler : network.RouteHandler ) : Promise < void > {
2021-06-28 13:27:38 -07:00
return this . _wrapApiCall ( async ( channel : channels.BrowserContextChannel ) = > {
2021-07-13 20:22:01 +02:00
this . _routes . unshift ( { url , handler } ) ;
2020-07-16 14:32:21 -07:00
if ( this . _routes . length === 1 )
2021-02-19 16:21:39 -08:00
await channel . setNetworkInterceptionEnabled ( { enabled : true } ) ;
2020-07-16 14:32:21 -07:00
} ) ;
2020-06-25 16:05:36 -07:00
}
2020-07-29 17:26:59 -07:00
async unroute ( url : URLMatch , handler? : network.RouteHandler ) : Promise < void > {
2021-06-28 13:27:38 -07:00
return this . _wrapApiCall ( async ( channel : channels.BrowserContextChannel ) = > {
2020-07-16 14:32:21 -07:00
this . _routes = this . _routes . filter ( route = > route . url !== url || ( handler && route . handler !== handler ) ) ;
if ( this . _routes . length === 0 )
2021-02-19 16:21:39 -08:00
await channel . setNetworkInterceptionEnabled ( { enabled : false } ) ;
2020-07-16 14:32:21 -07:00
} ) ;
2020-06-25 16:05:36 -07:00
}
2020-07-29 17:26:59 -07:00
async waitForEvent ( event : string , optionsOrPredicate : WaitForEventOptions = { } ) : Promise < any > {
2021-06-30 17:56:48 -07:00
return this . _wrapApiCall ( async ( channel : channels.BrowserContextChannel ) = > {
2021-06-28 13:27:38 -07:00
const timeout = this . _timeoutSettings . timeout ( typeof optionsOrPredicate === 'function' ? { } : optionsOrPredicate ) ;
const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate ;
2021-06-30 17:56:48 -07:00
const waiter = Waiter . createForEvent ( this , event ) ;
2021-06-28 13:27:38 -07:00
waiter . rejectOnTimeout ( timeout , ` Timeout while waiting for event " ${ event } " ` ) ;
if ( event !== Events . BrowserContext . Close )
waiter . rejectOnEvent ( this , Events . BrowserContext . Close , new Error ( 'Context closed' ) ) ;
const result = await waiter . waitForEvent ( this , event , predicate as any ) ;
waiter . dispose ( ) ;
return result ;
} ) ;
2020-06-25 16:05:36 -07:00
}
2020-12-14 16:03:52 -08:00
async storageState ( options : { path? : string } = { } ) : Promise < StorageState > {
2021-06-28 13:27:38 -07:00
return await this . _wrapApiCall ( async ( channel : channels.BrowserContextChannel ) = > {
2021-02-19 16:21:39 -08:00
const state = await channel . storageState ( ) ;
2020-12-14 16:03:52 -08:00
if ( options . path ) {
await mkdirIfNeeded ( options . path ) ;
2021-06-03 09:55:33 -07:00
await fs . promises . writeFile ( options . path , JSON . stringify ( state , undefined , 2 ) , 'utf8' ) ;
2020-12-14 16:03:52 -08:00
}
return state ;
2020-11-13 14:24:53 -08:00
} ) ;
}
2021-04-02 09:47:14 +08:00
backgroundPages ( ) : Page [ ] {
return [ . . . this . _backgroundPages ] ;
}
serviceWorkers ( ) : Worker [ ] {
return [ . . . this . _serviceWorkers ] ;
}
async newCDPSession ( page : Page ) : Promise < api.CDPSession > {
2021-06-28 13:27:38 -07:00
return this . _wrapApiCall ( async ( channel : channels.BrowserContextChannel ) = > {
2021-04-02 09:47:14 +08:00
const result = await channel . newCDPSession ( { page : page._channel } ) ;
return CDPSession . from ( result . session ) ;
} ) ;
}
2021-03-22 09:59:39 -07:00
_onClose() {
2020-06-26 17:24:21 -07:00
if ( this . _browser )
this . _browser . _contexts . delete ( this ) ;
2021-01-22 09:58:31 -08:00
this . emit ( Events . BrowserContext . Close , this ) ;
2020-06-25 16:05:36 -07:00
}
2020-06-30 10:55:11 -07:00
async close ( ) : Promise < void > {
2020-09-30 21:17:30 -07:00
try {
2021-06-28 13:27:38 -07:00
await this . _wrapApiCall ( async ( channel : channels.BrowserContextChannel ) = > {
2021-02-19 16:21:39 -08:00
await channel . close ( ) ;
2020-09-30 21:17:30 -07:00
await this . _closedPromise ;
} ) ;
} catch ( e ) {
if ( isSafeCloseError ( e ) )
return ;
throw e ;
}
2020-06-30 10:55:11 -07:00
}
2021-01-25 14:49:26 -08:00
2021-01-25 19:01:04 -08:00
async _enableRecorder ( params : {
language : string ,
launchOptions? : LaunchOptions ,
contextOptions? : BrowserContextOptions ,
device? : string ,
saveStorage? : string ,
2021-02-03 16:01:51 -08:00
startRecording? : boolean ,
2021-01-25 19:01:04 -08:00
outputFile? : string
} ) {
await this . _channel . recorderSupplementEnable ( params ) ;
2021-01-25 14:49:26 -08:00
}
2020-06-25 16:05:36 -07:00
}
2020-11-02 19:42:05 -08:00
2021-02-11 17:46:54 -08:00
export async function prepareBrowserContextParams ( options : BrowserContextOptions ) : Promise < channels.BrowserNewContextParams > {
2020-11-02 19:42:05 -08:00
if ( options . videoSize && ! options . videosPath )
throw new Error ( ` "videoSize" option requires "videosPath" to be specified ` ) ;
if ( options . extraHTTPHeaders )
network . validateHeaders ( options . extraHTTPHeaders ) ;
2021-02-11 17:46:54 -08:00
const contextParams : channels.BrowserNewContextParams = {
sdkLanguage : 'javascript' ,
2020-11-02 19:42:05 -08:00
. . . options ,
viewport : options.viewport === null ? undefined : options . viewport ,
noDefaultViewport : options.viewport === null ,
extraHTTPHeaders : options.extraHTTPHeaders ? headersObjectToArray ( options . extraHTTPHeaders ) : undefined ,
2021-06-03 09:55:33 -07:00
storageState : typeof options . storageState === 'string' ? JSON . parse ( await fs . promises . readFile ( options . storageState , 'utf8' ) ) : options . storageState ,
2020-11-02 19:42:05 -08:00
} ;
2021-02-11 17:46:54 -08:00
if ( ! contextParams . recordVideo && options . videosPath ) {
contextParams . recordVideo = {
2020-11-02 19:42:05 -08:00
dir : options.videosPath ,
size : options.videoSize
} ;
}
2021-02-11 17:46:54 -08:00
return contextParams ;
2020-11-02 19:42:05 -08:00
}