Message send from Electron Main process not received by Renderer

; Date: August 13, 2017

Tags: Node.JS »»»» Electron

Sending messages between Electron's main process and the Renderer looks simple. In the Renderer you use ipcRenderer to send a message to the main process, and in the main process you use windowObject.webContents.send to send the message. But what about when the process doesn't work and the documentation doesn't make it clear what to do?

In my case, I wanted to read the content of a file, send that content to the Renderer, to display the content in an Ace editor pane.

In editor.html I have a <script> tag as so:

<script>

    const electron = require('electron');
    const {
        ipcRenderer
    } = electron;

    var editor = ace.edit("editor");
    editor.setTheme("ace/theme/twilight");
    editor.session.setMode("ace/mode/markdown");

    ipcRenderer.on('editContent', (event, content) => {
        console.log(`edit:content ${content}`);
        editor.setValue(content, -1);
    });
</script>

Then in the index.js (main application)

app.on('ready', () => {
    editorWindow = new BrowserWindow({
        height: 600,
        width: 800,
        webPreferences: { backgroundThrottling: false }
    });
    editorWindow.webContents.openDevTools();
    editorWindow.loadURL(`file://${__dirname}/editor.html`);
    fs.readFile("/path/to/file.txt", 'utf-8', (err, data) => {
        if (err) {
            console.error(`FAIL to read file ${fileName} because ${err}`);
        } else {
            // console.log(`editing ${data}`);
            console.log(`sending data for file`);
            editorWindow.webContents.send('editContent', data);
        }
    });
});

On the terminal sending data for file was printed, but nowhere was edit:content printed. In other words, the message sent by the Main process was not received by the Renderer process.

What was frustrating is that the documentation said to use editorWindow.webContents.send as shown here. Except that every instance of this call was within a handler to a message sent by the Renderer.

After some thinking I decided to change this so fs.readFile is executed in response to a message from the Renderer. The simplest is to insert this in the middle of the <script> tag shown above.

ipcRenderer.send('starting');

This causes the Renderer to send a message during its launch. That gives the opportunity for the Main process to listen for this message and respond by reading the file, sending its contents in a message.

The Main process can respond as so:

ipcMain.on('starting', (event) => {
    console.log('received starting');
    fs.readFile("/path/to/file.txt", 'utf-8', (err, data) => {
        if (err) {
            console.error(`FAIL to read file ${fileName} because ${err}`);
        } else {
            // console.log(`editing ${data}`);
            console.log(`sending data for file`);
            editorWindow.webContents.send('editContent', data);
        }
    });
});

The fs.readFile inside app.on('ready' ... is of course removed, and moved to here.

Running the application now results in the correct behavior. The sending data for file message is printed in the terminal window, and in the Developer Tools Console the edit:content message is printed, and the content does show up in the main window.

Another variant, which doesn't require explicitly sending a message from the Renderer process, is this:

app.on('ready', () => {
    ...
    editorWindow.webContents.on('did-finish-load', () => {
        fs.readFile("/path/to/file.txt", 'utf-8', (err, data) => {
            if (err) {
                console.error(`FAIL to read file ${fileName} because ${err}`);
            } else {
                // console.log(`editing ${data}`);
                console.log(`sending data for file`);
                editorWindow.webContents.send('editContent', data);
            }
        });
    });
});

The Renderer is apparently sending did-finish-load automatically, giving an event within whose handler a message can be sent.