electron/spec/chromium-spec.js

1561 lines
52 KiB
JavaScript
Raw Normal View History

2016-03-25 20:03:49 +00:00
const assert = require('assert')
const chai = require('chai')
const dirtyChai = require('dirty-chai')
const fs = require('fs')
2016-03-25 20:03:49 +00:00
const http = require('http')
const path = require('path')
const ws = require('ws')
const url = require('url')
2018-02-08 18:50:19 +00:00
const ChildProcess = require('child_process')
2018-09-13 16:10:51 +00:00
const { ipcRenderer, remote } = require('electron')
const { emittedOnce } = require('./events-helpers')
const { closeWindow, waitForWebContentsToLoad } = require('./window-helpers')
const { resolveGetters } = require('./assert-helpers')
2018-09-13 16:10:51 +00:00
const { app, BrowserWindow, ipcMain, protocol, session, webContents } = remote
2016-03-25 20:03:49 +00:00
const isCI = remote.getGlobal('isCi')
const features = process.electronBinding('features')
const { expect } = chai
chai.use(dirtyChai)
2017-11-23 22:22:43 +00:00
/* Most of the APIs here don't use standard callbacks */
/* eslint-disable standard/no-callback-literal */
2017-11-13 20:13:19 +00:00
describe('chromium feature', () => {
const fixtures = path.resolve(__dirname, 'fixtures')
let listener = null
2017-04-18 23:04:27 +00:00
let w = null
2017-11-13 20:13:19 +00:00
afterEach(() => {
2016-01-12 02:40:23 +00:00
if (listener != null) {
2016-03-25 20:03:49 +00:00
window.removeEventListener('message', listener)
2016-01-12 02:40:23 +00:00
}
2016-03-25 20:03:49 +00:00
listener = null
})
2018-02-08 18:50:44 +00:00
describe('command line switches', () => {
2018-02-08 18:00:41 +00:00
describe('--lang switch', () => {
const currentLocale = app.getLocale()
2018-02-08 18:00:41 +00:00
const testLocale = (locale, result, done) => {
const appPath = path.join(__dirname, 'fixtures', 'api', 'locale-check')
const electronPath = remote.getGlobal('process').execPath
let output = ''
const appProcess = ChildProcess.spawn(electronPath, [appPath, `--lang=${locale}`])
2018-02-08 18:00:41 +00:00
appProcess.stdout.on('data', (data) => { output += data })
appProcess.stdout.on('end', () => {
output = output.replace(/(\r\n|\n|\r)/gm, '')
2018-09-13 16:10:51 +00:00
assert.strictEqual(output, result)
2018-02-08 18:00:41 +00:00
done()
})
}
it('should set the locale', (done) => testLocale('fr', 'fr', done))
it('should not set an invalid locale', (done) => testLocale('asdfkl', currentLocale, done))
2018-02-08 18:00:41 +00:00
})
describe('--remote-debugging-port switch', () => {
it('should display the discovery page', (done) => {
const electronPath = remote.getGlobal('process').execPath
let output = ''
const appProcess = ChildProcess.spawn(electronPath, [`--remote-debugging-port=`])
appProcess.stderr.on('data', (data) => {
output += data
const m = /DevTools listening on ws:\/\/127.0.0.1:(\d+)\//.exec(output)
if (m) {
appProcess.stderr.removeAllListeners('data')
const port = m[1]
http.get(`http://127.0.0.1:${port}`, (res) => {
res.destroy()
appProcess.kill()
expect(res.statusCode).to.eql(200)
expect(parseInt(res.headers['content-length'])).to.be.greaterThan(0)
done()
})
}
})
})
})
2018-02-08 18:00:41 +00:00
})
2017-11-13 20:13:19 +00:00
afterEach(() => closeWindow(w).then(() => { w = null }))
2017-04-18 23:04:27 +00:00
2017-11-13 20:13:19 +00:00
describe('heap snapshot', () => {
it('does not crash', function () {
process.electronBinding('v8_util').takeHeapSnapshot()
2016-03-25 20:03:49 +00:00
})
})
2017-11-13 20:13:19 +00:00
describe('sending request of http protocol urls', () => {
it('does not crash', (done) => {
const server = http.createServer((req, res) => {
2016-03-25 20:03:49 +00:00
res.end()
server.close()
done()
})
2017-11-13 20:13:19 +00:00
server.listen(0, '127.0.0.1', () => {
const port = server.address().port
$.get(`http://127.0.0.1:${port}`)
2016-03-25 20:03:49 +00:00
})
})
})
describe('accessing key names also used as Node.js module names', () => {
it('does not crash', (done) => {
2018-09-13 16:10:51 +00:00
w = new BrowserWindow({ show: false })
w.webContents.once('did-finish-load', () => { done() })
w.webContents.once('crashed', () => done(new Error('WebContents crashed.')))
w.loadFile(path.join(fixtures, 'pages', 'external-string.html'))
})
})
describe('loading jquery', () => {
it('does not crash', (done) => {
w = new BrowserWindow({ show: false })
w.webContents.once('did-finish-load', () => { done() })
w.webContents.once('crashed', () => done(new Error('WebContents crashed.')))
w.loadFile(path.join(fixtures, 'pages', 'jquery.html'))
})
})
2017-11-13 20:13:19 +00:00
describe('navigator.webkitGetUserMedia', () => {
it('calls its callbacks', (done) => {
2016-02-17 01:39:11 +00:00
navigator.webkitGetUserMedia({
2016-01-12 02:40:23 +00:00
audio: true,
video: false
2017-11-13 20:13:19 +00:00
}, () => done(),
2018-09-13 16:10:51 +00:00
() => done())
2016-03-25 20:03:49 +00:00
})
})
2017-11-13 20:13:19 +00:00
describe('navigator.mediaDevices', () => {
if (isCI) return
afterEach(() => {
remote.getGlobal('permissionChecks').allow()
})
2017-11-13 20:13:19 +00:00
it('can return labels of enumerated devices', (done) => {
navigator.mediaDevices.enumerateDevices().then((devices) => {
2016-03-25 20:03:49 +00:00
const labels = devices.map((device) => device.label)
const labelFound = labels.some((label) => !!label)
2016-03-28 23:19:18 +00:00
if (labelFound) {
2016-03-25 20:03:49 +00:00
done()
2016-03-28 23:19:18 +00:00
} else {
2017-04-18 23:06:10 +00:00
done(new Error(`No device labels found: ${JSON.stringify(labels)}`))
2016-03-28 23:19:18 +00:00
}
2016-03-25 20:03:49 +00:00
}).catch(done)
})
2016-12-03 18:52:57 +00:00
it('does not return labels of enumerated devices when permission denied', (done) => {
remote.getGlobal('permissionChecks').reject()
navigator.mediaDevices.enumerateDevices().then((devices) => {
const labels = devices.map((device) => device.label)
const labelFound = labels.some((label) => !!label)
if (labelFound) {
done(new Error(`Device labels were found: ${JSON.stringify(labels)}`))
} else {
done()
}
}).catch(done)
})
2017-11-13 20:13:19 +00:00
it('can return new device id when cookie storage is cleared', (done) => {
2016-12-03 18:52:57 +00:00
const options = {
origin: null,
storages: ['cookies']
}
2016-12-12 21:33:14 +00:00
const deviceIds = []
2016-12-03 18:52:57 +00:00
const ses = session.fromPartition('persist:media-device-id')
2017-04-18 23:04:27 +00:00
w = new BrowserWindow({
2016-12-03 18:52:57 +00:00
show: false,
webPreferences: {
nodeIntegration: true,
2016-12-03 18:52:57 +00:00
session: ses
}
})
w.webContents.on('ipc-message', (event, channel, deviceId) => {
if (channel === 'deviceIds') deviceIds.push(deviceId)
2016-12-03 18:52:57 +00:00
if (deviceIds.length === 2) {
2018-09-13 16:10:51 +00:00
assert.notDeepStrictEqual(deviceIds[0], deviceIds[1])
2017-11-13 20:13:19 +00:00
closeWindow(w).then(() => {
2016-12-03 18:52:57 +00:00
w = null
done()
2017-11-13 20:13:19 +00:00
}).catch((error) => done(error))
2016-12-03 18:52:57 +00:00
} else {
ses.clearStorageData(options).then(() => {
2016-12-03 18:52:57 +00:00
w.webContents.reload()
})
}
})
w.loadFile(path.join(fixtures, 'pages', 'media-id-reset.html'))
2016-12-03 18:52:57 +00:00
})
2016-03-25 20:03:49 +00:00
})
2017-11-13 20:13:19 +00:00
describe('navigator.language', () => {
it('should not be empty', () => {
2018-09-13 16:10:51 +00:00
assert.notStrictEqual(navigator.language, '')
2016-03-25 20:03:49 +00:00
})
})
describe('navigator.languages', (done) => {
it('should return the system locale only', () => {
const appLocale = app.getLocale()
2018-09-13 16:10:51 +00:00
assert.strictEqual(navigator.languages.length, 1)
assert.strictEqual(navigator.languages[0], appLocale)
})
})
2017-11-13 20:13:19 +00:00
describe('navigator.serviceWorker', () => {
it('should register for file scheme', (done) => {
w = new BrowserWindow({
show: false,
webPreferences: {
nodeIntegration: true,
partition: 'sw-file-scheme-spec'
}
})
w.webContents.on('ipc-message', (event, channel, message) => {
if (channel === 'reload') {
2016-03-25 20:03:49 +00:00
w.webContents.reload()
} else if (channel === 'error') {
done(message)
} else if (channel === 'response') {
assert.strictEqual(message, 'Hello from serviceWorker!')
session.fromPartition('sw-file-scheme-spec').clearStorageData({
2016-01-12 02:40:23 +00:00
storages: ['serviceworkers']
}).then(() => done())
2016-01-12 02:40:23 +00:00
}
2016-03-25 20:03:49 +00:00
})
w.webContents.on('crashed', () => done(new Error('WebContents crashed.')))
w.loadFile(path.join(fixtures, 'pages', 'service-worker', 'index.html'))
})
2017-11-13 20:13:19 +00:00
it('should register for intercepted file scheme', (done) => {
const customSession = session.fromPartition('intercept-file')
2017-11-13 20:13:19 +00:00
customSession.protocol.interceptBufferProtocol('file', (request, callback) => {
let file = url.parse(request.url).pathname
2017-11-13 20:13:19 +00:00
if (file[0] === '/' && process.platform === 'win32') file = file.slice(1)
const content = fs.readFileSync(path.normalize(file))
const ext = path.extname(file)
let type = 'text/html'
2017-11-13 20:13:19 +00:00
if (ext === '.js') type = 'application/javascript'
2018-09-13 16:10:51 +00:00
callback({ data: content, mimeType: type })
2017-11-13 20:13:19 +00:00
}, (error) => {
if (error) done(error)
})
w = new BrowserWindow({
show: false,
webPreferences: {
nodeIntegration: true,
session: customSession
}
})
w.webContents.on('ipc-message', (event, channel, message) => {
if (channel === 'reload') {
w.webContents.reload()
} else if (channel === 'error') {
done(`unexpected error : ${message}`)
} else if (channel === 'response') {
assert.strictEqual(message, 'Hello from serviceWorker!')
customSession.clearStorageData({
storages: ['serviceworkers']
}).then(() => {
customSession.protocol.uninterceptProtocol('file', error => done(error))
})
}
})
w.webContents.on('crashed', () => done(new Error('WebContents crashed.')))
w.loadFile(path.join(fixtures, 'pages', 'service-worker', 'index.html'))
2016-03-25 20:03:49 +00:00
})
})
describe('navigator.geolocation', () => {
before(function () {
if (!features.isFakeLocationProviderEnabled()) {
return this.skip()
}
})
it('returns position when permission is granted', (done) => {
navigator.geolocation.getCurrentPosition((position) => {
assert(position.timestamp)
done()
}, (error) => {
done(error)
})
})
it('returns error when permission is denied', (done) => {
w = new BrowserWindow({
show: false,
webPreferences: {
nodeIntegration: true,
partition: 'geolocation-spec'
}
})
w.webContents.on('ipc-message', (event, channel) => {
if (channel === 'success') {
done()
} else {
done('unexpected response from geolocation api')
}
})
w.webContents.session.setPermissionRequestHandler((wc, permission, callback) => {
if (permission === 'geolocation') {
callback(false)
} else {
callback(true)
}
})
w.loadFile(path.join(fixtures, 'pages', 'geolocation', 'index.html'))
})
})
2017-11-13 20:13:19 +00:00
describe('window.open', () => {
it('returns a BrowserWindowProxy object', () => {
const b = window.open('about:blank', '', 'show=no')
2018-09-13 16:10:51 +00:00
assert.strictEqual(b.closed, false)
assert.strictEqual(b.constructor.name, 'BrowserWindowProxy')
2016-03-25 20:03:49 +00:00
b.close()
})
2017-11-13 20:13:19 +00:00
it('accepts "nodeIntegration" as feature', (done) => {
let b = null
2017-11-13 20:13:19 +00:00
listener = (event) => {
2018-09-13 16:10:51 +00:00
assert.strictEqual(event.data.isProcessGlobalUndefined, true)
2016-03-25 20:03:49 +00:00
b.close()
done()
}
window.addEventListener('message', listener)
2017-11-13 20:13:19 +00:00
b = window.open(`file://${fixtures}/pages/window-opener-node.html`, '', 'nodeIntegration=no,show=no')
2016-03-25 20:03:49 +00:00
})
2017-11-13 20:13:19 +00:00
it('inherit options of parent window', (done) => {
let b = null
2017-11-13 20:13:19 +00:00
listener = (event) => {
const ref1 = remote.getCurrentWindow().getSize()
const width = ref1[0]
const height = ref1[1]
2018-09-13 16:10:51 +00:00
assert.strictEqual(event.data, `size: ${width} ${height}`)
2016-03-25 20:03:49 +00:00
b.close()
done()
}
window.addEventListener('message', listener)
2017-11-13 20:13:19 +00:00
b = window.open(`file://${fixtures}/pages/window-open-size.html`, '', 'show=no')
2016-03-25 20:03:49 +00:00
})
2018-03-13 08:09:47 +00:00
for (const show of [true, false]) {
2018-03-13 06:55:48 +00:00
it(`inherits parent visibility over parent {show=${show}} option`, (done) => {
2018-09-13 16:10:51 +00:00
const w = new BrowserWindow({ show })
2018-03-13 06:55:48 +00:00
// toggle visibility
if (show) {
w.hide()
} else {
w.show()
}
w.webContents.once('new-window', (e, url, frameName, disposition, options) => {
2018-09-13 16:10:51 +00:00
assert.strictEqual(options.show, w.isVisible())
2018-03-13 06:55:48 +00:00
w.close()
done()
})
w.loadFile(path.join(fixtures, 'pages', 'window-open.html'))
2018-03-13 06:55:48 +00:00
})
}
2017-11-13 20:13:19 +00:00
it('disables node integration when it is disabled on the parent window', (done) => {
let b = null
2017-11-13 20:13:19 +00:00
listener = (event) => {
2018-09-13 16:10:51 +00:00
assert.strictEqual(event.data.isProcessGlobalUndefined, true)
b.close()
done()
}
window.addEventListener('message', listener)
2017-11-13 20:13:19 +00:00
const windowUrl = require('url').format({
2016-03-24 00:40:25 +00:00
pathname: `${fixtures}/pages/window-opener-no-node-integration.html`,
protocol: 'file',
query: {
2016-03-24 00:40:25 +00:00
p: `${fixtures}/pages/window-opener-node.html`
},
slashes: true
})
2016-03-24 21:12:46 +00:00
b = window.open(windowUrl, '', 'nodeIntegration=no,show=no')
})
// TODO(codebytere): re-enable this test
xit('disables node integration when it is disabled on the parent window for chrome devtools URLs', (done) => {
let b = null
app.once('web-contents-created', (event, contents) => {
contents.once('did-finish-load', () => {
contents.executeJavaScript('typeof process').then((typeofProcessGlobal) => {
2018-09-13 16:10:51 +00:00
assert.strictEqual(typeofProcessGlobal, 'undefined')
b.close()
done()
}).catch(done)
})
})
b = window.open('chrome-devtools://devtools/bundled/inspector.html', '', 'nodeIntegration=no,show=no')
})
2017-11-13 20:13:19 +00:00
it('disables JavaScript when it is disabled on the parent window', (done) => {
let b = null
app.once('web-contents-created', (event, contents) => {
contents.once('did-finish-load', () => {
app.once('browser-window-created', (event, window) => {
const preferences = window.webContents.getLastWebPreferences()
2018-09-13 16:10:51 +00:00
assert.strictEqual(preferences.javascript, false)
window.destroy()
b.close()
done()
})
// Click link on page
2018-09-13 16:10:51 +00:00
contents.sendInputEvent({ type: 'mouseDown', clickCount: 1, x: 1, y: 1 })
contents.sendInputEvent({ type: 'mouseUp', clickCount: 1, x: 1, y: 1 })
})
})
2017-11-13 20:13:19 +00:00
const windowUrl = require('url').format({
pathname: `${fixtures}/pages/window-no-javascript.html`,
protocol: 'file',
slashes: true
})
b = window.open(windowUrl, '', 'javascript=no,show=no')
})
2017-11-13 20:13:19 +00:00
it('disables the <webview> tag when it is disabled on the parent window', (done) => {
let b = null
2017-11-13 20:13:19 +00:00
listener = (event) => {
2018-09-13 16:10:51 +00:00
assert.strictEqual(event.data.isWebViewGlobalUndefined, true)
b.close()
done()
}
window.addEventListener('message', listener)
2017-11-13 20:13:19 +00:00
const windowUrl = require('url').format({
pathname: `${fixtures}/pages/window-opener-no-webview-tag.html`,
protocol: 'file',
query: {
p: `${fixtures}/pages/window-opener-webview.html`
},
slashes: true
})
b = window.open(windowUrl, '', 'webviewTag=no,nodeIntegration=yes,show=no')
})
2017-11-13 20:13:19 +00:00
it('does not override child options', (done) => {
let b = null
2017-11-13 20:13:19 +00:00
const size = {
2016-01-12 02:40:23 +00:00
width: 350,
height: 450
2016-03-25 20:03:49 +00:00
}
2017-11-13 20:13:19 +00:00
listener = (event) => {
2018-09-13 16:10:51 +00:00
assert.strictEqual(event.data, `size: ${size.width} ${size.height}`)
2016-03-25 20:03:49 +00:00
b.close()
done()
}
window.addEventListener('message', listener)
2017-11-13 20:13:19 +00:00
b = window.open(`file://${fixtures}/pages/window-open-size.html`, '', 'show=no,width=' + size.width + ',height=' + size.height)
2016-03-25 20:03:49 +00:00
})
2017-01-04 22:44:14 +00:00
it('handles cycles when merging the parent options into the child options', (done) => {
w = BrowserWindow.fromId(ipcRenderer.sendSync('create-window-with-options-cycle'))
w.loadFile(path.join(fixtures, 'pages', 'window-open.html'))
2017-01-04 22:57:51 +00:00
w.webContents.once('new-window', (event, url, frameName, disposition, options) => {
2018-09-13 16:10:51 +00:00
assert.strictEqual(options.show, false)
assert.deepStrictEqual(...resolveGetters(options.foo, {
bar: undefined,
baz: {
hello: {
world: true
}
2017-01-04 22:54:18 +00:00
},
baz2: {
hello: {
world: true
}
2017-01-04 22:44:14 +00:00
}
}))
2017-01-04 22:44:14 +00:00
done()
})
})
2017-11-13 20:13:19 +00:00
it('defines a window.location getter', (done) => {
let b = null
2017-11-13 20:13:19 +00:00
let targetURL
2016-06-29 16:37:10 +00:00
if (process.platform === 'win32') {
2017-11-13 20:13:19 +00:00
targetURL = `file:///${fixtures.replace(/\\/g, '/')}/pages/base-page.html`
2016-06-29 16:37:10 +00:00
} else {
2017-11-13 20:13:19 +00:00
targetURL = `file://${fixtures}/pages/base-page.html`
2016-06-29 16:37:10 +00:00
}
app.once('browser-window-created', (event, window) => {
window.webContents.once('did-finish-load', () => {
2018-12-04 07:22:03 +00:00
assert.strictEqual(b.location.href, targetURL)
b.close()
done()
})
2016-03-25 20:03:49 +00:00
})
b = window.open(targetURL)
2016-03-25 20:03:49 +00:00
})
2017-11-13 20:13:19 +00:00
it('defines a window.location setter', (done) => {
let b = null
2018-09-13 16:10:51 +00:00
app.once('browser-window-created', (event, { webContents }) => {
2017-11-13 20:13:19 +00:00
webContents.once('did-finish-load', () => {
2017-01-12 21:19:27 +00:00
// When it loads, redirect
2017-11-13 20:13:19 +00:00
b.location = `file://${fixtures}/pages/base-page.html`
webContents.once('did-finish-load', () => {
2017-01-12 21:19:27 +00:00
// After our second redirect, cleanup and callback
b.close()
done()
})
2016-03-25 20:03:49 +00:00
})
})
2017-01-12 21:19:27 +00:00
b = window.open('about:blank')
2016-03-25 20:03:49 +00:00
})
it('open a blank page when no URL is specified', async () => {
const browserWindowCreated = emittedOnce(app, 'browser-window-created')
const w = window.open()
try {
const [, { webContents }] = await browserWindowCreated
await waitForWebContentsToLoad(webContents)
assert.strictEqual(w.location.href, 'about:blank')
} finally {
w.close()
}
})
it('open a blank page when an empty URL is specified', async () => {
const browserWindowCreated = emittedOnce(app, 'browser-window-created')
const w = window.open('')
try {
const [, { webContents }] = await browserWindowCreated
await waitForWebContentsToLoad(webContents)
assert.strictEqual(w.location.href, 'about:blank')
} finally {
w.close()
}
})
2017-11-13 20:13:19 +00:00
it('throws an exception when the arguments cannot be converted to strings', () => {
assert.throws(() => {
2018-09-13 16:10:51 +00:00
window.open('', { toString: null })
}, /Cannot convert object to primitive value/)
2017-11-13 20:13:19 +00:00
assert.throws(() => {
2018-09-13 16:10:51 +00:00
window.open('', '', { toString: 3 })
}, /Cannot convert object to primitive value/)
})
2017-11-13 20:13:19 +00:00
it('sets the window title to the specified frameName', (done) => {
let b = null
app.once('browser-window-created', (event, createdWindow) => {
2018-09-13 16:10:51 +00:00
assert.strictEqual(createdWindow.getTitle(), 'hello')
b.close()
done()
})
b = window.open('', 'hello')
})
2017-11-13 20:13:19 +00:00
it('does not throw an exception when the frameName is a built-in object property', (done) => {
let b = null
app.once('browser-window-created', (event, createdWindow) => {
2018-09-13 16:10:51 +00:00
assert.strictEqual(createdWindow.getTitle(), '__proto__')
b.close()
done()
})
b = window.open('', '__proto__')
})
2017-11-13 20:13:19 +00:00
it('does not throw an exception when the features include webPreferences', () => {
let b = null
2017-11-13 20:13:19 +00:00
assert.doesNotThrow(() => {
b = window.open('', '', 'webPreferences=')
})
b.close()
})
2016-03-25 20:03:49 +00:00
})
2017-11-13 20:13:19 +00:00
describe('window.opener', () => {
const url = `file://${fixtures}/pages/window-opener.html`
it('is null for main window', async () => {
w = new BrowserWindow({
show: false,
webPreferences: {
nodeIntegration: true
}
})
const promise = emittedOnce(w.webContents, 'ipc-message')
w.loadFile(path.join(fixtures, 'pages', 'window-opener.html'))
const [, channel, opener] = await promise
expect(channel).to.equal('opener')
expect(opener).to.equal(null)
2016-03-25 20:03:49 +00:00
})
2017-11-13 20:13:19 +00:00
it('is not null for window opened by window.open', (done) => {
let b = null
2017-11-13 20:13:19 +00:00
listener = (event) => {
2018-09-13 16:10:51 +00:00
assert.strictEqual(event.data, 'object')
2016-03-25 20:03:49 +00:00
b.close()
done()
}
window.addEventListener('message', listener)
b = window.open(url, '', 'show=no')
})
})
2017-11-13 20:13:19 +00:00
describe('window.opener access from BrowserWindow', () => {
2016-11-16 16:03:07 +00:00
const scheme = 'other'
const url = `${scheme}://${fixtures}/pages/window-opener-location.html`
let w = null
2017-11-13 20:13:19 +00:00
before((done) => {
protocol.registerFileProtocol(scheme, (request, callback) => {
callback(`${fixtures}/pages/window-opener-location.html`)
2017-11-13 20:13:19 +00:00
}, (error) => done(error))
})
2017-11-13 20:13:19 +00:00
after(() => {
protocol.unregisterProtocol(scheme)
})
2017-11-13 20:13:19 +00:00
afterEach(() => {
w.close()
})
2017-11-13 20:13:19 +00:00
it('does nothing when origin of current window does not match opener', (done) => {
listener = (event) => {
assert.strictEqual(event.data, '')
done()
}
window.addEventListener('message', listener)
w = window.open(url, '', 'show=no,nodeIntegration=no')
})
2017-11-13 20:13:19 +00:00
it('works when origin matches', (done) => {
listener = (event) => {
2018-09-13 16:10:51 +00:00
assert.strictEqual(event.data, location.href)
2016-11-25 17:47:28 +00:00
done()
}
window.addEventListener('message', listener)
w = window.open(`file://${fixtures}/pages/window-opener-location.html`, '', 'show=no,nodeIntegration=no')
2016-11-25 17:47:28 +00:00
})
2017-11-13 20:13:19 +00:00
it('works when origin does not match opener but has node integration', (done) => {
listener = (event) => {
2018-09-13 16:10:51 +00:00
assert.strictEqual(event.data, location.href)
done()
}
window.addEventListener('message', listener)
w = window.open(url, '', 'show=no,nodeIntegration=yes')
})
})
2017-11-13 20:13:19 +00:00
describe('window.opener access from <webview>', () => {
const scheme = 'other'
const srcPath = `${fixtures}/pages/webview-opener-postMessage.html`
const pageURL = `file://${fixtures}/pages/window-opener-location.html`
let webview = null
2017-11-13 20:13:19 +00:00
before((done) => {
protocol.registerFileProtocol(scheme, (request, callback) => {
callback(srcPath)
2017-11-13 20:13:19 +00:00
}, (error) => done(error))
})
2017-11-13 20:13:19 +00:00
after(() => {
protocol.unregisterProtocol(scheme)
})
2017-11-13 20:13:19 +00:00
afterEach(() => {
if (webview != null) webview.remove()
})
2017-11-13 20:13:19 +00:00
it('does nothing when origin of webview src URL does not match opener', (done) => {
webview = new WebView()
2017-11-13 20:13:19 +00:00
webview.addEventListener('console-message', (e) => {
assert.strictEqual(e.message, '')
done()
})
webview.setAttribute('allowpopups', 'on')
webview.src = url.format({
pathname: srcPath,
protocol: scheme,
query: {
p: pageURL
},
slashes: true
})
document.body.appendChild(webview)
})
2017-11-13 20:13:19 +00:00
it('works when origin matches', (done) => {
2016-11-25 17:47:28 +00:00
webview = new WebView()
2017-11-13 20:13:19 +00:00
webview.addEventListener('console-message', (e) => {
2018-09-13 16:10:51 +00:00
assert.strictEqual(e.message, webview.src)
2016-11-25 17:47:28 +00:00
done()
})
webview.setAttribute('allowpopups', 'on')
webview.src = url.format({
pathname: srcPath,
protocol: 'file',
query: {
p: pageURL
},
slashes: true
})
document.body.appendChild(webview)
})
2017-11-13 20:13:19 +00:00
it('works when origin does not match opener but has node integration', (done) => {
webview = new WebView()
2017-11-13 20:13:19 +00:00
webview.addEventListener('console-message', (e) => {
webview.remove()
2018-09-13 16:10:51 +00:00
assert.strictEqual(e.message, webview.src)
done()
})
webview.setAttribute('allowpopups', 'on')
webview.setAttribute('nodeintegration', 'on')
webview.src = url.format({
pathname: srcPath,
protocol: scheme,
query: {
p: pageURL
},
slashes: true
})
document.body.appendChild(webview)
})
})
2017-11-13 20:13:19 +00:00
describe('window.postMessage', () => {
it('sets the source and origin correctly', (done) => {
let b = null
2017-11-13 20:13:19 +00:00
listener = (event) => {
2016-03-25 20:03:49 +00:00
window.removeEventListener('message', listener)
b.close()
2017-11-13 20:13:19 +00:00
const message = JSON.parse(event.data)
2018-09-13 16:10:51 +00:00
assert.strictEqual(message.data, 'testing')
assert.strictEqual(message.origin, 'file://')
assert.strictEqual(message.sourceEqualsOpener, true)
assert.strictEqual(event.origin, 'file://')
2016-03-25 20:03:49 +00:00
done()
}
window.addEventListener('message', listener)
2018-09-13 16:10:51 +00:00
app.once('browser-window-created', (event, { webContents }) => {
2017-11-13 20:13:19 +00:00
webContents.once('did-finish-load', () => {
2017-01-12 21:19:27 +00:00
b.postMessage('testing', '*')
})
2016-03-25 20:03:49 +00:00
})
2017-11-13 20:13:19 +00:00
b = window.open(`file://${fixtures}/pages/window-open-postMessage.html`, '', 'show=no')
2016-03-25 20:03:49 +00:00
})
2017-11-13 20:13:19 +00:00
it('throws an exception when the targetOrigin cannot be converted to a string', () => {
const b = window.open('')
assert.throws(() => {
2018-09-13 16:10:51 +00:00
b.postMessage('test', { toString: null })
}, /Cannot convert object to primitive value/)
b.close()
})
2016-03-25 20:03:49 +00:00
})
2017-11-13 20:13:19 +00:00
describe('window.opener.postMessage', () => {
it('sets source and origin correctly', (done) => {
let b = null
2017-11-13 20:13:19 +00:00
listener = (event) => {
2016-03-25 20:03:49 +00:00
window.removeEventListener('message', listener)
b.close()
2018-09-13 16:10:51 +00:00
assert.strictEqual(event.source, b)
assert.strictEqual(event.origin, 'file://')
2016-03-25 20:03:49 +00:00
done()
}
window.addEventListener('message', listener)
2017-11-13 20:13:19 +00:00
b = window.open(`file://${fixtures}/pages/window-opener-postMessage.html`, '', 'show=no')
2016-03-25 20:03:49 +00:00
})
2017-11-13 20:13:19 +00:00
it('supports windows opened from a <webview>', (done) => {
const webview = new WebView()
2017-11-13 20:13:19 +00:00
webview.addEventListener('console-message', (e) => {
webview.remove()
2018-09-13 16:10:51 +00:00
assert.strictEqual(e.message, 'message')
done()
})
webview.allowpopups = true
webview.src = url.format({
pathname: `${fixtures}/pages/webview-opener-postMessage.html`,
protocol: 'file',
query: {
p: `${fixtures}/pages/window-opener-postMessage.html`
},
slashes: true
})
document.body.appendChild(webview)
})
2017-11-13 20:13:19 +00:00
describe('targetOrigin argument', () => {
let serverURL
let server
2017-11-13 20:13:19 +00:00
beforeEach((done) => {
server = http.createServer((req, res) => {
res.writeHead(200)
const filePath = path.join(fixtures, 'pages', 'window-opener-targetOrigin.html')
res.end(fs.readFileSync(filePath, 'utf8'))
})
2017-11-13 20:13:19 +00:00
server.listen(0, '127.0.0.1', () => {
serverURL = `http://127.0.0.1:${server.address().port}`
done()
})
})
2017-11-13 20:13:19 +00:00
afterEach(() => {
server.close()
})
2017-11-13 20:13:19 +00:00
it('delivers messages that match the origin', (done) => {
let b = null
2017-11-13 20:13:19 +00:00
listener = (event) => {
window.removeEventListener('message', listener)
b.close()
2018-09-13 16:10:51 +00:00
assert.strictEqual(event.data, 'deliver')
done()
}
window.addEventListener('message', listener)
b = window.open(serverURL, '', 'show=no')
})
})
2016-03-25 20:03:49 +00:00
})
2017-11-13 20:13:19 +00:00
describe('creating a Uint8Array under browser side', () => {
it('does not crash', () => {
const RUint8Array = remote.getGlobal('Uint8Array')
const arr = new RUint8Array()
2016-03-28 23:11:00 +00:00
assert(arr)
2016-03-25 20:03:49 +00:00
})
})
2017-11-13 20:13:19 +00:00
describe('webgl', () => {
before(function () {
if (isCI && process.platform === 'win32') {
this.skip()
}
})
2016-04-30 09:21:18 +00:00
2017-11-13 20:13:19 +00:00
it('can be get as context in canvas', () => {
if (process.platform === 'linux') {
// FIXME(alexeykuzmin): Skip the test.
// this.skip()
return
}
2016-03-28 23:11:00 +00:00
2017-11-13 20:13:19 +00:00
const webgl = document.createElement('canvas').getContext('webgl')
2018-09-13 16:10:51 +00:00
assert.notStrictEqual(webgl, null)
2016-03-25 20:03:49 +00:00
})
})
2017-11-13 20:13:19 +00:00
describe('web workers', () => {
it('Worker can work', (done) => {
const worker = new Worker('../fixtures/workers/worker.js')
const message = 'ping'
worker.onmessage = (event) => {
2018-09-13 16:10:51 +00:00
assert.strictEqual(event.data, message)
2016-03-25 20:03:49 +00:00
worker.terminate()
done()
}
worker.postMessage(message)
})
2017-11-13 20:13:19 +00:00
it('Worker has no node integration by default', (done) => {
const worker = new Worker('../fixtures/workers/worker_node.js')
2017-11-13 20:13:19 +00:00
worker.onmessage = (event) => {
2018-09-13 16:10:51 +00:00
assert.strictEqual(event.data, 'undefined undefined undefined undefined')
2017-03-15 11:07:28 +00:00
worker.terminate()
done()
}
})
2017-11-13 20:13:19 +00:00
it('Worker has node integration with nodeIntegrationInWorker', (done) => {
const webview = new WebView()
2017-11-13 20:13:19 +00:00
webview.addEventListener('ipc-message', (e) => {
2018-09-13 16:10:51 +00:00
assert.strictEqual(e.channel, 'object function object function')
2017-03-15 11:07:28 +00:00
webview.remove()
done()
})
2017-11-13 20:13:19 +00:00
webview.src = `file://${fixtures}/pages/worker.html`
2017-03-15 11:07:28 +00:00
webview.setAttribute('webpreferences', 'nodeIntegration, nodeIntegrationInWorker')
document.body.appendChild(webview)
})
2017-11-13 20:13:19 +00:00
it('SharedWorker can work', (done) => {
const worker = new SharedWorker('../fixtures/workers/shared_worker.js')
const message = 'ping'
worker.port.onmessage = (event) => {
2018-09-13 16:10:51 +00:00
assert.strictEqual(event.data, message)
2016-03-25 20:03:49 +00:00
done()
}
worker.port.postMessage(message)
})
2017-03-15 11:07:28 +00:00
2017-11-13 20:13:19 +00:00
it('SharedWorker has no node integration by default', (done) => {
const worker = new SharedWorker('../fixtures/workers/shared_worker_node.js')
2017-11-13 20:13:19 +00:00
worker.port.onmessage = (event) => {
2018-09-13 16:10:51 +00:00
assert.strictEqual(event.data, 'undefined undefined undefined undefined')
2017-03-15 11:07:28 +00:00
done()
}
})
2017-11-13 20:13:19 +00:00
it('SharedWorker has node integration with nodeIntegrationInWorker', (done) => {
const webview = new WebView()
2017-11-13 20:13:19 +00:00
webview.addEventListener('console-message', (e) => {
2017-03-15 11:07:28 +00:00
console.log(e)
})
2017-11-13 20:13:19 +00:00
webview.addEventListener('ipc-message', (e) => {
2018-09-13 16:10:51 +00:00
assert.strictEqual(e.channel, 'object function object function')
2017-03-15 11:07:28 +00:00
webview.remove()
done()
})
2017-11-13 20:13:19 +00:00
webview.src = `file://${fixtures}/pages/shared_worker.html`
2017-03-15 11:07:28 +00:00
webview.setAttribute('webpreferences', 'nodeIntegration, nodeIntegrationInWorker')
document.body.appendChild(webview)
})
2016-03-25 20:03:49 +00:00
})
2017-11-13 20:13:19 +00:00
describe('iframe', () => {
let iframe = null
2016-03-25 20:03:49 +00:00
2017-11-13 20:13:19 +00:00
beforeEach(() => {
2016-03-25 20:03:49 +00:00
iframe = document.createElement('iframe')
})
2017-11-13 20:13:19 +00:00
afterEach(() => {
2016-03-25 20:03:49 +00:00
document.body.removeChild(iframe)
})
2017-11-13 20:13:19 +00:00
it('does not have node integration', (done) => {
iframe.src = `file://${fixtures}/pages/set-global.html`
2016-03-25 20:03:49 +00:00
document.body.appendChild(iframe)
2017-11-13 20:13:19 +00:00
iframe.onload = () => {
2018-09-13 16:10:51 +00:00
assert.strictEqual(iframe.contentWindow.test, 'undefined undefined undefined')
2016-03-25 20:03:49 +00:00
done()
}
})
})
2017-11-13 20:13:19 +00:00
describe('storage', () => {
2018-11-12 17:19:01 +00:00
describe('DOM storage quota override', () => {
['localStorage', 'sessionStorage'].forEach((storageName) => {
it(`allows saving at least 50MiB in ${storageName}`, () => {
const storage = window[storageName]
const testKeyName = '_electronDOMStorageQuotaOverrideTest'
// 25 * 2^20 UTF-16 characters will require 50MiB
const arraySize = 25 * Math.pow(2, 20)
storage[testKeyName] = new Array(arraySize).fill('X').join('')
expect(storage[testKeyName]).to.have.lengthOf(arraySize)
delete storage[testKeyName]
})
})
})
2017-11-13 20:13:19 +00:00
it('requesting persitent quota works', (done) => {
navigator.webkitPersistentStorage.requestQuota(1024 * 1024, (grantedBytes) => {
2018-09-13 16:10:51 +00:00
assert.strictEqual(grantedBytes, 1048576)
2016-03-25 20:03:49 +00:00
done()
})
})
2016-09-21 18:35:37 +00:00
2017-11-13 20:13:19 +00:00
describe('custom non standard schemes', () => {
2016-09-21 18:35:37 +00:00
const protocolName = 'storage'
let contents = null
2017-11-13 20:13:19 +00:00
before((done) => {
const handler = (request, callback) => {
const parsedUrl = url.parse(request.url)
2016-09-21 18:35:37 +00:00
let filename
switch (parsedUrl.pathname) {
case '/localStorage' : filename = 'local_storage.html'; break
case '/sessionStorage' : filename = 'session_storage.html'; break
case '/WebSQL' : filename = 'web_sql.html'; break
case '/indexedDB' : filename = 'indexed_db.html'; break
case '/cookie' : filename = 'cookie.html'; break
default : filename = ''
}
2018-09-13 16:10:51 +00:00
callback({ path: `${fixtures}/pages/storage/${filename}` })
2016-09-21 18:35:37 +00:00
}
2017-11-13 20:13:19 +00:00
protocol.registerFileProtocol(protocolName, handler, (error) => done(error))
2016-09-21 18:35:37 +00:00
})
2017-11-13 20:13:19 +00:00
after((done) => {
2016-09-21 18:35:37 +00:00
protocol.unregisterProtocol(protocolName, () => done())
})
2017-11-13 20:13:19 +00:00
beforeEach(() => {
contents = webContents.create({
nodeIntegration: true
})
2016-09-21 18:35:37 +00:00
})
2017-11-13 20:13:19 +00:00
afterEach(() => {
2016-09-21 18:35:37 +00:00
contents.destroy()
contents = null
})
fix: use appropriate site instance for cross-site nav's (#15821) * fix: use Chromium's determined new site instance as candidate when navigating. When navigating to a new address, consider using Chromium's determined site instance for the new page as it should belong to an existing browsing instance when the navigation was triggered by window.open(). fixes 8100. * Revert "fix: use Chromium's determined new site instance as candidate when navigating." This reverts commit eb95f935654a2c4d4457821297670836c10fdfd5. * fix: delegate site instance creation back to content when sandboxed. * fix: ensure site isolation is on * test: adapt ut for cross-site navigation * fix: register pending processes during a navigation. * refactor: dont call loadURL for a window constructed from an existing webContents. * test: add sandboxed affinity UT's. * fix: check affinity before deciding if to force a new site instance. * chore: adapt subsequent patch. * refactor: constify logically const methods. * fix: do not reuse site instances when navigation redirects cross-site. * test: ensure localStorage accessible after x-site redirect. * test: adapt localStorage acess denied UT for site isolation. * fix: do not send render-view-deleted for speculative frames. * chore: amend tests after rebase. * test: add ut for webContents' render-view-deleted emission * fix: introduce current-render-view-deleted for current RVH's deletions. Revert render-view-deleted to being emitted with any RVH's deletion. current-render-view-deleted is emitted only when the RVH being deleted is the current one. * refactor: style and comments fixed.
2018-12-05 08:03:39 +00:00
it('cannot access localStorage', (done) => {
ipcMain.once('local-storage-response', (event, message) => {
2018-09-13 16:10:51 +00:00
assert.strictEqual(
fix: use appropriate site instance for cross-site nav's (#15821) * fix: use Chromium's determined new site instance as candidate when navigating. When navigating to a new address, consider using Chromium's determined site instance for the new page as it should belong to an existing browsing instance when the navigation was triggered by window.open(). fixes 8100. * Revert "fix: use Chromium's determined new site instance as candidate when navigating." This reverts commit eb95f935654a2c4d4457821297670836c10fdfd5. * fix: delegate site instance creation back to content when sandboxed. * fix: ensure site isolation is on * test: adapt ut for cross-site navigation * fix: register pending processes during a navigation. * refactor: dont call loadURL for a window constructed from an existing webContents. * test: add sandboxed affinity UT's. * fix: check affinity before deciding if to force a new site instance. * chore: adapt subsequent patch. * refactor: constify logically const methods. * fix: do not reuse site instances when navigation redirects cross-site. * test: ensure localStorage accessible after x-site redirect. * test: adapt localStorage acess denied UT for site isolation. * fix: do not send render-view-deleted for speculative frames. * chore: amend tests after rebase. * test: add ut for webContents' render-view-deleted emission * fix: introduce current-render-view-deleted for current RVH's deletions. Revert render-view-deleted to being emitted with any RVH's deletion. current-render-view-deleted is emitted only when the RVH being deleted is the current one. * refactor: style and comments fixed.
2018-12-05 08:03:39 +00:00
message,
2016-09-21 18:35:37 +00:00
'Failed to read the \'localStorage\' property from \'Window\': Access is denied for this document.')
done()
2016-09-21 18:35:37 +00:00
})
contents.loadURL(protocolName + '://host/localStorage')
})
2017-11-13 20:13:19 +00:00
it('cannot access sessionStorage', (done) => {
ipcMain.once('session-storage-response', (event, error) => {
2018-09-13 16:10:51 +00:00
assert.strictEqual(
2016-09-21 18:35:37 +00:00
error,
'Failed to read the \'sessionStorage\' property from \'Window\': Access is denied for this document.')
done()
})
2017-11-13 20:13:19 +00:00
contents.loadURL(`${protocolName}://host/sessionStorage`)
2016-09-21 18:35:37 +00:00
})
2017-11-13 20:13:19 +00:00
it('cannot access WebSQL database', (done) => {
ipcMain.once('web-sql-response', (event, error) => {
2018-09-13 16:10:51 +00:00
assert.strictEqual(
2016-09-21 18:35:37 +00:00
error,
'Failed to execute \'openDatabase\' on \'Window\': Access to the WebDatabase API is denied in this context.')
2016-09-21 18:35:37 +00:00
done()
})
2017-11-13 20:13:19 +00:00
contents.loadURL(`${protocolName}://host/WebSQL`)
2016-09-21 18:35:37 +00:00
})
2017-11-13 20:13:19 +00:00
it('cannot access indexedDB', (done) => {
ipcMain.once('indexed-db-response', (event, error) => {
assert.strictEqual(
error,
'Failed to execute \'open\' on \'IDBFactory\': access to the Indexed Database API is denied in this context.')
2016-09-21 18:35:37 +00:00
done()
})
2017-11-13 20:13:19 +00:00
contents.loadURL(`${protocolName}://host/indexedDB`)
2016-09-21 18:35:37 +00:00
})
2017-11-13 20:13:19 +00:00
it('cannot access cookie', (done) => {
ipcMain.once('cookie-response', (event, error) => {
assert.strictEqual(
error,
'Failed to set the \'cookie\' property on \'Document\': Access is denied for this document.')
2016-09-21 18:35:37 +00:00
done()
})
2017-11-13 20:13:19 +00:00
contents.loadURL(`${protocolName}://host/cookie`)
2016-09-21 18:35:37 +00:00
})
})
fix: use appropriate site instance for cross-site nav's (#15821) * fix: use Chromium's determined new site instance as candidate when navigating. When navigating to a new address, consider using Chromium's determined site instance for the new page as it should belong to an existing browsing instance when the navigation was triggered by window.open(). fixes 8100. * Revert "fix: use Chromium's determined new site instance as candidate when navigating." This reverts commit eb95f935654a2c4d4457821297670836c10fdfd5. * fix: delegate site instance creation back to content when sandboxed. * fix: ensure site isolation is on * test: adapt ut for cross-site navigation * fix: register pending processes during a navigation. * refactor: dont call loadURL for a window constructed from an existing webContents. * test: add sandboxed affinity UT's. * fix: check affinity before deciding if to force a new site instance. * chore: adapt subsequent patch. * refactor: constify logically const methods. * fix: do not reuse site instances when navigation redirects cross-site. * test: ensure localStorage accessible after x-site redirect. * test: adapt localStorage acess denied UT for site isolation. * fix: do not send render-view-deleted for speculative frames. * chore: amend tests after rebase. * test: add ut for webContents' render-view-deleted emission * fix: introduce current-render-view-deleted for current RVH's deletions. Revert render-view-deleted to being emitted with any RVH's deletion. current-render-view-deleted is emitted only when the RVH being deleted is the current one. * refactor: style and comments fixed.
2018-12-05 08:03:39 +00:00
describe('can be accessed', () => {
let server = null
before((done) => {
server = http.createServer((req, res) => {
const respond = () => {
if (req.url === '/redirect-cross-site') {
res.setHeader('Location', `${server.cross_site_url}/redirected`)
res.statusCode = 302
res.end()
} else if (req.url === '/redirected') {
res.end('<html><script>window.localStorage</script></html>')
} else {
res.end()
}
}
setTimeout(respond, 0)
})
server.listen(0, '127.0.0.1', () => {
server.url = `http://127.0.0.1:${server.address().port}`
server.cross_site_url = `http://localhost:${server.address().port}`
done()
})
})
after(() => {
server.close()
server = null
})
const testLocalStorageAfterXSiteRedirect = (testTitle, extraPreferences = {}) => {
it(testTitle, (done) => {
w = new BrowserWindow({
show: false,
...extraPreferences
})
fix: use appropriate site instance for cross-site nav's (#15821) * fix: use Chromium's determined new site instance as candidate when navigating. When navigating to a new address, consider using Chromium's determined site instance for the new page as it should belong to an existing browsing instance when the navigation was triggered by window.open(). fixes 8100. * Revert "fix: use Chromium's determined new site instance as candidate when navigating." This reverts commit eb95f935654a2c4d4457821297670836c10fdfd5. * fix: delegate site instance creation back to content when sandboxed. * fix: ensure site isolation is on * test: adapt ut for cross-site navigation * fix: register pending processes during a navigation. * refactor: dont call loadURL for a window constructed from an existing webContents. * test: add sandboxed affinity UT's. * fix: check affinity before deciding if to force a new site instance. * chore: adapt subsequent patch. * refactor: constify logically const methods. * fix: do not reuse site instances when navigation redirects cross-site. * test: ensure localStorage accessible after x-site redirect. * test: adapt localStorage acess denied UT for site isolation. * fix: do not send render-view-deleted for speculative frames. * chore: amend tests after rebase. * test: add ut for webContents' render-view-deleted emission * fix: introduce current-render-view-deleted for current RVH's deletions. Revert render-view-deleted to being emitted with any RVH's deletion. current-render-view-deleted is emitted only when the RVH being deleted is the current one. * refactor: style and comments fixed.
2018-12-05 08:03:39 +00:00
let redirected = false
w.webContents.on('crashed', () => {
assert.fail('renderer crashed / was killed')
})
w.webContents.on('did-redirect-navigation', (event, url) => {
assert.strictEqual(url, `${server.cross_site_url}/redirected`)
redirected = true
})
w.webContents.on('did-finish-load', () => {
assert.strictEqual(redirected, true, 'didnt redirect')
done()
})
w.loadURL(`${server.url}/redirect-cross-site`)
})
}
testLocalStorageAfterXSiteRedirect('after a cross-site redirect')
testLocalStorageAfterXSiteRedirect('after a cross-site redirect in sandbox mode', { sandbox: true })
})
2016-03-25 20:03:49 +00:00
})
2017-11-13 20:13:19 +00:00
describe('websockets', () => {
let wss = null
let server = null
const WebSocketServer = ws.Server
2016-03-25 20:03:49 +00:00
2017-11-13 20:13:19 +00:00
afterEach(() => {
2016-03-25 20:03:49 +00:00
wss.close()
server.close()
})
2017-11-13 20:13:19 +00:00
it('has user agent', (done) => {
2016-03-25 20:03:49 +00:00
server = http.createServer()
2017-11-13 20:13:19 +00:00
server.listen(0, '127.0.0.1', () => {
const port = server.address().port
wss = new WebSocketServer({ server: server })
2016-03-25 20:03:49 +00:00
wss.on('error', done)
wss.on('connection', (ws, upgradeReq) => {
if (upgradeReq.headers['user-agent']) {
2016-03-25 20:03:49 +00:00
done()
2016-01-12 02:40:23 +00:00
} else {
2016-03-25 20:03:49 +00:00
done('user agent is empty')
2016-01-12 02:40:23 +00:00
}
2016-03-25 20:03:49 +00:00
})
2017-11-13 20:13:19 +00:00
const socket = new WebSocket(`ws://127.0.0.1:${port}`)
2016-03-28 23:11:00 +00:00
assert(socket)
2016-03-25 20:03:49 +00:00
})
})
})
2017-11-13 20:13:19 +00:00
describe('Promise', () => {
it('resolves correctly in Node.js calls', (done) => {
2016-01-12 02:40:23 +00:00
document.registerElement('x-element', {
prototype: Object.create(HTMLElement.prototype, {
createdCallback: {
2017-11-13 20:13:19 +00:00
value: () => {}
2016-01-12 02:40:23 +00:00
}
})
2016-03-25 20:03:49 +00:00
})
2017-11-13 20:13:19 +00:00
setImmediate(() => {
let called = false
Promise.resolve().then(() => {
2016-03-25 20:03:49 +00:00
done(called ? void 0 : new Error('wrong sequence'))
})
document.createElement('x-element')
called = true
})
})
2017-11-13 20:13:19 +00:00
it('resolves correctly in Electron calls', (done) => {
2016-01-12 02:40:23 +00:00
document.registerElement('y-element', {
prototype: Object.create(HTMLElement.prototype, {
createdCallback: {
2017-11-13 20:13:19 +00:00
value: () => {}
2016-01-12 02:40:23 +00:00
}
})
2016-03-25 20:03:49 +00:00
})
2017-11-13 20:13:19 +00:00
remote.getGlobal('setImmediate')(() => {
let called = false
Promise.resolve().then(() => {
2016-03-25 20:03:49 +00:00
done(called ? void 0 : new Error('wrong sequence'))
})
document.createElement('y-element')
called = true
})
})
})
2016-08-22 10:26:07 +00:00
2017-11-13 20:13:19 +00:00
describe('fetch', () => {
it('does not crash', (done) => {
const server = http.createServer((req, res) => {
2016-08-22 10:26:07 +00:00
res.end('test')
server.close()
})
2017-11-13 20:13:19 +00:00
server.listen(0, '127.0.0.1', () => {
2016-08-22 10:26:07 +00:00
const port = server.address().port
2017-11-13 20:13:19 +00:00
fetch(`http://127.0.0.1:${port}`).then((res) => res.body.getReader())
.then((reader) => {
reader.read().then((r) => {
reader.cancel()
done()
})
}).catch((e) => done(e))
2016-08-22 10:26:07 +00:00
})
})
})
2017-02-04 14:48:16 +00:00
2017-11-13 20:13:19 +00:00
describe('PDF Viewer', () => {
2018-03-15 08:51:48 +00:00
before(function () {
if (!features.isPDFViewerEnabled()) {
2018-03-15 08:51:48 +00:00
return this.skip()
}
})
2017-10-20 06:46:41 +00:00
beforeEach(() => {
this.pdfSource = url.format({
2018-03-15 08:51:48 +00:00
pathname: path.join(fixtures, 'assets', 'cat.pdf').replace(/\\/g, '/'),
2017-10-20 06:46:41 +00:00
protocol: 'file',
slashes: true
})
this.pdfSourceWithParams = url.format({
2018-03-15 08:51:48 +00:00
pathname: path.join(fixtures, 'assets', 'cat.pdf').replace(/\\/g, '/'),
query: {
a: 1,
b: 2
},
protocol: 'file',
slashes: true
2017-10-20 06:46:41 +00:00
})
2018-03-15 08:51:48 +00:00
2018-09-13 16:10:51 +00:00
this.createBrowserWindow = ({ plugins, preload }) => {
2018-03-15 08:51:48 +00:00
w = new BrowserWindow({
show: false,
webPreferences: {
preload: path.join(fixtures, 'module', preload),
plugins: plugins
}
})
}
this.testPDFIsLoadedInSubFrame = (page, preloadFile, done) => {
2018-03-15 08:51:48 +00:00
const pagePath = url.format({
pathname: path.join(fixtures, 'pages', page).replace(/\\/g, '/'),
protocol: 'file',
slashes: true
})
2018-09-13 16:10:51 +00:00
this.createBrowserWindow({ plugins: true, preload: preloadFile })
2018-03-15 08:51:48 +00:00
ipcMain.once('pdf-loaded', (event, state) => {
2018-09-13 16:10:51 +00:00
assert.strictEqual(state, 'success')
2018-03-15 08:51:48 +00:00
done()
})
w.webContents.on('page-title-updated', () => {
const parsedURL = url.parse(w.webContents.getURL(), true)
2018-09-13 16:10:51 +00:00
assert.strictEqual(parsedURL.protocol, 'chrome:')
assert.strictEqual(parsedURL.hostname, 'pdf-viewer')
assert.strictEqual(parsedURL.query.src, pagePath)
assert.strictEqual(w.webContents.getTitle(), 'cat.pdf')
2018-03-15 08:51:48 +00:00
})
w.loadFile(path.join(fixtures, 'pages', page))
2018-03-15 08:51:48 +00:00
}
})
2017-10-20 06:46:41 +00:00
2017-11-13 20:13:19 +00:00
it('opens when loading a pdf resource as top level navigation', (done) => {
2018-09-13 16:10:51 +00:00
this.createBrowserWindow({ plugins: true, preload: 'preload-pdf-loaded.js' })
2017-11-13 20:13:19 +00:00
ipcMain.once('pdf-loaded', (event, state) => {
2018-09-13 16:10:51 +00:00
assert.strictEqual(state, 'success')
2017-06-28 20:01:22 +00:00
done()
2017-03-11 21:36:08 +00:00
})
2017-11-13 20:13:19 +00:00
w.webContents.on('page-title-updated', () => {
2017-02-04 14:48:16 +00:00
const parsedURL = url.parse(w.webContents.getURL(), true)
2018-09-13 16:10:51 +00:00
assert.strictEqual(parsedURL.protocol, 'chrome:')
assert.strictEqual(parsedURL.hostname, 'pdf-viewer')
assert.strictEqual(parsedURL.query.src, this.pdfSource)
assert.strictEqual(w.webContents.getTitle(), 'cat.pdf')
2017-02-04 14:48:16 +00:00
})
w.webContents.loadURL(this.pdfSource)
2017-02-04 14:48:16 +00:00
})
2017-11-13 20:13:19 +00:00
it('opens a pdf link given params, the query string should be escaped', (done) => {
2018-09-13 16:10:51 +00:00
this.createBrowserWindow({ plugins: true, preload: 'preload-pdf-loaded.js' })
2017-11-13 20:13:19 +00:00
ipcMain.once('pdf-loaded', (event, state) => {
2018-09-13 16:10:51 +00:00
assert.strictEqual(state, 'success')
done()
})
2017-11-13 20:13:19 +00:00
w.webContents.on('page-title-updated', () => {
const parsedURL = url.parse(w.webContents.getURL(), true)
2018-09-13 16:10:51 +00:00
assert.strictEqual(parsedURL.protocol, 'chrome:')
assert.strictEqual(parsedURL.hostname, 'pdf-viewer')
assert.strictEqual(parsedURL.query.src, this.pdfSourceWithParams)
assert.strictEqual(parsedURL.query.b, undefined)
Fix some flaky tests in CI (#12153) * Guard whole InitPrefs with ScopedAllowIO Saw a crash: 0 0x7f8d2f7d918d base::debug::StackTrace::StackTrace() 1 0x7f8d2f7d755c base::debug::StackTrace::StackTrace() 2 0x7f8d2f867caa logging::LogMessage::~LogMessage() 3 0x7f8d2fa157c7 base::ThreadRestrictions::AssertIOAllowed() 4 0x7f8d2f83453a base::OpenFile() 5 0x7f8d2f82a967 base::ReadFileToStringWithMaxSize() 6 0x7f8d2f82ad44 base::ReadFileToString() 7 0x7f8d2f846f73 JSONFileValueDeserializer::ReadFileToString() 8 0x7f8d2f84738c JSONFileValueDeserializer::Deserialize() 9 0x7f8d35a5d1f6 <unknown> 10 0x7f8d35a5c217 JsonPrefStore::ReadPrefs() 11 0x7f8d35a87d3e PrefService::InitFromStorage() 12 0x7f8d35a87c60 PrefService::PrefService() 13 0x7f8d35a91a10 PrefServiceFactory::Create() 14 0x000000e86e1b brightray::BrowserContext::InitPrefs() 15 0x000000c2bd64 atom::AtomBrowserContext::AtomBrowserContext() 16 0x000000c320db atom::AtomBrowserContext::From() 17 0x000000b4b8b5 atom::api::Session::FromPartition() * Fix done being called twice in setInterval test The callback passed to browser process is called asyncly, so it is possible that multiple callbacks has already been scheduled before we can clearInternval. * Fix failing test when dir name has special chars The pdfSource is not escaped while parsedURL.search is. * Call done with Error instead of string * Fix crash caused by not removing input observer Solve crash: 0 libcontent.dylib content::RenderWidgetHostImpl::DispatchInputEventWithLatencyInfo(blink::WebInputEvent const&, ui::LatencyInfo*) + 214 1 libcontent.dylib content::RenderWidgetHostImpl::ForwardMouseEventWithLatencyInfo(blink::WebMouseEvent const&, ui::LatencyInfo const&) + 1350 2 libcontent.dylib content::RenderWidgetHostViewMac::ProcessMouseEvent(blink::WebMouseEvent const&, ui::LatencyInfo const&) + 44 3 libcontent.dylib content::RenderWidgetHostInputEventRouter::RouteMouseEvent(content::RenderWidgetHostViewBase*, blink::WebMouseEvent*, ui::LatencyInfo const&) + 1817 * Print detailed error * Run tests after server is ready
2018-03-07 05:40:27 +00:00
assert(parsedURL.search.endsWith('%3Fa%3D1%26b%3D2'))
2018-09-13 16:10:51 +00:00
assert.strictEqual(w.webContents.getTitle(), 'cat.pdf')
})
w.webContents.loadURL(this.pdfSourceWithParams)
})
2017-11-13 20:13:19 +00:00
it('should download a pdf when plugins are disabled', (done) => {
2018-09-13 16:10:51 +00:00
this.createBrowserWindow({ plugins: false, preload: 'preload-pdf-loaded.js' })
ipcRenderer.sendSync('set-download-option', false, false)
2017-11-13 20:13:19 +00:00
ipcRenderer.once('download-done', (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename) => {
2018-09-13 16:10:51 +00:00
assert.strictEqual(state, 'completed')
assert.strictEqual(filename, 'cat.pdf')
assert.strictEqual(mimeType, 'application/pdf')
fs.unlinkSync(path.join(fixtures, 'mock.pdf'))
done()
})
w.webContents.loadURL(this.pdfSource)
})
2017-11-13 20:13:19 +00:00
it('should not open when pdf is requested as sub resource', (done) => {
fetch(this.pdfSource).then((res) => {
2018-09-13 16:10:51 +00:00
assert.strictEqual(res.status, 200)
assert.notStrictEqual(document.title, 'cat.pdf')
2017-02-04 14:48:16 +00:00
done()
2017-11-13 20:13:19 +00:00
}).catch((e) => done(e))
2017-02-04 14:48:16 +00:00
})
2017-10-20 06:46:41 +00:00
2017-11-13 20:13:19 +00:00
it('opens when loading a pdf resource in a iframe', (done) => {
this.testPDFIsLoadedInSubFrame('pdf-in-iframe.html', 'preload-pdf-loaded-in-subframe.js', done)
2017-10-19 14:15:02 +00:00
})
2017-10-20 06:46:41 +00:00
2017-11-13 20:13:19 +00:00
it('opens when loading a pdf resource in a nested iframe', (done) => {
this.testPDFIsLoadedInSubFrame('pdf-in-nested-iframe.html', 'preload-pdf-loaded-in-nested-subframe.js', done)
2017-10-19 14:15:02 +00:00
})
2017-02-04 14:48:16 +00:00
})
2017-11-13 20:13:19 +00:00
describe('window.alert(message, title)', () => {
it('throws an exception when the arguments cannot be converted to strings', () => {
assert.throws(() => {
2018-09-13 16:10:51 +00:00
window.alert({ toString: null })
}, /Cannot convert object to primitive value/)
})
})
2017-11-13 20:13:19 +00:00
describe('window.confirm(message, title)', () => {
it('throws an exception when the arguments cannot be converted to strings', () => {
assert.throws(() => {
2018-09-13 16:10:51 +00:00
window.confirm({ toString: null }, 'title')
}, /Cannot convert object to primitive value/)
})
})
2017-11-13 20:13:19 +00:00
describe('window.history', () => {
describe('window.history.go(offset)', () => {
it('throws an exception when the argumnet cannot be converted to a string', () => {
assert.throws(() => {
2018-09-13 16:10:51 +00:00
window.history.go({ toString: null })
}, /Cannot convert object to primitive value/)
})
})
2017-11-13 20:13:19 +00:00
describe('window.history.pushState', () => {
it('should push state after calling history.pushState() from the same url', (done) => {
2017-11-13 20:13:19 +00:00
w = new BrowserWindow({ show: false })
w.webContents.once('did-finish-load', async () => {
// History should have current page by now.
2018-09-13 16:10:51 +00:00
assert.strictEqual(w.webContents.length(), 1)
w.webContents.executeJavaScript('window.history.pushState({}, "")').then(() => {
// Initial page + pushed state
2018-09-13 16:10:51 +00:00
assert.strictEqual(w.webContents.length(), 2)
done()
})
})
2017-06-05 21:47:11 +00:00
w.loadURL('about:blank')
})
})
})
describe('SpeechSynthesis', () => {
before(function () {
if (isCI || !features.isTtsEnabled()) {
this.skip()
}
})
it('should emit lifecycle events', async () => {
const sentence = `long sentence which will take at least a few seconds to
utter so that it's possible to pause and resume before the end`
const utter = new SpeechSynthesisUtterance(sentence)
// Create a dummy utterence so that speech synthesis state
// is initialized for later calls.
speechSynthesis.speak(new SpeechSynthesisUtterance())
speechSynthesis.cancel()
speechSynthesis.speak(utter)
// paused state after speak()
expect(speechSynthesis.paused).to.be.false()
await new Promise((resolve) => { utter.onstart = resolve })
// paused state after start event
expect(speechSynthesis.paused).to.be.false()
speechSynthesis.pause()
// paused state changes async, right before the pause event
expect(speechSynthesis.paused).to.be.false()
await new Promise((resolve) => { utter.onpause = resolve })
expect(speechSynthesis.paused).to.be.true()
speechSynthesis.resume()
await new Promise((resolve) => { utter.onresume = resolve })
// paused state after resume event
expect(speechSynthesis.paused).to.be.false()
await new Promise((resolve) => { utter.onend = resolve })
})
})
describe('focus handling', () => {
let webviewContents = null
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(fixtures, 'pages', 'tab-focus-loop-elements.html'))
const [, wvContents] = await webviewReady
webviewContents = wvContents
await emittedOnce(webviewContents, 'did-finish-load')
w.focus()
})
afterEach(() => {
webviewContents = null
})
const expectFocusChange = async () => {
const [, focusedElementId] = await emittedOnce(ipcMain, 'focus-changed')
return focusedElementId
}
describe('a TAB press', () => {
const tabPressEvent = {
type: 'keyDown',
keyCode: 'Tab'
}
it('moves focus to the next focusable item', async () => {
let focusChange = expectFocusChange()
w.webContents.sendInputEvent(tabPressEvent)
let focusedElementId = await focusChange
assert.strictEqual(focusedElementId, 'BUTTON-element-1', `should start focused in element-1, it's instead in ${focusedElementId}`)
focusChange = expectFocusChange()
w.webContents.sendInputEvent(tabPressEvent)
focusedElementId = await focusChange
assert.strictEqual(focusedElementId, 'BUTTON-element-2', `focus should've moved to element-2, it's instead in ${focusedElementId}`)
focusChange = expectFocusChange()
w.webContents.sendInputEvent(tabPressEvent)
focusedElementId = await focusChange
assert.strictEqual(focusedElementId, '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
assert.strictEqual(focusedElementId, '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
assert.strictEqual(focusedElementId, 'BUTTON-element-3', `focus should've moved to element-3, it's instead in ${focusedElementId}`)
focusChange = expectFocusChange()
w.webContents.sendInputEvent(tabPressEvent)
focusedElementId = await focusChange
assert.strictEqual(focusedElementId, 'BUTTON-element-1', `focus should've looped back to element-1, it's instead in ${focusedElementId}`)
})
})
describe('a SHIFT + TAB press', () => {
const shiftTabPressEvent = {
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
assert.strictEqual(focusedElementId, 'BUTTON-element-3', `should start focused in element-3, it's instead in ${focusedElementId}`)
focusChange = expectFocusChange()
w.webContents.sendInputEvent(shiftTabPressEvent)
focusedElementId = await focusChange
assert.strictEqual(focusedElementId, '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
assert.strictEqual(focusedElementId, '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
assert.strictEqual(focusedElementId, 'BUTTON-element-2', `focus should've moved to element-2, it's instead in ${focusedElementId}`)
focusChange = expectFocusChange()
w.webContents.sendInputEvent(shiftTabPressEvent)
focusedElementId = await focusChange
assert.strictEqual(focusedElementId, 'BUTTON-element-1', `focus should've moved to element-1, it's instead in ${focusedElementId}`)
focusChange = expectFocusChange()
w.webContents.sendInputEvent(shiftTabPressEvent)
focusedElementId = await focusChange
assert.strictEqual(focusedElementId, 'BUTTON-element-3', `focus should've looped back to element-3, it's instead in ${focusedElementId}`)
})
})
})
2016-03-25 20:03:49 +00:00
})
describe('font fallback', () => {
async function getRenderedFonts (html) {
const w = new BrowserWindow({ show: false })
try {
await w.loadURL(`data:text/html,${html}`)
w.webContents.debugger.attach()
const sendCommand = (...args) => w.webContents.debugger.sendCommand(...args)
const { nodeId } = (await sendCommand('DOM.getDocument')).root.children[0]
await sendCommand('CSS.enable')
const { fonts } = await sendCommand('CSS.getPlatformFontsForNode', { nodeId })
return fonts
} finally {
w.close()
}
}
it('should use Helvetica for sans-serif on Mac, and Arial on Windows and Linux', async () => {
const html = `<body style="font-family: sans-serif">test</body>`
const fonts = await getRenderedFonts(html)
expect(fonts).to.be.an('array')
expect(fonts).to.have.length(1)
expect(fonts[0].familyName).to.equal({
'win32': 'Arial',
'darwin': 'Helvetica',
'linux': 'DejaVu Sans' // I think this depends on the distro? We don't specify a default.
}[process.platform])
})
it('should fall back to Japanese font for sans-serif Japanese script', async function () {
if (process.platform === 'linux') {
return this.skip()
}
const html = `
<html lang="ja-JP">
<head>
<meta charset="utf-8" />
</head>
<body style="font-family: sans-serif">test 智史</body>
</html>
`
const fonts = await getRenderedFonts(html)
expect(fonts).to.be.an('array')
expect(fonts).to.have.length(1)
expect(fonts[0].familyName).to.equal({
'win32': 'Meiryo',
'darwin': 'Hiragino Kaku Gothic ProN'
}[process.platform])
})
})