diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index f1d2be13f762..1048174136ab 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -70,10 +70,24 @@ Returns: * `frameProcessId` Integer * `frameRoutingId` Integer -This event is like `did-finish-load` but emitted when the load failed or was -cancelled, e.g. `window.stop()` is invoked. +This event is like `did-finish-load` but emitted when the load failed. The full list of error codes and their meaning is available [here](https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h). +#### Event: 'did-fail-provisional-load' + +Returns: + +* `event` Event +* `errorCode` Integer +* `errorDescription` String +* `validatedURL` String +* `isMainFrame` Boolean +* `frameProcessId` Integer +* `frameRoutingId` Integer + +This event is like `did-fail-load` but emitted when the load was cancelled +(e.g. `window.stop()` was invoked). + #### Event: 'did-frame-finish-load' Returns: diff --git a/docs/api/web-request.md b/docs/api/web-request.md index a17b121d32bb..55be8e2b6772 100644 --- a/docs/api/web-request.md +++ b/docs/api/web-request.md @@ -82,11 +82,11 @@ The `callback` has to be called with an `response` object. * `resourceType` String * `referrer` String * `timestamp` Double - * `requestHeaders` Object + * `requestHeaders` Record * `callback` Function * `response` Object * `cancel` Boolean (optional) - * `requestHeaders` Object (optional) - When provided, request will be made + * `requestHeaders` Record (optional) - When provided, request will be made with these headers. The `listener` will be called with `listener(details, callback)` before sending @@ -109,7 +109,7 @@ The `callback` has to be called with an `response` object. * `resourceType` String * `referrer` String * `timestamp` Double - * `requestHeaders` Object + * `requestHeaders` Record The `listener` will be called with `listener(details)` just before a request is going to be sent to the server, modifications of previous `onBeforeSendHeaders` diff --git a/spec-main/api-browser-window-spec.ts b/spec-main/api-browser-window-spec.ts new file mode 100644 index 000000000000..b51075a878df --- /dev/null +++ b/spec-main/api-browser-window-spec.ts @@ -0,0 +1,539 @@ +import * as chai from 'chai' +import * as chaiAsPromised from 'chai-as-promised' +import * as path from 'path' +import * as fs from 'fs' +import * as qs from 'querystring' +import * as http from 'http' +import { AddressInfo } from 'net' +import { app, BrowserWindow, ipcMain, OnBeforeSendHeadersListenerDetails } from 'electron' +import { emittedOnce } from './events-helpers'; +import { closeWindow } from './window-helpers'; + +const { expect } = chai + +chai.use(chaiAsPromised) + +const fixtures = path.resolve(__dirname, '..', 'spec', 'fixtures') + +describe('BrowserWindow module', () => { + describe('BrowserWindow constructor', () => { + it('allows passing void 0 as the webContents', async () => { + expect(() => { + const w = new BrowserWindow({ + show: false, + // apparently void 0 had different behaviour from undefined in the + // issue that this test is supposed to catch. + webContents: void 0 + } as any) + w.destroy() + }).not.to.throw() + }) + }) + + describe('BrowserWindow.close()', () => { + let w = null as unknown as BrowserWindow + beforeEach(() => { + w = new BrowserWindow({show: false, webPreferences: {nodeIntegration: true}}) + }) + afterEach(async () => { + await closeWindow(w) + w = null as unknown as BrowserWindow + }) + + it('should emit unload handler', async () => { + await w.loadFile(path.join(fixtures, 'api', 'unload.html')) + const closed = emittedOnce(w, 'closed') + w.close() + await closed + const test = path.join(fixtures, 'api', 'unload') + const content = fs.readFileSync(test) + fs.unlinkSync(test) + expect(String(content)).to.equal('unload') + }) + it('should emit beforeunload handler', async () => { + await w.loadFile(path.join(fixtures, 'api', 'beforeunload-false.html')) + const beforeunload = emittedOnce(w, 'onbeforeunload') + w.close() + await beforeunload + }) + + describe('when invoked synchronously inside navigation observer', () => { + let server: http.Server = null as unknown as http.Server + let url: string = null as unknown as string + + before((done) => { + server = http.createServer((request, response) => { + switch (request.url) { + case '/net-error': + response.destroy() + break + case '/301': + response.statusCode = 301 + response.setHeader('Location', '/200') + response.end() + break + case '/200': + response.statusCode = 200 + response.end('hello') + break + case '/title': + response.statusCode = 200 + response.end('Hello') + break + default: + throw new Error(`unsupported endpoint: ${request.url}`) + } + }).listen(0, '127.0.0.1', () => { + url = 'http://127.0.0.1:' + (server.address() as AddressInfo).port + done() + }) + }) + + after(() => { + server.close() + }) + + const events = [ + { name: 'did-start-loading', path: '/200' }, + { name: 'dom-ready', path: '/200' }, + { name: 'page-title-updated', path: '/title' }, + { name: 'did-stop-loading', path: '/200' }, + { name: 'did-finish-load', path: '/200' }, + { name: 'did-frame-finish-load', path: '/200' }, + { name: 'did-fail-load', path: '/net-error' } + ] + + for (const {name, path} of events) { + it(`should not crash when closed during ${name}`, async () => { + const w = new BrowserWindow({ show: false }) + w.webContents.once((name as any), () => { + w.close() + }) + const destroyed = emittedOnce(w.webContents, 'destroyed') + w.webContents.loadURL(url + path) + await destroyed + }) + } + }) + }) + + describe('window.close()', () => { + let w = null as unknown as BrowserWindow + beforeEach(() => { + w = new BrowserWindow({show: false, webPreferences: {nodeIntegration: true}}) + }) + afterEach(async () => { + await closeWindow(w) + w = null as unknown as BrowserWindow + }) + + it('should emit unload event', async () => { + w.loadFile(path.join(fixtures, 'api', 'close.html')) + await emittedOnce(w, 'closed') + const test = path.join(fixtures, 'api', 'close') + const content = fs.readFileSync(test).toString() + fs.unlinkSync(test) + expect(content).to.equal('close') + }) + it('should emit beforeunload event', async () => { + w.loadFile(path.join(fixtures, 'api', 'close-beforeunload-false.html')) + await emittedOnce(w, 'onbeforeunload') + }) + }) + + describe('BrowserWindow.destroy()', () => { + let w = null as unknown as BrowserWindow + beforeEach(() => { + w = new BrowserWindow({show: false, webPreferences: {nodeIntegration: true}}) + }) + afterEach(async () => { + await closeWindow(w) + w = null as unknown as BrowserWindow + }) + + it('prevents users to access methods of webContents', async () => { + const contents = w.webContents + w.destroy() + await new Promise(setImmediate) + expect(() => { + contents.getProcessId() + }).to.throw('Object has been destroyed') + }) + it('should not crash when destroying windows with pending events', () => { + const focusListener = () => {} + app.on('browser-window-focus', focusListener) + const windowCount = 3 + const windowOptions = { + show: false, + width: 400, + height: 400, + webPreferences: { + backgroundThrottling: false + } + } + const windows = Array.from(Array(windowCount)).map(x => new BrowserWindow(windowOptions)) + windows.forEach(win => win.show()) + windows.forEach(win => win.focus()) + windows.forEach(win => win.destroy()) + app.removeListener('browser-window-focus', focusListener) + }) + }) + + describe('BrowserWindow.loadURL(url)', () => { + let w = null as unknown as BrowserWindow + beforeEach(() => { + w = new BrowserWindow({show: false, webPreferences: {nodeIntegration: true}}) + }) + afterEach(async () => { + await closeWindow(w) + w = null as unknown as BrowserWindow + }) + let server = null as unknown as http.Server + let url = null as unknown as string + let postData = null as any + before((done) => { + const filePath = path.join(fixtures, 'pages', 'a.html') + const fileStats = fs.statSync(filePath) + postData = [ + { + type: 'rawData', + bytes: Buffer.from('username=test&file=') + }, + { + type: 'file', + filePath: filePath, + offset: 0, + length: fileStats.size, + modificationTime: fileStats.mtime.getTime() / 1000 + } + ] + server = http.createServer((req, res) => { + function respond () { + if (req.method === 'POST') { + let body = '' + req.on('data', (data) => { + if (data) body += data + }) + req.on('end', () => { + const parsedData = qs.parse(body) + fs.readFile(filePath, (err, data) => { + if (err) return + if (parsedData.username === 'test' && + parsedData.file === data.toString()) { + res.end() + } + }) + }) + } else if (req.url === '/302') { + res.setHeader('Location', '/200') + res.statusCode = 302 + res.end() + } else { + res.end() + } + } + setTimeout(respond, req.url && req.url.includes('slow') ? 200 : 0) + }) + server.listen(0, '127.0.0.1', () => { + url = `http://127.0.0.1:${(server.address() as AddressInfo).port}` + done() + }) + }) + + after(() => { + server.close() + }) + + it('should emit did-start-loading event', (done) => { + w.webContents.on('did-start-loading', () => { done() }) + w.loadURL('about:blank') + }) + it('should emit ready-to-show event', (done) => { + w.on('ready-to-show', () => { done() }) + w.loadURL('about:blank') + }) + it('should emit did-fail-load event for files that do not exist', (done) => { + w.webContents.on('did-fail-load', (event, code, desc, url, isMainFrame) => { + expect(code).to.equal(-6) + expect(desc).to.equal('ERR_FILE_NOT_FOUND') + expect(isMainFrame).to.equal(true) + done() + }) + w.loadURL('file://a.txt') + }) + it('should emit did-fail-load event for invalid URL', (done) => { + w.webContents.on('did-fail-load', (event, code, desc, url, isMainFrame) => { + expect(desc).to.equal('ERR_INVALID_URL') + expect(code).to.equal(-300) + expect(isMainFrame).to.equal(true) + done() + }) + w.loadURL('http://example:port') + }) + it('should set `mainFrame = false` on did-fail-load events in iframes', (done) => { + w.webContents.on('did-fail-load', (event, code, desc, url, isMainFrame) => { + expect(isMainFrame).to.equal(false) + done() + }) + w.loadFile(path.join(fixtures, 'api', 'did-fail-load-iframe.html')) + }) + it('does not crash in did-fail-provisional-load handler', (done) => { + w.webContents.once('did-fail-provisional-load', () => { + w.loadURL('http://127.0.0.1:11111') + done() + }) + w.loadURL('http://127.0.0.1:11111') + }) + it('should emit did-fail-load event for URL exceeding character limit', (done) => { + w.webContents.on('did-fail-load', (event, code, desc, url, isMainFrame) => { + expect(desc).to.equal('ERR_INVALID_URL') + expect(code).to.equal(-300) + expect(isMainFrame).to.equal(true) + done() + }) + const data = Buffer.alloc(2 * 1024 * 1024).toString('base64') + w.loadURL(`data:image/png;base64,${data}`) + }) + + it('should return a promise', () => { + const p = w.loadURL('about:blank') + expect(p).to.have.property('then') + }) + + it('should return a promise that resolves', async () => { + expect(w.loadURL('about:blank')).to.eventually.be.fulfilled + }) + + it('should return a promise that rejects on a load failure', async () => { + const data = Buffer.alloc(2 * 1024 * 1024).toString('base64') + const p = w.loadURL(`data:image/png;base64,${data}`) + await expect(p).to.eventually.be.rejected + }) + + it('should return a promise that resolves even if pushState occurs during navigation', async () => { + const p = w.loadURL('data:text/html,') + await expect(p).to.eventually.be.fulfilled + }) + + describe('POST navigations', () => { + afterEach(() => { w.webContents.session.webRequest.onBeforeSendHeaders(null) }) + + it('supports specifying POST data', async () => { + await w.loadURL(url, { postData }) + }) + it('sets the content type header on URL encoded forms', async () => { + await w.loadURL(url) + const requestDetails: Promise = new Promise(resolve => { + w.webContents.session.webRequest.onBeforeSendHeaders((details, callback) => { + resolve(details) + }) + }) + w.webContents.executeJavaScript(` + form = document.createElement('form') + document.body.appendChild(form) + form.method = 'POST' + form.submit() + `) + const details = await requestDetails + expect(details.requestHeaders['Content-Type']).to.equal('application/x-www-form-urlencoded') + }) + it('sets the content type header on multi part forms', async () => { + await w.loadURL(url) + const requestDetails: Promise = new Promise(resolve => { + w.webContents.session.webRequest.onBeforeSendHeaders((details, callback) => { + resolve(details) + }) + }) + w.webContents.executeJavaScript(` + form = document.createElement('form') + document.body.appendChild(form) + form.method = 'POST' + form.enctype = 'multipart/form-data' + file = document.createElement('input') + file.type = 'file' + file.name = 'file' + form.appendChild(file) + form.submit() + `) + const details = await requestDetails + expect(details.requestHeaders['Content-Type'].startsWith('multipart/form-data; boundary=----WebKitFormBoundary')).to.equal(true) + }) + }) + + it('should support support base url for data urls', (done) => { + ipcMain.once('answer', (event, test) => { + expect(test).to.equal('test') + done() + }) + w.loadURL('data:text/html,', { baseURLForDataURL: `file://${path.join(fixtures, 'api')}${path.sep}` }) + }) + }) + + describe('navigation events', () => { + let w = null as unknown as BrowserWindow + beforeEach(() => { + w = new BrowserWindow({show: false, webPreferences: {nodeIntegration: true}}) + }) + afterEach(async () => { + await closeWindow(w) + w = null as unknown as BrowserWindow + }) + + describe('will-navigate event', () => { + it('allows the window to be closed from the event listener', (done) => { + w.webContents.once('will-navigate', () => { + w.close() + done() + }) + w.loadFile(path.join(fixtures, 'pages', 'will-navigate.html')) + }) + }) + + describe('will-redirect event', () => { + let server = null as unknown as http.Server + let url = null as unknown as string + before((done) => { + server = http.createServer((req, res) => { + if (req.url === '/302') { + res.setHeader('Location', '/200') + res.statusCode = 302 + res.end() + } else if (req.url === '/navigate-302') { + res.end(``) + } else { + res.end() + } + }) + server.listen(0, '127.0.0.1', () => { + url = `http://127.0.0.1:${(server.address() as AddressInfo).port}` + done() + }) + }) + + after(() => { + server.close() + }) + it('is emitted on redirects', (done) => { + w.webContents.on('will-redirect', (event, url) => { + done() + }) + w.loadURL(`${url}/302`) + }) + + it('is emitted after will-navigate on redirects', (done) => { + let navigateCalled = false + w.webContents.on('will-navigate', () => { + navigateCalled = true + }) + w.webContents.on('will-redirect', (event, url) => { + expect(navigateCalled).to.equal(true, 'should have called will-navigate first') + done() + }) + w.loadURL(`${url}/navigate-302`) + }) + + it('is emitted before did-stop-loading on redirects', (done) => { + let stopCalled = false + w.webContents.on('did-stop-loading', () => { + stopCalled = true + }) + w.webContents.on('will-redirect', (event, url) => { + expect(stopCalled).to.equal(false, 'should not have called did-stop-loading first') + done() + }) + w.loadURL(`${url}/302`) + }) + + it('allows the window to be closed from the event listener', (done) => { + w.webContents.once('will-redirect', (event, input) => { + w.close() + done() + }) + w.loadURL(`${url}/302`) + }) + + it.skip('can be prevented', (done) => { + w.webContents.once('will-redirect', (event) => { + event.preventDefault() + }) + w.webContents.on('will-navigate', (e, url) => { + expect(url).to.equal(`${url}/302`) + }) + w.webContents.on('did-stop-loading', () => { + expect(w.webContents.getURL()).to.equal( + `${url}/navigate-302`, + 'url should not have changed after navigation event' + ) + done() + }) + w.webContents.on('will-redirect', (e, url) => { + expect(url).to.equal(`${url}/200`) + }) + w.loadURL(`${url}/navigate-302`) + }) + }) + }) + + describe('BrowserWindow.show()', () => { + let w = null as unknown as BrowserWindow + beforeEach(() => { + w = new BrowserWindow({show: false, webPreferences: {nodeIntegration: true}}) + }) + afterEach(async () => { + await closeWindow(w) + w = null as unknown as BrowserWindow + }) + it('should focus on window', () => { + w.show() + if (process.platform === 'darwin' && !isCI) { + // on CI, the Electron window will be the only one open, so it'll get + // focus. on not-CI, some other window will have focus, and we don't + // steal focus any more, so we expect isFocused to be false. + expect(w.isFocused()).to.equal(false) + } else { + expect(w.isFocused()).to.equal(true) + } + }) + it('should make the window visible', () => { + w.show() + expect(w.isVisible()).to.equal(true) + }) + it('emits when window is shown', (done) => { + w.once('show', () => { + expect(w.isVisible()).to.equal(true) + done() + }) + w.show() + }) + }) + + describe('BrowserWindow.hide()', () => { + let w = null as unknown as BrowserWindow + beforeEach(() => { + w = new BrowserWindow({show: false, webPreferences: {nodeIntegration: true}}) + }) + afterEach(async () => { + await closeWindow(w) + w = null as unknown as BrowserWindow + }) + it('should defocus on window', () => { + w.hide() + expect(w.isFocused()).to.equal(false) + }) + it('should make the window not visible', () => { + w.show() + w.hide() + expect(w.isVisible()).to.equal(false) + }) + it('emits when window is hidden', async () => { + const shown = emittedOnce(w, 'show') + w.show() + await shown + const hidden = emittedOnce(w, 'hide') + w.hide() + await hidden + expect(w.isVisible()).to.equal(false) + }) + }) + +}) diff --git a/spec-main/window-helpers.ts b/spec-main/window-helpers.ts index bce64bbec9b8..0ad282022795 100644 --- a/spec-main/window-helpers.ts +++ b/spec-main/window-helpers.ts @@ -30,9 +30,12 @@ export const closeWindow = async ( if (assertNotWindows) { const windows = BrowserWindow.getAllWindows() - for (const win of windows) { - await ensureWindowIsClosed(win) + try { + expect(windows).to.have.lengthOf(0) + } finally { + for (const win of windows) { + await ensureWindowIsClosed(win) + } } - expect(windows).to.have.lengthOf(0) } } diff --git a/spec/api-browser-window-spec.js b/spec/api-browser-window-spec.js index 02de0e306046..31fdc1abad08 100644 --- a/spec/api-browser-window-spec.js +++ b/spec/api-browser-window-spec.js @@ -113,374 +113,6 @@ describe('BrowserWindow module', () => { afterEach(closeTheWindow) - describe('BrowserWindow constructor', () => { - it('allows passing void 0 as the webContents', async () => { - await openTheWindow({ - webContents: void 0 - }) - }) - }) - - describe('BrowserWindow.close()', () => { - let server - - before((done) => { - server = http.createServer((request, response) => { - switch (request.url) { - case '/404': - response.statusCode = '404' - response.end() - break - case '/301': - response.statusCode = '301' - response.setHeader('Location', '/200') - response.end() - break - case '/200': - response.statusCode = '200' - response.end('hello') - break - case '/title': - response.statusCode = '200' - response.end('Hello') - break - default: - done('unsupported endpoint') - } - }).listen(0, '127.0.0.1', () => { - server.url = 'http://127.0.0.1:' + server.address().port - done() - }) - }) - - after(() => { - server.close() - server = null - }) - - it('should emit unload handler', (done) => { - w.webContents.once('did-finish-load', () => { w.close() }) - w.once('closed', () => { - const test = path.join(fixtures, 'api', 'unload') - const content = fs.readFileSync(test) - fs.unlinkSync(test) - expect(String(content)).to.equal('unload') - done() - }) - w.loadFile(path.join(fixtures, 'api', 'unload.html')) - }) - it('should emit beforeunload handler', (done) => { - w.once('onbeforeunload', () => { done() }) - w.webContents.once('did-finish-load', () => { w.close() }) - w.loadFile(path.join(fixtures, 'api', 'beforeunload-false.html')) - }) - it('should not crash when invoked synchronously inside navigation observer', (done) => { - const events = [ - { name: 'did-start-loading', url: `${server.url}/200` }, - { name: 'dom-ready', url: `${server.url}/200` }, - { name: 'page-title-updated', url: `${server.url}/title` }, - { name: 'did-stop-loading', url: `${server.url}/200` }, - { name: 'did-finish-load', url: `${server.url}/200` }, - { name: 'did-frame-finish-load', url: `${server.url}/200` }, - { name: 'did-fail-load', url: `${server.url}/404` } - ] - const responseEvent = 'window-webContents-destroyed' - - function * genNavigationEvent () { - let eventOptions = null - while ((eventOptions = events.shift()) && events.length) { - const w = new BrowserWindow({ show: false }) - eventOptions.id = w.id - eventOptions.responseEvent = responseEvent - ipcRenderer.send('test-webcontents-navigation-observer', eventOptions) - yield 1 - } - } - - const gen = genNavigationEvent() - ipcRenderer.on(responseEvent, () => { - if (!gen.next().value) done() - }) - gen.next() - }) - }) - - describe('window.close()', () => { - it('should emit unload handler', (done) => { - w.once('closed', () => { - const test = path.join(fixtures, 'api', 'close') - const content = fs.readFileSync(test) - fs.unlinkSync(test) - expect(String(content)).to.equal('close') - done() - }) - w.loadFile(path.join(fixtures, 'api', 'close.html')) - }) - it('should emit beforeunload handler', (done) => { - w.once('onbeforeunload', () => { done() }) - w.loadFile(path.join(fixtures, 'api', 'close-beforeunload-false.html')) - }) - }) - - describe('BrowserWindow.destroy()', () => { - it('prevents users to access methods of webContents', () => { - const contents = w.webContents - w.destroy() - expect(() => { - contents.getProcessId() - }).to.throw('Object has been destroyed') - }) - it('should not crash when destroying windows with pending events', (done) => { - const responseEvent = 'destroy-test-completed' - ipcRenderer.on(responseEvent, () => done()) - ipcRenderer.send('test-browserwindow-destroy', { responseEvent }) - }) - }) - - describe('BrowserWindow.loadURL(url)', () => { - it('should emit did-start-loading event', (done) => { - w.webContents.on('did-start-loading', () => { done() }) - w.loadURL('about:blank') - }) - it('should emit ready-to-show event', (done) => { - w.on('ready-to-show', () => { done() }) - w.loadURL('about:blank') - }) - it('should emit did-fail-load event for files that do not exist', (done) => { - w.webContents.on('did-fail-load', (event, code, desc, url, isMainFrame) => { - expect(code).to.equal(-6) - expect(desc).to.equal('ERR_FILE_NOT_FOUND') - expect(isMainFrame).to.be.true() - done() - }) - w.loadURL('file://a.txt') - }) - it('should emit did-fail-load event for invalid URL', (done) => { - w.webContents.on('did-fail-load', (event, code, desc, url, isMainFrame) => { - expect(desc).to.equal('ERR_INVALID_URL') - expect(code).to.equal(-300) - expect(isMainFrame).to.be.true() - done() - }) - w.loadURL('http://example:port') - }) - it('should set `mainFrame = false` on did-fail-load events in iframes', (done) => { - w.webContents.on('did-fail-load', (event, code, desc, url, isMainFrame) => { - expect(isMainFrame).to.be.false() - done() - }) - w.loadFile(path.join(fixtures, 'api', 'did-fail-load-iframe.html')) - }) - it('does not crash in did-fail-provisional-load handler', (done) => { - w.webContents.once('did-fail-provisional-load', () => { - w.loadURL('http://127.0.0.1:11111') - done() - }) - w.loadURL('http://127.0.0.1:11111') - }) - it('should emit did-fail-load event for URL exceeding character limit', (done) => { - w.webContents.on('did-fail-load', (event, code, desc, url, isMainFrame) => { - expect(desc).to.equal('ERR_INVALID_URL') - expect(code).to.equal(-300) - expect(isMainFrame).to.be.true() - done() - }) - const data = Buffer.alloc(2 * 1024 * 1024).toString('base64') - w.loadURL(`data:image/png;base64,${data}`) - }) - - it('should return a promise', () => { - const p = w.loadURL('about:blank') - expect(p).to.have.property('then') - }) - - it('should return a promise that resolves', async () => { - expect(w.loadURL('about:blank')).to.eventually.be.fulfilled() - }) - - it('should return a promise that rejects on a load failure', async () => { - const data = Buffer.alloc(2 * 1024 * 1024).toString('base64') - const p = w.loadURL(`data:image/png;base64,${data}`) - await expect(p).to.eventually.be.rejected() - }) - - it('should return a promise that resolves even if pushState occurs during navigation', async () => { - const data = Buffer.alloc(2 * 1024 * 1024).toString('base64') - const p = w.loadURL('data:text/html,') - await expect(p).to.eventually.be.fulfilled() - }) - - describe('POST navigations', () => { - afterEach(() => { w.webContents.session.webRequest.onBeforeSendHeaders(null) }) - - it('supports specifying POST data', async () => { - await w.loadURL(server.url, { postData: postData }) - }) - it('sets the content type header on URL encoded forms', async () => { - await w.loadURL(server.url) - const requestDetails = new Promise(resolve => { - w.webContents.session.webRequest.onBeforeSendHeaders((details, callback) => { - resolve(details) - }) - }) - w.webContents.executeJavaScript(` - form = document.createElement('form') - document.body.appendChild(form) - form.method = 'POST' - form.target = '_blank' - form.submit() - `) - const details = await requestDetails - expect(details.requestHeaders['content-type']).to.equal('application/x-www-form-urlencoded') - }) - it('sets the content type header on multi part forms', async () => { - await w.loadURL(server.url) - const requestDetails = new Promise(resolve => { - w.webContents.session.webRequest.onBeforeSendHeaders((details, callback) => { - resolve(details) - }) - }) - w.webContents.executeJavaScript(` - form = document.createElement('form') - document.body.appendChild(form) - form.method = 'POST' - form.target = '_blank' - form.enctype = 'multipart/form-data' - file = document.createElement('input') - file.type = 'file' - file.name = 'file' - form.appendChild(file) - form.submit() - `) - const details = await requestDetails - expect(details.requestHeaders['content-type'].startsWith('multipart/form-data; boundary=----WebKitFormBoundary')).to.be.true() - }) - }) - - it('should support support base url for data urls', (done) => { - ipcMain.once('answer', (event, test) => { - expect(test).to.equal('test') - done() - }) - w.loadURL('data:text/html,', { baseURLForDataURL: `file://${path.join(fixtures, 'api')}${path.sep}` }) - }) - }) - - describe('will-navigate event', () => { - it('allows the window to be closed from the event listener', (done) => { - ipcRenderer.send('close-on-will-navigate', w.id) - ipcRenderer.once('closed-on-will-navigate', () => { done() }) - w.loadFile(path.join(fixtures, 'pages', 'will-navigate.html')) - }) - }) - - describe('will-redirect event', () => { - it('is emitted on redirects', (done) => { - w.webContents.on('will-redirect', (event, url) => { - done() - }) - w.loadURL(`${server.url}/302`) - }) - - it('is emitted after will-navigate on redirects', (done) => { - let navigateCalled = false - w.webContents.on('will-navigate', () => { - navigateCalled = true - }) - w.webContents.on('will-redirect', (event, url) => { - expect(navigateCalled).to.equal(true, 'should have called will-navigate first') - done() - }) - w.loadURL(`${server.url}/navigate-302`) - }) - - it('is emitted before did-stop-loading on redirects', (done) => { - let stopCalled = false - w.webContents.on('did-stop-loading', () => { - stopCalled = true - }) - w.webContents.on('will-redirect', (event, url) => { - expect(stopCalled).to.equal(false, 'should not have called did-stop-loading first') - done() - }) - w.loadURL(`${server.url}/302`) - }) - - it('allows the window to be closed from the event listener', (done) => { - ipcRenderer.send('close-on-will-redirect', w.id) - ipcRenderer.once('closed-on-will-redirect', () => { done() }) - w.loadURL(`${server.url}/302`) - }) - - it.skip('can be prevented', (done) => { - ipcRenderer.send('prevent-will-redirect', w.id) - w.webContents.on('will-navigate', (e, url) => { - expect(url).to.equal(`${server.url}/302`) - }) - w.webContents.on('did-stop-loading', () => { - expect(w.webContents.getURL()).to.equal( - `${server.url}/navigate-302`, - 'url should not have changed after navigation event' - ) - done() - }) - w.webContents.on('will-redirect', (e, url) => { - expect(url).to.equal(`${server.url}/200`) - }) - w.loadURL(`${server.url}/navigate-302`) - }) - }) - - describe('BrowserWindow.show()', () => { - before(function () { - if (isCI) { - this.skip() - } - }) - - it('should focus on window', () => { - w.show() - expect(w.isFocused()).to.be.true() - }) - it('should make the window visible', () => { - w.show() - expect(w.isVisible()).to.be.true() - }) - it('emits when window is shown', (done) => { - w.once('show', () => { - expect(w.isVisible()).to.be.true() - done() - }) - w.show() - }) - }) - - describe('BrowserWindow.hide()', () => { - before(function () { - if (isCI) { - this.skip() - } - }) - - it('should defocus on window', () => { - w.hide() - expect(w.isFocused()).to.be.false() - }) - it('should make the window not visible', () => { - w.show() - w.hide() - expect(w.isVisible()).to.be.false() - }) - it('emits when window is hidden', (done) => { - w.show() - w.once('hide', () => { - expect(w.isVisible()).to.be.false() - done() - }) - w.hide() - }) - }) - describe('BrowserWindow.showInactive()', () => { it('should not focus on window', () => { w.showInactive() diff --git a/spec/static/main.js b/spec/static/main.js index 3db0956b7ab3..2b7c39d27b30 100644 --- a/spec/static/main.js +++ b/spec/static/main.js @@ -232,31 +232,6 @@ ipcMain.on('set-client-certificate-option', function (event, skip) { event.returnValue = 'done' }) -ipcMain.on('close-on-will-navigate', (event, id) => { - const contents = event.sender - const window = BrowserWindow.fromId(id) - window.webContents.once('will-navigate', (event, input) => { - window.close() - contents.send('closed-on-will-navigate') - }) -}) - -ipcMain.on('close-on-will-redirect', (event, id) => { - const contents = event.sender - const window = BrowserWindow.fromId(id) - window.webContents.once('will-redirect', (event, input) => { - window.close() - contents.send('closed-on-will-redirect') - }) -}) - -ipcMain.on('prevent-will-redirect', (event, id) => { - const window = BrowserWindow.fromId(id) - window.webContents.once('will-redirect', (event) => { - event.preventDefault() - }) -}) - ipcMain.on('create-window-with-options-cycle', (event) => { // This can't be done over remote since cycles are already // nulled out at the IPC layer @@ -347,47 +322,6 @@ ipcMain.on('crash-service-pid', (event, pid) => { event.returnValue = null }) -ipcMain.on('test-webcontents-navigation-observer', (event, options) => { - let contents = null - let destroy = () => {} - if (options.id) { - const w = BrowserWindow.fromId(options.id) - contents = w.webContents - destroy = () => w.close() - } else { - contents = webContents.create() - destroy = () => contents.destroy() - } - - contents.once(options.name, () => destroy()) - - contents.once('destroyed', () => { - event.sender.send(options.responseEvent) - }) - - contents.loadURL(options.url) -}) - -ipcMain.on('test-browserwindow-destroy', (event, testOptions) => { - const focusListener = (event, win) => win.id - app.on('browser-window-focus', focusListener) - const windowCount = 3 - const windowOptions = { - show: false, - width: 400, - height: 400, - webPreferences: { - backgroundThrottling: false - } - } - const windows = Array.from(Array(windowCount)).map(x => new BrowserWindow(windowOptions)) - windows.forEach(win => win.show()) - windows.forEach(win => win.focus()) - windows.forEach(win => win.destroy()) - app.removeListener('browser-window-focus', focusListener) - event.sender.send(testOptions.responseEvent) -}) - // Suspend listeners until the next event and then restore them const suspendListeners = (emitter, eventName, callback) => { const listeners = emitter.listeners(eventName)