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