I am working on a multi-windows desktop Electron based application and I need to share a object instances from the main application process with the other windows of the application. Currently in browser (Chrome 52.0.2743.116) you can reference window.opener to achieve this. In browser window.opener (in child window) will give you the instance of the Window object from which window.opent(myURL) was called.
For example if you set in window.myData = {}; (Main window) same instance of myData will be available in the (Child Window) and will be accessed via (window.opener.myData).
In Electron when I open window via (window.opent(myURL)) window.opener is replaced with BrowserWindowProxy (Electron API) which does not expose the same window (Window object instance) from which the (window.open(myURL)) was called. Is there a way in Electron to access window.opener the same way it works in Browser?
Using remote.sharedObject (Electron API) is not an option since it only serialize/deserialize data and can be used for passing data from one window to others but does not give access to same object instances across windows.
To correct window.opener, and work facebook(and others) logins
You need use webPreferences.nativeWindowOpen=true, and set same webPreferences.affinity for mainWindow, and opened windows(hook on mainWindow.webContents.on('new-window'))
https://gist.github.com/Gvozd/2cec0c8c510a707854e439fb15c561b0
I don't think you are right about the remote api. From the doc:
Each object (including functions) returned by the remote module represents an object in the main process (we call it a remote object or remote function).
Try this:
'use strict'
let Electron = require('electron')
let mainWindow = null
class myObj {
constructor(d) {
console.log('constructor ' + d)
this.d = d
}
get data() {
console.log('get ' + this.d)
return this.d
}
set data(d) {
this.d = d
console.log('set ' + this.d)
}
}
global.someObj = new myObj(1)
Electron.app.on('ready', () => {
mainWindow = new Electron.BrowserWindow()
mainWindow.loadURL(`file://${__dirname}/index.html`)
mainWindow.webContents.openDevTools()
mainWindow.on('closed', () => {
mainWindow = null
})
})
Electron.app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
Electron.app.quit()
}
})
and
<!DOCTYPE html>
<html>
<head>
<script>
let obj = require('electron').remote.getGlobal('someObj')
console.log(obj)
console.log(obj.data)
obj.data = 2
console.log(obj.data)
</script>
</head>
<body></body>
</html>
Related
How can i capture keypress in Electron without globalShortcut and without losing the key functionality. For example i want to capture Tab press but without losing it's ability to indent for example in visual studio code.
I use Electron 13.0 because if i use higher some required modules don't work.
I tried iohook but throws iohook.node module not found. I think it doens't have support for Electron 13 yet.
Anyone ideea how can i do accomplish this? Thank you !
Electron can be a bit of a headache when it comes to communicate between the window and the main process, and for good reason: Security.
However, this problem has two solutions:
Not recommended: plain ipcRenderer required with { nodeIntegration: true } and window.electron in index.html, that can cause a lot of trouble, don't do that, you give access to the user to all nodejs functions, like fs, child_process, ...
Recomended: preload. Preload makes the bridge between the process and the window allowing you to pick what you want to share with the window, in this case, ipcRenderer without the whole electron access.
Read more about Electron secuity here
First, create a preload.js to pass scope isolated ipcRenderer.send function to the window
// preload.js
const { contextBridge, ipcRenderer } = require('electron');
const exposedAPI = {
sendMessage: (message) => {
ipcRenderer.send('my-event', string);
}
};
contextBridge.exposeInMainWorld("electron", exposedAPI);
More about contextBridge here
In the main electron script
// main.js
const { ipcRenderer } = require('electron');
...
const window = new BrowserWindow({
...
preload: 'my/preload/path/preload.js', // Here preload is loaded when the window is created
})
...
ipcRenderer.on('my-event', (string) => {
// do struff with string
});
Great full example here
Finally, the window where you want to capture the event from without changing the behaviour
// index.html or your-script.js
document.addEventListener('keydown', (evt) => { // keyup, keydown or keypress
window.electron.exposedAPI.sendMessage('key was pressed');
});
When executed in Node context (node-main),
setTimeout(function () {
console.log(nw);
}, 20);
throws
nw is not defined
because WebKit context is not ready (right from the start window is unavailable in NW.js <= 0.12, window.nw in NW.js >= 0.13). And
setTimeout(function () {
console.log(nw);
}, 200);
works just fine but setTimeout looks like a hack, setting it to safe delay value may cause undesirable lag.
How can the availability of WebKit context and nw be checked from Node context? Is there a reasonable way, like an event that could be handled?
The following achieves the same thing but does it the other way around.
In your html file:
<body onload="process.mainModule.exports.init()">
In your node-main JS file:
exports.init = function() {
console.log(nw);
}
Here, init function is only called when Webkit context/DOM is available.
You could use pollit :) ...
var pit = require("pollit");
foo = function(data) {
console.log(nw);
};
pit.nw('nw', foo);
I've tested it and it works for me :). This modularizes the solution that I give near the end of this.
The nw object does not exist until webkit is up and running ie the browser
window has been created. This happens after Node starts up which is why you're
getting this error. To use the nw api you either create events that can be
listened to or call global functions the former being preferable. The following code will demonstrate both and should give you a good idea of how Node and WebKit are interfacing with each other.
This example creates a Window, opens devtools and allows you to toggle the
screen. It also displays the mouse location in the console. It also demonstrates how to send events using the DOM ie body.onclick() and attaching events from within Node ie we're going to catch minimize events and write them to the console.
For this to work you need to be using the SDK version of NW. This is my package.json
{
"name": "hello",
"node-main": "index.js",
"main": "index.html",
"window": {
"toolbar": true,
"width": 800,
"height": 600
},
"dependencies" : {
"robotjs" : "*",
"markdown" : "*"
}
}
The two files you need are index.html
<!DOCTYPE html>
<html>
<head>
<script>
var win = nw.Window.get();
global.win = win;
global.console = console;
global.main(nw);
global.mouse();
var markdown = require('markdown').markdown;
document.write(markdown.toHTML("-->Click between the arrows to toggle full screen<---"));
</script>
</head>
<body onclick="global.mouse();">
</body>
</html>
and index.js.
var robot = require("robotjs");
global.mouse = function() {
var mouse = robot.getMousePos();
console.log("Mouse is at x:" + mouse.x + " y:" + mouse.y);
global.win.toggleFullscreen();
}
global.main = function(nw_passed_in) {
global.win.showDevTools();
console.log("Starting main");
console.log(nw_passed_in);
console.log(nw);
global.win.on('minimize', function() {
console.log('n: Window is minimized from Node');
});
}
When running this I used
nwjs --enable-logging --remote-debugging-port=1729 ./
You can then open up the browser using
http://localhost:1729/
for debugging if needed.
If you want to do something as soon as the nw object exists you can poll it. I'd use eventEmitter, if you don't want to use event emitter you can just as easily wrap this in a function and call it recursively. The following will display how many milliseconds it took before the nw object was setup. On my system this ranged between 43 - 48 milliseconds. Using a recursive function was no different. If you add this to the code above you'll see everything logged to the console.
var start = new Date().getTime();
var events = require('events');
var e = new events.EventEmitter();
var stop = 0;
e.on('foo', function() {
if(typeof nw === 'undefined') {
setTimeout(function () {
e.emit('is_nw_defined');
}, 1);
}
else {
if(stop === 0) {
stop = new Date().getTime();
}
setTimeout(function () {
console.log(stop - start);
console.log(nw);
e.emit('is_nw_defined');
}, 2000);
}
});
e.emit('is_nw_defined');
Solution 1:
You can use onload, see reference.
main.js:
var gui = require("nw.gui"),
win = gui.Window.get();
onload = function() {
console.log("loaded");
console.log(win.nw);
};
index.html:
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="main.js"></script>
</head>
<body></body>
</html>
package.json:
{
"name": "Freebox",
"main": "index.html"
}
Solution 2:
(To prevent issue, but it is not necessary).
var gui = require("nw.gui"),
win = gui.Window.get();
onload = function() {
console.log("loaded");
var a = function () {
if (!win.nw) return setTimeout(a, 10);
console.log(win.nw);
};
};
The solution I've initially come up looks like
app-node.js
process.once('webkit', () => {
console.log(nw);
});
app.html
<html>
<head>
<script>
global.process.emit('webkit');
</script>
...
I would be glad to know that there is already an event to listen, so cross-platform client scripts could omit NW-related code.
I am not able to access variable 'dps' declare in index.js within index.html , using npm start(for starting electron app)
I am able to access my sql and get data in index.js and i want to show that on index.html (used nodeJs and Electron)
'dps' refers to js object which has mysql data
//My index.js file has
var app = require('app');
var dps = [{x:1,y:2}];
// Module to create native browser window.
var BrowserWindow = require('browser-window');
var mainWindow = null;
var dps = [{x:1,y:2}];
var mysql = require('mysql');
// Quit when all windows are closed.
app.on('window-all-closed', function () {
if (process.platform != 'darwin') {
app.quit();
}
});
app.on('ready', function () {
// Create the browser window.
mainWindow = new BrowserWindow({ width: 800, height: 600 });
mainWindow.loadUrl('file://' + __dirname + '/index.html');
// Open the devtools.
// mainWindow.openDevTools();
// Emitted when the window is closed.
mainWindow.on('closed', function () {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
mainWindow = null;
});
});
//My html file
<html>
<head>
<!--<script type="text/javascript" src = "index.js"/>-->
<script type="text/javascript" src="index.js"></script>
<script type="text/javascript">
alert(dps); --- not getting dps value here(/anywhere in html)
</head>
</html>
Your code in index.html runs in a different process (usually called the Renderer process) than your in index.js (which runs in the Main process). Furthermore, your index.js file is a module, therefore, even if the two were run by the same process, you'd have to export your dps variable or make it a global variable like global.dps (however, that's a bad practice!).
There are a number of ways to share date between the Main and the Renderer process; Electron provides the ipcMain, ipcRenderer and remote modules for that purpose; you can also pass data from Main to the Renderer using URL encoded parameters (but this doesn't help you if the data changes); finally, you can use any other form of IPC (e.g., send messages over a socket, use shared memory or shared files, etc.) -- but it's a good idea to start with Electron's ipc or remote modules.
Having said that, in your case, the consensus seems to be not to use the Main process for DB access only to then pass the information on to the Renderer, but rather access the DB directly from the Renderer; that way you won't have to worry about IPC at all (at least in this case).
I'm using the BrowserWindow to display an app and I would like to force the external links to be opened in the default browser. Is that even possible or I have to approach this differently?
I came up with this, after checking the solution from the previous answer.
mainWindow.webContents.on('new-window', function(e, url) {
e.preventDefault();
require('electron').shell.openExternal(url);
});
According to the electron spec, new-window is fired when external links are clicked.
NOTE: Requires that you use target="_blank" on your anchor tags.
new-window is now deprecated in favor of setWindowOpenHandler in Electron 12 (see https://github.com/electron/electron/pull/24517).
So a more up to date answer would be:
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
shell.openExternal(url);
return { action: 'deny' };
});
Improved from the accepted answer ;
the link must be target="_blank" ;
add in background.js(or anywhere you created your window) :
window.webContents.on('new-window', function(e, url) {
// make sure local urls stay in electron perimeter
if('file://' === url.substr(0, 'file://'.length)) {
return;
}
// and open every other protocols on the browser
e.preventDefault();
shell.openExternal(url);
});
Note : To ensure this behavior across all application windows, this code should be run after each window creation.
If you're not using target="_blank" in your anchor elements, this might work for you:
const shell = require('electron').shell;
$(document).on('click', 'a[href^="http"]', function(event) {
event.preventDefault();
shell.openExternal(this.href);
});
I haven't tested this but I assume this is should work:
1) Get WebContents of the your BrowserWindow
var wc = browserWindow.webContents;
2) Register for will-navigate of WebContent and intercept navigation/link clicks:
wc.on('will-navigate', function(e, url) {
/* If url isn't the actual page */
if(url != wc.getURL()) {
e.preventDefault();
openBrowser(url);
}
}
3) Implement openBrowser using child_process. An example for Linux desktops:
var openBrowser(url) {
require('child_process').exec('xdg-open ' + url);
}
let me know if this works for you!
For anybody coming by.
My use case:
I was using SimpleMDE in my app and it's preview mode was opening links in the same window. I wanted all links to open in the default OS browser. I put this snippet, based on the other answers, inside my main.js file. It calls it after it creates the new BrowserWindow instance. My instance is called mainWindow
let wc = mainWindow.webContents
wc.on('will-navigate', function (e, url) {
if (url != wc.getURL()) {
e.preventDefault()
electron.shell.openExternal(url)
}
})
Check whether the requested url is an external link. If yes then use shell.openExternal.
mainWindow.webContents.on('will-navigate', function(e, reqUrl) {
let getHost = url=>require('url').parse(url).host;
let reqHost = getHost(reqUrl);
let isExternal = reqHost && reqHost != getHost(wc.getURL());
if(isExternal) {
e.preventDefault();
electron.shell.openExternal(reqUrl);
}
}
Put this in renderer side js file. It'll open http, https links in user's default browser.
No JQuery attached! no target="_blank" required!
let shell = require('electron').shell
document.addEventListener('click', function (event) {
if (event.target.tagName === 'A' && event.target.href.startsWith('http')) {
event.preventDefault()
shell.openExternal(event.target.href)
}
})
For Electron 5, this is what worked for me:
In main.js (where you create your browser window), include 'shell' in your main require statement (usually at the top of the file), e.g.:
// Modules to control application life and create native browser window
const {
BrowserWindow,
shell
} = require('electron');
Inside the createWindow() function, after mainWindow = new BrowserWindow({ ... }), add these lines:
mainWindow.webContents.on('new-window', function(e, url) {
e.preventDefault();
shell.openExternal(url);
});
I solved the problem by the following step
Add shell on const {app, BrowserWindow} = require('electron')
const {app, BrowserWindow, shell} = require('electron')
Set nativeWindowOpen is true
function createWindow () {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 1350,
height: 880,
webPreferences: {
nativeWindowOpen: true,
preload: path.join(__dirname, 'preload.js')
},
icon: path.join(__dirname, './img/icon.icns')
})
Add the following listener code
mainWindow.webContents.on('will-navigate', function(e, reqUrl) {
let getHost = url=>require('url').parse(url).host;
let reqHost = getHost(reqUrl);
let isExternal = reqHost && reqHost !== getHost(wc.getURL());
if(isExternal) {
e.preventDefault();
shell.openExternal(reqUrl, {});
}
})
reference https://stackoverflow.com/a/42570770/7458156 by cuixiping
I tend to use these lines in external .js script:
let ele = document.createElement("a");
let url = "https://google.com";
ele.setAttribute("href", url);
ele.setAttribute("onclick", "require('electron').shell.openExternal('" + url + "')");
I'm using page-mod to attach content script to all open tabs !
After that at cretain moment/event i want to remove all attached content scripts from all open tabs!
How can i do that ? .... using already sdk 1.11
myPanel.port.on('userlogged', function(rdata) {
var workers= [];
function detachWorker(worker, workerArray) {
var index = workerArray.indexOf(worker);
if(index != -1) {
workerArray.splice(index, 1);
}
}
var pMod = pageMod.PageMod({
include: "*",
contentScriptWhen: "end",
contentScriptFile: data.url("sas_tb.js"),
attachTo: ["existing", "top", "frame"],
onAttach: function(worker) {
workers.push(worker);
worker.on('detach', function () {
detachWorker(this, workers);
});
worker.port.emit('logged', rdata.logged);
}
});
});
So the contentScriptFile will be attached to all open tabs in the browser, but if i want to ... say logout from my addon how can i remove the contentScriptFile from all attached tabs/workers!?
Explicitly call the Worker's destroy method and the SDK will take care of the content scripts