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' ;
2020-09-04 22:37:38 -07:00
import { TimeoutSettings } from '../utils/timeoutSettings' ;
2021-04-20 15:58:34 -07:00
import { debugMode , mkdirIfNeeded , createGuid } from '../utils/utils' ;
2020-09-18 17:36:43 -07:00
import { Browser , BrowserOptions } from './browser' ;
2020-09-04 22:37:38 -07:00
import { Download } from './download' ;
import * 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' ;
2020-11-12 12:41:23 -08:00
import { Page , PageBinding , PageDelegate } from './page' ;
2021-02-09 14:44:48 -08:00
import { Progress } from './progress' ;
2021-06-15 14:56:29 -07:00
import { Selectors } from './selectors' ;
2020-09-04 22:37:38 -07:00
import * as types from './types' ;
2021-02-11 06:36:15 -08:00
import path from 'path' ;
2021-04-23 18:34:52 -07:00
import { CallMetadata , internalCallMetadata , createInstrumentation , SdkObject } from './instrumentation' ;
import { Debugger } from './supplements/debugger' ;
2021-04-27 11:07:07 -07:00
import { Tracing } from './trace/recorder/tracing' ;
2021-08-24 21:09:41 -07:00
import { HarRecorder } from './supplements/har/harRecorder' ;
2021-04-23 18:34:52 -07:00
import { RecorderSupplement } from './supplements/recorderSupplement' ;
2021-04-23 20:39:09 -07:00
import * as consoleApiSource from '../generated/consoleApiSource' ;
2021-09-14 18:31:35 -07:00
import { BrowserContextFetchRequest } from './fetch' ;
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 = {
Close : 'close' ,
Page : 'page' ,
2021-05-13 10:29:14 -07:00
Request : 'request' ,
Response : 'response' ,
RequestFailed : 'requestfailed' ,
RequestFinished : 'requestfinished' ,
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 > ( ) ;
2020-08-17 14:12:31 -07:00
readonly _options : types.BrowserContextOptions ;
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 > ( ) ;
2021-08-25 13:32:56 -07:00
readonly _harRecorder : HarRecorder | undefined ;
2021-04-27 11:07:07 -07:00
readonly tracing : Tracing ;
2021-09-14 18:31:35 -07:00
readonly fetchRequest = new BrowserContextFetchRequest ( this ) ;
2020-03-05 17:22:57 -08:00
2020-08-25 16:33:16 -07:00
constructor ( browser : Browser , options : types.BrowserContextOptions , 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
if ( this . _options . recordHar )
2021-08-25 13:32:56 -07:00
this . _harRecorder = new HarRecorder ( this , { . . . this . _options . recordHar , path : path.join ( this . _browser . options . artifactsDir , ` ${ createGuid ( ) } .har ` ) } ) ;
2021-04-27 11:07:07 -07:00
this . tracing = new Tracing ( this ) ;
2020-03-05 17:22:57 -08:00
}
2020-09-02 16:15:43 -07:00
_setSelectors ( selectors : Selectors ) {
this . _selectors = selectors ;
}
2021-02-09 09:00:00 -08:00
selectors ( ) : Selectors {
2021-06-15 14:56:29 -07:00
return this . _selectors || this . _browser . options . selectors ;
2020-09-02 16:15:43 -07:00
}
2020-05-27 22:16:54 -07:00
async _initialize() {
2021-04-23 18:34:52 -07:00
if ( this . attribution . isInternal )
return ;
// Create instrumentation per context.
this . instrumentation = createInstrumentation ( ) ;
// Debugger will pause execution upon page.pause in headed mode.
const contextDebugger = new Debugger ( this ) ;
this . instrumentation . addListener ( contextDebugger ) ;
// When PWDEBUG=1, show inspector for each context.
if ( debugMode ( ) === 'inspector' )
await RecorderSupplement . show ( this , { pauseOnNextStatement : true } ) ;
// When paused, show inspector.
if ( contextDebugger . isPaused ( ) )
RecorderSupplement . showInspector ( this ) ;
contextDebugger . on ( Debugger . Events . PausedStateChanged , ( ) = > {
RecorderSupplement . showInspector ( this ) ;
} ) ;
2021-04-23 20:39:09 -07:00
if ( debugMode ( ) === 'console' )
2021-09-08 14:27:05 -07:00
await this . extendInjectedScript ( consoleApiSource . source ) ;
2020-08-28 10:51:55 -07:00
}
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
}
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 ;
}
this . _closedStatus = 'closed' ;
2021-04-08 18:56:09 -07:00
this . _deleteAllDownloads ( ) ;
2020-04-02 17:56:14 -07:00
this . _downloads . clear ( ) ;
2021-09-14 18:31:35 -07:00
this . fetchRequest . dispose ( ) ;
2021-06-14 21:55:55 -07:00
if ( this . _isPersistentContext )
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 > ;
2020-06-25 08:30:56 -07:00
abstract _doCookies ( urls : string [ ] ) : Promise < types.NetworkCookie [ ] > ;
abstract addCookies ( cookies : types.SetNetworkCookieParam [ ] ) : Promise < void > ;
2020-03-05 17:22:57 -08:00
abstract clearCookies ( ) : Promise < void > ;
2020-03-17 15:32:50 -07:00
abstract _doGrantPermissions ( origin : string , permissions : string [ ] ) : Promise < void > ;
abstract _doClearPermissions ( ) : Promise < void > ;
2020-08-17 16:19:21 -07:00
abstract setGeolocation ( geolocation? : types.Geolocation ) : Promise < void > ;
abstract _doSetHTTPCredentials ( httpCredentials? : types.Credentials ) : Promise < void > ;
2020-08-18 15:38:29 -07:00
abstract setExtraHTTPHeaders ( headers : types.HeadersArray ) : Promise < void > ;
2020-03-05 17:22:57 -08:00
abstract setOffline ( offline : boolean ) : Promise < void > ;
2020-06-25 08:30:56 -07:00
abstract _doAddInitScript ( expression : string ) : Promise < void > ;
2020-05-18 14:28:06 -07:00
abstract _doExposeBinding ( binding : PageBinding ) : Promise < void > ;
2020-08-18 17:34:04 -07:00
abstract _doUpdateRequestInterception ( ) : Promise < void > ;
2020-06-29 16:26:32 -07:00
abstract _doClose ( ) : Promise < void > ;
2021-06-14 21:55:55 -07:00
abstract _onClosePersistent ( ) : void ;
2021-06-12 21:23:22 +01:00
abstract _doCancelDownload ( uuid : string ) : Promise < void > ;
2020-03-05 17:22:57 -08:00
2020-06-25 08:30:56 -07:00
async cookies ( urls : string | string [ ] | undefined = [ ] ) : Promise < types.NetworkCookie [ ] > {
if ( urls && ! Array . isArray ( urls ) )
urls = [ urls ] ;
return await this . _doCookies ( urls as string [ ] ) ;
}
2020-08-17 16:19:21 -07:00
setHTTPCredentials ( httpCredentials? : types.Credentials ) : Promise < void > {
2020-06-30 11:02:12 -07:00
return this . _doSetHTTPCredentials ( httpCredentials ) ;
}
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 ) ;
2021-02-19 18:58:32 -08:00
await this . _doExposeBinding ( binding ) ;
2020-05-18 14:28:06 -07:00
}
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 ) ;
await this . _doGrantPermissions ( resolvedOrigin , list ) ;
2020-03-17 15:32:50 -07:00
}
async clearPermissions() {
this . _permissions . clear ( ) ;
await this . _doClearPermissions ( ) ;
}
2020-03-05 17:22:57 -08:00
setDefaultNavigationTimeout ( timeout : number ) {
this . _timeoutSettings . setDefaultNavigationTimeout ( timeout ) ;
}
setDefaultTimeout ( timeout : number ) {
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 ) ;
2020-05-21 15:13:16 -07:00
if ( this . _options . isMobile || this . _options . locale ) {
// 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
2020-08-18 17:34:04 -07:00
async _setRequestInterceptor ( handler : network.RouteHandler | undefined ) : Promise < void > {
this . _requestInterceptor = handler ;
await this . _doUpdateRequestInterception ( ) ;
}
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 ( ) ) ) ;
}
2021-02-19 09:33:24 -08:00
async close ( metadata : CallMetadata ) {
2020-07-08 21:36:03 -07:00
if ( this . _closedStatus === 'open' ) {
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
2021-08-24 21:09:41 -07:00
await this . _harRecorder ? . flush ( ) ;
2021-04-27 11:07:07 -07:00
await this . tracing . dispose ( ) ;
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 ( ) ) ;
}
2020-10-04 18:18:05 -07:00
if ( this . _isPersistentContext ) {
// Close all the pages instead of the context,
// because we cannot close the default context.
2021-02-19 09:33:24 -08:00
await Promise . all ( this . pages ( ) . map ( page = > page . close ( metadata ) ) ) ;
2020-10-04 18:18:05 -07:00
} else {
// Close the context.
await this . _doClose ( ) ;
}
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 ( ) ) ;
2020-09-18 17:36:43 -07:00
await Promise . all ( promises ) ;
2020-10-04 18:18:05 -07:00
// Persistent context should also close the browser.
if ( this . _isPersistentContext )
await this . _browser . close ( ) ;
// Bookkeeping.
2020-06-29 16:26:32 -07:00
this . _didCloseInternal ( ) ;
}
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 ( ) ;
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 ) ;
}
2021-02-09 14:44:48 -08:00
async storageState ( metadata : CallMetadata ) : Promise < types.StorageState > {
2020-11-13 14:24:53 -08:00
const result : types.StorageState = {
cookies : ( await this . cookies ( ) ) . filter ( c = > c . value !== '' ) ,
origins : [ ]
} ;
if ( this . _origins . size ) {
2021-02-19 09:33:24 -08:00
const internalMetadata = internalCallMetadata ( ) ;
const page = await this . newPage ( internalMetadata ) ;
2020-11-13 14:24:53 -08:00
await page . _setServerRequestInterceptor ( handler = > {
handler . fulfill ( { body : '<html></html>' } ) . catch ( ( ) = > { } ) ;
} ) ;
for ( const origin of this . _origins ) {
const originStorage : types.OriginStorage = { origin , localStorage : [ ] } ;
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 ) } ) ) ,
2021-07-09 16:19:42 +02:00
} ) ` , false, undefined, '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 ;
}
2021-02-09 14:44:48 -08:00
async setStorageState ( metadata : CallMetadata , state : types.SetStorageState ) {
2020-11-13 14:24:53 -08:00
if ( state . cookies )
await this . addCookies ( state . cookies ) ;
if ( state . origins && state . origins . length ) {
2021-02-19 09:33:24 -08:00
const internalMetadata = internalCallMetadata ( ) ;
const page = await this . newPage ( internalMetadata ) ;
2020-11-13 14:24:53 -08:00
await page . _setServerRequestInterceptor ( handler = > {
handler . fulfill ( { body : '<html></html>' } ) . catch ( ( ) = > { } ) ;
} ) ;
for ( const originState of state . origins ) {
const frame = page . mainFrame ( ) ;
2021-02-09 14:44:48 -08:00
await frame . goto ( metadata , originState . origin ) ;
2021-07-09 16:19:42 +02:00
await frame . evaluateExpression ( `
originState = > {
for ( const { name , value } of ( originState . localStorage || [ ] ) )
localStorage . setItem ( name , value ) ;
} ` , true, originState, 'utility');
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
}
}
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 ) ) ;
}
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
2020-09-18 17:36:43 -07:00
export function validateBrowserContextOptions ( options : types.BrowserContextOptions , 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" ` ) ;
2020-08-18 09:37:40 -07:00
if ( options . noDefaultViewport && options . isMobile !== undefined )
2020-05-12 18:31:17 -07:00
throw new Error ( ` "isMobile" option is not supported with null "viewport" ` ) ;
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 ) ;
}
2021-04-20 15:58:34 -07:00
if ( debugMode ( ) === 'inspector' )
2021-02-26 14:16:32 -08:00
options . bypassCSP = true ;
2020-08-18 09:37:40 -07:00
verifyGeolocation ( options . geolocation ) ;
2021-04-08 22:59:05 +08:00
if ( ! options . _debugName )
options . _debugName = createGuid ( ) ;
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 } ;
}