diff --git a/spec/api-ipc-main-spec.js b/spec/api-ipc-main-spec.js new file mode 100644 index 000000000000..1a501fc6d6c5 --- /dev/null +++ b/spec/api-ipc-main-spec.js @@ -0,0 +1,87 @@ +'use strict' + +const assert = require('assert') +const http = require('http') +const path = require('path') +const {closeWindow} = require('./window-helpers') + +const {ipcRenderer, remote} = require('electron') +const {ipcMain, webContents, BrowserWindow} = remote + +const comparePaths = (path1, path2) => { + if (process.platform === 'win32') { + path1 = path1.toLowerCase() + path2 = path2.toLowerCase() + } + assert.equal(path1, path2) +} + +describe.only('ipc main module', () => { + const fixtures = path.join(__dirname, 'fixtures') + + let w = null + + afterEach(() => closeWindow(w).then(() => { w = null })) + + describe('ipc.sendSync', () => { + afterEach(() => { ipcMain.removeAllListeners('send-sync-message') }) + + it('does not crash when reply is not sent and browser is destroyed', (done) => { + w = new BrowserWindow({ show: false }) + ipcMain.once('send-sync-message', (event) => { + event.returnValue = null + done() + }) + w.loadURL(`file://${path.join(fixtures, 'api', 'send-sync-message.html')}`) + }) + + it('does not crash when reply is sent by multiple listeners', (done) => { + w = new BrowserWindow({ show: false }) + ipcMain.on('send-sync-message', (event) => { + event.returnValue = null + }) + ipcMain.on('send-sync-message', (event) => { + event.returnValue = null + done() + }) + w.loadURL(`file://${path.join(fixtures, 'api', 'send-sync-message.html')}`) + }) + }) + + describe('remote listeners', () => { + it('can be added and removed correctly', () => { + w = new BrowserWindow({ show: false }) + const listener = () => {} + + w.on('test', listener) + assert.equal(w.listenerCount('test'), 1) + w.removeListener('test', listener) + assert.equal(w.listenerCount('test'), 0) + }) + }) + + it('throws an error when removing all the listeners', () => { + ipcMain.on('test-event', () => {}) + assert.equal(ipcMain.listenerCount('test-event'), 1) + + assert.throws(() => { + ipcMain.removeAllListeners() + }, /Removing all listeners from ipcMain will make Electron internals stop working/) + + ipcMain.removeAllListeners('test-event') + assert.equal(ipcMain.listenerCount('test-event'), 0) + }) + + describe('remote objects registry', () => { + it('does not dereference until the render view is deleted (regression)', (done) => { + w = new BrowserWindow({ show: false }) + + ipcMain.once('error-message', (event, message) => { + assert(message.startsWith('Cannot call function \'getURL\' on missing remote object'), message) + done() + }) + + w.loadURL(`file://${path.join(fixtures, 'api', 'render-view-deleted.html')}`) + }) + }) +}) diff --git a/spec/api-ipc-renderer-spec.js b/spec/api-ipc-renderer-spec.js new file mode 100644 index 000000000000..db5980f2371c --- /dev/null +++ b/spec/api-ipc-renderer-spec.js @@ -0,0 +1,199 @@ +'use strict' + +const assert = require('assert') +const http = require('http') +const path = require('path') +const {closeWindow} = require('./window-helpers') + +const {ipcRenderer, remote} = require('electron') +const {ipcMain, webContents, BrowserWindow} = remote + +const comparePaths = (path1, path2) => { + if (process.platform === 'win32') { + path1 = path1.toLowerCase() + path2 = path2.toLowerCase() + } + assert.equal(path1, path2) +} + +describe.only('ipc renderer module', () => { + const fixtures = path.join(__dirname, 'fixtures') + + let w = null + + afterEach(() => closeWindow(w).then(() => { w = null })) + + describe('ipc.sender.send', () => { + it('should work when sending an object containing id property', (done) => { + const obj = { + id: 1, + name: 'ly' + } + ipcRenderer.once('message', function (event, message) { + assert.deepEqual(message, obj) + done() + }) + ipcRenderer.send('message', obj) + }) + + it('can send instances of Date', (done) => { + const currentDate = new Date() + ipcRenderer.once('message', function (event, value) { + assert.equal(value, currentDate.toISOString()) + done() + }) + ipcRenderer.send('message', currentDate) + }) + + it('can send instances of Buffer', (done) => { + const buffer = Buffer.from('hello') + ipcRenderer.once('message', function (event, message) { + assert.ok(buffer.equals(message)) + done() + }) + ipcRenderer.send('message', buffer) + }) + + it('can send objects with DOM class prototypes', (done) => { + ipcRenderer.once('message', function (event, value) { + assert.equal(value.protocol, 'file:') + assert.equal(value.hostname, '') + done() + }) + ipcRenderer.send('message', document.location) + }) + + it('can send Electron API objects', (done) => { + const webContents = remote.getCurrentWebContents() + ipcRenderer.once('message', function (event, value) { + assert.deepEqual(value.browserWindowOptions, webContents.browserWindowOptions) + done() + }) + ipcRenderer.send('message', webContents) + }) + + it('does not crash on external objects (regression)', (done) => { + const request = http.request({port: 5000, hostname: '127.0.0.1', method: 'GET', path: '/'}) + const stream = request.agent.sockets['127.0.0.1:5000:'][0]._handle._externalStream + request.on('error', () => {}) + ipcRenderer.once('message', function (event, requestValue, externalStreamValue) { + assert.equal(requestValue.method, 'GET') + assert.equal(requestValue.path, '/') + assert.equal(externalStreamValue, null) + done() + }) + + ipcRenderer.send('message', request, stream) + }) + + it('can send objects that both reference the same object', (done) => { + const child = {hello: 'world'} + const foo = {name: 'foo', child: child} + const bar = {name: 'bar', child: child} + const array = [foo, bar] + + ipcRenderer.once('message', function (event, arrayValue, fooValue, barValue, childValue) { + assert.deepEqual(arrayValue, array) + assert.deepEqual(fooValue, foo) + assert.deepEqual(barValue, bar) + assert.deepEqual(childValue, child) + done() + }) + ipcRenderer.send('message', array, foo, bar, child) + }) + + it('inserts null for cyclic references', (done) => { + const array = [5] + array.push(array) + + const child = {hello: 'world'} + child.child = child + + ipcRenderer.once('message', function (event, arrayValue, childValue) { + assert.equal(arrayValue[0], 5) + assert.equal(arrayValue[1], null) + + assert.equal(childValue.hello, 'world') + assert.equal(childValue.child, null) + + done() + }) + ipcRenderer.send('message', array, child) + }) + }) + + describe('ipc.sendSync', () => { + afterEach(() => { + ipcMain.removeAllListeners('send-sync-message') + }) + + it('can be replied by setting event.returnValue', () => { + const msg = ipcRenderer.sendSync('echo', 'test') + assert.equal(msg, 'test') + }) + }) + + describe('ipcRenderer.sendTo', () => { + let contents = null + + beforeEach(() => { contents = webContents.create({}) }) + + afterEach(() => { + ipcRenderer.removeAllListeners('pong') + contents.destroy() + contents = null + }) + + it('sends message to WebContents', (done) => { + const webContentsId = remote.getCurrentWebContents().id + + ipcRenderer.once('pong', function (event, id) { + assert.equal(webContentsId, id) + done() + }) + + contents.once('did-finish-load', () => { + ipcRenderer.sendTo(contents.id, 'ping', webContentsId) + }) + + contents.loadURL(`file://${path.join(fixtures, 'pages', 'ping-pong.html')}`) + }) + }) + + describe('remote listeners', () => { + it('detaches listeners subscribed to destroyed renderers, and shows a warning', (done) => { + w = new BrowserWindow({ show: false }) + + w.webContents.once('did-finish-load', () => { + w.webContents.once('did-finish-load', () => { + const expectedMessage = [ + 'Attempting to call a function in a renderer window that has been closed or released.', + 'Function provided here: remote-event-handler.html:11:33', + 'Remote event names: remote-handler, other-remote-handler' + ].join('\n') + const results = ipcRenderer.sendSync('try-emit-web-contents-event', w.webContents.id, 'remote-handler') + assert.deepEqual(results, { + warningMessage: expectedMessage, + listenerCountBefore: 2, + listenerCountAfter: 1 + }) + done() + }) + w.webContents.reload() + }) + w.loadURL(`file://${path.join(fixtures, 'api', 'remote-event-handler.html')}`) + }) + }) + + it('throws an error when removing all the listeners', () => { + ipcRenderer.on('test-event', () => {}) + assert.equal(ipcRenderer.listenerCount('test-event'), 1) + + assert.throws(() => { + ipcRenderer.removeAllListeners() + }, /Removing all listeners from ipcRenderer will make Electron internals stop working/) + + ipcRenderer.removeAllListeners('test-event') + assert.equal(ipcRenderer.listenerCount('test-event'), 0) + }) +}) diff --git a/spec/api-ipc-spec.js b/spec/api-remote-spec.js similarity index 61% rename from spec/api-ipc-spec.js rename to spec/api-remote-spec.js index b7b233adc3db..1044f2a59a25 100644 --- a/spec/api-ipc-spec.js +++ b/spec/api-remote-spec.js @@ -5,8 +5,7 @@ const http = require('http') const path = require('path') const {closeWindow} = require('./window-helpers') -const {ipcRenderer, remote} = require('electron') -const {ipcMain, webContents, BrowserWindow} = remote +const {remote} = require('electron') const comparePaths = (path1, path2) => { if (process.platform === 'win32') { @@ -16,7 +15,7 @@ const comparePaths = (path1, path2) => { assert.equal(path1, path2) } -describe('ipc module', () => { +describe.only('remote module', () => { const fixtures = path.join(__dirname, 'fixtures') let w = null @@ -372,237 +371,5 @@ describe('ipc module', () => { assert.equal(method(), 'method') }) }) - - describe('ipc.sender.send', () => { - it('should work when sending an object containing id property', (done) => { - const obj = { - id: 1, - name: 'ly' - } - ipcRenderer.once('message', function (event, message) { - assert.deepEqual(message, obj) - done() - }) - ipcRenderer.send('message', obj) - }) - - it('can send instances of Date', (done) => { - const currentDate = new Date() - ipcRenderer.once('message', function (event, value) { - assert.equal(value, currentDate.toISOString()) - done() - }) - ipcRenderer.send('message', currentDate) - }) - - it('can send instances of Buffer', (done) => { - const buffer = Buffer.from('hello') - ipcRenderer.once('message', function (event, message) { - assert.ok(buffer.equals(message)) - done() - }) - ipcRenderer.send('message', buffer) - }) - - it('can send objects with DOM class prototypes', (done) => { - ipcRenderer.once('message', function (event, value) { - assert.equal(value.protocol, 'file:') - assert.equal(value.hostname, '') - done() - }) - ipcRenderer.send('message', document.location) - }) - - it('can send Electron API objects', (done) => { - const webContents = remote.getCurrentWebContents() - ipcRenderer.once('message', function (event, value) { - assert.deepEqual(value.browserWindowOptions, webContents.browserWindowOptions) - done() - }) - ipcRenderer.send('message', webContents) - }) - - it('does not crash on external objects (regression)', (done) => { - const request = http.request({port: 5000, hostname: '127.0.0.1', method: 'GET', path: '/'}) - const stream = request.agent.sockets['127.0.0.1:5000:'][0]._handle._externalStream - request.on('error', () => {}) - ipcRenderer.once('message', function (event, requestValue, externalStreamValue) { - assert.equal(requestValue.method, 'GET') - assert.equal(requestValue.path, '/') - assert.equal(externalStreamValue, null) - done() - }) - - ipcRenderer.send('message', request, stream) - }) - - it('can send objects that both reference the same object', (done) => { - const child = {hello: 'world'} - const foo = {name: 'foo', child: child} - const bar = {name: 'bar', child: child} - const array = [foo, bar] - - ipcRenderer.once('message', function (event, arrayValue, fooValue, barValue, childValue) { - assert.deepEqual(arrayValue, array) - assert.deepEqual(fooValue, foo) - assert.deepEqual(barValue, bar) - assert.deepEqual(childValue, child) - done() - }) - ipcRenderer.send('message', array, foo, bar, child) - }) - - it('inserts null for cyclic references', (done) => { - const array = [5] - array.push(array) - - const child = {hello: 'world'} - child.child = child - - ipcRenderer.once('message', function (event, arrayValue, childValue) { - assert.equal(arrayValue[0], 5) - assert.equal(arrayValue[1], null) - - assert.equal(childValue.hello, 'world') - assert.equal(childValue.child, null) - - done() - }) - ipcRenderer.send('message', array, child) - }) - }) - - describe('ipc.sendSync', () => { - afterEach(() => { - ipcMain.removeAllListeners('send-sync-message') - }) - - it('can be replied by setting event.returnValue', () => { - const msg = ipcRenderer.sendSync('echo', 'test') - assert.equal(msg, 'test') - }) - - it('does not crash when reply is not sent and browser is destroyed', (done) => { - w = new BrowserWindow({ - show: false - }) - ipcMain.once('send-sync-message', function (event) { - event.returnValue = null - done() - }) - w.loadURL('file://' + path.join(fixtures, 'api', 'send-sync-message.html')) - }) - - it('does not crash when reply is sent by multiple listeners', (done) => { - w = new BrowserWindow({ - show: false - }) - ipcMain.on('send-sync-message', function (event) { - event.returnValue = null - }) - ipcMain.on('send-sync-message', function (event) { - event.returnValue = null - done() - }) - w.loadURL('file://' + path.join(fixtures, 'api', 'send-sync-message.html')) - }) - }) - - describe('ipcRenderer.sendTo', () => { - let contents = null - beforeEach(() => { - contents = webContents.create({}) - }) - afterEach(() => { - ipcRenderer.removeAllListeners('pong') - contents.destroy() - contents = null - }) - - it('sends message to WebContents', (done) => { - const webContentsId = remote.getCurrentWebContents().id - ipcRenderer.once('pong', function (event, id) { - assert.equal(webContentsId, id) - done() - }) - contents.once('did-finish-load', () => { - ipcRenderer.sendTo(contents.id, 'ping', webContentsId) - }) - contents.loadURL('file://' + path.join(fixtures, 'pages', 'ping-pong.html')) - }) - }) - - describe('remote listeners', () => { - it('can be added and removed correctly', () => { - w = new BrowserWindow({ - show: false - }) - const listener = () => {} - w.on('test', listener) - assert.equal(w.listenerCount('test'), 1) - w.removeListener('test', listener) - assert.equal(w.listenerCount('test'), 0) - }) - - it('detaches listeners subscribed to destroyed renderers, and shows a warning', (done) => { - w = new BrowserWindow({ - show: false - }) - w.webContents.once('did-finish-load', () => { - w.webContents.once('did-finish-load', () => { - const expectedMessage = [ - 'Attempting to call a function in a renderer window that has been closed or released.', - 'Function provided here: remote-event-handler.html:11:33', - 'Remote event names: remote-handler, other-remote-handler' - ].join('\n') - const results = ipcRenderer.sendSync('try-emit-web-contents-event', w.webContents.id, 'remote-handler') - assert.deepEqual(results, { - warningMessage: expectedMessage, - listenerCountBefore: 2, - listenerCountAfter: 1 - }) - done() - }) - w.webContents.reload() - }) - w.loadURL('file://' + path.join(fixtures, 'api', 'remote-event-handler.html')) - }) - }) - - it('throws an error when removing all the listeners', () => { - ipcMain.on('test-event', () => {}) - assert.equal(ipcMain.listenerCount('test-event'), 1) - - ipcRenderer.on('test-event', () => {}) - assert.equal(ipcRenderer.listenerCount('test-event'), 1) - - assert.throws(() => { - ipcMain.removeAllListeners() - }, /Removing all listeners from ipcMain will make Electron internals stop working/) - - assert.throws(() => { - ipcRenderer.removeAllListeners() - }, /Removing all listeners from ipcRenderer will make Electron internals stop working/) - - ipcMain.removeAllListeners('test-event') - assert.equal(ipcMain.listenerCount('test-event'), 0) - - ipcRenderer.removeAllListeners('test-event') - assert.equal(ipcRenderer.listenerCount('test-event'), 0) - }) - - describe('remote objects registry', () => { - it('does not dereference until the render view is deleted (regression)', (done) => { - w = new BrowserWindow({ - show: false - }) - - ipcMain.once('error-message', (event, message) => { - assert(message.startsWith('Cannot call function \'getURL\' on missing remote object'), message) - done() - }) - - w.loadURL('file://' + path.join(fixtures, 'api', 'render-view-deleted.html')) - }) - }) }) +