Merge pull request #8357 from electron/remote-events-error-message

Don't crash when calling stale remote listeners
This commit is contained in:
Kevin Sawicki 2017-01-25 14:23:57 -08:00 committed by GitHub
commit 8e69f94df0
4 changed files with 90 additions and 1 deletions

View file

@ -3,6 +3,8 @@
const {Buffer} = require('buffer') const {Buffer} = require('buffer')
const electron = require('electron') const electron = require('electron')
const v8Util = process.atomBinding('v8_util') const v8Util = process.atomBinding('v8_util')
const {WebContents} = process.atomBinding('web_contents')
const {ipcMain, isPromise, webContents} = electron const {ipcMain, isPromise, webContents} = electron
const fs = require('fs') const fs = require('fs')
@ -146,6 +148,27 @@ const throwRPCError = function (message) {
throw error 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. // Convert array of meta data from renderer into array of real values.
const unwrapArgs = function (sender, args) { const unwrapArgs = function (sender, args) {
const metaToValue = function (meta) { const metaToValue = function (meta) {
@ -196,7 +219,7 @@ const unwrapArgs = function (sender, args) {
if (!sender.isDestroyed() && webContentsId === sender.getId()) { if (!sender.isDestroyed() && webContentsId === sender.getId()) {
sender.send('ELECTRON_RENDERER_CALLBACK', meta.id, valueToMeta(sender, args)) sender.send('ELECTRON_RENDERER_CALLBACK', meta.id, valueToMeta(sender, args))
} else { } 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)
} }
} }

View file

@ -494,6 +494,30 @@ describe('ipc module', function () {
w.removeListener('test', listener) w.removeListener('test', listener)
assert.equal(w.listenerCount('test'), 0) 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', () => { it('throws an error when removing all the listeners', () => {

View file

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script>
const {remote} = require('electron')
const browserWindow = remote.getCurrentWindow()
const handler = () => {}
browserWindow.webContents.on('remote-handler', handler)
browserWindow.webContents.on('other-remote-handler', handler)
</script>
</head>
<body>
</body>
</html>

View file

@ -249,3 +249,27 @@ ipcMain.on('create-window-with-options-cycle', (event) => {
ipcMain.on('prevent-next-new-window', (event, id) => { ipcMain.on('prevent-next-new-window', (event, id) => {
webContents.fromId(id).once('new-window', event => event.preventDefault()) webContents.fromId(id).once('new-window', event => event.preventDefault())
}) })
ipcMain.on('try-emit-web-contents-event', (event, id, eventName) => {
const consoleWarn = console.warn
let warningMessage = null
const contents = webContents.fromId(id)
const listenerCountBefore = contents.listenerCount(eventName)
try {
console.warn = (message) => {
warningMessage = message
}
contents.emit(eventName, {sender: contents})
} finally {
console.warn = consoleWarn
}
const listenerCountAfter = contents.listenerCount(eventName)
event.returnValue = {
warningMessage,
listenerCountBefore,
listenerCountAfter
}
})