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-09-04 22:37:38 -07:00
import * as jpeg from 'jpeg-js' ;
2021-02-11 06:36:15 -08:00
import path from 'path' ;
2020-09-04 22:37:38 -07:00
import * as png from 'pngjs' ;
2021-04-19 22:37:38 +02:00
import { splitErrorMessage } from '../../utils/stackTrace' ;
2021-05-06 10:38:46 -07:00
import { hostPlatform } from '../../utils/registry' ;
2020-10-28 13:46:05 -07:00
import { assert , createGuid , debugAssert , headersArrayToObject , headersObjectToArray } from '../../utils/utils' ;
2020-09-04 22:37:38 -07:00
import * as accessibility from '../accessibility' ;
import * as dialog from '../dialog' ;
import * as dom from '../dom' ;
2020-08-24 06:51:51 -07:00
import * as frames from '../frames' ;
2021-07-07 21:14:16 +02:00
import { eventsHelper , RegisteredListener } from '../../utils/eventsHelper' ;
import { helper } from '../helper' ;
2021-06-02 20:17:24 -07:00
import { JSHandle , kSwappedOutErrorMessage } from '../javascript' ;
2020-08-24 06:51:51 -07:00
import * as network from '../network' ;
2020-09-04 22:37:38 -07:00
import { Page , PageBinding , PageDelegate } from '../page' ;
2021-04-07 07:01:38 +08:00
import { Progress } from '../progress' ;
2020-09-04 22:37:38 -07:00
import * as types from '../types' ;
import { Protocol } from './protocol' ;
import { getAccessibilityTree } from './wkAccessibility' ;
import { WKBrowserContext } from './wkBrowser' ;
2020-01-09 15:14:35 -08:00
import { WKSession } from './wkConnection' ;
2020-01-29 19:13:44 -08:00
import { WKExecutionContext } from './wkExecutionContext' ;
2020-10-19 10:07:33 -07:00
import { RawKeyboardImpl , RawMouseImpl , RawTouchscreenImpl } from './wkInput' ;
2020-01-21 14:58:12 -08:00
import { WKInterceptableRequest } from './wkInterceptableRequest' ;
2020-01-22 14:17:44 -08:00
import { WKProvisionalPage } from './wkProvisionalPage' ;
2020-09-04 22:37:38 -07:00
import { WKWorkers } from './wkWorkers' ;
2021-05-08 11:35:36 -07:00
import { debugLogger } from '../../utils/debugLogger' ;
2019-12-19 16:53:24 -08:00
const UTILITY_WORLD_NAME = '__playwright_utility_world__' ;
const BINDING_CALL_MESSAGE = '__playwright_binding_call__' ;
2019-12-23 11:39:57 -08:00
export class WKPage implements PageDelegate {
2019-12-19 16:53:24 -08:00
readonly rawMouse : RawMouseImpl ;
readonly rawKeyboard : RawKeyboardImpl ;
2020-10-19 10:07:33 -07:00
readonly rawTouchscreen : RawTouchscreenImpl ;
2020-01-09 11:02:55 -08:00
_session : WKSession ;
2020-01-22 14:17:44 -08:00
private _provisionalPage : WKProvisionalPage | null = null ;
2019-12-19 16:53:24 -08:00
readonly _page : Page ;
2020-03-06 11:41:46 -08:00
private readonly _pagePromise : Promise < Page | Error > ;
private _pagePromiseCallback : ( page : Page | Error ) = > void = ( ) = > { } ;
2020-01-09 15:14:35 -08:00
private readonly _pageProxySession : WKSession ;
2020-04-29 18:36:24 -07:00
readonly _opener : WKPage | null ;
2020-01-21 14:58:12 -08:00
private readonly _requestIdToRequest = new Map < string , WKInterceptableRequest > ( ) ;
2020-01-07 17:16:27 -08:00
private readonly _workers : WKWorkers ;
private readonly _contextIdToContext : Map < number , dom.FrameExecutionContext > ;
2020-01-27 16:51:52 -08:00
private _mainFrameContextId? : number ;
2019-12-19 16:53:24 -08:00
private _sessionListeners : RegisteredListener [ ] = [ ] ;
2020-03-06 11:41:46 -08:00
private _eventListeners : RegisteredListener [ ] ;
readonly _browserContext : WKBrowserContext ;
2020-04-16 13:09:24 -07:00
_initializedPage : Page | null = null ;
2020-03-18 17:14:18 -07:00
private _firstNonInitialNavigationCommittedPromise : Promise < void > ;
2020-04-16 13:09:24 -07:00
private _firstNonInitialNavigationCommittedFulfill = ( ) = > { } ;
2020-04-29 18:36:24 -07:00
_firstNonInitialNavigationCommittedReject = ( e : Error ) = > { } ;
2020-06-25 18:01:18 -07:00
private _lastConsoleMessage : { derivedType : string , text : string , handles : JSHandle [ ] ; count : number , location : types.ConsoleMessageLocation ; } | null = null ;
2019-12-19 16:53:24 -08:00
2021-06-15 00:48:08 -07:00
private readonly _requestIdToResponseReceivedPayloadEvent = new Map < string , Protocol.Network.responseReceivedPayload > ( ) ;
2021-06-18 11:04:48 -07:00
_needsResponseInterception : boolean = false ;
2020-05-22 15:56:37 -07:00
// Holds window features for the next popup being opened via window.open,
// until the popup page proxy arrives.
private _nextWindowOpenPopupFeatures? : string [ ] ;
2020-08-19 12:45:31 -07:00
private _recordingVideoFile : string | null = null ;
2021-05-08 11:35:36 -07:00
private _screencastGeneration : number = 0 ;
2020-05-22 15:56:37 -07:00
2020-04-14 14:51:23 -07:00
constructor ( browserContext : WKBrowserContext , pageProxySession : WKSession , opener : WKPage | null ) {
2020-01-07 17:16:27 -08:00
this . _pageProxySession = pageProxySession ;
2020-02-27 08:49:09 -08:00
this . _opener = opener ;
2020-01-07 17:16:27 -08:00
this . rawKeyboard = new RawKeyboardImpl ( pageProxySession ) ;
this . rawMouse = new RawMouseImpl ( pageProxySession ) ;
2020-10-19 10:07:33 -07:00
this . rawTouchscreen = new RawTouchscreenImpl ( pageProxySession ) ;
2019-12-19 16:53:24 -08:00
this . _contextIdToContext = new Map ( ) ;
this . _page = new Page ( this , browserContext ) ;
2020-01-07 12:59:01 -08:00
this . _workers = new WKWorkers ( this . _page ) ;
2020-01-13 13:33:25 -08:00
this . _session = undefined as any as WKSession ;
2020-02-27 16:18:33 -08:00
this . _browserContext = browserContext ;
2020-08-21 16:26:33 -07:00
this . _page . on ( Page . Events . FrameDetached , ( frame : frames.Frame ) = > this . _removeContextsForFrame ( frame , false ) ) ;
2020-03-06 11:41:46 -08:00
this . _eventListeners = [
2021-07-07 21:14:16 +02:00
eventsHelper . addEventListener ( this . _pageProxySession , 'Target.targetCreated' , this . _onTargetCreated . bind ( this ) ) ,
eventsHelper . addEventListener ( this . _pageProxySession , 'Target.targetDestroyed' , this . _onTargetDestroyed . bind ( this ) ) ,
eventsHelper . addEventListener ( this . _pageProxySession , 'Target.dispatchMessageFromTarget' , this . _onDispatchMessageFromTarget . bind ( this ) ) ,
eventsHelper . addEventListener ( this . _pageProxySession , 'Target.didCommitProvisionalTarget' , this . _onDidCommitProvisionalTarget . bind ( this ) ) ,
eventsHelper . addEventListener ( this . _pageProxySession , 'Screencast.screencastFrame' , this . _onScreencastFrame . bind ( this ) ) ,
2020-03-06 11:41:46 -08:00
] ;
this . _pagePromise = new Promise ( f = > this . _pagePromiseCallback = f ) ;
2020-04-16 13:09:24 -07:00
this . _firstNonInitialNavigationCommittedPromise = new Promise ( ( f , r ) = > {
this . _firstNonInitialNavigationCommittedFulfill = f ;
this . _firstNonInitialNavigationCommittedReject = r ;
} ) ;
2020-08-18 09:37:40 -07:00
if ( opener && ! browserContext . _options . noDefaultViewport && opener . _nextWindowOpenPopupFeatures ) {
2020-05-22 15:56:37 -07:00
const viewportSize = helper . getViewportSizeFromWindowFeatures ( opener . _nextWindowOpenPopupFeatures ) ;
opener . _nextWindowOpenPopupFeatures = undefined ;
if ( viewportSize )
2021-03-30 05:10:58 +08:00
this . _page . _state . emulatedSize = { viewport : viewportSize , screen : viewportSize } ;
2020-05-22 15:56:37 -07:00
}
2019-12-19 16:53:24 -08:00
}
2020-01-17 15:33:55 -08:00
private async _initializePageProxySession() {
2020-02-07 13:36:49 -08:00
const promises : Promise < any > [ ] = [
2020-01-07 17:16:27 -08:00
this . _pageProxySession . send ( 'Dialog.enable' ) ,
2020-02-11 19:10:57 -08:00
this . _pageProxySession . send ( 'Emulation.setActiveAndFocused' , { active : true } ) ,
2020-01-07 17:16:27 -08:00
] ;
2020-03-05 17:22:57 -08:00
const contextOptions = this . _browserContext . _options ;
2020-01-07 17:16:27 -08:00
if ( contextOptions . javaScriptEnabled === false )
promises . push ( this . _pageProxySession . send ( 'Emulation.setJavaScriptEnabled' , { enabled : false } ) ) ;
2020-05-12 18:31:17 -07:00
promises . push ( this . _updateViewport ( ) ) ;
2020-03-06 13:50:42 -08:00
promises . push ( this . updateHttpCredentials ( ) ) ;
2020-03-20 19:45:35 -07:00
if ( this . _browserContext . _permissions . size ) {
for ( const [ key , value ] of this . _browserContext . _permissions )
2021-03-22 09:59:39 -07:00
promises . push ( this . _grantPermissions ( key , value ) ) ;
2020-03-20 19:45:35 -07:00
}
2020-11-02 19:42:05 -08:00
if ( this . _browserContext . _options . recordVideo ) {
const outputFile = path . join ( this . _browserContext . _options . recordVideo . dir , createGuid ( ) + '.webm' ) ;
2020-10-01 11:06:19 -07:00
promises . push ( this . _browserContext . _ensureVideosPath ( ) . then ( ( ) = > {
2021-05-08 11:35:36 -07:00
return this . _startVideo ( {
2021-03-24 10:56:20 -07:00
// validateBrowserContextOptions ensures correct video size.
. . . this . _browserContext . _options . recordVideo ! . size ! ,
2020-09-18 17:36:43 -07:00
outputFile ,
} ) ;
2020-09-11 15:14:31 -07:00
} ) ) ;
2020-08-24 17:23:54 -07:00
}
2020-01-07 17:16:27 -08:00
await Promise . all ( promises ) ;
}
2020-01-22 14:17:44 -08:00
private _setSession ( session : WKSession ) {
2021-07-07 21:14:16 +02:00
eventsHelper . removeEventListeners ( this . _sessionListeners ) ;
2019-12-19 16:53:24 -08:00
this . _session = session ;
this . rawKeyboard . setSession ( session ) ;
this . _addSessionListeners ( ) ;
2020-01-07 12:59:01 -08:00
this . _workers . setSession ( session ) ;
2019-12-19 16:53:24 -08:00
}
// This method is called for provisional targets as well. The session passed as the parameter
// may be different from the current session and may be destroyed without becoming current.
2020-05-14 15:43:23 -07:00
async _initializeSession ( session : WKSession , provisional : boolean , resourceTreeHandler : ( r : Protocol.Page.getResourceTreeReturnValue ) = > void ) {
2020-01-30 17:30:47 -08:00
await this . _initializeSessionMayThrow ( session , resourceTreeHandler ) . catch ( e = > {
2020-05-14 15:43:23 -07:00
// Provisional session can be disposed at any time, for example due to new navigation initiating
// a new provisional page.
if ( provisional && session . isDisposed ( ) )
2020-01-30 17:30:47 -08:00
return ;
// Swallow initialization errors due to newer target swap in,
// since we will reinitialize again.
if ( this . _session === session )
throw e ;
} ) ;
}
private async _initializeSessionMayThrow ( session : WKSession , resourceTreeHandler : ( r : Protocol.Page.getResourceTreeReturnValue ) = > void ) {
const [ , frameTree ] = await Promise . all ( [
2019-12-19 16:53:24 -08:00
// Page agent must be enabled before Runtime.
session . send ( 'Page.enable' ) ,
2020-01-30 17:30:47 -08:00
session . send ( 'Page.getResourceTree' ) ,
2020-02-05 16:53:36 -08:00
] as const ) ;
2020-01-30 17:30:47 -08:00
resourceTreeHandler ( frameTree ) ;
2020-02-07 13:36:49 -08:00
const promises : Promise < any > [ ] = [
2019-12-19 16:53:24 -08:00
// Resource tree should be received before first execution context.
2020-01-17 14:02:57 -08:00
session . send ( 'Runtime.enable' ) ,
2020-01-29 19:13:44 -08:00
session . send ( 'Page.createUserWorld' , { name : UTILITY_WORLD_NAME } ) . catch ( _ = > { } ) , // Worlds are per-process
2019-12-19 16:53:24 -08:00
session . send ( 'Console.enable' ) ,
2020-01-21 14:58:12 -08:00
session . send ( 'Network.enable' ) ,
2020-01-17 15:33:55 -08:00
this . _workers . initializeSession ( session )
2019-12-19 16:53:24 -08:00
] ;
2020-06-17 08:34:42 -07:00
if ( this . _page . _needsRequestInterception ( ) ) {
promises . push ( session . send ( 'Network.setInterceptionEnabled' , { enabled : true } ) ) ;
promises . push ( session . send ( 'Network.addInterception' , { url : '.*' , stage : 'request' , isRegex : true } ) ) ;
2021-06-18 11:04:48 -07:00
if ( this . _needsResponseInterception )
promises . push ( session . send ( 'Network.addInterception' , { url : '.*' , stage : 'response' , isRegex : true } ) ) ;
2020-06-17 08:34:42 -07:00
}
2020-01-31 16:23:15 -08:00
2020-03-05 17:22:57 -08:00
const contextOptions = this . _browserContext . _options ;
2019-12-19 16:53:24 -08:00
if ( contextOptions . userAgent )
promises . push ( session . send ( 'Page.overrideUserAgent' , { value : contextOptions.userAgent } ) ) ;
2021-05-22 01:56:09 +02:00
if ( this . _page . _state . mediaType || this . _page . _state . colorScheme || this . _page . _state . reducedMotion )
promises . push ( WKPage . _setEmulateMedia ( session , this . _page . _state . mediaType , this . _page . _state . colorScheme , this . _page . _state . reducedMotion ) ) ;
2021-05-17 18:32:29 +00:00
for ( const world of [ 'main' , 'utility' ] as const ) {
const bootstrapScript = this . _calculateBootstrapScript ( world ) ;
if ( bootstrapScript . length )
promises . push ( session . send ( 'Page.setBootstrapScript' , { source : bootstrapScript , worldName : webkitWorldName ( world ) } ) ) ;
2021-07-09 16:19:42 +02:00
this . _page . frames ( ) . map ( frame = > frame . evaluateExpression ( bootstrapScript , false , undefined , world ) . catch ( e = > { } ) ) ;
2021-05-17 18:32:29 +00:00
}
2019-12-19 16:53:24 -08:00
if ( contextOptions . bypassCSP )
promises . push ( session . send ( 'Page.setBypassCSP' , { enabled : true } ) ) ;
2021-03-30 05:10:58 +08:00
if ( this . _page . _state . emulatedSize ) {
2020-03-10 13:16:33 -07:00
promises . push ( session . send ( 'Page.setScreenSizeOverride' , {
2021-03-30 05:10:58 +08:00
width : this._page._state.emulatedSize.screen.width ,
height : this._page._state.emulatedSize.screen.height ,
2020-03-10 13:16:33 -07:00
} ) ) ;
}
2020-04-06 19:49:33 -07:00
promises . push ( this . updateEmulateMedia ( ) ) ;
2020-08-18 15:38:29 -07:00
promises . push ( session . send ( 'Network.setExtraHTTPHeaders' , { headers : headersArrayToObject ( this . _calculateExtraHTTPHeaders ( ) , false /* lowerCase */ ) } ) ) ;
2020-03-04 17:58:12 -08:00
if ( contextOptions . offline )
promises . push ( session . send ( 'Network.setEmulateOfflineState' , { offline : true } ) ) ;
2020-03-17 18:21:02 -07:00
promises . push ( session . send ( 'Page.setTouchEmulationEnabled' , { enabled : ! ! contextOptions . hasTouch } ) ) ;
2020-02-12 17:02:59 -08:00
if ( contextOptions . timezoneId ) {
promises . push ( session . send ( 'Page.setTimeZone' , { timeZone : contextOptions.timezoneId } ) .
catch ( e = > { throw new Error ( ` Invalid timezone ID: ${ contextOptions . timezoneId } ` ) ; } ) ) ;
}
2020-01-30 17:30:47 -08:00
await Promise . all ( promises ) ;
2019-12-19 16:53:24 -08:00
}
2020-03-06 11:41:46 -08:00
private _onDidCommitProvisionalTarget ( event : Protocol.Target.didCommitProvisionalTargetPayload ) {
const { oldTargetId , newTargetId } = event ;
2020-01-22 14:17:44 -08:00
assert ( this . _provisionalPage ) ;
2020-03-06 16:51:45 -08:00
assert ( this . _provisionalPage . _session . sessionId === newTargetId , 'Unknown new target: ' + newTargetId ) ;
assert ( this . _session . sessionId === oldTargetId , 'Unknown old target: ' + oldTargetId ) ;
2021-06-02 20:17:24 -07:00
this . _session . errorText = kSwappedOutErrorMessage ;
2020-03-06 16:51:45 -08:00
const newSession = this . _provisionalPage . _session ;
2020-02-07 13:38:50 -08:00
this . _provisionalPage . commit ( ) ;
this . _provisionalPage . dispose ( ) ;
2020-01-22 14:17:44 -08:00
this . _provisionalPage = null ;
2020-03-06 11:41:46 -08:00
this . _setSession ( newSession ) ;
2020-01-22 14:17:44 -08:00
}
2020-03-06 11:41:46 -08:00
private _onTargetDestroyed ( event : Protocol.Target.targetDestroyedPayload ) {
const { targetId , crashed } = event ;
2020-03-06 16:51:45 -08:00
if ( this . _provisionalPage && this . _provisionalPage . _session . sessionId === targetId ) {
2020-12-08 09:35:28 -08:00
this . _provisionalPage . _session . dispose ( false ) ;
2020-01-22 14:17:44 -08:00
this . _provisionalPage . dispose ( ) ;
this . _provisionalPage = null ;
2020-03-06 16:51:45 -08:00
} else if ( this . _session . sessionId === targetId ) {
2020-12-08 09:35:28 -08:00
this . _session . dispose ( false ) ;
2021-07-07 21:14:16 +02:00
eventsHelper . removeEventListeners ( this . _sessionListeners ) ;
2020-04-15 00:04:35 -07:00
if ( crashed ) {
this . _session . markAsCrashed ( ) ;
2020-03-09 12:32:42 -07:00
this . _page . _didCrash ( ) ;
2020-04-15 00:04:35 -07:00
}
2020-01-22 14:17:44 -08:00
}
}
2020-03-09 12:32:42 -07:00
didClose() {
this . _page . _didClose ( ) ;
2019-12-19 16:53:24 -08:00
}
2020-12-08 09:35:28 -08:00
dispose ( disconnected : boolean ) {
this . _pageProxySession . dispose ( disconnected ) ;
2021-07-07 21:14:16 +02:00
eventsHelper . removeEventListeners ( this . _sessionListeners ) ;
eventsHelper . removeEventListeners ( this . _eventListeners ) ;
2020-03-06 16:51:45 -08:00
if ( this . _session )
2020-12-08 09:35:28 -08:00
this . _session . dispose ( disconnected ) ;
2020-01-22 14:17:44 -08:00
if ( this . _provisionalPage ) {
2020-12-08 09:35:28 -08:00
this . _provisionalPage . _session . dispose ( disconnected ) ;
2020-01-22 14:17:44 -08:00
this . _provisionalPage . dispose ( ) ;
this . _provisionalPage = null ;
}
2020-01-09 15:14:35 -08:00
this . _page . _didDisconnect ( ) ;
2020-04-16 13:09:24 -07:00
this . _firstNonInitialNavigationCommittedReject ( new Error ( 'Page closed' ) ) ;
2020-01-09 15:14:35 -08:00
}
2020-03-06 11:41:46 -08:00
dispatchMessageToSession ( message : any ) {
this . _pageProxySession . dispatchMessage ( message ) ;
}
2020-03-11 21:08:22 +00:00
handleProvisionalLoadFailed ( event : Protocol.Playwright.provisionalLoadFailedPayload ) {
2020-04-16 13:09:24 -07:00
if ( ! this . _initializedPage ) {
this . _firstNonInitialNavigationCommittedReject ( new Error ( 'Initial load failed' ) ) ;
return ;
}
if ( ! this . _provisionalPage )
2020-03-06 11:41:46 -08:00
return ;
let errorText = event . error ;
if ( errorText . includes ( 'cancelled' ) )
errorText += '; maybe frame was detached?' ;
2020-07-06 15:58:27 -07:00
this . _page . _frameManager . frameAbortedNavigation ( this . _page . mainFrame ( ) . _id , errorText , event . loaderId ) ;
2020-03-06 11:41:46 -08:00
}
2020-05-22 15:56:37 -07:00
handleWindowOpen ( event : Protocol.Playwright.windowOpenPayload ) {
debugAssert ( ! this . _nextWindowOpenPopupFeatures ) ;
this . _nextWindowOpenPopupFeatures = event . windowFeatures ;
}
2020-03-06 11:41:46 -08:00
async pageOrError ( ) : Promise < Page | Error > {
return this . _pagePromise ;
}
private async _onTargetCreated ( event : Protocol.Target.targetCreatedPayload ) {
const { targetInfo } = event ;
const session = new WKSession ( this . _pageProxySession . connection , targetInfo . targetId , ` The ${ targetInfo . type } has been closed. ` , ( message : any ) = > {
this . _pageProxySession . send ( 'Target.sendMessageToTarget' , {
message : JSON.stringify ( message ) , targetId : targetInfo.targetId
} ) . catch ( e = > {
session . dispatchMessage ( { id : message.id , error : { message : e.message } } ) ;
} ) ;
} ) ;
assert ( targetInfo . type === 'page' , 'Only page targets are expected in WebKit, received: ' + targetInfo . type ) ;
2020-05-02 10:57:33 -07:00
if ( ! targetInfo . isProvisional ) {
assert ( ! this . _initializedPage ) ;
2020-03-06 11:41:46 -08:00
let pageOrError : Page | Error ;
try {
this . _setSession ( session ) ;
await Promise . all ( [
this . _initializePageProxySession ( ) ,
2020-05-14 15:43:23 -07:00
this . _initializeSession ( session , false , ( { frameTree } ) = > this . _handleFrameTree ( frameTree ) ) ,
2020-03-06 11:41:46 -08:00
] ) ;
pageOrError = this . _page ;
} catch ( e ) {
pageOrError = e ;
}
if ( targetInfo . isPaused )
2020-06-05 07:50:26 -07:00
this . _pageProxySession . sendMayFail ( 'Target.resume' , { targetId : targetInfo.targetId } ) ;
2020-04-16 10:59:27 -07:00
if ( ( pageOrError instanceof Page ) && this . _page . mainFrame ( ) . url ( ) === '' ) {
2020-04-16 13:09:24 -07:00
try {
// Initial empty page has an empty url. We should wait until the first real url has been loaded,
// even if that url is about:blank. This is especially important for popups, where we need the
// actual url before interacting with it.
await this . _firstNonInitialNavigationCommittedPromise ;
} catch ( e ) {
pageOrError = e ;
}
} else {
// Avoid rejection on disconnect.
this . _firstNonInitialNavigationCommittedPromise . catch ( ( ) = > { } ) ;
2020-04-14 14:51:23 -07:00
}
2021-04-02 11:15:07 -07:00
await this . _page . initOpener ( this . _opener ) ;
2021-03-30 17:35:42 -07:00
// Note: it is important to call |reportAsNew| before resolving pageOrError promise,
// so that anyone who awaits pageOrError got a ready and reported page.
2020-04-16 13:09:24 -07:00
this . _initializedPage = pageOrError instanceof Page ? pageOrError : null ;
2021-03-30 17:35:42 -07:00
this . _page . reportAsNew ( pageOrError instanceof Page ? undefined : pageOrError ) ;
2020-03-06 11:41:46 -08:00
this . _pagePromiseCallback ( pageOrError ) ;
} else {
assert ( targetInfo . isProvisional ) ;
assert ( ! this . _provisionalPage ) ;
this . _provisionalPage = new WKProvisionalPage ( session , this ) ;
if ( targetInfo . isPaused ) {
this . _provisionalPage . initializationPromise . then ( ( ) = > {
2020-06-05 07:50:26 -07:00
this . _pageProxySession . sendMayFail ( 'Target.resume' , { targetId : targetInfo.targetId } ) ;
2020-03-06 11:41:46 -08:00
} ) ;
}
}
}
private _onDispatchMessageFromTarget ( event : Protocol.Target.dispatchMessageFromTargetPayload ) {
const { targetId , message } = event ;
2020-03-06 16:51:45 -08:00
if ( this . _provisionalPage && this . _provisionalPage . _session . sessionId === targetId )
this . _provisionalPage . _session . dispatchMessage ( JSON . parse ( message ) ) ;
else if ( this . _session . sessionId === targetId )
this . _session . dispatchMessage ( JSON . parse ( message ) ) ;
else
throw new Error ( 'Unknown target: ' + targetId ) ;
2020-03-06 11:41:46 -08:00
}
2020-01-22 14:17:44 -08:00
private _addSessionListeners() {
2020-05-13 17:20:33 -07:00
// TODO: remove Page.willRequestOpenWindow and Page.didRequestOpenWindow from the protocol.
2019-12-19 16:53:24 -08:00
this . _sessionListeners = [
2021-07-07 21:14:16 +02:00
eventsHelper . addEventListener ( this . _session , 'Page.frameNavigated' , event = > this . _onFrameNavigated ( event . frame , false ) ) ,
eventsHelper . addEventListener ( this . _session , 'Page.navigatedWithinDocument' , event = > this . _onFrameNavigatedWithinDocument ( event . frameId , event . url ) ) ,
eventsHelper . addEventListener ( this . _session , 'Page.frameAttached' , event = > this . _onFrameAttached ( event . frameId , event . parentFrameId ) ) ,
eventsHelper . addEventListener ( this . _session , 'Page.frameDetached' , event = > this . _onFrameDetached ( event . frameId ) ) ,
eventsHelper . addEventListener ( this . _session , 'Page.frameScheduledNavigation' , event = > this . _onFrameScheduledNavigation ( event . frameId ) ) ,
eventsHelper . addEventListener ( this . _session , 'Page.frameStoppedLoading' , event = > this . _onFrameStoppedLoading ( event . frameId ) ) ,
eventsHelper . addEventListener ( this . _session , 'Page.loadEventFired' , event = > this . _onLifecycleEvent ( event . frameId , 'load' ) ) ,
eventsHelper . addEventListener ( this . _session , 'Page.domContentEventFired' , event = > this . _onLifecycleEvent ( event . frameId , 'domcontentloaded' ) ) ,
eventsHelper . addEventListener ( this . _session , 'Runtime.executionContextCreated' , event = > this . _onExecutionContextCreated ( event . context ) ) ,
eventsHelper . addEventListener ( this . _session , 'Console.messageAdded' , event = > this . _onConsoleMessage ( event ) ) ,
eventsHelper . addEventListener ( this . _session , 'Console.messageRepeatCountUpdated' , event = > this . _onConsoleRepeatCountUpdated ( event ) ) ,
eventsHelper . addEventListener ( this . _pageProxySession , 'Dialog.javascriptDialogOpening' , event = > this . _onDialog ( event ) ) ,
eventsHelper . addEventListener ( this . _session , 'Page.fileChooserOpened' , event = > this . _onFileChooserOpened ( event ) ) ,
eventsHelper . addEventListener ( this . _session , 'Network.requestWillBeSent' , e = > this . _onRequestWillBeSent ( this . _session , e ) ) ,
eventsHelper . addEventListener ( this . _session , 'Network.requestIntercepted' , e = > this . _onRequestIntercepted ( this . _session , e ) ) ,
eventsHelper . addEventListener ( this . _session , 'Network.responseIntercepted' , e = > this . _onResponseIntercepted ( this . _session , e ) ) ,
eventsHelper . addEventListener ( this . _session , 'Network.responseReceived' , e = > this . _onResponseReceived ( e ) ) ,
eventsHelper . addEventListener ( this . _session , 'Network.loadingFinished' , e = > this . _onLoadingFinished ( e ) ) ,
eventsHelper . addEventListener ( this . _session , 'Network.loadingFailed' , e = > this . _onLoadingFailed ( e ) ) ,
eventsHelper . addEventListener ( this . _session , 'Network.webSocketCreated' , e = > this . _page . _frameManager . onWebSocketCreated ( e . requestId , e . url ) ) ,
eventsHelper . addEventListener ( this . _session , 'Network.webSocketWillSendHandshakeRequest' , e = > this . _page . _frameManager . onWebSocketRequest ( e . requestId ) ) ,
eventsHelper . addEventListener ( this . _session , 'Network.webSocketHandshakeResponseReceived' , e = > this . _page . _frameManager . onWebSocketResponse ( e . requestId , e . response . status , e . response . statusText ) ) ,
eventsHelper . addEventListener ( this . _session , 'Network.webSocketFrameSent' , e = > e . response . payloadData && this . _page . _frameManager . onWebSocketFrameSent ( e . requestId , e . response . opcode , e . response . payloadData ) ) ,
eventsHelper . addEventListener ( this . _session , 'Network.webSocketFrameReceived' , e = > e . response . payloadData && this . _page . _frameManager . webSocketFrameReceived ( e . requestId , e . response . opcode , e . response . payloadData ) ) ,
eventsHelper . addEventListener ( this . _session , 'Network.webSocketClosed' , e = > this . _page . _frameManager . webSocketClosed ( e . requestId ) ) ,
eventsHelper . addEventListener ( this . _session , 'Network.webSocketFrameError' , e = > this . _page . _frameManager . webSocketError ( e . requestId , e . errorMessage ) ) ,
2019-12-19 16:53:24 -08:00
] ;
}
2020-01-22 14:17:44 -08:00
private async _updateState < T extends keyof Protocol.CommandParameters > (
method : T ,
params? : Protocol.CommandParameters [ T ]
) : Promise < void > {
await this . _forAllSessions ( session = > session . send ( method , params ) . then ( ) ) ;
}
private async _forAllSessions ( callback : ( ( session : WKSession ) = > Promise < void > ) ) : Promise < void > {
const sessions = [
this . _session
] ;
// If the state changes during provisional load, push it to the provisional page
// as well to always be in sync with the backend.
if ( this . _provisionalPage )
sessions . push ( this . _provisionalPage . _session ) ;
2020-06-09 16:11:17 -07:00
await Promise . all ( sessions . map ( session = > callback ( session ) . catch ( e = > { } ) ) ) ;
2020-01-22 14:17:44 -08:00
}
2020-03-04 19:15:01 -08:00
private _onFrameScheduledNavigation ( frameId : string ) {
2020-07-06 15:58:27 -07:00
this . _page . _frameManager . frameRequestedNavigation ( frameId ) ;
2020-03-04 19:15:01 -08:00
}
2020-01-22 14:17:44 -08:00
private _onFrameStoppedLoading ( frameId : string ) {
2019-12-19 16:53:24 -08:00
this . _page . _frameManager . frameStoppedLoading ( frameId ) ;
}
2020-03-06 08:24:32 -08:00
private _onLifecycleEvent ( frameId : string , event : types.LifecycleEvent ) {
2019-12-19 16:53:24 -08:00
this . _page . _frameManager . frameLifecycleEvent ( frameId , event ) ;
}
2020-01-31 10:08:45 -08:00
private _handleFrameTree ( frameTree : Protocol.Page.FrameResourceTree ) {
this . _onFrameAttached ( frameTree . frame . id , frameTree . frame . parentId || null ) ;
2019-12-19 16:53:24 -08:00
this . _onFrameNavigated ( frameTree . frame , true ) ;
2020-03-18 17:14:18 -07:00
this . _page . _frameManager . frameLifecycleEvent ( frameTree . frame . id , 'domcontentloaded' ) ;
this . _page . _frameManager . frameLifecycleEvent ( frameTree . frame . id , 'load' ) ;
2020-01-28 13:05:38 -08:00
2019-12-19 16:53:24 -08:00
if ( ! frameTree . childFrames )
return ;
for ( const child of frameTree . childFrames )
2020-01-31 10:08:45 -08:00
this . _handleFrameTree ( child ) ;
2019-12-19 16:53:24 -08:00
}
2020-01-28 13:05:38 -08:00
_onFrameAttached ( frameId : string , parentFrameId : string | null ) : frames . Frame {
return this . _page . _frameManager . frameAttached ( frameId , parentFrameId ) ;
2019-12-19 16:53:24 -08:00
}
2020-01-22 14:17:44 -08:00
private _onFrameNavigated ( framePayload : Protocol.Page.Frame , initial : boolean ) {
2019-12-19 16:53:24 -08:00
const frame = this . _page . _frameManager . frame ( framePayload . id ) ;
2020-01-23 14:58:30 -08:00
assert ( frame ) ;
2020-02-07 13:38:50 -08:00
this . _removeContextsForFrame ( frame , true ) ;
2020-01-17 18:46:45 -08:00
if ( ! framePayload . parentId )
this . _workers . clear ( ) ;
2020-01-10 15:16:06 -08:00
this . _page . _frameManager . frameCommittedNewDocumentNavigation ( framePayload . id , framePayload . url , framePayload . name || '' , framePayload . loaderId , initial ) ;
2020-03-18 17:14:18 -07:00
if ( ! initial )
2020-04-16 13:09:24 -07:00
this . _firstNonInitialNavigationCommittedFulfill ( ) ;
2019-12-19 16:53:24 -08:00
}
2020-01-22 14:17:44 -08:00
private _onFrameNavigatedWithinDocument ( frameId : string , url : string ) {
2019-12-19 16:53:24 -08:00
this . _page . _frameManager . frameCommittedSameDocumentNavigation ( frameId , url ) ;
}
2020-01-22 14:17:44 -08:00
private _onFrameDetached ( frameId : string ) {
2019-12-19 16:53:24 -08:00
this . _page . _frameManager . frameDetached ( frameId ) ;
}
2020-01-23 14:58:30 -08:00
private _removeContextsForFrame ( frame : frames.Frame , notifyFrame : boolean ) {
for ( const [ contextId , context ] of this . _contextIdToContext ) {
if ( context . frame === frame ) {
( context . _delegate as WKExecutionContext ) . _dispose ( ) ;
this . _contextIdToContext . delete ( contextId ) ;
if ( notifyFrame )
frame . _contextDestroyed ( context ) ;
}
}
}
2020-02-07 13:36:49 -08:00
private _onExecutionContextCreated ( contextPayload : Protocol.Runtime.ExecutionContextDescription ) {
2019-12-19 16:53:24 -08:00
if ( this . _contextIdToContext . has ( contextPayload . id ) )
return ;
const frame = this . _page . _frameManager . frame ( contextPayload . frameId ) ;
if ( ! frame )
return ;
2020-01-07 12:59:01 -08:00
const delegate = new WKExecutionContext ( this . _session , contextPayload . id ) ;
2020-12-02 13:43:16 -08:00
let worldName : types.World | null = null ;
2020-01-29 19:13:44 -08:00
if ( contextPayload . type === 'normal' )
2020-12-02 13:43:16 -08:00
worldName = 'main' ;
2020-01-29 19:13:44 -08:00
else if ( contextPayload . type === 'user' && contextPayload . name === UTILITY_WORLD_NAME )
2020-12-02 13:43:16 -08:00
worldName = 'utility' ;
const context = new dom . FrameExecutionContext ( delegate , frame , worldName ) ;
if ( worldName )
frame . _contextCreated ( worldName , context ) ;
2020-01-29 19:13:44 -08:00
if ( contextPayload . type === 'normal' && frame === this . _page . mainFrame ( ) )
2020-01-27 16:51:52 -08:00
this . _mainFrameContextId = contextPayload . id ;
2019-12-19 16:53:24 -08:00
this . _contextIdToContext . set ( contextPayload . id , context ) ;
}
async navigateFrame ( frame : frames.Frame , url : string , referrer : string | undefined ) : Promise < frames.GotoResult > {
2020-01-14 11:46:08 -08:00
if ( this . _pageProxySession . isDisposed ( ) )
throw new Error ( 'Target closed' ) ;
const pageProxyId = this . _pageProxySession . sessionId ;
2020-03-11 21:08:22 +00:00
const result = await this . _pageProxySession . connection . browserSession . send ( 'Playwright.navigate' , { url , pageProxyId , frameId : frame._id , referrer } ) ;
2020-02-10 18:35:47 -08:00
return { newDocumentId : result.loaderId } ;
2019-12-19 16:53:24 -08:00
}
2020-01-27 16:51:52 -08:00
private _onConsoleMessage ( event : Protocol.Console.messageAddedPayload ) {
// Note: do no introduce await in this function, otherwise we lose the ordering.
// For example, frame.setContent relies on this.
2020-01-03 10:07:49 -08:00
const { type , level , text , parameters , url , line : lineNumber , column : columnNumber , source } = event . message ;
2019-12-19 16:53:24 -08:00
if ( level === 'debug' && parameters && parameters [ 0 ] . value === BINDING_CALL_MESSAGE ) {
2020-01-13 13:33:25 -08:00
const parsedObjectId = JSON . parse ( parameters [ 1 ] . objectId ! ) ;
const context = this . _contextIdToContext . get ( parsedObjectId . injectedScriptId ) ! ;
2020-07-14 13:34:49 -07:00
this . pageOrError ( ) . then ( pageOrError = > {
if ( ! ( pageOrError instanceof Error ) )
this . _page . _onBindingCalled ( parameters [ 2 ] . value , context ) ;
} ) ;
2019-12-19 16:53:24 -08:00
return ;
}
2020-01-03 10:07:49 -08:00
if ( level === 'error' && source === 'javascript' ) {
2021-07-01 14:31:20 -07:00
const { name , message } = splitErrorMessage ( text ) ;
let stack : string ;
2020-04-20 11:37:02 -07:00
if ( event . message . stackTrace ) {
2021-07-01 14:31:20 -07:00
stack = text + '\n' + event . message . stackTrace . map ( callFrame = > {
return ` at ${ callFrame . functionName || 'unknown' } ( ${ callFrame . url } : ${ callFrame . lineNumber } : ${ callFrame . columnNumber } ) ` ;
2020-04-20 11:37:02 -07:00
} ) . join ( '\n' ) ;
} else {
2021-07-01 14:31:20 -07:00
stack = '' ;
2020-04-20 11:37:02 -07:00
}
2021-07-01 14:31:20 -07:00
const error = new Error ( message ) ;
error . stack = stack ;
2021-04-19 22:37:38 +02:00
error . name = name ;
2021-07-01 14:31:20 -07:00
2021-07-01 15:26:55 -07:00
this . _page . firePageError ( error ) ;
2020-01-03 10:07:49 -08:00
return ;
}
2020-01-13 13:33:25 -08:00
let derivedType : string = type || '' ;
2019-12-19 16:53:24 -08:00
if ( type === 'log' )
derivedType = level ;
else if ( type === 'timing' )
derivedType = 'timeEnd' ;
const handles = ( parameters || [ ] ) . map ( p = > {
let context : dom.FrameExecutionContext | null = null ;
if ( p . objectId ) {
const objectId = JSON . parse ( p . objectId ) ;
2020-01-13 13:33:25 -08:00
context = this . _contextIdToContext . get ( objectId . injectedScriptId ) ! ;
2019-12-19 16:53:24 -08:00
} else {
2020-01-27 16:51:52 -08:00
context = this . _contextIdToContext . get ( this . _mainFrameContextId ! ) ! ;
2019-12-19 16:53:24 -08:00
}
2020-05-15 15:21:49 -07:00
return context . createHandle ( p ) ;
2019-12-19 16:53:24 -08:00
} ) ;
2020-04-29 12:13:57 -07:00
this . _lastConsoleMessage = {
derivedType ,
text ,
handles ,
count : 0 ,
location : {
2020-08-05 22:25:56 -07:00
url : url || '' ,
2020-04-29 12:13:57 -07:00
lineNumber : ( lineNumber || 1 ) - 1 ,
columnNumber : ( columnNumber || 1 ) - 1 ,
}
} ;
this . _onConsoleRepeatCountUpdated ( { count : 1 } ) ;
}
_onConsoleRepeatCountUpdated ( event : Protocol.Console.messageRepeatCountUpdatedPayload ) {
if ( this . _lastConsoleMessage ) {
const {
derivedType ,
text ,
handles ,
count ,
location
} = this . _lastConsoleMessage ;
for ( let i = count ; i < event . count ; ++ i )
this . _page . _addConsoleMessage ( derivedType , handles , location , handles . length ? undefined : text ) ;
this . _lastConsoleMessage . count = event . count ;
}
2019-12-19 16:53:24 -08:00
}
_onDialog ( event : Protocol.Dialog.javascriptDialogOpeningPayload ) {
2020-08-21 16:26:33 -07:00
this . _page . emit ( Page . Events . Dialog , new dialog . Dialog (
2021-01-15 18:30:55 -08:00
this . _page ,
2020-07-01 22:10:37 -07:00
event . type as dialog . DialogType ,
event . message ,
async ( accept : boolean , promptText? : string ) = > {
await this . _pageProxySession . send ( 'Dialog.handleJavaScriptDialog' , { accept , promptText } ) ;
} ,
event . defaultPrompt ) ) ;
2019-12-19 16:53:24 -08:00
}
2020-01-22 14:17:44 -08:00
private async _onFileChooserOpened ( event : { frameId : Protocol.Network.FrameId , element : Protocol.Runtime.RemoteObject } ) {
2021-02-05 11:30:44 -08:00
let handle ;
try {
const context = await this . _page . _frameManager . frame ( event . frameId ) ! . _mainContext ( ) ;
handle = context . createHandle ( event . element ) . asElement ( ) ! ;
} catch ( e ) {
// During async processing, frame/context may go away. We should not throw.
return ;
}
await this . _page . _onFileChooserOpened ( handle ) ;
2019-12-19 16:53:24 -08:00
}
2021-05-22 01:56:09 +02:00
private static async _setEmulateMedia ( session : WKSession , mediaType : types.MediaType | null , colorScheme : types.ColorScheme | null , reducedMotion : types.ReducedMotion | null ) : Promise < void > {
2019-12-19 16:53:24 -08:00
const promises = [ ] ;
promises . push ( session . send ( 'Page.setEmulatedMedia' , { media : mediaType || '' } ) ) ;
2021-05-22 01:56:09 +02:00
let appearance : any = undefined ;
switch ( colorScheme ) {
case 'light' : appearance = 'Light' ; break ;
case 'dark' : appearance = 'Dark' ; break ;
}
promises . push ( session . send ( 'Page.setForcedAppearance' , { appearance } ) ) ;
let reducedMotionWk : any = undefined ;
switch ( reducedMotion ) {
case 'reduce' : reducedMotionWk = 'Reduce' ; break ;
case 'no-preference' : reducedMotionWk = 'NoPreference' ; break ;
2019-12-19 16:53:24 -08:00
}
2021-05-22 01:56:09 +02:00
promises . push ( session . send ( 'Page.setForcedReducedMotion' , { reducedMotion : reducedMotionWk } ) ) ;
2019-12-19 16:53:24 -08:00
await Promise . all ( promises ) ;
}
2020-02-26 12:42:20 -08:00
async updateExtraHTTPHeaders ( ) : Promise < void > {
2020-08-18 15:38:29 -07:00
await this . _updateState ( 'Network.setExtraHTTPHeaders' , { headers : headersArrayToObject ( this . _calculateExtraHTTPHeaders ( ) , false /* lowerCase */ ) } ) ;
2020-02-26 12:42:20 -08:00
}
2020-08-18 15:38:29 -07:00
_calculateExtraHTTPHeaders ( ) : types . HeadersArray {
const locale = this . _browserContext . _options . locale ;
2020-02-26 12:42:20 -08:00
const headers = network . mergeHeaders ( [
2020-03-05 17:22:57 -08:00
this . _browserContext . _options . extraHTTPHeaders ,
2020-08-18 15:38:29 -07:00
this . _page . _state . extraHTTPHeaders ,
locale ? network . singleHeader ( 'Accept-Language' , locale ) : undefined ,
2020-02-26 12:42:20 -08:00
] ) ;
return headers ;
2019-12-19 16:53:24 -08:00
}
2020-04-06 19:49:33 -07:00
async updateEmulateMedia ( ) : Promise < void > {
2021-05-22 01:56:09 +02:00
const colorScheme = this . _page . _state . colorScheme ;
const reducedMotion = this . _page . _state . reducedMotion ;
await this . _forAllSessions ( session = > WKPage . _setEmulateMedia ( session , this . _page . _state . mediaType , colorScheme , reducedMotion ) ) ;
2019-12-19 16:53:24 -08:00
}
2021-03-30 05:10:58 +08:00
async setEmulatedSize ( emulatedSize : types.EmulatedSize ) : Promise < void > {
assert ( this . _page . _state . emulatedSize === emulatedSize ) ;
2020-03-09 21:02:54 -07:00
await this . _updateViewport ( ) ;
2020-02-06 19:02:55 -08:00
}
2020-07-21 09:36:54 -07:00
async bringToFront ( ) : Promise < void > {
this . _pageProxySession . send ( 'Target.activate' , {
targetId : this._session.sessionId
} ) ;
}
2020-03-09 21:02:54 -07:00
async _updateViewport ( ) : Promise < void > {
2020-03-17 18:21:02 -07:00
const options = this . _browserContext . _options ;
2021-03-30 05:10:58 +08:00
const deviceSize = this . _page . _state . emulatedSize ;
if ( deviceSize === null )
2020-05-12 18:31:17 -07:00
return ;
2021-03-30 05:10:58 +08:00
const viewportSize = deviceSize . viewport ;
const screenSize = deviceSize . screen ;
2020-02-06 19:02:55 -08:00
const promises : Promise < any > [ ] = [
this . _pageProxySession . send ( 'Emulation.setDeviceMetricsOverride' , {
2020-05-12 18:31:17 -07:00
width : viewportSize.width ,
height : viewportSize.height ,
2020-03-17 18:21:02 -07:00
fixedLayout : ! ! options . isMobile ,
deviceScaleFactor : options.deviceScaleFactor || 1
2020-02-06 19:02:55 -08:00
} ) ,
2020-03-10 13:16:33 -07:00
this . _session . send ( 'Page.setScreenSizeOverride' , {
2021-03-30 05:10:58 +08:00
width : screenSize.width ,
height : screenSize.height ,
2020-03-10 13:16:33 -07:00
} ) ,
2020-02-06 19:02:55 -08:00
] ;
2020-04-20 14:43:44 -07:00
if ( options . isMobile ) {
2020-05-12 18:31:17 -07:00
const angle = viewportSize . width > viewportSize . height ? 90 : 0 ;
2020-04-20 14:43:44 -07:00
promises . push ( this . _session . send ( 'Page.setOrientationOverride' , { angle } ) ) ;
}
2020-02-06 19:02:55 -08:00
await Promise . all ( promises ) ;
2019-12-19 16:53:24 -08:00
}
2021-06-18 11:04:48 -07:00
async _ensureResponseInterceptionEnabled() {
if ( this . _needsResponseInterception )
return ;
this . _needsResponseInterception = true ;
await this . updateRequestInterception ( ) ;
}
2020-03-09 21:02:54 -07:00
async updateRequestInterception ( ) : Promise < void > {
const enabled = this . _page . _needsRequestInterception ( ) ;
2021-06-18 11:04:48 -07:00
const promises = [
2020-06-17 08:34:42 -07:00
this . _updateState ( 'Network.setInterceptionEnabled' , { enabled } ) ,
2021-06-18 11:04:48 -07:00
this . _updateState ( 'Network.addInterception' , { url : '.*' , stage : 'request' , isRegex : true } ) ,
] ;
if ( this . _needsResponseInterception )
2021-06-23 11:08:35 +02:00
this . _updateState ( 'Network.addInterception' , { url : '.*' , stage : 'response' , isRegex : true } ) ;
2021-06-18 11:04:48 -07:00
await Promise . all ( promises ) ;
2019-12-30 14:05:28 -08:00
}
2020-03-04 17:58:12 -08:00
async updateOffline() {
2020-03-05 17:22:57 -08:00
await this . _updateState ( 'Network.setEmulateOfflineState' , { offline : ! ! this . _browserContext . _options . offline } ) ;
2019-12-30 14:09:54 -08:00
}
2020-03-06 13:50:42 -08:00
async updateHttpCredentials() {
const credentials = this . _browserContext . _options . httpCredentials || { username : '' , password : '' } ;
await this . _pageProxySession . send ( 'Emulation.setAuthCredentials' , { username : credentials.username , password : credentials.password } ) ;
2019-12-30 14:09:54 -08:00
}
2020-01-30 17:43:06 -08:00
async setFileChooserIntercepted ( enabled : boolean ) {
await this . _session . send ( 'Page.setInterceptFileChooserDialog' , { enabled } ) . catch ( e = > { } ) ; // target can be closed.
}
2019-12-19 16:53:24 -08:00
async reload ( ) : Promise < void > {
await this . _session . send ( 'Page.reload' ) ;
}
goBack ( ) : Promise < boolean > {
return this . _session . send ( 'Page.goBack' ) . then ( ( ) = > true ) . catch ( error = > {
if ( error instanceof Error && error . message . includes ( ` Protocol error (Page.goBack): Failed to go ` ) )
return false ;
throw error ;
} ) ;
}
goForward ( ) : Promise < boolean > {
return this . _session . send ( 'Page.goForward' ) . then ( ( ) = > true ) . catch ( error = > {
if ( error instanceof Error && error . message . includes ( ` Protocol error (Page.goForward): Failed to go ` ) )
return false ;
throw error ;
} ) ;
}
2020-03-03 16:46:06 -08:00
async exposeBinding ( binding : PageBinding ) : Promise < void > {
2021-05-17 18:32:29 +00:00
await this . _updateBootstrapScript ( binding . world ) ;
2020-03-03 16:46:06 -08:00
await this . _evaluateBindingScript ( binding ) ;
}
private async _evaluateBindingScript ( binding : PageBinding ) : Promise < void > {
const script = this . _bindingToScript ( binding ) ;
2021-07-09 16:19:42 +02:00
await Promise . all ( this . _page . frames ( ) . map ( frame = > frame . evaluateExpression ( script , false , { } , binding . world ) . catch ( e = > { } ) ) ) ;
2019-12-19 16:53:24 -08:00
}
async evaluateOnNewDocument ( script : string ) : Promise < void > {
2021-05-17 18:32:29 +00:00
await this . _updateBootstrapScript ( 'main' ) ;
2020-02-27 16:18:33 -08:00
}
2020-03-03 16:46:06 -08:00
private _bindingToScript ( binding : PageBinding ) : string {
return ` self. ${ binding . name } = (param) => console.debug(' ${ BINDING_CALL_MESSAGE } ', {}, param); ${ binding . source } ` ;
}
2021-05-17 18:32:29 +00:00
private _calculateBootstrapScript ( world : types.World ) : string {
2020-03-03 16:46:06 -08:00
const scripts : string [ ] = [ ] ;
2021-05-17 18:32:29 +00:00
for ( const binding of this . _page . allBindings ( ) ) {
if ( binding . world === world )
scripts . push ( this . _bindingToScript ( binding ) ) ;
}
if ( world === 'main' ) {
scripts . push ( . . . this . _browserContext . _evaluateOnNewDocumentSources ) ;
scripts . push ( . . . this . _page . _evaluateOnNewDocumentSources ) ;
}
2020-03-03 16:46:06 -08:00
return scripts . join ( ';' ) ;
2019-12-20 17:16:32 -07:00
}
2021-05-17 18:32:29 +00:00
async _updateBootstrapScript ( world : types.World ) : Promise < void > {
await this . _updateState ( 'Page.setBootstrapScript' , { source : this._calculateBootstrapScript ( world ) , worldName : webkitWorldName ( world ) } ) ;
2019-12-19 16:53:24 -08:00
}
async closePage ( runBeforeUnload : boolean ) : Promise < void > {
2021-05-08 11:35:36 -07:00
await this . _stopVideo ( ) ;
2020-08-19 12:45:31 -07:00
await this . _pageProxySession . sendMayFail ( 'Target.close' , {
2020-01-08 16:34:45 -08:00
targetId : this._session.sessionId ,
2020-01-07 10:39:01 -08:00
runBeforeUnload
2020-06-05 07:50:26 -07:00
} ) ;
2019-12-19 16:53:24 -08:00
}
canScreenshotOutsideViewport ( ) : boolean {
2020-03-03 16:09:32 -08:00
return true ;
2019-12-19 16:53:24 -08:00
}
async setBackgroundColor ( color ? : { r : number ; g : number ; b : number ; a : number ; } ) : Promise < void > {
2020-01-09 15:14:35 -08:00
await this . _session . send ( 'Page.setDefaultBackgroundColorOverride' , { color } ) ;
2019-12-19 16:53:24 -08:00
}
2021-05-11 13:21:01 -07:00
private _toolbarHeight ( ) : number {
if ( this . _page . _browserContext . _browser ? . options . headful )
return hostPlatform . startsWith ( '10.15' ) ? 55 : 59 ;
return 0 ;
}
2021-05-08 11:35:36 -07:00
private async _startVideo ( options : types.PageScreencastOptions ) : Promise < void > {
2020-11-19 19:26:53 -08:00
assert ( ! this . _recordingVideoFile ) ;
2021-05-06 10:38:46 -07:00
const START_VIDEO_PROTOCOL_COMMAND = hostPlatform === 'mac10.14' ? 'Screencast.start' : 'Screencast.startVideo' ;
const { screencastId } = await this . _pageProxySession . send ( START_VIDEO_PROTOCOL_COMMAND as any , {
2020-11-19 19:26:53 -08:00
file : options.outputFile ,
width : options.width ,
height : options.height ,
2021-05-11 13:21:01 -07:00
toolbarHeight : this._toolbarHeight ( )
2020-11-19 19:26:53 -08:00
} ) ;
2020-08-19 12:45:31 -07:00
this . _recordingVideoFile = options . outputFile ;
2020-11-19 19:26:53 -08:00
this . _browserContext . _browser . _videoStarted ( this . _browserContext , screencastId , options . outputFile , this . pageOrError ( ) ) ;
2020-08-10 21:22:57 -07:00
}
2021-05-08 11:35:36 -07:00
private async _stopVideo ( ) : Promise < void > {
2020-08-19 12:45:31 -07:00
if ( ! this . _recordingVideoFile )
2020-11-19 19:26:53 -08:00
return ;
2021-05-06 10:38:46 -07:00
const STOP_VIDEO_PROTOCOL_COMMAND = hostPlatform === 'mac10.14' ? 'Screencast.stop' : 'Screencast.stopVideo' ;
await this . _pageProxySession . sendMayFail ( STOP_VIDEO_PROTOCOL_COMMAND as any ) ;
2020-08-19 12:45:31 -07:00
this . _recordingVideoFile = null ;
2020-08-10 21:22:57 -07:00
}
2021-04-07 07:01:38 +08:00
async takeScreenshot ( progress : Progress , format : string , documentRect : types.Rect | undefined , viewportRect : types.Rect | undefined , quality : number | undefined ) : Promise < Buffer > {
2020-03-03 16:09:32 -08:00
const rect = ( documentRect || viewportRect ) ! ;
const result = await this . _session . send ( 'Page.snapshotRect' , { . . . rect , coordinateSystem : documentRect ? 'Page' : 'Viewport' } ) ;
2019-12-19 16:53:24 -08:00
const prefix = 'data:image/png;base64,' ;
2020-04-01 14:42:47 -07:00
let buffer = Buffer . from ( result . dataURL . substr ( prefix . length ) , 'base64' ) ;
2019-12-19 16:53:24 -08:00
if ( format === 'jpeg' )
2020-04-01 14:42:47 -07:00
buffer = jpeg . encode ( png . PNG . sync . read ( buffer ) , quality ) . data ;
2019-12-19 16:53:24 -08:00
return buffer ;
}
2020-03-03 16:09:32 -08:00
async resetViewport ( ) : Promise < void > {
assert ( false , 'Should not be called' ) ;
2019-12-19 16:53:24 -08:00
}
async getContentFrame ( handle : dom.ElementHandle ) : Promise < frames.Frame | null > {
const nodeInfo = await this . _session . send ( 'DOM.describeNode' , {
2020-05-27 22:19:05 -07:00
objectId : handle._objectId
2019-12-19 16:53:24 -08:00
} ) ;
if ( ! nodeInfo . contentFrameId )
return null ;
return this . _page . _frameManager . frame ( nodeInfo . contentFrameId ) ;
}
2020-01-27 11:43:43 -08:00
async getOwnerFrame ( handle : dom.ElementHandle ) : Promise < string | null > {
2020-05-27 17:19:05 -07:00
if ( ! handle . _objectId )
2019-12-26 14:05:46 -08:00
return null ;
const nodeInfo = await this . _session . send ( 'DOM.describeNode' , {
2020-05-27 17:19:05 -07:00
objectId : handle._objectId
2019-12-26 14:05:46 -08:00
} ) ;
2020-01-27 11:43:43 -08:00
return nodeInfo . ownerFrameId || null ;
2019-12-19 16:53:24 -08:00
}
isElementHandle ( remoteObject : any ) : boolean {
return ( remoteObject as Protocol . Runtime . RemoteObject ) . subtype === 'node' ;
}
async getBoundingBox ( handle : dom.ElementHandle ) : Promise < types.Rect | null > {
const quads = await this . getContentQuads ( handle ) ;
if ( ! quads || ! quads . length )
return null ;
let minX = Infinity ;
let maxX = - Infinity ;
let minY = Infinity ;
let maxY = - Infinity ;
for ( const quad of quads ) {
for ( const point of quad ) {
minX = Math . min ( minX , point . x ) ;
maxX = Math . max ( maxX , point . x ) ;
minY = Math . min ( minY , point . y ) ;
maxY = Math . max ( maxY , point . y ) ;
}
}
return { x : minX , y : minY , width : maxX - minX , height : maxY - minY } ;
}
2020-06-24 15:12:17 -07:00
async scrollRectIntoViewIfNeeded ( handle : dom.ElementHandle , rect? : types.Rect ) : Promise < 'error:notvisible' | 'error:notconnected' | 'done' > {
2020-05-22 11:15:57 -07:00
return await this . _session . send ( 'DOM.scrollIntoViewIfNeeded' , {
2020-05-27 22:19:05 -07:00
objectId : handle._objectId ,
2020-02-11 10:30:09 -08:00
rect ,
2020-06-12 14:59:26 -07:00
} ) . then ( ( ) = > 'done' as const ) . catch ( e = > {
2020-05-22 11:15:57 -07:00
if ( e instanceof Error && e . message . includes ( 'Node does not have a layout object' ) )
2020-06-24 15:12:17 -07:00
return 'error:notvisible' ;
2020-04-18 18:29:31 -07:00
if ( e instanceof Error && e . message . includes ( 'Node is detached from document' ) )
2020-06-24 15:12:17 -07:00
return 'error:notconnected' ;
2020-02-11 10:30:09 -08:00
throw e ;
} ) ;
}
2021-05-11 13:21:01 -07:00
async setScreencastOptions ( options : { width : number , height : number , quality : number } | null ) : Promise < void > {
if ( options ) {
const so = { . . . options , toolbarHeight : this._toolbarHeight ( ) } ;
const { generation } = await this . _pageProxySession . send ( 'Screencast.startScreencast' , so ) ;
2021-05-08 11:35:36 -07:00
this . _screencastGeneration = generation ;
} else {
await this . _pageProxySession . send ( 'Screencast.stopScreencast' ) ;
}
}
private _onScreencastFrame ( event : Protocol.Screencast.screencastFramePayload ) {
2021-05-12 08:35:19 -07:00
this . _pageProxySession . send ( 'Screencast.screencastFrameAck' , { generation : this._screencastGeneration } ) . catch ( e = > debugLogger . log ( 'error' , e ) ) ;
2021-05-08 11:35:36 -07:00
const buffer = Buffer . from ( event . data , 'base64' ) ;
this . _page . emit ( Page . Events . ScreencastFrame , {
buffer ,
width : event.deviceWidth ,
height : event.deviceHeight ,
} ) ;
2021-04-08 05:32:12 +08:00
}
2020-05-04 16:30:19 -07:00
rafCountForStablePosition ( ) : number {
return process . platform === 'win32' ? 5 : 1 ;
}
2019-12-19 16:53:24 -08:00
async getContentQuads ( handle : dom.ElementHandle ) : Promise < types.Quad [ ] | null > {
2020-06-05 07:50:26 -07:00
const result = await this . _session . sendMayFail ( 'DOM.getContentQuads' , {
2020-05-27 22:19:05 -07:00
objectId : handle._objectId
2020-06-05 07:50:26 -07:00
} ) ;
2019-12-19 16:53:24 -08:00
if ( ! result )
return null ;
return result . quads . map ( quad = > [
{ x : quad [ 0 ] , y : quad [ 1 ] } ,
{ x : quad [ 2 ] , y : quad [ 3 ] } ,
{ x : quad [ 4 ] , y : quad [ 5 ] } ,
{ x : quad [ 6 ] , y : quad [ 7 ] }
] ) ;
}
2020-01-13 13:33:25 -08:00
async setInputFiles ( handle : dom.ElementHandle < HTMLInputElement > , files : types.FilePayload [ ] ) : Promise < void > {
2020-05-27 22:19:05 -07:00
const objectId = handle . _objectId ;
2020-08-18 17:32:11 -07:00
const protocolFiles = files . map ( file = > ( {
name : file.name ,
type : file . mimeType ,
data : file.buffer ,
} ) ) ;
await this . _session . send ( 'DOM.setInputFiles' , { objectId , files : protocolFiles } ) ;
2019-12-19 16:53:24 -08:00
}
async adoptElementHandle < T extends Node > ( handle : dom.ElementHandle < T > , to : dom.FrameExecutionContext ) : Promise < dom.ElementHandle < T > > {
2020-06-05 07:50:26 -07:00
const result = await this . _session . sendMayFail ( 'DOM.resolveNode' , {
2020-05-27 22:19:05 -07:00
objectId : handle._objectId ,
2019-12-19 16:53:24 -08:00
executionContextId : ( to . _delegate as WKExecutionContext ) . _contextId
2020-06-05 07:50:26 -07:00
} ) ;
2019-12-19 16:53:24 -08:00
if ( ! result || result . object . subtype === 'null' )
2021-06-02 20:17:24 -07:00
throw new Error ( dom . kUnableToAdoptErrorMessage ) ;
2020-05-15 15:21:49 -07:00
return to . createHandle ( result . object ) as dom . ElementHandle < T > ;
2019-12-19 16:53:24 -08:00
}
2020-01-03 11:15:43 -08:00
2020-02-07 13:36:49 -08:00
async getAccessibilityTree ( needle? : dom.ElementHandle ) : Promise < { tree : accessibility.AXNode , needle : accessibility.AXNode | null } > {
2020-01-14 16:54:50 -08:00
return getAccessibilityTree ( this . _session , needle ) ;
2020-01-03 11:15:43 -08:00
}
2020-01-07 13:57:37 -08:00
2020-03-07 08:19:31 -08:00
async inputActionEpilogue ( ) : Promise < void > {
}
2020-02-05 17:20:23 -08:00
async getFrameElement ( frame : frames.Frame ) : Promise < dom.ElementHandle > {
const parent = frame . parentFrame ( ) ;
if ( ! parent )
throw new Error ( 'Frame has been detached.' ) ;
2021-05-08 21:25:12 -07:00
const handles = await this . _page . selectors . _queryAll ( parent , 'frame,iframe' , undefined ) ;
2020-02-05 17:20:23 -08:00
const items = await Promise . all ( handles . map ( async handle = > {
const frame = await handle . contentFrame ( ) . catch ( e = > null ) ;
return { handle , frame } ;
} ) ) ;
const result = items . find ( item = > item . frame === frame ) ;
2020-03-04 17:57:35 -08:00
items . map ( item = > item === result ? Promise . resolve ( ) : item . handle . dispose ( ) ) ;
2020-02-05 17:20:23 -08:00
if ( ! result )
throw new Error ( 'Frame has been detached.' ) ;
return result . handle ;
}
2020-01-21 14:58:12 -08:00
_onRequestWillBeSent ( session : WKSession , event : Protocol.Network.requestWillBeSentPayload ) {
if ( event . request . url . startsWith ( 'data:' ) )
return ;
2020-03-16 13:31:06 -07:00
let redirectedFrom : network.Request | null = null ;
2020-01-21 14:58:12 -08:00
if ( event . redirectResponse ) {
const request = this . _requestIdToRequest . get ( event . requestId ) ;
// If we connect late to the target, we could have missed the requestWillBeSent event.
if ( request ) {
2020-10-21 23:25:57 -07:00
this . _handleRequestRedirect ( request , event . redirectResponse , event . timestamp ) ;
2020-03-16 13:31:06 -07:00
redirectedFrom = request . request ;
2020-01-21 14:58:12 -08:00
}
}
2021-05-03 12:34:09 -07:00
const frame = redirectedFrom ? redirectedFrom . frame ( ) : this . _page . _frameManager . frame ( event . frameId ) ;
// sometimes we get stray network events for detached frames
// TODO(einbinder) why?
if ( ! frame )
return ;
2020-01-21 14:58:12 -08:00
// TODO(einbinder) this will fail if we are an XHR document request
const isNavigationRequest = event . type === 'Document' ;
const documentId = isNavigationRequest ? event.loaderId : undefined ;
2020-06-18 17:15:47 -07:00
// We do not support intercepting redirects.
const allowInterception = this . _page . _needsRequestInterception ( ) && ! redirectedFrom ;
const request = new WKInterceptableRequest ( session , allowInterception , frame , event , redirectedFrom , documentId ) ;
2020-01-21 14:58:12 -08:00
this . _requestIdToRequest . set ( event . requestId , request ) ;
this . _page . _frameManager . requestStarted ( request . request ) ;
}
2020-10-21 23:25:57 -07:00
private _handleRequestRedirect ( request : WKInterceptableRequest , responsePayload : Protocol.Network.Response , timestamp : number ) {
2020-01-21 14:58:12 -08:00
const response = request . createResponse ( responsePayload ) ;
2021-06-15 00:48:08 -07:00
response . _securityDetailsFinished ( ) ;
response . _serverAddrFinished ( ) ;
2020-10-21 23:25:57 -07:00
response . _requestFinished ( responsePayload . timing ? helper . secondsToRoundishMillis ( timestamp - request . _timestamp ) : - 1 , 'Response body is unavailable for redirect responses' ) ;
2020-01-21 14:58:12 -08:00
this . _requestIdToRequest . delete ( request . _requestId ) ;
this . _page . _frameManager . requestReceivedResponse ( response ) ;
this . _page . _frameManager . requestFinished ( request . request ) ;
}
2021-06-18 11:04:48 -07:00
_onRequestIntercepted ( session : WKSession , event : Protocol.Network.requestInterceptedPayload ) {
2020-01-21 14:58:12 -08:00
const request = this . _requestIdToRequest . get ( event . requestId ) ;
2021-05-03 12:34:09 -07:00
if ( ! request ) {
2021-06-18 11:04:48 -07:00
session . sendMayFail ( 'Network.interceptRequestWithError' , { errorType : 'Cancellation' , requestId : event.requestId } ) ;
2020-06-18 17:15:47 -07:00
return ;
2021-05-03 12:34:09 -07:00
}
2020-06-18 17:15:47 -07:00
if ( ! request . _allowInterception ) {
// Intercepted, although we do not intend to allow interception.
// Just continue.
2021-06-18 11:04:48 -07:00
session . sendMayFail ( 'Network.interceptWithRequest' , { requestId : request._requestId } ) ;
2020-06-18 17:15:47 -07:00
} else {
2020-01-21 14:58:12 -08:00
request . _interceptedCallback ( ) ;
2020-06-18 17:15:47 -07:00
}
2020-01-21 14:58:12 -08:00
}
2021-06-18 11:04:48 -07:00
_onResponseIntercepted ( session : WKSession , event : Protocol.Network.responseInterceptedPayload ) {
const request = this . _requestIdToRequest . get ( event . requestId ) ;
if ( ! request || ! request . _responseInterceptedCallback ) {
session . sendMayFail ( 'Network.interceptContinue' , { requestId : event.requestId , stage : 'response' } ) ;
return ;
}
request . _responseInterceptedCallback ( event . response ) ;
}
2020-01-21 14:58:12 -08:00
_onResponseReceived ( event : Protocol.Network.responseReceivedPayload ) {
const request = this . _requestIdToRequest . get ( event . requestId ) ;
// FileUpload sends a response without a matching request.
if ( ! request )
return ;
2021-06-15 00:48:08 -07:00
this . _requestIdToResponseReceivedPayloadEvent . set ( request . _requestId , event ) ;
2020-01-21 14:58:12 -08:00
const response = request . createResponse ( event . response ) ;
2020-10-28 13:46:05 -07:00
if ( event . response . requestHeaders && Object . keys ( event . response . requestHeaders ) . length )
request . request . updateWithRawHeaders ( headersObjectToArray ( event . response . requestHeaders ) ) ;
2020-01-21 14:58:12 -08:00
this . _page . _frameManager . requestReceivedResponse ( response ) ;
2020-03-09 11:42:56 -07:00
if ( response . status ( ) === 204 ) {
this . _onLoadingFailed ( {
requestId : event.requestId ,
errorText : 'Aborted: 204 No Content' ,
timestamp : event.timestamp
} ) ;
}
2020-01-21 14:58:12 -08:00
}
_onLoadingFinished ( event : Protocol.Network.loadingFinishedPayload ) {
const request = this . _requestIdToRequest . get ( event . requestId ) ;
// For certain requestIds we never receive requestWillBeSent event.
// @see https://crbug.com/750469
if ( ! request )
return ;
// Under certain conditions we never get the Network.responseReceived
// event from protocol. @see https://crbug.com/883475
2020-03-13 08:54:19 -07:00
const response = request . request . _existingResponse ( ) ;
2021-06-15 00:48:08 -07:00
if ( response ) {
const responseReceivedPayload = this . _requestIdToResponseReceivedPayloadEvent . get ( request . _requestId ) ;
response . _serverAddrFinished ( parseRemoteAddress ( event ? . metrics ? . remoteAddress ) ) ;
response . _securityDetailsFinished ( {
protocol : isLoadedSecurely ( response . url ( ) , response . timing ( ) ) ? event.metrics?.securityConnection?.protocol : undefined ,
subjectName : responseReceivedPayload?.response.security?.certificate?.subject ,
validFrom : responseReceivedPayload?.response.security?.certificate?.validFrom ,
validTo : responseReceivedPayload?.response.security?.certificate?.validUntil ,
} ) ;
2021-07-08 18:22:37 +02:00
const { responseBodyBytesReceived , responseHeaderBytesReceived } = event . metrics || { } ;
const transferSize = responseBodyBytesReceived ? responseBodyBytesReceived + ( responseHeaderBytesReceived || 0 ) : undefined ;
if ( event . metrics ? . protocol )
response . _setHttpVersion ( event . metrics . protocol ) ;
response . _requestFinished ( helper . secondsToRoundishMillis ( event . timestamp - request . _timestamp ) , undefined , transferSize ) ;
2021-06-15 00:48:08 -07:00
}
this . _requestIdToResponseReceivedPayloadEvent . delete ( request . _requestId ) ;
2020-01-21 14:58:12 -08:00
this . _requestIdToRequest . delete ( request . _requestId ) ;
this . _page . _frameManager . requestFinished ( request . request ) ;
}
_onLoadingFailed ( event : Protocol.Network.loadingFailedPayload ) {
const request = this . _requestIdToRequest . get ( event . requestId ) ;
// For certain requestIds we never receive requestWillBeSent event.
// @see https://crbug.com/750469
if ( ! request )
return ;
2020-03-13 08:54:19 -07:00
const response = request . request . _existingResponse ( ) ;
2021-06-15 00:48:08 -07:00
if ( response ) {
response . _serverAddrFinished ( ) ;
response . _securityDetailsFinished ( ) ;
2020-10-21 23:25:57 -07:00
response . _requestFinished ( helper . secondsToRoundishMillis ( event . timestamp - request . _timestamp ) ) ;
2021-06-15 00:48:08 -07:00
}
2020-01-21 14:58:12 -08:00
this . _requestIdToRequest . delete ( request . _requestId ) ;
request . request . _setFailureText ( event . errorText ) ;
this . _page . _frameManager . requestFailed ( request . request , event . errorText . includes ( 'cancelled' ) ) ;
}
2020-03-20 19:45:35 -07:00
async _grantPermissions ( origin : string , permissions : string [ ] ) {
const webPermissionToProtocol = new Map < string , string > ( [
[ 'geolocation' , 'geolocation' ] ,
] ) ;
const filtered = permissions . map ( permission = > {
const protocolPermission = webPermissionToProtocol . get ( permission ) ;
if ( ! protocolPermission )
throw new Error ( 'Unknown permission: ' + permission ) ;
return protocolPermission ;
} ) ;
await this . _pageProxySession . send ( 'Emulation.grantPermissions' , { origin , permissions : filtered } ) ;
}
async _clearPermissions() {
await this . _pageProxySession . send ( 'Emulation.resetPermissions' , { } ) ;
}
2019-12-19 16:53:24 -08:00
}
2021-05-17 18:32:29 +00:00
function webkitWorldName ( world : types.World ) {
switch ( world ) {
case 'main' : return undefined ;
case 'utility' : return UTILITY_WORLD_NAME ;
}
}
2021-06-15 00:48:08 -07:00
/ * *
* WebKit Remote Addresses look like :
*
* macOS :
* : : 1.8911
* 2606 :2800 : 220 :1 : 248 :1893 : 25c8 :1946.443
* 127.0.0.1 :8000
*
* ubuntu :
* : : 1 :8907
* 127.0.0.1 :8000
*
* NB : They look IPv4 and IPv6 ' s with ports but use an alternative notation .
* /
function parseRemoteAddress ( value? : string ) {
if ( ! value )
return ;
try {
const colon = value . lastIndexOf ( ':' ) ;
const dot = value . lastIndexOf ( '.' ) ;
if ( dot < 0 ) { // IPv6ish:port
return {
ipAddress : ` [ ${ value . slice ( 0 , colon ) } ] ` ,
port : + value . slice ( colon + 1 )
} ;
}
if ( colon > dot ) { // IPv4:port
const [ address , port ] = value . split ( ':' ) ;
return {
ipAddress : address ,
port : + port ,
} ;
} else { // IPv6ish.port
const [ address , port ] = value . split ( '.' ) ;
return {
ipAddress : ` [ ${ address } ] ` ,
port : + port ,
} ;
}
} catch ( _ ) { }
}
/ * *
* Adapted from Source / WebInspectorUI / UserInterface / Models / Resource . js in
* WebKit codebase .
* /
function isLoadedSecurely ( url : string , timing : network.ResourceTiming ) {
try {
const u = new URL ( url ) ;
if ( u . protocol !== 'https:' && u . protocol !== 'wss:' && u . protocol !== 'sftp:' )
return false ;
if ( timing . secureConnectionStart === - 1 && timing . connectStart !== - 1 )
return false ;
return true ;
} catch ( _ ) { }
}