2019-12-19 16:53:24 -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 .
* /
2020-06-25 08:30:56 -07:00
import { BrowserBase , BrowserOptions , BrowserContextOptions } from '../browser' ;
import { assertBrowserContextIsNotOwned , BrowserContext , BrowserContextBase , validateBrowserContextOptions , verifyGeolocation } from '../browserContext' ;
2020-03-05 17:22:57 -08:00
import { Events } from '../events' ;
2020-06-29 16:37:01 -07:00
import { helper , deprecate , RegisteredListener , assert } from '../helper' ;
2019-12-19 16:53:24 -08:00
import * as network from '../network' ;
2020-03-19 16:25:12 -07:00
import { Page , PageBinding } from '../page' ;
2020-02-04 19:41:38 -08:00
import { ConnectionTransport , SlowMoTransport } from '../transport' ;
2020-01-07 10:39:01 -08:00
import * as types from '../types' ;
import { Protocol } from './protocol' ;
2020-03-05 17:22:57 -08:00
import { kPageProxyMessageReceived , PageProxyMessageReceivedPayload , WKConnection , WKSession } from './wkConnection' ;
2020-02-26 12:42:20 -08:00
import { WKPage } from './wkPage' ;
2019-12-19 16:53:24 -08:00
2020-05-14 16:47:18 -07:00
const DEFAULT_USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.2 Safari/605.1.15' ;
2020-01-29 20:12:09 -08:00
2020-04-02 17:56:14 -07:00
export class WKBrowser extends BrowserBase {
2020-01-09 15:14:35 -08:00
private readonly _connection : WKConnection ;
2020-02-24 08:53:30 -08:00
readonly _browserSession : WKSession ;
readonly _contexts = new Map < string , WKBrowserContext > ( ) ;
2020-03-06 11:41:46 -08:00
readonly _wkPages = new Map < string , WKPage > ( ) ;
2020-01-04 10:12:40 -08:00
private readonly _eventListeners : RegisteredListener [ ] ;
2020-05-14 13:22:33 -07:00
static async connect ( transport : ConnectionTransport , options : BrowserOptions ) : Promise < WKBrowser > {
const browser = new WKBrowser ( SlowMoTransport . wrap ( transport , options . slowMo ) , options ) ;
2020-05-21 15:13:16 -07:00
const promises : Promise < any > [ ] = [
browser . _browserSession . send ( 'Playwright.enable' ) ,
] ;
if ( options . persistent ) {
browser . _defaultContext = new WKBrowserContext ( browser , undefined , options . persistent ) ;
promises . push ( ( browser . _defaultContext as WKBrowserContext ) . _initialize ( ) ) ;
}
await Promise . all ( promises ) ;
2020-01-07 16:15:07 -08:00
return browser ;
}
2020-05-14 13:22:33 -07:00
constructor ( transport : ConnectionTransport , options : BrowserOptions ) {
super ( options ) ;
2020-06-16 17:11:19 -07:00
this . _connection = new WKConnection ( transport , options . loggers , this . _onDisconnect . bind ( this ) ) ;
2020-01-09 15:14:35 -08:00
this . _browserSession = this . _connection . browserSession ;
2019-12-19 16:53:24 -08:00
this . _eventListeners = [
2020-03-11 21:08:22 +00:00
helper . addEventListener ( this . _browserSession , 'Playwright.pageProxyCreated' , this . _onPageProxyCreated . bind ( this ) ) ,
helper . addEventListener ( this . _browserSession , 'Playwright.pageProxyDestroyed' , this . _onPageProxyDestroyed . bind ( this ) ) ,
helper . addEventListener ( this . _browserSession , 'Playwright.provisionalLoadFailed' , event = > this . _onProvisionalLoadFailed ( event ) ) ,
2020-05-22 15:56:37 -07:00
helper . addEventListener ( this . _browserSession , 'Playwright.windowOpen' , event = > this . _onWindowOpen ( event ) ) ,
2020-04-02 17:56:14 -07:00
helper . addEventListener ( this . _browserSession , 'Playwright.downloadCreated' , this . _onDownloadCreated . bind ( this ) ) ,
2020-05-12 19:23:08 -07:00
helper . addEventListener ( this . _browserSession , 'Playwright.downloadFilenameSuggested' , this . _onDownloadFilenameSuggested . bind ( this ) ) ,
2020-04-02 17:56:14 -07:00
helper . addEventListener ( this . _browserSession , 'Playwright.downloadFinished' , this . _onDownloadFinished . bind ( this ) ) ,
2020-01-09 15:14:35 -08:00
helper . addEventListener ( this . _browserSession , kPageProxyMessageReceived , this . _onPageProxyMessageReceived . bind ( this ) ) ,
2019-12-19 16:53:24 -08:00
] ;
}
2020-01-09 15:14:35 -08:00
_onDisconnect() {
2020-03-06 11:41:46 -08:00
for ( const wkPage of this . _wkPages . values ( ) )
wkPage . dispose ( ) ;
2020-06-29 16:26:32 -07:00
this . _didClose ( ) ;
2020-01-09 15:14:35 -08:00
}
2019-12-19 16:53:24 -08:00
async newContext ( options : BrowserContextOptions = { } ) : Promise < BrowserContext > {
2020-02-24 08:53:30 -08:00
options = validateBrowserContextOptions ( options ) ;
2020-03-11 21:08:22 +00:00
const { browserContextId } = await this . _browserSession . send ( 'Playwright.createContext' ) ;
2020-01-29 20:12:09 -08:00
options . userAgent = options . userAgent || DEFAULT_USER_AGENT ;
2020-02-24 08:53:30 -08:00
const context = new WKBrowserContext ( this , browserContextId , options ) ;
2020-01-13 13:32:44 -08:00
await context . _initialize ( ) ;
2019-12-19 16:53:24 -08:00
this . _contexts . set ( browserContextId , context ) ;
return context ;
}
2020-02-10 10:41:45 -08:00
contexts ( ) : BrowserContext [ ] {
2020-02-05 12:41:55 -08:00
return Array . from ( this . _contexts . values ( ) ) ;
2019-12-19 16:53:24 -08:00
}
2020-04-02 17:56:14 -07:00
_onDownloadCreated ( payload : Protocol.Playwright.downloadCreatedPayload ) {
const page = this . _wkPages . get ( payload . pageProxyId ) ;
if ( ! page )
return ;
2020-04-23 20:04:19 -07:00
const frameManager = page . _page . _frameManager ;
const frame = frameManager . frame ( payload . frameId ) ;
if ( frame ) {
// In some cases, e.g. blob url download, we receive only frameScheduledNavigation
// but no signals that the navigation was canceled and replaced by download. Fix it
// here by simulating cancelled provisional load which matches downloads from network.
frameManager . provisionalLoadFailed ( frame , '' , 'Download is starting' ) ;
}
2020-04-29 18:36:24 -07:00
let originPage = page . _initializedPage ;
// If it's a new window download, report it on the opener page.
if ( ! originPage ) {
// Resume the page creation with an error. The page will automatically close right
// after the download begins.
page . _firstNonInitialNavigationCommittedReject ( new Error ( 'Starting new page download' ) ) ;
if ( page . _opener )
originPage = page . _opener . _initializedPage ;
}
if ( ! originPage )
return ;
this . _downloadCreated ( originPage , payload . uuid , payload . url ) ;
2020-04-02 17:56:14 -07:00
}
2020-05-12 19:23:08 -07:00
_onDownloadFilenameSuggested ( payload : Protocol.Playwright.downloadFilenameSuggestedPayload ) {
this . _downloadFilenameSuggested ( payload . uuid , payload . suggestedFilename ) ;
}
2020-04-02 17:56:14 -07:00
_onDownloadFinished ( payload : Protocol.Playwright.downloadFinishedPayload ) {
this . _downloadFinished ( payload . uuid , payload . error ) ;
}
2020-03-11 21:08:22 +00:00
_onPageProxyCreated ( event : Protocol.Playwright.pageProxyCreatedPayload ) {
2020-06-10 13:36:45 -07:00
const pageProxyId = event . pageProxyId ;
2020-03-02 18:32:56 -08:00
let context : WKBrowserContext | null = null ;
2020-06-10 13:36:45 -07:00
if ( event . browserContextId ) {
2019-12-19 16:53:24 -08:00
// FIXME: we don't know about the default context id, so assume that all targets from
// unknown contexts are created in the 'default' context which can in practice be represented
// by multiple actual contexts in WebKit. Solving this properly will require adding context
// lifecycle events.
2020-06-10 13:36:45 -07:00
context = this . _contexts . get ( event . browserContextId ) || null ;
2019-12-19 16:53:24 -08:00
}
if ( ! context )
2020-05-20 16:30:04 -07:00
context = this . _defaultContext as WKBrowserContext ;
2020-04-18 19:58:11 -07:00
if ( ! context )
return ;
2020-01-09 15:14:35 -08:00
const pageProxySession = new WKSession ( this . _connection , pageProxyId , ` The page has been closed. ` , ( message : any ) = > {
this . _connection . rawSend ( { . . . message , pageProxyId } ) ;
} ) ;
2020-06-10 13:36:45 -07:00
const opener = event . openerId ? this . _wkPages . get ( event . openerId ) : undefined ;
2020-04-14 14:51:23 -07:00
const wkPage = new WKPage ( context , pageProxySession , opener || null ) ;
2020-03-06 11:41:46 -08:00
this . _wkPages . set ( pageProxyId , wkPage ) ;
2019-12-19 16:53:24 -08:00
2020-03-13 11:33:33 -07:00
wkPage . pageOrError ( ) . then ( async ( ) = > {
2020-03-19 16:25:12 -07:00
const page = wkPage . _page ;
context ! . emit ( Events . BrowserContext . Page , page ) ;
2020-03-13 11:33:33 -07:00
if ( ! opener )
return ;
2020-03-19 16:25:12 -07:00
await opener . pageOrError ( ) ;
const openerPage = opener . _page ;
if ( ! openerPage . isClosed ( ) )
openerPage . emit ( Events . Page . Popup , page ) ;
2020-03-05 15:18:27 -08:00
} ) ;
2019-12-19 16:53:24 -08:00
}
2020-03-11 21:08:22 +00:00
_onPageProxyDestroyed ( event : Protocol.Playwright.pageProxyDestroyedPayload ) {
2020-01-09 15:14:35 -08:00
const pageProxyId = event . pageProxyId ;
2020-03-06 11:41:46 -08:00
const wkPage = this . _wkPages . get ( pageProxyId ) ;
if ( ! wkPage )
2020-02-27 08:49:09 -08:00
return ;
2020-03-09 12:32:42 -07:00
wkPage . didClose ( ) ;
2020-03-06 11:41:46 -08:00
wkPage . dispose ( ) ;
this . _wkPages . delete ( pageProxyId ) ;
2019-12-19 16:53:24 -08:00
}
2020-01-09 15:14:35 -08:00
_onPageProxyMessageReceived ( event : PageProxyMessageReceivedPayload ) {
2020-03-06 11:41:46 -08:00
const wkPage = this . _wkPages . get ( event . pageProxyId ) ;
if ( ! wkPage )
2020-02-27 08:49:09 -08:00
return ;
2020-03-06 11:41:46 -08:00
wkPage . dispatchMessageToSession ( event . message ) ;
2020-01-09 15:14:35 -08:00
}
2020-03-11 21:08:22 +00:00
_onProvisionalLoadFailed ( event : Protocol.Playwright.provisionalLoadFailedPayload ) {
2020-03-06 11:41:46 -08:00
const wkPage = this . _wkPages . get ( event . pageProxyId ) ;
if ( ! wkPage )
2020-02-27 08:49:09 -08:00
return ;
2020-03-06 11:41:46 -08:00
wkPage . handleProvisionalLoadFailed ( event ) ;
2020-01-14 11:46:08 -08:00
}
2020-05-22 15:56:37 -07:00
_onWindowOpen ( event : Protocol.Playwright.windowOpenPayload ) {
const wkPage = this . _wkPages . get ( event . pageProxyId ) ;
if ( ! wkPage )
return ;
wkPage . handleWindowOpen ( event ) ;
}
2019-12-19 16:53:24 -08:00
isConnected ( ) : boolean {
2020-01-22 17:42:10 -08:00
return ! this . _connection . isClosed ( ) ;
2019-12-19 16:53:24 -08:00
}
2020-04-03 16:34:07 -07:00
_disconnect() {
2019-12-19 16:53:24 -08:00
helper . removeEventListeners ( this . _eventListeners ) ;
2020-02-06 12:41:43 -08:00
this . _connection . close ( ) ;
2020-04-02 16:57:12 -07:00
}
2019-12-19 16:53:24 -08:00
}
2020-02-24 08:53:30 -08:00
2020-03-05 17:22:57 -08:00
export class WKBrowserContext extends BrowserContextBase {
2020-02-24 08:53:30 -08:00
readonly _browser : WKBrowser ;
readonly _browserContextId : string | undefined ;
2020-02-27 16:18:33 -08:00
readonly _evaluateOnNewDocumentSources : string [ ] ;
2020-02-24 08:53:30 -08:00
2020-06-25 08:30:56 -07:00
constructor ( browser : WKBrowser , browserContextId : string | undefined , options : types.BrowserContextOptions ) {
2020-06-29 16:26:32 -07:00
super ( browser , options , ! browserContextId ) ;
2020-02-24 08:53:30 -08:00
this . _browser = browser ;
this . _browserContextId = browserContextId ;
2020-02-27 16:18:33 -08:00
this . _evaluateOnNewDocumentSources = [ ] ;
2020-06-05 13:50:15 -07:00
this . _authenticateProxyViaHeader ( ) ;
2020-02-24 08:53:30 -08:00
}
async _initialize() {
2020-05-21 15:13:16 -07:00
assert ( ! this . _wkPages ( ) . length ) ;
2020-04-02 17:56:14 -07:00
const browserContextId = this . _browserContextId ;
2020-05-27 22:16:54 -07:00
const promises : Promise < any > [ ] = [ super . _initialize ( ) ] ;
2020-05-21 15:13:16 -07:00
if ( this . _browser . _options . downloadsPath ) {
promises . push ( this . _browser . _browserSession . send ( 'Playwright.setDownloadBehavior' , {
2020-04-02 17:56:14 -07:00
behavior : this._options.acceptDownloads ? 'allow' : 'deny' ,
2020-05-14 13:22:33 -07:00
downloadPath : this._browser._options.downloadsPath ,
2020-04-02 17:56:14 -07:00
browserContextId
2020-05-21 15:13:16 -07:00
} ) ) ;
}
2020-02-24 08:53:30 -08:00
if ( this . _options . ignoreHTTPSErrors )
2020-04-02 17:56:14 -07:00
promises . push ( this . _browser . _browserSession . send ( 'Playwright.setIgnoreCertificateErrors' , { browserContextId , ignore : true } ) ) ;
2020-02-24 08:53:30 -08:00
if ( this . _options . locale )
2020-04-02 17:56:14 -07:00
promises . push ( this . _browser . _browserSession . send ( 'Playwright.setLanguages' , { browserContextId , languages : [ this . _options . locale ] } ) ) ;
2020-03-17 15:32:50 -07:00
if ( this . _options . permissions )
2020-04-02 17:56:14 -07:00
promises . push ( this . grantPermissions ( this . _options . permissions ) ) ;
2020-02-24 08:53:30 -08:00
if ( this . _options . geolocation )
2020-04-02 17:56:14 -07:00
promises . push ( this . setGeolocation ( this . _options . geolocation ) ) ;
2020-03-04 17:58:12 -08:00
if ( this . _options . offline )
2020-04-02 17:56:14 -07:00
promises . push ( this . setOffline ( this . _options . offline ) ) ;
2020-03-06 13:50:42 -08:00
if ( this . _options . httpCredentials )
2020-04-02 17:56:14 -07:00
promises . push ( this . setHTTPCredentials ( this . _options . httpCredentials ) ) ;
await Promise . all ( promises ) ;
2020-02-24 08:53:30 -08:00
}
2020-03-09 12:32:42 -07:00
_wkPages ( ) : WKPage [ ] {
return Array . from ( this . _browser . _wkPages . values ( ) ) . filter ( wkPage = > wkPage . _browserContext === this ) ;
}
2020-03-13 11:33:33 -07:00
pages ( ) : Page [ ] {
2020-04-16 13:09:24 -07:00
return this . _wkPages ( ) . map ( wkPage = > wkPage . _initializedPage ) . filter ( pageOrNull = > ! ! pageOrNull ) as Page [ ] ;
2020-02-24 08:53:30 -08:00
}
async newPage ( ) : Promise < Page > {
assertBrowserContextIsNotOwned ( this ) ;
2020-03-11 21:08:22 +00:00
const { pageProxyId } = await this . _browser . _browserSession . send ( 'Playwright.createPage' , { browserContextId : this._browserContextId } ) ;
2020-03-06 11:41:46 -08:00
const wkPage = this . _browser . _wkPages . get ( pageProxyId ) ! ;
const result = await wkPage . pageOrError ( ) ;
2020-03-05 15:18:27 -08:00
if ( result instanceof Page ) {
if ( result . isClosed ( ) )
throw new Error ( 'Page has been closed.' ) ;
return result ;
}
throw result ;
2020-02-24 08:53:30 -08:00
}
2020-06-25 08:30:56 -07:00
async _doCookies ( urls : string [ ] ) : Promise < types.NetworkCookie [ ] > {
2020-03-11 21:08:22 +00:00
const { cookies } = await this . _browser . _browserSession . send ( 'Playwright.getAllCookies' , { browserContextId : this._browserContextId } ) ;
2020-06-25 08:30:56 -07:00
return network . filterCookies ( cookies . map ( ( c : types.NetworkCookie ) = > {
2020-03-07 08:41:57 -08:00
const copy : any = { . . . c } ;
2020-03-11 21:08:22 +00:00
copy . expires = c . expires === - 1 ? - 1 : c.expires / 1000 ;
2020-03-07 08:41:57 -08:00
delete copy . session ;
2020-06-25 08:30:56 -07:00
return copy as types . NetworkCookie ;
2020-03-07 08:41:57 -08:00
} ) , urls ) ;
2020-02-24 08:53:30 -08:00
}
2020-06-25 08:30:56 -07:00
async addCookies ( cookies : types.SetNetworkCookieParam [ ] ) {
2020-03-07 08:41:57 -08:00
const cc = network . rewriteCookies ( cookies ) . map ( c = > ( {
. . . c ,
session : c.expires === - 1 || c . expires === undefined ,
expires : c.expires && c . expires !== - 1 ? c . expires * 1000 : c.expires
2020-03-11 21:08:22 +00:00
} ) ) as Protocol . Playwright . SetCookieParam [ ] ;
await this . _browser . _browserSession . send ( 'Playwright.setCookies' , { cookies : cc , browserContextId : this._browserContextId } ) ;
2020-02-24 08:53:30 -08:00
}
async clearCookies() {
2020-03-11 21:08:22 +00:00
await this . _browser . _browserSession . send ( 'Playwright.deleteAllCookies' , { browserContextId : this._browserContextId } ) ;
2020-02-24 08:53:30 -08:00
}
2020-03-17 15:32:50 -07:00
async _doGrantPermissions ( origin : string , permissions : string [ ] ) {
2020-03-20 19:45:35 -07:00
await Promise . all ( this . pages ( ) . map ( page = > ( page . _delegate as WKPage ) . _grantPermissions ( origin , permissions ) ) ) ;
2020-02-24 08:53:30 -08:00
}
2020-03-17 15:32:50 -07:00
async _doClearPermissions() {
2020-03-20 19:45:35 -07:00
await Promise . all ( this . pages ( ) . map ( page = > ( page . _delegate as WKPage ) . _clearPermissions ( ) ) ) ;
2020-02-24 08:53:30 -08:00
}
async setGeolocation ( geolocation : types.Geolocation | null ) : Promise < void > {
if ( geolocation )
geolocation = verifyGeolocation ( geolocation ) ;
this . _options . geolocation = geolocation || undefined ;
const payload : any = geolocation ? { . . . geolocation , timestamp : Date.now ( ) } : undefined ;
2020-03-11 21:08:22 +00:00
await this . _browser . _browserSession . send ( 'Playwright.setGeolocationOverride' , { browserContextId : this._browserContextId , geolocation : payload } ) ;
2020-02-24 08:53:30 -08:00
}
2020-06-25 08:30:56 -07:00
async setExtraHTTPHeaders ( headers : types.Headers ) : Promise < void > {
2020-02-26 12:42:20 -08:00
this . _options . extraHTTPHeaders = network . verifyHeaders ( headers ) ;
2020-03-13 11:33:33 -07:00
for ( const page of this . pages ( ) )
2020-02-26 12:42:20 -08:00
await ( page . _delegate as WKPage ) . updateExtraHTTPHeaders ( ) ;
}
2020-03-04 17:58:12 -08:00
async setOffline ( offline : boolean ) : Promise < void > {
this . _options . offline = offline ;
2020-03-13 11:33:33 -07:00
for ( const page of this . pages ( ) )
2020-03-04 17:58:12 -08:00
await ( page . _delegate as WKPage ) . updateOffline ( ) ;
}
2020-03-06 13:50:42 -08:00
async setHTTPCredentials ( httpCredentials : types.Credentials | null ) : Promise < void > {
2020-06-29 16:37:01 -07:00
deprecate ( ` context.setHTTPCredentials ` , ` warning: method |context.setHTTPCredentials()| is deprecated. Instead of changing credentials, create another browser context with new credentials. ` ) ;
2020-03-06 13:50:42 -08:00
this . _options . httpCredentials = httpCredentials || undefined ;
2020-03-13 11:33:33 -07:00
for ( const page of this . pages ( ) )
2020-03-06 13:50:42 -08:00
await ( page . _delegate as WKPage ) . updateHttpCredentials ( ) ;
}
2020-06-25 08:30:56 -07:00
async _doAddInitScript ( source : string ) {
2020-02-27 16:18:33 -08:00
this . _evaluateOnNewDocumentSources . push ( source ) ;
2020-03-13 11:33:33 -07:00
for ( const page of this . pages ( ) )
2020-02-27 16:18:33 -08:00
await ( page . _delegate as WKPage ) . _updateBootstrapScript ( ) ;
}
2020-05-18 14:28:06 -07:00
async _doExposeBinding ( binding : PageBinding ) {
2020-03-13 11:33:33 -07:00
for ( const page of this . pages ( ) )
2020-03-03 16:46:06 -08:00
await ( page . _delegate as WKPage ) . exposeBinding ( binding ) ;
}
2020-03-09 21:02:54 -07:00
async route ( url : types.URLMatch , handler : network.RouteHandler ) : Promise < void > {
this . _routes . push ( { url , handler } ) ;
2020-03-13 11:33:33 -07:00
for ( const page of this . pages ( ) )
2020-03-09 21:02:54 -07:00
await ( page . _delegate as WKPage ) . updateRequestInterception ( ) ;
}
2020-04-15 19:55:22 -07:00
async unroute ( url : types.URLMatch , handler? : network.RouteHandler ) : Promise < void > {
this . _routes = this . _routes . filter ( route = > route . url !== url || ( handler && route . handler !== handler ) ) ;
for ( const page of this . pages ( ) )
await ( page . _delegate as WKPage ) . updateRequestInterception ( ) ;
}
2020-06-29 16:26:32 -07:00
async _doClose() {
assert ( this . _browserContextId ) ;
2020-03-11 21:08:22 +00:00
await this . _browser . _browserSession . send ( 'Playwright.deleteContext' , { browserContextId : this._browserContextId } ) ;
2020-02-24 08:53:30 -08:00
this . _browser . _contexts . delete ( this . _browserContextId ) ;
}
}