How to create a contextMenu (right-click menu) using contextBridge in Electron

Viewed 492

Problem

Hello, I'm an Electron beginner.

I understand that using contextBridge is a good way to make a secure app in Electron. I want to implement a contextMenu(right-click menu) in contextBridge to specify what kind of process to do to the Main process in the Renderer process, but I can't do that.

Could you give me some advice?

My code

main.js(Main process)(part of initialization)

function createWindow() {
  mainWindow = new BrowserWindow({
    width: 960,
    height: 540,
    webPreferences: {
      nodeIntegration: false,
      contextIsolation: true,
      enableRemoteModule: false,
      preload: __dirname + "/preload.js"
    }
  });
  mainWindow.loadFile(__dirname + "/index.html");
}

preload.js

const contextBridge = require("electron");
const remote = require("electron").remote;
const Menu =remote.Menu;
const MenuItem=remote.MenuItem;

contextBridge.exposeInMainWorld(
  "api", {
    menu:()=>{
      const menu = new Menu();

      // label definition
      menu.append(new MenuItem({
        label: 'MenuItem1',
        click() {
          console.log('item 1 clicked');
        }
      }));

      // right clicked
      window.addEventListener('contextmenu', (e) => {
        e.preventDefault();
        menu.popup({
          window: remote.getCurrentWindow()
        });
      }, false);
    }
  }
);

index.html(Renderer process)

<!DOCTYPE html>

<html>

<head>
  <script>
    window.api.menu();
  </script>
</head>

<body>
</body>

</html>



What I tried

In the beginning, this was a failure.

Changing webreferences in main.js to nodeIntegration:true only (without using contextBridge) and putting the menu of preload.js to <script> in index.html, I could implement the contextMenu.

However I know this way can't make a secure app because not using contextBridge.

main.js

function createWindow() {
  mainWindow = new BrowserWindow({
    width: 960,
    height: 540,
    webPreferences: {
      nodeIntegration: true // changing false to true, deleting the others
    }
  });
  mainWindow.loadFile(__dirname + "/index.html");
}

preload.js
No changes(because didn't load)

index.html

<!DOCTYPE html>

<html>

<head>
  <script>
    // add the following
    const remote = require("electron").remote;
    const Menu =remote.Menu;
    const MenuItem=remote.MenuItem;

    const menu = new Menu();

    // label definition
    menu.append(new MenuItem({
      label: 'MenuItem1',
      click() {
        console.log('item 1 clicked');
      }
    }));

    // right cliced
    window.addEventListener('contextmenu', (e) => {
      e.preventDefault();
      menu.popup({
        window: remote.getCurrentWindow()
      });
    }, false);
  </script>
</head>

<body>
</body>

</html>



Thank you for your reading this to the end.

1 Answers

I believe your context menu won't work because you are trying to build it in the preloader, after it has preloaded. You need to build your menu in your main.js file.

Here is the approach I used.

Set your preloader.js like this:

// whitelist channels
let validChannels = ["load-context-menu", "pop-context-menu"];

contextBridge.exposeInMainWorld(
    "electron",
    {
        on: (channel, callback) => {
            //console.log('Channel: ' + channel);
            if (validChannels.includes(channel)) {
                ipcRenderer.on(channel, callback);
            }
        },
        sendSync: (channel, data) => {
            //console.log('Channel: ' + channel);
            if (validChannels.includes(channel)) {
                return ipcRenderer.sendSync(channel, data);
            }
        }
    }
);

Then call it like this:

window.electron.sendSync('load-context-menu', null);
window.electron.sendSync('pop-context-menu', null);

Finally, add this to your main.js file:

const editorContextMenuBuilder = require('./scripts/editorContextMenuBuilder');
let contextMenu;

ipcMain.on('load-context-menu', (event, arg) => {
  try {
    contextMenu = editorContextMenuBuilder();
    event.returnValue = true;
  } catch (err) {
    event.returnValue = 'LOAD CONTEXT ERROR';
  }
});
ipcMain.on('pop-context-menu', (event, arg) => {
  try{
    contextMenu.popup();
    event.returnValue = true;
  } catch (err) {
    event.returnValue = 'POP CONTEXT ERROR';
  }
});

If you need to send data to the menu builder, stringify your object before sending it, and then parse it back in the main.js using JSON.

Related