2020-03-20 20:28:31 +00:00
import { expect } from 'chai' ;
2020-04-07 00:04:09 +00:00
import { BrowserWindow , WebContents , session , ipcMain , app , protocol , webContents } from 'electron/main' ;
2021-01-13 21:49:35 +00:00
import { emittedOnce } from './events-helpers' ;
2020-03-20 20:28:31 +00:00
import { closeAllWindows } from './window-helpers' ;
import * as https from 'https' ;
import * as http from 'http' ;
import * as path from 'path' ;
import * as fs from 'fs' ;
import * as url from 'url' ;
import * as ChildProcess from 'child_process' ;
import { EventEmitter } from 'events' ;
import { promisify } from 'util' ;
2020-06-30 22:10:36 +00:00
import { ifit , ifdescribe , delay , defer } from './spec-helpers' ;
2020-03-20 20:28:31 +00:00
import { AddressInfo } from 'net' ;
2020-05-08 04:27:55 +00:00
import { PipeTransport } from './pipe-transport' ;
2020-03-20 20:28:31 +00:00
2020-06-23 03:32:45 +00:00
const features = process . _linkedBinding ( 'electron_common_features' ) ;
2020-03-20 20:28:31 +00:00
const fixturesPath = path . resolve ( __dirname , '..' , 'spec' , 'fixtures' ) ;
2019-05-29 23:33:19 +00:00
describe ( 'reporting api' , ( ) = > {
2020-12-09 17:16:41 +00:00
// TODO(nornagon): this started failing a lot on CI. Figure out why and fix
// it.
it . skip ( 'sends a report for a deprecation' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const reports = new EventEmitter ( ) ;
2019-05-29 23:33:19 +00:00
// The Reporting API only works on https with valid certs. To dodge having
// to set up a trusted certificate, hack the validator.
session . defaultSession . setCertificateVerifyProc ( ( req , cb ) = > {
2020-03-20 20:28:31 +00:00
cb ( 0 ) ;
} ) ;
const certPath = path . join ( fixturesPath , 'certificates' ) ;
2019-05-29 23:33:19 +00:00
const options = {
key : fs.readFileSync ( path . join ( certPath , 'server.key' ) ) ,
cert : fs.readFileSync ( path . join ( certPath , 'server.pem' ) ) ,
ca : [
fs . readFileSync ( path . join ( certPath , 'rootCA.pem' ) ) ,
fs . readFileSync ( path . join ( certPath , 'intermediateCA.pem' ) )
] ,
requestCert : true ,
rejectUnauthorized : false
2020-03-20 20:28:31 +00:00
} ;
2019-05-29 23:33:19 +00:00
const server = https . createServer ( options , ( req , res ) = > {
if ( req . url === '/report' ) {
2020-03-20 20:28:31 +00:00
let data = '' ;
req . on ( 'data' , ( d ) = > { data += d . toString ( 'utf-8' ) ; } ) ;
2019-05-29 23:33:19 +00:00
req . on ( 'end' , ( ) = > {
2020-03-20 20:28:31 +00:00
reports . emit ( 'report' , JSON . parse ( data ) ) ;
} ) ;
2019-05-29 23:33:19 +00:00
}
res . setHeader ( 'Report-To' , JSON . stringify ( {
group : 'default' ,
max_age : 120 ,
2020-03-20 15:12:18 +00:00
endpoints : [ { url : ` https://localhost: ${ ( server . address ( ) as any ) . port } /report ` } ]
2020-03-20 20:28:31 +00:00
} ) ) ;
res . setHeader ( 'Content-Type' , 'text/html' ) ;
2019-05-29 23:33:19 +00:00
// using the deprecated `webkitRequestAnimationFrame` will trigger a
// "deprecation" report.
2020-03-20 20:28:31 +00:00
res . end ( '<script>webkitRequestAnimationFrame(() => {})</script>' ) ;
} ) ;
2021-01-22 19:25:47 +00:00
await new Promise < void > ( resolve = > server . listen ( 0 , '127.0.0.1' , resolve ) ) ;
2019-05-29 23:33:19 +00:00
const bw = new BrowserWindow ( {
2019-11-01 20:37:02 +00:00
show : false
2020-03-20 20:28:31 +00:00
} ) ;
2019-05-29 23:33:19 +00:00
try {
2020-03-20 20:28:31 +00:00
const reportGenerated = emittedOnce ( reports , 'report' ) ;
const url = ` https://localhost: ${ ( server . address ( ) as any ) . port } /a ` ;
await bw . loadURL ( url ) ;
const [ report ] = await reportGenerated ;
expect ( report ) . to . be . an ( 'array' ) ;
expect ( report [ 0 ] . type ) . to . equal ( 'deprecation' ) ;
expect ( report [ 0 ] . url ) . to . equal ( url ) ;
expect ( report [ 0 ] . body . id ) . to . equal ( 'PrefixedRequestAnimationFrame' ) ;
2019-05-29 23:33:19 +00:00
} finally {
2020-03-20 20:28:31 +00:00
bw . destroy ( ) ;
server . close ( ) ;
2019-05-29 23:33:19 +00:00
}
2020-03-20 20:28:31 +00:00
} ) ;
} ) ;
2019-06-11 23:35:58 +00:00
describe ( 'window.postMessage' , ( ) = > {
afterEach ( async ( ) = > {
2020-03-20 20:28:31 +00:00
await closeAllWindows ( ) ;
} ) ;
2019-06-11 23:35:58 +00:00
2020-11-10 17:06:03 +00:00
for ( const nativeWindowOpen of [ true , false ] ) {
describe ( ` when nativeWindowOpen: ${ nativeWindowOpen } ` , ( ) = > {
it ( 'sets the source and origin correctly' , async ( ) = > {
const w = new BrowserWindow ( { show : false , webPreferences : { nodeIntegration : true , nativeWindowOpen } } ) ;
w . loadURL ( ` file:// ${ fixturesPath } /pages/window-open-postMessage-driver.html ` ) ;
const [ , message ] = await emittedOnce ( ipcMain , 'complete' ) ;
expect ( message . data ) . to . equal ( 'testing' ) ;
expect ( message . origin ) . to . equal ( 'file://' ) ;
expect ( message . sourceEqualsOpener ) . to . equal ( true ) ;
expect ( message . eventOrigin ) . to . equal ( 'file://' ) ;
} ) ;
} ) ;
}
2020-03-20 20:28:31 +00:00
} ) ;
2019-06-15 07:15:15 +00:00
describe ( 'focus handling' , ( ) = > {
2020-03-20 20:28:31 +00:00
let webviewContents : WebContents = null as unknown as WebContents ;
let w : BrowserWindow = null as unknown as BrowserWindow ;
2019-06-15 07:15:15 +00:00
beforeEach ( async ( ) = > {
w = new BrowserWindow ( {
show : true ,
webPreferences : {
nodeIntegration : true ,
webviewTag : true
}
2020-03-20 20:28:31 +00:00
} ) ;
2019-06-15 07:15:15 +00:00
2020-03-20 20:28:31 +00:00
const webviewReady = emittedOnce ( w . webContents , 'did-attach-webview' ) ;
await w . loadFile ( path . join ( fixturesPath , 'pages' , 'tab-focus-loop-elements.html' ) ) ;
const [ , wvContents ] = await webviewReady ;
webviewContents = wvContents ;
await emittedOnce ( webviewContents , 'did-finish-load' ) ;
w . focus ( ) ;
} ) ;
2019-06-15 07:15:15 +00:00
afterEach ( ( ) = > {
2020-03-20 20:28:31 +00:00
webviewContents = null as unknown as WebContents ;
w . destroy ( ) ;
w = null as unknown as BrowserWindow ;
} ) ;
2019-06-15 07:15:15 +00:00
const expectFocusChange = async ( ) = > {
2020-03-20 20:28:31 +00:00
const [ , focusedElementId ] = await emittedOnce ( ipcMain , 'focus-changed' ) ;
return focusedElementId ;
} ;
2019-06-15 07:15:15 +00:00
describe ( 'a TAB press' , ( ) = > {
const tabPressEvent : any = {
type : 'keyDown' ,
keyCode : 'Tab'
2020-03-20 20:28:31 +00:00
} ;
2019-06-15 07:15:15 +00:00
it ( 'moves focus to the next focusable item' , async ( ) = > {
2020-03-20 20:28:31 +00:00
let focusChange = expectFocusChange ( ) ;
w . webContents . sendInputEvent ( tabPressEvent ) ;
let focusedElementId = await focusChange ;
expect ( focusedElementId ) . to . equal ( 'BUTTON-element-1' , ` should start focused in element-1, it's instead in ${ focusedElementId } ` ) ;
focusChange = expectFocusChange ( ) ;
w . webContents . sendInputEvent ( tabPressEvent ) ;
focusedElementId = await focusChange ;
expect ( focusedElementId ) . to . equal ( 'BUTTON-element-2' , ` focus should've moved to element-2, it's instead in ${ focusedElementId } ` ) ;
focusChange = expectFocusChange ( ) ;
w . webContents . sendInputEvent ( tabPressEvent ) ;
focusedElementId = await focusChange ;
expect ( focusedElementId ) . to . equal ( 'BUTTON-wv-element-1' , ` focus should've moved to the webview's element-1, it's instead in ${ focusedElementId } ` ) ;
focusChange = expectFocusChange ( ) ;
webviewContents . sendInputEvent ( tabPressEvent ) ;
focusedElementId = await focusChange ;
expect ( focusedElementId ) . to . equal ( 'BUTTON-wv-element-2' , ` focus should've moved to the webview's element-2, it's instead in ${ focusedElementId } ` ) ;
focusChange = expectFocusChange ( ) ;
webviewContents . sendInputEvent ( tabPressEvent ) ;
focusedElementId = await focusChange ;
expect ( focusedElementId ) . to . equal ( 'BUTTON-element-3' , ` focus should've moved to element-3, it's instead in ${ focusedElementId } ` ) ;
focusChange = expectFocusChange ( ) ;
w . webContents . sendInputEvent ( tabPressEvent ) ;
focusedElementId = await focusChange ;
expect ( focusedElementId ) . to . equal ( 'BUTTON-element-1' , ` focus should've looped back to element-1, it's instead in ${ focusedElementId } ` ) ;
} ) ;
} ) ;
2019-06-15 07:15:15 +00:00
describe ( 'a SHIFT + TAB press' , ( ) = > {
const shiftTabPressEvent : any = {
type : 'keyDown' ,
modifiers : [ 'Shift' ] ,
keyCode : 'Tab'
2020-03-20 20:28:31 +00:00
} ;
2019-06-15 07:15:15 +00:00
it ( 'moves focus to the previous focusable item' , async ( ) = > {
2020-03-20 20:28:31 +00:00
let focusChange = expectFocusChange ( ) ;
w . webContents . sendInputEvent ( shiftTabPressEvent ) ;
let focusedElementId = await focusChange ;
expect ( focusedElementId ) . to . equal ( 'BUTTON-element-3' , ` should start focused in element-3, it's instead in ${ focusedElementId } ` ) ;
focusChange = expectFocusChange ( ) ;
w . webContents . sendInputEvent ( shiftTabPressEvent ) ;
focusedElementId = await focusChange ;
expect ( focusedElementId ) . to . equal ( 'BUTTON-wv-element-2' , ` focus should've moved to the webview's element-2, it's instead in ${ focusedElementId } ` ) ;
focusChange = expectFocusChange ( ) ;
webviewContents . sendInputEvent ( shiftTabPressEvent ) ;
focusedElementId = await focusChange ;
expect ( focusedElementId ) . to . equal ( 'BUTTON-wv-element-1' , ` focus should've moved to the webview's element-1, it's instead in ${ focusedElementId } ` ) ;
focusChange = expectFocusChange ( ) ;
webviewContents . sendInputEvent ( shiftTabPressEvent ) ;
focusedElementId = await focusChange ;
expect ( focusedElementId ) . to . equal ( 'BUTTON-element-2' , ` focus should've moved to element-2, it's instead in ${ focusedElementId } ` ) ;
focusChange = expectFocusChange ( ) ;
w . webContents . sendInputEvent ( shiftTabPressEvent ) ;
focusedElementId = await focusChange ;
expect ( focusedElementId ) . to . equal ( 'BUTTON-element-1' , ` focus should've moved to element-1, it's instead in ${ focusedElementId } ` ) ;
focusChange = expectFocusChange ( ) ;
w . webContents . sendInputEvent ( shiftTabPressEvent ) ;
focusedElementId = await focusChange ;
expect ( focusedElementId ) . to . equal ( 'BUTTON-element-3' , ` focus should've looped back to element-3, it's instead in ${ focusedElementId } ` ) ;
} ) ;
} ) ;
} ) ;
2019-08-02 23:56:46 +00:00
describe ( 'web security' , ( ) = > {
2020-03-20 20:28:31 +00:00
afterEach ( closeAllWindows ) ;
let server : http.Server ;
let serverUrl : string ;
2019-08-02 23:56:46 +00:00
before ( async ( ) = > {
server = http . createServer ( ( req , res ) = > {
2020-03-20 20:28:31 +00:00
res . setHeader ( 'Content-Type' , 'text/html' ) ;
res . end ( '<body>' ) ;
} ) ;
2021-01-22 19:25:47 +00:00
await new Promise < void > ( resolve = > server . listen ( 0 , '127.0.0.1' , resolve ) ) ;
2020-03-20 20:28:31 +00:00
serverUrl = ` http://localhost: ${ ( server . address ( ) as any ) . port } ` ;
} ) ;
2019-08-02 23:56:46 +00:00
after ( ( ) = > {
2020-03-20 20:28:31 +00:00
server . close ( ) ;
} ) ;
2019-08-02 23:56:46 +00:00
it ( 'engages CORB when web security is not disabled' , async ( ) = > {
2020-09-15 18:48:12 +00:00
const w = new BrowserWindow ( { show : false , webPreferences : { webSecurity : true , nodeIntegration : true } } ) ;
2020-03-20 20:28:31 +00:00
const p = emittedOnce ( ipcMain , 'success' ) ;
2019-08-02 23:56:46 +00:00
await w . loadURL ( ` data:text/html,<script>
const s = document . createElement ( 'script' )
s . src = "${serverUrl}"
// The script will load successfully but its body will be emptied out
// by CORB, so we don't expect a syntax error.
s . onload = ( ) = > { require ( 'electron' ) . ipcRenderer . send ( 'success' ) }
document . documentElement . appendChild ( s )
2020-03-20 20:28:31 +00:00
< / script > ` );
await p ;
} ) ;
2019-08-02 23:56:46 +00:00
it ( 'bypasses CORB when web security is disabled' , async ( ) = > {
2020-09-15 18:48:12 +00:00
const w = new BrowserWindow ( { show : false , webPreferences : { webSecurity : false , nodeIntegration : true } } ) ;
2020-03-20 20:28:31 +00:00
const p = emittedOnce ( ipcMain , 'success' ) ;
2019-08-02 23:56:46 +00:00
await w . loadURL ( ` data:text/html,
< script >
window . onerror = ( e ) = > { require ( 'electron' ) . ipcRenderer . send ( 'success' , e ) }
< / script >
2020-03-20 20:28:31 +00:00
< script src = "${serverUrl}" > < / script > ` );
await p ;
} ) ;
2020-08-28 01:43:08 +00:00
2020-09-16 21:55:53 +00:00
it ( 'engages CORS when web security is not disabled' , async ( ) = > {
const w = new BrowserWindow ( { show : false , webPreferences : { webSecurity : true , nodeIntegration : true } } ) ;
const p = emittedOnce ( ipcMain , 'response' ) ;
await w . loadURL ( ` data:text/html,<script>
( async function ( ) {
try {
await fetch ( '${serverUrl}' ) ;
require ( 'electron' ) . ipcRenderer . send ( 'response' , 'passed' ) ;
} catch {
require ( 'electron' ) . ipcRenderer . send ( 'response' , 'failed' ) ;
}
} ) ( ) ;
< / script > ` );
const [ , response ] = await p ;
expect ( response ) . to . equal ( 'failed' ) ;
} ) ;
it ( 'bypasses CORS when web security is disabled' , async ( ) = > {
const w = new BrowserWindow ( { show : false , webPreferences : { webSecurity : false , nodeIntegration : true } } ) ;
const p = emittedOnce ( ipcMain , 'response' ) ;
await w . loadURL ( ` data:text/html,<script>
( async function ( ) {
try {
await fetch ( '${serverUrl}' ) ;
require ( 'electron' ) . ipcRenderer . send ( 'response' , 'passed' ) ;
} catch {
require ( 'electron' ) . ipcRenderer . send ( 'response' , 'failed' ) ;
}
} ) ( ) ;
< / script > ` );
const [ , response ] = await p ;
expect ( response ) . to . equal ( 'passed' ) ;
} ) ;
2020-08-28 01:43:08 +00:00
it ( 'does not crash when multiple WebContent are created with web security disabled' , ( ) = > {
2020-09-15 18:48:12 +00:00
const options = { show : false , webPreferences : { webSecurity : false } } ;
2020-08-28 01:43:08 +00:00
const w1 = new BrowserWindow ( options ) ;
w1 . loadURL ( serverUrl ) ;
const w2 = new BrowserWindow ( options ) ;
w2 . loadURL ( serverUrl ) ;
} ) ;
2020-03-20 20:28:31 +00:00
} ) ;
2019-10-11 20:55:50 +00:00
describe ( 'command line switches' , ( ) = > {
2020-05-08 23:13:32 +00:00
let appProcess : ChildProcess.ChildProcessWithoutNullStreams | undefined ;
afterEach ( ( ) = > {
if ( appProcess && ! appProcess . killed ) {
appProcess . kill ( ) ;
appProcess = undefined ;
}
} ) ;
2019-10-11 20:55:50 +00:00
describe ( '--lang switch' , ( ) = > {
2020-03-20 20:28:31 +00:00
const currentLocale = app . getLocale ( ) ;
2020-11-18 02:13:01 +00:00
const testLocale = async ( locale : string , result : string , printEnv : boolean = false ) = > {
2020-03-20 20:28:31 +00:00
const appPath = path . join ( fixturesPath , 'api' , 'locale-check' ) ;
2020-11-18 02:13:01 +00:00
const args = [ appPath , ` --set-lang= ${ locale } ` ] ;
if ( printEnv ) {
args . push ( '--print-env' ) ;
}
appProcess = ChildProcess . spawn ( process . execPath , args ) ;
2019-10-11 20:55:50 +00:00
2020-06-30 22:10:36 +00:00
let output = '' ;
2020-03-20 20:28:31 +00:00
appProcess . stdout . on ( 'data' , ( data ) = > { output += data ; } ) ;
2020-06-30 22:10:36 +00:00
await emittedOnce ( appProcess . stdout , 'end' ) ;
output = output . replace ( /(\r\n|\n|\r)/gm , '' ) ;
expect ( output ) . to . equal ( result ) ;
2020-03-20 20:28:31 +00:00
} ;
2019-10-11 20:55:50 +00:00
2020-06-30 22:10:36 +00:00
it ( 'should set the locale' , async ( ) = > testLocale ( 'fr' , 'fr' ) ) ;
it ( 'should not set an invalid locale' , async ( ) = > testLocale ( 'asdfkl' , currentLocale ) ) ;
2020-11-18 02:13:01 +00:00
const lcAll = String ( process . env . LC_ALL ) ;
ifit ( process . platform === 'linux' ) ( 'current process has a valid LC_ALL env' , async ( ) = > {
// The LC_ALL env should not be set to DOM locale string.
expect ( lcAll ) . to . not . equal ( app . getLocale ( ) ) ;
} ) ;
ifit ( process . platform === 'linux' ) ( 'should not change LC_ALL' , async ( ) = > testLocale ( 'fr' , lcAll , true ) ) ;
ifit ( process . platform === 'linux' ) ( 'should not change LC_ALL when setting invalid locale' , async ( ) = > testLocale ( 'asdfkl' , lcAll , true ) ) ;
ifit ( process . platform === 'linux' ) ( 'should not change LC_ALL when --lang is not set' , async ( ) = > testLocale ( '' , lcAll , true ) ) ;
2020-03-20 20:28:31 +00:00
} ) ;
2019-10-11 20:55:50 +00:00
2020-05-08 04:27:55 +00:00
describe ( '--remote-debugging-pipe switch' , ( ) = > {
it ( 'should expose CDP via pipe' , async ( ) = > {
const electronPath = process . execPath ;
2020-05-08 23:13:32 +00:00
appProcess = ChildProcess . spawn ( electronPath , [ '--remote-debugging-pipe' ] , {
2020-05-08 04:27:55 +00:00
stdio : [ 'pipe' , 'pipe' , 'pipe' , 'pipe' , 'pipe' ]
} ) ;
const stdio = appProcess . stdio as unknown as [ NodeJS . ReadableStream , NodeJS . WritableStream , NodeJS . WritableStream , NodeJS . WritableStream , NodeJS . ReadableStream ] ;
const pipe = new PipeTransport ( stdio [ 3 ] , stdio [ 4 ] ) ;
const versionPromise = new Promise ( resolve = > { pipe . onmessage = resolve ; } ) ;
pipe . send ( { id : 1 , method : 'Browser.getVersion' , params : { } } ) ;
const message = ( await versionPromise ) as any ;
expect ( message . id ) . to . equal ( 1 ) ;
expect ( message . result . product ) . to . contain ( 'Chrome' ) ;
expect ( message . result . userAgent ) . to . contain ( 'Electron' ) ;
} ) ;
it ( 'should override --remote-debugging-port switch' , async ( ) = > {
const electronPath = process . execPath ;
2020-05-08 23:13:32 +00:00
appProcess = ChildProcess . spawn ( electronPath , [ '--remote-debugging-pipe' , '--remote-debugging-port=0' ] , {
2020-05-08 04:27:55 +00:00
stdio : [ 'pipe' , 'pipe' , 'pipe' , 'pipe' , 'pipe' ]
} ) ;
let stderr = '' ;
2020-05-08 23:13:32 +00:00
appProcess . stderr . on ( 'data' , ( data : string ) = > { stderr += data ; } ) ;
2020-05-08 04:27:55 +00:00
const stdio = appProcess . stdio as unknown as [ NodeJS . ReadableStream , NodeJS . WritableStream , NodeJS . WritableStream , NodeJS . WritableStream , NodeJS . ReadableStream ] ;
const pipe = new PipeTransport ( stdio [ 3 ] , stdio [ 4 ] ) ;
const versionPromise = new Promise ( resolve = > { pipe . onmessage = resolve ; } ) ;
pipe . send ( { id : 1 , method : 'Browser.getVersion' , params : { } } ) ;
const message = ( await versionPromise ) as any ;
expect ( message . id ) . to . equal ( 1 ) ;
expect ( stderr ) . to . not . include ( 'DevTools listening on' ) ;
2020-05-08 23:13:32 +00:00
} ) ;
it ( 'should shut down Electron upon Browser.close CDP command' , async ( ) = > {
const electronPath = process . execPath ;
appProcess = ChildProcess . spawn ( electronPath , [ '--remote-debugging-pipe' ] , {
stdio : [ 'pipe' , 'pipe' , 'pipe' , 'pipe' , 'pipe' ]
} ) ;
const stdio = appProcess . stdio as unknown as [ NodeJS . ReadableStream , NodeJS . WritableStream , NodeJS . WritableStream , NodeJS . WritableStream , NodeJS . ReadableStream ] ;
const pipe = new PipeTransport ( stdio [ 3 ] , stdio [ 4 ] ) ;
pipe . send ( { id : 1 , method : 'Browser.close' , params : { } } ) ;
await new Promise ( resolve = > { appProcess ! . on ( 'exit' , resolve ) ; } ) ;
2020-05-08 04:27:55 +00:00
} ) ;
} ) ;
2019-10-11 20:55:50 +00:00
describe ( '--remote-debugging-port switch' , ( ) = > {
it ( 'should display the discovery page' , ( done ) = > {
2020-03-20 20:28:31 +00:00
const electronPath = process . execPath ;
let output = '' ;
2020-05-08 23:13:32 +00:00
appProcess = ChildProcess . spawn ( electronPath , [ '--remote-debugging-port=' ] ) ;
2019-10-11 20:55:50 +00:00
appProcess . stderr . on ( 'data' , ( data ) = > {
2020-03-20 20:28:31 +00:00
output += data ;
const m = /DevTools listening on ws:\/\/127.0.0.1:(\d+)\// . exec ( output ) ;
2019-10-11 20:55:50 +00:00
if ( m ) {
2020-05-08 23:13:32 +00:00
appProcess ! . stderr . removeAllListeners ( 'data' ) ;
2020-03-20 20:28:31 +00:00
const port = m [ 1 ] ;
2019-10-11 20:55:50 +00:00
http . get ( ` http://127.0.0.1: ${ port } ` , ( res ) = > {
2020-06-30 22:10:36 +00:00
try {
expect ( res . statusCode ) . to . eql ( 200 ) ;
expect ( parseInt ( res . headers [ 'content-length' ] ! ) ) . to . be . greaterThan ( 0 ) ;
done ( ) ;
} catch ( e ) {
done ( e ) ;
} finally {
res . destroy ( ) ;
}
2020-03-20 20:28:31 +00:00
} ) ;
2019-10-11 20:55:50 +00:00
}
2020-03-20 20:28:31 +00:00
} ) ;
} ) ;
} ) ;
} ) ;
2019-10-11 20:55:50 +00:00
describe ( 'chromium features' , ( ) = > {
2020-03-20 20:28:31 +00:00
afterEach ( closeAllWindows ) ;
2019-10-11 20:55:50 +00:00
describe ( 'accessing key names also used as Node.js module names' , ( ) = > {
it ( 'does not crash' , ( done ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
w . webContents . once ( 'did-finish-load' , ( ) = > { done ( ) ; } ) ;
w . webContents . once ( 'crashed' , ( ) = > done ( new Error ( 'WebContents crashed.' ) ) ) ;
w . loadFile ( path . join ( fixturesPath , 'pages' , 'external-string.html' ) ) ;
} ) ;
} ) ;
2019-10-11 20:55:50 +00:00
describe ( 'loading jquery' , ( ) = > {
it ( 'does not crash' , ( done ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
w . webContents . once ( 'did-finish-load' , ( ) = > { done ( ) ; } ) ;
w . webContents . once ( 'crashed' , ( ) = > done ( new Error ( 'WebContents crashed.' ) ) ) ;
w . loadFile ( path . join ( __dirname , 'fixtures' , 'pages' , 'jquery.html' ) ) ;
} ) ;
} ) ;
2019-10-11 20:55:50 +00:00
describe ( 'navigator.languages' , ( ) = > {
it ( 'should return the system locale only' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const appLocale = app . getLocale ( ) ;
const w = new BrowserWindow ( { show : false } ) ;
await w . loadURL ( 'about:blank' ) ;
const languages = await w . webContents . executeJavaScript ( 'navigator.languages' ) ;
2020-05-04 17:49:29 +00:00
expect ( languages . length ) . to . be . greaterThan ( 0 ) ;
expect ( languages ) . to . contain ( appLocale ) ;
2020-03-20 20:28:31 +00:00
} ) ;
} ) ;
2019-10-11 20:55:50 +00:00
describe ( 'navigator.serviceWorker' , ( ) = > {
it ( 'should register for file scheme' , ( done ) = > {
const w = new BrowserWindow ( {
show : false ,
webPreferences : {
nodeIntegration : true ,
partition : 'sw-file-scheme-spec'
}
2020-03-20 20:28:31 +00:00
} ) ;
2019-10-11 20:55:50 +00:00
w . webContents . on ( 'ipc-message' , ( event , channel , message ) = > {
if ( channel === 'reload' ) {
2020-03-20 20:28:31 +00:00
w . webContents . reload ( ) ;
2019-10-11 20:55:50 +00:00
} else if ( channel === 'error' ) {
2020-03-20 20:28:31 +00:00
done ( message ) ;
2019-10-11 20:55:50 +00:00
} else if ( channel === 'response' ) {
2020-03-20 20:28:31 +00:00
expect ( message ) . to . equal ( 'Hello from serviceWorker!' ) ;
2019-10-11 20:55:50 +00:00
session . fromPartition ( 'sw-file-scheme-spec' ) . clearStorageData ( {
storages : [ 'serviceworkers' ]
2020-03-20 20:28:31 +00:00
} ) . then ( ( ) = > done ( ) ) ;
2019-10-11 20:55:50 +00:00
}
2020-03-20 20:28:31 +00:00
} ) ;
w . webContents . on ( 'crashed' , ( ) = > done ( new Error ( 'WebContents crashed.' ) ) ) ;
w . loadFile ( path . join ( fixturesPath , 'pages' , 'service-worker' , 'index.html' ) ) ;
} ) ;
2019-10-11 20:55:50 +00:00
it ( 'should register for intercepted file scheme' , ( done ) = > {
2020-03-20 20:28:31 +00:00
const customSession = session . fromPartition ( 'intercept-file' ) ;
2019-10-11 20:55:50 +00:00
customSession . protocol . interceptBufferProtocol ( 'file' , ( request , callback ) = > {
2020-03-20 20:28:31 +00:00
let file = url . parse ( request . url ) . pathname ! ;
if ( file [ 0 ] === '/' && process . platform === 'win32' ) file = file . slice ( 1 ) ;
2019-10-11 20:55:50 +00:00
2020-03-20 20:28:31 +00:00
const content = fs . readFileSync ( path . normalize ( file ) ) ;
const ext = path . extname ( file ) ;
let type = 'text/html' ;
2019-10-11 20:55:50 +00:00
2020-03-20 20:28:31 +00:00
if ( ext === '.js' ) type = 'application/javascript' ;
callback ( { data : content , mimeType : type } as any ) ;
} ) ;
2019-10-11 20:55:50 +00:00
const w = new BrowserWindow ( {
show : false ,
webPreferences : {
nodeIntegration : true ,
session : customSession
}
2020-03-20 20:28:31 +00:00
} ) ;
2019-10-11 20:55:50 +00:00
w . webContents . on ( 'ipc-message' , ( event , channel , message ) = > {
if ( channel === 'reload' ) {
2020-03-20 20:28:31 +00:00
w . webContents . reload ( ) ;
2019-10-11 20:55:50 +00:00
} else if ( channel === 'error' ) {
2020-03-20 20:28:31 +00:00
done ( ` unexpected error : ${ message } ` ) ;
2019-10-11 20:55:50 +00:00
} else if ( channel === 'response' ) {
2020-03-20 20:28:31 +00:00
expect ( message ) . to . equal ( 'Hello from serviceWorker!' ) ;
2019-10-11 20:55:50 +00:00
customSession . clearStorageData ( {
storages : [ 'serviceworkers' ]
} ) . then ( ( ) = > {
2020-06-02 16:46:18 +00:00
customSession . protocol . uninterceptProtocol ( 'file' ) ;
done ( ) ;
2020-03-20 20:28:31 +00:00
} ) ;
2019-10-11 20:55:50 +00:00
}
2020-03-20 20:28:31 +00:00
} ) ;
w . webContents . on ( 'crashed' , ( ) = > done ( new Error ( 'WebContents crashed.' ) ) ) ;
w . loadFile ( path . join ( fixturesPath , 'pages' , 'service-worker' , 'index.html' ) ) ;
} ) ;
2020-07-28 01:48:37 +00:00
it ( 'should not crash when nodeIntegration is enabled' , ( done ) = > {
const w = new BrowserWindow ( {
show : false ,
webPreferences : {
nodeIntegration : true ,
2020-08-18 16:31:24 +00:00
nodeIntegrationInWorker : true ,
partition : 'sw-file-scheme-worker-spec'
2020-07-28 01:48:37 +00:00
}
} ) ;
w . webContents . on ( 'ipc-message' , ( event , channel , message ) = > {
if ( channel === 'reload' ) {
w . webContents . reload ( ) ;
} else if ( channel === 'error' ) {
done ( ` unexpected error : ${ message } ` ) ;
} else if ( channel === 'response' ) {
expect ( message ) . to . equal ( 'Hello from serviceWorker!' ) ;
2020-08-18 16:31:24 +00:00
session . fromPartition ( 'sw-file-scheme-worker-spec' ) . clearStorageData ( {
storages : [ 'serviceworkers' ]
} ) . then ( ( ) = > done ( ) ) ;
2020-07-28 01:48:37 +00:00
}
} ) ;
w . webContents . on ( 'crashed' , ( ) = > done ( new Error ( 'WebContents crashed.' ) ) ) ;
w . loadFile ( path . join ( fixturesPath , 'pages' , 'service-worker' , 'index.html' ) ) ;
} ) ;
2020-03-20 20:28:31 +00:00
} ) ;
2019-10-11 20:55:50 +00:00
describe ( 'navigator.geolocation' , ( ) = > {
before ( function ( ) {
if ( ! features . isFakeLocationProviderEnabled ( ) ) {
2020-03-20 20:28:31 +00:00
return this . skip ( ) ;
2019-10-11 20:55:50 +00:00
}
2020-03-20 20:28:31 +00:00
} ) ;
2019-10-11 20:55:50 +00:00
2020-07-01 19:14:38 +00:00
it ( 'returns error when permission is denied' , async ( ) = > {
2019-10-11 20:55:50 +00:00
const w = new BrowserWindow ( {
show : false ,
webPreferences : {
nodeIntegration : true ,
partition : 'geolocation-spec'
}
2020-03-20 20:28:31 +00:00
} ) ;
2020-07-01 19:14:38 +00:00
const message = emittedOnce ( w . webContents , 'ipc-message' ) ;
2019-10-11 20:55:50 +00:00
w . webContents . session . setPermissionRequestHandler ( ( wc , permission , callback ) = > {
if ( permission === 'geolocation' ) {
2020-03-20 20:28:31 +00:00
callback ( false ) ;
2019-10-11 20:55:50 +00:00
} else {
2020-03-20 20:28:31 +00:00
callback ( true ) ;
2019-10-11 20:55:50 +00:00
}
2020-03-20 20:28:31 +00:00
} ) ;
w . loadFile ( path . join ( fixturesPath , 'pages' , 'geolocation' , 'index.html' ) ) ;
2020-07-01 19:14:38 +00:00
const [ , channel ] = await message ;
expect ( channel ) . to . equal ( 'success' , 'unexpected response from geolocation api' ) ;
2020-03-20 20:28:31 +00:00
} ) ;
} ) ;
2019-10-11 20:55:50 +00:00
2019-12-11 06:46:25 +00:00
describe ( 'form submit' , ( ) = > {
2020-03-20 20:28:31 +00:00
let server : http.Server ;
let serverUrl : string ;
2019-12-11 06:46:25 +00:00
before ( async ( ) = > {
server = http . createServer ( ( req , res ) = > {
2020-03-20 20:28:31 +00:00
let body = '' ;
2019-12-11 06:46:25 +00:00
req . on ( 'data' , ( chunk ) = > {
2020-03-20 20:28:31 +00:00
body += chunk ;
} ) ;
res . setHeader ( 'Content-Type' , 'application/json' ) ;
2019-12-11 06:46:25 +00:00
req . on ( 'end' , ( ) = > {
2020-03-20 20:28:31 +00:00
res . end ( ` body: ${ body } ` ) ;
} ) ;
} ) ;
2021-01-22 19:25:47 +00:00
await new Promise < void > ( resolve = > server . listen ( 0 , '127.0.0.1' , resolve ) ) ;
2020-03-20 20:28:31 +00:00
serverUrl = ` http://localhost: ${ ( server . address ( ) as any ) . port } ` ;
} ) ;
2019-12-11 06:46:25 +00:00
after ( async ( ) = > {
2020-03-20 20:28:31 +00:00
server . close ( ) ;
await closeAllWindows ( ) ;
2019-12-11 06:46:25 +00:00
} ) ;
[ true , false ] . forEach ( ( isSandboxEnabled ) = >
describe ( ` sandbox= ${ isSandboxEnabled } ` , ( ) = > {
2020-03-20 15:12:18 +00:00
it ( 'posts data in the same window' , async ( ) = > {
2019-12-11 06:46:25 +00:00
const w = new BrowserWindow ( {
show : false ,
webPreferences : {
sandbox : isSandboxEnabled
}
2020-03-20 20:28:31 +00:00
} ) ;
2019-12-11 06:46:25 +00:00
2020-03-20 20:28:31 +00:00
await w . loadFile ( path . join ( fixturesPath , 'pages' , 'form-with-data.html' ) ) ;
2019-12-11 06:46:25 +00:00
2020-03-20 20:28:31 +00:00
const loadPromise = emittedOnce ( w . webContents , 'did-finish-load' ) ;
2019-12-11 06:46:25 +00:00
2020-03-20 15:12:18 +00:00
w . webContents . executeJavaScript ( `
const form = document . querySelector ( 'form' )
form . action = '${serverUrl}' ;
form . submit ( ) ;
2020-03-20 20:28:31 +00:00
` );
2020-03-20 15:12:18 +00:00
2020-03-20 20:28:31 +00:00
await loadPromise ;
2020-03-20 15:12:18 +00:00
2020-03-20 20:28:31 +00:00
const res = await w . webContents . executeJavaScript ( 'document.body.innerText' ) ;
expect ( res ) . to . equal ( 'body:greeting=hello' ) ;
} ) ;
2019-12-11 06:46:25 +00:00
2020-03-20 15:12:18 +00:00
it ( 'posts data to a new window with target=_blank' , async ( ) = > {
2019-12-11 06:46:25 +00:00
const w = new BrowserWindow ( {
show : false ,
webPreferences : {
sandbox : isSandboxEnabled
}
2020-03-20 20:28:31 +00:00
} ) ;
2019-12-11 06:46:25 +00:00
2020-03-20 20:28:31 +00:00
await w . loadFile ( path . join ( fixturesPath , 'pages' , 'form-with-data.html' ) ) ;
2019-12-11 06:46:25 +00:00
2020-03-20 20:28:31 +00:00
const windowCreatedPromise = emittedOnce ( app , 'browser-window-created' ) ;
2019-12-11 06:46:25 +00:00
2020-03-20 15:12:18 +00:00
w . webContents . executeJavaScript ( `
const form = document . querySelector ( 'form' )
form . action = '${serverUrl}' ;
form . target = '_blank' ;
form . submit ( ) ;
2020-03-20 20:28:31 +00:00
` );
2020-03-20 15:12:18 +00:00
2020-03-20 20:28:31 +00:00
const [ , newWin ] = await windowCreatedPromise ;
2020-03-20 15:12:18 +00:00
2020-03-20 20:28:31 +00:00
const res = await newWin . webContents . executeJavaScript ( 'document.body.innerText' ) ;
expect ( res ) . to . equal ( 'body:greeting=hello' ) ;
} ) ;
2019-12-11 06:46:25 +00:00
} )
2020-03-20 20:28:31 +00:00
) ;
} ) ;
2019-12-11 06:46:25 +00:00
2019-10-11 20:55:50 +00:00
describe ( 'window.open' , ( ) = > {
for ( const show of [ true , false ] ) {
2020-11-10 17:06:03 +00:00
it ( ` shows the child regardless of parent visibility when parent {show= ${ show } } ` , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show } ) ;
2019-10-11 20:55:50 +00:00
// toggle visibility
if ( show ) {
2020-03-20 20:28:31 +00:00
w . hide ( ) ;
2019-10-11 20:55:50 +00:00
} else {
2020-03-20 20:28:31 +00:00
w . show ( ) ;
2019-10-11 20:55:50 +00:00
}
2020-06-30 22:10:36 +00:00
defer ( ( ) = > { w . close ( ) ; } ) ;
const newWindow = emittedOnce ( w . webContents , 'new-window' ) ;
2020-03-20 20:28:31 +00:00
w . loadFile ( path . join ( fixturesPath , 'pages' , 'window-open.html' ) ) ;
2020-06-30 22:10:36 +00:00
const [ , , , , options ] = await newWindow ;
2020-11-10 17:06:03 +00:00
expect ( options . show ) . to . equal ( true ) ;
2020-03-20 20:28:31 +00:00
} ) ;
2019-10-11 20:55:50 +00:00
}
it ( 'disables node integration when it is disabled on the parent window for chrome devtools URLs' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false , webPreferences : { nodeIntegration : true } } ) ;
w . loadURL ( 'about:blank' ) ;
2019-10-11 20:55:50 +00:00
w . webContents . executeJavaScript ( `
b = window . open ( 'devtools://devtools/bundled/inspector.html' , '' , 'nodeIntegration=no,show=no' )
2020-03-20 20:28:31 +00:00
` );
const [ , contents ] = await emittedOnce ( app , 'web-contents-created' ) ;
const typeofProcessGlobal = await contents . executeJavaScript ( 'typeof process' ) ;
expect ( typeofProcessGlobal ) . to . equal ( 'undefined' ) ;
} ) ;
2019-10-11 20:55:50 +00:00
it ( 'disables JavaScript when it is disabled on the parent window' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false , webPreferences : { nodeIntegration : true } } ) ;
w . webContents . loadURL ( 'about:blank' ) ;
2019-10-11 20:55:50 +00:00
const windowUrl = require ( 'url' ) . format ( {
pathname : ` ${ fixturesPath } /pages/window-no-javascript.html ` ,
protocol : 'file' ,
slashes : true
2020-03-20 20:28:31 +00:00
} ) ;
2019-10-11 20:55:50 +00:00
w . webContents . executeJavaScript ( `
b = window . open ( $ { JSON . stringify ( windowUrl ) } , '' , 'javascript=no,show=no' )
2020-03-20 20:28:31 +00:00
` );
const [ , contents ] = await emittedOnce ( app , 'web-contents-created' ) ;
await emittedOnce ( contents , 'did-finish-load' ) ;
2019-10-11 20:55:50 +00:00
// Click link on page
2020-03-20 20:28:31 +00:00
contents . sendInputEvent ( { type : 'mouseDown' , clickCount : 1 , x : 1 , y : 1 } ) ;
contents . sendInputEvent ( { type : 'mouseUp' , clickCount : 1 , x : 1 , y : 1 } ) ;
const [ , window ] = await emittedOnce ( app , 'browser-window-created' ) ;
2020-06-25 17:19:08 +00:00
const preferences = window . webContents . getLastWebPreferences ( ) ;
2020-03-20 20:28:31 +00:00
expect ( preferences . javascript ) . to . be . false ( ) ;
} ) ;
2019-10-11 20:55:50 +00:00
it ( 'defines a window.location getter' , async ( ) = > {
2020-03-20 20:28:31 +00:00
let targetURL : string ;
2019-10-11 20:55:50 +00:00
if ( process . platform === 'win32' ) {
2020-03-20 20:28:31 +00:00
targetURL = ` file:/// ${ fixturesPath . replace ( /\\/g , '/' ) } /pages/base-page.html ` ;
2019-10-11 20:55:50 +00:00
} else {
2020-03-20 20:28:31 +00:00
targetURL = ` file:// ${ fixturesPath } /pages/base-page.html ` ;
2019-10-11 20:55:50 +00:00
}
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
w . loadURL ( 'about:blank' ) ;
w . webContents . executeJavaScript ( ` { b = window.open( ${ JSON . stringify ( targetURL ) } ); null } ` ) ;
const [ , window ] = await emittedOnce ( app , 'browser-window-created' ) ;
await emittedOnce ( window . webContents , 'did-finish-load' ) ;
expect ( await w . webContents . executeJavaScript ( 'b.location.href' ) ) . to . equal ( targetURL ) ;
} ) ;
2019-10-11 20:55:50 +00:00
it ( 'defines a window.location setter' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
w . loadURL ( 'about:blank' ) ;
w . webContents . executeJavaScript ( '{ b = window.open("about:blank"); null }' ) ;
const [ , { webContents } ] = await emittedOnce ( app , 'browser-window-created' ) ;
await emittedOnce ( webContents , 'did-finish-load' ) ;
2019-10-11 20:55:50 +00:00
// When it loads, redirect
2020-03-20 20:28:31 +00:00
w . webContents . executeJavaScript ( ` { b.location = ${ JSON . stringify ( ` file:// ${ fixturesPath } /pages/base-page.html ` ) } ; null } ` ) ;
await emittedOnce ( webContents , 'did-finish-load' ) ;
} ) ;
2019-10-11 20:55:50 +00:00
it ( 'defines a window.location.href setter' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
w . loadURL ( 'about:blank' ) ;
w . webContents . executeJavaScript ( '{ b = window.open("about:blank"); null }' ) ;
const [ , { webContents } ] = await emittedOnce ( app , 'browser-window-created' ) ;
await emittedOnce ( webContents , 'did-finish-load' ) ;
2019-10-11 20:55:50 +00:00
// When it loads, redirect
2020-03-20 20:28:31 +00:00
w . webContents . executeJavaScript ( ` { b.location.href = ${ JSON . stringify ( ` file:// ${ fixturesPath } /pages/base-page.html ` ) } ; null } ` ) ;
await emittedOnce ( webContents , 'did-finish-load' ) ;
} ) ;
2019-10-11 20:55:50 +00:00
it ( 'open a blank page when no URL is specified' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
w . loadURL ( 'about:blank' ) ;
w . webContents . executeJavaScript ( '{ b = window.open(); null }' ) ;
const [ , { webContents } ] = await emittedOnce ( app , 'browser-window-created' ) ;
await emittedOnce ( webContents , 'did-finish-load' ) ;
expect ( await w . webContents . executeJavaScript ( 'b.location.href' ) ) . to . equal ( 'about:blank' ) ;
} ) ;
2019-10-11 20:55:50 +00:00
it ( 'open a blank page when an empty URL is specified' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
w . loadURL ( 'about:blank' ) ;
w . webContents . executeJavaScript ( '{ b = window.open(\'\'); null }' ) ;
const [ , { webContents } ] = await emittedOnce ( app , 'browser-window-created' ) ;
await emittedOnce ( webContents , 'did-finish-load' ) ;
expect ( await w . webContents . executeJavaScript ( 'b.location.href' ) ) . to . equal ( 'about:blank' ) ;
} ) ;
2019-10-11 20:55:50 +00:00
it ( 'does not throw an exception when the frameName is a built-in object property' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
w . loadURL ( 'about:blank' ) ;
w . webContents . executeJavaScript ( '{ b = window.open(\'\', \'__proto__\'); null }' ) ;
2021-02-05 19:20:58 +00:00
const [ , , frameName ] = await emittedOnce ( w . webContents , 'new-window' ) ;
expect ( frameName ) . to . equal ( '__proto__' ) ;
2020-03-20 20:28:31 +00:00
} ) ;
2020-04-22 17:48:54 +00:00
it ( 'denies custom open when nativeWindowOpen: true' , async ( ) = > {
const w = new BrowserWindow ( {
show : false ,
webPreferences : {
contextIsolation : false ,
nodeIntegration : true ,
nativeWindowOpen : true
}
} ) ;
w . loadURL ( 'about:blank' ) ;
const previousListeners = process . listeners ( 'uncaughtException' ) ;
process . removeAllListeners ( 'uncaughtException' ) ;
try {
const uncaughtException = new Promise < Error > ( resolve = > {
process . once ( 'uncaughtException' , resolve ) ;
} ) ;
expect ( await w . webContents . executeJavaScript ( ` ( ${ function ( ) {
2020-06-23 03:32:45 +00:00
const { ipc } = process . _linkedBinding ( 'electron_renderer_ipc' ) ;
2020-10-28 15:48:20 +00:00
return ipc . sendSync ( true , 'GUEST_WINDOW_MANAGER_WINDOW_OPEN' , [ '' , '' , '' ] ) ;
2020-04-22 17:48:54 +00:00
} } ) ( ) ` )).to.be.null();
const exception = await uncaughtException ;
expect ( exception . message ) . to . match ( /denied: expected native window\.open/ ) ;
} finally {
previousListeners . forEach ( l = > process . on ( 'uncaughtException' , l ) ) ;
}
} ) ;
2020-03-20 20:28:31 +00:00
} ) ;
2019-10-11 20:55:50 +00:00
describe ( 'window.opener' , ( ) = > {
it ( 'is null for main window' , async ( ) = > {
const w = new BrowserWindow ( {
show : false ,
webPreferences : {
nodeIntegration : true
}
2020-03-20 20:28:31 +00:00
} ) ;
w . loadFile ( path . join ( fixturesPath , 'pages' , 'window-opener.html' ) ) ;
const [ , channel , opener ] = await emittedOnce ( w . webContents , 'ipc-message' ) ;
expect ( channel ) . to . equal ( 'opener' ) ;
expect ( opener ) . to . equal ( null ) ;
} ) ;
} ) ;
2019-10-11 20:55:50 +00:00
describe ( 'navigator.mediaDevices' , ( ) = > {
2020-03-20 20:28:31 +00:00
afterEach ( closeAllWindows ) ;
2019-10-11 20:55:50 +00:00
afterEach ( ( ) = > {
2020-03-20 20:28:31 +00:00
session . defaultSession . setPermissionCheckHandler ( null ) ;
} ) ;
2019-10-11 20:55:50 +00:00
it ( 'can return labels of enumerated devices' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
w . loadFile ( path . join ( fixturesPath , 'pages' , 'blank.html' ) ) ;
const labels = await w . webContents . executeJavaScript ( 'navigator.mediaDevices.enumerateDevices().then(ds => ds.map(d => d.label))' ) ;
expect ( labels . some ( ( l : any ) = > l ) ) . to . be . true ( ) ;
} ) ;
2019-10-11 20:55:50 +00:00
it ( 'does not return labels of enumerated devices when permission denied' , async ( ) = > {
2020-03-20 20:28:31 +00:00
session . defaultSession . setPermissionCheckHandler ( ( ) = > false ) ;
const w = new BrowserWindow ( { show : false } ) ;
w . loadFile ( path . join ( fixturesPath , 'pages' , 'blank.html' ) ) ;
const labels = await w . webContents . executeJavaScript ( 'navigator.mediaDevices.enumerateDevices().then(ds => ds.map(d => d.label))' ) ;
expect ( labels . some ( ( l : any ) = > l ) ) . to . be . false ( ) ;
} ) ;
2019-10-11 20:55:50 +00:00
2020-02-27 00:03:55 +00:00
it ( 'returns the same device ids across reloads' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const ses = session . fromPartition ( 'persist:media-device-id' ) ;
2020-02-27 00:03:55 +00:00
const w = new BrowserWindow ( {
show : false ,
webPreferences : {
nodeIntegration : true ,
session : ses
}
2020-03-20 20:28:31 +00:00
} ) ;
w . loadFile ( path . join ( fixturesPath , 'pages' , 'media-id-reset.html' ) ) ;
const [ , firstDeviceIds ] = await emittedOnce ( ipcMain , 'deviceIds' ) ;
const [ , secondDeviceIds ] = await emittedOnce ( ipcMain , 'deviceIds' , ( ) = > w . webContents . reload ( ) ) ;
expect ( firstDeviceIds ) . to . deep . equal ( secondDeviceIds ) ;
} ) ;
2020-02-27 00:03:55 +00:00
2019-10-11 20:55:50 +00:00
it ( 'can return new device id when cookie storage is cleared' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const ses = session . fromPartition ( 'persist:media-device-id' ) ;
2019-10-11 20:55:50 +00:00
const w = new BrowserWindow ( {
show : false ,
webPreferences : {
nodeIntegration : true ,
session : ses
}
2020-03-20 20:28:31 +00:00
} ) ;
w . loadFile ( path . join ( fixturesPath , 'pages' , 'media-id-reset.html' ) ) ;
const [ , firstDeviceIds ] = await emittedOnce ( ipcMain , 'deviceIds' ) ;
await ses . clearStorageData ( { storages : [ 'cookies' ] } ) ;
const [ , secondDeviceIds ] = await emittedOnce ( ipcMain , 'deviceIds' , ( ) = > w . webContents . reload ( ) ) ;
expect ( firstDeviceIds ) . to . not . deep . equal ( secondDeviceIds ) ;
} ) ;
} ) ;
2019-10-14 16:00:34 +00:00
describe ( 'window.opener access' , ( ) = > {
2020-03-20 20:28:31 +00:00
const scheme = 'app' ;
2019-10-14 16:00:34 +00:00
2020-03-20 20:28:31 +00:00
const fileUrl = ` file:// ${ fixturesPath } /pages/window-opener-location.html ` ;
const httpUrl1 = ` ${ scheme } ://origin1 ` ;
const httpUrl2 = ` ${ scheme } ://origin2 ` ;
const fileBlank = ` file:// ${ fixturesPath } /pages/blank.html ` ;
const httpBlank = ` ${ scheme } ://origin1/blank ` ;
2019-10-14 16:00:34 +00:00
const table = [
2019-11-01 20:37:02 +00:00
{ parent : fileBlank , child : httpUrl1 , nodeIntegration : false , nativeWindowOpen : false , openerAccessible : false } ,
{ parent : fileBlank , child : httpUrl1 , nodeIntegration : false , nativeWindowOpen : true , openerAccessible : false } ,
{ parent : fileBlank , child : httpUrl1 , nodeIntegration : true , nativeWindowOpen : false , openerAccessible : true } ,
{ parent : fileBlank , child : httpUrl1 , nodeIntegration : true , nativeWindowOpen : true , openerAccessible : false } ,
2019-10-14 16:00:34 +00:00
2019-11-01 20:37:02 +00:00
{ parent : httpBlank , child : fileUrl , nodeIntegration : false , nativeWindowOpen : false , openerAccessible : false } ,
// {parent: httpBlank, child: fileUrl, nodeIntegration: false, nativeWindowOpen: true, openerAccessible: false}, // can't window.open()
{ parent : httpBlank , child : fileUrl , nodeIntegration : true , nativeWindowOpen : false , openerAccessible : true } ,
// {parent: httpBlank, child: fileUrl, nodeIntegration: true, nativeWindowOpen: true, openerAccessible: false}, // can't window.open()
2019-10-14 16:00:34 +00:00
// NB. this is different from Chrome's behavior, which isolates file: urls from each other
2019-11-01 20:37:02 +00:00
{ parent : fileBlank , child : fileUrl , nodeIntegration : false , nativeWindowOpen : false , openerAccessible : true } ,
{ parent : fileBlank , child : fileUrl , nodeIntegration : false , nativeWindowOpen : true , openerAccessible : true } ,
{ parent : fileBlank , child : fileUrl , nodeIntegration : true , nativeWindowOpen : false , openerAccessible : true } ,
{ parent : fileBlank , child : fileUrl , nodeIntegration : true , nativeWindowOpen : true , openerAccessible : true } ,
{ parent : httpBlank , child : httpUrl1 , nodeIntegration : false , nativeWindowOpen : false , openerAccessible : true } ,
{ parent : httpBlank , child : httpUrl1 , nodeIntegration : false , nativeWindowOpen : true , openerAccessible : true } ,
{ parent : httpBlank , child : httpUrl1 , nodeIntegration : true , nativeWindowOpen : false , openerAccessible : true } ,
{ parent : httpBlank , child : httpUrl1 , nodeIntegration : true , nativeWindowOpen : true , openerAccessible : true } ,
{ parent : httpBlank , child : httpUrl2 , nodeIntegration : false , nativeWindowOpen : false , openerAccessible : false } ,
{ parent : httpBlank , child : httpUrl2 , nodeIntegration : false , nativeWindowOpen : true , openerAccessible : false } ,
{ parent : httpBlank , child : httpUrl2 , nodeIntegration : true , nativeWindowOpen : false , openerAccessible : true } ,
{ parent : httpBlank , child : httpUrl2 , nodeIntegration : true , nativeWindowOpen : true , openerAccessible : false }
2020-03-20 20:28:31 +00:00
] ;
const s = ( url : string ) = > url . startsWith ( 'file' ) ? 'file://...' : url ;
2019-10-14 16:00:34 +00:00
2020-06-02 16:46:18 +00:00
before ( ( ) = > {
protocol . registerFileProtocol ( scheme , ( request , callback ) = > {
2019-10-16 15:12:31 +00:00
if ( request . url . includes ( 'blank' ) ) {
2020-03-20 20:28:31 +00:00
callback ( ` ${ fixturesPath } /pages/blank.html ` ) ;
2019-10-16 15:12:31 +00:00
} else {
2020-03-20 20:28:31 +00:00
callback ( ` ${ fixturesPath } /pages/window-opener-location.html ` ) ;
2019-10-16 15:12:31 +00:00
}
2020-03-20 20:28:31 +00:00
} ) ;
} ) ;
2020-06-02 16:46:18 +00:00
after ( ( ) = > {
protocol . unregisterProtocol ( scheme ) ;
2020-03-20 20:28:31 +00:00
} ) ;
afterEach ( closeAllWindows ) ;
2019-10-14 16:00:34 +00:00
2019-10-16 15:12:31 +00:00
describe ( 'when opened from main window' , ( ) = > {
2019-11-01 20:37:02 +00:00
for ( const { parent , child , nodeIntegration , nativeWindowOpen , openerAccessible } of table ) {
2020-01-06 21:23:03 +00:00
for ( const sandboxPopup of [ false , true ] ) {
2020-03-20 20:28:31 +00:00
const description = ` when parent= ${ s ( parent ) } opens child= ${ s ( child ) } with nodeIntegration= ${ nodeIntegration } nativeWindowOpen= ${ nativeWindowOpen } sandboxPopup= ${ sandboxPopup } , child should ${ openerAccessible ? '' : 'not ' } be able to access opener ` ;
2020-01-06 21:23:03 +00:00
it ( description , async ( ) = > {
2020-11-10 17:06:03 +00:00
const w = new BrowserWindow ( { show : true , webPreferences : { nodeIntegration : true , nativeWindowOpen } } ) ;
w . webContents . setWindowOpenHandler ( ( ) = > ( {
action : 'allow' ,
overrideBrowserWindowOptions : {
webPreferences : {
sandbox : sandboxPopup
}
}
} ) ) ;
2020-03-20 20:28:31 +00:00
await w . loadURL ( parent ) ;
2020-01-06 21:23:03 +00:00
const childOpenerLocation = await w . webContents . executeJavaScript ( ` new Promise(resolve => {
window . addEventListener ( 'message' , function f ( e ) {
resolve ( e . data )
} )
window . open ( $ { JSON . stringify ( child ) } , "" , "show=no,nodeIntegration=${nodeIntegration ? 'yes' : 'no'}" )
2020-03-20 20:28:31 +00:00
} ) ` );
2020-01-06 21:23:03 +00:00
if ( openerAccessible ) {
2020-03-20 20:28:31 +00:00
expect ( childOpenerLocation ) . to . be . a ( 'string' ) ;
2020-01-06 21:23:03 +00:00
} else {
2020-03-20 20:28:31 +00:00
expect ( childOpenerLocation ) . to . be . null ( ) ;
2020-01-06 21:23:03 +00:00
}
2020-03-20 20:28:31 +00:00
} ) ;
2020-01-06 21:23:03 +00:00
}
2019-10-16 15:12:31 +00:00
}
2020-03-20 20:28:31 +00:00
} ) ;
2019-10-16 15:12:31 +00:00
describe ( 'when opened from <webview>' , ( ) = > {
2019-11-01 20:37:02 +00:00
for ( const { parent , child , nodeIntegration , nativeWindowOpen , openerAccessible } of table ) {
2020-03-20 20:28:31 +00:00
const description = ` when parent= ${ s ( parent ) } opens child= ${ s ( child ) } with nodeIntegration= ${ nodeIntegration } nativeWindowOpen= ${ nativeWindowOpen } , child should ${ openerAccessible ? '' : 'not ' } be able to access opener ` ;
2019-10-16 15:12:31 +00:00
// WebView erroneously allows access to the parent window when nativeWindowOpen is false.
2020-03-20 20:28:31 +00:00
const skip = ! nativeWindowOpen && ! openerAccessible ;
2019-10-16 15:12:31 +00:00
ifit ( ! skip ) ( description , async ( ) = > {
// This test involves three contexts:
// 1. The root BrowserWindow in which the test is run,
// 2. A <webview> belonging to the root window,
// 3. A window opened by calling window.open() from within the <webview>.
// We are testing whether context (3) can access context (2) under various conditions.
// This is context (1), the base window for the test.
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false , webPreferences : { nodeIntegration : true , webviewTag : true } } ) ;
await w . loadURL ( 'about:blank' ) ;
2019-10-16 15:12:31 +00:00
const parentCode = ` new Promise((resolve) => {
// This is context (3), a child window of the WebView.
const child = window . open ( $ { JSON . stringify ( child ) } , "" , "show=no" )
window . addEventListener ( "message" , e = > {
resolve ( e . data )
} )
2020-03-20 20:28:31 +00:00
} ) ` ;
2019-10-16 15:12:31 +00:00
const childOpenerLocation = await w . webContents . executeJavaScript ( ` new Promise((resolve, reject) => {
// This is context (2), a WebView which will call window.open()
const webview = new WebView ( )
2019-11-01 20:37:02 +00:00
webview . setAttribute ( 'nodeintegration' , '${nodeIntegration ? ' on ' : ' off '}' )
webview . setAttribute ( 'webpreferences' , 'nativeWindowOpen=${nativeWindowOpen ? ' yes ' : ' no '}' )
2019-10-16 15:12:31 +00:00
webview . setAttribute ( 'allowpopups' , 'on' )
webview . src = $ { JSON . stringify ( parent + '?p=' + encodeURIComponent ( child ) ) }
webview . addEventListener ( 'dom-ready' , async ( ) = > {
webview . executeJavaScript ( $ { JSON . stringify ( parentCode ) } ) . then ( resolve , reject )
} )
document . body . appendChild ( webview )
2020-03-20 20:28:31 +00:00
} ) ` );
2019-10-16 15:12:31 +00:00
if ( openerAccessible ) {
2020-03-20 20:28:31 +00:00
expect ( childOpenerLocation ) . to . be . a ( 'string' ) ;
2019-10-16 15:12:31 +00:00
} else {
2020-03-20 20:28:31 +00:00
expect ( childOpenerLocation ) . to . be . null ( ) ;
2019-10-16 15:12:31 +00:00
}
2020-03-20 20:28:31 +00:00
} ) ;
2019-10-16 15:12:31 +00:00
}
2020-03-20 20:28:31 +00:00
} ) ;
} ) ;
2019-10-16 15:12:31 +00:00
describe ( 'storage' , ( ) = > {
describe ( 'custom non standard schemes' , ( ) = > {
2020-03-20 20:28:31 +00:00
const protocolName = 'storage' ;
let contents : WebContents ;
2020-06-02 16:46:18 +00:00
before ( ( ) = > {
2019-10-16 15:12:31 +00:00
protocol . registerFileProtocol ( protocolName , ( request , callback ) = > {
2020-03-20 20:28:31 +00:00
const parsedUrl = url . parse ( request . url ) ;
let filename ;
2019-10-16 15:12:31 +00:00
switch ( parsedUrl . pathname ) {
2020-03-20 20:28:31 +00:00
case '/localStorage' : filename = 'local_storage.html' ; break ;
case '/sessionStorage' : filename = 'session_storage.html' ; break ;
case '/WebSQL' : filename = 'web_sql.html' ; break ;
case '/indexedDB' : filename = 'indexed_db.html' ; break ;
case '/cookie' : filename = 'cookie.html' ; break ;
default : filename = '' ;
2019-10-16 15:12:31 +00:00
}
2020-03-20 20:28:31 +00:00
callback ( { path : ` ${ fixturesPath } /pages/storage/ ${ filename } ` } ) ;
2020-06-02 16:46:18 +00:00
} ) ;
2020-03-20 20:28:31 +00:00
} ) ;
2019-10-16 15:12:31 +00:00
2020-06-02 16:46:18 +00:00
after ( ( ) = > {
protocol . unregisterProtocol ( protocolName ) ;
2020-03-20 20:28:31 +00:00
} ) ;
2019-10-16 15:12:31 +00:00
beforeEach ( ( ) = > {
contents = ( webContents as any ) . create ( {
nodeIntegration : true
2020-03-20 20:28:31 +00:00
} ) ;
} ) ;
2019-10-16 15:12:31 +00:00
afterEach ( ( ) = > {
2020-03-20 20:28:31 +00:00
( contents as any ) . destroy ( ) ;
contents = null as any ;
} ) ;
2019-10-16 15:12:31 +00:00
2020-06-30 22:10:36 +00:00
it ( 'cannot access localStorage' , async ( ) = > {
const response = emittedOnce ( ipcMain , 'local-storage-response' ) ;
2020-03-20 20:28:31 +00:00
contents . loadURL ( protocolName + '://host/localStorage' ) ;
2020-06-30 22:10:36 +00:00
const [ , error ] = await response ;
expect ( error ) . to . equal ( 'Failed to read the \'localStorage\' property from \'Window\': Access is denied for this document.' ) ;
2020-03-20 20:28:31 +00:00
} ) ;
2019-10-16 15:12:31 +00:00
2020-06-30 22:10:36 +00:00
it ( 'cannot access sessionStorage' , async ( ) = > {
const response = emittedOnce ( ipcMain , 'session-storage-response' ) ;
2020-03-20 20:28:31 +00:00
contents . loadURL ( ` ${ protocolName } ://host/sessionStorage ` ) ;
2020-06-30 22:10:36 +00:00
const [ , error ] = await response ;
expect ( error ) . to . equal ( 'Failed to read the \'sessionStorage\' property from \'Window\': Access is denied for this document.' ) ;
2020-03-20 20:28:31 +00:00
} ) ;
2019-10-16 15:12:31 +00:00
2020-06-30 22:10:36 +00:00
it ( 'cannot access WebSQL database' , async ( ) = > {
const response = emittedOnce ( ipcMain , 'web-sql-response' ) ;
2020-03-20 20:28:31 +00:00
contents . loadURL ( ` ${ protocolName } ://host/WebSQL ` ) ;
2020-06-30 22:10:36 +00:00
const [ , error ] = await response ;
expect ( error ) . to . equal ( 'Failed to execute \'openDatabase\' on \'Window\': Access to the WebDatabase API is denied in this context.' ) ;
2020-03-20 20:28:31 +00:00
} ) ;
2019-10-16 15:12:31 +00:00
2020-06-30 22:10:36 +00:00
it ( 'cannot access indexedDB' , async ( ) = > {
const response = emittedOnce ( ipcMain , 'indexed-db-response' ) ;
2020-03-20 20:28:31 +00:00
contents . loadURL ( ` ${ protocolName } ://host/indexedDB ` ) ;
2020-06-30 22:10:36 +00:00
const [ , error ] = await response ;
expect ( error ) . to . equal ( 'Failed to execute \'open\' on \'IDBFactory\': access to the Indexed Database API is denied in this context.' ) ;
2020-03-20 20:28:31 +00:00
} ) ;
2019-10-16 15:12:31 +00:00
2020-06-30 22:10:36 +00:00
it ( 'cannot access cookie' , async ( ) = > {
const response = emittedOnce ( ipcMain , 'cookie-response' ) ;
2020-03-20 20:28:31 +00:00
contents . loadURL ( ` ${ protocolName } ://host/cookie ` ) ;
2020-06-30 22:10:36 +00:00
const [ , error ] = await response ;
expect ( error ) . to . equal ( 'Failed to set the \'cookie\' property on \'Document\': Access is denied for this document.' ) ;
2020-03-20 20:28:31 +00:00
} ) ;
} ) ;
2019-10-16 15:12:31 +00:00
describe ( 'can be accessed' , ( ) = > {
2020-03-20 20:28:31 +00:00
let server : http.Server ;
let serverUrl : string ;
let serverCrossSiteUrl : string ;
2019-10-16 15:12:31 +00:00
before ( ( done ) = > {
server = http . createServer ( ( req , res ) = > {
const respond = ( ) = > {
if ( req . url === '/redirect-cross-site' ) {
2020-03-20 20:28:31 +00:00
res . setHeader ( 'Location' , ` ${ serverCrossSiteUrl } /redirected ` ) ;
res . statusCode = 302 ;
res . end ( ) ;
2019-10-16 15:12:31 +00:00
} else if ( req . url === '/redirected' ) {
2020-03-20 20:28:31 +00:00
res . end ( '<html><script>window.localStorage</script></html>' ) ;
2019-10-16 15:12:31 +00:00
} else {
2020-03-20 20:28:31 +00:00
res . end ( ) ;
2019-10-16 15:12:31 +00:00
}
2020-03-20 20:28:31 +00:00
} ;
setTimeout ( respond , 0 ) ;
} ) ;
2019-10-16 15:12:31 +00:00
server . listen ( 0 , '127.0.0.1' , ( ) = > {
2020-03-20 20:28:31 +00:00
serverUrl = ` http://127.0.0.1: ${ ( server . address ( ) as AddressInfo ) . port } ` ;
serverCrossSiteUrl = ` http://localhost: ${ ( server . address ( ) as AddressInfo ) . port } ` ;
done ( ) ;
} ) ;
} ) ;
2019-10-16 15:12:31 +00:00
after ( ( ) = > {
2020-03-20 20:28:31 +00:00
server . close ( ) ;
server = null as any ;
} ) ;
2019-10-16 15:12:31 +00:00
2020-03-20 20:28:31 +00:00
afterEach ( closeAllWindows ) ;
2019-10-16 15:12:31 +00:00
const testLocalStorageAfterXSiteRedirect = ( testTitle : string , extraPreferences = { } ) = > {
2020-06-30 22:10:36 +00:00
it ( testTitle , async ( ) = > {
2019-10-16 15:12:31 +00:00
const w = new BrowserWindow ( {
show : false ,
. . . extraPreferences
2020-03-20 20:28:31 +00:00
} ) ;
let redirected = false ;
2019-10-16 15:12:31 +00:00
w . webContents . on ( 'crashed' , ( ) = > {
2020-03-20 20:28:31 +00:00
expect . fail ( 'renderer crashed / was killed' ) ;
} ) ;
2019-10-16 15:12:31 +00:00
w . webContents . on ( 'did-redirect-navigation' , ( event , url ) = > {
2020-03-20 20:28:31 +00:00
expect ( url ) . to . equal ( ` ${ serverCrossSiteUrl } /redirected ` ) ;
redirected = true ;
} ) ;
2020-06-30 22:10:36 +00:00
await w . loadURL ( ` ${ serverUrl } /redirect-cross-site ` ) ;
expect ( redirected ) . to . be . true ( 'didnt redirect' ) ;
2020-03-20 20:28:31 +00:00
} ) ;
} ;
testLocalStorageAfterXSiteRedirect ( 'after a cross-site redirect' ) ;
testLocalStorageAfterXSiteRedirect ( 'after a cross-site redirect in sandbox mode' , { sandbox : true } ) ;
} ) ;
2020-05-06 19:52:59 +00:00
describe ( 'enableWebSQL webpreference' , ( ) = > {
const standardScheme = ( global as any ) . standardScheme ;
const origin = ` ${ standardScheme } ://fake-host ` ;
const filePath = path . join ( fixturesPath , 'pages' , 'storage' , 'web_sql.html' ) ;
const sqlPartition = 'web-sql-preference-test' ;
const sqlSession = session . fromPartition ( sqlPartition ) ;
const securityError = 'An attempt was made to break through the security policy of the user agent.' ;
let contents : WebContents , w : BrowserWindow ;
before ( ( ) = > {
sqlSession . protocol . registerFileProtocol ( standardScheme , ( request , callback ) = > {
callback ( { path : filePath } ) ;
} ) ;
} ) ;
after ( ( ) = > {
sqlSession . protocol . unregisterProtocol ( standardScheme ) ;
} ) ;
afterEach ( async ( ) = > {
if ( contents ) {
( contents as any ) . destroy ( ) ;
contents = null as any ;
}
await closeAllWindows ( ) ;
( w as any ) = null ;
} ) ;
it ( 'default value allows websql' , async ( ) = > {
contents = ( webContents as any ) . create ( {
session : sqlSession ,
nodeIntegration : true
} ) ;
contents . loadURL ( origin ) ;
const [ , error ] = await emittedOnce ( ipcMain , 'web-sql-response' ) ;
expect ( error ) . to . be . null ( ) ;
} ) ;
it ( 'when set to false can disallow websql' , async ( ) = > {
contents = ( webContents as any ) . create ( {
session : sqlSession ,
nodeIntegration : true ,
enableWebSQL : false
} ) ;
contents . loadURL ( origin ) ;
const [ , error ] = await emittedOnce ( ipcMain , 'web-sql-response' ) ;
expect ( error ) . to . equal ( securityError ) ;
} ) ;
it ( 'when set to false does not disable indexedDB' , async ( ) = > {
contents = ( webContents as any ) . create ( {
session : sqlSession ,
nodeIntegration : true ,
enableWebSQL : false
} ) ;
contents . loadURL ( origin ) ;
const [ , error ] = await emittedOnce ( ipcMain , 'web-sql-response' ) ;
expect ( error ) . to . equal ( securityError ) ;
const dbName = 'random' ;
const result = await contents . executeJavaScript ( `
new Promise ( ( resolve , reject ) = > {
try {
let req = window . indexedDB . open ( '${dbName}' ) ;
2020-06-11 02:07:49 +00:00
req . onsuccess = ( event ) = > {
2020-05-06 19:52:59 +00:00
let db = req . result ;
resolve ( db . name ) ;
}
req . onerror = ( event ) = > { resolve ( event . target . code ) ; }
} catch ( e ) {
resolve ( e . message ) ;
}
} ) ;
` );
expect ( result ) . to . equal ( dbName ) ;
} ) ;
it ( 'child webContents can override when the embedder has allowed websql' , async ( ) = > {
w = new BrowserWindow ( {
show : false ,
webPreferences : {
nodeIntegration : true ,
webviewTag : true ,
session : sqlSession
}
} ) ;
w . webContents . loadURL ( origin ) ;
const [ , error ] = await emittedOnce ( ipcMain , 'web-sql-response' ) ;
expect ( error ) . to . be . null ( ) ;
const webviewResult = emittedOnce ( ipcMain , 'web-sql-response' ) ;
await w . webContents . executeJavaScript ( `
new Promise ( ( resolve , reject ) = > {
const webview = new WebView ( ) ;
webview . setAttribute ( 'src' , '${origin}' ) ;
webview . setAttribute ( 'webpreferences' , 'enableWebSQL=0' ) ;
webview . setAttribute ( 'partition' , '${sqlPartition}' ) ;
webview . setAttribute ( 'nodeIntegration' , 'on' ) ;
document . body . appendChild ( webview ) ;
webview . addEventListener ( 'dom-ready' , ( ) = > resolve ( ) ) ;
} ) ;
` );
const [ , childError ] = await webviewResult ;
expect ( childError ) . to . equal ( securityError ) ;
} ) ;
it ( 'child webContents cannot override when the embedder has disallowed websql' , async ( ) = > {
w = new BrowserWindow ( {
show : false ,
webPreferences : {
nodeIntegration : true ,
enableWebSQL : false ,
webviewTag : true ,
session : sqlSession
}
} ) ;
w . webContents . loadURL ( 'data:text/html,<html></html>' ) ;
const webviewResult = emittedOnce ( ipcMain , 'web-sql-response' ) ;
await w . webContents . executeJavaScript ( `
new Promise ( ( resolve , reject ) = > {
const webview = new WebView ( ) ;
webview . setAttribute ( 'src' , '${origin}' ) ;
webview . setAttribute ( 'webpreferences' , 'enableWebSQL=1' ) ;
webview . setAttribute ( 'partition' , '${sqlPartition}' ) ;
webview . setAttribute ( 'nodeIntegration' , 'on' ) ;
document . body . appendChild ( webview ) ;
webview . addEventListener ( 'dom-ready' , ( ) = > resolve ( ) ) ;
} ) ;
` );
const [ , childError ] = await webviewResult ;
expect ( childError ) . to . equal ( securityError ) ;
} ) ;
it ( 'child webContents can use websql when the embedder has allowed websql' , async ( ) = > {
w = new BrowserWindow ( {
show : false ,
webPreferences : {
nodeIntegration : true ,
webviewTag : true ,
session : sqlSession
}
} ) ;
w . webContents . loadURL ( origin ) ;
const [ , error ] = await emittedOnce ( ipcMain , 'web-sql-response' ) ;
expect ( error ) . to . be . null ( ) ;
const webviewResult = emittedOnce ( ipcMain , 'web-sql-response' ) ;
await w . webContents . executeJavaScript ( `
new Promise ( ( resolve , reject ) = > {
const webview = new WebView ( ) ;
webview . setAttribute ( 'src' , '${origin}' ) ;
webview . setAttribute ( 'webpreferences' , 'enableWebSQL=1' ) ;
webview . setAttribute ( 'partition' , '${sqlPartition}' ) ;
webview . setAttribute ( 'nodeIntegration' , 'on' ) ;
document . body . appendChild ( webview ) ;
webview . addEventListener ( 'dom-ready' , ( ) = > resolve ( ) ) ;
} ) ;
` );
const [ , childError ] = await webviewResult ;
expect ( childError ) . to . be . null ( ) ;
} ) ;
} ) ;
2020-03-20 20:28:31 +00:00
} ) ;
2019-10-16 15:12:31 +00:00
ifdescribe ( features . isPDFViewerEnabled ( ) ) ( 'PDF Viewer' , ( ) = > {
const pdfSource = url . format ( {
2020-02-13 00:39:12 +00:00
pathname : path.join ( __dirname , 'fixtures' , 'cat.pdf' ) . replace ( /\\/g , '/' ) ,
2019-10-16 15:12:31 +00:00
protocol : 'file' ,
slashes : true
2020-03-20 20:28:31 +00:00
} ) ;
2019-10-16 15:12:31 +00:00
2021-01-28 01:09:55 +00:00
it ( 'successfully loads a PDF file' , async ( ) = > {
const w = new BrowserWindow ( { show : false } ) ;
w . loadURL ( pdfSource ) ;
await emittedOnce ( w . webContents , 'did-finish-load' ) ;
} ) ;
2020-02-13 00:39:12 +00:00
it ( 'opens when loading a pdf resource as top level navigation' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
w . loadURL ( pdfSource ) ;
2021-01-13 21:49:35 +00:00
const [ , contents ] = await emittedOnce ( app , 'web-contents-created' ) ;
expect ( contents . getURL ( ) ) . to . equal ( 'chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/index.html' ) ;
2020-03-20 20:28:31 +00:00
} ) ;
2019-10-16 15:12:31 +00:00
2020-02-13 00:39:12 +00:00
it ( 'opens when loading a pdf resource in a iframe' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
w . loadFile ( path . join ( __dirname , 'fixtures' , 'pages' , 'pdf-in-iframe.html' ) ) ;
2021-01-13 21:49:35 +00:00
const [ , contents ] = await emittedOnce ( app , 'web-contents-created' ) ;
expect ( contents . getURL ( ) ) . to . equal ( 'chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/index.html' ) ;
2020-03-20 20:28:31 +00:00
} ) ;
} ) ;
2019-10-16 15:12:31 +00:00
describe ( 'window.history' , ( ) = > {
describe ( 'window.history.pushState' , ( ) = > {
2020-01-23 18:51:41 +00:00
it ( 'should push state after calling history.pushState() from the same url' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
await w . loadFile ( path . join ( fixturesPath , 'pages' , 'blank.html' ) ) ;
2020-01-23 18:51:41 +00:00
// History should have current page by now.
2020-03-20 20:28:31 +00:00
expect ( ( w . webContents as any ) . length ( ) ) . to . equal ( 1 ) ;
2020-01-23 18:51:41 +00:00
2020-03-20 20:28:31 +00:00
const waitCommit = emittedOnce ( w . webContents , 'navigation-entry-committed' ) ;
w . webContents . executeJavaScript ( 'window.history.pushState({}, "")' ) ;
await waitCommit ;
2020-01-23 18:51:41 +00:00
// Initial page + pushed state.
2020-03-20 20:28:31 +00:00
expect ( ( w . webContents as any ) . length ( ) ) . to . equal ( 2 ) ;
} ) ;
} ) ;
} ) ;
2020-06-11 02:07:49 +00:00
describe ( 'chrome://media-internals' , ( ) = > {
it ( 'loads the page successfully' , async ( ) = > {
const w = new BrowserWindow ( { show : false } ) ;
w . loadURL ( 'chrome://media-internals' ) ;
const pageExists = await w . webContents . executeJavaScript (
"window.hasOwnProperty('chrome') && window.chrome.hasOwnProperty('send')"
) ;
expect ( pageExists ) . to . be . true ( ) ;
} ) ;
} ) ;
describe ( 'chrome://webrtc-internals' , ( ) = > {
it ( 'loads the page successfully' , async ( ) = > {
const w = new BrowserWindow ( { show : false } ) ;
w . loadURL ( 'chrome://webrtc-internals' ) ;
const pageExists = await w . webContents . executeJavaScript (
"window.hasOwnProperty('chrome') && window.chrome.hasOwnProperty('send')"
) ;
expect ( pageExists ) . to . be . true ( ) ;
} ) ;
} ) ;
2020-03-20 20:28:31 +00:00
} ) ;
2019-10-16 15:12:31 +00:00
describe ( 'font fallback' , ( ) = > {
async function getRenderedFonts ( html : string ) {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
2019-10-16 15:12:31 +00:00
try {
2020-03-20 20:28:31 +00:00
await w . loadURL ( ` data:text/html, ${ html } ` ) ;
w . webContents . debugger . attach ( ) ;
const sendCommand = ( method : string , commandParams? : any ) = > w . webContents . debugger . sendCommand ( method , commandParams ) ;
const { nodeId } = ( await sendCommand ( 'DOM.getDocument' ) ) . root . children [ 0 ] ;
await sendCommand ( 'CSS.enable' ) ;
const { fonts } = await sendCommand ( 'CSS.getPlatformFontsForNode' , { nodeId } ) ;
return fonts ;
2019-10-16 15:12:31 +00:00
} finally {
2020-03-20 20:28:31 +00:00
w . close ( ) ;
2019-10-16 15:12:31 +00:00
}
}
it ( 'should use Helvetica for sans-serif on Mac, and Arial on Windows and Linux' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const html = '<body style="font-family: sans-serif">test</body>' ;
const fonts = await getRenderedFonts ( html ) ;
expect ( fonts ) . to . be . an ( 'array' ) ;
expect ( fonts ) . to . have . length ( 1 ) ;
if ( process . platform === 'win32' ) { expect ( fonts [ 0 ] . familyName ) . to . equal ( 'Arial' ) ; } else if ( process . platform === 'darwin' ) { expect ( fonts [ 0 ] . familyName ) . to . equal ( 'Helvetica' ) ; } else if ( process . platform === 'linux' ) { expect ( fonts [ 0 ] . familyName ) . to . equal ( 'DejaVu Sans' ) ; } // I think this depends on the distro? We don't specify a default.
} ) ;
2019-10-16 15:12:31 +00:00
ifit ( process . platform !== 'linux' ) ( 'should fall back to Japanese font for sans-serif Japanese script' , async function ( ) {
const html = `
< html lang = "ja-JP" >
< head >
< meta charset = "utf-8" / >
< / head >
< body style = "font-family: sans-serif" > test 智 史 < / body >
< / html >
2020-03-20 20:28:31 +00:00
` ;
const fonts = await getRenderedFonts ( html ) ;
expect ( fonts ) . to . be . an ( 'array' ) ;
expect ( fonts ) . to . have . length ( 1 ) ;
if ( process . platform === 'win32' ) { expect ( fonts [ 0 ] . familyName ) . to . be . oneOf ( [ 'Meiryo' , 'Yu Gothic' ] ) ; } else if ( process . platform === 'darwin' ) { expect ( fonts [ 0 ] . familyName ) . to . equal ( 'Hiragino Kaku Gothic ProN' ) ; }
} ) ;
} ) ;
2019-11-05 21:34:45 +00:00
describe ( 'iframe using HTML fullscreen API while window is OS-fullscreened' , ( ) = > {
const fullscreenChildHtml = promisify ( fs . readFile ) (
path . join ( fixturesPath , 'pages' , 'fullscreen-oopif.html' )
2020-03-20 20:28:31 +00:00
) ;
let w : BrowserWindow , server : http.Server ;
2019-11-05 21:34:45 +00:00
before ( ( ) = > {
server = http . createServer ( async ( _req , res ) = > {
2020-03-20 20:28:31 +00:00
res . writeHead ( 200 , { 'Content-Type' : 'text/html' } ) ;
res . write ( await fullscreenChildHtml ) ;
res . end ( ) ;
} ) ;
2019-11-05 21:34:45 +00:00
2020-03-20 20:28:31 +00:00
server . listen ( 8989 , '127.0.0.1' ) ;
} ) ;
2019-11-05 21:34:45 +00:00
beforeEach ( ( ) = > {
w = new BrowserWindow ( {
show : true ,
fullscreen : true ,
webPreferences : {
nodeIntegration : true ,
nodeIntegrationInSubFrames : true
}
2020-03-20 20:28:31 +00:00
} ) ;
} ) ;
2019-11-05 21:34:45 +00:00
afterEach ( async ( ) = > {
await closeAllWindows ( )
2020-03-20 20:28:31 +00:00
; ( w as any ) = null ;
server . close ( ) ;
} ) ;
2019-11-05 21:34:45 +00:00
2020-06-30 22:10:36 +00:00
it ( 'can fullscreen from out-of-process iframes (OOPIFs)' , async ( ) = > {
const fullscreenChange = emittedOnce ( ipcMain , 'fullscreenChange' ) ;
const html =
'<iframe style="width: 0" frameborder=0 src="http://localhost:8989" allowfullscreen></iframe>' ;
w . loadURL ( ` data:text/html, ${ html } ` ) ;
await fullscreenChange ;
2019-11-05 21:34:45 +00:00
2020-06-30 22:10:36 +00:00
const fullscreenWidth = await w . webContents . executeJavaScript (
"document.querySelector('iframe').offsetWidth"
) ;
expect ( fullscreenWidth > 0 ) . to . be . true ( ) ;
2019-11-05 21:34:45 +00:00
2020-06-30 22:10:36 +00:00
await w . webContents . executeJavaScript (
"document.querySelector('iframe').contentWindow.postMessage('exitFullscreen', '*')"
) ;
2019-11-05 21:34:45 +00:00
2020-06-30 22:10:36 +00:00
await delay ( 500 ) ;
2019-11-05 21:34:45 +00:00
2020-06-30 22:10:36 +00:00
const width = await w . webContents . executeJavaScript (
"document.querySelector('iframe').offsetWidth"
) ;
expect ( width ) . to . equal ( 0 ) ;
2020-03-20 20:28:31 +00:00
} ) ;
2019-11-05 21:34:45 +00:00
2020-06-30 22:10:36 +00:00
it ( 'can fullscreen from in-process iframes' , async ( ) = > {
const fullscreenChange = emittedOnce ( ipcMain , 'fullscreenChange' ) ;
w . loadFile ( path . join ( fixturesPath , 'pages' , 'fullscreen-ipif.html' ) ) ;
await fullscreenChange ;
2019-11-05 21:34:45 +00:00
2020-06-30 22:10:36 +00:00
const fullscreenWidth = await w . webContents . executeJavaScript (
"document.querySelector('iframe').offsetWidth"
) ;
expect ( fullscreenWidth > 0 ) . to . true ( ) ;
2020-03-20 20:28:31 +00:00
2020-06-30 22:10:36 +00:00
await w . webContents . executeJavaScript ( 'document.exitFullscreen()' ) ;
const width = await w . webContents . executeJavaScript (
"document.querySelector('iframe').offsetWidth"
) ;
expect ( width ) . to . equal ( 0 ) ;
2020-03-20 20:28:31 +00:00
} ) ;
} ) ;
2020-09-28 16:22:03 +00:00
describe ( 'navigator.serial' , ( ) = > {
let w : BrowserWindow ;
before ( async ( ) = > {
w = new BrowserWindow ( {
show : false ,
webPreferences : {
enableBlinkFeatures : 'Serial'
}
} ) ;
await w . loadFile ( path . join ( fixturesPath , 'pages' , 'blank.html' ) ) ;
} ) ;
const getPorts : any = ( ) = > {
return w . webContents . executeJavaScript ( `
navigator . serial . requestPort ( ) . then ( port = > port . toString ( ) ) . catch ( err = > err . toString ( ) ) ;
` , true);
} ;
after ( closeAllWindows ) ;
afterEach ( ( ) = > {
session . defaultSession . setPermissionCheckHandler ( null ) ;
session . defaultSession . removeAllListeners ( 'select-serial-port' ) ;
} ) ;
it ( 'does not return a port if select-serial-port event is not defined' , async ( ) = > {
w . loadFile ( path . join ( fixturesPath , 'pages' , 'blank.html' ) ) ;
const port = await getPorts ( ) ;
expect ( port ) . to . equal ( 'NotFoundError: No port selected by the user.' ) ;
} ) ;
it ( 'does not return a port when permission denied' , async ( ) = > {
w . webContents . session . on ( 'select-serial-port' , ( event , portList , webContents , callback ) = > {
callback ( portList [ 0 ] . portId ) ;
} ) ;
session . defaultSession . setPermissionCheckHandler ( ( ) = > false ) ;
const port = await getPorts ( ) ;
expect ( port ) . to . equal ( 'NotFoundError: No port selected by the user.' ) ;
} ) ;
it ( 'returns a port when select-serial-port event is defined' , async ( ) = > {
w . webContents . session . on ( 'select-serial-port' , ( event , portList , webContents , callback ) = > {
callback ( portList [ 0 ] . portId ) ;
} ) ;
const port = await getPorts ( ) ;
expect ( port ) . to . equal ( '[object SerialPort]' ) ;
} ) ;
} ) ;
2020-10-29 18:22:32 +00:00
describe ( 'navigator.clipboard' , ( ) = > {
let w : BrowserWindow ;
before ( async ( ) = > {
w = new BrowserWindow ( {
show : false ,
webPreferences : {
enableBlinkFeatures : 'Serial'
}
} ) ;
await w . loadFile ( path . join ( fixturesPath , 'pages' , 'blank.html' ) ) ;
} ) ;
const readClipboard : any = ( ) = > {
return w . webContents . executeJavaScript ( `
navigator . clipboard . read ( ) . then ( clipboard = > clipboard . toString ( ) ) . catch ( err = > err . message ) ;
` , true);
} ;
after ( closeAllWindows ) ;
afterEach ( ( ) = > {
session . defaultSession . setPermissionRequestHandler ( null ) ;
} ) ;
it ( 'returns clipboard contents when a PermissionRequestHandler is not defined' , async ( ) = > {
const clipboard = await readClipboard ( ) ;
expect ( clipboard ) . to . not . equal ( 'Read permission denied.' ) ;
} ) ;
it ( 'returns an error when permission denied' , async ( ) = > {
session . defaultSession . setPermissionRequestHandler ( ( wc , permission , callback ) = > {
if ( permission === 'clipboard-read' ) {
callback ( false ) ;
} else {
callback ( true ) ;
}
} ) ;
const clipboard = await readClipboard ( ) ;
expect ( clipboard ) . to . equal ( 'Read permission denied.' ) ;
} ) ;
it ( 'returns clipboard contents when permission is granted' , async ( ) = > {
session . defaultSession . setPermissionRequestHandler ( ( wc , permission , callback ) = > {
if ( permission === 'clipboard-read' ) {
callback ( true ) ;
} else {
callback ( false ) ;
}
} ) ;
const clipboard = await readClipboard ( ) ;
expect ( clipboard ) . to . not . equal ( 'Read permission denied.' ) ;
} ) ;
} ) ;
2021-01-21 05:45:06 +00:00
ifdescribe ( ( process . platform !== 'linux' || app . isUnityRunning ( ) ) ) ( 'navigator.setAppBadge/clearAppBadge' , ( ) = > {
let w : BrowserWindow ;
before ( async ( ) = > {
w = new BrowserWindow ( {
show : false
} ) ;
await w . loadFile ( path . join ( fixturesPath , 'pages' , 'blank.html' ) ) ;
} ) ;
const expectedBadgeCount = 42 ;
const fireAppBadgeAction : any = ( action : string , value : any ) = > {
return w . webContents . executeJavaScript ( `
navigator . $ { action } AppBadge ( $ { value } ) . then ( ( ) = > 'success' ) . catch ( err = > err . message ) ` );
} ;
// For some reason on macOS changing the badge count doesn't happen right away, so wait
// until it changes.
async function waitForBadgeCount ( value : number ) {
let badgeCount = app . getBadgeCount ( ) ;
while ( badgeCount !== value ) {
await new Promise ( resolve = > setTimeout ( resolve , 10 ) ) ;
badgeCount = app . getBadgeCount ( ) ;
}
return badgeCount ;
}
after ( ( ) = > {
app . badgeCount = 0 ;
closeAllWindows ( ) ;
} ) ;
it ( 'setAppBadge can set a numerical value' , async ( ) = > {
const result = await fireAppBadgeAction ( 'set' , expectedBadgeCount ) ;
expect ( result ) . to . equal ( 'success' ) ;
expect ( waitForBadgeCount ( expectedBadgeCount ) ) . to . eventually . equal ( expectedBadgeCount ) ;
} ) ;
it ( 'setAppBadge can set an empty(dot) value' , async ( ) = > {
const result = await fireAppBadgeAction ( 'set' ) ;
expect ( result ) . to . equal ( 'success' ) ;
expect ( waitForBadgeCount ( 0 ) ) . to . eventually . equal ( 0 ) ;
} ) ;
it ( 'clearAppBadge can clear a value' , async ( ) = > {
let result = await fireAppBadgeAction ( 'set' , expectedBadgeCount ) ;
expect ( result ) . to . equal ( 'success' ) ;
expect ( waitForBadgeCount ( expectedBadgeCount ) ) . to . eventually . equal ( expectedBadgeCount ) ;
result = await fireAppBadgeAction ( 'clear' ) ;
expect ( result ) . to . equal ( 'success' ) ;
expect ( waitForBadgeCount ( 0 ) ) . to . eventually . equal ( 0 ) ;
} ) ;
} ) ;