diff --git a/docs/api/structures/input-event.md b/docs/api/structures/input-event.md
index c24cb528a79..c7bcb2f4b36 100644
--- a/docs/api/structures/input-event.md
+++ b/docs/api/structures/input-event.md
@@ -1,6 +1,6 @@
# InputEvent Object
-* `modifiers` String[] - An array of modifiers of the event, can
- be `shift`, `control`, `alt`, `meta`, `isKeypad`, `isAutoRepeat`,
- `leftButtonDown`, `middleButtonDown`, `rightButtonDown`, `capsLock`,
- `numLock`, `left`, `right`.
+* `modifiers` String[] (optional) - An array of modifiers of the event, can
+ be `shift`, `control`, `ctrl`, `alt`, `meta`, `command`, `cmd`, `isKeypad`,
+ `isAutoRepeat`, `leftButtonDown`, `middleButtonDown`, `rightButtonDown`,
+ `capsLock`, `numLock`, `left`, `right`.
diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md
index f5d25bac825..52a1a39b886 100644
--- a/docs/api/web-contents.md
+++ b/docs/api/web-contents.md
@@ -1593,8 +1593,8 @@ End subscribing for frame presentation events.
* `item` Object
* `file` String[] | String - The path(s) to the file(s) being dragged.
- * `icon` [NativeImage](native-image.md) - The image must be non-empty on
- macOS.
+ * `icon` [NativeImage](native-image.md) | String - The image must be
+ non-empty on macOS.
Sets the `item` as dragging item for current drag-drop operation, `file` is the
absolute path of the file to be dragged, and `icon` is the image showing under
diff --git a/script/codesign/import-testing-cert-ci.sh b/script/codesign/import-testing-cert-ci.sh
index 3772373015f..03732f89d6e 100755
--- a/script/codesign/import-testing-cert-ci.sh
+++ b/script/codesign/import-testing-cert-ci.sh
@@ -7,8 +7,6 @@ security create-keychain -p $KEYCHAIN_PASSWORD $KEY_CHAIN
security default-keychain -s $KEY_CHAIN
# Unlock the keychain
security unlock-keychain -p $KEYCHAIN_PASSWORD $KEY_CHAIN
-# Set keychain locking timeout to 3600 seconds
-security set-keychain-settings -t 3600 -u $KEY_CHAIN
# Add certificates to keychain and allow codesign to access them
security import "$(dirname $0)"/signing.cer -k $KEY_CHAIN -A /usr/bin/codesign
@@ -16,7 +14,7 @@ security import "$(dirname $0)"/signing.pem -k $KEY_CHAIN -A /usr/bin/codesign
security import "$(dirname $0)"/signing.p12 -k $KEY_CHAIN -P $SPEC_KEY_PASSWORD -A /usr/bin/codesign
echo "Add keychain to keychain-list"
-security list-keychains -s mac-build.keychain
+security list-keychains -s $KEY_CHAIN
echo "Setting key partition list"
security set-key-partition-list -S apple-tool:,apple: -s -k $KEYCHAIN_PASSWORD $KEY_CHAIN
diff --git a/spec-main/ambient.d.ts b/spec-main/ambient.d.ts
index 10f112f86af..bb3b467b5b0 100644
--- a/spec-main/ambient.d.ts
+++ b/spec-main/ambient.d.ts
@@ -16,6 +16,7 @@ declare namespace Electron {
interface WebContents {
getOwnerBrowserWindow(): BrowserWindow;
+ getWebPreferences(): any;
}
interface Session {
diff --git a/spec-main/api-web-contents-spec.ts b/spec-main/api-web-contents-spec.ts
index 31bff4b866d..989b4b9c971 100644
--- a/spec-main/api-web-contents-spec.ts
+++ b/spec-main/api-web-contents-spec.ts
@@ -3,9 +3,10 @@ import { AddressInfo } from 'net'
import * as chaiAsPromised from 'chai-as-promised'
import * as path from 'path'
import * as http from 'http'
-import { BrowserWindow, ipcMain, webContents } from 'electron'
+import { BrowserWindow, ipcMain, webContents, session } from 'electron'
import { emittedOnce } from './events-helpers';
import { closeAllWindows } from './window-helpers';
+import { ifdescribe } from './spec-helpers';
const { expect } = chai
@@ -100,6 +101,7 @@ describe('webContents module', () => {
})
describe('webContents.print()', () => {
+ afterEach(closeAllWindows)
it('throws when invalid settings are passed', () => {
const w = new BrowserWindow({ show: false })
expect(() => {
@@ -221,4 +223,727 @@ describe('webContents module', () => {
})
})
})
+
+ describe('loadURL() promise API', () => {
+ let w: BrowserWindow
+ beforeEach(async () => {
+ w = new BrowserWindow({show: false})
+ })
+ afterEach(closeAllWindows)
+
+ it('resolves when done loading', async () => {
+ await expect(w.loadURL('about:blank')).to.eventually.be.fulfilled()
+ })
+
+ it('resolves when done loading a file URL', async () => {
+ await expect(w.loadFile(path.join(fixturesPath, 'pages', 'base-page.html'))).to.eventually.be.fulfilled()
+ })
+
+ it('rejects when failing to load a file URL', async () => {
+ await expect(w.loadURL('file:non-existent')).to.eventually.be.rejected()
+ .and.have.property('code', 'ERR_FILE_NOT_FOUND')
+ })
+
+ it('rejects when loading fails due to DNS not resolved', async () => {
+ await expect(w.loadURL('https://err.name.not.resolved')).to.eventually.be.rejected()
+ .and.have.property('code', 'ERR_NAME_NOT_RESOLVED')
+ })
+
+ it('rejects when navigation is cancelled due to a bad scheme', async () => {
+ await expect(w.loadURL('bad-scheme://foo')).to.eventually.be.rejected()
+ .and.have.property('code', 'ERR_FAILED')
+ })
+
+ it('sets appropriate error information on rejection', async () => {
+ let err
+ try {
+ await w.loadURL('file:non-existent')
+ } catch (e) {
+ err = e
+ }
+ expect(err).not.to.be.null()
+ expect(err.code).to.eql('ERR_FILE_NOT_FOUND')
+ expect(err.errno).to.eql(-6)
+ expect(err.url).to.eql(process.platform === 'win32' ? 'file://non-existent/' : 'file:///non-existent')
+ })
+
+ it('rejects if the load is aborted', async () => {
+ const s = http.createServer((req, res) => { /* never complete the request */ })
+ await new Promise(resolve => s.listen(0, '127.0.0.1', resolve))
+ const { port } = s.address() as AddressInfo
+ const p = expect(w.loadURL(`http://127.0.0.1:${port}`)).to.eventually.be.rejectedWith(Error, /ERR_ABORTED/)
+ // load a different file before the first load completes, causing the
+ // first load to be aborted.
+ await w.loadFile(path.join(fixturesPath, 'pages', 'base-page.html'))
+ await p
+ s.close()
+ })
+
+ it("doesn't reject when a subframe fails to load", async () => {
+ let resp = null as unknown as http.ServerResponse
+ const s = http.createServer((req, res) => {
+ res.writeHead(200, { 'Content-Type': 'text/html' })
+ res.write('')
+ resp = res
+ // don't end the response yet
+ })
+ await new Promise(resolve => s.listen(0, '127.0.0.1', resolve))
+ const { port } = s.address() as AddressInfo
+ const p = new Promise(resolve => {
+ w.webContents.on('did-fail-load', (event, errorCode, errorDescription, validatedURL, isMainFrame, frameProcessId, frameRoutingId) => {
+ if (!isMainFrame) {
+ resolve()
+ }
+ })
+ })
+ const main = w.loadURL(`http://127.0.0.1:${port}`)
+ await p
+ resp.end()
+ await main
+ s.close()
+ })
+
+ it("doesn't resolve when a subframe loads", async () => {
+ let resp = null as unknown as http.ServerResponse
+ const s = http.createServer((req, res) => {
+ res.writeHead(200, { 'Content-Type': 'text/html' })
+ res.write('')
+ resp = res
+ // don't end the response yet
+ })
+ await new Promise(resolve => s.listen(0, '127.0.0.1', resolve))
+ const { port } = s.address() as AddressInfo
+ const p = new Promise(resolve => {
+ w.webContents.on('did-frame-finish-load', (event, isMainFrame, frameProcessId, frameRoutingId) => {
+ if (!isMainFrame) {
+ resolve()
+ }
+ })
+ })
+ const main = w.loadURL(`http://127.0.0.1:${port}`)
+ await p
+ resp.destroy() // cause the main request to fail
+ await expect(main).to.eventually.be.rejected()
+ .and.have.property('errno', -355) // ERR_INCOMPLETE_CHUNKED_ENCODING
+ s.close()
+ })
+ })
+
+ describe('getFocusedWebContents() API', () => {
+ afterEach(closeAllWindows)
+ it('returns the focused web contents', async () => {
+ const w = new BrowserWindow({show: true})
+ await w.loadURL('about:blank')
+ expect(webContents.getFocusedWebContents().id).to.equal(w.webContents.id)
+
+ const devToolsOpened = emittedOnce(w.webContents, 'devtools-opened')
+ w.webContents.openDevTools()
+ await devToolsOpened
+ expect(webContents.getFocusedWebContents().id).to.equal(w.webContents.devToolsWebContents.id)
+ const devToolsClosed = emittedOnce(w.webContents, 'devtools-closed')
+ w.webContents.closeDevTools()
+ await devToolsClosed
+ expect(webContents.getFocusedWebContents().id).to.equal(w.webContents.id)
+ })
+
+ it('does not crash when called on a detached dev tools window', async () => {
+ const w = new BrowserWindow({show: true})
+
+ w.webContents.openDevTools({ mode: 'detach' })
+ w.webContents.inspectElement(100, 100)
+
+ // For some reason we have to wait for two focused events...?
+ await emittedOnce(w.webContents, 'devtools-focused')
+ await emittedOnce(w.webContents, 'devtools-focused')
+
+ expect(() => { webContents.getFocusedWebContents() }).to.not.throw()
+
+ // Work around https://github.com/electron/electron/issues/19985
+ await new Promise(r => setTimeout(r, 0))
+
+ const devToolsClosed = emittedOnce(w.webContents, 'devtools-closed')
+ w.webContents.closeDevTools()
+ await devToolsClosed
+ expect(() => { webContents.getFocusedWebContents() }).to.not.throw()
+ })
+ })
+
+ describe('setDevToolsWebContents() API', () => {
+ afterEach(closeAllWindows)
+ it('sets arbitrary webContents as devtools', async () => {
+ const w = new BrowserWindow({ show: false })
+ const devtools = new BrowserWindow({ show: false })
+ const promise = emittedOnce(devtools.webContents, 'dom-ready')
+ w.webContents.setDevToolsWebContents(devtools.webContents)
+ w.webContents.openDevTools()
+ await promise
+ expect(devtools.webContents.getURL().startsWith('devtools://devtools')).to.be.true()
+ const result = await devtools.webContents.executeJavaScript('InspectorFrontendHost.constructor.name')
+ expect(result).to.equal('InspectorFrontendHostImpl')
+ devtools.destroy()
+ })
+ })
+
+ describe('isFocused() API', () => {
+ it('returns false when the window is hidden', async () => {
+ const w = new BrowserWindow({ show: false })
+ await w.loadURL('about:blank')
+ expect(w.isVisible()).to.be.false()
+ expect(w.webContents.isFocused()).to.be.false()
+ })
+ })
+
+ describe('isCurrentlyAudible() API', () => {
+ afterEach(closeAllWindows)
+ it('returns whether audio is playing', async () => {
+ const w = new BrowserWindow({ show: false })
+ await w.loadURL('about:blank')
+ await w.webContents.executeJavaScript(`
+ window.context = new AudioContext
+ // Start in suspended state, because of the
+ // new web audio api policy.
+ context.suspend()
+ window.oscillator = context.createOscillator()
+ oscillator.connect(context.destination)
+ oscillator.start()
+ `)
+ let p = emittedOnce(w.webContents, '-audio-state-changed')
+ w.webContents.executeJavaScript('context.resume()')
+ await p
+ expect(w.webContents.isCurrentlyAudible()).to.be.true()
+ p = emittedOnce(w.webContents, '-audio-state-changed')
+ w.webContents.executeJavaScript('oscillator.stop()')
+ await p
+ expect(w.webContents.isCurrentlyAudible()).to.be.false()
+ })
+ })
+
+ describe('getWebPreferences() API', () => {
+ afterEach(closeAllWindows)
+ it('should not crash when called for devTools webContents', (done) => {
+ const w = new BrowserWindow({show: false})
+ w.webContents.openDevTools()
+ w.webContents.once('devtools-opened', () => {
+ expect(w.webContents.devToolsWebContents.getWebPreferences()).to.be.null()
+ done()
+ })
+ })
+ })
+
+ describe('openDevTools() API', () => {
+ afterEach(closeAllWindows)
+ it('can show window with activation', async () => {
+ const w = new BrowserWindow({show: false})
+ const focused = emittedOnce(w, 'focus')
+ w.show()
+ await focused
+ expect(w.isFocused()).to.be.true()
+ w.webContents.openDevTools({ mode: 'detach', activate: true })
+ await emittedOnce(w.webContents, 'devtools-focused')
+ await emittedOnce(w.webContents, 'devtools-focused')
+ await emittedOnce(w.webContents, 'devtools-opened')
+ await new Promise(resolve => setTimeout(resolve, 0))
+ expect(w.isFocused()).to.be.false()
+ })
+
+ it('can show window without activation', async () => {
+ const w = new BrowserWindow({show: false})
+ const devtoolsOpened = emittedOnce(w.webContents, 'devtools-opened')
+ w.webContents.openDevTools({ mode: 'detach', activate: false })
+ await devtoolsOpened
+ expect(w.webContents.isDevToolsOpened()).to.be.true()
+ })
+ })
+
+ describe('before-input-event event', () => {
+ afterEach(closeAllWindows)
+ it('can prevent document keyboard events', async () => {
+ const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } })
+ await w.loadFile(path.join(fixturesPath, 'pages', 'key-events.html'))
+ const keyDown = new Promise(resolve => {
+ ipcMain.once('keydown', (event, key) => resolve(key))
+ })
+ w.webContents.once('before-input-event', (event, input) => {
+ if ('a' === input.key) event.preventDefault()
+ })
+ w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'a' })
+ w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'b' })
+ expect(await keyDown).to.equal('b')
+ })
+
+ it('has the correct properties', async () => {
+ const w = new BrowserWindow({show: false})
+ await w.loadFile(path.join(fixturesPath, 'pages', 'base-page.html'))
+ const testBeforeInput = async (opts: any) => {
+ const modifiers = []
+ if (opts.shift) modifiers.push('shift')
+ if (opts.control) modifiers.push('control')
+ if (opts.alt) modifiers.push('alt')
+ if (opts.meta) modifiers.push('meta')
+ if (opts.isAutoRepeat) modifiers.push('isAutoRepeat')
+
+ const p = emittedOnce(w.webContents, 'before-input-event')
+ w.webContents.sendInputEvent({
+ type: opts.type,
+ keyCode: opts.keyCode,
+ modifiers: modifiers as any
+ })
+ const [, input] = await p
+
+ expect(input.type).to.equal(opts.type)
+ expect(input.key).to.equal(opts.key)
+ expect(input.code).to.equal(opts.code)
+ expect(input.isAutoRepeat).to.equal(opts.isAutoRepeat)
+ expect(input.shift).to.equal(opts.shift)
+ expect(input.control).to.equal(opts.control)
+ expect(input.alt).to.equal(opts.alt)
+ expect(input.meta).to.equal(opts.meta)
+ }
+ await testBeforeInput({
+ type: 'keyDown',
+ key: 'A',
+ code: 'KeyA',
+ keyCode: 'a',
+ shift: true,
+ control: true,
+ alt: true,
+ meta: true,
+ isAutoRepeat: true
+ })
+ await testBeforeInput({
+ type: 'keyUp',
+ key: '.',
+ code: 'Period',
+ keyCode: '.',
+ shift: false,
+ control: true,
+ alt: true,
+ meta: false,
+ isAutoRepeat: false
+ })
+ await testBeforeInput({
+ type: 'keyUp',
+ key: '!',
+ code: 'Digit1',
+ keyCode: '1',
+ shift: true,
+ control: false,
+ alt: false,
+ meta: true,
+ isAutoRepeat: false
+ })
+ await testBeforeInput({
+ type: 'keyUp',
+ key: 'Tab',
+ code: 'Tab',
+ keyCode: 'Tab',
+ shift: false,
+ control: true,
+ alt: false,
+ meta: false,
+ isAutoRepeat: true
+ })
+ })
+ })
+
+ // On Mac, zooming isn't done with the mouse wheel.
+ ifdescribe(process.platform !== 'darwin')('zoom-changed', () => {
+ afterEach(closeAllWindows)
+ it('is emitted with the correct zooming info', async () => {
+ const w = new BrowserWindow({ show: false })
+ await w.loadFile(path.join(fixturesPath, 'pages', 'base-page.html'))
+
+ const testZoomChanged = async ({ zoomingIn }: { zoomingIn: boolean }) => {
+ w.webContents.sendInputEvent({
+ type: 'mouseWheel',
+ x: 300,
+ y: 300,
+ deltaX: 0,
+ deltaY: zoomingIn ? 1 : -1,
+ wheelTicksX: 0,
+ wheelTicksY: zoomingIn ? 1 : -1,
+ modifiers: ['control', 'meta']
+ })
+
+ const [, zoomDirection] = await emittedOnce(w.webContents, 'zoom-changed')
+ expect(zoomDirection).to.equal(zoomingIn ? 'in' : 'out')
+ // Apparently we get two zoom-changed events??
+ await emittedOnce(w.webContents, 'zoom-changed')
+ }
+
+ await testZoomChanged({ zoomingIn: true })
+ await testZoomChanged({ zoomingIn: false })
+ })
+ })
+
+ describe('sendInputEvent(event)', () => {
+ let w: BrowserWindow
+ beforeEach(async () => {
+ w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } })
+ await w.loadFile(path.join(fixturesPath, 'pages', 'key-events.html'))
+ })
+ afterEach(closeAllWindows)
+
+ it('can send keydown events', (done) => {
+ ipcMain.once('keydown', (event, key, code, keyCode, shiftKey, ctrlKey, altKey) => {
+ expect(key).to.equal('a')
+ expect(code).to.equal('KeyA')
+ expect(keyCode).to.equal(65)
+ expect(shiftKey).to.be.false()
+ expect(ctrlKey).to.be.false()
+ expect(altKey).to.be.false()
+ done()
+ })
+ w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'A' })
+ })
+
+ it('can send keydown events with modifiers', (done) => {
+ ipcMain.once('keydown', (event, key, code, keyCode, shiftKey, ctrlKey, altKey) => {
+ expect(key).to.equal('Z')
+ expect(code).to.equal('KeyZ')
+ expect(keyCode).to.equal(90)
+ expect(shiftKey).to.be.true()
+ expect(ctrlKey).to.be.true()
+ expect(altKey).to.be.false()
+ done()
+ })
+ w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'Z', modifiers: ['shift', 'ctrl'] })
+ })
+
+ it('can send keydown events with special keys', (done) => {
+ ipcMain.once('keydown', (event, key, code, keyCode, shiftKey, ctrlKey, altKey) => {
+ expect(key).to.equal('Tab')
+ expect(code).to.equal('Tab')
+ expect(keyCode).to.equal(9)
+ expect(shiftKey).to.be.false()
+ expect(ctrlKey).to.be.false()
+ expect(altKey).to.be.true()
+ done()
+ })
+ w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'Tab', modifiers: ['alt'] })
+ })
+
+ it('can send char events', (done) => {
+ ipcMain.once('keypress', (event, key, code, keyCode, shiftKey, ctrlKey, altKey) => {
+ expect(key).to.equal('a')
+ expect(code).to.equal('KeyA')
+ expect(keyCode).to.equal(65)
+ expect(shiftKey).to.be.false()
+ expect(ctrlKey).to.be.false()
+ expect(altKey).to.be.false()
+ done()
+ })
+ w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'A' })
+ w.webContents.sendInputEvent({ type: 'char', keyCode: 'A' })
+ })
+
+ it('can send char events with modifiers', (done) => {
+ ipcMain.once('keypress', (event, key, code, keyCode, shiftKey, ctrlKey, altKey) => {
+ expect(key).to.equal('Z')
+ expect(code).to.equal('KeyZ')
+ expect(keyCode).to.equal(90)
+ expect(shiftKey).to.be.true()
+ expect(ctrlKey).to.be.true()
+ expect(altKey).to.be.false()
+ done()
+ })
+ w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'Z' })
+ w.webContents.sendInputEvent({ type: 'char', keyCode: 'Z', modifiers: ['shift', 'ctrl'] })
+ })
+ })
+
+ describe('insertCSS', () => {
+ afterEach(closeAllWindows)
+ it('supports inserting CSS', async () => {
+ const w = new BrowserWindow({ show: false })
+ w.loadURL('about:blank')
+ await w.webContents.insertCSS('body { background-repeat: round; }')
+ const result = await w.webContents.executeJavaScript('window.getComputedStyle(document.body).getPropertyValue("background-repeat")')
+ expect(result).to.equal('round')
+ })
+
+ it('supports removing inserted CSS', async () => {
+ const w = new BrowserWindow({ show: false })
+ w.loadURL('about:blank')
+ const key = await w.webContents.insertCSS('body { background-repeat: round; }')
+ await w.webContents.removeInsertedCSS(key)
+ const result = await w.webContents.executeJavaScript('window.getComputedStyle(document.body).getPropertyValue("background-repeat")')
+ expect(result).to.equal('repeat')
+ })
+ })
+
+ describe('inspectElement()', () => {
+ afterEach(closeAllWindows)
+ it('supports inspecting an element in the devtools', (done) => {
+ const w = new BrowserWindow({ show: false })
+ w.loadURL('about:blank')
+ w.webContents.once('devtools-opened', () => { done() })
+ w.webContents.inspectElement(10, 10)
+ })
+ })
+
+ describe('startDrag({file, icon})', () => {
+ it('throws errors for a missing file or a missing/empty icon', () => {
+ const w = new BrowserWindow({ show: false })
+ expect(() => {
+ w.webContents.startDrag({ icon: path.join(fixturesPath, 'assets', 'logo.png') } as any)
+ }).to.throw(`Must specify either 'file' or 'files' option`)
+
+ expect(() => {
+ w.webContents.startDrag({ file: __filename } as any)
+ }).to.throw(`Must specify 'icon' option`)
+
+ if (process.platform === 'darwin') {
+ expect(() => {
+ w.webContents.startDrag({ file: __filename, icon: __filename })
+ }).to.throw(`Must specify non-empty 'icon' option`)
+ }
+ })
+ })
+
+ describe('focus()', () => {
+ describe('when the web contents is hidden', () => {
+ afterEach(closeAllWindows)
+ it('does not blur the focused window', (done) => {
+ const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } })
+ ipcMain.once('answer', (event, parentFocused, childFocused) => {
+ expect(parentFocused).to.be.true()
+ expect(childFocused).to.be.false()
+ done()
+ })
+ w.show()
+ w.loadFile(path.join(fixturesPath, 'pages', 'focus-web-contents.html'))
+ })
+ })
+ })
+
+ describe('getOSProcessId()', () => {
+ afterEach(closeAllWindows)
+ it('returns a valid procress id', async () => {
+ const w = new BrowserWindow({ show: false })
+ expect(w.webContents.getOSProcessId()).to.equal(0)
+
+ await w.loadURL('about:blank')
+ expect(w.webContents.getOSProcessId()).to.be.above(0)
+ })
+ })
+
+ describe('zoom api', () => {
+ const scheme = (global as any).standardScheme
+ const hostZoomMap: Record = {
+ host1: 0.3,
+ host2: 0.7,
+ host3: 0.2
+ }
+
+ before((done) => {
+ const protocol = session.defaultSession.protocol
+ protocol.registerStringProtocol(scheme, (request, callback) => {
+ const response = ``
+ callback({ data: response, mimeType: 'text/html' })
+ }, (error) => done(error))
+ })
+
+ after((done) => {
+ const protocol = session.defaultSession.protocol
+ protocol.unregisterProtocol(scheme, (error) => done(error))
+ })
+
+ afterEach(closeAllWindows)
+
+ // TODO(codebytere): remove in Electron v8.0.0
+ it('can set the correct zoom level (functions)', async () => {
+ const w = new BrowserWindow({ show: false })
+ try {
+ await w.loadURL('about:blank')
+ const zoomLevel = w.webContents.getZoomLevel()
+ expect(zoomLevel).to.eql(0.0)
+ w.webContents.setZoomLevel(0.5)
+ const newZoomLevel = w.webContents.getZoomLevel()
+ expect(newZoomLevel).to.eql(0.5)
+ } finally {
+ w.webContents.setZoomLevel(0)
+ }
+ })
+
+ it('can set the correct zoom level', async () => {
+ const w = new BrowserWindow({ show: false })
+ try {
+ await w.loadURL('about:blank')
+ const zoomLevel = w.webContents.zoomLevel
+ expect(zoomLevel).to.eql(0.0)
+ w.webContents.zoomLevel = 0.5
+ const newZoomLevel = w.webContents.zoomLevel
+ expect(newZoomLevel).to.eql(0.5)
+ } finally {
+ w.webContents.zoomLevel = 0
+ }
+ })
+
+ it('can persist zoom level across navigation', (done) => {
+ const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } })
+ let finalNavigation = false
+ ipcMain.on('set-zoom', (e, host) => {
+ const zoomLevel = hostZoomMap[host]
+ if (!finalNavigation) w.webContents.zoomLevel = zoomLevel
+ e.sender.send(`${host}-zoom-set`)
+ })
+ ipcMain.on('host1-zoom-level', (e, zoomLevel) => {
+ const expectedZoomLevel = hostZoomMap.host1
+ expect(zoomLevel).to.equal(expectedZoomLevel)
+ if (finalNavigation) {
+ done()
+ } else {
+ w.loadURL(`${scheme}://host2`)
+ }
+ })
+ ipcMain.once('host2-zoom-level', (e, zoomLevel) => {
+ const expectedZoomLevel = hostZoomMap.host2
+ expect(zoomLevel).to.equal(expectedZoomLevel)
+ finalNavigation = true
+ w.webContents.goBack()
+ })
+ w.loadURL(`${scheme}://host1`)
+ })
+
+ it('can propagate zoom level across same session', (done) => {
+ const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } })
+ const w2 = new BrowserWindow({ show: false })
+ w2.webContents.on('did-finish-load', () => {
+ const zoomLevel1 = w.webContents.zoomLevel
+ expect(zoomLevel1).to.equal(hostZoomMap.host3)
+
+ const zoomLevel2 = w2.webContents.zoomLevel
+ expect(zoomLevel1).to.equal(zoomLevel2)
+ w2.setClosable(true)
+ w2.close()
+ done()
+ })
+ w.webContents.on('did-finish-load', () => {
+ w.webContents.zoomLevel = hostZoomMap.host3
+ w2.loadURL(`${scheme}://host3`)
+ })
+ w.loadURL(`${scheme}://host3`)
+ })
+
+ it('cannot propagate zoom level across different session', (done) => {
+ const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } })
+ const w2 = new BrowserWindow({
+ show: false,
+ webPreferences: {
+ partition: 'temp'
+ }
+ })
+ const protocol = w2.webContents.session.protocol
+ protocol.registerStringProtocol(scheme, (request, callback) => {
+ callback('hello')
+ }, (error) => {
+ if (error) return done(error)
+ w2.webContents.on('did-finish-load', () => {
+ const zoomLevel1 = w.webContents.zoomLevel
+ expect(zoomLevel1).to.equal(hostZoomMap.host3)
+
+ const zoomLevel2 = w2.webContents.zoomLevel
+ expect(zoomLevel2).to.equal(0)
+ expect(zoomLevel1).to.not.equal(zoomLevel2)
+
+ protocol.unregisterProtocol(scheme, (error) => {
+ if (error) return done(error)
+ w2.setClosable(true)
+ w2.close()
+ done()
+ })
+ })
+ w.webContents.on('did-finish-load', () => {
+ w.webContents.zoomLevel = hostZoomMap.host3
+ w2.loadURL(`${scheme}://host3`)
+ })
+ w.loadURL(`${scheme}://host3`)
+ })
+ })
+
+ it('can persist when it contains iframe', (done) => {
+ const w = new BrowserWindow({ show: false })
+ const server = http.createServer((req, res) => {
+ setTimeout(() => {
+ res.end()
+ }, 200)
+ })
+ server.listen(0, '127.0.0.1', () => {
+ const url = 'http://127.0.0.1:' + (server.address() as AddressInfo).port
+ const content = ``
+ w.webContents.on('did-frame-finish-load', (e, isMainFrame) => {
+ if (!isMainFrame) {
+ const zoomLevel = w.webContents.zoomLevel
+ expect(zoomLevel).to.equal(2.0)
+
+ w.webContents.zoomLevel = 0
+ server.close()
+ done()
+ }
+ })
+ w.webContents.on('dom-ready', () => {
+ w.webContents.zoomLevel = 2.0
+ })
+ w.loadURL(`data:text/html,${content}`)
+ })
+ })
+
+ it('cannot propagate when used with webframe', (done) => {
+ const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } })
+ let finalZoomLevel = 0
+ const w2 = new BrowserWindow({
+ show: false
+ })
+ w2.webContents.on('did-finish-load', () => {
+ const zoomLevel1 = w.webContents.zoomLevel
+ expect(zoomLevel1).to.equal(finalZoomLevel)
+
+ const zoomLevel2 = w2.webContents.zoomLevel
+ expect(zoomLevel2).to.equal(0)
+ expect(zoomLevel1).to.not.equal(zoomLevel2)
+
+ w2.setClosable(true)
+ w2.close()
+ done()
+ })
+ ipcMain.once('temporary-zoom-set', (e, zoomLevel) => {
+ w2.loadFile(path.join(fixturesPath, 'pages', 'c.html'))
+ finalZoomLevel = zoomLevel
+ })
+ w.loadFile(path.join(fixturesPath, 'pages', 'webframe-zoom.html'))
+ })
+
+ it('cannot persist zoom level after navigation with webFrame', (done) => {
+ const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } })
+ let initialNavigation = true
+ const source = `
+ const {ipcRenderer, webFrame} = require('electron')
+ webFrame.setZoomLevel(0.6)
+ ipcRenderer.send('zoom-level-set', webFrame.getZoomLevel())
+ `
+ w.webContents.on('did-finish-load', () => {
+ if (initialNavigation) {
+ w.webContents.executeJavaScript(source)
+ } else {
+ const zoomLevel = w.webContents.zoomLevel
+ expect(zoomLevel).to.equal(0)
+ done()
+ }
+ })
+ ipcMain.once('zoom-level-set', (e, zoomLevel) => {
+ expect(zoomLevel).to.equal(0.6)
+ w.loadFile(path.join(fixturesPath, 'pages', 'd.html'))
+ initialNavigation = false
+ })
+ w.loadFile(path.join(fixturesPath, 'pages', 'c.html'))
+ })
+ })
})
diff --git a/spec-main/index.js b/spec-main/index.js
index 1ad1442bbe3..772f66ec26c 100644
--- a/spec-main/index.js
+++ b/spec-main/index.js
@@ -25,8 +25,10 @@ app.on('window-all-closed', () => null)
app.commandLine.appendSwitch('ignore-certificate-errors')
global.standardScheme = 'app'
+global.zoomScheme = 'zoom'
protocol.registerSchemesAsPrivileged([
{ scheme: global.standardScheme, privileges: { standard: true, secure: true } },
+ { scheme: global.zoomScheme, privileges: { standard: true, secure: true } },
{ scheme: 'cors-blob', privileges: { corsEnabled: true, supportFetchAPI: true } },
{ scheme: 'cors', privileges: { corsEnabled: true, supportFetchAPI: true } },
{ scheme: 'no-cors', privileges: { supportFetchAPI: true } },
diff --git a/spec/api-shell-spec.js b/spec/api-shell-spec.js
index 346d3f9521b..11f266c5ffc 100644
--- a/spec/api-shell-spec.js
+++ b/spec/api-shell-spec.js
@@ -4,10 +4,12 @@ const dirtyChai = require('dirty-chai')
const fs = require('fs')
const path = require('path')
const os = require('os')
+const http = require('http')
const { shell, remote } = require('electron')
const { BrowserWindow } = remote
const { closeWindow } = require('./window-helpers')
+const { emittedOnce } = require('./events-helpers')
const { expect } = chai
chai.use(dirtyChai)
@@ -47,32 +49,34 @@ describe('shell module', () => {
}
})
- it('opens an external link', done => {
- const url = 'http://www.example.com'
+ it('opens an external link', async () => {
+ let url = 'http://127.0.0.1'
+ let requestReceived
if (process.platform === 'linux') {
process.env.BROWSER = '/bin/true'
process.env.DE = 'generic'
process.env.DISPLAY = ''
+ requestReceived = Promise.resolve()
+ } else if (process.platform === 'darwin') {
+ // On the Mac CI machines, Safari tries to ask for a password to the
+ // code signing keychain we set up to test code signing (see
+ // https://github.com/electron/electron/pull/19969#issuecomment-526278890),
+ // so use a blur event as a crude proxy.
+ w = new BrowserWindow({ show: true })
+ requestReceived = emittedOnce(w, 'blur')
+ } else {
+ const server = http.createServer((req, res) => {
+ res.end()
+ })
+ await new Promise(resolve => server.listen(0, '127.0.0.1', resolve))
+ requestReceived = new Promise(resolve => server.on('connection', () => resolve()))
+ url = `http://127.0.0.1:${server.address().port}`
}
- // Ensure an external window is activated via a new window's blur event
- w = new BrowserWindow()
- let promiseResolved = false
- let blurEventEmitted = false
-
- w.on('blur', () => {
- blurEventEmitted = true
- if (promiseResolved) {
- done()
- }
- })
-
- shell.openExternal(url).then(() => {
- promiseResolved = true
- if (blurEventEmitted || process.platform === 'linux') {
- done()
- }
- })
+ await Promise.all([
+ shell.openExternal(url),
+ requestReceived
+ ])
})
})
diff --git a/spec/api-web-contents-spec.js b/spec/api-web-contents-spec.js
index 7c556d644c6..c2ff63135a8 100644
--- a/spec/api-web-contents-spec.js
+++ b/spec/api-web-contents-spec.js
@@ -40,347 +40,6 @@ describe('webContents module', () => {
afterEach(() => closeWindow(w).then(() => { w = null }))
- describe('loadURL() promise API', () => {
- it('resolves when done loading', async () => {
- await expect(w.loadURL('about:blank')).to.eventually.be.fulfilled
- })
-
- it('resolves when done loading a file URL', async () => {
- await expect(w.loadFile(path.join(fixtures, 'pages', 'base-page.html'))).to.eventually.be.fulfilled
- })
-
- it('rejects when failing to load a file URL', async () => {
- await expect(w.loadURL('file:non-existent')).to.eventually.be.rejected
- .and.have.property('code', 'ERR_FILE_NOT_FOUND')
- })
-
- it('rejects when loading fails due to DNS not resolved', async () => {
- await expect(w.loadURL('https://err.name.not.resolved')).to.eventually.be.rejected
- .and.have.property('code', 'ERR_NAME_NOT_RESOLVED')
- })
-
- it('rejects when navigation is cancelled due to a bad scheme', async () => {
- await expect(w.loadURL('bad-scheme://foo')).to.eventually.be.rejected
- .and.have.property('code', 'ERR_FAILED')
- })
-
- it('sets appropriate error information on rejection', async () => {
- let err
- try {
- await w.loadURL('file:non-existent')
- } catch (e) {
- err = e
- }
- expect(err).not.to.be.null()
- expect(err.code).to.eql('ERR_FILE_NOT_FOUND')
- expect(err.errno).to.eql(-6)
- expect(err.url).to.eql(process.platform === 'win32' ? 'file://non-existent/' : 'file:///non-existent')
- })
-
- it('rejects if the load is aborted', async () => {
- const s = http.createServer((req, res) => { /* never complete the request */ })
- await new Promise(resolve => s.listen(0, '127.0.0.1', resolve))
- const { port } = s.address()
- const p = expect(w.loadURL(`http://127.0.0.1:${port}`)).to.eventually.be.rejectedWith(Error, /ERR_ABORTED/)
- // load a different file before the first load completes, causing the
- // first load to be aborted.
- await w.loadFile(path.join(fixtures, 'pages', 'base-page.html'))
- await p
- s.close()
- })
-
- it("doesn't reject when a subframe fails to load", async () => {
- let resp = null
- const s = http.createServer((req, res) => {
- res.writeHead(200, { 'Content-Type': 'text/html' })
- res.write('')
- resp = res
- // don't end the response yet
- })
- await new Promise(resolve => s.listen(0, '127.0.0.1', resolve))
- const { port } = s.address()
- const p = new Promise(resolve => {
- w.webContents.on('did-fail-load', (event, errorCode, errorDescription, validatedURL, isMainFrame, frameProcessId, frameRoutingId) => {
- if (!isMainFrame) {
- resolve()
- }
- })
- })
- const main = w.loadURL(`http://127.0.0.1:${port}`)
- await p
- resp.end()
- await main
- s.close()
- })
-
- it("doesn't resolve when a subframe loads", async () => {
- let resp = null
- const s = http.createServer((req, res) => {
- res.writeHead(200, { 'Content-Type': 'text/html' })
- res.write('')
- resp = res
- // don't end the response yet
- })
- await new Promise(resolve => s.listen(0, '127.0.0.1', resolve))
- const { port } = s.address()
- const p = new Promise(resolve => {
- w.webContents.on('did-frame-finish-load', (event, errorCode, errorDescription, validatedURL, isMainFrame, frameProcessId, frameRoutingId) => {
- if (!isMainFrame) {
- resolve()
- }
- })
- })
- const main = w.loadURL(`http://127.0.0.1:${port}`)
- await p
- resp.destroy() // cause the main request to fail
- await expect(main).to.eventually.be.rejected
- .and.have.property('errno', -355) // ERR_INCOMPLETE_CHUNKED_ENCODING
- s.close()
- })
- })
-
- describe('getFocusedWebContents() API', () => {
- it('returns the focused web contents', (done) => {
- if (isCi) return done()
-
- const specWebContents = remote.getCurrentWebContents()
- expect(specWebContents.id).to.equal(webContents.getFocusedWebContents().id)
-
- specWebContents.once('devtools-opened', () => {
- expect(specWebContents.devToolsWebContents.id).to.equal(webContents.getFocusedWebContents().id)
- specWebContents.closeDevTools()
- })
-
- specWebContents.once('devtools-closed', () => {
- expect(specWebContents.id).to.equal(webContents.getFocusedWebContents().id)
- done()
- })
-
- specWebContents.openDevTools()
- })
-
- it('does not crash when called on a detached dev tools window', (done) => {
- const specWebContents = w.webContents
-
- specWebContents.once('devtools-opened', () => {
- expect(() => {
- webContents.getFocusedWebContents()
- }).to.not.throw()
- specWebContents.closeDevTools()
- })
-
- specWebContents.once('devtools-closed', () => {
- expect(() => {
- webContents.getFocusedWebContents()
- }).to.not.throw()
- done()
- })
-
- specWebContents.openDevTools({ mode: 'detach' })
- w.inspectElement(100, 100)
- })
- })
-
- describe('setDevToolsWebContents() API', () => {
- it('sets arbitrary webContents as devtools', async () => {
- const devtools = new BrowserWindow({ show: false })
- const promise = emittedOnce(devtools.webContents, 'dom-ready')
- w.webContents.setDevToolsWebContents(devtools.webContents)
- w.webContents.openDevTools()
- await promise
- expect(devtools.getURL().startsWith('devtools://devtools')).to.be.true()
- const result = await devtools.webContents.executeJavaScript('InspectorFrontendHost.constructor.name')
- expect(result).to.equal('InspectorFrontendHostImpl')
- devtools.destroy()
- })
- })
-
- describe('isFocused() API', () => {
- it('returns false when the window is hidden', () => {
- BrowserWindow.getAllWindows().forEach((window) => {
- expect(!window.isVisible() && window.webContents.isFocused()).to.be.false()
- })
- })
- })
-
- describe('isCurrentlyAudible() API', () => {
- it('returns whether audio is playing', async () => {
- const webContents = remote.getCurrentWebContents()
- const context = new window.AudioContext()
- // Start in suspended state, because of the
- // new web audio api policy.
- context.suspend()
- const oscillator = context.createOscillator()
- oscillator.connect(context.destination)
- oscillator.start()
- let p = emittedOnce(webContents, '-audio-state-changed')
- await context.resume()
- await p
- expect(webContents.isCurrentlyAudible()).to.be.true()
- p = emittedOnce(webContents, '-audio-state-changed')
- oscillator.stop()
- await p
- expect(webContents.isCurrentlyAudible()).to.be.false()
- oscillator.disconnect()
- context.close()
- })
- })
-
- describe('getWebPreferences() API', () => {
- it('should not crash when called for devTools webContents', (done) => {
- w.webContents.openDevTools()
- w.webContents.once('devtools-opened', () => {
- expect(w.devToolsWebContents.getWebPreferences()).to.be.null()
- done()
- })
- })
- })
-
- describe('openDevTools() API', () => {
- it('can show window with activation', async () => {
- const focused = emittedOnce(w, 'focus')
- w.show()
- await focused
- expect(w.isFocused()).to.be.true()
- const devtoolsOpened = emittedOnce(w.webContents, 'devtools-opened')
- w.webContents.openDevTools({ mode: 'detach', activate: true })
- await devtoolsOpened
- expect(w.isFocused()).to.be.false()
- })
-
- it('can show window without activation', async () => {
- const devtoolsOpened = emittedOnce(w.webContents, 'devtools-opened')
- w.webContents.openDevTools({ mode: 'detach', activate: false })
- await devtoolsOpened
- expect(w.isDevToolsOpened()).to.be.true()
- })
- })
-
- describe('before-input-event event', () => {
- it('can prevent document keyboard events', async () => {
- await w.loadFile(path.join(fixtures, 'pages', 'key-events.html'))
- const keyDown = new Promise(resolve => {
- ipcMain.once('keydown', (event, key) => resolve(key))
- })
- ipcRenderer.sendSync('prevent-next-input-event', 'a', w.webContents.id)
- w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'a' })
- w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'b' })
- expect(await keyDown).to.equal('b')
- })
-
- it('has the correct properties', async () => {
- await w.loadFile(path.join(fixtures, 'pages', 'base-page.html'))
- const testBeforeInput = async (opts) => {
- const modifiers = []
- if (opts.shift) modifiers.push('shift')
- if (opts.control) modifiers.push('control')
- if (opts.alt) modifiers.push('alt')
- if (opts.meta) modifiers.push('meta')
- if (opts.isAutoRepeat) modifiers.push('isAutoRepeat')
-
- const p = emittedOnce(w.webContents, 'before-input-event')
- w.webContents.sendInputEvent({
- type: opts.type,
- keyCode: opts.keyCode,
- modifiers: modifiers
- })
- const [, input] = await p
-
- expect(input.type).to.equal(opts.type)
- expect(input.key).to.equal(opts.key)
- expect(input.code).to.equal(opts.code)
- expect(input.isAutoRepeat).to.equal(opts.isAutoRepeat)
- expect(input.shift).to.equal(opts.shift)
- expect(input.control).to.equal(opts.control)
- expect(input.alt).to.equal(opts.alt)
- expect(input.meta).to.equal(opts.meta)
- }
- await testBeforeInput({
- type: 'keyDown',
- key: 'A',
- code: 'KeyA',
- keyCode: 'a',
- shift: true,
- control: true,
- alt: true,
- meta: true,
- isAutoRepeat: true
- })
- await testBeforeInput({
- type: 'keyUp',
- key: '.',
- code: 'Period',
- keyCode: '.',
- shift: false,
- control: true,
- alt: true,
- meta: false,
- isAutoRepeat: false
- })
- await testBeforeInput({
- type: 'keyUp',
- key: '!',
- code: 'Digit1',
- keyCode: '1',
- shift: true,
- control: false,
- alt: false,
- meta: true,
- isAutoRepeat: false
- })
- await testBeforeInput({
- type: 'keyUp',
- key: 'Tab',
- code: 'Tab',
- keyCode: 'Tab',
- shift: false,
- control: true,
- alt: false,
- meta: false,
- isAutoRepeat: true
- })
- })
- })
-
- describe('zoom-changed', () => {
- beforeEach(function () {
- // On Mac, zooming isn't done with the mouse wheel.
- if (process.platform === 'darwin') {
- return closeWindow(w).then(() => {
- w = null
- this.skip()
- })
- }
- })
-
- it('is emitted with the correct zooming info', async () => {
- w.loadFile(path.join(fixtures, 'pages', 'base-page.html'))
- await emittedOnce(w.webContents, 'did-finish-load')
-
- const testZoomChanged = async ({ zoomingIn }) => {
- const promise = emittedOnce(w.webContents, 'zoom-changed')
-
- w.webContents.sendInputEvent({
- type: 'mousewheel',
- x: 300,
- y: 300,
- deltaX: 0,
- deltaY: zoomingIn ? 1 : -1,
- wheelTicksX: 0,
- wheelTicksY: zoomingIn ? 1 : -1,
- phase: 'began',
- modifiers: ['control', 'meta']
- })
-
- const [, zoomDirection] = await promise
- expect(zoomDirection).to.equal(zoomingIn ? 'in' : 'out')
- }
-
- await testZoomChanged({ zoomingIn: true })
- await testZoomChanged({ zoomingIn: false })
- })
- })
-
describe('devtools window', () => {
let testFn = it
if (process.platform === 'darwin' && isCi) {
@@ -439,353 +98,6 @@ describe('webContents module', () => {
})
})
- describe('sendInputEvent(event)', () => {
- beforeEach(async () => {
- await w.loadFile(path.join(fixtures, 'pages', 'key-events.html'))
- })
-
- it('can send keydown events', (done) => {
- ipcMain.once('keydown', (event, key, code, keyCode, shiftKey, ctrlKey, altKey) => {
- expect(key).to.equal('a')
- expect(code).to.equal('KeyA')
- expect(keyCode).to.equal(65)
- expect(shiftKey).to.be.false()
- expect(ctrlKey).to.be.false()
- expect(altKey).to.be.false()
- done()
- })
- w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'A' })
- })
-
- it('can send keydown events with modifiers', (done) => {
- ipcMain.once('keydown', (event, key, code, keyCode, shiftKey, ctrlKey, altKey) => {
- expect(key).to.equal('Z')
- expect(code).to.equal('KeyZ')
- expect(keyCode).to.equal(90)
- expect(shiftKey).to.be.true()
- expect(ctrlKey).to.be.true()
- expect(altKey).to.be.false()
- done()
- })
- w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'Z', modifiers: ['shift', 'ctrl'] })
- })
-
- it('can send keydown events with special keys', (done) => {
- ipcMain.once('keydown', (event, key, code, keyCode, shiftKey, ctrlKey, altKey) => {
- expect(key).to.equal('Tab')
- expect(code).to.equal('Tab')
- expect(keyCode).to.equal(9)
- expect(shiftKey).to.be.false()
- expect(ctrlKey).to.be.false()
- expect(altKey).to.be.true()
- done()
- })
- w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'Tab', modifiers: ['alt'] })
- })
-
- it('can send char events', (done) => {
- ipcMain.once('keypress', (event, key, code, keyCode, shiftKey, ctrlKey, altKey) => {
- expect(key).to.equal('a')
- expect(code).to.equal('KeyA')
- expect(keyCode).to.equal(65)
- expect(shiftKey).to.be.false()
- expect(ctrlKey).to.be.false()
- expect(altKey).to.be.false()
- done()
- })
- w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'A' })
- w.webContents.sendInputEvent({ type: 'char', keyCode: 'A' })
- })
-
- it('can send char events with modifiers', (done) => {
- ipcMain.once('keypress', (event, key, code, keyCode, shiftKey, ctrlKey, altKey) => {
- expect(key).to.equal('Z')
- expect(code).to.equal('KeyZ')
- expect(keyCode).to.equal(90)
- expect(shiftKey).to.be.true()
- expect(ctrlKey).to.be.true()
- expect(altKey).to.be.false()
- done()
- })
- w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'Z' })
- w.webContents.sendInputEvent({ type: 'char', keyCode: 'Z', modifiers: ['shift', 'ctrl'] })
- })
- })
-
- it('supports inserting CSS', async () => {
- w.loadURL('about:blank')
- await w.webContents.insertCSS('body { background-repeat: round; }')
- const result = await w.webContents.executeJavaScript('window.getComputedStyle(document.body).getPropertyValue("background-repeat")')
- expect(result).to.equal('round')
- })
-
- it('supports removing inserted CSS', async () => {
- w.loadURL('about:blank')
- const key = await w.webContents.insertCSS('body { background-repeat: round; }')
- await w.webContents.removeInsertedCSS(key)
- const result = await w.webContents.executeJavaScript('window.getComputedStyle(document.body).getPropertyValue("background-repeat")')
- expect(result).to.equal('repeat')
- })
-
- it('supports inspecting an element in the devtools', (done) => {
- w.loadURL('about:blank')
- w.webContents.once('devtools-opened', () => { done() })
- w.webContents.inspectElement(10, 10)
- })
-
- describe('startDrag({file, icon})', () => {
- it('throws errors for a missing file or a missing/empty icon', () => {
- expect(() => {
- w.webContents.startDrag({ icon: path.join(fixtures, 'assets', 'logo.png') })
- }).to.throw(`Must specify either 'file' or 'files' option`)
-
- expect(() => {
- w.webContents.startDrag({ file: __filename })
- }).to.throw(`Must specify 'icon' option`)
-
- if (process.platform === 'darwin') {
- expect(() => {
- w.webContents.startDrag({ file: __filename, icon: __filename })
- }).to.throw(`Must specify non-empty 'icon' option`)
- }
- })
- })
-
- describe('focus()', () => {
- describe('when the web contents is hidden', () => {
- it('does not blur the focused window', (done) => {
- ipcMain.once('answer', (event, parentFocused, childFocused) => {
- expect(parentFocused).to.be.true()
- expect(childFocused).to.be.false()
- done()
- })
- w.show()
- w.loadFile(path.join(fixtures, 'pages', 'focus-web-contents.html'))
- })
- })
- })
-
- describe('getOSProcessId()', () => {
- it('returns a valid procress id', async () => {
- expect(w.webContents.getOSProcessId()).to.equal(0)
-
- await w.loadURL('about:blank')
- expect(w.webContents.getOSProcessId()).to.be.above(0)
- })
- })
-
- describe('zoom api', () => {
- const zoomScheme = remote.getGlobal('zoomScheme')
- const hostZoomMap = {
- host1: 0.3,
- host2: 0.7,
- host3: 0.2
- }
-
- before((done) => {
- const protocol = session.defaultSession.protocol
- protocol.registerStringProtocol(zoomScheme, (request, callback) => {
- const response = ``
- callback({ data: response, mimeType: 'text/html' })
- }, (error) => done(error))
- })
-
- after((done) => {
- const protocol = session.defaultSession.protocol
- protocol.unregisterProtocol(zoomScheme, (error) => done(error))
- })
-
- // TODO(codebytere): remove in Electron v8.0.0
- it('can set the correct zoom level (functions)', async () => {
- try {
- await w.loadURL('about:blank')
- const zoomLevel = w.webContents.getZoomLevel()
- expect(zoomLevel).to.eql(0.0)
- w.webContents.setZoomLevel(0.5)
- const newZoomLevel = w.webContents.getZoomLevel()
- expect(newZoomLevel).to.eql(0.5)
- } finally {
- w.webContents.setZoomLevel(0)
- }
- })
-
- it('can set the correct zoom level', async () => {
- try {
- await w.loadURL('about:blank')
- const zoomLevel = w.webContents.zoomLevel
- expect(zoomLevel).to.eql(0.0)
- w.webContents.zoomLevel = 0.5
- const newZoomLevel = w.webContents.zoomLevel
- expect(newZoomLevel).to.eql(0.5)
- } finally {
- w.webContents.zoomLevel = 0
- }
- })
-
- it('can persist zoom level across navigation', (done) => {
- let finalNavigation = false
- ipcMain.on('set-zoom', (e, host) => {
- const zoomLevel = hostZoomMap[host]
- if (!finalNavigation) w.webContents.zoomLevel = zoomLevel
- console.log()
- e.sender.send(`${host}-zoom-set`)
- })
- ipcMain.on('host1-zoom-level', (e, zoomLevel) => {
- const expectedZoomLevel = hostZoomMap.host1
- expect(zoomLevel).to.equal(expectedZoomLevel)
- if (finalNavigation) {
- done()
- } else {
- w.loadURL(`${zoomScheme}://host2`)
- }
- })
- ipcMain.once('host2-zoom-level', (e, zoomLevel) => {
- const expectedZoomLevel = hostZoomMap.host2
- expect(zoomLevel).to.equal(expectedZoomLevel)
- finalNavigation = true
- w.webContents.goBack()
- })
- w.loadURL(`${zoomScheme}://host1`)
- })
-
- it('can propagate zoom level across same session', (done) => {
- const w2 = new BrowserWindow({
- show: false
- })
- w2.webContents.on('did-finish-load', () => {
- const zoomLevel1 = w.webContents.zoomLevel
- expect(zoomLevel1).to.equal(hostZoomMap.host3)
-
- const zoomLevel2 = w2.webContents.zoomLevel
- expect(zoomLevel1).to.equal(zoomLevel2)
- w2.setClosable(true)
- w2.close()
- done()
- })
- w.webContents.on('did-finish-load', () => {
- w.webContents.zoomLevel = hostZoomMap.host3
- w2.loadURL(`${zoomScheme}://host3`)
- })
- w.loadURL(`${zoomScheme}://host3`)
- })
-
- it('cannot propagate zoom level across different session', (done) => {
- const w2 = new BrowserWindow({
- show: false,
- webPreferences: {
- partition: 'temp'
- }
- })
- const protocol = w2.webContents.session.protocol
- protocol.registerStringProtocol(zoomScheme, (request, callback) => {
- callback('hello')
- }, (error) => {
- if (error) return done(error)
- w2.webContents.on('did-finish-load', () => {
- const zoomLevel1 = w.webContents.zoomLevel
- expect(zoomLevel1).to.equal(hostZoomMap.host3)
-
- const zoomLevel2 = w2.webContents.zoomLevel
- expect(zoomLevel2).to.equal(0)
- expect(zoomLevel1).to.not.equal(zoomLevel2)
-
- protocol.unregisterProtocol(zoomScheme, (error) => {
- if (error) return done(error)
- w2.setClosable(true)
- w2.close()
- done()
- })
- })
- w.webContents.on('did-finish-load', () => {
- w.webContents.zoomLevel = hostZoomMap.host3
- w2.loadURL(`${zoomScheme}://host3`)
- })
- w.loadURL(`${zoomScheme}://host3`)
- })
- })
-
- it('can persist when it contains iframe', (done) => {
- const server = http.createServer((req, res) => {
- setTimeout(() => {
- res.end()
- }, 200)
- })
- server.listen(0, '127.0.0.1', () => {
- const url = 'http://127.0.0.1:' + server.address().port
- const content = ``
- w.webContents.on('did-frame-finish-load', (e, isMainFrame) => {
- if (!isMainFrame) {
- const zoomLevel = w.webContents.zoomLevel
- expect(zoomLevel).to.equal(2.0)
-
- w.webContents.zoomLevel = 0
- server.close()
- done()
- }
- })
- w.webContents.on('dom-ready', () => {
- w.webContents.zoomLevel = 2.0
- })
- w.loadURL(`data:text/html,${content}`)
- })
- })
-
- it('cannot propagate when used with webframe', (done) => {
- let finalZoomLevel = 0
- const w2 = new BrowserWindow({
- show: false
- })
- w2.webContents.on('did-finish-load', () => {
- const zoomLevel1 = w.webContents.zoomLevel
- expect(zoomLevel1).to.equal(finalZoomLevel)
-
- const zoomLevel2 = w2.webContents.zoomLevel
- expect(zoomLevel2).to.equal(0)
- expect(zoomLevel1).to.not.equal(zoomLevel2)
-
- w2.setClosable(true)
- w2.close()
- done()
- })
- ipcMain.once('temporary-zoom-set', (e, zoomLevel) => {
- w2.loadFile(path.join(fixtures, 'pages', 'c.html'))
- finalZoomLevel = zoomLevel
- })
- w.loadFile(path.join(fixtures, 'pages', 'webframe-zoom.html'))
- })
-
- it('cannot persist zoom level after navigation with webFrame', (done) => {
- let initialNavigation = true
- const source = `
- const {ipcRenderer, webFrame} = require('electron')
- webFrame.setZoomLevel(0.6)
- ipcRenderer.send('zoom-level-set', webFrame.getZoomLevel())
- `
- w.webContents.on('did-finish-load', () => {
- if (initialNavigation) {
- w.webContents.executeJavaScript(source)
- } else {
- const zoomLevel = w.webContents.zoomLevel
- expect(zoomLevel).to.equal(0)
- done()
- }
- })
- ipcMain.once('zoom-level-set', (e, zoomLevel) => {
- expect(zoomLevel).to.equal(0.6)
- w.loadFile(path.join(fixtures, 'pages', 'd.html'))
- initialNavigation = false
- })
- w.loadFile(path.join(fixtures, 'pages', 'c.html'))
- })
- })
-
describe('webrtc ip policy api', () => {
it('can set and get webrtc ip policies', () => {
const policies = [