2020-03-20 20:28:31 +00:00
import { expect } from 'chai' ;
2020-12-18 23:11:43 +00:00
import { app , session , BrowserWindow , ipcMain , WebContents , Extension , Session } from 'electron/main' ;
2020-03-20 20:28:31 +00:00
import { closeAllWindows , closeWindow } from './window-helpers' ;
import * as http from 'http' ;
import { AddressInfo } from 'net' ;
import * as path from 'path' ;
import * as fs from 'fs' ;
2020-12-18 23:11:43 +00:00
import * as WebSocket from 'ws' ;
2021-03-08 16:15:39 +00:00
import { emittedOnce , emittedNTimes , emittedUntil } from './events-helpers' ;
2020-03-20 20:28:31 +00:00
2020-12-18 23:11:43 +00:00
const uuid = require ( 'uuid' ) ;
2020-03-20 20:28:31 +00:00
const fixtures = path . join ( __dirname , 'fixtures' ) ;
2019-07-24 23:01:08 +00:00
2020-04-30 16:38:09 +00:00
describe ( 'chrome extensions' , ( ) = > {
2020-06-03 07:56:28 +00:00
const emptyPage = '<script>console.log("loaded")</script>' ;
2019-07-24 23:01:08 +00:00
// NB. extensions are only allowed on http://, https:// and ftp:// (!) urls by default.
2020-03-20 20:28:31 +00:00
let server : http.Server ;
let url : string ;
2020-12-18 23:11:43 +00:00
let port : string ;
2019-07-24 23:01:08 +00:00
before ( async ( ) = > {
2020-08-03 23:56:18 +00:00
server = http . createServer ( ( req , res ) = > {
if ( req . url === '/cors' ) {
res . setHeader ( 'Access-Control-Allow-Origin' , 'http://example.com' ) ;
}
res . end ( emptyPage ) ;
} ) ;
2020-12-18 23:11:43 +00:00
const wss = new WebSocket . Server ( { noServer : true } ) ;
wss . on ( 'connection' , function connection ( ws ) {
ws . on ( 'message' , function incoming ( message ) {
if ( message === 'foo' ) {
ws . send ( 'bar' ) ;
}
} ) ;
} ) ;
2021-01-22 19:25:47 +00:00
await new Promise < void > ( resolve = > server . listen ( 0 , '127.0.0.1' , ( ) = > {
2020-12-18 23:11:43 +00:00
port = String ( ( server . address ( ) as AddressInfo ) . port ) ;
url = ` http://127.0.0.1: ${ port } ` ;
2020-03-20 20:28:31 +00:00
resolve ( ) ;
} ) ) ;
} ) ;
2019-07-24 23:01:08 +00:00
after ( ( ) = > {
2020-03-20 20:28:31 +00:00
server . close ( ) ;
} ) ;
afterEach ( closeAllWindows ) ;
2020-02-10 16:28:03 +00:00
afterEach ( ( ) = > {
session . defaultSession . getAllExtensions ( ) . forEach ( ( e : any ) = > {
2020-03-20 20:28:31 +00:00
session . defaultSession . removeExtension ( e . id ) ;
} ) ;
} ) ;
2020-02-10 16:28:03 +00:00
2020-09-02 00:59:56 +00:00
it ( 'does not crash when using chrome.management' , async ( ) = > {
const customSession = session . fromPartition ( ` persist: ${ require ( 'uuid' ) . v4 ( ) } ` ) ;
const w = new BrowserWindow ( { show : false , webPreferences : { session : customSession , sandbox : true } } ) ;
2020-09-15 16:29:32 +00:00
await w . loadURL ( 'about:blank' ) ;
2020-09-02 00:59:56 +00:00
2021-04-23 20:51:37 +00:00
const promise = emittedOnce ( app , 'web-contents-created' ) ;
2020-09-02 00:59:56 +00:00
await customSession . loadExtension ( path . join ( fixtures , 'extensions' , 'persistent-background-page' ) ) ;
2021-04-23 20:51:37 +00:00
const args : any = await promise ;
2020-09-02 00:59:56 +00:00
const wc : Electron.WebContents = args [ 1 ] ;
await expect ( wc . executeJavaScript ( `
( ( ) = > {
return new Promise ( ( resolve ) = > {
chrome . management . getSelf ( ( info ) = > {
resolve ( info ) ;
} ) ;
} )
} ) ( ) ;
` )).to.eventually.have.property('id');
} ) ;
2020-08-19 00:41:47 +00:00
it ( 'can open WebSQLDatabase in a background page' , async ( ) = > {
const customSession = session . fromPartition ( ` persist: ${ require ( 'uuid' ) . v4 ( ) } ` ) ;
const w = new BrowserWindow ( { show : false , webPreferences : { session : customSession , sandbox : true } } ) ;
2020-09-15 16:29:32 +00:00
await w . loadURL ( 'about:blank' ) ;
2020-08-19 00:41:47 +00:00
2021-04-23 20:51:37 +00:00
const promise = emittedOnce ( app , 'web-contents-created' ) ;
2020-08-19 00:41:47 +00:00
await customSession . loadExtension ( path . join ( fixtures , 'extensions' , 'persistent-background-page' ) ) ;
2021-04-23 20:51:37 +00:00
const args : any = await promise ;
2020-08-19 00:41:47 +00:00
const wc : Electron.WebContents = args [ 1 ] ;
await expect ( wc . executeJavaScript ( '(()=>{try{openDatabase("t", "1.0", "test", 2e5);return true;}catch(e){throw e}})()' ) ) . to . not . be . rejected ( ) ;
} ) ;
2020-08-03 23:56:18 +00:00
function fetch ( contents : WebContents , url : string ) {
return contents . executeJavaScript ( ` fetch( ${ JSON . stringify ( url ) } ) ` ) ;
}
it ( 'bypasses CORS in requests made from extensions' , async ( ) = > {
const customSession = session . fromPartition ( ` persist: ${ require ( 'uuid' ) . v4 ( ) } ` ) ;
const w = new BrowserWindow ( { show : false , webPreferences : { session : customSession , sandbox : true } } ) ;
const extension = await customSession . loadExtension ( path . join ( fixtures , 'extensions' , 'ui-page' ) ) ;
2020-09-15 16:29:32 +00:00
await w . loadURL ( ` ${ extension . url } bare-page.html ` ) ;
2020-08-03 23:56:18 +00:00
await expect ( fetch ( w . webContents , ` ${ url } /cors ` ) ) . to . not . be . rejectedWith ( TypeError ) ;
} ) ;
2019-07-24 23:01:08 +00:00
it ( 'loads an extension' , async ( ) = > {
// NB. we have to use a persist: session (i.e. non-OTR) because the
// extension registry is redirected to the main session. so installing an
// extension in an in-memory session results in it being installed in the
// default session.
2020-12-18 23:11:43 +00:00
const customSession = session . fromPartition ( ` persist: ${ uuid . v4 ( ) } ` ) ;
2020-03-20 20:28:31 +00:00
await customSession . loadExtension ( path . join ( fixtures , 'extensions' , 'red-bg' ) ) ;
const w = new BrowserWindow ( { show : false , webPreferences : { session : customSession } } ) ;
2020-09-15 16:29:32 +00:00
await w . loadURL ( url ) ;
2020-03-20 20:28:31 +00:00
const bg = await w . webContents . executeJavaScript ( 'document.documentElement.style.backgroundColor' ) ;
expect ( bg ) . to . equal ( 'red' ) ;
} ) ;
2019-07-24 23:01:08 +00:00
2021-02-02 10:20:05 +00:00
it ( 'does not crash when failing to load an extension' , async ( ) = > {
const customSession = session . fromPartition ( ` persist: ${ uuid . v4 ( ) } ` ) ;
const promise = customSession . loadExtension ( path . join ( fixtures , 'extensions' , 'load-error' ) ) ;
await expect ( promise ) . to . eventually . be . rejected ( ) ;
} ) ;
2020-02-24 03:30:32 +00:00
it ( 'serializes a loaded extension' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const extensionPath = path . join ( fixtures , 'extensions' , 'red-bg' ) ;
const manifest = JSON . parse ( fs . readFileSync ( path . join ( extensionPath , 'manifest.json' ) , 'utf-8' ) ) ;
2020-12-18 23:11:43 +00:00
const customSession = session . fromPartition ( ` persist: ${ uuid . v4 ( ) } ` ) ;
2020-03-20 20:28:31 +00:00
const extension = await customSession . loadExtension ( extensionPath ) ;
expect ( extension . id ) . to . be . a ( 'string' ) ;
expect ( extension . name ) . to . be . a ( 'string' ) ;
expect ( extension . path ) . to . be . a ( 'string' ) ;
expect ( extension . version ) . to . be . a ( 'string' ) ;
expect ( extension . url ) . to . be . a ( 'string' ) ;
expect ( extension . manifest ) . to . deep . equal ( manifest ) ;
} ) ;
2020-02-24 03:30:32 +00:00
2020-01-13 22:56:28 +00:00
it ( 'removes an extension' , async ( ) = > {
2020-12-18 23:11:43 +00:00
const customSession = session . fromPartition ( ` persist: ${ uuid . v4 ( ) } ` ) ;
2020-03-20 20:28:31 +00:00
const { id } = await customSession . loadExtension ( path . join ( fixtures , 'extensions' , 'red-bg' ) ) ;
2020-01-13 22:56:28 +00:00
{
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false , webPreferences : { session : customSession } } ) ;
await w . loadURL ( url ) ;
const bg = await w . webContents . executeJavaScript ( 'document.documentElement.style.backgroundColor' ) ;
expect ( bg ) . to . equal ( 'red' ) ;
2020-01-13 22:56:28 +00:00
}
2020-03-20 20:28:31 +00:00
customSession . removeExtension ( id ) ;
2020-01-13 22:56:28 +00:00
{
2020-03-20 20:28:31 +00:00
const w = new BrowserWindow ( { show : false , webPreferences : { session : customSession } } ) ;
await w . loadURL ( url ) ;
const bg = await w . webContents . executeJavaScript ( 'document.documentElement.style.backgroundColor' ) ;
expect ( bg ) . to . equal ( '' ) ;
2020-01-13 22:56:28 +00:00
}
2020-03-20 20:28:31 +00:00
} ) ;
2020-01-13 22:56:28 +00:00
2020-09-23 19:29:08 +00:00
it ( 'emits extension lifecycle events' , async ( ) = > {
const customSession = session . fromPartition ( ` persist: ${ require ( 'uuid' ) . v4 ( ) } ` ) ;
const loadedPromise = emittedOnce ( customSession , 'extension-loaded' ) ;
const extension = await customSession . loadExtension ( path . join ( fixtures , 'extensions' , 'red-bg' ) ) ;
const [ , loadedExtension ] = await loadedPromise ;
2021-03-08 16:15:39 +00:00
const [ , readyExtension ] = await emittedUntil ( customSession , 'extension-ready' , ( event : Event , extension : Extension ) = > {
return extension . name !== 'Chromium PDF Viewer' ;
} ) ;
2020-09-23 19:29:08 +00:00
2021-03-09 07:05:02 +00:00
expect ( loadedExtension ) . to . deep . equal ( extension ) ;
expect ( readyExtension ) . to . deep . equal ( extension ) ;
2020-09-23 19:29:08 +00:00
const unloadedPromise = emittedOnce ( customSession , 'extension-unloaded' ) ;
await customSession . removeExtension ( extension . id ) ;
const [ , unloadedExtension ] = await unloadedPromise ;
2021-03-09 07:05:02 +00:00
expect ( unloadedExtension ) . to . deep . equal ( extension ) ;
2020-09-23 19:29:08 +00:00
} ) ;
2020-01-13 22:56:28 +00:00
it ( 'lists loaded extensions in getAllExtensions' , async ( ) = > {
2020-12-18 23:11:43 +00:00
const customSession = session . fromPartition ( ` persist: ${ uuid . v4 ( ) } ` ) ;
2020-03-20 20:28:31 +00:00
const e = await customSession . loadExtension ( path . join ( fixtures , 'extensions' , 'red-bg' ) ) ;
expect ( customSession . getAllExtensions ( ) ) . to . deep . equal ( [ e ] ) ;
customSession . removeExtension ( e . id ) ;
expect ( customSession . getAllExtensions ( ) ) . to . deep . equal ( [ ] ) ;
} ) ;
2020-01-13 22:56:28 +00:00
it ( 'gets an extension by id' , async ( ) = > {
2020-12-18 23:11:43 +00:00
const customSession = session . fromPartition ( ` persist: ${ uuid . v4 ( ) } ` ) ;
2020-03-20 20:28:31 +00:00
const e = await customSession . loadExtension ( path . join ( fixtures , 'extensions' , 'red-bg' ) ) ;
expect ( customSession . getExtension ( e . id ) ) . to . deep . equal ( e ) ;
} ) ;
2020-01-13 22:56:28 +00:00
2019-07-24 23:01:08 +00:00
it ( 'confines an extension to the session it was loaded in' , async ( ) = > {
2020-12-18 23:11:43 +00:00
const customSession = session . fromPartition ( ` persist: ${ uuid . v4 ( ) } ` ) ;
2020-03-20 20:28:31 +00:00
await customSession . loadExtension ( path . join ( fixtures , 'extensions' , 'red-bg' ) ) ;
const w = new BrowserWindow ( { show : false } ) ; // not in the session
2020-09-15 16:29:32 +00:00
await w . loadURL ( url ) ;
2020-03-20 20:28:31 +00:00
const bg = await w . webContents . executeJavaScript ( 'document.documentElement.style.backgroundColor' ) ;
expect ( bg ) . to . equal ( '' ) ;
} ) ;
2019-07-24 23:01:08 +00:00
2020-02-10 16:28:03 +00:00
it ( 'loading an extension in a temporary session throws an error' , async ( ) = > {
2020-12-18 23:11:43 +00:00
const customSession = session . fromPartition ( uuid . v4 ( ) ) ;
2020-03-20 20:28:31 +00:00
await expect ( customSession . loadExtension ( path . join ( fixtures , 'extensions' , 'red-bg' ) ) ) . to . eventually . be . rejectedWith ( 'Extensions cannot be loaded in a temporary session' ) ;
} ) ;
2020-02-10 16:28:03 +00:00
2020-03-05 14:56:21 +00:00
describe ( 'chrome.i18n' , ( ) = > {
2020-03-20 20:28:31 +00:00
let w : BrowserWindow ;
let extension : Extension ;
2020-03-05 14:56:21 +00:00
const exec = async ( name : string ) = > {
2020-03-20 20:28:31 +00:00
const p = emittedOnce ( ipcMain , 'success' ) ;
await w . webContents . executeJavaScript ( ` exec(' ${ name } ') ` ) ;
const [ , result ] = await p ;
return result ;
} ;
2020-03-05 14:56:21 +00:00
beforeEach ( async ( ) = > {
2020-12-18 23:11:43 +00:00
const customSession = session . fromPartition ( ` persist: ${ uuid . v4 ( ) } ` ) ;
2020-03-20 20:28:31 +00:00
extension = await customSession . loadExtension ( path . join ( fixtures , 'extensions' , 'chrome-i18n' ) ) ;
2021-03-01 21:52:29 +00:00
w = new BrowserWindow ( { show : false , webPreferences : { session : customSession , nodeIntegration : true , contextIsolation : false } } ) ;
2020-09-15 16:29:32 +00:00
await w . loadURL ( url ) ;
2020-03-20 20:28:31 +00:00
} ) ;
2020-03-05 14:56:21 +00:00
it ( 'getAcceptLanguages()' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const result = await exec ( 'getAcceptLanguages' ) ;
expect ( result ) . to . be . an ( 'array' ) . and . deep . equal ( [ 'en-US' ] ) ;
} ) ;
2020-03-05 14:56:21 +00:00
it ( 'getMessage()' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const result = await exec ( 'getMessage' ) ;
expect ( result . id ) . to . be . a ( 'string' ) . and . equal ( extension . id ) ;
expect ( result . name ) . to . be . a ( 'string' ) . and . equal ( 'chrome-i18n' ) ;
} ) ;
} ) ;
2020-03-05 14:56:21 +00:00
2019-07-24 23:01:08 +00:00
describe ( 'chrome.runtime' , ( ) = > {
2020-09-15 16:29:32 +00:00
let w : BrowserWindow ;
const exec = async ( name : string ) = > {
const p = emittedOnce ( ipcMain , 'success' ) ;
await w . webContents . executeJavaScript ( ` exec(' ${ name } ') ` ) ;
const [ , result ] = await p ;
return result ;
} ;
beforeEach ( async ( ) = > {
2020-12-18 23:11:43 +00:00
const customSession = session . fromPartition ( ` persist: ${ uuid . v4 ( ) } ` ) ;
2020-06-03 07:56:28 +00:00
await customSession . loadExtension ( path . join ( fixtures , 'extensions' , 'chrome-runtime' ) ) ;
2021-03-01 21:52:29 +00:00
w = new BrowserWindow ( { show : false , webPreferences : { session : customSession , nodeIntegration : true , contextIsolation : false } } ) ;
2020-09-15 16:29:32 +00:00
await w . loadURL ( url ) ;
} ) ;
it ( 'getManifest()' , async ( ) = > {
const result = await exec ( 'getManifest' ) ;
expect ( result ) . to . be . an ( 'object' ) . with . property ( 'name' , 'chrome-runtime' ) ;
2020-03-20 20:28:31 +00:00
} ) ;
2020-09-15 16:29:32 +00:00
it ( 'id' , async ( ) = > {
const result = await exec ( 'id' ) ;
expect ( result ) . to . be . a ( 'string' ) . with . lengthOf ( 32 ) ;
2020-03-20 20:28:31 +00:00
} ) ;
2020-09-15 16:29:32 +00:00
it ( 'getURL()' , async ( ) = > {
const result = await exec ( 'getURL' ) ;
expect ( result ) . to . be . a ( 'string' ) . and . match ( /^chrome-extension:\/\/.*main.js$/ ) ;
2020-03-20 20:28:31 +00:00
} ) ;
2020-09-15 16:29:32 +00:00
it ( 'getPlatformInfo()' , async ( ) = > {
const result = await exec ( 'getPlatformInfo' ) ;
expect ( result ) . to . be . an ( 'object' ) ;
expect ( result . os ) . to . be . a ( 'string' ) ;
expect ( result . arch ) . to . be . a ( 'string' ) ;
expect ( result . nacl_arch ) . to . be . a ( 'string' ) ;
2020-03-20 20:28:31 +00:00
} ) ;
} ) ;
2019-07-24 23:01:08 +00:00
describe ( 'chrome.storage' , ( ) = > {
it ( 'stores and retrieves a key' , async ( ) = > {
2020-12-18 23:11:43 +00:00
const customSession = session . fromPartition ( ` persist: ${ uuid . v4 ( ) } ` ) ;
2020-03-20 20:28:31 +00:00
await customSession . loadExtension ( path . join ( fixtures , 'extensions' , 'chrome-storage' ) ) ;
2021-03-01 21:52:29 +00:00
const w = new BrowserWindow ( { show : false , webPreferences : { session : customSession , nodeIntegration : true , contextIsolation : false } } ) ;
2019-07-24 23:01:08 +00:00
try {
2020-03-20 20:28:31 +00:00
const p = emittedOnce ( ipcMain , 'storage-success' ) ;
await w . loadURL ( url ) ;
const [ , v ] = await p ;
expect ( v ) . to . equal ( 'value' ) ;
2019-07-24 23:01:08 +00:00
} finally {
2020-03-20 20:28:31 +00:00
w . destroy ( ) ;
2019-07-24 23:01:08 +00:00
}
2020-03-20 20:28:31 +00:00
} ) ;
} ) ;
2020-01-13 22:55:58 +00:00
2020-12-18 23:11:43 +00:00
describe ( 'chrome.webRequest' , ( ) = > {
function fetch ( contents : WebContents , url : string ) {
return contents . executeJavaScript ( ` fetch( ${ JSON . stringify ( url ) } ) ` ) ;
}
let customSession : Session ;
let w : BrowserWindow ;
beforeEach ( ( ) = > {
customSession = session . fromPartition ( ` persist: ${ uuid . v4 ( ) } ` ) ;
w = new BrowserWindow ( { show : false , webPreferences : { session : customSession , sandbox : true , contextIsolation : true } } ) ;
} ) ;
describe ( 'onBeforeRequest' , ( ) = > {
it ( 'can cancel http requests' , async ( ) = > {
await w . loadURL ( url ) ;
await customSession . loadExtension ( path . join ( fixtures , 'extensions' , 'chrome-webRequest' ) ) ;
2021-01-28 19:51:08 +00:00
await expect ( fetch ( w . webContents , url ) ) . to . eventually . be . rejectedWith ( 'Failed to fetch' ) ;
2020-12-18 23:11:43 +00:00
} ) ;
it ( 'does not cancel http requests when no extension loaded' , async ( ) = > {
await w . loadURL ( url ) ;
2021-01-28 19:51:08 +00:00
await expect ( fetch ( w . webContents , url ) ) . to . not . be . rejectedWith ( 'Failed to fetch' ) ;
2020-12-18 23:11:43 +00:00
} ) ;
} ) ;
it ( 'does not take precedence over Electron webRequest - http' , async ( ) = > {
2021-01-22 19:25:47 +00:00
return new Promise < void > ( ( resolve ) = > {
2020-12-18 23:11:43 +00:00
( async ( ) = > {
customSession . webRequest . onBeforeRequest ( ( details , callback ) = > {
resolve ( ) ;
callback ( { cancel : true } ) ;
} ) ;
await w . loadURL ( url ) ;
await customSession . loadExtension ( path . join ( fixtures , 'extensions' , 'chrome-webRequest' ) ) ;
fetch ( w . webContents , url ) ;
} ) ( ) ;
} ) ;
} ) ;
it ( 'does not take precedence over Electron webRequest - WebSocket' , ( ) = > {
2021-01-22 19:25:47 +00:00
return new Promise < void > ( ( resolve ) = > {
2020-12-18 23:11:43 +00:00
( async ( ) = > {
customSession . webRequest . onBeforeSendHeaders ( ( ) = > {
resolve ( ) ;
} ) ;
await w . loadFile ( path . join ( fixtures , 'api' , 'webrequest.html' ) , { query : { port } } ) ;
await customSession . loadExtension ( path . join ( fixtures , 'extensions' , 'chrome-webRequest-wss' ) ) ;
} ) ( ) ;
} ) ;
} ) ;
describe ( 'WebSocket' , ( ) = > {
it ( 'can be proxied' , async ( ) = > {
await w . loadFile ( path . join ( fixtures , 'api' , 'webrequest.html' ) , { query : { port } } ) ;
await customSession . loadExtension ( path . join ( fixtures , 'extensions' , 'chrome-webRequest-wss' ) ) ;
customSession . webRequest . onSendHeaders ( ( details ) = > {
if ( details . url . startsWith ( 'ws://' ) ) {
expect ( details . requestHeaders . foo ) . be . equal ( 'bar' ) ;
}
} ) ;
} ) ;
} ) ;
} ) ;
2020-01-15 23:11:51 +00:00
describe ( 'chrome.tabs' , ( ) = > {
it ( 'executeScript' , async ( ) = > {
2020-12-18 23:11:43 +00:00
const customSession = session . fromPartition ( ` persist: ${ uuid . v4 ( ) } ` ) ;
2020-03-20 20:28:31 +00:00
await customSession . loadExtension ( path . join ( fixtures , 'extensions' , 'chrome-api' ) ) ;
const w = new BrowserWindow ( { show : false , webPreferences : { session : customSession , nodeIntegration : true } } ) ;
await w . loadURL ( url ) ;
2020-01-15 23:11:51 +00:00
2020-03-20 20:28:31 +00:00
const message = { method : 'executeScript' , args : [ '1 + 2' ] } ;
w . webContents . executeJavaScript ( ` window.postMessage(' ${ JSON . stringify ( message ) } ', '*') ` ) ;
2020-01-15 23:11:51 +00:00
2020-03-20 20:28:31 +00:00
const [ , , responseString ] = await emittedOnce ( w . webContents , 'console-message' ) ;
const response = JSON . parse ( responseString ) ;
2020-01-15 23:11:51 +00:00
2020-03-20 20:28:31 +00:00
expect ( response ) . to . equal ( 3 ) ;
} ) ;
2020-01-15 23:11:51 +00:00
2020-03-05 14:59:32 +00:00
it ( 'connect' , async ( ) = > {
2020-12-18 23:11:43 +00:00
const customSession = session . fromPartition ( ` persist: ${ uuid . v4 ( ) } ` ) ;
2020-03-20 20:28:31 +00:00
await customSession . loadExtension ( path . join ( fixtures , 'extensions' , 'chrome-api' ) ) ;
const w = new BrowserWindow ( { show : false , webPreferences : { session : customSession , nodeIntegration : true } } ) ;
await w . loadURL ( url ) ;
2020-03-05 14:59:32 +00:00
2020-12-18 23:11:43 +00:00
const portName = uuid . v4 ( ) ;
2020-03-20 20:28:31 +00:00
const message = { method : 'connectTab' , args : [ portName ] } ;
w . webContents . executeJavaScript ( ` window.postMessage(' ${ JSON . stringify ( message ) } ', '*') ` ) ;
2020-03-05 14:59:32 +00:00
2020-03-20 20:28:31 +00:00
const [ , , responseString ] = await emittedOnce ( w . webContents , 'console-message' ) ;
const response = responseString . split ( ',' ) ;
expect ( response [ 0 ] ) . to . equal ( portName ) ;
expect ( response [ 1 ] ) . to . equal ( 'howdy' ) ;
} ) ;
2020-03-05 14:59:32 +00:00
2020-01-15 23:11:51 +00:00
it ( 'sendMessage receives the response' , async function ( ) {
2020-12-18 23:11:43 +00:00
const customSession = session . fromPartition ( ` persist: ${ uuid . v4 ( ) } ` ) ;
2020-03-20 20:28:31 +00:00
await customSession . loadExtension ( path . join ( fixtures , 'extensions' , 'chrome-api' ) ) ;
const w = new BrowserWindow ( { show : false , webPreferences : { session : customSession , nodeIntegration : true } } ) ;
await w . loadURL ( url ) ;
2020-01-15 23:11:51 +00:00
2020-03-20 20:28:31 +00:00
const message = { method : 'sendMessage' , args : [ 'Hello World!' ] } ;
w . webContents . executeJavaScript ( ` window.postMessage(' ${ JSON . stringify ( message ) } ', '*') ` ) ;
2020-01-15 23:11:51 +00:00
2020-03-20 20:28:31 +00:00
const [ , , responseString ] = await emittedOnce ( w . webContents , 'console-message' ) ;
const response = JSON . parse ( responseString ) ;
2020-01-15 23:11:51 +00:00
2020-03-20 20:28:31 +00:00
expect ( response . message ) . to . equal ( 'Hello World!' ) ;
expect ( response . tabId ) . to . equal ( w . webContents . id ) ;
} ) ;
} ) ;
2020-01-15 23:11:51 +00:00
2020-01-13 22:55:58 +00:00
describe ( 'background pages' , ( ) = > {
it ( 'loads a lazy background page when sending a message' , async ( ) = > {
2020-12-18 23:11:43 +00:00
const customSession = session . fromPartition ( ` persist: ${ uuid . v4 ( ) } ` ) ;
2020-03-20 20:28:31 +00:00
await customSession . loadExtension ( path . join ( fixtures , 'extensions' , 'lazy-background-page' ) ) ;
2021-03-01 21:52:29 +00:00
const w = new BrowserWindow ( { show : false , webPreferences : { session : customSession , nodeIntegration : true , contextIsolation : false } } ) ;
2020-01-13 22:55:58 +00:00
try {
2020-03-20 20:28:31 +00:00
w . loadURL ( url ) ;
const [ , resp ] = await emittedOnce ( ipcMain , 'bg-page-message-response' ) ;
expect ( resp . message ) . to . deep . equal ( { some : 'message' } ) ;
expect ( resp . sender . id ) . to . be . a ( 'string' ) ;
expect ( resp . sender . origin ) . to . equal ( url ) ;
expect ( resp . sender . url ) . to . equal ( url + '/' ) ;
2020-01-13 22:55:58 +00:00
} finally {
2020-03-20 20:28:31 +00:00
w . destroy ( ) ;
2020-01-13 22:55:58 +00:00
}
2020-03-20 20:28:31 +00:00
} ) ;
2020-02-06 21:42:34 +00:00
it ( 'can use extension.getBackgroundPage from a ui page' , async ( ) = > {
2020-12-18 23:11:43 +00:00
const customSession = session . fromPartition ( ` persist: ${ uuid . v4 ( ) } ` ) ;
2020-03-20 20:28:31 +00:00
const { id } = await customSession . loadExtension ( path . join ( fixtures , 'extensions' , 'lazy-background-page' ) ) ;
const w = new BrowserWindow ( { show : false , webPreferences : { session : customSession } } ) ;
await w . loadURL ( ` chrome-extension:// ${ id } /page-get-background.html ` ) ;
const receivedMessage = await w . webContents . executeJavaScript ( 'window.completionPromise' ) ;
expect ( receivedMessage ) . to . deep . equal ( { some : 'message' } ) ;
} ) ;
2020-02-06 21:42:34 +00:00
it ( 'can use extension.getBackgroundPage from a ui page' , async ( ) = > {
2020-12-18 23:11:43 +00:00
const customSession = session . fromPartition ( ` persist: ${ uuid . v4 ( ) } ` ) ;
2020-03-20 20:28:31 +00:00
const { id } = await customSession . loadExtension ( path . join ( fixtures , 'extensions' , 'lazy-background-page' ) ) ;
const w = new BrowserWindow ( { show : false , webPreferences : { session : customSession } } ) ;
await w . loadURL ( ` chrome-extension:// ${ id } /page-get-background.html ` ) ;
const receivedMessage = await w . webContents . executeJavaScript ( 'window.completionPromise' ) ;
expect ( receivedMessage ) . to . deep . equal ( { some : 'message' } ) ;
} ) ;
2020-02-06 21:42:34 +00:00
it ( 'can use runtime.getBackgroundPage from a ui page' , async ( ) = > {
2020-12-18 23:11:43 +00:00
const customSession = session . fromPartition ( ` persist: ${ uuid . v4 ( ) } ` ) ;
2020-03-20 20:28:31 +00:00
const { id } = await customSession . loadExtension ( path . join ( fixtures , 'extensions' , 'lazy-background-page' ) ) ;
const w = new BrowserWindow ( { show : false , webPreferences : { session : customSession } } ) ;
await w . loadURL ( ` chrome-extension:// ${ id } /page-runtime-get-background.html ` ) ;
const receivedMessage = await w . webContents . executeJavaScript ( 'window.completionPromise' ) ;
expect ( receivedMessage ) . to . deep . equal ( { some : 'message' } ) ;
} ) ;
2020-01-15 00:20:30 +00:00
2020-09-08 11:55:40 +00:00
it ( 'has session in background page' , async ( ) = > {
const customSession = session . fromPartition ( ` persist: ${ require ( 'uuid' ) . v4 ( ) } ` ) ;
2021-04-23 20:51:37 +00:00
const promise = emittedOnce ( app , 'web-contents-created' ) ;
2020-09-08 11:55:40 +00:00
await customSession . loadExtension ( path . join ( fixtures , 'extensions' , 'persistent-background-page' ) ) ;
const w = new BrowserWindow ( { show : false , webPreferences : { session : customSession } } ) ;
await w . loadURL ( 'about:blank' ) ;
const [ , bgPageContents ] = await promise ;
2021-04-23 20:51:37 +00:00
expect ( bgPageContents . getType ( ) ) . to . equal ( 'backgroundPage' ) ;
expect ( bgPageContents . getURL ( ) ) . to . match ( /^chrome-extension:\/\/.+\/_generated_background_page.html$/ ) ;
2020-09-08 11:55:40 +00:00
expect ( bgPageContents . session ) . to . not . equal ( undefined ) ;
} ) ;
it ( 'can open devtools of background page' , async ( ) = > {
const customSession = session . fromPartition ( ` persist: ${ require ( 'uuid' ) . v4 ( ) } ` ) ;
2021-04-23 20:51:37 +00:00
const promise = emittedOnce ( app , 'web-contents-created' ) ;
2020-09-08 11:55:40 +00:00
await customSession . loadExtension ( path . join ( fixtures , 'extensions' , 'persistent-background-page' ) ) ;
const w = new BrowserWindow ( { show : false , webPreferences : { session : customSession } } ) ;
await w . loadURL ( 'about:blank' ) ;
const [ , bgPageContents ] = await promise ;
expect ( bgPageContents . getType ( ) ) . to . equal ( 'backgroundPage' ) ;
bgPageContents . openDevTools ( ) ;
bgPageContents . closeDevTools ( ) ;
} ) ;
2020-06-11 03:57:51 +00:00
} ) ;
2020-01-15 00:20:30 +00:00
describe ( 'devtools extensions' , ( ) = > {
2020-03-20 20:28:31 +00:00
let showPanelTimeoutId : any = null ;
2020-01-15 00:20:30 +00:00
afterEach ( ( ) = > {
2020-03-20 20:28:31 +00:00
if ( showPanelTimeoutId ) clearTimeout ( showPanelTimeoutId ) ;
} ) ;
2020-01-15 00:20:30 +00:00
const showLastDevToolsPanel = ( w : BrowserWindow ) = > {
w . webContents . once ( 'devtools-opened' , ( ) = > {
const show = ( ) = > {
2020-03-20 20:28:31 +00:00
if ( w == null || w . isDestroyed ( ) ) return ;
const { devToolsWebContents } = w as unknown as { devToolsWebContents : WebContents | undefined } ;
2020-01-15 00:20:30 +00:00
if ( devToolsWebContents == null || devToolsWebContents . isDestroyed ( ) ) {
2020-03-20 20:28:31 +00:00
return ;
2020-01-15 00:20:30 +00:00
}
const showLastPanel = ( ) = > {
// this is executed in the devtools context, where UI is a global
2020-03-20 20:28:31 +00:00
const { UI } = ( window as any ) ;
2021-01-25 16:46:00 +00:00
const tabs = UI . inspectorView . _tabbedPane . _tabs ;
const lastPanelId = tabs [ tabs . length - 1 ] . id ;
2020-03-20 20:28:31 +00:00
UI . inspectorView . showPanel ( lastPanelId ) ;
} ;
2020-01-15 00:20:30 +00:00
devToolsWebContents . executeJavaScript ( ` ( ${ showLastPanel } )() ` , false ) . then ( ( ) = > {
2020-03-20 20:28:31 +00:00
showPanelTimeoutId = setTimeout ( show , 100 ) ;
} ) ;
} ;
showPanelTimeoutId = setTimeout ( show , 100 ) ;
} ) ;
} ;
2020-01-15 00:20:30 +00:00
it ( 'loads a devtools extension' , async ( ) = > {
2020-12-18 23:11:43 +00:00
const customSession = session . fromPartition ( ` persist: ${ uuid . v4 ( ) } ` ) ;
2020-03-20 20:28:31 +00:00
customSession . loadExtension ( path . join ( fixtures , 'extensions' , 'devtools-extension' ) ) ;
2020-05-26 14:25:57 +00:00
const winningMessage = emittedOnce ( ipcMain , 'winning' ) ;
2021-03-01 21:52:29 +00:00
const w = new BrowserWindow ( { show : true , webPreferences : { session : customSession , nodeIntegration : true , contextIsolation : false } } ) ;
2020-04-13 23:39:26 +00:00
await w . loadURL ( url ) ;
2020-03-20 20:28:31 +00:00
w . webContents . openDevTools ( ) ;
showLastDevToolsPanel ( w ) ;
2020-05-26 14:25:57 +00:00
await winningMessage ;
2020-03-20 20:28:31 +00:00
} ) ;
} ) ;
2020-01-21 17:42:55 +00:00
2020-02-03 22:01:10 +00:00
describe ( 'chrome extension content scripts' , ( ) = > {
2020-03-20 20:28:31 +00:00
const fixtures = path . resolve ( __dirname , 'fixtures' ) ;
const extensionPath = path . resolve ( fixtures , 'extensions' ) ;
2020-02-03 22:01:10 +00:00
2020-03-20 20:28:31 +00:00
const addExtension = ( name : string ) = > session . defaultSession . loadExtension ( path . resolve ( extensionPath , name ) ) ;
2020-02-03 22:01:10 +00:00
const removeAllExtensions = ( ) = > {
Object . keys ( session . defaultSession . getAllExtensions ( ) ) . map ( extName = > {
2020-03-20 20:28:31 +00:00
session . defaultSession . removeExtension ( extName ) ;
} ) ;
} ;
2020-02-03 22:01:10 +00:00
2020-03-20 20:28:31 +00:00
let responseIdCounter = 0 ;
2020-02-03 22:01:10 +00:00
const executeJavaScriptInFrame = ( webContents : WebContents , frameRoutingId : number , code : string ) = > {
return new Promise ( resolve = > {
2020-03-20 20:28:31 +00:00
const responseId = responseIdCounter ++ ;
2020-02-03 22:01:10 +00:00
ipcMain . once ( ` executeJavaScriptInFrame_ ${ responseId } ` , ( event , result ) = > {
2020-03-20 20:28:31 +00:00
resolve ( result ) ;
} ) ;
webContents . send ( 'executeJavaScriptInFrame' , frameRoutingId , code , responseId ) ;
} ) ;
} ;
2020-02-03 22:01:10 +00:00
const generateTests = ( sandboxEnabled : boolean , contextIsolationEnabled : boolean ) = > {
describe ( ` with sandbox ${ sandboxEnabled ? 'enabled' : 'disabled' } and context isolation ${ contextIsolationEnabled ? 'enabled' : 'disabled' } ` , ( ) = > {
2020-03-20 20:28:31 +00:00
let w : BrowserWindow ;
2020-02-03 22:01:10 +00:00
describe ( 'supports "run_at" option' , ( ) = > {
beforeEach ( async ( ) = > {
2020-03-20 20:28:31 +00:00
await closeWindow ( w ) ;
2020-02-03 22:01:10 +00:00
w = new BrowserWindow ( {
show : false ,
width : 400 ,
height : 400 ,
webPreferences : {
contextIsolation : contextIsolationEnabled ,
sandbox : sandboxEnabled
}
2020-03-20 20:28:31 +00:00
} ) ;
} ) ;
2020-02-03 22:01:10 +00:00
afterEach ( ( ) = > {
2020-03-20 20:28:31 +00:00
removeAllExtensions ( ) ;
return closeWindow ( w ) . then ( ( ) = > { w = null as unknown as BrowserWindow ; } ) ;
} ) ;
2020-02-03 22:01:10 +00:00
it ( 'should run content script at document_start' , async ( ) = > {
2020-03-20 20:28:31 +00:00
await addExtension ( 'content-script-document-start' ) ;
2020-02-03 22:01:10 +00:00
w . webContents . once ( 'dom-ready' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const result = await w . webContents . executeJavaScript ( 'document.documentElement.style.backgroundColor' ) ;
expect ( result ) . to . equal ( 'red' ) ;
} ) ;
w . loadURL ( url ) ;
} ) ;
2020-02-03 22:01:10 +00:00
it ( 'should run content script at document_idle' , async ( ) = > {
2020-03-20 20:28:31 +00:00
await addExtension ( 'content-script-document-idle' ) ;
w . loadURL ( url ) ;
const result = await w . webContents . executeJavaScript ( 'document.body.style.backgroundColor' ) ;
expect ( result ) . to . equal ( 'red' ) ;
} ) ;
2020-02-03 22:01:10 +00:00
it ( 'should run content script at document_end' , async ( ) = > {
2020-03-20 20:28:31 +00:00
await addExtension ( 'content-script-document-end' ) ;
2020-02-03 22:01:10 +00:00
w . webContents . once ( 'did-finish-load' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const result = await w . webContents . executeJavaScript ( 'document.documentElement.style.backgroundColor' ) ;
expect ( result ) . to . equal ( 'red' ) ;
} ) ;
w . loadURL ( url ) ;
} ) ;
} ) ;
2020-02-03 22:01:10 +00:00
// TODO(nornagon): real extensions don't load on file: urls, so this
// test needs to be updated to serve its content over http.
describe . skip ( 'supports "all_frames" option' , ( ) = > {
2020-03-20 20:28:31 +00:00
const contentScript = path . resolve ( fixtures , 'extensions/content-script' ) ;
2020-02-03 22:01:10 +00:00
// Computed style values
2020-03-20 20:28:31 +00:00
const COLOR_RED = 'rgb(255, 0, 0)' ;
const COLOR_BLUE = 'rgb(0, 0, 255)' ;
const COLOR_TRANSPARENT = 'rgba(0, 0, 0, 0)' ;
2020-02-03 22:01:10 +00:00
before ( ( ) = > {
2020-11-30 22:40:56 +00:00
session . defaultSession . loadExtension ( contentScript ) ;
2020-03-20 20:28:31 +00:00
} ) ;
2020-02-03 22:01:10 +00:00
after ( ( ) = > {
2020-11-30 22:40:56 +00:00
session . defaultSession . removeExtension ( 'content-script-test' ) ;
2020-03-20 20:28:31 +00:00
} ) ;
2020-02-03 22:01:10 +00:00
beforeEach ( ( ) = > {
w = new BrowserWindow ( {
show : false ,
webPreferences : {
// enable content script injection in subframes
nodeIntegrationInSubFrames : true ,
preload : path.join ( contentScript , 'all_frames-preload.js' )
}
2020-03-20 20:28:31 +00:00
} ) ;
} ) ;
2020-02-03 22:01:10 +00:00
afterEach ( ( ) = >
closeWindow ( w ) . then ( ( ) = > {
2020-03-20 20:28:31 +00:00
w = null as unknown as BrowserWindow ;
2020-02-03 22:01:10 +00:00
} )
2020-03-20 20:28:31 +00:00
) ;
2020-02-03 22:01:10 +00:00
it ( 'applies matching rules in subframes' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const detailsPromise = emittedNTimes ( w . webContents , 'did-frame-finish-load' , 2 ) ;
w . loadFile ( path . join ( contentScript , 'frame-with-frame.html' ) ) ;
const frameEvents = await detailsPromise ;
2020-02-03 22:01:10 +00:00
await Promise . all (
frameEvents . map ( async frameEvent = > {
2020-03-20 20:28:31 +00:00
const [ , isMainFrame , , frameRoutingId ] = frameEvent ;
2020-02-03 22:01:10 +00:00
const result : any = await executeJavaScriptInFrame (
w . webContents ,
frameRoutingId ,
` (() => {
const a = document . getElementById ( 'all_frames_enabled' )
const b = document . getElementById ( 'all_frames_disabled' )
return {
enabledColor : getComputedStyle ( a ) . backgroundColor ,
disabledColor : getComputedStyle ( b ) . backgroundColor
}
} ) ( ) `
2020-03-20 20:28:31 +00:00
) ;
expect ( result . enabledColor ) . to . equal ( COLOR_RED ) ;
2020-02-03 22:01:10 +00:00
if ( isMainFrame ) {
2020-03-20 20:28:31 +00:00
expect ( result . disabledColor ) . to . equal ( COLOR_BLUE ) ;
2020-02-03 22:01:10 +00:00
} else {
2020-03-20 20:28:31 +00:00
expect ( result . disabledColor ) . to . equal ( COLOR_TRANSPARENT ) ; // null color
2020-02-03 22:01:10 +00:00
}
} )
2020-03-20 20:28:31 +00:00
) ;
} ) ;
} ) ;
} ) ;
} ;
generateTests ( false , false ) ;
generateTests ( false , true ) ;
generateTests ( true , false ) ;
generateTests ( true , true ) ;
} ) ;
2020-02-06 21:42:34 +00:00
describe ( 'extension ui pages' , ( ) = > {
afterEach ( ( ) = > {
session . defaultSession . getAllExtensions ( ) . forEach ( e = > {
2020-03-20 20:28:31 +00:00
session . defaultSession . removeExtension ( e . id ) ;
} ) ;
} ) ;
2020-02-06 21:42:34 +00:00
it ( 'loads a ui page of an extension' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const { id } = await session . defaultSession . loadExtension ( path . join ( fixtures , 'extensions' , 'ui-page' ) ) ;
const w = new BrowserWindow ( { show : false } ) ;
2020-09-15 16:29:32 +00:00
await w . loadURL ( ` chrome-extension:// ${ id } /bare-page.html ` ) ;
2020-03-20 20:28:31 +00:00
const textContent = await w . webContents . executeJavaScript ( 'document.body.textContent' ) ;
expect ( textContent ) . to . equal ( 'ui page loaded ok\n' ) ;
} ) ;
2020-02-06 21:42:34 +00:00
it ( 'can load resources' , async ( ) = > {
2020-03-20 20:28:31 +00:00
const { id } = await session . defaultSession . loadExtension ( path . join ( fixtures , 'extensions' , 'ui-page' ) ) ;
const w = new BrowserWindow ( { show : false } ) ;
2020-09-15 16:29:32 +00:00
await w . loadURL ( ` chrome-extension:// ${ id } /page-script-load.html ` ) ;
2020-03-20 20:28:31 +00:00
const textContent = await w . webContents . executeJavaScript ( 'document.body.textContent' ) ;
expect ( textContent ) . to . equal ( 'script loaded ok\n' ) ;
} ) ;
} ) ;
2021-02-11 15:58:03 +00:00
describe ( 'manifest v3' , ( ) = > {
it ( 'registers background service worker' , async ( ) = > {
const customSession = session . fromPartition ( ` persist: ${ uuid . v4 ( ) } ` ) ;
const registrationPromise = new Promise < string > ( resolve = > {
customSession . serviceWorkers . once ( 'registration-completed' , ( event , { scope } ) = > resolve ( scope ) ) ;
} ) ;
const extension = await customSession . loadExtension ( path . join ( fixtures , 'extensions' , 'mv3-service-worker' ) ) ;
const scope = await registrationPromise ;
expect ( scope ) . equals ( extension . url ) ;
} ) ;
} ) ;
2020-03-20 20:28:31 +00:00
} ) ;