diff --git a/lib/browser/rpc-server.js b/lib/browser/rpc-server.js index b12b518888a2..1dac51645926 100644 --- a/lib/browser/rpc-server.js +++ b/lib/browser/rpc-server.js @@ -3,6 +3,8 @@ const {Buffer} = require('buffer') const electron = require('electron') const v8Util = process.atomBinding('v8_util') +const {WebContents} = process.atomBinding('web_contents') + const {ipcMain, isPromise, webContents} = electron const fs = require('fs') @@ -146,6 +148,27 @@ const throwRPCError = function (message) { throw error } +const removeRemoteListenersAndLogWarning = (meta, args, callIntoRenderer) => { + let message = `Attempting to call a function in a renderer window that has been closed or released.` + + `\nFunction provided here: ${meta.location}` + + if (args.length > 0 && (args[0].sender instanceof WebContents)) { + const {sender} = args[0] + const remoteEvents = sender.eventNames().filter((eventName) => { + return sender.listeners(eventName).includes(callIntoRenderer) + }) + + if (remoteEvents.length > 0) { + message += `\nRemote event names: ${remoteEvents.join(', ')}` + remoteEvents.forEach((eventName) => { + sender.removeListener(eventName, callIntoRenderer) + }) + } + } + + console.warn(message) +} + // Convert array of meta data from renderer into array of real values. const unwrapArgs = function (sender, args) { const metaToValue = function (meta) { @@ -196,7 +219,7 @@ const unwrapArgs = function (sender, args) { if (!sender.isDestroyed() && webContentsId === sender.getId()) { sender.send('ELECTRON_RENDERER_CALLBACK', meta.id, valueToMeta(sender, args)) } else { - throw new Error(`Attempting to call a function in a renderer window that has been closed or released. Function provided here: ${meta.location}.`) + removeRemoteListenersAndLogWarning(meta, args, callIntoRenderer) } } diff --git a/spec/api-ipc-spec.js b/spec/api-ipc-spec.js index 76310f47812a..b1ca29c6a158 100644 --- a/spec/api-ipc-spec.js +++ b/spec/api-ipc-spec.js @@ -494,6 +494,30 @@ describe('ipc module', function () { 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', () => { diff --git a/spec/fixtures/api/remote-event-handler.html b/spec/fixtures/api/remote-event-handler.html new file mode 100644 index 000000000000..30c3cfb36ad7 --- /dev/null +++ b/spec/fixtures/api/remote-event-handler.html @@ -0,0 +1,18 @@ + + +
+ +