2020-03-20 20:28:31 +00:00
import { expect } from 'chai' ;
import { AddressInfo } from 'net' ;
import * as path from 'path' ;
import * as fs from 'fs' ;
import * as http from 'http' ;
import * as ChildProcess from 'child_process' ;
2020-04-07 00:04:09 +00:00
import { BrowserWindow , ipcMain , webContents , session , WebContents , app } from 'electron/main' ;
import { clipboard } from 'electron/common' ;
2020-03-20 20:28:31 +00:00
import { emittedOnce } from './events-helpers' ;
import { closeAllWindows } from './window-helpers' ;
2020-06-30 22:10:36 +00:00
import { ifdescribe , ifit , delay , defer } from './spec-helpers' ;
2020-03-20 20:28:31 +00:00
const pdfjs = require ( 'pdfjs-dist' ) ;
const fixturesPath = path . resolve ( __dirname , '..' , 'spec' , 'fixtures' ) ;
2020-08-24 16:58:55 +00:00
const mainFixturesPath = path . resolve ( __dirname , 'fixtures' ) ;
2020-06-23 03:32:45 +00:00
const features = process . _linkedBinding ( 'electron_common_features' ) ;
2019-04-02 01:28:11 +00:00
describe ( 'webContents module' , ( ) = > {
describe ( 'getAllWebContents() API' , ( ) = > {
2020-03-20 20:28:31 +00:00
afterEach ( closeAllWindows ) ;
2019-04-02 01:28:11 +00:00
it ( 'returns an array of web contents' , async ( ) = > {
2019-07-26 14:09:33 +00:00
const w = new BrowserWindow ( {
show : false ,
webPreferences : { webviewTag : true }
2020-03-20 20:28:31 +00:00
} ) ;
w . loadFile ( path . join ( fixturesPath , 'pages' , 'webview-zoom-factor.html' ) ) ;
2019-04-02 01:28:11 +00:00
2020-03-20 20:28:31 +00:00
await emittedOnce ( w . webContents , 'did-attach-webview' ) ;
2019-04-02 01:28:11 +00:00
2020-03-20 20:28:31 +00:00
w . webContents . openDevTools ( ) ;
2019-04-02 01:28:11 +00:00
2020-03-20 20:28:31 +00:00
await emittedOnce ( w . webContents , 'devtools-opened' ) ;
2019-04-02 01:28:11 +00:00
const all = webContents . getAllWebContents ( ) . sort ( ( a , b ) = > {
2020-03-20 20:28:31 +00:00
return a . id - b . id ;
} ) ;
2019-04-02 01:28:11 +00:00
2020-03-20 20:28:31 +00:00
expect ( all ) . to . have . length ( 3 ) ;
expect ( all [ 0 ] . getType ( ) ) . to . equal ( 'window' ) ;
expect ( all [ all . length - 2 ] . getType ( ) ) . to . equal ( 'webview' ) ;
expect ( all [ all . length - 1 ] . getType ( ) ) . to . equal ( 'remote' ) ;
} ) ;
} ) ;
2019-05-29 20:38:14 +00:00
2020-05-18 15:04:41 +00:00
describe ( 'will-prevent-unload event' , function ( ) {
2020-03-20 20:28:31 +00:00
afterEach ( closeAllWindows ) ;
2020-05-26 13:21:38 +00:00
it ( 'does not emit if beforeunload returns undefined' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
2019-11-01 20:37:02 +00:00
w . webContents . once ( 'will-prevent-unload' , ( ) = > {
2020-03-20 20:28:31 +00:00
expect . fail ( 'should not have fired' ) ;
} ) ;
2020-06-02 02:32:39 +00:00
await w . loadFile ( path . join ( __dirname , 'fixtures' , 'api' , 'beforeunload-undefined.html' ) ) ;
2020-05-26 13:21:38 +00:00
const wait = emittedOnce ( w , 'closed' ) ;
w . close ( ) ;
await wait ;
2020-03-20 20:28:31 +00:00
} ) ;
2019-05-29 20:38:14 +00:00
2019-11-14 22:09:03 +00:00
it ( 'emits if beforeunload returns false' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
2020-06-02 02:32:39 +00:00
await w . loadFile ( path . join ( __dirname , 'fixtures' , 'api' , 'beforeunload-false.html' ) ) ;
2020-05-26 13:21:38 +00:00
w . close ( ) ;
2020-03-20 20:28:31 +00:00
await emittedOnce ( w . webContents , 'will-prevent-unload' ) ;
} ) ;
2019-05-29 20:38:14 +00:00
2019-11-14 22:09:03 +00:00
it ( 'supports calling preventDefault on will-prevent-unload events' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
w . webContents . once ( 'will-prevent-unload' , event = > event . preventDefault ( ) ) ;
2020-06-02 02:32:39 +00:00
await w . loadFile ( path . join ( __dirname , 'fixtures' , 'api' , 'beforeunload-false.html' ) ) ;
2020-05-26 13:21:38 +00:00
const wait = emittedOnce ( w , 'closed' ) ;
w . close ( ) ;
await wait ;
2020-03-20 20:28:31 +00:00
} ) ;
} ) ;
2019-07-26 14:09:33 +00:00
describe ( 'webContents.send(channel, args...)' , ( ) = > {
2020-03-20 20:28:31 +00:00
afterEach ( closeAllWindows ) ;
2019-07-26 14:09:33 +00:00
it ( 'throws an error when the channel is missing' , ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
2019-07-26 14:09:33 +00:00
expect ( ( ) = > {
2020-03-20 20:28:31 +00:00
( w . webContents . send as any ) ( ) ;
} ) . to . throw ( 'Missing required channel argument' ) ;
2019-07-26 14:09:33 +00:00
expect ( ( ) = > {
2020-03-20 20:28:31 +00:00
w . webContents . send ( null as any ) ;
} ) . to . throw ( 'Missing required channel argument' ) ;
} ) ;
2019-08-12 17:38:41 +00:00
it ( 'does not block node async APIs when sent before document is ready' , ( done ) = > {
// Please reference https://github.com/electron/electron/issues/19368 if
// this test fails.
ipcMain . once ( 'async-node-api-done' , ( ) = > {
2020-03-20 20:28:31 +00:00
done ( ) ;
} ) ;
2019-08-12 17:38:41 +00:00
const w = new BrowserWindow ( {
show : false ,
webPreferences : {
nodeIntegration : true ,
sandbox : false ,
contextIsolation : false
}
2020-03-20 20:28:31 +00:00
} ) ;
w . loadFile ( path . join ( fixturesPath , 'pages' , 'send-after-node.html' ) ) ;
2019-08-12 17:38:41 +00:00
setTimeout ( ( ) = > {
2020-03-20 20:28:31 +00:00
w . webContents . send ( 'test' ) ;
} , 50 ) ;
} ) ;
} ) ;
2019-07-26 14:09:33 +00:00
2019-10-14 20:49:21 +00:00
ifdescribe ( features . isPrintingEnabled ( ) ) ( 'webContents.print()' , ( ) = > {
2020-03-20 20:28:31 +00:00
let w : BrowserWindow ;
2020-01-31 02:49:13 +00:00
beforeEach ( ( ) = > {
2020-03-20 20:28:31 +00:00
w = new BrowserWindow ( { show : false } ) ;
} ) ;
2020-01-31 02:49:13 +00:00
2020-03-20 20:28:31 +00:00
afterEach ( closeAllWindows ) ;
2020-01-31 02:49:13 +00:00
2019-08-13 06:44:14 +00:00
it ( 'throws when invalid settings are passed' , ( ) = > {
expect ( ( ) = > {
// @ts-ignore this line is intentionally incorrect
2020-03-20 20:28:31 +00:00
w . webContents . print ( true ) ;
} ) . to . throw ( 'webContents.print(): Invalid print settings specified.' ) ;
} ) ;
2019-08-13 06:44:14 +00:00
2020-01-31 02:49:13 +00:00
it ( 'throws when an invalid callback is passed' , ( ) = > {
2019-08-13 06:44:14 +00:00
expect ( ( ) = > {
// @ts-ignore this line is intentionally incorrect
2020-03-20 20:28:31 +00:00
w . webContents . print ( { } , true ) ;
} ) . to . throw ( 'webContents.print(): Invalid optional callback provided.' ) ;
} ) ;
2019-08-13 06:44:14 +00:00
2020-01-31 02:49:13 +00:00
ifit ( process . platform !== 'linux' ) ( 'throws when an invalid deviceName is passed' , ( ) = > {
expect ( ( ) = > {
2020-03-20 20:28:31 +00:00
w . webContents . print ( { deviceName : 'i-am-a-nonexistent-printer' } , ( ) = > { } ) ;
} ) . to . throw ( 'webContents.print(): Invalid deviceName provided.' ) ;
} ) ;
2020-01-31 02:49:13 +00:00
2020-02-05 04:25:02 +00:00
it ( 'throws when an invalid pageSize is passed' , ( ) = > {
expect ( ( ) = > {
// @ts-ignore this line is intentionally incorrect
2020-03-20 20:28:31 +00:00
w . webContents . print ( { pageSize : 'i-am-a-bad-pagesize' } , ( ) = > { } ) ;
} ) . to . throw ( 'Unsupported pageSize: i-am-a-bad-pagesize' ) ;
} ) ;
2020-02-05 04:25:02 +00:00
2020-07-10 16:42:22 +00:00
it ( 'throws when an invalid custom pageSize is passed' , ( ) = > {
expect ( ( ) = > {
w . webContents . print ( {
pageSize : {
width : 100 ,
height : 200
}
} ) ;
} ) . to . throw ( 'height and width properties must be minimum 352 microns.' ) ;
} ) ;
2020-02-13 17:15:25 +00:00
it ( 'does not crash with custom margins' , ( ) = > {
2019-08-13 06:44:14 +00:00
expect ( ( ) = > {
2020-02-13 17:15:25 +00:00
w . webContents . print ( {
silent : true ,
margins : {
marginType : 'custom' ,
top : 1 ,
bottom : 1 ,
left : 1 ,
right : 1
}
2020-03-20 20:28:31 +00:00
} ) ;
} ) . to . not . throw ( ) ;
} ) ;
} ) ;
2019-08-13 06:44:14 +00:00
2019-07-26 14:09:33 +00:00
describe ( 'webContents.executeJavaScript' , ( ) = > {
describe ( 'in about:blank' , ( ) = > {
2020-03-20 20:28:31 +00:00
const expected = 'hello, world!' ;
const expectedErrorMsg = 'woops!' ;
const code = ` (() => " ${ expected } ")() ` ;
const asyncCode = ` (() => new Promise(r => setTimeout(() => r(" ${ expected } "), 500)))() ` ;
const badAsyncCode = ` (() => new Promise((r, e) => setTimeout(() => e(" ${ expectedErrorMsg } "), 500)))() ` ;
2019-07-26 14:09:33 +00:00
const errorTypes = new Set ( [
Error ,
ReferenceError ,
EvalError ,
RangeError ,
SyntaxError ,
TypeError ,
URIError
2020-03-20 20:28:31 +00:00
] ) ;
let w : BrowserWindow ;
2019-07-26 14:09:33 +00:00
before ( async ( ) = > {
2020-03-20 20:28:31 +00:00
w = new BrowserWindow ( { show : false } ) ;
await w . loadURL ( 'about:blank' ) ;
} ) ;
after ( closeAllWindows ) ;
2019-07-26 14:09:33 +00:00
it ( 'resolves the returned promise with the result' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const result = await w . webContents . executeJavaScript ( code ) ;
expect ( result ) . to . equal ( expected ) ;
} ) ;
2019-07-26 14:09:33 +00:00
it ( 'resolves the returned promise with the result if the code returns an asyncronous promise' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const result = await w . webContents . executeJavaScript ( asyncCode ) ;
expect ( result ) . to . equal ( expected ) ;
} ) ;
2019-07-26 14:09:33 +00:00
it ( 'rejects the returned promise if an async error is thrown' , async ( ) = > {
2020-03-20 20:28:31 +00:00
await expect ( w . webContents . executeJavaScript ( badAsyncCode ) ) . to . eventually . be . rejectedWith ( expectedErrorMsg ) ;
} ) ;
2019-07-26 14:09:33 +00:00
it ( 'rejects the returned promise with an error if an Error.prototype is thrown' , async ( ) = > {
for ( const error of errorTypes ) {
await expect ( w . webContents . executeJavaScript ( ` Promise.reject(new ${ error . name } ("Wamp-wamp")) ` ) )
2020-03-20 20:28:31 +00:00
. to . eventually . be . rejectedWith ( error ) ;
2019-07-26 14:09:33 +00:00
}
2020-03-20 20:28:31 +00:00
} ) ;
} ) ;
2019-07-26 14:09:33 +00:00
2019-11-01 20:37:02 +00:00
describe ( 'on a real page' , ( ) = > {
2020-03-20 20:28:31 +00:00
let w : BrowserWindow ;
2019-07-26 14:09:33 +00:00
beforeEach ( ( ) = > {
2020-03-20 20:28:31 +00:00
w = new BrowserWindow ( { show : false } ) ;
} ) ;
afterEach ( closeAllWindows ) ;
2019-07-26 14:09:33 +00:00
2020-03-20 20:28:31 +00:00
let server : http.Server = null as unknown as http . Server ;
let serverUrl : string = null as unknown as string ;
2019-07-26 14:09:33 +00:00
before ( ( done ) = > {
server = http . createServer ( ( request , response ) = > {
2020-03-20 20:28:31 +00:00
response . end ( ) ;
2019-07-26 14:09:33 +00:00
} ) . 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 ;
done ( ) ;
} ) ;
} ) ;
2019-07-26 14:09:33 +00:00
after ( ( ) = > {
2020-03-20 20:28:31 +00:00
server . close ( ) ;
} ) ;
2019-07-26 14:09:33 +00:00
2020-06-30 22:10:36 +00:00
it ( 'works after page load and during subframe load' , async ( ) = > {
await w . loadURL ( serverUrl ) ;
// initiate a sub-frame load, then try and execute script during it
await w . webContents . executeJavaScript ( `
var iframe = document . createElement ( 'iframe' )
iframe . src = '${serverUrl}/slow'
document . body . appendChild ( iframe )
null // don't return the iframe
` );
await w . webContents . executeJavaScript ( 'console.log(\'hello\')' ) ;
} ) ;
it ( 'executes after page load' , async ( ) = > {
const executeJavaScript = w . webContents . executeJavaScript ( '(() => "test")()' ) ;
2020-03-20 20:28:31 +00:00
w . loadURL ( serverUrl ) ;
2020-06-30 22:10:36 +00:00
const result = await executeJavaScript ;
expect ( result ) . to . equal ( 'test' ) ;
2020-03-20 20:28:31 +00:00
} ) ;
} ) ;
} ) ;
2019-08-30 00:45:41 +00:00
2019-11-22 23:33:55 +00:00
describe ( 'webContents.executeJavaScriptInIsolatedWorld' , ( ) = > {
2020-03-20 20:28:31 +00:00
let w : BrowserWindow ;
2019-11-22 23:33:55 +00:00
before ( async ( ) = > {
2020-03-20 20:28:31 +00:00
w = new BrowserWindow ( { show : false , webPreferences : { contextIsolation : true } } ) ;
await w . loadURL ( 'about:blank' ) ;
} ) ;
2019-11-22 23:33:55 +00:00
it ( 'resolves the returned promise with the result' , async ( ) = > {
2020-03-20 20:28:31 +00:00
await w . webContents . executeJavaScriptInIsolatedWorld ( 999 , [ { code : 'window.X = 123' } ] ) ;
const isolatedResult = await w . webContents . executeJavaScriptInIsolatedWorld ( 999 , [ { code : 'window.X' } ] ) ;
const mainWorldResult = await w . webContents . executeJavaScript ( 'window.X' ) ;
expect ( isolatedResult ) . to . equal ( 123 ) ;
expect ( mainWorldResult ) . to . equal ( undefined ) ;
} ) ;
} ) ;
2019-11-22 23:33:55 +00:00
2019-08-30 00:45:41 +00:00
describe ( 'loadURL() promise API' , ( ) = > {
2020-03-20 20:28:31 +00:00
let w : BrowserWindow ;
2019-08-30 00:45:41 +00:00
beforeEach ( async ( ) = > {
2020-03-20 20:28:31 +00:00
w = new BrowserWindow ( { show : false } ) ;
} ) ;
afterEach ( closeAllWindows ) ;
2019-08-30 00:45:41 +00:00
it ( 'resolves when done loading' , async ( ) = > {
2020-03-20 20:28:31 +00:00
await expect ( w . loadURL ( 'about:blank' ) ) . to . eventually . be . fulfilled ( ) ;
} ) ;
2019-08-30 00:45:41 +00:00
it ( 'resolves when done loading a file URL' , async ( ) = > {
2020-03-20 20:28:31 +00:00
await expect ( w . loadFile ( path . join ( fixturesPath , 'pages' , 'base-page.html' ) ) ) . to . eventually . be . fulfilled ( ) ;
} ) ;
2019-08-30 00:45:41 +00:00
it ( 'rejects when failing to load a file URL' , async ( ) = > {
await expect ( w . loadURL ( 'file:non-existent' ) ) . to . eventually . be . rejected ( )
2020-03-20 20:28:31 +00:00
. and . have . property ( 'code' , 'ERR_FILE_NOT_FOUND' ) ;
} ) ;
2019-08-30 00:45:41 +00:00
2019-09-03 17:13:06 +00:00
// Temporarily disable on WOA until
// https://github.com/electron/electron/issues/20008 is resolved
2020-03-20 20:28:31 +00:00
const testFn = ( process . platform === 'win32' && process . arch === 'arm64' ? it.skip : it ) ;
2019-09-03 17:13:06 +00:00
testFn ( 'rejects when loading fails due to DNS not resolved' , async ( ) = > {
2019-08-30 00:45:41 +00:00
await expect ( w . loadURL ( 'https://err.name.not.resolved' ) ) . to . eventually . be . rejected ( )
2020-03-20 20:28:31 +00:00
. and . have . property ( 'code' , 'ERR_NAME_NOT_RESOLVED' ) ;
} ) ;
2019-08-30 00:45:41 +00:00
it ( 'rejects when navigation is cancelled due to a bad scheme' , async ( ) = > {
await expect ( w . loadURL ( 'bad-scheme://foo' ) ) . to . eventually . be . rejected ( )
2020-03-20 20:28:31 +00:00
. and . have . property ( 'code' , 'ERR_FAILED' ) ;
} ) ;
2019-08-30 00:45:41 +00:00
it ( 'sets appropriate error information on rejection' , async ( ) = > {
2020-03-20 20:28:31 +00:00
let err ;
2019-08-30 00:45:41 +00:00
try {
2020-03-20 20:28:31 +00:00
await w . loadURL ( 'file:non-existent' ) ;
2019-08-30 00:45:41 +00:00
} catch ( e ) {
2020-03-20 20:28:31 +00:00
err = e ;
2019-08-30 00:45:41 +00:00
}
2020-03-20 20:28:31 +00:00
expect ( err ) . not . to . be . null ( ) ;
expect ( err . code ) . to . eql ( 'ERR_FILE_NOT_FOUND' ) ;
expect ( err . errno ) . to . eql ( - 6 ) ;
expect ( err . url ) . to . eql ( process . platform === 'win32' ? 'file://non-existent/' : 'file:///non-existent' ) ;
} ) ;
2019-08-30 00:45:41 +00:00
it ( 'rejects if the load is aborted' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const s = http . createServer ( ( ) = > { /* never complete the request */ } ) ;
await new Promise ( resolve = > s . listen ( 0 , '127.0.0.1' , resolve ) ) ;
const { port } = s . address ( ) as AddressInfo ;
const p = expect ( w . loadURL ( ` http://127.0.0.1: ${ port } ` ) ) . to . eventually . be . rejectedWith ( Error , /ERR_ABORTED/ ) ;
2019-08-30 00:45:41 +00:00
// load a different file before the first load completes, causing the
// first load to be aborted.
2020-03-20 20:28:31 +00:00
await w . loadFile ( path . join ( fixturesPath , 'pages' , 'base-page.html' ) ) ;
await p ;
s . close ( ) ;
} ) ;
2019-08-30 00:45:41 +00:00
it ( "doesn't reject when a subframe fails to load" , async ( ) = > {
2020-03-20 20:28:31 +00:00
let resp = null as unknown as http . ServerResponse ;
2019-08-30 00:45:41 +00:00
const s = http . createServer ( ( req , res ) = > {
2020-03-20 20:28:31 +00:00
res . writeHead ( 200 , { 'Content-Type' : 'text/html' } ) ;
res . write ( '<iframe src="http://err.name.not.resolved"></iframe>' ) ;
resp = res ;
2019-08-30 00:45:41 +00:00
// don't end the response yet
2020-03-20 20:28:31 +00:00
} ) ;
await new Promise ( resolve = > s . listen ( 0 , '127.0.0.1' , resolve ) ) ;
const { port } = s . address ( ) as AddressInfo ;
2019-08-30 00:45:41 +00:00
const p = new Promise ( resolve = > {
2019-11-01 20:37:02 +00:00
w . webContents . on ( 'did-fail-load' , ( event , errorCode , errorDescription , validatedURL , isMainFrame ) = > {
2019-08-30 00:45:41 +00:00
if ( ! isMainFrame ) {
2020-03-20 20:28:31 +00:00
resolve ( ) ;
2019-08-30 00:45:41 +00:00
}
2020-03-20 20:28:31 +00:00
} ) ;
} ) ;
const main = w . loadURL ( ` http://127.0.0.1: ${ port } ` ) ;
await p ;
resp . end ( ) ;
await main ;
s . close ( ) ;
} ) ;
2019-08-30 00:45:41 +00:00
it ( "doesn't resolve when a subframe loads" , async ( ) = > {
2020-03-20 20:28:31 +00:00
let resp = null as unknown as http . ServerResponse ;
2019-08-30 00:45:41 +00:00
const s = http . createServer ( ( req , res ) = > {
2020-03-20 20:28:31 +00:00
res . writeHead ( 200 , { 'Content-Type' : 'text/html' } ) ;
2020-09-21 08:00:36 +00:00
res . write ( '<iframe src="about:blank"></iframe>' ) ;
2020-03-20 20:28:31 +00:00
resp = res ;
2019-08-30 00:45:41 +00:00
// don't end the response yet
2020-03-20 20:28:31 +00:00
} ) ;
await new Promise ( resolve = > s . listen ( 0 , '127.0.0.1' , resolve ) ) ;
const { port } = s . address ( ) as AddressInfo ;
2019-08-30 00:45:41 +00:00
const p = new Promise ( resolve = > {
2019-11-01 20:37:02 +00:00
w . webContents . on ( 'did-frame-finish-load' , ( event , isMainFrame ) = > {
2019-08-30 00:45:41 +00:00
if ( ! isMainFrame ) {
2020-03-20 20:28:31 +00:00
resolve ( ) ;
2019-08-30 00:45:41 +00:00
}
2020-03-20 20:28:31 +00:00
} ) ;
} ) ;
const main = w . loadURL ( ` http://127.0.0.1: ${ port } ` ) ;
await p ;
resp . destroy ( ) ; // cause the main request to fail
2019-08-30 00:45:41 +00:00
await expect ( main ) . to . eventually . be . rejected ( )
2020-03-20 20:28:31 +00:00
. and . have . property ( 'errno' , - 355 ) ; // ERR_INCOMPLETE_CHUNKED_ENCODING
s . close ( ) ;
} ) ;
} ) ;
2019-08-30 00:45:41 +00:00
describe ( 'getFocusedWebContents() API' , ( ) = > {
2020-03-20 20:28:31 +00:00
afterEach ( closeAllWindows ) ;
2019-09-03 17:13:06 +00:00
2020-03-20 20:28:31 +00:00
const testFn = ( process . platform === 'win32' && process . arch === 'arm64' ? it.skip : it ) ;
2019-09-03 17:13:06 +00:00
testFn ( 'returns the focused web contents' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : true } ) ;
await w . loadURL ( 'about:blank' ) ;
expect ( webContents . getFocusedWebContents ( ) . id ) . to . equal ( w . webContents . id ) ;
const devToolsOpened = emittedOnce ( w . webContents , 'devtools-opened' ) ;
w . webContents . openDevTools ( ) ;
await devToolsOpened ;
2020-04-20 23:51:27 +00:00
expect ( webContents . getFocusedWebContents ( ) . id ) . to . equal ( w . webContents . devToolsWebContents ! . id ) ;
2020-03-20 20:28:31 +00:00
const devToolsClosed = emittedOnce ( w . webContents , 'devtools-closed' ) ;
w . webContents . closeDevTools ( ) ;
await devToolsClosed ;
expect ( webContents . getFocusedWebContents ( ) . id ) . to . equal ( w . webContents . id ) ;
} ) ;
2019-08-30 00:45:41 +00:00
it ( 'does not crash when called on a detached dev tools window' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : true } ) ;
2019-08-30 00:45:41 +00:00
2020-03-20 20:28:31 +00:00
w . webContents . openDevTools ( { mode : 'detach' } ) ;
w . webContents . inspectElement ( 100 , 100 ) ;
2019-08-30 00:45:41 +00:00
// For some reason we have to wait for two focused events...?
2020-03-20 20:28:31 +00:00
await emittedOnce ( w . webContents , 'devtools-focused' ) ;
2019-08-30 00:45:41 +00:00
2020-03-20 20:28:31 +00:00
expect ( ( ) = > { webContents . getFocusedWebContents ( ) ; } ) . to . not . throw ( ) ;
2019-08-30 00:45:41 +00:00
// Work around https://github.com/electron/electron/issues/19985
2020-06-26 20:59:54 +00:00
await delay ( ) ;
2019-08-30 00:45:41 +00:00
2020-03-20 20:28:31 +00:00
const devToolsClosed = emittedOnce ( w . webContents , 'devtools-closed' ) ;
w . webContents . closeDevTools ( ) ;
await devToolsClosed ;
expect ( ( ) = > { webContents . getFocusedWebContents ( ) ; } ) . to . not . throw ( ) ;
} ) ;
} ) ;
2019-08-30 00:45:41 +00:00
describe ( 'setDevToolsWebContents() API' , ( ) = > {
2020-03-20 20:28:31 +00:00
afterEach ( closeAllWindows ) ;
2019-08-30 00:45:41 +00:00
it ( 'sets arbitrary webContents as devtools' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
const devtools = new BrowserWindow ( { show : false } ) ;
const promise = emittedOnce ( devtools . webContents , 'dom-ready' ) ;
w . webContents . setDevToolsWebContents ( devtools . webContents ) ;
w . webContents . openDevTools ( ) ;
await promise ;
expect ( devtools . webContents . getURL ( ) . startsWith ( 'devtools://devtools' ) ) . to . be . true ( ) ;
const result = await devtools . webContents . executeJavaScript ( 'InspectorFrontendHost.constructor.name' ) ;
expect ( result ) . to . equal ( 'InspectorFrontendHostImpl' ) ;
devtools . destroy ( ) ;
} ) ;
} ) ;
2019-08-30 00:45:41 +00:00
describe ( 'isFocused() API' , ( ) = > {
it ( 'returns false when the window is hidden' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
await w . loadURL ( 'about:blank' ) ;
expect ( w . isVisible ( ) ) . to . be . false ( ) ;
expect ( w . webContents . isFocused ( ) ) . to . be . false ( ) ;
} ) ;
} ) ;
2019-08-30 00:45:41 +00:00
describe ( 'isCurrentlyAudible() API' , ( ) = > {
2020-03-20 20:28:31 +00:00
afterEach ( closeAllWindows ) ;
2019-08-30 00:45:41 +00:00
it ( 'returns whether audio is playing' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
await w . loadURL ( 'about:blank' ) ;
2019-08-30 00:45:41 +00:00
await w . webContents . executeJavaScript ( `
window . context = new AudioContext
// Start in suspended state, because of the
// new web audio api policy.
context . suspend ( )
window . oscillator = context . createOscillator ( )
oscillator . connect ( context . destination )
oscillator . start ( )
2020-03-20 20:28:31 +00:00
` );
let p = emittedOnce ( w . webContents , '-audio-state-changed' ) ;
w . webContents . executeJavaScript ( 'context.resume()' ) ;
await p ;
expect ( w . webContents . isCurrentlyAudible ( ) ) . to . be . true ( ) ;
p = emittedOnce ( w . webContents , '-audio-state-changed' ) ;
w . webContents . executeJavaScript ( 'oscillator.stop()' ) ;
await p ;
expect ( w . webContents . isCurrentlyAudible ( ) ) . to . be . false ( ) ;
} ) ;
} ) ;
2019-08-30 00:45:41 +00:00
describe ( 'getWebPreferences() API' , ( ) = > {
2020-03-20 20:28:31 +00:00
afterEach ( closeAllWindows ) ;
2020-06-30 22:10:36 +00:00
it ( 'should not crash when called for devTools webContents' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
w . webContents . openDevTools ( ) ;
2020-06-30 22:10:36 +00:00
await emittedOnce ( w . webContents , 'devtools-opened' ) ;
expect ( w . webContents . devToolsWebContents ! . getWebPreferences ( ) ) . to . be . null ( ) ;
2020-03-20 20:28:31 +00:00
} ) ;
} ) ;
2019-08-30 00:45:41 +00:00
describe ( 'openDevTools() API' , ( ) = > {
2020-03-20 20:28:31 +00:00
afterEach ( closeAllWindows ) ;
2019-08-30 00:45:41 +00:00
it ( 'can show window with activation' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
const focused = emittedOnce ( w , 'focus' ) ;
w . show ( ) ;
await focused ;
expect ( w . isFocused ( ) ) . to . be . true ( ) ;
w . webContents . openDevTools ( { mode : 'detach' , activate : true } ) ;
2019-09-25 21:38:50 +00:00
await Promise . all ( [
emittedOnce ( w . webContents , 'devtools-opened' ) ,
2019-11-01 20:37:02 +00:00
emittedOnce ( w . webContents , 'devtools-focused' )
2020-03-20 20:28:31 +00:00
] ) ;
2020-06-26 20:59:54 +00:00
await delay ( ) ;
2020-03-20 20:28:31 +00:00
expect ( w . isFocused ( ) ) . to . be . false ( ) ;
} ) ;
2019-08-30 00:45:41 +00:00
it ( 'can show window without activation' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
const devtoolsOpened = emittedOnce ( w . webContents , 'devtools-opened' ) ;
w . webContents . openDevTools ( { mode : 'detach' , activate : false } ) ;
await devtoolsOpened ;
expect ( w . webContents . isDevToolsOpened ( ) ) . to . be . true ( ) ;
} ) ;
} ) ;
2019-08-30 00:45:41 +00:00
describe ( 'before-input-event event' , ( ) = > {
2020-03-20 20:28:31 +00:00
afterEach ( closeAllWindows ) ;
2019-08-30 00:45:41 +00:00
it ( 'can prevent document keyboard events' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false , webPreferences : { nodeIntegration : true } } ) ;
await w . loadFile ( path . join ( fixturesPath , 'pages' , 'key-events.html' ) ) ;
2019-08-30 00:45:41 +00:00
const keyDown = new Promise ( resolve = > {
2020-03-20 20:28:31 +00:00
ipcMain . once ( 'keydown' , ( event , key ) = > resolve ( key ) ) ;
} ) ;
2019-08-30 00:45:41 +00:00
w . webContents . once ( 'before-input-event' , ( event , input ) = > {
2020-03-20 20:28:31 +00:00
if ( input . key === 'a' ) event . preventDefault ( ) ;
} ) ;
w . webContents . sendInputEvent ( { type : 'keyDown' , keyCode : 'a' } ) ;
w . webContents . sendInputEvent ( { type : 'keyDown' , keyCode : 'b' } ) ;
expect ( await keyDown ) . to . equal ( 'b' ) ;
} ) ;
2019-08-30 00:45:41 +00:00
it ( 'has the correct properties' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
await w . loadFile ( path . join ( fixturesPath , 'pages' , 'base-page.html' ) ) ;
2019-08-30 00:45:41 +00:00
const testBeforeInput = async ( opts : any ) = > {
2020-03-20 20:28:31 +00:00
const modifiers = [ ] ;
if ( opts . shift ) modifiers . push ( 'shift' ) ;
if ( opts . control ) modifiers . push ( 'control' ) ;
if ( opts . alt ) modifiers . push ( 'alt' ) ;
if ( opts . meta ) modifiers . push ( 'meta' ) ;
if ( opts . isAutoRepeat ) modifiers . push ( 'isAutoRepeat' ) ;
const p = emittedOnce ( w . webContents , 'before-input-event' ) ;
2019-08-30 00:45:41 +00:00
w . webContents . sendInputEvent ( {
type : opts . type ,
keyCode : opts.keyCode ,
modifiers : modifiers as any
2020-03-20 20:28:31 +00:00
} ) ;
const [ , input ] = await p ;
expect ( input . type ) . to . equal ( opts . type ) ;
expect ( input . key ) . to . equal ( opts . key ) ;
expect ( input . code ) . to . equal ( opts . code ) ;
expect ( input . isAutoRepeat ) . to . equal ( opts . isAutoRepeat ) ;
expect ( input . shift ) . to . equal ( opts . shift ) ;
expect ( input . control ) . to . equal ( opts . control ) ;
expect ( input . alt ) . to . equal ( opts . alt ) ;
expect ( input . meta ) . to . equal ( opts . meta ) ;
} ;
2019-08-30 00:45:41 +00:00
await testBeforeInput ( {
type : 'keyDown' ,
key : 'A' ,
code : 'KeyA' ,
keyCode : 'a' ,
shift : true ,
control : true ,
alt : true ,
meta : true ,
isAutoRepeat : true
2020-03-20 20:28:31 +00:00
} ) ;
2019-08-30 00:45:41 +00:00
await testBeforeInput ( {
type : 'keyUp' ,
key : '.' ,
code : 'Period' ,
keyCode : '.' ,
shift : false ,
control : true ,
alt : true ,
meta : false ,
isAutoRepeat : false
2020-03-20 20:28:31 +00:00
} ) ;
2019-08-30 00:45:41 +00:00
await testBeforeInput ( {
type : 'keyUp' ,
key : '!' ,
code : 'Digit1' ,
keyCode : '1' ,
shift : true ,
control : false ,
alt : false ,
meta : true ,
isAutoRepeat : false
2020-03-20 20:28:31 +00:00
} ) ;
2019-08-30 00:45:41 +00:00
await testBeforeInput ( {
type : 'keyUp' ,
key : 'Tab' ,
code : 'Tab' ,
keyCode : 'Tab' ,
shift : false ,
control : true ,
alt : false ,
meta : false ,
isAutoRepeat : true
2020-03-20 20:28:31 +00:00
} ) ;
} ) ;
} ) ;
2019-08-30 00:45:41 +00:00
// On Mac, zooming isn't done with the mouse wheel.
ifdescribe ( process . platform !== 'darwin' ) ( 'zoom-changed' , ( ) = > {
2020-03-20 20:28:31 +00:00
afterEach ( closeAllWindows ) ;
2020-06-17 17:08:10 +00:00
it ( 'is emitted with the correct zoom-in info' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
await w . loadFile ( path . join ( fixturesPath , 'pages' , 'base-page.html' ) ) ;
2019-08-30 00:45:41 +00:00
2020-06-17 17:08:10 +00:00
const testZoomChanged = async ( ) = > {
2019-08-30 00:45:41 +00:00
w . webContents . sendInputEvent ( {
type : 'mouseWheel' ,
x : 300 ,
y : 300 ,
deltaX : 0 ,
2020-06-17 17:08:10 +00:00
deltaY : 1 ,
2019-08-30 00:45:41 +00:00
wheelTicksX : 0 ,
2020-06-17 17:08:10 +00:00
wheelTicksY : 1 ,
2019-08-30 00:45:41 +00:00
modifiers : [ 'control' , 'meta' ]
2020-03-20 20:28:31 +00:00
} ) ;
2019-08-30 00:45:41 +00:00
2020-03-20 20:28:31 +00:00
const [ , zoomDirection ] = await emittedOnce ( w . webContents , 'zoom-changed' ) ;
2020-06-17 17:08:10 +00:00
expect ( zoomDirection ) . to . equal ( 'in' ) ;
2020-03-20 20:28:31 +00:00
} ;
2019-08-30 00:45:41 +00:00
2020-06-17 17:08:10 +00:00
await testZoomChanged ( ) ;
} ) ;
it ( 'is emitted with the correct zoom-out info' , async ( ) = > {
const w = new BrowserWindow ( { show : false } ) ;
await w . loadFile ( path . join ( fixturesPath , 'pages' , 'base-page.html' ) ) ;
const testZoomChanged = async ( ) = > {
w . webContents . sendInputEvent ( {
type : 'mouseWheel' ,
x : 300 ,
y : 300 ,
deltaX : 0 ,
deltaY : - 1 ,
wheelTicksX : 0 ,
wheelTicksY : - 1 ,
modifiers : [ 'control' , 'meta' ]
} ) ;
const [ , zoomDirection ] = await emittedOnce ( w . webContents , 'zoom-changed' ) ;
expect ( zoomDirection ) . to . equal ( 'out' ) ;
} ;
await testZoomChanged ( ) ;
2020-03-20 20:28:31 +00:00
} ) ;
} ) ;
2019-08-30 00:45:41 +00:00
describe ( 'sendInputEvent(event)' , ( ) = > {
2020-03-20 20:28:31 +00:00
let w : BrowserWindow ;
2019-08-30 00:45:41 +00:00
beforeEach ( async ( ) = > {
2020-03-20 20:28:31 +00:00
w = new BrowserWindow ( { show : false , webPreferences : { nodeIntegration : true } } ) ;
await w . loadFile ( path . join ( fixturesPath , 'pages' , 'key-events.html' ) ) ;
} ) ;
afterEach ( closeAllWindows ) ;
2019-08-30 00:45:41 +00:00
2020-06-30 22:10:36 +00:00
it ( 'can send keydown events' , async ( ) = > {
const keydown = emittedOnce ( ipcMain , 'keydown' ) ;
2020-03-20 20:28:31 +00:00
w . webContents . sendInputEvent ( { type : 'keyDown' , keyCode : 'A' } ) ;
2020-06-30 22:10:36 +00:00
const [ , key , code , keyCode , shiftKey , ctrlKey , altKey ] = await keydown ;
expect ( key ) . to . equal ( 'a' ) ;
expect ( code ) . to . equal ( 'KeyA' ) ;
expect ( keyCode ) . to . equal ( 65 ) ;
expect ( shiftKey ) . to . be . false ( ) ;
expect ( ctrlKey ) . to . be . false ( ) ;
expect ( altKey ) . to . be . false ( ) ;
2020-03-20 20:28:31 +00:00
} ) ;
2019-08-30 00:45:41 +00:00
2020-06-30 22:10:36 +00:00
it ( 'can send keydown events with modifiers' , async ( ) = > {
const keydown = emittedOnce ( ipcMain , 'keydown' ) ;
2020-03-20 20:28:31 +00:00
w . webContents . sendInputEvent ( { type : 'keyDown' , keyCode : 'Z' , modifiers : [ 'shift' , 'ctrl' ] } ) ;
2020-06-30 22:10:36 +00:00
const [ , key , code , keyCode , shiftKey , ctrlKey , altKey ] = await keydown ;
expect ( key ) . to . equal ( 'Z' ) ;
expect ( code ) . to . equal ( 'KeyZ' ) ;
expect ( keyCode ) . to . equal ( 90 ) ;
expect ( shiftKey ) . to . be . true ( ) ;
expect ( ctrlKey ) . to . be . true ( ) ;
expect ( altKey ) . to . be . false ( ) ;
2020-03-20 20:28:31 +00:00
} ) ;
2019-08-30 00:45:41 +00:00
2020-06-30 22:10:36 +00:00
it ( 'can send keydown events with special keys' , async ( ) = > {
const keydown = emittedOnce ( ipcMain , 'keydown' ) ;
2020-03-20 20:28:31 +00:00
w . webContents . sendInputEvent ( { type : 'keyDown' , keyCode : 'Tab' , modifiers : [ 'alt' ] } ) ;
2020-06-30 22:10:36 +00:00
const [ , key , code , keyCode , shiftKey , ctrlKey , altKey ] = await keydown ;
expect ( key ) . to . equal ( 'Tab' ) ;
expect ( code ) . to . equal ( 'Tab' ) ;
expect ( keyCode ) . to . equal ( 9 ) ;
expect ( shiftKey ) . to . be . false ( ) ;
expect ( ctrlKey ) . to . be . false ( ) ;
expect ( altKey ) . to . be . true ( ) ;
2020-03-20 20:28:31 +00:00
} ) ;
2019-08-30 00:45:41 +00:00
2020-06-30 22:10:36 +00:00
it ( 'can send char events' , async ( ) = > {
const keypress = emittedOnce ( ipcMain , 'keypress' ) ;
2020-03-20 20:28:31 +00:00
w . webContents . sendInputEvent ( { type : 'keyDown' , keyCode : 'A' } ) ;
w . webContents . sendInputEvent ( { type : 'char' , keyCode : 'A' } ) ;
2020-06-30 22:10:36 +00:00
const [ , key , code , keyCode , shiftKey , ctrlKey , altKey ] = await keypress ;
expect ( key ) . to . equal ( 'a' ) ;
expect ( code ) . to . equal ( 'KeyA' ) ;
expect ( keyCode ) . to . equal ( 65 ) ;
expect ( shiftKey ) . to . be . false ( ) ;
expect ( ctrlKey ) . to . be . false ( ) ;
expect ( altKey ) . to . be . false ( ) ;
2020-03-20 20:28:31 +00:00
} ) ;
2019-08-30 00:45:41 +00:00
2020-06-30 22:10:36 +00:00
it ( 'can send char events with modifiers' , async ( ) = > {
const keypress = emittedOnce ( ipcMain , 'keypress' ) ;
2020-03-20 20:28:31 +00:00
w . webContents . sendInputEvent ( { type : 'keyDown' , keyCode : 'Z' } ) ;
w . webContents . sendInputEvent ( { type : 'char' , keyCode : 'Z' , modifiers : [ 'shift' , 'ctrl' ] } ) ;
2020-06-30 22:10:36 +00:00
const [ , key , code , keyCode , shiftKey , ctrlKey , altKey ] = await keypress ;
expect ( key ) . to . equal ( 'Z' ) ;
expect ( code ) . to . equal ( 'KeyZ' ) ;
expect ( keyCode ) . to . equal ( 90 ) ;
expect ( shiftKey ) . to . be . true ( ) ;
expect ( ctrlKey ) . to . be . true ( ) ;
expect ( altKey ) . to . be . false ( ) ;
2020-03-20 20:28:31 +00:00
} ) ;
} ) ;
2019-08-30 00:45:41 +00:00
describe ( 'insertCSS' , ( ) = > {
2020-03-20 20:28:31 +00:00
afterEach ( closeAllWindows ) ;
2019-08-30 00:45:41 +00:00
it ( 'supports inserting CSS' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
w . loadURL ( 'about:blank' ) ;
await w . webContents . insertCSS ( 'body { background-repeat: round; }' ) ;
const result = await w . webContents . executeJavaScript ( 'window.getComputedStyle(document.body).getPropertyValue("background-repeat")' ) ;
expect ( result ) . to . equal ( 'round' ) ;
} ) ;
2019-08-30 00:45:41 +00:00
it ( 'supports removing inserted CSS' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
w . loadURL ( 'about:blank' ) ;
const key = await w . webContents . insertCSS ( 'body { background-repeat: round; }' ) ;
await w . webContents . removeInsertedCSS ( key ) ;
const result = await w . webContents . executeJavaScript ( 'window.getComputedStyle(document.body).getPropertyValue("background-repeat")' ) ;
expect ( result ) . to . equal ( 'repeat' ) ;
} ) ;
} ) ;
2019-08-30 00:45:41 +00:00
describe ( 'inspectElement()' , ( ) = > {
2020-03-20 20:28:31 +00:00
afterEach ( closeAllWindows ) ;
2019-08-30 00:45:41 +00:00
it ( 'supports inspecting an element in the devtools' , ( done ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
w . loadURL ( 'about:blank' ) ;
w . webContents . once ( 'devtools-opened' , ( ) = > { done ( ) ; } ) ;
w . webContents . inspectElement ( 10 , 10 ) ;
} ) ;
} ) ;
2019-08-30 00:45:41 +00:00
describe ( 'startDrag({file, icon})' , ( ) = > {
it ( 'throws errors for a missing file or a missing/empty icon' , ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
2019-08-30 00:45:41 +00:00
expect ( ( ) = > {
2020-03-20 20:28:31 +00:00
w . webContents . startDrag ( { icon : path.join ( fixturesPath , 'assets' , 'logo.png' ) } as any ) ;
} ) . to . throw ( 'Must specify either \'file\' or \'files\' option' ) ;
2019-08-30 00:45:41 +00:00
expect ( ( ) = > {
2020-03-20 20:28:31 +00:00
w . webContents . startDrag ( { file : __filename } as any ) ;
} ) . to . throw ( 'Must specify non-empty \'icon\' option' ) ;
2019-08-30 00:45:41 +00:00
2019-10-25 13:03:28 +00:00
expect ( ( ) = > {
2020-08-24 16:58:55 +00:00
w . webContents . startDrag ( { file : __filename , icon : path.join ( mainFixturesPath , 'blank.png' ) } ) ;
2020-03-20 20:28:31 +00:00
} ) . to . throw ( 'Must specify non-empty \'icon\' option' ) ;
} ) ;
} ) ;
2019-08-30 00:45:41 +00:00
describe ( 'focus()' , ( ) = > {
describe ( 'when the web contents is hidden' , ( ) = > {
2020-03-20 20:28:31 +00:00
afterEach ( closeAllWindows ) ;
2019-11-14 22:09:03 +00:00
it ( 'does not blur the focused window' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false , webPreferences : { nodeIntegration : true } } ) ;
w . show ( ) ;
await w . loadURL ( 'about:blank' ) ;
w . focus ( ) ;
const child = new BrowserWindow ( { show : false } ) ;
child . loadURL ( 'about:blank' ) ;
child . webContents . focus ( ) ;
const currentFocused = w . isFocused ( ) ;
const childFocused = child . isFocused ( ) ;
child . close ( ) ;
expect ( currentFocused ) . to . be . true ( ) ;
expect ( childFocused ) . to . be . false ( ) ;
} ) ;
} ) ;
} ) ;
2019-08-30 00:45:41 +00:00
describe ( 'getOSProcessId()' , ( ) = > {
2020-03-20 20:28:31 +00:00
afterEach ( closeAllWindows ) ;
2019-08-30 00:45:41 +00:00
it ( 'returns a valid procress id' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
expect ( w . webContents . getOSProcessId ( ) ) . to . equal ( 0 ) ;
2019-08-30 00:45:41 +00:00
2020-03-20 20:28:31 +00:00
await w . loadURL ( 'about:blank' ) ;
expect ( w . webContents . getOSProcessId ( ) ) . to . be . above ( 0 ) ;
} ) ;
} ) ;
2019-08-30 00:45:41 +00:00
2020-03-13 17:16:08 +00:00
describe ( 'userAgent APIs' , ( ) = > {
it ( 'can set the user agent (functions)' , ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
const userAgent = w . webContents . getUserAgent ( ) ;
2020-03-13 17:16:08 +00:00
2020-03-20 20:28:31 +00:00
w . webContents . setUserAgent ( 'my-user-agent' ) ;
expect ( w . webContents . getUserAgent ( ) ) . to . equal ( 'my-user-agent' ) ;
2020-03-13 17:16:08 +00:00
2020-03-20 20:28:31 +00:00
w . webContents . setUserAgent ( userAgent ) ;
expect ( w . webContents . getUserAgent ( ) ) . to . equal ( userAgent ) ;
} ) ;
2020-03-13 17:16:08 +00:00
it ( 'can set the user agent (properties)' , ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
const userAgent = w . webContents . userAgent ;
2020-03-13 17:16:08 +00:00
2020-03-20 20:28:31 +00:00
w . webContents . userAgent = 'my-user-agent' ;
expect ( w . webContents . userAgent ) . to . equal ( 'my-user-agent' ) ;
2020-03-13 17:16:08 +00:00
2020-03-20 20:28:31 +00:00
w . webContents . userAgent = userAgent ;
expect ( w . webContents . userAgent ) . to . equal ( userAgent ) ;
} ) ;
} ) ;
2020-03-13 17:16:08 +00:00
describe ( 'audioMuted APIs' , ( ) = > {
it ( 'can set the audio mute level (functions)' , ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
2020-03-13 17:16:08 +00:00
2020-03-20 20:28:31 +00:00
w . webContents . setAudioMuted ( true ) ;
expect ( w . webContents . isAudioMuted ( ) ) . to . be . true ( ) ;
2020-03-13 17:16:08 +00:00
2020-03-20 20:28:31 +00:00
w . webContents . setAudioMuted ( false ) ;
expect ( w . webContents . isAudioMuted ( ) ) . to . be . false ( ) ;
} ) ;
2020-03-13 17:16:08 +00:00
it ( 'can set the audio mute level (functions)' , ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
2020-03-13 17:16:08 +00:00
2020-03-20 20:28:31 +00:00
w . webContents . audioMuted = true ;
expect ( w . webContents . audioMuted ) . to . be . true ( ) ;
2020-03-13 17:16:08 +00:00
2020-03-20 20:28:31 +00:00
w . webContents . audioMuted = false ;
expect ( w . webContents . audioMuted ) . to . be . false ( ) ;
} ) ;
} ) ;
2020-03-13 17:16:08 +00:00
2019-08-30 00:45:41 +00:00
describe ( 'zoom api' , ( ) = > {
2020-03-20 20:28:31 +00:00
const scheme = ( global as any ) . standardScheme ;
2019-08-30 00:45:41 +00:00
const hostZoomMap : Record < string , number > = {
host1 : 0.3 ,
host2 : 0.7 ,
host3 : 0.2
2020-03-20 20:28:31 +00:00
} ;
2019-08-30 00:45:41 +00:00
2020-06-02 16:46:18 +00:00
before ( ( ) = > {
2020-03-20 20:28:31 +00:00
const protocol = session . defaultSession . protocol ;
2019-08-30 00:45:41 +00:00
protocol . registerStringProtocol ( scheme , ( request , callback ) = > {
const response = ` <script>
2020-05-13 18:05:44 +00:00
const { ipcRenderer } = require ( 'electron' )
2019-08-30 00:45:41 +00:00
ipcRenderer . send ( 'set-zoom' , window . location . hostname )
ipcRenderer . on ( window . location . hostname + '-zoom-set' , ( ) = > {
2020-05-13 18:05:44 +00:00
ipcRenderer . send ( window . location . hostname + '-zoom-level' )
2019-08-30 00:45:41 +00:00
} )
2020-03-20 20:28:31 +00:00
< / script > ` ;
callback ( { data : response , mimeType : 'text/html' } ) ;
2020-06-02 16:46:18 +00:00
} ) ;
2020-03-20 20:28:31 +00:00
} ) ;
2019-08-30 00:45:41 +00:00
2020-06-02 16:46:18 +00:00
after ( ( ) = > {
2020-03-20 20:28:31 +00:00
const protocol = session . defaultSession . protocol ;
2020-06-02 16:46:18 +00:00
protocol . unregisterProtocol ( scheme ) ;
2020-03-20 20:28:31 +00:00
} ) ;
2019-08-30 00:45:41 +00:00
2020-03-20 20:28:31 +00:00
afterEach ( closeAllWindows ) ;
2019-08-30 00:45:41 +00:00
2020-03-13 23:13:05 +00:00
it ( 'throws on an invalid zoomFactor' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
await w . loadURL ( 'about:blank' ) ;
2020-03-13 23:13:05 +00:00
expect ( ( ) = > {
2020-03-20 20:28:31 +00:00
w . webContents . setZoomFactor ( 0.0 ) ;
} ) . to . throw ( /'zoomFactor' must be a double greater than 0.0/ ) ;
2020-03-13 23:13:05 +00:00
expect ( ( ) = > {
2020-03-20 20:28:31 +00:00
w . webContents . setZoomFactor ( - 2.0 ) ;
} ) . to . throw ( /'zoomFactor' must be a double greater than 0.0/ ) ;
} ) ;
2020-03-13 23:13:05 +00:00
2019-08-30 00:45:41 +00:00
it ( 'can set the correct zoom level (functions)' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
2019-08-30 00:45:41 +00:00
try {
2020-03-20 20:28:31 +00:00
await w . loadURL ( 'about:blank' ) ;
const zoomLevel = w . webContents . getZoomLevel ( ) ;
expect ( zoomLevel ) . to . eql ( 0.0 ) ;
w . webContents . setZoomLevel ( 0.5 ) ;
const newZoomLevel = w . webContents . getZoomLevel ( ) ;
expect ( newZoomLevel ) . to . eql ( 0.5 ) ;
2019-08-30 00:45:41 +00:00
} finally {
2020-03-20 20:28:31 +00:00
w . webContents . setZoomLevel ( 0 ) ;
2019-08-30 00:45:41 +00:00
}
2020-03-20 20:28:31 +00:00
} ) ;
2019-08-30 00:45:41 +00:00
2020-03-13 17:16:08 +00:00
it ( 'can set the correct zoom level (properties)' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
2019-08-30 00:45:41 +00:00
try {
2020-03-20 20:28:31 +00:00
await w . loadURL ( 'about:blank' ) ;
const zoomLevel = w . webContents . zoomLevel ;
expect ( zoomLevel ) . to . eql ( 0.0 ) ;
w . webContents . zoomLevel = 0.5 ;
const newZoomLevel = w . webContents . zoomLevel ;
expect ( newZoomLevel ) . to . eql ( 0.5 ) ;
2019-08-30 00:45:41 +00:00
} finally {
2020-03-20 20:28:31 +00:00
w . webContents . zoomLevel = 0 ;
2019-08-30 00:45:41 +00:00
}
2020-03-20 20:28:31 +00:00
} ) ;
2019-08-30 00:45:41 +00:00
2020-03-13 17:16:08 +00:00
it ( 'can set the correct zoom factor (functions)' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
2020-03-13 17:16:08 +00:00
try {
2020-03-20 20:28:31 +00:00
await w . loadURL ( 'about:blank' ) ;
const zoomFactor = w . webContents . getZoomFactor ( ) ;
expect ( zoomFactor ) . to . eql ( 1.0 ) ;
2020-03-13 17:16:08 +00:00
2020-03-20 20:28:31 +00:00
w . webContents . setZoomFactor ( 0.5 ) ;
const newZoomFactor = w . webContents . getZoomFactor ( ) ;
expect ( newZoomFactor ) . to . eql ( 0.5 ) ;
2020-03-13 17:16:08 +00:00
} finally {
2020-03-20 20:28:31 +00:00
w . webContents . setZoomFactor ( 1.0 ) ;
2020-03-13 17:16:08 +00:00
}
2020-03-20 20:28:31 +00:00
} ) ;
2020-03-13 17:16:08 +00:00
it ( 'can set the correct zoom factor (properties)' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
2020-03-13 17:16:08 +00:00
try {
2020-03-20 20:28:31 +00:00
await w . loadURL ( 'about:blank' ) ;
const zoomFactor = w . webContents . zoomFactor ;
expect ( zoomFactor ) . to . eql ( 1.0 ) ;
2020-03-13 17:16:08 +00:00
2020-03-20 20:28:31 +00:00
w . webContents . zoomFactor = 0.5 ;
const newZoomFactor = w . webContents . zoomFactor ;
expect ( newZoomFactor ) . to . eql ( 0.5 ) ;
2020-03-13 17:16:08 +00:00
} finally {
2020-03-20 20:28:31 +00:00
w . webContents . zoomFactor = 1.0 ;
2020-03-13 17:16:08 +00:00
}
2020-03-20 20:28:31 +00:00
} ) ;
2020-03-13 17:16:08 +00:00
2019-08-30 00:45:41 +00:00
it ( 'can persist zoom level across navigation' , ( done ) = > {
2020-05-13 18:05:44 +00:00
const w = new BrowserWindow ( { show : false , webPreferences : { nodeIntegration : true } } ) ;
2020-03-20 20:28:31 +00:00
let finalNavigation = false ;
2019-08-30 00:45:41 +00:00
ipcMain . on ( 'set-zoom' , ( e , host ) = > {
2020-03-20 20:28:31 +00:00
const zoomLevel = hostZoomMap [ host ] ;
if ( ! finalNavigation ) w . webContents . zoomLevel = zoomLevel ;
e . sender . send ( ` ${ host } -zoom-set ` ) ;
} ) ;
2020-05-13 18:05:44 +00:00
ipcMain . on ( 'host1-zoom-level' , ( e ) = > {
2020-06-30 22:10:36 +00:00
try {
const zoomLevel = e . sender . getZoomLevel ( ) ;
const expectedZoomLevel = hostZoomMap . host1 ;
expect ( zoomLevel ) . to . equal ( expectedZoomLevel ) ;
if ( finalNavigation ) {
done ( ) ;
} else {
w . loadURL ( ` ${ scheme } ://host2 ` ) ;
}
} catch ( e ) {
done ( e ) ;
2019-08-30 00:45:41 +00:00
}
2020-03-20 20:28:31 +00:00
} ) ;
2020-05-13 18:05:44 +00:00
ipcMain . once ( 'host2-zoom-level' , ( e ) = > {
2020-06-30 22:10:36 +00:00
try {
const zoomLevel = e . sender . getZoomLevel ( ) ;
const expectedZoomLevel = hostZoomMap . host2 ;
expect ( zoomLevel ) . to . equal ( expectedZoomLevel ) ;
finalNavigation = true ;
w . webContents . goBack ( ) ;
} catch ( e ) {
done ( e ) ;
}
2020-03-20 20:28:31 +00:00
} ) ;
w . loadURL ( ` ${ scheme } ://host1 ` ) ;
} ) ;
2019-08-30 00:45:41 +00:00
2020-06-30 22:10:36 +00:00
it ( 'can propagate zoom level across same session' , async ( ) = > {
2020-05-13 18:05:44 +00:00
const w = new BrowserWindow ( { show : false , webPreferences : { nodeIntegration : true } } ) ;
2020-03-20 20:28:31 +00:00
const w2 = new BrowserWindow ( { show : false } ) ;
2020-06-30 22:10:36 +00:00
defer ( ( ) = > {
2020-03-20 20:28:31 +00:00
w2 . setClosable ( true ) ;
w2 . close ( ) ;
} ) ;
2020-06-30 22:10:36 +00:00
await w . loadURL ( ` ${ scheme } ://host3 ` ) ;
w . webContents . zoomLevel = hostZoomMap . host3 ;
await w2 . loadURL ( ` ${ scheme } ://host3 ` ) ;
const zoomLevel1 = w . webContents . zoomLevel ;
expect ( zoomLevel1 ) . to . equal ( hostZoomMap . host3 ) ;
const zoomLevel2 = w2 . webContents . zoomLevel ;
expect ( zoomLevel1 ) . to . equal ( zoomLevel2 ) ;
2020-03-20 20:28:31 +00:00
} ) ;
2019-08-30 00:45:41 +00:00
2020-06-30 22:10:36 +00:00
it ( 'cannot propagate zoom level across different session' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false , webPreferences : { nodeIntegration : true } } ) ;
2019-08-30 00:45:41 +00:00
const w2 = new BrowserWindow ( {
show : false ,
webPreferences : {
partition : 'temp'
}
2020-03-20 20:28:31 +00:00
} ) ;
const protocol = w2 . webContents . session . protocol ;
2019-08-30 00:45:41 +00:00
protocol . registerStringProtocol ( scheme , ( request , callback ) = > {
2020-03-20 20:28:31 +00:00
callback ( 'hello' ) ;
} ) ;
2020-06-02 16:46:18 +00:00
2020-06-30 22:10:36 +00:00
defer ( ( ) = > {
2020-06-02 16:46:18 +00:00
w2 . setClosable ( true ) ;
w2 . close ( ) ;
2020-07-01 19:14:38 +00:00
protocol . unregisterProtocol ( scheme ) ;
2020-06-02 16:46:18 +00:00
} ) ;
2020-06-30 22:10:36 +00:00
await w . loadURL ( ` ${ scheme } ://host3 ` ) ;
w . webContents . zoomLevel = hostZoomMap . host3 ;
await w2 . loadURL ( ` ${ scheme } ://host3 ` ) ;
const zoomLevel1 = w . webContents . zoomLevel ;
expect ( zoomLevel1 ) . to . equal ( hostZoomMap . host3 ) ;
const zoomLevel2 = w2 . webContents . zoomLevel ;
expect ( zoomLevel2 ) . to . equal ( 0 ) ;
expect ( zoomLevel1 ) . to . not . equal ( zoomLevel2 ) ;
2020-03-20 20:28:31 +00:00
} ) ;
2019-08-30 00:45:41 +00:00
it ( 'can persist when it contains iframe' , ( done ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
2019-08-30 00:45:41 +00:00
const server = http . createServer ( ( req , res ) = > {
setTimeout ( ( ) = > {
2020-03-20 20:28:31 +00:00
res . end ( ) ;
} , 200 ) ;
} ) ;
2019-08-30 00:45:41 +00:00
server . listen ( 0 , '127.0.0.1' , ( ) = > {
2020-03-20 20:28:31 +00:00
const url = 'http://127.0.0.1:' + ( server . address ( ) as AddressInfo ) . port ;
const content = ` <iframe src= ${ url } ></iframe> ` ;
2019-08-30 00:45:41 +00:00
w . webContents . on ( 'did-frame-finish-load' , ( e , isMainFrame ) = > {
if ( ! isMainFrame ) {
2020-06-30 22:10:36 +00:00
try {
const zoomLevel = w . webContents . zoomLevel ;
expect ( zoomLevel ) . to . equal ( 2.0 ) ;
2019-08-30 00:45:41 +00:00
2020-06-30 22:10:36 +00:00
w . webContents . zoomLevel = 0 ;
done ( ) ;
} catch ( e ) {
done ( e ) ;
} finally {
server . close ( ) ;
}
2019-08-30 00:45:41 +00:00
}
2020-03-20 20:28:31 +00:00
} ) ;
2019-08-30 00:45:41 +00:00
w . webContents . on ( 'dom-ready' , ( ) = > {
2020-03-20 20:28:31 +00:00
w . webContents . zoomLevel = 2.0 ;
} ) ;
w . loadURL ( ` data:text/html, ${ content } ` ) ;
} ) ;
} ) ;
2019-08-30 00:45:41 +00:00
2020-06-30 22:10:36 +00:00
it ( 'cannot propagate when used with webframe' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false , webPreferences : { nodeIntegration : true } } ) ;
2020-06-30 22:10:36 +00:00
const w2 = new BrowserWindow ( { show : false } ) ;
2019-08-30 00:45:41 +00:00
2020-06-30 22:10:36 +00:00
const temporaryZoomSet = emittedOnce ( ipcMain , 'temporary-zoom-set' ) ;
2020-03-20 20:28:31 +00:00
w . loadFile ( path . join ( fixturesPath , 'pages' , 'webframe-zoom.html' ) ) ;
2020-06-30 22:10:36 +00:00
await temporaryZoomSet ;
const finalZoomLevel = w . webContents . getZoomLevel ( ) ;
await w2 . loadFile ( path . join ( fixturesPath , 'pages' , 'c.html' ) ) ;
const zoomLevel1 = w . webContents . zoomLevel ;
const zoomLevel2 = w2 . webContents . zoomLevel ;
w2 . setClosable ( true ) ;
w2 . close ( ) ;
expect ( zoomLevel1 ) . to . equal ( finalZoomLevel ) ;
expect ( zoomLevel2 ) . to . equal ( 0 ) ;
expect ( zoomLevel1 ) . to . not . equal ( zoomLevel2 ) ;
2020-03-20 20:28:31 +00:00
} ) ;
2019-08-30 00:45:41 +00:00
2020-02-25 02:11:06 +00:00
describe ( 'with unique domains' , ( ) = > {
2020-03-20 20:28:31 +00:00
let server : http.Server ;
let serverUrl : string ;
let crossSiteUrl : string ;
2020-02-25 02:11:06 +00:00
before ( ( done ) = > {
server = http . createServer ( ( req , res ) = > {
2020-03-20 20:28:31 +00:00
setTimeout ( ( ) = > res . end ( 'hey' ) , 0 ) ;
} ) ;
2020-02-25 02:11:06 +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 } ` ;
crossSiteUrl = ` http://localhost: ${ ( server . address ( ) as AddressInfo ) . port } ` ;
done ( ) ;
} ) ;
} ) ;
2020-02-25 02:11:06 +00:00
after ( ( ) = > {
2020-03-20 20:28:31 +00:00
server . close ( ) ;
} ) ;
2020-02-25 02:11:06 +00:00
it ( 'cannot persist zoom level after navigation with webFrame' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false , webPreferences : { nodeIntegration : true } } ) ;
2020-02-25 02:11:06 +00:00
const source = `
const { ipcRenderer , webFrame } = require ( 'electron' )
webFrame . setZoomLevel ( 0.6 )
ipcRenderer . send ( 'zoom-level-set' , webFrame . getZoomLevel ( ) )
2020-03-20 20:28:31 +00:00
` ;
const zoomLevelPromise = emittedOnce ( ipcMain , 'zoom-level-set' ) ;
await w . loadURL ( serverUrl ) ;
await w . webContents . executeJavaScript ( source ) ;
let [ , zoomLevel ] = await zoomLevelPromise ;
expect ( zoomLevel ) . to . equal ( 0.6 ) ;
const loadPromise = emittedOnce ( w . webContents , 'did-finish-load' ) ;
await w . loadURL ( crossSiteUrl ) ;
await loadPromise ;
zoomLevel = w . webContents . zoomLevel ;
expect ( zoomLevel ) . to . equal ( 0 ) ;
} ) ;
} ) ;
} ) ;
2019-09-04 21:44:13 +00:00
2019-09-30 22:00:47 +00:00
describe ( 'webrtc ip policy api' , ( ) = > {
2020-03-20 20:28:31 +00:00
afterEach ( closeAllWindows ) ;
2019-09-30 22:00:47 +00:00
it ( 'can set and get webrtc ip policies' , ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
2019-09-30 22:00:47 +00:00
const policies = [
'default' ,
'default_public_interface_only' ,
'default_public_and_private_interfaces' ,
'disable_non_proxied_udp'
2020-03-20 20:28:31 +00:00
] ;
2019-09-30 22:00:47 +00:00
policies . forEach ( ( policy ) = > {
2020-03-20 20:28:31 +00:00
w . webContents . setWebRTCIPHandlingPolicy ( policy as any ) ;
expect ( w . webContents . getWebRTCIPHandlingPolicy ( ) ) . to . equal ( policy ) ;
} ) ;
} ) ;
} ) ;
2019-09-30 22:00:47 +00:00
describe ( 'render view deleted events' , ( ) = > {
2020-03-20 20:28:31 +00:00
let server : http.Server ;
let serverUrl : string ;
let crossSiteUrl : string ;
2019-09-30 22:00:47 +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' , ` ${ crossSiteUrl } /redirected ` ) ;
res . statusCode = 302 ;
res . end ( ) ;
2019-09-30 22:00:47 +00:00
} else if ( req . url === '/redirected' ) {
2020-03-20 20:28:31 +00:00
res . end ( '<html><script>window.localStorage</script></html>' ) ;
2020-10-07 23:11:19 +00:00
} else if ( req . url === '/first-window-open' ) {
res . end ( ` <html><script>window.open(' ${ serverUrl } /second-window-open', 'first child');</script></html> ` ) ;
} else if ( req . url === '/second-window-open' ) {
res . end ( '<html><script>window.open(\'wrong://url\', \'second child\');</script></html>' ) ;
2019-09-30 22:00:47 +00:00
} else {
2020-03-20 20:28:31 +00:00
res . end ( ) ;
2019-09-30 22:00:47 +00:00
}
2020-03-20 20:28:31 +00:00
} ;
setTimeout ( respond , 0 ) ;
} ) ;
2019-09-30 22:00:47 +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 } ` ;
crossSiteUrl = ` http://localhost: ${ ( server . address ( ) as AddressInfo ) . port } ` ;
done ( ) ;
} ) ;
} ) ;
2019-09-30 22:00:47 +00:00
after ( ( ) = > {
2020-03-20 20:28:31 +00:00
server . close ( ) ;
} ) ;
2019-09-30 22:00:47 +00:00
2020-03-20 20:28:31 +00:00
afterEach ( closeAllWindows ) ;
2019-09-30 22:00:47 +00:00
2020-06-30 22:10:36 +00:00
it ( 'does not emit current-render-view-deleted when speculative RVHs are deleted' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
let currentRenderViewDeletedEmitted = false ;
2019-09-30 22:00:47 +00:00
const renderViewDeletedHandler = ( ) = > {
2020-03-20 20:28:31 +00:00
currentRenderViewDeletedEmitted = true ;
} ;
w . webContents . on ( 'current-render-view-deleted' as any , renderViewDeletedHandler ) ;
2019-09-30 22:00:47 +00:00
w . webContents . on ( 'did-finish-load' , ( ) = > {
2020-03-20 20:28:31 +00:00
w . webContents . removeListener ( 'current-render-view-deleted' as any , renderViewDeletedHandler ) ;
w . close ( ) ;
} ) ;
2020-06-30 22:10:36 +00:00
const destroyed = emittedOnce ( w . webContents , 'destroyed' ) ;
2020-03-20 20:28:31 +00:00
w . loadURL ( ` ${ serverUrl } /redirect-cross-site ` ) ;
2020-06-30 22:10:36 +00:00
await destroyed ;
expect ( currentRenderViewDeletedEmitted ) . to . be . false ( 'current-render-view-deleted was emitted' ) ;
2020-03-20 20:28:31 +00:00
} ) ;
2019-09-30 22:00:47 +00:00
2020-10-28 00:33:04 +00:00
// TODO (jkleinsc) - this test is very flaky on WOA due to its dependence on a setTimeout; disabling until it can be rewritten
ifit ( process . platform !== 'win32' || process . arch !== 'arm64' ) ( 'does not emit current-render-view-deleted when speculative RVHs are deleted and nativeWindowOpen is set to true' , async ( ) = > {
2020-10-07 23:11:19 +00:00
const parentWindow = new BrowserWindow ( { show : false , webPreferences : { nativeWindowOpen : true } } ) ;
let currentRenderViewDeletedEmitted = false ;
let childWindow :BrowserWindow ;
const destroyed = emittedOnce ( parentWindow . webContents , 'destroyed' ) ;
const renderViewDeletedHandler = ( ) = > {
currentRenderViewDeletedEmitted = true ;
} ;
app . once ( 'browser-window-created' , ( event , window ) = > {
childWindow = window ;
window . webContents . on ( 'current-render-view-deleted' as any , renderViewDeletedHandler ) ;
} ) ;
parentWindow . loadURL ( ` ${ serverUrl } /first-window-open ` ) ;
setTimeout ( ( ) = > {
childWindow . webContents . removeListener ( 'current-render-view-deleted' as any , renderViewDeletedHandler ) ;
parentWindow . close ( ) ;
} , 500 ) ;
await destroyed ;
expect ( currentRenderViewDeletedEmitted ) . to . be . false ( 'child window was destroyed' ) ;
} ) ;
2020-06-30 22:10:36 +00:00
it ( 'emits current-render-view-deleted if the current RVHs are deleted' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
let currentRenderViewDeletedEmitted = false ;
2019-09-30 22:00:47 +00:00
w . webContents . on ( 'current-render-view-deleted' as any , ( ) = > {
2020-03-20 20:28:31 +00:00
currentRenderViewDeletedEmitted = true ;
} ) ;
2019-09-30 22:00:47 +00:00
w . webContents . on ( 'did-finish-load' , ( ) = > {
2020-03-20 20:28:31 +00:00
w . close ( ) ;
} ) ;
2020-06-30 22:10:36 +00:00
const destroyed = emittedOnce ( w . webContents , 'destroyed' ) ;
2020-03-20 20:28:31 +00:00
w . loadURL ( ` ${ serverUrl } /redirect-cross-site ` ) ;
2020-06-30 22:10:36 +00:00
await destroyed ;
expect ( currentRenderViewDeletedEmitted ) . to . be . true ( 'current-render-view-deleted wasn\'t emitted' ) ;
2020-03-20 20:28:31 +00:00
} ) ;
2019-09-30 22:00:47 +00:00
2020-06-30 22:10:36 +00:00
it ( 'emits render-view-deleted if any RVHs are deleted' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
let rvhDeletedCount = 0 ;
2019-09-30 22:00:47 +00:00
w . webContents . on ( 'render-view-deleted' as any , ( ) = > {
2020-03-20 20:28:31 +00:00
rvhDeletedCount ++ ;
} ) ;
2019-09-30 22:00:47 +00:00
w . webContents . on ( 'did-finish-load' , ( ) = > {
2020-03-20 20:28:31 +00:00
w . close ( ) ;
} ) ;
2020-06-30 22:10:36 +00:00
const destroyed = emittedOnce ( w . webContents , 'destroyed' ) ;
2020-03-20 20:28:31 +00:00
w . loadURL ( ` ${ serverUrl } /redirect-cross-site ` ) ;
2020-06-30 22:10:36 +00:00
await destroyed ;
const expectedRenderViewDeletedEventCount = 1 ;
expect ( rvhDeletedCount ) . to . equal ( expectedRenderViewDeletedEventCount , 'render-view-deleted wasn\'t emitted the expected nr. of times' ) ;
2020-03-20 20:28:31 +00:00
} ) ;
} ) ;
2019-09-30 22:00:47 +00:00
describe ( 'setIgnoreMenuShortcuts(ignore)' , ( ) = > {
2020-03-20 20:28:31 +00:00
afterEach ( closeAllWindows ) ;
2019-09-30 22:00:47 +00:00
it ( 'does not throw' , ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
2019-09-30 22:00:47 +00:00
expect ( ( ) = > {
2020-03-20 20:28:31 +00:00
w . webContents . setIgnoreMenuShortcuts ( true ) ;
w . webContents . setIgnoreMenuShortcuts ( false ) ;
} ) . to . not . throw ( ) ;
} ) ;
} ) ;
2019-09-30 22:00:47 +00:00
describe ( 'create()' , ( ) = > {
it ( 'does not crash on exit' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const appPath = path . join ( fixturesPath , 'api' , 'leak-exit-webcontents.js' ) ;
const electronPath = process . execPath ;
const appProcess = ChildProcess . spawn ( electronPath , [ appPath ] ) ;
const [ code ] = await emittedOnce ( appProcess , 'close' ) ;
expect ( code ) . to . equal ( 0 ) ;
} ) ;
} ) ;
2019-09-30 22:00:47 +00:00
2020-10-02 21:50:24 +00:00
const crashPrefs = [
{
nodeIntegration : true
} ,
{
sandbox : true
}
] ;
const nicePrefs = ( o : any ) = > {
let s = '' ;
for ( const key of Object . keys ( o ) ) {
s += ` ${ key } = ${ o [ key ] } , ` ;
}
return ` ( ${ s . slice ( 0 , s . length - 2 ) } ) ` ;
} ;
for ( const prefs of crashPrefs ) {
describe ( ` crash with webPreferences ${ nicePrefs ( prefs ) } ` , ( ) = > {
let w : BrowserWindow ;
beforeEach ( async ( ) = > {
w = new BrowserWindow ( { show : false , webPreferences : { nodeIntegration : true } } ) ;
await w . loadURL ( 'about:blank' ) ;
} ) ;
afterEach ( closeAllWindows ) ;
it ( 'isCrashed() is false by default' , ( ) = > {
expect ( w . webContents . isCrashed ( ) ) . to . equal ( false ) ;
} ) ;
it ( 'forcefullyCrashRenderer() crashes the process with reason=killed||crashed' , async ( ) = > {
expect ( w . webContents . isCrashed ( ) ) . to . equal ( false ) ;
const crashEvent = emittedOnce ( w . webContents , 'render-process-gone' ) ;
w . webContents . forcefullyCrashRenderer ( ) ;
const [ , details ] = await crashEvent ;
expect ( details . reason === 'killed' || details . reason === 'crashed' ) . to . equal ( true , 'reason should be killed || crashed' ) ;
expect ( w . webContents . isCrashed ( ) ) . to . equal ( true ) ;
} ) ;
it ( 'a crashed process is recoverable with reload()' , async ( ) = > {
expect ( w . webContents . isCrashed ( ) ) . to . equal ( false ) ;
w . webContents . forcefullyCrashRenderer ( ) ;
w . webContents . reload ( ) ;
expect ( w . webContents . isCrashed ( ) ) . to . equal ( false ) ;
} ) ;
} ) ;
}
2019-09-30 22:00:47 +00:00
// Destroying webContents in its event listener is going to crash when
// Electron is built in Debug mode.
describe ( 'destroy()' , ( ) = > {
2020-03-20 20:28:31 +00:00
let server : http.Server ;
let serverUrl : string ;
2019-09-30 22:00:47 +00:00
before ( ( done ) = > {
server = http . createServer ( ( request , response ) = > {
switch ( request . url ) {
case '/net-error' :
2020-03-20 20:28:31 +00:00
response . destroy ( ) ;
break ;
2019-09-30 22:00:47 +00:00
case '/200' :
2020-03-20 20:28:31 +00:00
response . end ( ) ;
break ;
2019-09-30 22:00:47 +00:00
default :
2020-03-20 20:28:31 +00:00
done ( 'unsupported endpoint' ) ;
2019-09-30 22:00:47 +00:00
}
} ) . 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 ;
done ( ) ;
} ) ;
} ) ;
2019-09-30 22:00:47 +00:00
after ( ( ) = > {
2020-03-20 20:28:31 +00:00
server . close ( ) ;
} ) ;
2019-09-30 22:00:47 +00:00
const events = [
{ name : 'did-start-loading' , url : '/200' } ,
{ name : 'dom-ready' , url : '/200' } ,
{ name : 'did-stop-loading' , url : '/200' } ,
{ name : 'did-finish-load' , url : '/200' } ,
// FIXME: Multiple Emit calls inside an observer assume that object
// will be alive till end of the observer. Synchronous `destroy` api
// violates this contract and crashes.
{ name : 'did-frame-finish-load' , url : '/200' } ,
{ name : 'did-fail-load' , url : '/net-error' }
2020-03-20 20:28:31 +00:00
] ;
2019-09-30 22:00:47 +00:00
for ( const e of events ) {
2020-01-09 19:50:30 +00:00
it ( ` should not crash when invoked synchronously inside ${ e . name } handler ` , async function ( ) {
// This test is flaky on Windows CI and we don't know why, but the
// purpose of this test is to make sure Electron does not crash so it
// is fine to retry this test for a few times.
2020-03-20 20:28:31 +00:00
this . retries ( 3 ) ;
const contents = ( webContents as any ) . create ( ) as WebContents ;
const originalEmit = contents . emit . bind ( contents ) ;
contents . emit = ( . . . args ) = > { return originalEmit ( . . . args ) ; } ;
contents . once ( e . name as any , ( ) = > ( contents as any ) . destroy ( ) ) ;
const destroyed = emittedOnce ( contents , 'destroyed' ) ;
contents . loadURL ( serverUrl + e . url ) ;
await destroyed ;
} ) ;
2019-09-30 22:00:47 +00:00
}
2020-03-20 20:28:31 +00:00
} ) ;
2019-09-30 22:00:47 +00:00
2020-10-20 00:11:30 +00:00
// TODO (jkleinsc) - reenable this test on WOA once https://github.com/electron/electron/issues/26045 is resolved
ifdescribe ( process . platform !== 'win32' || process . arch !== 'arm64' ) ( 'did-change-theme-color event' , ( ) = > {
2020-03-20 20:28:31 +00:00
afterEach ( closeAllWindows ) ;
2019-09-30 22:00:47 +00:00
it ( 'is triggered with correct theme color' , ( done ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : true } ) ;
let count = 0 ;
2019-09-30 22:00:47 +00:00
w . webContents . on ( 'did-change-theme-color' , ( e , color ) = > {
2020-06-30 22:10:36 +00:00
try {
if ( count === 0 ) {
count += 1 ;
expect ( color ) . to . equal ( '#FFEEDD' ) ;
w . loadFile ( path . join ( fixturesPath , 'pages' , 'base-page.html' ) ) ;
} else if ( count === 1 ) {
expect ( color ) . to . be . null ( ) ;
done ( ) ;
}
} catch ( e ) {
done ( e ) ;
2019-09-30 22:00:47 +00:00
}
2020-03-20 20:28:31 +00:00
} ) ;
w . loadFile ( path . join ( fixturesPath , 'pages' , 'theme-color.html' ) ) ;
} ) ;
} ) ;
2019-09-30 22:00:47 +00:00
describe ( 'console-message event' , ( ) = > {
2020-03-20 20:28:31 +00:00
afterEach ( closeAllWindows ) ;
2019-09-30 22:00:47 +00:00
it ( 'is triggered with correct log message' , ( done ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : true } ) ;
2019-09-30 22:00:47 +00:00
w . webContents . on ( 'console-message' , ( e , level , message ) = > {
// Don't just assert as Chromium might emit other logs that we should ignore.
if ( message === 'a' ) {
2020-03-20 20:28:31 +00:00
done ( ) ;
2019-09-30 22:00:47 +00:00
}
2020-03-20 20:28:31 +00:00
} ) ;
w . loadFile ( path . join ( fixturesPath , 'pages' , 'a.html' ) ) ;
} ) ;
} ) ;
2019-09-30 22:00:47 +00:00
describe ( 'ipc-message event' , ( ) = > {
2020-03-20 20:28:31 +00:00
afterEach ( closeAllWindows ) ;
2019-09-30 22:00:47 +00:00
it ( 'emits when the renderer process sends an asynchronous message' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : true , webPreferences : { nodeIntegration : true } } ) ;
await w . webContents . loadURL ( 'about:blank' ) ;
2019-09-30 22:00:47 +00:00
w . webContents . executeJavaScript ( `
require ( 'electron' ) . ipcRenderer . send ( 'message' , 'Hello World!' )
2020-03-20 20:28:31 +00:00
` );
2019-09-30 22:00:47 +00:00
2020-03-20 20:28:31 +00:00
const [ , channel , message ] = await emittedOnce ( w . webContents , 'ipc-message' ) ;
expect ( channel ) . to . equal ( 'message' ) ;
expect ( message ) . to . equal ( 'Hello World!' ) ;
} ) ;
} ) ;
2019-09-30 22:00:47 +00:00
describe ( 'ipc-message-sync event' , ( ) = > {
2020-03-20 20:28:31 +00:00
afterEach ( closeAllWindows ) ;
2019-09-30 22:00:47 +00:00
it ( 'emits when the renderer process sends a synchronous message' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : true , webPreferences : { nodeIntegration : true } } ) ;
await w . webContents . loadURL ( 'about:blank' ) ;
2019-09-30 22:00:47 +00:00
const promise : Promise < [ string , string ] > = new Promise ( resolve = > {
w . webContents . once ( 'ipc-message-sync' , ( event , channel , arg ) = > {
2020-03-20 20:28:31 +00:00
event . returnValue = 'foobar' as any ;
resolve ( [ channel , arg ] ) ;
} ) ;
} ) ;
2019-09-30 22:00:47 +00:00
const result = await w . webContents . executeJavaScript ( `
require ( 'electron' ) . ipcRenderer . sendSync ( 'message' , 'Hello World!' )
2020-03-20 20:28:31 +00:00
` );
2019-09-30 22:00:47 +00:00
2020-03-20 20:28:31 +00:00
const [ channel , message ] = await promise ;
expect ( channel ) . to . equal ( 'message' ) ;
expect ( message ) . to . equal ( 'Hello World!' ) ;
expect ( result ) . to . equal ( 'foobar' ) ;
} ) ;
} ) ;
2019-09-30 22:00:47 +00:00
describe ( 'referrer' , ( ) = > {
2020-03-20 20:28:31 +00:00
afterEach ( closeAllWindows ) ;
2019-09-30 22:00:47 +00:00
it ( 'propagates referrer information to new target=_blank windows' , ( done ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
2019-09-30 22:00:47 +00:00
const server = http . createServer ( ( req , res ) = > {
if ( req . url === '/should_have_referrer' ) {
2020-06-30 22:10:36 +00:00
try {
expect ( req . headers . referer ) . to . equal ( ` http://127.0.0.1: ${ ( server . address ( ) as AddressInfo ) . port } / ` ) ;
return done ( ) ;
} catch ( e ) {
return done ( e ) ;
} finally {
server . close ( ) ;
}
2019-09-30 22:00:47 +00:00
}
2020-03-20 20:28:31 +00:00
res . end ( '<a id="a" href="/should_have_referrer" target="_blank">link</a>' ) ;
} ) ;
2019-09-30 22:00:47 +00:00
server . listen ( 0 , '127.0.0.1' , ( ) = > {
2020-03-20 20:28:31 +00:00
const url = 'http://127.0.0.1:' + ( server . address ( ) as AddressInfo ) . port + '/' ;
2019-09-30 22:00:47 +00:00
w . webContents . once ( 'did-finish-load' , ( ) = > {
w . webContents . once ( 'new-window' , ( event , newUrl , frameName , disposition , options , features , referrer ) = > {
2020-03-20 20:28:31 +00:00
expect ( referrer . url ) . to . equal ( url ) ;
expect ( referrer . policy ) . to . equal ( 'no-referrer-when-downgrade' ) ;
} ) ;
w . webContents . executeJavaScript ( 'a.click()' ) ;
} ) ;
w . loadURL ( url ) ;
} ) ;
} ) ;
2019-09-30 22:00:47 +00:00
// TODO(jeremy): window.open() in a real browser passes the referrer, but
// our hacked-up window.open() shim doesn't. It should.
xit ( 'propagates referrer information to windows opened with window.open' , ( done ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
2019-09-30 22:00:47 +00:00
const server = http . createServer ( ( req , res ) = > {
if ( req . url === '/should_have_referrer' ) {
2020-06-30 22:10:36 +00:00
try {
expect ( req . headers . referer ) . to . equal ( ` http://127.0.0.1: ${ ( server . address ( ) as AddressInfo ) . port } / ` ) ;
return done ( ) ;
} catch ( e ) {
return done ( e ) ;
}
2019-09-30 22:00:47 +00:00
}
2020-03-20 20:28:31 +00:00
res . end ( '' ) ;
} ) ;
2019-09-30 22:00:47 +00:00
server . listen ( 0 , '127.0.0.1' , ( ) = > {
2020-03-20 20:28:31 +00:00
const url = 'http://127.0.0.1:' + ( server . address ( ) as AddressInfo ) . port + '/' ;
2019-09-30 22:00:47 +00:00
w . webContents . once ( 'did-finish-load' , ( ) = > {
w . webContents . once ( 'new-window' , ( event , newUrl , frameName , disposition , options , features , referrer ) = > {
2020-03-20 20:28:31 +00:00
expect ( referrer . url ) . to . equal ( url ) ;
expect ( referrer . policy ) . to . equal ( 'no-referrer-when-downgrade' ) ;
} ) ;
w . webContents . executeJavaScript ( 'window.open(location.href + "should_have_referrer")' ) ;
} ) ;
w . loadURL ( url ) ;
} ) ;
} ) ;
} ) ;
2019-09-30 22:00:47 +00:00
describe ( 'webframe messages in sandboxed contents' , ( ) = > {
2020-03-20 20:28:31 +00:00
afterEach ( closeAllWindows ) ;
2019-09-30 22:00:47 +00:00
it ( 'responds to executeJavaScript' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false , webPreferences : { sandbox : true } } ) ;
await w . loadURL ( 'about:blank' ) ;
const result = await w . webContents . executeJavaScript ( '37 + 5' ) ;
expect ( result ) . to . equal ( 42 ) ;
} ) ;
} ) ;
2019-09-30 22:00:47 +00:00
describe ( 'preload-error event' , ( ) = > {
2020-03-20 20:28:31 +00:00
afterEach ( closeAllWindows ) ;
2019-09-30 22:00:47 +00:00
const generateSpecs = ( description : string , sandbox : boolean ) = > {
describe ( description , ( ) = > {
it ( 'is triggered when unhandled exception is thrown' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const preload = path . join ( fixturesPath , 'module' , 'preload-error-exception.js' ) ;
2019-09-30 22:00:47 +00:00
const w = new BrowserWindow ( {
show : false ,
webPreferences : {
sandbox ,
preload
}
2020-03-20 20:28:31 +00:00
} ) ;
2019-09-30 22:00:47 +00:00
2020-03-20 20:28:31 +00:00
const promise = emittedOnce ( w . webContents , 'preload-error' ) ;
w . loadURL ( 'about:blank' ) ;
2019-09-30 22:00:47 +00:00
2020-03-20 20:28:31 +00:00
const [ , preloadPath , error ] = await promise ;
expect ( preloadPath ) . to . equal ( preload ) ;
expect ( error . message ) . to . equal ( 'Hello World!' ) ;
} ) ;
2019-09-30 22:00:47 +00:00
it ( 'is triggered on syntax errors' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const preload = path . join ( fixturesPath , 'module' , 'preload-error-syntax.js' ) ;
2019-09-30 22:00:47 +00:00
const w = new BrowserWindow ( {
show : false ,
webPreferences : {
sandbox ,
preload
}
2020-03-20 20:28:31 +00:00
} ) ;
2019-09-30 22:00:47 +00:00
2020-03-20 20:28:31 +00:00
const promise = emittedOnce ( w . webContents , 'preload-error' ) ;
w . loadURL ( 'about:blank' ) ;
2019-09-30 22:00:47 +00:00
2020-03-20 20:28:31 +00:00
const [ , preloadPath , error ] = await promise ;
expect ( preloadPath ) . to . equal ( preload ) ;
expect ( error . message ) . to . equal ( 'foobar is not defined' ) ;
} ) ;
2019-09-30 22:00:47 +00:00
it ( 'is triggered when preload script loading fails' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const preload = path . join ( fixturesPath , 'module' , 'preload-invalid.js' ) ;
2019-09-30 22:00:47 +00:00
const w = new BrowserWindow ( {
show : false ,
webPreferences : {
sandbox ,
preload
}
2020-03-20 20:28:31 +00:00
} ) ;
2019-09-30 22:00:47 +00:00
2020-03-20 20:28:31 +00:00
const promise = emittedOnce ( w . webContents , 'preload-error' ) ;
w . loadURL ( 'about:blank' ) ;
2019-09-30 22:00:47 +00:00
2020-03-20 20:28:31 +00:00
const [ , preloadPath , error ] = await promise ;
expect ( preloadPath ) . to . equal ( preload ) ;
expect ( error . message ) . to . contain ( 'preload-invalid.js' ) ;
} ) ;
} ) ;
} ;
2019-09-30 22:00:47 +00:00
2020-03-20 20:28:31 +00:00
generateSpecs ( 'without sandbox' , false ) ;
generateSpecs ( 'with sandbox' , true ) ;
} ) ;
2019-09-30 22:00:47 +00:00
describe ( 'takeHeapSnapshot()' , ( ) = > {
2020-03-20 20:28:31 +00:00
afterEach ( closeAllWindows ) ;
2019-09-30 22:00:47 +00:00
it ( 'works with sandboxed renderers' , async ( ) = > {
const w = new BrowserWindow ( {
show : false ,
webPreferences : {
sandbox : true
}
2020-03-20 20:28:31 +00:00
} ) ;
2019-09-30 22:00:47 +00:00
2020-03-20 20:28:31 +00:00
await w . loadURL ( 'about:blank' ) ;
2019-09-30 22:00:47 +00:00
2020-03-20 20:28:31 +00:00
const filePath = path . join ( app . getPath ( 'temp' ) , 'test.heapsnapshot' ) ;
2019-09-30 22:00:47 +00:00
const cleanup = ( ) = > {
try {
2020-03-20 20:28:31 +00:00
fs . unlinkSync ( filePath ) ;
2019-09-30 22:00:47 +00:00
} catch ( e ) {
// ignore error
}
2020-03-20 20:28:31 +00:00
} ;
2019-09-30 22:00:47 +00:00
try {
2020-03-20 20:28:31 +00:00
await w . webContents . takeHeapSnapshot ( filePath ) ;
const stats = fs . statSync ( filePath ) ;
expect ( stats . size ) . not . to . be . equal ( 0 ) ;
2019-09-30 22:00:47 +00:00
} finally {
2020-03-20 20:28:31 +00:00
cleanup ( ) ;
2019-09-30 22:00:47 +00:00
}
2020-03-20 20:28:31 +00:00
} ) ;
2019-09-30 22:00:47 +00:00
it ( 'fails with invalid file path' , async ( ) = > {
const w = new BrowserWindow ( {
show : false ,
webPreferences : {
sandbox : true
}
2020-03-20 20:28:31 +00:00
} ) ;
2019-09-30 22:00:47 +00:00
2020-03-20 20:28:31 +00:00
await w . loadURL ( 'about:blank' ) ;
2019-09-30 22:00:47 +00:00
2020-03-20 20:28:31 +00:00
const promise = w . webContents . takeHeapSnapshot ( '' ) ;
return expect ( promise ) . to . be . eventually . rejectedWith ( Error , 'takeHeapSnapshot failed' ) ;
} ) ;
} ) ;
2019-09-30 22:00:47 +00:00
describe ( 'setBackgroundThrottling()' , ( ) = > {
2020-03-20 20:28:31 +00:00
afterEach ( closeAllWindows ) ;
2019-09-30 22:00:47 +00:00
it ( 'does not crash when allowing' , ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
w . webContents . setBackgroundThrottling ( true ) ;
} ) ;
2019-09-30 22:00:47 +00:00
it ( 'does not crash when called via BrowserWindow' , ( ) = > {
const w = new BrowserWindow ( { show : false } ) ;
2020-03-20 20:28:31 +00:00
( w as any ) . setBackgroundThrottling ( true ) ;
} ) ;
2019-09-30 22:00:47 +00:00
it ( 'does not crash when disallowing' , ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false , webPreferences : { backgroundThrottling : true } } ) ;
2019-09-30 22:00:47 +00:00
2020-03-20 20:28:31 +00:00
w . webContents . setBackgroundThrottling ( false ) ;
} ) ;
} ) ;
2019-09-30 22:00:47 +00:00
2020-05-14 13:11:45 +00:00
describe ( 'getBackgroundThrottling()' , ( ) = > {
afterEach ( closeAllWindows ) ;
it ( 'works via getter' , ( ) = > {
const w = new BrowserWindow ( { show : false } ) ;
w . webContents . setBackgroundThrottling ( false ) ;
expect ( w . webContents . getBackgroundThrottling ( ) ) . to . equal ( false ) ;
w . webContents . setBackgroundThrottling ( true ) ;
expect ( w . webContents . getBackgroundThrottling ( ) ) . to . equal ( true ) ;
} ) ;
it ( 'works via property' , ( ) = > {
const w = new BrowserWindow ( { show : false } ) ;
w . webContents . backgroundThrottling = false ;
expect ( w . webContents . backgroundThrottling ) . to . equal ( false ) ;
w . webContents . backgroundThrottling = true ;
expect ( w . webContents . backgroundThrottling ) . to . equal ( true ) ;
} ) ;
it ( 'works via BrowserWindow' , ( ) = > {
const w = new BrowserWindow ( { show : false } ) ;
( w as any ) . setBackgroundThrottling ( false ) ;
expect ( ( w as any ) . getBackgroundThrottling ( ) ) . to . equal ( false ) ;
( w as any ) . setBackgroundThrottling ( true ) ;
expect ( ( w as any ) . getBackgroundThrottling ( ) ) . to . equal ( true ) ;
} ) ;
} ) ;
2019-09-30 22:00:47 +00:00
ifdescribe ( features . isPrintingEnabled ( ) ) ( 'getPrinters()' , ( ) = > {
2020-03-20 20:28:31 +00:00
afterEach ( closeAllWindows ) ;
2019-09-30 22:00:47 +00:00
it ( 'can get printer list' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false , webPreferences : { sandbox : true } } ) ;
await w . loadURL ( 'about:blank' ) ;
const printers = w . webContents . getPrinters ( ) ;
expect ( printers ) . to . be . an ( 'array' ) ;
} ) ;
} ) ;
2019-09-30 22:00:47 +00:00
ifdescribe ( features . isPrintingEnabled ( ) ) ( 'printToPDF()' , ( ) = > {
2020-03-20 20:28:31 +00:00
let w : BrowserWindow ;
2020-01-28 20:47:24 +00:00
beforeEach ( async ( ) = > {
2020-03-20 20:28:31 +00:00
w = new BrowserWindow ( { show : false , webPreferences : { sandbox : true } } ) ;
await w . loadURL ( 'data:text/html,<h1>Hello, World!</h1>' ) ;
} ) ;
2020-01-28 20:47:24 +00:00
2020-03-20 20:28:31 +00:00
afterEach ( closeAllWindows ) ;
2020-01-28 20:47:24 +00:00
it ( 'rejects on incorrectly typed parameters' , async ( ) = > {
const badTypes = {
marginsType : 'terrible' ,
scaleFactor : 'not-a-number' ,
landscape : [ ] ,
2020-03-20 15:12:18 +00:00
pageRanges : { oops : 'im-not-the-right-key' } ,
2020-01-28 20:47:24 +00:00
headerFooter : '123' ,
printSelectionOnly : 1 ,
printBackground : 2 ,
pageSize : 'IAmAPageSize'
2020-03-20 20:28:31 +00:00
} ;
2020-01-28 20:47:24 +00:00
// These will hard crash in Chromium unless we type-check
for ( const [ key , value ] of Object . entries ( badTypes ) ) {
2020-03-20 20:28:31 +00:00
const param = { [ key ] : value } ;
await expect ( w . webContents . printToPDF ( param ) ) . to . eventually . be . rejected ( ) ;
2020-01-28 20:47:24 +00:00
}
2020-03-20 20:28:31 +00:00
} ) ;
2020-01-28 20:47:24 +00:00
2020-01-16 05:05:36 +00:00
it ( 'can print to PDF' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const data = await w . webContents . printToPDF ( { } ) ;
expect ( data ) . to . be . an . instanceof ( Buffer ) . that . is . not . empty ( ) ;
} ) ;
2019-10-29 02:36:29 +00:00
2020-01-30 20:12:35 +00:00
it ( 'respects custom settings' , async ( ) = > {
2020-06-30 22:10:36 +00:00
await w . loadFile ( path . join ( __dirname , 'fixtures' , 'api' , 'print-to-pdf.html' ) ) ;
2020-01-30 20:12:35 +00:00
const data = await w . webContents . printToPDF ( {
pageRanges : {
from : 0 ,
to : 2
} ,
landscape : true
2020-03-20 20:28:31 +00:00
} ) ;
2020-01-30 20:12:35 +00:00
2020-03-20 20:28:31 +00:00
const doc = await pdfjs . getDocument ( data ) . promise ;
2020-01-30 20:12:35 +00:00
// Check that correct # of pages are rendered.
2020-03-20 20:28:31 +00:00
expect ( doc . numPages ) . to . equal ( 3 ) ;
2020-01-30 20:12:35 +00:00
// Check that PDF is generated in landscape mode.
2020-03-20 20:28:31 +00:00
const firstPage = await doc . getPage ( 1 ) ;
const { width , height } = firstPage . getViewport ( { scale : 100 } ) ;
expect ( width ) . to . be . greaterThan ( height ) ;
} ) ;
2020-01-30 20:12:35 +00:00
2020-07-14 01:13:34 +00:00
it ( 'does not crash when called multiple times in parallel' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const promises = [ ] ;
2020-07-14 01:13:34 +00:00
for ( let i = 0 ; i < 3 ; i ++ ) {
2020-03-20 20:28:31 +00:00
promises . push ( w . webContents . printToPDF ( { } ) ) ;
2019-10-29 02:36:29 +00:00
}
2020-07-14 01:13:34 +00:00
2020-03-20 20:28:31 +00:00
const results = await Promise . all ( promises ) ;
2019-10-29 02:36:29 +00:00
for ( const data of results ) {
2020-03-20 20:28:31 +00:00
expect ( data ) . to . be . an . instanceof ( Buffer ) . that . is . not . empty ( ) ;
2019-10-29 02:36:29 +00:00
}
2020-03-20 20:28:31 +00:00
} ) ;
2020-07-14 01:13:34 +00:00
it ( 'does not crash when called multiple times in sequence' , async ( ) = > {
const results = [ ] ;
for ( let i = 0 ; i < 3 ; i ++ ) {
const result = await w . webContents . printToPDF ( { } ) ;
results . push ( result ) ;
}
for ( const data of results ) {
expect ( data ) . to . be . an . instanceof ( Buffer ) . that . is . not . empty ( ) ;
}
} ) ;
2020-03-20 20:28:31 +00:00
} ) ;
2019-09-30 22:00:47 +00:00
describe ( 'PictureInPicture video' , ( ) = > {
2020-03-20 20:28:31 +00:00
afterEach ( closeAllWindows ) ;
2020-06-30 22:10:36 +00:00
it ( 'works as expected' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false , webPreferences : { sandbox : true } } ) ;
2020-06-30 22:10:36 +00:00
await w . loadFile ( path . join ( fixturesPath , 'api' , 'picture-in-picture.html' ) ) ;
const result = await w . webContents . executeJavaScript (
` runTest( ${ features . isPictureInPictureEnabled ( ) } ) ` , true ) ;
expect ( result ) . to . be . true ( ) ;
2020-03-20 20:28:31 +00:00
} ) ;
} ) ;
2019-09-30 22:00:47 +00:00
2019-09-04 21:44:13 +00:00
describe ( 'devtools window' , ( ) = > {
2020-03-20 20:28:31 +00:00
let hasRobotJS = false ;
2019-09-04 21:44:13 +00:00
try {
// We have other tests that check if native modules work, if we fail to require
// robotjs let's skip this test to avoid false negatives
2020-03-20 20:28:31 +00:00
require ( 'robotjs' ) ;
hasRobotJS = true ;
2019-09-04 21:44:13 +00:00
} catch ( err ) { /* no-op */ }
2020-03-20 20:28:31 +00:00
afterEach ( closeAllWindows ) ;
2019-09-04 21:44:13 +00:00
// NB. on macOS, this requires that you grant your terminal the ability to
// control your computer. Open System Preferences > Security & Privacy >
// Privacy > Accessibility and grant your terminal the permission to control
// your computer.
ifit ( hasRobotJS ) ( 'can receive and handle menu events' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : true , webPreferences : { nodeIntegration : true } } ) ;
w . loadFile ( path . join ( fixturesPath , 'pages' , 'key-events.html' ) ) ;
2019-09-04 21:44:13 +00:00
// Ensure the devtools are loaded
2020-03-20 20:28:31 +00:00
w . webContents . closeDevTools ( ) ;
const opened = emittedOnce ( w . webContents , 'devtools-opened' ) ;
w . webContents . openDevTools ( ) ;
await opened ;
2020-04-20 23:51:27 +00:00
await emittedOnce ( w . webContents . devToolsWebContents ! , 'did-finish-load' ) ;
w . webContents . devToolsWebContents ! . focus ( ) ;
2019-09-04 21:44:13 +00:00
// Focus an input field
2020-04-20 23:51:27 +00:00
await w . webContents . devToolsWebContents ! . executeJavaScript ( `
2019-09-04 21:44:13 +00:00
const input = document . createElement ( 'input' )
document . body . innerHTML = ''
document . body . appendChild ( input )
input . focus ( )
2020-03-20 20:28:31 +00:00
` );
2019-09-04 21:44:13 +00:00
// Write something to the clipboard
2020-03-20 20:28:31 +00:00
clipboard . writeText ( 'test value' ) ;
2019-09-04 21:44:13 +00:00
2020-04-20 23:51:27 +00:00
const pasted = w . webContents . devToolsWebContents ! . executeJavaScript ( ` new Promise(resolve => {
2019-09-04 21:44:13 +00:00
document . querySelector ( 'input' ) . addEventListener ( 'paste' , ( e ) = > {
resolve ( e . target . value )
} )
2020-03-20 20:28:31 +00:00
} ) ` );
2019-09-04 21:44:13 +00:00
// Fake a paste request using robotjs to emulate a REAL keyboard paste event
2020-03-20 20:28:31 +00:00
require ( 'robotjs' ) . keyTap ( 'v' , process . platform === 'darwin' ? [ 'command' ] : [ 'control' ] ) ;
2019-09-04 21:44:13 +00:00
2020-03-20 20:28:31 +00:00
const val = await pasted ;
2019-09-04 21:44:13 +00:00
// Once we're done expect the paste to have been successful
2020-03-20 20:28:31 +00:00
expect ( val ) . to . equal ( 'test value' , 'value should eventually become the pasted value' ) ;
} ) ;
} ) ;
2019-10-02 12:38:27 +00:00
describe ( 'Shared Workers' , ( ) = > {
2020-03-20 20:28:31 +00:00
afterEach ( closeAllWindows ) ;
2019-10-02 12:38:27 +00:00
it ( 'can get multiple shared workers' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false , webPreferences : { nodeIntegration : true } } ) ;
2019-10-02 12:38:27 +00:00
2020-03-20 20:28:31 +00:00
const ready = emittedOnce ( ipcMain , 'ready' ) ;
w . loadFile ( path . join ( fixturesPath , 'api' , 'shared-worker' , 'shared-worker.html' ) ) ;
await ready ;
2019-10-02 12:38:27 +00:00
2020-03-20 20:28:31 +00:00
const sharedWorkers = w . webContents . getAllSharedWorkers ( ) ;
2019-10-02 12:38:27 +00:00
2020-03-20 20:28:31 +00:00
expect ( sharedWorkers ) . to . have . lengthOf ( 2 ) ;
expect ( sharedWorkers [ 0 ] . url ) . to . contain ( 'shared-worker' ) ;
expect ( sharedWorkers [ 1 ] . url ) . to . contain ( 'shared-worker' ) ;
} ) ;
2019-10-02 12:38:27 +00:00
it ( 'can inspect a specific shared worker' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false , webPreferences : { nodeIntegration : true } } ) ;
2019-10-02 12:38:27 +00:00
2020-03-20 20:28:31 +00:00
const ready = emittedOnce ( ipcMain , 'ready' ) ;
w . loadFile ( path . join ( fixturesPath , 'api' , 'shared-worker' , 'shared-worker.html' ) ) ;
await ready ;
2019-10-02 12:38:27 +00:00
2020-03-20 20:28:31 +00:00
const sharedWorkers = w . webContents . getAllSharedWorkers ( ) ;
2019-10-02 12:38:27 +00:00
2020-03-20 20:28:31 +00:00
const devtoolsOpened = emittedOnce ( w . webContents , 'devtools-opened' ) ;
w . webContents . inspectSharedWorkerById ( sharedWorkers [ 0 ] . id ) ;
await devtoolsOpened ;
2019-10-02 12:38:27 +00:00
2020-03-20 20:28:31 +00:00
const devtoolsClosed = emittedOnce ( w . webContents , 'devtools-closed' ) ;
w . webContents . closeDevTools ( ) ;
await devtoolsClosed ;
} ) ;
} ) ;
2019-11-11 17:47:01 +00:00
describe ( 'login event' , ( ) = > {
2020-03-20 20:28:31 +00:00
afterEach ( closeAllWindows ) ;
2019-11-11 17:47:01 +00:00
2020-03-20 20:28:31 +00:00
let server : http.Server ;
let serverUrl : string ;
let serverPort : number ;
let proxyServer : http.Server ;
let proxyServerPort : number ;
2019-11-11 17:47:01 +00:00
before ( ( done ) = > {
server = http . createServer ( ( request , response ) = > {
if ( request . url === '/no-auth' ) {
2020-03-20 20:28:31 +00:00
return response . end ( 'ok' ) ;
2019-11-11 17:47:01 +00:00
}
if ( request . headers . authorization ) {
2020-03-20 20:28:31 +00:00
response . writeHead ( 200 , { 'Content-type' : 'text/plain' } ) ;
return response . end ( request . headers . authorization ) ;
2019-11-11 17:47:01 +00:00
}
response
. writeHead ( 401 , { 'WWW-Authenticate' : 'Basic realm="Foo"' } )
2020-03-20 20:28:31 +00:00
. end ( '401' ) ;
2019-11-11 17:47:01 +00:00
} ) . listen ( 0 , '127.0.0.1' , ( ) = > {
2020-03-20 20:28:31 +00:00
serverPort = ( server . address ( ) as AddressInfo ) . port ;
serverUrl = ` http://127.0.0.1: ${ serverPort } ` ;
done ( ) ;
} ) ;
} ) ;
2019-11-11 17:47:01 +00:00
before ( ( done ) = > {
proxyServer = http . createServer ( ( request , response ) = > {
if ( request . headers [ 'proxy-authorization' ] ) {
2020-03-20 20:28:31 +00:00
response . writeHead ( 200 , { 'Content-type' : 'text/plain' } ) ;
return response . end ( request . headers [ 'proxy-authorization' ] ) ;
2019-11-11 17:47:01 +00:00
}
response
. writeHead ( 407 , { 'Proxy-Authenticate' : 'Basic realm="Foo"' } )
2020-03-20 20:28:31 +00:00
. end ( ) ;
2019-11-11 17:47:01 +00:00
} ) . listen ( 0 , '127.0.0.1' , ( ) = > {
2020-03-20 20:28:31 +00:00
proxyServerPort = ( proxyServer . address ( ) as AddressInfo ) . port ;
done ( ) ;
} ) ;
} ) ;
2019-11-11 17:47:01 +00:00
2019-11-14 18:01:18 +00:00
afterEach ( async ( ) = > {
2020-03-20 20:28:31 +00:00
await session . defaultSession . clearAuthCache ( ) ;
} ) ;
2019-11-14 18:01:18 +00:00
2019-11-11 17:47:01 +00:00
after ( ( ) = > {
2020-03-20 20:28:31 +00:00
server . close ( ) ;
proxyServer . close ( ) ;
} ) ;
2019-11-11 17:47:01 +00:00
it ( 'is emitted when navigating' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const [ user , pass ] = [ 'user' , 'pass' ] ;
const w = new BrowserWindow ( { show : false } ) ;
let eventRequest : any ;
let eventAuthInfo : any ;
2019-11-11 17:47:01 +00:00
w . webContents . on ( 'login' , ( event , request , authInfo , cb ) = > {
2020-03-20 20:28:31 +00:00
eventRequest = request ;
eventAuthInfo = authInfo ;
event . preventDefault ( ) ;
cb ( user , pass ) ;
} ) ;
await w . loadURL ( serverUrl ) ;
const body = await w . webContents . executeJavaScript ( 'document.documentElement.textContent' ) ;
expect ( body ) . to . equal ( ` Basic ${ Buffer . from ( ` ${ user } : ${ pass } ` ) . toString ( 'base64' ) } ` ) ;
expect ( eventRequest . url ) . to . equal ( serverUrl + '/' ) ;
expect ( eventAuthInfo . isProxy ) . to . be . false ( ) ;
expect ( eventAuthInfo . scheme ) . to . equal ( 'basic' ) ;
expect ( eventAuthInfo . host ) . to . equal ( '127.0.0.1' ) ;
expect ( eventAuthInfo . port ) . to . equal ( serverPort ) ;
expect ( eventAuthInfo . realm ) . to . equal ( 'Foo' ) ;
} ) ;
2019-11-11 17:47:01 +00:00
it ( 'is emitted when a proxy requests authorization' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const customSession = session . fromPartition ( ` ${ Math . random ( ) } ` ) ;
await customSession . setProxy ( { proxyRules : ` 127.0.0.1: ${ proxyServerPort } ` , proxyBypassRules : '<-loopback>' } ) ;
const [ user , pass ] = [ 'user' , 'pass' ] ;
const w = new BrowserWindow ( { show : false , webPreferences : { session : customSession } } ) ;
let eventRequest : any ;
let eventAuthInfo : any ;
2019-11-11 17:47:01 +00:00
w . webContents . on ( 'login' , ( event , request , authInfo , cb ) = > {
2020-03-20 20:28:31 +00:00
eventRequest = request ;
eventAuthInfo = authInfo ;
event . preventDefault ( ) ;
cb ( user , pass ) ;
} ) ;
await w . loadURL ( ` ${ serverUrl } /no-auth ` ) ;
const body = await w . webContents . executeJavaScript ( 'document.documentElement.textContent' ) ;
expect ( body ) . to . equal ( ` Basic ${ Buffer . from ( ` ${ user } : ${ pass } ` ) . toString ( 'base64' ) } ` ) ;
expect ( eventRequest . url ) . to . equal ( ` ${ serverUrl } /no-auth ` ) ;
expect ( eventAuthInfo . isProxy ) . to . be . true ( ) ;
expect ( eventAuthInfo . scheme ) . to . equal ( 'basic' ) ;
expect ( eventAuthInfo . host ) . to . equal ( '127.0.0.1' ) ;
expect ( eventAuthInfo . port ) . to . equal ( proxyServerPort ) ;
expect ( eventAuthInfo . realm ) . to . equal ( 'Foo' ) ;
} ) ;
2019-11-14 18:01:18 +00:00
it ( 'cancels authentication when callback is called with no arguments' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false } ) ;
2019-11-14 18:01:18 +00:00
w . webContents . on ( 'login' , ( event , request , authInfo , cb ) = > {
2020-03-20 20:28:31 +00:00
event . preventDefault ( ) ;
cb ( ) ;
} ) ;
await w . loadURL ( serverUrl ) ;
const body = await w . webContents . executeJavaScript ( 'document.documentElement.textContent' ) ;
expect ( body ) . to . equal ( '401' ) ;
} ) ;
} ) ;
2020-04-13 15:37:41 +00:00
it ( 'emits a cancelable event before creating a child webcontents' , async ( ) = > {
const w = new BrowserWindow ( {
show : false ,
webPreferences : {
sandbox : true
}
} ) ;
w . webContents . on ( '-will-add-new-contents' as any , ( event : any , url : any ) = > {
expect ( url ) . to . equal ( 'about:blank' ) ;
event . preventDefault ( ) ;
} ) ;
let wasCalled = false ;
w . webContents . on ( 'new-window' as any , ( ) = > {
wasCalled = true ;
} ) ;
await w . loadURL ( 'about:blank' ) ;
2020-07-09 17:18:49 +00:00
await w . webContents . executeJavaScript ( 'window.open(\'about:blank\')' ) ;
2020-04-13 15:37:41 +00:00
await new Promise ( ( resolve ) = > { process . nextTick ( resolve ) ; } ) ;
expect ( wasCalled ) . to . equal ( false ) ;
await closeAllWindows ( ) ;
} ) ;
2020-03-20 20:28:31 +00:00
} ) ;