123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513 |
- import * as chai from 'chai'
- import * as chaiAsPromised from 'chai-as-promised'
- import { BrowserWindow, webContents, WebContents, session, ipcMain } from 'electron'
- import { emittedOnce } from './events-helpers'
- import { closeAllWindows } from './window-helpers'
- import * as https from 'https'
- import * as http from 'http'
- import * as path from 'path'
- import * as fs from 'fs'
- import { EventEmitter } from 'events'
- import { promisify } from 'util'
- const { expect } = chai
- chai.use(chaiAsPromised)
- const fixturesPath = path.resolve(__dirname, '..', 'spec', 'fixtures')
- describe('reporting api', () => {
- it('sends a report for a deprecation', async () => {
- const reports = new EventEmitter
- // The Reporting API only works on https with valid certs. To dodge having
- // to set up a trusted certificate, hack the validator.
- session.defaultSession.setCertificateVerifyProc((req, cb) => {
- cb(0)
- })
- const certPath = path.join(fixturesPath, 'certificates')
- const options = {
- key: fs.readFileSync(path.join(certPath, 'server.key')),
- cert: fs.readFileSync(path.join(certPath, 'server.pem')),
- ca: [
- fs.readFileSync(path.join(certPath, 'rootCA.pem')),
- fs.readFileSync(path.join(certPath, 'intermediateCA.pem'))
- ],
- requestCert: true,
- rejectUnauthorized: false
- }
- const server = https.createServer(options, (req, res) => {
- if (req.url === '/report') {
- let data = ''
- req.on('data', (d) => data += d.toString('utf-8'))
- req.on('end', () => {
- reports.emit('report', JSON.parse(data))
- })
- }
- res.setHeader('Report-To', JSON.stringify({
- group: 'default',
- max_age: 120,
- endpoints: [ {url: `https://localhost:${(server.address() as any).port}/report`} ],
- }))
- res.setHeader('Content-Type', 'text/html')
- // using the deprecated `webkitRequestAnimationFrame` will trigger a
- // "deprecation" report.
- res.end('<script>webkitRequestAnimationFrame(() => {})</script>')
- })
- await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
- const bw = new BrowserWindow({
- show: false,
- })
- try {
- const reportGenerated = emittedOnce(reports, 'report')
- const url = `https://localhost:${(server.address() as any).port}/a`
- await bw.loadURL(url)
- const [report] = await reportGenerated
- expect(report).to.be.an('array')
- expect(report[0].type).to.equal('deprecation')
- expect(report[0].url).to.equal(url)
- expect(report[0].body.id).to.equal('PrefixedRequestAnimationFrame')
- } finally {
- bw.destroy()
- server.close()
- }
- })
- describe('window.open', () => {
- it('denies custom open when nativeWindowOpen: true', async () => {
- const w = new BrowserWindow({
- show: false,
- webPreferences: {
- contextIsolation: false,
- nodeIntegration: true,
- nativeWindowOpen: true
- }
- });
- w.loadURL('about:blank');
- const previousListeners = process.listeners('uncaughtException');
- process.removeAllListeners('uncaughtException');
- try {
- const uncaughtException = new Promise<Error>(resolve => {
- process.once('uncaughtException', resolve);
- });
- expect(await w.webContents.executeJavaScript(`(${function () {
- const ipc = process.electronBinding('ipc').ipc;
- return ipc.sendSync(true, 'ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', ['', '', ''])[0];
- }})()`)).to.be.null('null');
- const exception = await uncaughtException;
- expect(exception.message).to.match(/denied: expected native window\.open/);
- } finally {
- previousListeners.forEach(l => process.on('uncaughtException', l));
- }
- });
- });
- })
- describe('window.postMessage', () => {
- afterEach(async () => {
- await closeAllWindows()
- })
- it('sets the source and origin correctly', async () => {
- const w = new BrowserWindow({show: false, webPreferences: {nodeIntegration: true}})
- w.loadURL(`file://${fixturesPath}/pages/window-open-postMessage-driver.html`)
- const [, message] = await emittedOnce(ipcMain, 'complete')
- expect(message.data).to.equal('testing')
- expect(message.origin).to.equal('file://')
- expect(message.sourceEqualsOpener).to.equal(true)
- expect(message.eventOrigin).to.equal('file://')
- })
- })
- describe('focus handling', () => {
- let webviewContents: WebContents = null as unknown as WebContents
- let w: BrowserWindow = null as unknown as BrowserWindow
- beforeEach(async () => {
- w = new BrowserWindow({
- show: true,
- webPreferences: {
- nodeIntegration: true,
- webviewTag: true
- }
- })
- const webviewReady = emittedOnce(w.webContents, 'did-attach-webview')
- await w.loadFile(path.join(fixturesPath, 'pages', 'tab-focus-loop-elements.html'))
- const [, wvContents] = await webviewReady
- webviewContents = wvContents
- await emittedOnce(webviewContents, 'did-finish-load')
- w.focus()
- })
- afterEach(() => {
- webviewContents = null as unknown as WebContents
- w.destroy()
- w = null as unknown as BrowserWindow
- })
- const expectFocusChange = async () => {
- const [, focusedElementId] = await emittedOnce(ipcMain, 'focus-changed')
- return focusedElementId
- }
- describe('a TAB press', () => {
- const tabPressEvent: any = {
- type: 'keyDown',
- keyCode: 'Tab'
- }
- it('moves focus to the next focusable item', async () => {
- let focusChange = expectFocusChange()
- w.webContents.sendInputEvent(tabPressEvent)
- let focusedElementId = await focusChange
- expect(focusedElementId).to.equal('BUTTON-element-1', `should start focused in element-1, it's instead in ${focusedElementId}`)
- focusChange = expectFocusChange()
- w.webContents.sendInputEvent(tabPressEvent)
- focusedElementId = await focusChange
- expect(focusedElementId).to.equal('BUTTON-element-2', `focus should've moved to element-2, it's instead in ${focusedElementId}`)
- focusChange = expectFocusChange()
- w.webContents.sendInputEvent(tabPressEvent)
- focusedElementId = await focusChange
- expect(focusedElementId).to.equal('BUTTON-wv-element-1', `focus should've moved to the webview's element-1, it's instead in ${focusedElementId}`)
- focusChange = expectFocusChange()
- webviewContents.sendInputEvent(tabPressEvent)
- focusedElementId = await focusChange
- expect(focusedElementId).to.equal('BUTTON-wv-element-2', `focus should've moved to the webview's element-2, it's instead in ${focusedElementId}`)
- focusChange = expectFocusChange()
- webviewContents.sendInputEvent(tabPressEvent)
- focusedElementId = await focusChange
- expect(focusedElementId).to.equal('BUTTON-element-3', `focus should've moved to element-3, it's instead in ${focusedElementId}`)
- focusChange = expectFocusChange()
- w.webContents.sendInputEvent(tabPressEvent)
- focusedElementId = await focusChange
- expect(focusedElementId).to.equal('BUTTON-element-1', `focus should've looped back to element-1, it's instead in ${focusedElementId}`)
- })
- })
- describe('a SHIFT + TAB press', () => {
- const shiftTabPressEvent: any = {
- type: 'keyDown',
- modifiers: ['Shift'],
- keyCode: 'Tab'
- }
- it('moves focus to the previous focusable item', async () => {
- let focusChange = expectFocusChange()
- w.webContents.sendInputEvent(shiftTabPressEvent)
- let focusedElementId = await focusChange
- expect(focusedElementId).to.equal('BUTTON-element-3', `should start focused in element-3, it's instead in ${focusedElementId}`)
- focusChange = expectFocusChange()
- w.webContents.sendInputEvent(shiftTabPressEvent)
- focusedElementId = await focusChange
- expect(focusedElementId).to.equal('BUTTON-wv-element-2', `focus should've moved to the webview's element-2, it's instead in ${focusedElementId}`)
- focusChange = expectFocusChange()
- webviewContents.sendInputEvent(shiftTabPressEvent)
- focusedElementId = await focusChange
- expect(focusedElementId).to.equal('BUTTON-wv-element-1', `focus should've moved to the webview's element-1, it's instead in ${focusedElementId}`)
- focusChange = expectFocusChange()
- webviewContents.sendInputEvent(shiftTabPressEvent)
- focusedElementId = await focusChange
- expect(focusedElementId).to.equal('BUTTON-element-2', `focus should've moved to element-2, it's instead in ${focusedElementId}`)
- focusChange = expectFocusChange()
- w.webContents.sendInputEvent(shiftTabPressEvent)
- focusedElementId = await focusChange
- expect(focusedElementId).to.equal('BUTTON-element-1', `focus should've moved to element-1, it's instead in ${focusedElementId}`)
- focusChange = expectFocusChange()
- w.webContents.sendInputEvent(shiftTabPressEvent)
- focusedElementId = await focusChange
- expect(focusedElementId).to.equal('BUTTON-element-3', `focus should've looped back to element-3, it's instead in ${focusedElementId}`)
- })
- })
- })
- describe('web security', () => {
- afterEach(closeAllWindows)
- let server: http.Server
- let serverUrl: string
- before(async () => {
- server = http.createServer((req, res) => {
- res.setHeader('Content-Type', 'text/html')
- res.end('<body>')
- })
- await new Promise(resolve => server.listen(0, '127.0.0.1', resolve))
- serverUrl = `http://localhost:${(server.address() as any).port}`
- })
- after(() => {
- server.close()
- })
- it('engages CORB when web security is not disabled', async () => {
- const w = new BrowserWindow({ show: true, webPreferences: { webSecurity: true, nodeIntegration: true } })
- const p = emittedOnce(ipcMain, 'success')
- await w.loadURL(`data:text/html,<script>
- const s = document.createElement('script')
- s.src = "${serverUrl}"
- // The script will load successfully but its body will be emptied out
- // by CORB, so we don't expect a syntax error.
- s.onload = () => { require('electron').ipcRenderer.send('success') }
- document.documentElement.appendChild(s)
- </script>`)
- await p
- })
- it('bypasses CORB when web security is disabled', async () => {
- const w = new BrowserWindow({ show: true, webPreferences: { webSecurity: false, nodeIntegration: true } })
- const p = emittedOnce(ipcMain, 'success')
- await w.loadURL(`data:text/html,
- <script>
- window.onerror = (e) => { require('electron').ipcRenderer.send('success', e) }
- </script>
- <script src="${serverUrl}"></script>`)
- await p
- })
- })
- describe('iframe using HTML fullscreen API while window is OS-fullscreened', () => {
- const fullscreenChildHtml = promisify(fs.readFile)(
- path.join(fixturesPath, 'pages', 'fullscreen-oopif.html')
- )
- let w: BrowserWindow, server: http.Server
- before(() => {
- server = http.createServer(async (_req, res) => {
- res.writeHead(200, { 'Content-Type': 'text/html' })
- res.write(await fullscreenChildHtml)
- res.end()
- })
- server.listen(8989, '127.0.0.1')
- })
- beforeEach(() => {
- w = new BrowserWindow({
- show: true,
- fullscreen: true,
- webPreferences: {
- nodeIntegration: true,
- nodeIntegrationInSubFrames: true
- }
- })
- })
- afterEach(async () => {
- await closeAllWindows()
- ;(w as any) = null
- server.close()
- })
- it('can fullscreen from out-of-process iframes (OOPIFs)', done => {
- ipcMain.once('fullscreenChange', async () => {
- const fullscreenWidth = await w.webContents.executeJavaScript(
- "document.querySelector('iframe').offsetWidth"
- )
- expect(fullscreenWidth > 0).to.be.true
- await w.webContents.executeJavaScript(
- "document.querySelector('iframe').contentWindow.postMessage('exitFullscreen', '*')"
- )
- await new Promise(resolve => setTimeout(resolve, 500))
- const width = await w.webContents.executeJavaScript(
- "document.querySelector('iframe').offsetWidth"
- )
- expect(width).to.equal(0)
- done()
- })
- const html =
- '<iframe style="width: 0" frameborder=0 src="http://localhost:8989" allowfullscreen></iframe>'
- w.loadURL(`data:text/html,${html}`)
- })
- it('can fullscreen from in-process iframes', done => {
- ipcMain.once('fullscreenChange', async () => {
- const fullscreenWidth = await w.webContents.executeJavaScript(
- "document.querySelector('iframe').offsetWidth"
- )
- expect(fullscreenWidth > 0).to.true
- await w.webContents.executeJavaScript('document.exitFullscreen()')
- const width = await w.webContents.executeJavaScript(
- "document.querySelector('iframe').offsetWidth"
- )
- expect(width).to.equal(0)
- done()
- })
- w.loadFile(path.join(fixturesPath, 'pages', 'fullscreen-ipif.html'))
- })
- })
- describe('enableWebSQL webpreference', () => {
- const standardScheme = (global as any).standardScheme;
- const origin = `${standardScheme}://fake-host`;
- const filePath = path.join(fixturesPath, 'pages', 'storage', 'web_sql.html');
- const sqlPartition = 'web-sql-preference-test';
- const sqlSession = session.fromPartition(sqlPartition);
- const securityError = 'An attempt was made to break through the security policy of the user agent.';
- let contents: WebContents, w: BrowserWindow;
- before(() => {
- sqlSession.protocol.registerFileProtocol(standardScheme, (request, callback) => {
- callback({ path: filePath });
- });
- });
- after(() => {
- sqlSession.protocol.unregisterProtocol(standardScheme);
- });
- afterEach(async () => {
- if (contents) {
- (contents as any).destroy();
- contents = null as any;
- }
- await closeAllWindows();
- (w as any) = null;
- });
- it('default value allows websql', async () => {
- contents = (webContents as any).create({
- session: sqlSession,
- nodeIntegration: true
- });
- contents.loadURL(origin);
- const [, error] = await emittedOnce(ipcMain, 'web-sql-response');
- expect(error).to.be.null('error');
- });
- it('when set to false can disallow websql', async () => {
- contents = (webContents as any).create({
- session: sqlSession,
- nodeIntegration: true,
- enableWebSQL: false
- });
- contents.loadURL(origin);
- const [, error] = await emittedOnce(ipcMain, 'web-sql-response');
- expect(error).to.equal(securityError);
- });
- it('when set to false does not disable indexedDB', async () => {
- contents = (webContents as any).create({
- session: sqlSession,
- nodeIntegration: true,
- enableWebSQL: false
- });
- contents.loadURL(origin);
- const [, error] = await emittedOnce(ipcMain, 'web-sql-response');
- expect(error).to.equal(securityError);
- const dbName = 'random';
- const result = await contents.executeJavaScript(`
- new Promise((resolve, reject) => {
- try {
- let req = window.indexedDB.open('${dbName}');
- req.onsuccess = (event) => {
- let db = req.result;
- resolve(db.name);
- }
- req.onerror = (event) => { resolve(event.target.code); }
- } catch (e) {
- resolve(e.message);
- }
- });
- `);
- expect(result).to.equal(dbName);
- });
- it('child webContents can override when the embedder has allowed websql', async () => {
- w = new BrowserWindow({
- show: false,
- webPreferences: {
- nodeIntegration: true,
- webviewTag: true,
- session: sqlSession
- }
- });
- w.webContents.loadURL(origin);
- const [, error] = await emittedOnce(ipcMain, 'web-sql-response');
- expect(error).to.be.null('error');
- const webviewResult = emittedOnce(ipcMain, 'web-sql-response');
- await w.webContents.executeJavaScript(`
- new Promise((resolve, reject) => {
- const webview = new WebView();
- webview.setAttribute('src', '${origin}');
- webview.setAttribute('webpreferences', 'enableWebSQL=0');
- webview.setAttribute('partition', '${sqlPartition}');
- webview.setAttribute('nodeIntegration', 'on');
- document.body.appendChild(webview);
- webview.addEventListener('dom-ready', () => resolve());
- });
- `);
- const [, childError] = await webviewResult;
- expect(childError).to.equal(securityError);
- });
- it('child webContents cannot override when the embedder has disallowed websql', async () => {
- w = new BrowserWindow({
- show: false,
- webPreferences: {
- nodeIntegration: true,
- enableWebSQL: false,
- webviewTag: true,
- session: sqlSession
- }
- });
- w.webContents.loadURL('data:text/html,<html></html>');
- const webviewResult = emittedOnce(ipcMain, 'web-sql-response');
- await w.webContents.executeJavaScript(`
- new Promise((resolve, reject) => {
- const webview = new WebView();
- webview.setAttribute('src', '${origin}');
- webview.setAttribute('webpreferences', 'enableWebSQL=1');
- webview.setAttribute('partition', '${sqlPartition}');
- webview.setAttribute('nodeIntegration', 'on');
- document.body.appendChild(webview);
- webview.addEventListener('dom-ready', () => resolve());
- });
- `);
- const [, childError] = await webviewResult;
- expect(childError).to.equal(securityError);
- });
- it('child webContents can use websql when the embedder has allowed websql', async () => {
- w = new BrowserWindow({
- show: false,
- webPreferences: {
- nodeIntegration: true,
- webviewTag: true,
- session: sqlSession
- }
- });
- w.webContents.loadURL(origin);
- const [, error] = await emittedOnce(ipcMain, 'web-sql-response');
- expect(error).to.be.null('error');
- const webviewResult = emittedOnce(ipcMain, 'web-sql-response');
- await w.webContents.executeJavaScript(`
- new Promise((resolve, reject) => {
- const webview = new WebView();
- webview.setAttribute('src', '${origin}');
- webview.setAttribute('webpreferences', 'enableWebSQL=1');
- webview.setAttribute('partition', '${sqlPartition}');
- webview.setAttribute('nodeIntegration', 'on');
- document.body.appendChild(webview);
- webview.addEventListener('dom-ready', () => resolve());
- });
- `);
- const [, childError] = await webviewResult;
- expect(childError).to.be.null('error');
- });
- });
|