Reputation: 1804
Here I want show the hwnd of a BrowserWindow in a electron window:
// preload.js
window.addEventListener('DOMContentLoaded', () => {
const replaceText = (selector, text) => {
const element = document.getElementById(selector)
if (element) element.innerText = text
}
for (const dependency of ['chrome', 'node', 'electron']) {
replaceText(`${dependency}-version`, process.versions[dependency])
}
replaceText('hwnd-version', window.getNativeWindowHandle().readInt32LE())
})
console.log("xxxxxxx")
// index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<meta http-equiv="X-Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<title>Native Editor</title>
</head>
<body>
<h1>World Editor running</h1>
We are using Node.js <span id="node-version"></span>,
Chromium <span id="chrome-version"></span>,
and Electron <span id="electron-version"></span>,
based on hwnd <span id="hwnd-version"></span>.
<script src="./renderer.js"></script>
</body>
</html>
What I got is the hwnd-version will not be replaced by the hwnd. How to access it?
Upvotes: 1
Views: 898
Reputation: 41
The issue is you are using the globalThis
and its window
object, which is different from Electron's BrowserWindow
object defined in main. Your preload script should use Electron's BrowserWindow
object to access that getNativeWindowHandle()
function. However, this is tricky, so read on.
It is not secure to enable nodeIntegration
nor enableRemoteModule
. Both of those in your Electron window should be set to false when creating, as you have already done. I also enable contextIsolation
and use something similar in the following Stack Overflow answer: How to use preload.js properly in Electron
Require contextBridge
and ipcRenderer
in your preload script and use as follows:
// preload.js
const { contextBridge, ipcRenderer} = require('electron')
contextBridge.exposeInMainWorld("api", {
send: (channel, data) => {
// whitelist channels
let validChannels = ["getBrowserWindowFromMain"]
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data);
}
},
receive: (channel, func) => {
let validChannels = ["sendBrowserWindowToRenderer"]
if (validChannels.includes(channel)) {
ipcRenderer.on(channel, (event, ...args) => {
func(...args)
})
}
}
})
window.addEventListener('DOMContentLoaded', () => {
const replaceText = (selector, text) => {
const element = document.getElementById(selector)
if (element) {
element.innerText = text
}
}
for (const dependency of ['chrome', 'node', 'electron']) {
replaceText(`${dependency}-version`, process.versions[dependency])
}
})
console.log("xxxxxxx")
Require ipcMain
from Electron and change your const win = new BrowserWindow(...)
to win = new BrowserWindow(...)
and be sure to define var win = null
so that it is accessible anywhere in main.js
, and use like so in the following code:
// main.js
const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path')
var win = null;
function createWindow () {
win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: false, // false is default value after Electron v5
contextIsolation: true, // true is default value since Electron v12
preload: path.join(__dirname, 'preload.js'),
enableRemoteModule: false
}
})
win.loadFile('index.html')
}
app.whenReady().then(() => {
createWindow()
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
})
})
ipcMain.on("getBrowserWindowFromMain", (event, args) => {
win.webContents.send("sendBrowserWindowToRenderer", win.getNativeWindowHandle().readInt32LE());
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
This file is fine and can remain as is.
Add the following code to your renderer.js file in addition to what you already have. I found that accessing the new api
object from the globalThis
and it's window
object like window.api.receive
or window.api.send
as defined in the contextBridge
in preload.js
would fail because api
would be undefined during the DomContentLoaded
event.
Here is the code for renderer.js
:
// renderer.js
window.api.receive("sendBrowserWindowToRenderer", (windowHandle) => {
console.log("Window object received.");
const replaceText = (selector, text) => {
const element = document.getElementById(selector)
if (element) {
element.innerText = text
}
}
if (windowHandle) {
replaceText('hwnd-version', windowHandle)
} else {
replaceText('hwnd-version', '*** Could not retrieve window handle for window at index [0] ***')
}
})
window.api.send("getBrowserWindowFromMain", null)
I have tested this code using NodeJS version 18.12.1, NPM version 9.2.0 with Electron version ^22.0.0 in my own workspace in VS Code.
win.webContents.send()
for the second parameter, I only send the integer returned from calling the win.getNativeWindowHandle().readInt32LE()
function. The reason is because win.webContents.send()
serializes the second parameter and the object win
is not serializable. I would think it's for the best, too, to avoid sending large objects to and from the renderer and main processes.Native Editor
for the title
tag. Are you passing this window handle to, say, a C++ module imported in the renderer process to pass to some graphics API like DirectX or Vulkan? If so, let me know how that goes, cause I'm not sure if it will work or not due to the window needing to be created with certain capabilities to use with those APIs, but I wanted to try it. But it is generally not something that's supported, and it breaks cross-platform abilities of Electron becoming dependent on Windows, and then each windowing system (X11, MacOS X, Windows, etc...) would need to have their own way of accessing the electron window, which I guarantee will vary from OS to OS.Upvotes: 3