Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IpcRenderer does not execute normally in preload.js #21437

Closed
3 tasks done
ChinaLiuxiaosong opened this issue Dec 9, 2019 · 28 comments
Closed
3 tasks done

IpcRenderer does not execute normally in preload.js #21437

ChinaLiuxiaosong opened this issue Dec 9, 2019 · 28 comments

Comments

@ChinaLiuxiaosong
Copy link

Preflight Checklist

  • I have read the Contributing Guidelines for this project.
  • I agree to follow the Code of Conduct that this project adheres to.
  • I have searched the issue tracker for an issue that matches the one I want to file, without success.

Issue Details

  • Electron Version:
    • 7.1.3
  • Operating System:
    • Windows 10 (1909)
  • Last Known Working Electron version:
    • never

Expected Behavior

In preload.js, ipcRenderer.on can work when script loading.

Actual Behavior

In preload.js, ipcRenderer.on does not work while script loaded.

To Reproduce

https://gist.github.com/8beadb34f2123ee942968494edf439d0

Screenshots

image

image

@reZach
Copy link

reZach commented Jan 8, 2020

I'd like to add some detail, it appears ipcRenderer does not include all methods when executing in a preload.js script. This is problematic, because there is no other way to pass an ipcRenderer to a renderer process. What does this mean? Renderer process cannot use ipcRenderer.on("", {}).

preload.js

const { contextBridge, ipcRenderer } = require("electron");

console.log(ipcRenderer);
contextBridge.exposeInMainWorld(
    "electron",
    {
        ipcRenderer: ipcRenderer
    }
);

What gets displayed in the console:
image

@reZach
Copy link

reZach commented Jan 9, 2020

@sofianguy @ChinaLiuxiaosong

It appears the ipcRenderer is only an EventEmitter instance in the preload.js There must be some lifecycle code/event/gen that populates the various properties for an ipcRenderer, and this process isn't happening in the preload script.

@reZach
Copy link

reZach commented Jan 11, 2020

I would gladly help if someone could point me in the right direction, my familiarity with the source code is very low.

@reZach
Copy link

reZach commented Jan 11, 2020

Another note, if you preload the ipcRenderer, using the contextBridge, even in a renderer process you get a subset of the methods (only the EventEmitter methods as described above), not the entire method set as mentioned in the docs. So the issue is not with the ipcRenderer but with the context bridge.

@reZach
Copy link

reZach commented Jan 11, 2020

There is no issue with contextBridge, simply the preload script itself, see MVP where you can test the bug (Electron v.7.1.8): https://github.com/reZach/broken-preload-example

@reZach
Copy link

reZach commented Jan 11, 2020

It looks like the problem is here:

const ipcEmitter = new EventEmitter()
const ipcInternalEmitter = new EventEmitter()
v8Util.setHiddenValue(global, 'ipc', ipcEmitter)
v8Util.setHiddenValue(global, 'ipc-internal', ipcInternalEmitter)
v8Util.setHiddenValue(global, 'ipcNative', {
onMessage (internal: boolean, channel: string, args: any[], senderId: number) {
const sender = internal ? ipcInternalEmitter : ipcEmitter
sender.emit(channel, { sender, senderId }, ...args)
}
})
. Can anyone explain why we need to use an EventEmitter here?

@reZach
Copy link

reZach commented Jan 11, 2020

Update on the sample repo. @ChinaLiuxiaosong It turns out ipcRenderer.on works fine in the preload, at least in my sample repo. Please view the following gist for more details. My test repo has also been updated to show you can call ipcRenderer.on in the preload.

However - you still cannot call ipcRenderer.on(channel, args) from the renderer process. This is desired because it is not feasible to put all logic regarding the ipcRenderer in the preload. ie. I have a i18n library that needs to send ipc messages to the main process in order to save translation files to disk. This library exists in the renderer process, and in order for the translation files to be saved it needs to call out to the main process (which has access to fs). Upon finishing writing translation files, the library must finish executing it's code. Without ipcRenderer.on(channel, args) available in the renderer process, this process is impossible.
image

Again, the MVP test repo has been updated and can replicate this problem for someone to replicate the issue.
image

@reZach
Copy link

reZach commented Jan 11, 2020

I've posted a $230 bounty on this issue for prioritization, thanks!

@reZach
Copy link

reZach commented Jan 13, 2020

Turns out, the problem is contextBridge. A possible solution is to not use contextBridge and simply load the ipcRenderer to the renderer process the "old-way". More discussion is in that thread.

@MarshallOfSound
Copy link
Member

MarshallOfSound commented Jan 13, 2020

@reZach That is not a solution and is a terrible idea. The problem is not the context bridge.

Copying your desired state from your other issue, this is how you do it correctly:

renderer

class LibraryClass {
    constructor(myValue = 1) {
        this.classValue = myValue;

        window.electron.ipcRenderer.on("response", (IpcRendererEvent, args) => {
            if (args.success) this.classValue++;
        });
    }

    send() {
        window.electron.ipcRenderer.send("request", {
            data: this.classValue
        });
    }
}

main

let win = new BrowserWindow({...});

ipcMain.on("request", (IpcMainEvent, args) => {
    // Use node module (ie. "fs");
    // perhaps save value of args.data to file with a timestamp

    win.webContents.send("response", {
        success: true
    });
});

Isolated Preload

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

contextBridge.exposeInMainWorld('myapi', {
  request: (data) => ipcRenderer.send("request", {
    data,
  }),
  onResponse: (fn) => {
    // Deliberately strip event as it includes `sender` 
    ipcRenderer.on('response', (event, ...args) => fn(...args));
  }
}

Renderer

class LibraryClass {
    constructor(myValue = 1) {
        this.classValue = myValue;

        window.myapi.onResponse((args) => {
            if (args.success) this.classValue++;
        });
    }

    send() {
        window.myapi.request(this.classValue);
    }
}

Main Process

let win = new BrowserWindow({...});

ipcMain.on("request", (IpcMainEvent, args) => {
    // Use node module (ie. "fs");
    // perhaps save value of args.data to file with a timestamp

    win.webContents.send("response", {
        success: true
    });
});

Things to note

  1. Exposing a minimal API surface from the preload to the renderer, just two helpers, not the entire ipcRenderer module
  2. Core business logic still remains in the renderer (LibraryClass) just the stuff that requires Electron APIs lives in the isolated preload
  3. Even with this set up you should validate the incoming arguments are valid / what you would expect when you are handling the event in the main process

Final Note

@reZach I'd ask that you stop spamming these issues as your issue is not this potential bug rather user issues with how the APIs work.

@ChinaLiuxiaosong
Copy link
Author

@MarshallOfSound Now, I have two questions:

  1. In proload.js, what should I do to know when ipcRenderer.on will normal work? Since I have some scripts that must work in proload.js, I need to know when ipcRenderer.on will work and I will trigger it.
  2. On line 27 of preload.js, the args of sendTo are different. But on line 16, the args of the two on test.reply are the same.

@MarshallOfSound
Copy link
Member

@ChinaLiuxiaosong sorry my reply above wasn't to you, the reply was addressing a user who is not experiencing whatever you are and I haven't looked into your particular hug report yet.

@reZach
Copy link

reZach commented Jan 13, 2020

@MarshallOfSound Yes, this is my fault. I continued the conversation after I had realized the issue was not related to @ChinaLiuxiaosong's. I did so to help anyone who might find value in the process to work around my specific issue, not the one created.

Thank you for steering me on the right course for this repo with you are a member of. I will remember my faults and not repeat them again.

@reZach
Copy link

reZach commented Jan 14, 2020

@ChinaLiuxiaosong, can you explain what you are trying to achieve, I think it would help us solve for you your question. What I think you are trying to do is communicate between two BrowserWindows? I think your current code can work, but I feel its not set up in the best way it can be.

I took a stab at your code and I was able to see the "LOADED" message as I think you were aiming to see that. Something about the .once() calls were getting caught, so I removed it and removed the "LOADING" message as well (we could just send a LOADED message when done, it can feel redundant to send a LOADING message because we know it's still loading :))

const {
    remote,
    ipcRenderer
} = require("electron");

const winName = process.argv.pop();
const webContentsId = remote.getCurrentWebContents().id;
const otherWebContents = remote.webContents.getAllWebContents().find(wc => wc.id != webContentsId);
const otherWebContentsId = otherWebContents && otherWebContents.id;

function now() {
    let date = new Date();
    return `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}.${date.getMilliseconds()}`;
}

function sendToOtherWin(msg) {
    // console.log(`[${now()}] listen once 'test.reply'`);
    // ipcRenderer.once("test.reply", (event, message) => {
    //     console.log(`[${now()}] on 'test.reply' [message: ${message}] (expected message: reply.${msg})`);
    // });
    console.log(`[${now()}] send 'test' [message: ${msg}] to worker`);
    ipcRenderer.sendTo(otherWebContentsId, "test", msg);
}

if (winName == "worker") {
    console.log(`[${now()}] load preload in worker (${webContentsId})`);
    console.log(`[${now()}] listen 'test'`);
    ipcRenderer.on("test", (event, message) => {
        console.log(`[${now()}] on 'test' [message: ${message}], reply to ui`);
        ipcRenderer.sendTo(event.senderId, "test.reply", `reply.${message}`);
        // event.sender.sendTo(event.senderId, "test.reply", `reply.${message}`);
    });
} else if (winName == "ui") {
    console.log(`[${now()}] load preload in ui (${webContentsId})`);
    ipcRenderer.on("test.reply", (event, message) => {
        console.log(`[${now()}] on 'test.reply' [message: ${message}]`);
    });    
    //sendToOtherWin("LOADING");
    window.addEventListener("load", () => {
        let button = document.createElement("button");
        button.innerHTML = "send";
        button.onclick = () => {
            sendToOtherWin("LOADED");
        };
        document.body.appendChild(button);
    });
}

console.log(`[${now()}] loaded`);

I keep on thinking, and of course Marshall is the better judge here because he's actually developing in the codebase, but my thought is that you need to communicate between the browser windows through your main file. My thought was something like this:
image

Each browser window maintains it's own state, (ie. when it is "ready"), and when it is, each browser window can send an event back to the main process. The main process can keep track when both browser windows are ready and perform any logic you were thinking that should happen. This code is very rough but I hope it gives you an idea.

preload.js (probably can be the same preload for both BrowserWindows)

const {
    remote,
    ipcRenderer
} = require("electron");

const winName = process.argv.pop();

ipcRenderer.on("receive", (event, args) => {
    console.log(`Received message from ${args.name}: '${args.message}'.`);
    if (typeof args.callback === "function") args.callback();
});

window.api = {
    "sendInformation": function(data){
        ipcRenderer.send("send", {
            name: winName,
            message: data
        });
    }
};

index.html

<!DOCTYPE html>
<html lang="en-US">

<head>
    <title>Title</title>
    <meta http-equiv="Content-Security-Policy" content="default-src 'none'">
</head>

<body>
    <script>
        // Send data to main process
        window.api.sendInformation("data");
    </script>
</body>

</html>

main

const {
    app,
    BrowserWindow,
    ipcMain
} = require("electron");
const path = require("path");

let workerWin;
let uiWin;
let windows = ["worker", "ui"];

app.on("ready", async () => {
    workerWin = new BrowserWindow({
        height: 600,
        width: 800,
        title: "Worker",
        webPreferences: {
            preload: path.join(__dirname, "./preload.js"),
            additionalArguments: [windows[0]]
        },
    });
    await workerWin.loadFile(path.join(__dirname, "./index.html"));
    workerWin.webContents.openDevTools();

    uiWin = new BrowserWindow({
        height: 600,
        width: 800,
        title: "UI",
        webPreferences: {
            preload: path.join(__dirname, "./preload.js"),
            additionalArguments: [windows[1]]
        },
    });
    await uiWin.loadFile(path.join(__dirname, "./index.html"));
    uiWin.webContents.openDevTools();
});

ipcMain.on("send", (event, data) => {
    // find other window to send the event to
    let other = windows[0] !== data.name ? windows[0] : windows[1];

    if (other === "worker"){
        workerWin.webContents.send("receive", {
            name: "ui",
            message: data
        });
    } else {
        uiWin.webContents.send("receive", {
            name: "worker",
            message: data
        });
    }
});

@miniak
Copy link
Contributor

miniak commented May 26, 2020

Closing due to lack of activity

@miniak miniak closed this as completed May 26, 2020
@jhzzzz
Copy link

jhzzzz commented Aug 22, 2020

@reZach That is not a solution and is a terrible idea. The problem is not the context bridge.

Copying your desired state from your other issue, this is how you do it correctly:

Isolated Preload

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

contextBridge.exposeInMainWorld('myapi', {
  request: (data) => ipcRenderer.send("request", {
    data,
  }),
  onResponse: (fn) => {
    // Deliberately strip event as it includes `sender` 
    ipcRenderer.on('response', (event, ...args) => fn(...args));
  }
}

Renderer

class LibraryClass {
    constructor(myValue = 1) {
        this.classValue = myValue;

        window.myapi.onResponse((args) => {
            if (args.success) this.classValue++;
        });
    }

    send() {
        window.myapi.request(this.classValue);
    }
}

Main Process

let win = new BrowserWindow({...});

ipcMain.on("request", (IpcMainEvent, args) => {
    // Use node module (ie. "fs");
    // perhaps save value of args.data to file with a timestamp

    win.webContents.send("response", {
        success: true
    });
});

Things to note

  1. Exposing a minimal API surface from the preload to the renderer, just two helpers, not the entire ipcRenderer module
  2. Core business logic still remains in the renderer (LibraryClass) just the stuff that requires Electron APIs lives in the isolated preload
  3. Even with this set up you should validate the incoming arguments are valid / what you would expect when you are handling the event in the main process

Final Note

@reZach I'd ask that you stop spamming these issues as your issue is not this potential bug rather user issues with how the APIs work.

this worked! thank you.

@BeardScript
Copy link

@reZach That is not a solution and is a terrible idea. The problem is not the context bridge.

Copying your desired state from your other issue, this is how you do it correctly:

Isolated Preload

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

contextBridge.exposeInMainWorld('myapi', {
  request: (data) => ipcRenderer.send("request", {
    data,
  }),
  onResponse: (fn) => {
    // Deliberately strip event as it includes `sender` 
    ipcRenderer.on('response', (event, ...args) => fn(...args));
  }
}

Renderer

class LibraryClass {
    constructor(myValue = 1) {
        this.classValue = myValue;

        window.myapi.onResponse((args) => {
            if (args.success) this.classValue++;
        });
    }

    send() {
        window.myapi.request(this.classValue);
    }
}

Main Process

let win = new BrowserWindow({...});

ipcMain.on("request", (IpcMainEvent, args) => {
    // Use node module (ie. "fs");
    // perhaps save value of args.data to file with a timestamp

    win.webContents.send("response", {
        success: true
    });
});

Things to note

  1. Exposing a minimal API surface from the preload to the renderer, just two helpers, not the entire ipcRenderer module
  2. Core business logic still remains in the renderer (LibraryClass) just the stuff that requires Electron APIs lives in the isolated preload
  3. Even with this set up you should validate the incoming arguments are valid / what you would expect when you are handling the event in the main process

Final Note

@reZach I'd ask that you stop spamming these issues as your issue is not this potential bug rather user issues with how the APIs work.

Thanks for sharing this @MarshallOfSound. I just found it while looking into a similar issue. This seems to solve my problem but, now I'm wondering, wouldn't sending a callback to the preload script potentially give access to Electron and node APIs to a hijacker?

Also, if I'm not mistaken there's a memory leak here right?:

onResponse: (fn) => {
     // Deliberately strip event as it includes `sender` 
     ipcRenderer.on('response', (event, ...args) => fn(...args));
}

The event handler is being created over and over and never cleaned up.

@raphael10-collab
Copy link

@MarshallOfSound

In an electron-react-typescript-webpack app I'm trying to make two windows communicate.

preload.ts :

import { ipcRenderer, contextBridge, BrowserWindow } from 'electron';

let secondWindow;

contextBridge.exposeInMainWorld('electron', {
  openNewWindow: () => ipcRenderer.send('open-second-window', {
    secondWindow = new BrowserWindow({width: 1000, height: 1200});
  });
});

index.d.ts :

declare interface Window {
  electron: {
    //doThing(): void;
    openNewWindow(): void;
  };
  myapi;
}

main.ts :

import { app, BrowserWindow, ipcMain } from 'electron';

ipcMain.on("reply", (IpcMainEvent, message) => {
  console.log(event, message);
  mainWindow.webContents.send('messageFromMain', `This is the message from the second 
window: ${message}`);
});

renderer.ts:

window.myapi.onResponse('messageFromMain', (event, message) => {
  console.log(`This is the message from the second window sent via main: ${message}`);
});

const openSecondWindowButton = document.getElementById('open-second-window');

window.electron.openNewWindow();

app/components/App.tsx :

import logo from '@static/logo.png';
import React from 'react';
import { hot } from 'react-hot-loader';

interface AppProps {
  title?: string;
}

interface AppState {
  counter: number;
}

class App extends React.Component<AppProps, AppState> {
  readonly state: AppState = { counter: 0 };

  render(): JSX.Element {
    return (
      <div className='container'>
        <h2 className='heading'>
          <img src={logo} width='32' title='Codesbiome' /> &nbsp;
            Two Windows Communication
       </h2>

        <button
          onClick={(): void =>
            this.setState({ counter: this.state.counter + 1 })
          }
       >
        Counter &nbsp; <span>{this.state.counter}</span>
       </button>
       <p>
         <button id="open-second-window">Open Second Window</button>
       </p>
     </div>
   );
 }

}

export default hot(module)(App);

I get this error: electron_WEBPACK_IMPORTED_MODULE_0__.BrowserWindow is not a constructor

image

These are the specs of the API Objects: https://www.electronjs.org/docs/all#api-objects

The lines that cause this error seems to be this line in preload.ts :

const secondWindow = new BrowserWindow({width: 1000, height: 1200});

together with this line in renderer.ts:

window.electron.openNewWindow();

How to solve the error?

@chunkybanana
Copy link

chunkybanana commented Dec 25, 2020

Thanks @MarshallOfSound, this worked!

@Nantris
Copy link
Contributor

Nantris commented Mar 18, 2021

@MarshallOfSound do you have any advice for how to remove an individual listener from a channel with this pattern, since it's an anonymous function that's being registered?

Or is there any good workaround to avoid increasing the attack surface? I'm having trouble formulating a reasonable alternative.

contextBridge.exposeInMainWorld('myapi', {
  request: (data) => ipcRenderer.send("request", {
    data,
  }),
  onResponse: (fn) => {
    // Deliberately strip event as it includes `sender` 
    ipcRenderer.on('response', (event, ...args) => fn(...args)); // How could this listener be removed without a function name?
  }
}

@Nantris
Copy link
Contributor

Nantris commented Mar 18, 2021

I guess we could do something like this but it seems likely to quickly get out of hand.

contextBridge.exposeInMainWorld('myapi', {
  request: (data) => ipcRenderer.send("request", {
    data,
  }),
  onResponse: (fn) => {
    const saferFn = (event, ...args) => fn(...args)
    // Deliberately strip event as it includes `sender` 
    ipcRenderer.on('response', saferFn);
    return saferFn; // Return the newly created function so the user can store it somewhere and later remove it.
  }
}

Unfortunately storing these extra references would quickly make the code quite convoluted.

@MarshallOfSound
Copy link
Member

@slapbox Unfortunately that wouldn't work as every time a function is passed over the bridge a new proxy function is made. i.e.

function A (world 1) --> function B (world 2)
function B (world 2) --> function C (world 1)

It's a known inefficiency of the contextBridge.

How I'd do this is by using some kind of token system.

const listeners = {};

contextBridge.exposeInMainWorld('myapi', {
  request: (data) => ipcRenderer.send("request", {
    data,
  }),
  onResponse: (fn) => {
    const saferFn = (event, ...args) => fn(...args)
    // Deliberately strip event as it includes `sender` 
    ipcRenderer.on('response', saferFn);
    const key = symbol();
    listeners[key] = saferFn;
    return key;
  },
  removeResponseHandler: (key) => {
    const fn = listeners[key];
    delete listeners[key];
    ipcRenderer.removeListener('response', fn);
  }
}

You can always make a pretty helper for this to make the usage for multiple IPC events much cleaner

@Nantris
Copy link
Contributor

Nantris commented Mar 18, 2021

@MarshallOfSound thanks so much for letting me know that what I'd outlined wouldn't work! The extra logic required is unfortunate, but it makes sense; security doesn't come free.

@shumeiko
Copy link

Here is another simple way to avoid the problem of not being able to remove the listener because of that function references issues:

// preload.js
contextBridge.exposeInMainWorld('ipc', {
  on: (channel, listener) => {
    ipcRenderer.on(channel, listener);
    return () => {
      ipcRenderer.removeListener(channel, listener);
    };
  },
});

// renderer process
const removeListener = window.ipc.on('channel', () => {
  // listener code
});
removeListener();

@JanMisker
Copy link

Here is another simple way to avoid the problem of not being able to remove the listener because of that function references issues:

// preload.js
contextBridge.exposeInMainWorld('ipc', {
  on: (channel, listener) => {
    ipcRenderer.on(channel, listener);
    return () => {
      ipcRenderer.removeListener(channel, listener);
    };
  },
});

// renderer process
const removeListener = window.ipc.on('channel', () => {
  // listener code
});
removeListener();

Thanks for this @shumeiko, really helped me. Would you say this is a good way to generalise your approach, also stripping away the IpcRendererEvent (not so useful in generic case?) and not exposing all channels.

// preload.js
function makeListener(channel) {
  return function (listener) {
    ipcRenderer.on(channel, (_evt, ...args) => listener(...args));
    return () => ipcRenderer.removeListener(channel, listener);
  };
}
contextBridge.exposeInMainWorld("__MY_API__", {
  attributeA: 42, // initial value
  onAttributeA: makeListener("set-attribute-a"),
  onAttributeB: makeListener("set-attribute-b"),
});
// main.js
win.webContents.send("set-attribute-a", 42);
// renderer
const removeListenerA = window.__MY_API__.onAttributeA((value) => { 
  // do something with the value
});
// renderer with React, semi-pseudo-code
export const useMyApi = () => {
  const myApi = useRef(window.__MY_API__);
  const [attributeA, setAttributeA] = useState(myApi.current?.attributeA);
  
  useEffect(() => {
    if (myApi.current?.onAttributeA) {
      const unsub = myApi.current.onAttributeA(setAttributeA);
      return () => unsub();
      // or if you like short code that the next dev wont understand: return myApi.current.onAttributeA(setAttributeA);
    }
  }, [myApi.current?.onAttributeA]);

  return { attributeA };
}

@alex8088

This comment was marked as abuse.

@DarkSynx
Copy link

2022 simple and functional

//main.js 
    ipcMain.on('messageFromRenderer', (event, arg) => {
        console.log(arg); // Affiche "Hello world !" dans la console principale
        event.reply('responseToRenderer', 'Message reçu !'); // Répond à l'ipcRenderer
    });
//index.html
...
<WebView id='frame' preload="./js/preload.js" src='URL' nodeintegration spellcheck=1></WebView>
...
// preload.js

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

    ipcRenderer.send('messageFromRenderer', 'Hello world !'); // Envoie un message à l'ipcMain

    ipcRenderer.on('responseToRenderer', (event, arg) => {
        console.log(arg); // Affiche "Message reçu !" dans la console de la WebView
    });

@DarkSynx
Copy link

DarkSynx commented Mar 21, 2023

version controle button in index.html
I solved the problem by sending "webContents" in a global variable "mainWindowWebContents" for later use

//index.html
...
<WebView id='frame' preload="./js/preload.js" src='URL' nodeintegration spellcheck=1></WebView>
<button onclick="ipcaction()">Button action</button>
...
<script>
const ipc = require("electron").ipcRenderer;
function ipcaction() {
    console.log("ipcaction");
    ipc.send('messageFromMain', 'Hello world 555 !');
}
</script>
//main.js
const { app, BrowserWindow, ipcMain } = require('electron');
const ipc = require("electron").ipcMain;
let mainWindowWebContents = null;

app.on('ready', () => {
  const mainWindow = new BrowserWindow({ width: 800, height: 600, webPreferences: { nodeIntegration: true } })
  mainWindow.loadFile('index.html')

    ipc.on("messageFromMain", event => {
        console.log('messageFromMain');
        sendToMainWindow('Hello from main.js W!');
    });

})

function sendToMainWindow(message) {
    if (mainWindowWebContents) {
        mainWindowWebContents.send('messageFromMain', message)
    }
}

    app.on("web-contents-created", (event, webContents) => {

        webContents.on('did-finish-load', () => {
            if (webContents.getType() === 'webview') {
                mainWindowWebContents = webContents;
                sendToMainWindow('Hello from main.js X!');
            }
        });
});
//preload.js    :::: WebView id='frame' preload="./js/preload.js"
window.addEventListener('load', () => {

    const { ipcRenderer } = require('electron')
    ipcRenderer.on('messageFromMain', (event, arg) => {
        console.log(arg) // affiche "Hello world 555 !"
    })
    
 });

aavarghese pushed a commit to IBM/lunchpail that referenced this issue Jul 17, 2024
We need to handle the `off` function differently due to issues
with contextBridge. It turns out that `cb` will be a *copy* of
the original function, hence a naive use of removeListener
won't actually unlisten. See
electron/electron#21437 (comment)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests