I have 2 BrowserWindow instances in my electron application, mainWindow and secondaryWindow. There is a button in mainWindow which when clicked should open secondaryWindow.
Now my issue is that I don't want to be able to click on anything in the mainWindow until the secondaryWindow is closed.
The closest I could get was to use mainWindow.hide() but this just completely hides the window, I want to still see the mainWindow while the secondaryWindow is active but it should be disabled / inactive.
Any suggestions?
There are 2 ways to open a child window:
1. from the main process:
You can open a child window from the main process. This is for example useful for a custom window in the menu.
Here you can use the constructor to make it a child of parent. If the attribute modal is true, the parent window will not be accessible until the child window is closed.
function createChild(parent, html) {
//creates modal window
child = new BrowserWindow({
width: 786,
height: 847,
parent: parent,
modal: true,
show: false
});
child.loadURL(html);
child.webContents.openDevTools();
child.once("ready-to-show", () => {
child.show();
});
}
2. from the renderer process
Now, we don't always want to send an event over the IPC to the main process just to open a child window, right?
We don't need to. We can use the open function on our window for that.
For example:
const button = document.querySelector('button')
button.addEventListener('click', e => {
self.open(`file://${__dirname}/child.html`)
})
To make this window a child of your parent and modal, you can register an eventlistener on the parent window:
parent.webContents.on(
"new-window",
(event, url, frameName, disposition, options, additionalFeatures) => {
Object.assign(options, {
parent: parent,
modal: true
});
}
);
With this, when window.open() is called on the parent window, it will open a modal child window.
Example
main.js
const { app, BrowserWindow } = require("electron");
let win;
function createWindow() {
win = new BrowserWindow({ width: 1000, height: 800 });
win.loadURL(`file://${__dirname}/index.html`);
win.webContents.openDevTools();
win.on("closed", () => {
win = null;
});
win.webContents.on(
"new-window",
(event, url, frameName, disposition, options, additionalFeatures) => {
Object.assign(options, {
parent: win,
modal: true
});
}
);
}
app.on("ready", createWindow);
index.html
<!DOCTYPE html>
<html>
<body>
<p>I am the parent, you can't touch me until you closed my child!</p>
<button>Open child!</button>
<script>
const button = document.querySelector('button')
button.addEventListener('click', e => {
self.open(`file://${__dirname}/child.html`)
})
</script>
</body>
</html>
child.html
<!DOCTYPE html>
<html>
<body>
I'm the child!
</body>
</html>
Update Electron 5 or higher
With electron 5 node integration was disabled in the renderer process by default for security reasons. Since this example uses __dirname (which is part of the node API) in the renderer process, we need to reintroduce it, because it is not available anymore. In this example I use a preload script for this purpose:
main.js
const { app, BrowserWindow } = require("electron");
let win;
function createWindow() {
win = new BrowserWindow({
width: 1000,
height: 800,
webPreferences: {
preload: `${__dirname}/preload.js`,
},
});
win.loadURL(`file://${__dirname}/index.html`);
win.webContents.openDevTools();
win.on("closed", () => {
win = null;
});
win.webContents.on(
"new-window",
(_event, _url, _frameName, _disposition, options, _additionalFeatures) => {
Object.assign(options, {
parent: win,
modal: true,
});
}
);
}
app.whenReady().then(createWindow).catch(console.error);
preload.js
window.__dirname = __dirname;
index.html
<!DOCTYPE html>
<html>
<body>
<p>I am the parent, you can't touch me until you closed my child!</p>
<button>Open child!</button>
<script>
const button = document.querySelector("button");
button.addEventListener("click", (e) => {
self.open(`file://${__dirname}/child.html`);
});
</script>
</body>
</html>
child.html
<!DOCTYPE html>
<html>
<body>
I'm the child!
</body>
</html>
Related
I'm currently creating a JS Desktop App using Electron. I'm able to get everything functional how I want it, but I want to be able to update the users on certain tasks and also display errors in the app itself.
Is there any way to add a section (terminal if you will) or something similar inside the UI, so I can log things out to the user?
Providing application feedback to your user is as simple as having a statusUpdate function in your main process send
a message (via your preload.js script) to your render process. Within your render process, listen for a message on the
assigned channel and once received, update the content of your DOM element.
main.js (main process)
// Import required electron modules
const electronApp = require('electron').app;
const electronBrowserWindow = require('electron').BrowserWindow;
// Import required Node modules
const nodePath = require('path');
// Prevent garbage collection
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();
statusTest();
});
electronApp.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
electronApp.quit();
}
});
electronApp.on('activate', () => {
if (electronBrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
// ---
function statusTest() {
let counter = 1;
let message;
setInterval(() => {
statusUpdate(`Status message ${counter} from main process.`);
counter++;
}, 1000);
}
function statusUpdate(message) {
window.webContents.send('statusMessage', message);
}
preload.js (main process)
// Import the necessary Electron modules
const contextBridge = require('electron').contextBridge;
const ipcRenderer = require('electron').ipcRenderer;
// Exposed protected methods in the render process
contextBridge.exposeInMainWorld(
// Allowed 'ipcRenderer' methods
'ipcRenderer', {
// From main to render
statusMessage: (message) => {
ipcRenderer.on('statusMessage', message);
}
}
);
index.html (render process)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Electron Test</title>
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';"/>
</head>
<body>
<label for="status">Status: </label>
<textarea id="status" cols="40" rows="10" disabled></textarea>
</body>
<script>
let status = document.getElementById('status');
window.ipcRenderer.statusMessage((event, message) => {
status.value += message + '\n'; // Append message
status.scrollTop = status.scrollHeight; // Show last entry
});
</script>
</html>
Hey i am making a project where i want to send data to ipc main
my index.js
const {app, BrowserWindow , ipcMain} = require('electron')
const ejse = require('ejs-electron')
app.on('ready', () => {
ipcMain.on("getUrl",(event,url)=>{
console.log(url);
})
mainWindow = new BrowserWindow({
autoHideMenuBar: true,
icon: __dirname + '/logo.ico',
webPreferences: {
devTools: false,
nodeIntegration:true,
webviewTag:true
}
})
mainWindow.loadURL('file://' + __dirname + '/files/index.ejs');
})
index.ejs contains a anchor tag with value as link
<button id="dw" style="display: none;"><a class="button" id="dwl" onclick="heal()" value="#">Download</a></button>
<script src=download.js></script>
download.js i am able to alert the link but ipcRender not working
const { ipcRenderer } = require("electron");
function heal(){
var url = document.getElementById("dwl").value;
alert(url);
ipcRenderer.send("getUrl",url);
}
i am not able to send url from ejs file to main index.js file alert is working in download.js but some error in sending data to main file console.log is not working
The use of contextBridge within your preload.js script safely exposes API's you approve for use in your render
scripts.
Whist understanding how all this works can be challenging at first, becoming familiar with the below content and the
following code should put you in good stead for a solid understanding of how you can wire up your application.
Context Isolation
contextBridge
Enabling Content Isolation for remote content
Inter-Process Communication
ipcMain
ipcRenderer
webContents - contents.send(channel, ...args)
Some people like to implement concrete functions within their preload.js script(s). In this example,
the preload.js script is only used to configure whitelisted channel names for use between the main process and render
process(es). This keeps your code seperated. IE: Separation of concerns.
Let's set the channel name getUrl within the ipc.render.send array.
preload.js (main process)
// Import the necessary Electron components.
const contextBridge = require('electron').contextBridge;
const ipcRenderer = require('electron').ipcRenderer;
// White-listed channels.
const ipc = {
'render': {
// From render to main.
'send': [
'getUrl' // Channel name
],
// From main to render.
'receive': [],
// From render to main and back again.
'sendReceive': []
}
};
// Exposed protected methods in the render process.
contextBridge.exposeInMainWorld(
// Allowed 'ipcRenderer' methods.
'ipcRender', {
// From render to main.
send: (channel, args) => {
let validChannels = ipc.render.send;
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, args);
}
},
// From main to render.
receive: (channel, listener) => {
let validChannels = ipc.render.receive;
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`.
ipcRenderer.on(channel, (event, ...args) => listener(...args));
}
},
// From render to main and back again.
invoke: (channel, args) => {
let validChannels = ipc.render.sendReceive;
if (validChannels.includes(channel)) {
return ipcRenderer.invoke(channel, args);
}
}
}
);
Let's set the webPreferences.contextIsolation value to true and specify the webPreferences.preload path.
webPreferences.nodeIntegration can be set to false, but in doing so webPreferences.contextIsolation should be
set to true to "truly enforce strong isolation and prevent the use of Node primitives".
Lastly, let's listen for a message from the render process on the getUrl channel.
main.js (main process)
const electronApp = require('electron').app;
const electronBrowserWindow = require('electron').BrowserWindow;
const electronIpcMain = require('electron').ipcMain;
const nodePath = require("path");
// Prevent garbage collection
let window;
function createWindow() {
const window = new electronBrowserWindow({
x: 0,
y: 0,
width: 800,
height: 600,
show: false,
webPreferences: {
nodeIntegration: true,
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();
}
});
// Listen for a message on the 'getUrl' channel
electronIpcMain.on('getUrl', (event, url) => {
console.log(url);
})
This file is fairly standard and easy to understand. Just adapt code for use with .ejs
Added Javascript code in between <script> tags for sake of brevity.
index.html (render process)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Electron Test</title>
</head>
<body>
<input type="button" id="dwl" value="#">
</body>
<script>
let button = document.getElementById('dwl');
button.addEventListener('click', () => {
// Send URL on the 'getUrl' channel
window.ipcRender.send('getUrl', button.value);
});
</script>
</html>
I am starting an Electron based desktop app, which extends existing web app capabilities.
I am interested whether I can get the absolute file path from file input. HTML is displayed inside a BrowserView, which has nodeIntegration disabled and contextIsolation enabled.
My simple test does return a valid file path in this case, though my understanding is that it shouldn’t.
Maybe I am missing something? That looks like a security vulnerability…
Can I count in my desktop app that Electron will return a valid file path in a scenario similar to the one in the code below?
main.js
const { app, BrowserWindow, BrowserView } = require('electron');
let mainWindow;
const createWindow = () => {
// Create the browser window.
mainWindow = new BrowserWindow({
width: 800,
height: 600,
});
let view = new BrowserView({
webPreferences: {
nodeIntegration: false,
contextIsolation: true
}
})
mainWindow.setBrowserView(view)
view.setBounds({ x: 0, y: 0, width: 800, height: 600 })
view.webContents.loadURL('http://localhost:8000/index.html')
mainWindow.on('closed', () => {
mainWindow = null;
});
};
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', () => {
if (mainWindow === null) {
createWindow();
}
});
index.html
<html>
<script type="text/javascript">
function _on_file_change_() {
file_input = document.getElementById('file1');
console.log(file_input.files[0]);
}
</script>
<head>
<meta charset="UTF-8">
<title>Hello World!</title>
</head>
<body>
<input type="file" id='file1' onchange="_on_file_change_(this.files)"/>
</body>
</html>
In my main process I create a renderer window:
var mainWindow = new BrowserWindow({
height: 600,
width: 800,
x: 0,
y: 0,
frame: false,
resizable: true
});
mainWindow.openDevTools();
mainWindow.loadURL('file://' + __dirname + '/renderer/index.html');
Then I want to communicate with it in some way:
mainWindow.webContents.send('message', 'hello world');
However the main window doesn't receive this message because it isn't fully done being created at the time I attempt to send it.
I have temporarily solved this by wrapping the latter code in a setTimeout() but that is most definitely not the right way to resolve a race condition.
Is there a callback for when the main window is ready? I tried the 'ready-to-show' event mentioned in the docs but it did not work.
A listener on "mainWindow" doesn't worked for me. I used instead "mainWindow.webContents".
mainWindow.webContents.once('dom-ready', () => {});
Have a look at the did-finish-load event mentioned in the Electron browser-window documentation.
mainWindow.once('did-finish-load', () => {
// Send Message
})
There seems to be a dom-ready event too.
not mentioned in the previous answers, loadURL returns a promise that resolves at the same time the 'did-finish-load' event is fired; i.e., they're essentially equivalent, except one's a promise, and the other's a callback.
Check this: https://github.com/electron/electron/blob/master/docs/api/web-contents.md
You can use this event to know if your windows is ready in you main.js [CASE 1], but if want to know when your page is full loaded you should add an event in your index.html [CASE 2] and then you can attach a function that send a message to his parent Main.js telling him, he is ready, using IPCrenderer and IPCmain
CASE 1
main.js:
mainWindows.webContents.on('did-finish-load',WindowsReady);
function WindowsReady() {
console.log('Ready');
}
CASE 2
html:
<script>
const {ipcRenderer} = require('electron');
document.addEventListener('DOMContentLoaded',pageLoaded);
function pageLoaded(){
alert('The page is loade');
ipcRenderer.send('Am_I_Ready',"Im ready");
}
</script>
Main.js:
const {ipcMain} = electron;
ipcMain.on('Am_I_Ready', doSomething)
function doSomething(){
console.log('Everything is ready.');
}
Use mainWindow.webContents like this:
mainWindow.webContents.on('did-finish-load', () => {
mainWindow.webContents.send('message', 'hello world');
}
I tried the following code in my app
window.webContents.once("did-finish-load", () => {
console.log("did-finish-load");
});
window.webContents.once("dom-ready", () => {
console.log("dom-ready");
});
window.once("ready-to-show", () => {
console.log("ready-to-show");
});
This is after loading an index.html from local file system:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Hi mom</title>
<script defer src="renderer.js"></script></head>
<body>
<div id="renderer"></div>
</body>
</html>
According to the console.log output they fired in the following sequence:
dom-ready
ready-to-show
did-finish-load
Therefore did-finish-load is probably the one to wait for -- because it's the latest therefore presumably the most-fully-loaded.
Also the API documentation for webContents.send includes this example:
// In the main process.
const { app, BrowserWindow } = require('electron')
let win = null
app.whenReady().then(() => {
win = new BrowserWindow({ width: 800, height: 600 })
win.loadURL(`file://${__dirname}/index.html`)
win.webContents.on('did-finish-load', () => {
win.webContents.send('ping', 'whoooooooh!')
})
})
If I remove the loading of an external script file ...
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Hi mom</title>
<!-- <script defer src="renderer.js"></script> -->
</head>
<body>
<div id="renderer"></div>
</body>
</html>
... then the order of events is changed slightly ...
dom-ready
did-finish-load
ready-to-show
... which may explain why some of the other answers to this question contract each other.
These days, you use the "ready-to-show" event.
https://electronjs.org/docs/api/browser-window#using-ready-to-show-event
I have an Electron app + Vue for rooting. I am having problems loading the content into a newly opened window. The window is launched from a Vue component. When it opens I get a blank window and:
Not allowed to load local resource: file:///app/src/Products.vue
I have tried different methods mentioned on stackoverflow but the error still persists.
<style scoped>
</style>
<template>
<div class="container-fluid">
Parent window...
<button type="submit" class="btn btn-primary" v-on:click="add">+ Add Product</button>
</div>
</template>
<script>
export default {
methods: {
add: function () {
const remote = require('electron').remote
const BrowserWindow = remote.BrowserWindow
let win
win = new BrowserWindow({
height: 600,
width: 800
})
win.loadURL(`file://${__dirname}/app/src/Products.vue`)
win.openDevTools()
}
}
}
</script>
In your case, child window must be created from the main process to launch a child window with local resources in Electron. You can use ipc (ipcMain, ipcRenderer) for this.
For example,
In main process :
function createChildWindow(payload) {
let child = new BrowserWindow({ parent :mainWindow
});
child.loadURL(url.format({
pathname: path.join(__dirname, 'child.html'),
protocol: 'file:',
slashes: true,
}));
child.once('ready-to-show', () => {
child.show()
});
}
ipcMain.on('asynchronous-message', (event, arg) => {
console.log(arg) // prints "ping"
createChildWindow(arg);
});
In renderer process(web page) :
const {ipcRenderer} = window.require('electron')
async launchChildWindow(){
ipcRenderer.send('asynchronous-message', '<payload>');
}
You can also write custom events like this,
// Renderer process
ipcRenderer.invoke('some-name', someArgument).then((result) => {
// ...
})
// Main process
ipcMain.handle('some-name', async (event, someArgument) => {
const result = await doSomeWork(someArgument)
return result
})