diff --git a/spec-main/api-session-spec.js b/spec-main/api-session-spec.js index 0f029017973b..ff9023a15b7c 100644 --- a/spec-main/api-session-spec.js +++ b/spec-main/api-session-spec.js @@ -9,7 +9,7 @@ const ChildProcess = require('child_process') const { closeWindow } = require('./window-helpers') const { emittedOnce } = require('./events-helpers') -const { session, BrowserWindow, net } = require('electron') +const { session, BrowserWindow, net, ipcMain } = require('electron') const { expect } = chai /* The whole session API doesn't use standard callbacks */ @@ -26,7 +26,8 @@ describe('session module', () => { width: 400, height: 400, webPreferences: { - nodeIntegration: true + nodeIntegration: true, + webviewTag: true, } }) }) @@ -62,23 +63,18 @@ describe('session module', () => { const name = '0' const value = '0' - it('should get cookies', (done) => { + it('should get cookies', async () => { const server = http.createServer((req, res) => { res.setHeader('Set-Cookie', [`${name}=${value}`]) res.end('finished') server.close() }) - server.listen(0, '127.0.0.1', () => { - w.webContents.once('did-finish-load', async () => { - const list = await w.webContents.session.cookies.get({ url }) - const cookie = list.find(cookie => cookie.name === name) - - expect(cookie).to.exist.and.to.have.property('value', value) - done() - }) - const { port } = server.address() - w.loadURL(`${url}:${port}`) - }) + await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)) + const { port } = server.address() + await w.loadURL(`${url}:${port}`) + const list = await w.webContents.session.cookies.get({ url }) + const cookie = list.find(cookie => cookie.name === name) + expect(cookie).to.exist.and.to.have.property('value', value) }) it('sets cookies', async () => { @@ -92,17 +88,12 @@ describe('session module', () => { }) it('yields an error when setting a cookie with missing required fields', async () => { - let error - try { + await expect((async () => { const { cookies } = session.defaultSession const name = '1' const value = '1' await cookies.set({ url: '', name, value }) - } catch (e) { - error = e - } - expect(error).is.an('Error') - expect(error).to.have.property('message').which.equals('Failed to get cookie domain') + })()).to.eventually.be.rejectedWith('Failed to get cookie domain') }) it('should overwrite previous cookies', async () => { @@ -173,7 +164,7 @@ describe('session module', () => { }) describe('ses.cookies.flushStore()', async () => { - describe('flushes the cookies to disk', async () => { + it('flushes the cookies to disk', async () => { const name = 'foo' const value = 'bar' const { cookies } = session.defaultSession @@ -185,29 +176,26 @@ describe('session module', () => { it('should survive an app restart for persistent partition', async () => { const appPath = path.join(fixtures, 'api', 'cookie-app') - const electronPath = process.execPath - const test = (result, phase) => { + const runAppWithPhase = (phase) => { return new Promise((resolve, reject) => { let output = '' const appProcess = ChildProcess.spawn( - electronPath, + process.execPath, [appPath], { env: { PHASE: phase, ...process.env } } ) appProcess.stdout.on('data', data => { output += data }) appProcess.stdout.on('end', () => { - output = output.replace(/(\r\n|\n|\r)/gm, '') - expect(output).to.equal(result) - resolve() + resolve(output.replace(/(\r\n|\n|\r)/gm, '')) }) }) } - await test('011', 'one') - await test('110', 'two') + expect(await runAppWithPhase('one')).to.equal('011') + expect(await runAppWithPhase('two')).to.equal('110') }) }) @@ -228,16 +216,7 @@ describe('session module', () => { }) describe('will-download event', () => { - beforeEach(() => { - if (w != null) w.destroy() - w = new BrowserWindow({ - show: false, - width: 400, - height: 400 - }) - }) - - it('can cancel default download behavior', (done) => { + it('can cancel default download behavior', async () => { const mockFile = Buffer.alloc(1024) const contentDisposition = 'inline; filename="mockFile.txt"' const downloadServer = http.createServer((req, res) => { @@ -249,22 +228,23 @@ describe('session module', () => { res.end(mockFile) downloadServer.close() }) + await new Promise(resolve => downloadServer.listen(0, '127.0.0.1', resolve)) - downloadServer.listen(0, '127.0.0.1', () => { - const port = downloadServer.address().port - const url = `http://127.0.0.1:${port}/` + const port = downloadServer.address().port + const url = `http://127.0.0.1:${port}/` + const downloadPrevented = new Promise(resolve => { w.webContents.session.once('will-download', function (e, item) { e.preventDefault() - expect(item.getURL()).to.equal(url) - expect(item.getFilename()).to.equal('mockFile.txt') - setImmediate(() => { - expect(() => item.getURL()).to.throw('Object has been destroyed') - done() - }) + resolve(item) }) - w.loadURL(url) }) + w.loadURL(url) + const item = await downloadPrevented + expect(item.getURL()).to.equal(url) + expect(item.getFilename()).to.equal('mockFile.txt') + await new Promise(setImmediate) + expect(() => item.getURL()).to.throw('Object has been destroyed') }) }) @@ -277,7 +257,7 @@ describe('session module', () => { callback({ data: 'test', mimeType: 'text/html' }) } - beforeEach((done) => { + beforeEach(async () => { if (w != null) w.destroy() w = new BrowserWindow({ show: false, @@ -286,13 +266,11 @@ describe('session module', () => { } }) customSession = session.fromPartition(partitionName) - customSession.protocol.registerStringProtocol(protocolName, handler, (error) => { - done(error != null ? error : undefined) - }) + await customSession.protocol.registerStringProtocol(protocolName, handler) }) - afterEach((done) => { - customSession.protocol.unregisterProtocol(protocolName, () => done()) + afterEach(async () => { + await customSession.protocol.unregisterProtocol(protocolName) customSession = null }) @@ -314,13 +292,13 @@ describe('session module', () => { let server = null let customSession = null - beforeEach((done) => { + beforeEach(async () => { customSession = session.fromPartition('proxyconfig') // FIXME(deepak1556): This is just a hack to force // creation of request context which in turn initializes // the network context, can be removed with network // service enabled. - customSession.clearHostResolverCache().then(() => done()) + await customSession.clearHostResolverCache() }) afterEach(() => { @@ -362,19 +340,11 @@ describe('session module', () => { }) res.end(pac) }) - return new Promise((resolve, reject) => { - server.listen(0, '127.0.0.1', async () => { - try { - const config = { pacScript: `http://127.0.0.1:${server.address().port}` } - await customSession.setProxy(config) - const proxy = await customSession.resolveProxy('https://google.com') - expect(proxy).to.equal('PROXY myproxy:8132') - resolve() - } catch (error) { - reject(error) - } - }) - }) + await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)) + const config = { pacScript: `http://127.0.0.1:${server.address().port}` } + await customSession.setProxy(config) + const proxy = await customSession.resolveProxy('https://google.com') + expect(proxy).to.equal('PROXY myproxy:8132') }) it('allows bypassing proxy settings', async () => { @@ -389,24 +359,14 @@ describe('session module', () => { }) describe('ses.getBlobData()', () => { + const scheme = 'cors-blob' + const protocol = session.defaultSession.protocol + const url = `${scheme}://host` + after(async () => { + await protocol.unregisterProtocol(scheme) + }) + it('returns blob data for uuid', (done) => { - const scheme = 'cors-blob' - const protocol = session.defaultSession.protocol - const url = `${scheme}://host` - before(() => { - if (w != null) w.destroy() - w = new BrowserWindow({ show: false }) - }) - - after((done) => { - protocol.unregisterProtocol(scheme, () => { - closeWindow(w).then(() => { - w = null - done() - }) - }) - }) - const postData = JSON.stringify({ type: 'blob', value: 'hello' @@ -460,26 +420,23 @@ describe('session module', () => { server.listen(0, '127.0.0.1', done) }) - afterEach(() => { + afterEach((done) => { session.defaultSession.setCertificateVerifyProc(null) - server.close() + server.close(done) }) - it('accepts the request when the callback is called with 0', (done) => { + it('accepts the request when the callback is called with 0', async () => { session.defaultSession.setCertificateVerifyProc(({ hostname, certificate, verificationResult, errorCode }, callback) => { expect(['net::ERR_CERT_AUTHORITY_INVALID', 'net::ERR_CERT_COMMON_NAME_INVALID'].includes(verificationResult)).to.be.true expect([-202, -200].includes(errorCode)).to.be.true callback(0) }) - w.webContents.once('did-finish-load', () => { - expect(w.webContents.getTitle()).to.equal('hello') - done() - }) - w.loadURL(`https://127.0.0.1:${server.address().port}`) + await w.loadURL(`https://127.0.0.1:${server.address().port}`) + expect(w.webContents.getTitle()).to.equal('hello') }) - it('rejects the request when the callback is called with -2', (done) => { + it('rejects the request when the callback is called with -2', async () => { session.defaultSession.setCertificateVerifyProc(({ hostname, certificate, verificationResult }, callback) => { expect(hostname).to.equal('127.0.0.1') expect(certificate.issuerName).to.equal('Intermediate CA') @@ -496,11 +453,8 @@ describe('session module', () => { }) const url = `https://127.0.0.1:${server.address().port}` - w.webContents.once('did-finish-load', () => { - expect(w.webContents.getTitle()).to.equal(url) - done() - }) - w.loadURL(url) + await expect(w.loadURL(url)).to.eventually.be.rejectedWith(/ERR_FAILED/) + expect(w.webContents.getTitle()).to.equal(url) }) }) @@ -557,4 +511,292 @@ describe('session module', () => { }) }) }) + + describe('DownloadItem', () => { + const mockPDF = Buffer.alloc(1024 * 1024 * 5) + const downloadFilePath = path.join(__dirname, '..', 'fixtures', 'mock.pdf') + const protocolName = 'custom-dl' + const contentDisposition = 'inline; filename="mock.pdf"' + let address = null + let downloadServer = null + before(async () => { + downloadServer = http.createServer((req, res) => { + address = downloadServer.address() + res.writeHead(200, { + 'Content-Length': mockPDF.length, + 'Content-Type': 'application/pdf', + 'Content-Disposition': req.url === '/?testFilename' ? 'inline' : contentDisposition + }) + res.end(mockPDF) + }) + await new Promise(resolve => downloadServer.listen(0, '127.0.0.1', resolve)) + }) + after(async () => { + await new Promise(resolve => downloadServer.close(resolve)) + }) + + const isPathEqual = (path1, path2) => { + return path.relative(path1, path2) === '' + } + const assertDownload = (state, item, isCustom = false) => { + expect(state).to.equal('completed') + expect(item.getFilename()).to.equal('mock.pdf') + expect(path.isAbsolute(item.getSavePath())).to.equal(true) + expect(isPathEqual(item.getSavePath(), downloadFilePath)).to.equal(true) + if (isCustom) { + expect(item.getURL()).to.equal(`${protocolName}://item`) + } else { + expect(item.getURL()).to.be.equal(`${url}:${address.port}/`) + } + expect(item.getMimeType()).to.equal('application/pdf') + expect(item.getReceivedBytes()).to.equal(mockPDF.length) + expect(item.getTotalBytes()).to.equal(mockPDF.length) + expect(item.getContentDisposition()).to.equal(contentDisposition) + expect(fs.existsSync(downloadFilePath)).to.equal(true) + fs.unlinkSync(downloadFilePath) + } + + it('can download using WebContents.downloadURL', (done) => { + const port = downloadServer.address().port + w.webContents.session.once('will-download', function (e, item) { + item.setSavePath(downloadFilePath) + item.on('done', function (e, state) { + assertDownload(state, item) + done() + }) + }) + w.webContents.downloadURL(`${url}:${port}`) + }) + + it('can download from custom protocols using WebContents.downloadURL', (done) => { + const protocol = session.defaultSession.protocol + const port = downloadServer.address().port + const handler = (ignoredError, callback) => { + callback({ url: `${url}:${port}` }) + } + protocol.registerHttpProtocol(protocolName, handler, (error) => { + if (error) return done(error) + w.webContents.session.once('will-download', function (e, item) { + item.setSavePath(downloadFilePath) + item.on('done', function (e, state) { + assertDownload(state, item, true) + done() + }) + }) + w.webContents.downloadURL(`${protocolName}://item`) + }) + }) + + it('can download using WebView.downloadURL', async () => { + const port = downloadServer.address().port + await w.loadURL('about:blank') + function webviewDownload({fixtures, url, port}) { + const webview = new WebView() + webview.addEventListener('did-finish-load', () => { + webview.downloadURL(`${url}:${port}/`) + }) + webview.src = `file://${fixtures}/api/blank.html` + document.body.appendChild(webview) + } + const done = new Promise(resolve => { + w.webContents.session.once('will-download', function (e, item) { + item.setSavePath(downloadFilePath) + item.on('done', function (e, state) { + resolve([state, item]) + }) + }) + }) + await w.webContents.executeJavaScript(`(${webviewDownload})(${JSON.stringify({fixtures, url, port})})`) + const [state, item] = await done + assertDownload(state, item) + }) + + it('can cancel download', (done) => { + const port = downloadServer.address().port + w.webContents.session.once('will-download', function (e, item) { + item.setSavePath(downloadFilePath) + item.on('done', function (e, state) { + expect(state).to.equal('cancelled') + expect(item.getFilename()).to.equal('mock.pdf') + expect(item.getMimeType()).to.equal('application/pdf') + expect(item.getReceivedBytes()).to.equal(0) + expect(item.getTotalBytes()).to.equal(mockPDF.length) + expect(item.getContentDisposition()).to.equal(contentDisposition) + done() + }) + item.cancel() + }) + w.webContents.downloadURL(`${url}:${port}/`) + }) + + it('can generate a default filename', function (done) { + if (process.env.APPVEYOR === 'True') { + // FIXME(alexeykuzmin): Skip the test. + // this.skip() + return done() + } + + const port = downloadServer.address().port + w.webContents.session.once('will-download', function (e, item) { + item.setSavePath(downloadFilePath) + item.on('done', function (e, state) { + expect(item.getFilename()).to.equal('download.pdf') + done() + }) + item.cancel() + }) + w.webContents.downloadURL(`${url}:${port}/?testFilename`) + }) + + it('can set options for the save dialog', (done) => { + const filePath = path.join(__dirname, 'fixtures', 'mock.pdf') + const port = downloadServer.address().port + const options = { + window: null, + title: 'title', + message: 'message', + buttonLabel: 'buttonLabel', + nameFieldLabel: 'nameFieldLabel', + defaultPath: '/', + filters: [{ + name: '1', extensions: ['.1', '.2'] + }, { + name: '2', extensions: ['.3', '.4', '.5'] + }], + showsTagField: true, + securityScopedBookmarks: true + } + + w.webContents.session.once('will-download', function (e, item) { + item.setSavePath(filePath) + item.setSaveDialogOptions(options) + item.on('done', function (e, state) { + expect(item.getSaveDialogOptions()).to.deep.equal(options) + done() + }) + item.cancel() + }) + w.webContents.downloadURL(`${url}:${port}`) + }) + + describe('when a save path is specified and the URL is unavailable', () => { + it('does not display a save dialog and reports the done state as interrupted', (done) => { + w.webContents.session.once('will-download', function (e, item) { + item.setSavePath(downloadFilePath) + if (item.getState() === 'interrupted') { + item.resume() + } + item.on('done', function (e, state) { + expect(state).to.equal('interrupted') + done() + }) + }) + w.webContents.downloadURL(`file://${path.join(__dirname, 'does-not-exist.txt')}`) + }) + }) + }) + + describe('ses.createInterruptedDownload(options)', () => { + it('can create an interrupted download item', (done) => { + const downloadFilePath = path.join(__dirname, '..', 'fixtures', 'mock.pdf') + const options = { + path: downloadFilePath, + urlChain: ['http://127.0.0.1/'], + mimeType: 'application/pdf', + offset: 0, + length: 5242880 + } + w.webContents.session.once('will-download', function (e, item) { + expect(item.getState()).to.equal('interrupted') + item.cancel() + expect(item.getURLChain()).to.deep.equal(options.urlChain) + expect(item.getMimeType()).to.equal(options.mimeType) + expect(item.getReceivedBytes()).to.equal(options.offset) + expect(item.getTotalBytes()).to.equal(options.length) + expect(item.getSavePath()).to.equal(downloadFilePath) + done() + }) + w.webContents.session.createInterruptedDownload(options) + }) + + it('can be resumed', async () => { + const downloadFilePath = path.join(fixtures, 'logo.png') + const rangeServer = http.createServer((req, res) => { + const options = { root: fixtures } + send(req, req.url, options) + .on('error', (error) => { done(error) }).pipe(res) + }) + try { + await new Promise(resolve => rangeServer.listen(0, '127.0.0.1', resolve)) + const port = rangeServer.address().port + const downloadCancelled = new Promise((resolve) => { + w.webContents.session.once('will-download', function (e, item) { + item.setSavePath(downloadFilePath) + item.on('done', function (e, state) { + resolve(item) + }) + item.cancel() + }) + }) + const downloadUrl = `http://127.0.0.1:${port}/assets/logo.png` + w.webContents.downloadURL(downloadUrl) + const item = await downloadCancelled + expect(item.getState()).to.equal('cancelled') + + const options = { + path: item.getSavePath(), + urlChain: item.getURLChain(), + mimeType: item.getMimeType(), + offset: item.getReceivedBytes(), + length: item.getTotalBytes(), + lastModified: item.getLastModifiedTime(), + eTag: item.getETag(), + } + const downloadResumed = new Promise((resolve) => { + w.webContents.session.once('will-download', function (e, item) { + expect(item.getState()).to.equal('interrupted') + item.setSavePath(downloadFilePath) + item.resume() + item.on('done', function (e, state) { + resolve(item) + }) + }) + }) + w.webContents.session.createInterruptedDownload(options) + const completedItem = await downloadResumed + expect(completedItem.getState()).to.equal('completed') + expect(completedItem.getFilename()).to.equal('logo.png') + expect(completedItem.getSavePath()).to.equal(downloadFilePath) + expect(completedItem.getURL()).to.equal(downloadUrl) + expect(completedItem.getMimeType()).to.equal('image/png') + expect(completedItem.getReceivedBytes()).to.equal(14022) + expect(completedItem.getTotalBytes()).to.equal(14022) + expect(fs.existsSync(downloadFilePath)).to.equal(true) + } finally { + rangeServer.close() + } + }) + }) + + describe('ses.setPermissionRequestHandler(handler)', () => { + it('cancels any pending requests when cleared', async () => { + const ses = w.webContents.session + ses.setPermissionRequestHandler(() => { + ses.setPermissionRequestHandler(null) + }) + + const result = emittedOnce(require('electron').ipcMain, 'message') + + function remote() { + navigator.requestMIDIAccess({sysex: true}).then(() => {}, (err) => { + require('electron').ipcRenderer.send('message', err.name); + }); + } + + await w.loadURL(`data:text/html,`) + + const [,name] = await result + expect(name).to.deep.equal('SecurityError') + }) + }) }) diff --git a/spec/api-session-spec.js b/spec/api-session-spec.js deleted file mode 100644 index 0926fe75c8f5..000000000000 --- a/spec/api-session-spec.js +++ /dev/null @@ -1,331 +0,0 @@ -const chai = require('chai') -const http = require('http') -const https = require('https') -const path = require('path') -const fs = require('fs') -const send = require('send') -const auth = require('basic-auth') -const ChildProcess = require('child_process') -const { closeWindow } = require('./window-helpers') - -const { ipcRenderer, remote } = require('electron') -const { ipcMain, session, BrowserWindow, net } = remote -const { expect } = chai - -/* The whole session API doesn't use standard callbacks */ -/* eslint-disable standard/no-callback-literal */ - -describe('session module', () => { - const fixtures = path.resolve(__dirname, 'fixtures') - let w = null - let webview = null - const url = 'http://127.0.0.1' - - beforeEach(() => { - w = new BrowserWindow({ - show: false, - width: 400, - height: 400, - webPreferences: { - nodeIntegration: true - } - }) - }) - - afterEach(() => { - if (webview != null) { - if (!document.body.contains(webview)) { - document.body.appendChild(webview) - } - webview.remove() - } - - return closeWindow(w).then(() => { w = null }) - }) - - describe('DownloadItem', () => { - const mockPDF = Buffer.alloc(1024 * 1024 * 5) - const protocolName = 'custom-dl' - let contentDisposition = 'inline; filename="mock.pdf"' - const downloadFilePath = path.join(fixtures, 'mock.pdf') - const downloadServer = http.createServer((req, res) => { - if (req.url === '/?testFilename') contentDisposition = 'inline' - res.writeHead(200, { - 'Content-Length': mockPDF.length, - 'Content-Type': 'application/pdf', - 'Content-Disposition': contentDisposition - }) - res.end(mockPDF) - downloadServer.close() - }) - - const isPathEqual = (path1, path2) => { - return path.relative(path1, path2) === '' - } - const assertDownload = (event, state, url, mimeType, - receivedBytes, totalBytes, disposition, - filename, port, savePath, isCustom) => { - expect(state).to.equal('completed') - expect(filename).to.equal('mock.pdf') - expect(path.isAbsolute(savePath)).to.be.true() - expect(isPathEqual(savePath, path.join(__dirname, 'fixtures', 'mock.pdf'))).to.be.true() - if (isCustom) { - expect(url).to.be.equal(`${protocolName}://item`) - } else { - expect(url).to.be.equal(`http://127.0.0.1:${port}/`) - } - expect(mimeType).to.equal('application/pdf') - expect(receivedBytes).to.equal(mockPDF.length) - expect(totalBytes).to.equal(mockPDF.length) - expect(disposition).to.equal(contentDisposition) - expect(fs.existsSync(downloadFilePath)).to.be.true() - fs.unlinkSync(downloadFilePath) - } - - it('can download using WebContents.downloadURL', (done) => { - downloadServer.listen(0, '127.0.0.1', () => { - const port = downloadServer.address().port - ipcRenderer.sendSync('set-download-option', false, false) - w.webContents.downloadURL(`${url}:${port}`) - ipcRenderer.once('download-done', (event, state, url, - mimeType, receivedBytes, - totalBytes, disposition, - filename, savePath) => { - assertDownload(event, state, url, mimeType, receivedBytes, - totalBytes, disposition, filename, port, savePath) - done() - }) - }) - }) - - it('can download from custom protocols using WebContents.downloadURL', (done) => { - const protocol = session.defaultSession.protocol - downloadServer.listen(0, '127.0.0.1', () => { - const port = downloadServer.address().port - const handler = (ignoredError, callback) => { - callback({ url: `${url}:${port}` }) - } - protocol.registerHttpProtocol(protocolName, handler, (error) => { - if (error) return done(error) - ipcRenderer.sendSync('set-download-option', false, false) - w.webContents.downloadURL(`${protocolName}://item`) - ipcRenderer.once('download-done', (event, state, url, - mimeType, receivedBytes, - totalBytes, disposition, - filename, savePath) => { - assertDownload(event, state, url, mimeType, receivedBytes, - totalBytes, disposition, filename, port, savePath, - true) - done() - }) - }) - }) - }) - - it('can download using WebView.downloadURL', (done) => { - downloadServer.listen(0, '127.0.0.1', () => { - const port = downloadServer.address().port - ipcRenderer.sendSync('set-download-option', false, false) - webview = new WebView() - webview.addEventListener('did-finish-load', () => { - webview.downloadURL(`${url}:${port}/`) - }) - webview.src = `file://${fixtures}/api/blank.html` - ipcRenderer.once('download-done', (event, state, url, - mimeType, receivedBytes, - totalBytes, disposition, - filename, savePath) => { - assertDownload(event, state, url, mimeType, receivedBytes, - totalBytes, disposition, filename, port, savePath) - document.body.removeChild(webview) - done() - }) - document.body.appendChild(webview) - }) - }) - - it('can cancel download', (done) => { - downloadServer.listen(0, '127.0.0.1', () => { - const port = downloadServer.address().port - ipcRenderer.sendSync('set-download-option', true, false) - w.webContents.downloadURL(`${url}:${port}/`) - ipcRenderer.once('download-done', (event, state, url, - mimeType, receivedBytes, - totalBytes, disposition, - filename) => { - expect(state).to.equal('cancelled') - expect(filename).to.equal('mock.pdf') - expect(mimeType).to.equal('application/pdf') - expect(receivedBytes).to.equal(0) - expect(totalBytes).to.equal(mockPDF.length) - expect(disposition).to.equal(contentDisposition) - done() - }) - }) - }) - - it('can generate a default filename', function (done) { - if (process.env.APPVEYOR === 'True') { - // FIXME(alexeykuzmin): Skip the test. - // this.skip() - return done() - } - - downloadServer.listen(0, '127.0.0.1', () => { - const port = downloadServer.address().port - ipcRenderer.sendSync('set-download-option', true, false) - w.webContents.downloadURL(`${url}:${port}/?testFilename`) - ipcRenderer.once('download-done', (event, state, url, - mimeType, receivedBytes, - totalBytes, disposition, - filename) => { - expect(state).to.equal('cancelled') - expect(filename).to.equal('download.pdf') - expect(mimeType).to.equal('application/pdf') - expect(receivedBytes).to.equal(0) - expect(totalBytes).to.equal(mockPDF.length) - expect(disposition).to.equal(contentDisposition) - done() - }) - }) - }) - - it('can set options for the save dialog', (done) => { - downloadServer.listen(0, '127.0.0.1', () => { - const filePath = path.join(__dirname, 'fixtures', 'mock.pdf') - const port = downloadServer.address().port - const options = { - window: null, - title: 'title', - message: 'message', - buttonLabel: 'buttonLabel', - nameFieldLabel: 'nameFieldLabel', - defaultPath: '/', - filters: [{ - name: '1', extensions: ['.1', '.2'] - }, { - name: '2', extensions: ['.3', '.4', '.5'] - }], - showsTagField: true, - securityScopedBookmarks: true - } - - ipcRenderer.sendSync('set-download-option', true, false, filePath, options) - w.webContents.downloadURL(`${url}:${port}`) - ipcRenderer.once('download-done', (event, state, url, - mimeType, receivedBytes, - totalBytes, disposition, - filename, savePath, dialogOptions) => { - expect(dialogOptions).to.deep.equal(options) - done() - }) - }) - }) - - describe('when a save path is specified and the URL is unavailable', () => { - it('does not display a save dialog and reports the done state as interrupted', (done) => { - ipcRenderer.sendSync('set-download-option', false, false) - ipcRenderer.once('download-done', (event, state) => { - expect(state).to.equal('interrupted') - done() - }) - w.webContents.downloadURL(`file://${path.join(__dirname, 'does-not-exist.txt')}`) - }) - }) - }) - - describe('ses.createInterruptedDownload(options)', () => { - it('can create an interrupted download item', (done) => { - ipcRenderer.sendSync('set-download-option', true, false) - const filePath = path.join(__dirname, 'fixtures', 'mock.pdf') - const options = { - path: filePath, - urlChain: ['http://127.0.0.1/'], - mimeType: 'application/pdf', - offset: 0, - length: 5242880 - } - w.webContents.session.createInterruptedDownload(options) - ipcRenderer.once('download-created', (event, state, urlChain, - mimeType, receivedBytes, - totalBytes, filename, - savePath) => { - expect(state).to.equal('interrupted') - expect(urlChain).to.deep.equal(['http://127.0.0.1/']) - expect(mimeType).to.equal('application/pdf') - expect(receivedBytes).to.equal(0) - expect(totalBytes).to.equal(5242880) - expect(savePath).to.equal(filePath) - done() - }) - }) - - it('can be resumed', (done) => { - const fixtures = path.join(__dirname, 'fixtures') - const downloadFilePath = path.join(fixtures, 'logo.png') - const rangeServer = http.createServer((req, res) => { - const options = { root: fixtures } - send(req, req.url, options) - .on('error', (error) => { done(error) }).pipe(res) - }) - ipcRenderer.sendSync('set-download-option', true, false, downloadFilePath) - rangeServer.listen(0, '127.0.0.1', () => { - const port = rangeServer.address().port - const downloadUrl = `http://127.0.0.1:${port}/assets/logo.png` - const callback = (event, state, url, mimeType, - receivedBytes, totalBytes, disposition, - filename, savePath, dialogOptions, urlChain, - lastModifiedTime, eTag) => { - if (state === 'cancelled') { - const options = { - path: savePath, - urlChain: urlChain, - mimeType: mimeType, - offset: receivedBytes, - length: totalBytes, - lastModified: lastModifiedTime, - eTag: eTag - } - ipcRenderer.sendSync('set-download-option', false, false, downloadFilePath) - w.webContents.session.createInterruptedDownload(options) - } else { - expect(state).to.equal('completed') - expect(filename).to.equal('logo.png') - expect(savePath).to.equal(downloadFilePath) - expect(url).to.equal(downloadUrl) - expect(mimeType).to.equal('image/png') - expect(receivedBytes).to.equal(14022) - expect(totalBytes).to.equal(14022) - expect(fs.existsSync(downloadFilePath)).to.be.true() - fs.unlinkSync(downloadFilePath) - rangeServer.close() - ipcRenderer.removeListener('download-done', callback) - done() - } - } - ipcRenderer.on('download-done', callback) - w.webContents.downloadURL(downloadUrl) - }) - }) - }) - - describe('ses.setPermissionRequestHandler(handler)', () => { - it('cancels any pending requests when cleared', (done) => { - const ses = session.fromPartition('permissionTest') - ses.setPermissionRequestHandler(() => { - ses.setPermissionRequestHandler(null) - }) - - webview = new WebView() - webview.addEventListener('ipc-message', (e) => { - expect(e.channel).to.equal('message') - expect(e.args).to.deep.equal(['SecurityError']) - done() - }) - webview.src = `file://${fixtures}/pages/permissions/midi-sysex.html` - webview.partition = 'permissionTest' - webview.setAttribute('nodeintegration', 'on') - document.body.appendChild(webview) - }) - }) -}) diff --git a/spec/chromium-spec.js b/spec/chromium-spec.js index 1e2f3bc97f5b..70ea9bb6a963 100644 --- a/spec/chromium-spec.js +++ b/spec/chromium-spec.js @@ -1278,6 +1278,11 @@ describe('chromium feature', () => { it('should download a pdf when plugins are disabled', (done) => { this.createBrowserWindow({ plugins: false, preload: 'preload-pdf-loaded.js' }) + // NOTE(nornagon): this test has been skipped for ages, so there's no way + // to refactor it confidently. The 'set-download-option' ipc was removed + // around May 2019, so if you're working on the pdf viewer and arrive at + // this test and want to know what 'set-download-option' did, look here: + // https://github.com/electron/electron/blob/d87b3ead760ae2d20f2401a8dac4ce548f8cd5f5/spec/static/main.js#L164 ipcRenderer.sendSync('set-download-option', false, false) ipcRenderer.once('download-done', (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename) => { expect(state).to.equal('completed') diff --git a/spec/static/main.js b/spec/static/main.js index b724f4dd882b..3db0956b7ab3 100644 --- a/spec/static/main.js +++ b/spec/static/main.js @@ -158,58 +158,6 @@ app.on('ready', function () { process.exit(1) }) - // For session's download test, listen 'will-download' event in browser, and - // reply the result to renderer for verifying - const downloadFilePath = path.join(__dirname, '..', 'fixtures', 'mock.pdf') - ipcMain.on('set-download-option', function (event, needCancel, preventDefault, filePath = downloadFilePath, dialogOptions = {}) { - window.webContents.session.once('will-download', function (e, item) { - window.webContents.send('download-created', - item.getState(), - item.getURLChain(), - item.getMimeType(), - item.getReceivedBytes(), - item.getTotalBytes(), - item.getFilename(), - item.getSavePath()) - if (preventDefault) { - e.preventDefault() - const url = item.getURL() - const filename = item.getFilename() - setImmediate(function () { - try { - item.getURL() - } catch (err) { - window.webContents.send('download-error', url, filename, err.message) - } - }) - } else { - if (item.getState() === 'interrupted' && !needCancel) { - item.resume() - } else { - item.setSavePath(filePath) - item.setSaveDialogOptions(dialogOptions) - } - item.on('done', function (e, state) { - window.webContents.send('download-done', - state, - item.getURL(), - item.getMimeType(), - item.getReceivedBytes(), - item.getTotalBytes(), - item.getContentDisposition(), - item.getFilename(), - item.getSavePath(), - item.getSaveDialogOptions(), - item.getURLChain(), - item.getLastModifiedTime(), - item.getETag()) - }) - if (needCancel) item.cancel() - } - }) - event.returnValue = 'done' - }) - ipcMain.on('prevent-next-input-event', (event, key, id) => { webContents.fromId(id).once('before-input-event', (event, input) => { if (key === input.key) event.preventDefault()