How to capture the window close event on Electron's renderer process?

Viewed 470

On a classic front-end JavaScript, capturing the "window close" event can be done in a multiple ways. It can be done with:

// Method A: `close` event via an event listener
window.addEventListener( 'close', function(event){ /** your magic here **/ } )

// Method B: `beforeunload` event via an event listener
window.addEventListener( 'beforeunload', function(event){ /** your magic here **/ } )

// Method C: classic `onclose` event binder
window.onclose = function(event){ /** your magic here **/ }

// Method D: classic `onbeforeunload` event binder
window.onbeforeunload = function(event){ /** your magic here **/ }

I tried these methods inside the Electron's renderer script/environment but the window close event doesn't seem to get triggered.

Which brings to my question, how to capture the "window close" event on Electron's renderer process?

2 Answers

According to Electron's official documentation:

Event: "before-quit" Emitted before the application starts closing its windows. Calling event.preventDefault() will prevent the default behavior, which is terminating the application.

So you need to use the before-quit event on your Electron application.

Normally, proving that these events trigger (or not) is difficult as the render process would normally close prior to confirmation.

Instead, through the use of Inter-Process Communication, one can tell if any of these events trigger by sending one or more messages back to the main process for viewing.

To test this, I have implemented a simple preload.js, main.js and index.html script.


preload.js (main process)

const contextBridge = require('electron').contextBridge;
const ipcRenderer = require('electron').ipcRenderer;

contextBridge.exposeInMainWorld(
    'ipcRender', {
        send: (message) => {
            ipcRenderer.send('closing', message);
        }
    });

main.js (main process)

const electronApp = require('electron').app;
const electronBrowserWindow = require('electron').BrowserWindow;
const electronIpcMain = require('electron').ipcMain;

const nodePath = require('path');

let window;

function createWindow() {
    const window = new electronBrowserWindow({
        x: 0,
        y: 0,
        width: 800,
        height: 600,
        show: false,
        webPreferences: {
            nodeIntegration: false,
            contextIsolation: true,
            preload: nodePath.join(__dirname, 'preload.js')
        }
    });

    window.loadFile('index.html')
        .then(() => { window.show(); });

    return window;
}

electronApp.on('ready', () => {
    window = createWindow();
});

electronApp.on('window-all-closed', () => {
    if (process.platform !== 'darwin') {
        electronApp.quit();
    }
});

electronApp.on('activate', () => {
    if (electronBrowserWindow.getAllWindows().length === 0) {
        createWindow();
    }
});

// Testing
electronIpcMain.on('closing', (event, message) => {
    console.log(message);
})

index.html (render process)

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Electron Test</title>
    </head>

    <body>
        <input type="button" id="button" value="Close">
    </body>

    <script>
        // Method A: `close` event via an event listener
        window.addEventListener('close', (event) => {
            window.ipcRender.send('Method A'); // Doesn't work
        })

        // Method B: `beforeunload` event via an event listener
        window.addEventListener('beforeunload', (event) => {
            window.ipcRender.send('Method B'); // Works
        })

        // Method C: classic `onclose` event binder
        window.onclose = (event) => {
            window.ipcRender.send('Method C'); // Doesn't work
        }

        // Method D: classic `onbeforeunload` event binder
        window.onbeforeunload = (event) => {
            window.ipcRender.send('Method D'); // Works
        }

        // Method E: `click' event via an event listener
        document.getElementById('button').addEventListener('click', () => {
            // Let's pretent that this can close the window from the main process
            window.ipcRender.send('Method E');
        })
    </script>
</html>

Below is the result when closed via the traffic light close button or the task bar close menu.

Method B // `beforeunload` event via an event listener
Method D // classic `onbeforeunload` event binder

Depending on how one (correctly or incorrectly) implements their preload.js script, one may receive no results compared to the results shown above.

Whilst I usually implement my preload.js script differently to that shown above, I have kept it simple and inline with what most people appear to be familiar with. IE: Context Isolation (Enabled)

Related