使用 ipcRenderer 和 ipcmain 将消息从 preload.js 发送到 main.js 时出现问题
Issue while sending message from preload.js to main.js using ipcRenderer and ipcmain
我是使用 Electron 开发桌面应用程序的初学者,当我尝试使用 main.js 中的 ipcRenderer 和 ipcMain 从预加载脚本向 main.js 发送消息时,详细信息显示在 VScode 中终端,没有任何反应(没有事件发生)甚至 console.log 都不起作用
{
preventDefault: [Function: preventDefault],
sender: EventEmitter {
isDestroyed: [Function: isDestroyed],
destroy: [Function: destroy],
getBackgroundThrottling: [Function: getBackgroundThrottling],
setBackgroundThrottling: [Function: setBackgroundThrottling],
getProcessId: [Function: getProcessId],
getOSProcessId: [Function: getOSProcessId],
equal: [Function: equal],
_loadURL: [Function: _loadURL],
reload: [Function: reload],
reloadIgnoringCache: [Function: reloadIgnoringCache],
downloadURL: [Function: downloadURL],
getURL: [Function: getURL],
getTitle: [Function: getTitle],
isLoading: [Function: isLoading],
isLoadingMainFrame: [Function: isLoadingMainFrame],
isWaitingForResponse: [Function: isWaitingForResponse],
stop: [Function: stop],
canGoBack: [Function: canGoBack],
goBack: [Function: goBack],
canGoForward: [Function: canGoForward],
goForward: [Function: goForward],
canGoToOffset: [Function: canGoToOffset],
goToOffset: [Function: goToOffset],
canGoToIndex: [Function: canGoToIndex],
goToIndex: [Function: goToIndex],
getActiveIndex: [Function: getActiveIndex],
clearHistory: [Function: clearHistory],
length: [Function: length],
isCrashed: [Function: isCrashed],
forcefullyCrashRenderer: [Function: forcefullyCrashRenderer],
setUserAgent: [Function: setUserAgent],
getUserAgent: [Function: getUserAgent],
savePage: [Function: savePage],
openDevTools: [Function: openDevTools],
closeDevTools: [Function: closeDevTools],
isDevToolsOpened: [Function: isDevToolsOpened],
isDevToolsFocused: [Function: isDevToolsFocused],
enableDeviceEmulation: [Function: enableDeviceEmulation],
disableDeviceEmulation: [Function: disableDeviceEmulation],
toggleDevTools: [Function: toggleDevTools],
inspectElement: [Function: inspectElement],
setIgnoreMenuShortcuts: [Function: setIgnoreMenuShortcuts],
setAudioMuted: [Function: setAudioMuted],
isAudioMuted: [Function: isAudioMuted],
isCurrentlyAudible: [Function: isCurrentlyAudible],
undo: [Function: undo],
redo: [Function: redo],
cut: [Function: cut],
copy: [Function: copy],
paste: [Function: paste],
pasteAndMatchStyle: [Function: pasteAndMatchStyle],
delete: [Function: delete],
selectAll: [Function: selectAll],
unselect: [Function: unselect],
replace: [Function: replace],
replaceMisspelling: [Function: replaceMisspelling],
findInPage: [Function: findInPage],
stopFindInPage: [Function: stopFindInPage],
focus: [Function: focus],
isFocused: [Function: isFocused],
sendInputEvent: [Function: sendInputEvent],
beginFrameSubscription: [Function: beginFrameSubscription],
endFrameSubscription: [Function: endFrameSubscription],
startDrag: [Function: startDrag],
attachToIframe: [Function: attachToIframe],
detachFromOuterFrame: [Function: detachFromOuterFrame],
isOffscreen: [Function: isOffscreen],
startPainting: [Function: startPainting],
stopPainting: [Function: stopPainting],
isPainting: [Function: isPainting],
setFrameRate: [Function: setFrameRate],
getFrameRate: [Function: getFrameRate],
invalidate: [Function: invalidate],
setZoomLevel: [Function: setZoomLevel],
getZoomLevel: [Function: getZoomLevel],
setZoomFactor: [Function: setZoomFactor],
getZoomFactor: [Function: getZoomFactor],
getType: [Function: getType],
_getPreloadPaths: [Function: _getPreloadPaths],
getLastWebPreferences: [Function: getLastWebPreferences],
getOwnerBrowserWindow: [Function: getOwnerBrowserWindow],
inspectServiceWorker: [Function: inspectServiceWorker],
inspectSharedWorker: [Function: inspectSharedWorker],
inspectSharedWorkerById: [Function: inspectSharedWorkerById],
getAllSharedWorkers: [Function: getAllSharedWorkers],
_print: [Function: _print],
_printToPDF: [Function: _printToPDF],
_setNextChildWebPreferences: [Function: _setNextChildWebPreferences],
addWorkSpace: [Function: addWorkSpace],
removeWorkSpace: [Function: removeWorkSpace],
showDefinitionForSelection: [Function: showDefinitionForSelection],
copyImageAt: [Function: copyImageAt],
capturePage: [Function: capturePage],
setEmbedder: [Function: setEmbedder],
setDevToolsWebContents: [Function: setDevToolsWebContents],
getNativeView: [Function: getNativeView],
incrementCapturerCount: [Function: incrementCapturerCount],
decrementCapturerCount: [Function: decrementCapturerCount],
isBeingCaptured: [Function: isBeingCaptured],
setWebRTCIPHandlingPolicy: [Function: setWebRTCIPHandlingPolicy],
getWebRTCIPHandlingPolicy: [Function: getWebRTCIPHandlingPolicy],
takeHeapSnapshot: [Function: takeHeapSnapshot],
setImageAnimationPolicy: [Function: setImageAnimationPolicy],
_getProcessMemoryInfo: [Function: _getProcessMemoryInfo],
id: 1,
session: [Getter],
hostWebContents: [Getter],
devToolsWebContents: [Getter],
debugger: [Getter],
mainFrame: [Getter],
_windowOpenHandler: null,
_events: [Object: null prototype] {
'-ipc-message': [Function (anonymous)],
'-ipc-invoke': [Function (anonymous)],
'-ipc-message-sync': [Function (anonymous)],
'-ipc-ports': [Function (anonymous)],
crashed: [Function (anonymous)],
'render-process-gone': [Function (anonymous)],
'devtools-reload-page': [Function (anonymous)],
'-new-window': [Function (anonymous)],
'-will-add-new-contents': [Function (anonymous)],
'-add-new-contents': [Function (anonymous)],
login: [Function (anonymous)],
'ready-to-show': [Function (anonymous)],
'select-bluetooth-device': [Function (anonymous)]
},
_eventsCount: 13
},
frameId: 1,
processId: 4,
reply: [Function (anonymous)]
}
在按钮上单击将调用来自预加载的函数,该函数将向主进程发送消息
renderer.js
button.addEventListener('click', function() {
window.LoadProductWindow.load();
});
preload.js
const {
contextBridge,
electron,
ipcRenderer
} = require('electron')
contextBridge.exposeInMainWorld('LoadProductWindow', {
load: () => ipcRenderer.send('load_product_window')
})
main.js
ipcMain.on('load_product_window', () => {
console.log('received')
});
请解决我的问题,我也想添加一个新的 window 单击按钮添加新项目我该怎么做?
谢谢
毫无疑问,很多例子展示了
Electron 的 Inter-Process Communication 和 preload.js
是
复杂且难以理解。一旦你有了坚定的认识
Context-Isolation 然后事情会开始下降
到位。
我最近发现的主要问题是很多人都在破解
Separation of Concerns 设计原则
他们的 preload.js
脚本。虽然这不是硬性规定,但通过移动所有内容来简化 preload.js
脚本
除了纯通信逻辑之外的逻辑使得代码非常简单、可读。
首先,让我们定义一个简单易读的 preload.js
脚本。
在此脚本中,您会发现一个包含 3 个数组(send
、receive
和 sendReceive
)的 ipc
对象,您可以在其中
定义您的 white-listed 频道名称。当从主调用时
thread 或 render thread,它们会毫无阻碍地通过。使用的任何和所有其他频道名称不是
在这 3 个数组中定义的将被阻止。
在这里,我已将通道名称 productWindow:load
添加到 send
(从渲染到主线程)数组。
preload.js
(主线程)
'use strict';
// 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': [
'productWindow:load'
],
// 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);
}
}
}
);
接下来,我们将快速定义您的 index.html
文件,其中将包含 Javascript 发送 IPC 消息的功能
按下按钮后到主线程。
index.html
(渲染线程)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Main Page</title>
</head>
<body>
<label for="button">Product Window: </label>
<input type="button" id="button" value="Load Now">
</body>
<script>
document.getElementById('button').addEventListener('click', () => {
window.ipcRender.send('productWindow:load');
})
</script>
</html>
product.html
(渲染线程)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Product Page</title>
</head>
<body>
<h1>It Works..!</h1>
</body>
</html>
最后,在您的 main.js
脚本(作为您的应用程序的入口点)中,我们会监听一条消息
productWindow:load
通道使用 Electron 的 ipcMain
模块,特别是 ipcMain.on()
方法。
main.js
(主线程)
const electronApp = require('electron').app;
const electronBrowserWindow = require('electron').BrowserWindow;
const electronIpcMain = require('electron').ipcMain;
const nodePath = require("path");
let mainWindow;
let productWindow;
function createMainWindow() {
const mainWindow = new electronBrowserWindow({
x: 0,
y: 0,
width: 800,
height: 600,
show: false,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: nodePath.join(__dirname, 'preload.js')
}
});
mainWindow.loadFile('index.html')
.then(() => { mainWindow.show(); });
return mainWindow;
}
function createProductWindow(parentWindow) {
const productWindow = new electronBrowserWindow({
x: 0,
y: 0,
width: 800,
height: 600,
parent: parentWindow,
show: false,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: nodePath.join(__dirname, 'preload.js')
}
});
productWindow.loadFile('product.html')
.then(() => { productWindow.show(); });
return productWindow;
}
electronApp.on('ready', () => {
mainWindow = createMainWindow();
});
// Let's listen for the 'productWindow:load' signal.
electronIpcMain.on('productWindow:load', () => {
productWindow = createProductWindow(mainWindow);
});
electronApp.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
electronApp.quit();
}
});
electronApp.on('activate', () => {
if (electronBrowserWindow.getAllWindows().length === 0) {
createMainWindow();
}
});
随着您的 Electron 应用程序开始增长,您将希望开始将 main.js
文件拆分成更小的文件,
到 'separate your concerns'。 IE:将不同的 window 创建代码段移动到它们自己的文件中,包括它们相关的 IPC 代码。
这将改善您的代码结构,简化可读性并使代码易于维护
代码库。
我是使用 Electron 开发桌面应用程序的初学者,当我尝试使用 main.js 中的 ipcRenderer 和 ipcMain 从预加载脚本向 main.js 发送消息时,详细信息显示在 VScode 中终端,没有任何反应(没有事件发生)甚至 console.log 都不起作用
{
preventDefault: [Function: preventDefault],
sender: EventEmitter {
isDestroyed: [Function: isDestroyed],
destroy: [Function: destroy],
getBackgroundThrottling: [Function: getBackgroundThrottling],
setBackgroundThrottling: [Function: setBackgroundThrottling],
getProcessId: [Function: getProcessId],
getOSProcessId: [Function: getOSProcessId],
equal: [Function: equal],
_loadURL: [Function: _loadURL],
reload: [Function: reload],
reloadIgnoringCache: [Function: reloadIgnoringCache],
downloadURL: [Function: downloadURL],
getURL: [Function: getURL],
getTitle: [Function: getTitle],
isLoading: [Function: isLoading],
isLoadingMainFrame: [Function: isLoadingMainFrame],
isWaitingForResponse: [Function: isWaitingForResponse],
stop: [Function: stop],
canGoBack: [Function: canGoBack],
goBack: [Function: goBack],
canGoForward: [Function: canGoForward],
goForward: [Function: goForward],
canGoToOffset: [Function: canGoToOffset],
goToOffset: [Function: goToOffset],
canGoToIndex: [Function: canGoToIndex],
goToIndex: [Function: goToIndex],
getActiveIndex: [Function: getActiveIndex],
clearHistory: [Function: clearHistory],
length: [Function: length],
isCrashed: [Function: isCrashed],
forcefullyCrashRenderer: [Function: forcefullyCrashRenderer],
setUserAgent: [Function: setUserAgent],
getUserAgent: [Function: getUserAgent],
savePage: [Function: savePage],
openDevTools: [Function: openDevTools],
closeDevTools: [Function: closeDevTools],
isDevToolsOpened: [Function: isDevToolsOpened],
isDevToolsFocused: [Function: isDevToolsFocused],
enableDeviceEmulation: [Function: enableDeviceEmulation],
disableDeviceEmulation: [Function: disableDeviceEmulation],
toggleDevTools: [Function: toggleDevTools],
inspectElement: [Function: inspectElement],
setIgnoreMenuShortcuts: [Function: setIgnoreMenuShortcuts],
setAudioMuted: [Function: setAudioMuted],
isAudioMuted: [Function: isAudioMuted],
isCurrentlyAudible: [Function: isCurrentlyAudible],
undo: [Function: undo],
redo: [Function: redo],
cut: [Function: cut],
copy: [Function: copy],
paste: [Function: paste],
pasteAndMatchStyle: [Function: pasteAndMatchStyle],
delete: [Function: delete],
selectAll: [Function: selectAll],
unselect: [Function: unselect],
replace: [Function: replace],
replaceMisspelling: [Function: replaceMisspelling],
findInPage: [Function: findInPage],
stopFindInPage: [Function: stopFindInPage],
focus: [Function: focus],
isFocused: [Function: isFocused],
sendInputEvent: [Function: sendInputEvent],
beginFrameSubscription: [Function: beginFrameSubscription],
endFrameSubscription: [Function: endFrameSubscription],
startDrag: [Function: startDrag],
attachToIframe: [Function: attachToIframe],
detachFromOuterFrame: [Function: detachFromOuterFrame],
isOffscreen: [Function: isOffscreen],
startPainting: [Function: startPainting],
stopPainting: [Function: stopPainting],
isPainting: [Function: isPainting],
setFrameRate: [Function: setFrameRate],
getFrameRate: [Function: getFrameRate],
invalidate: [Function: invalidate],
setZoomLevel: [Function: setZoomLevel],
getZoomLevel: [Function: getZoomLevel],
setZoomFactor: [Function: setZoomFactor],
getZoomFactor: [Function: getZoomFactor],
getType: [Function: getType],
_getPreloadPaths: [Function: _getPreloadPaths],
getLastWebPreferences: [Function: getLastWebPreferences],
getOwnerBrowserWindow: [Function: getOwnerBrowserWindow],
inspectServiceWorker: [Function: inspectServiceWorker],
inspectSharedWorker: [Function: inspectSharedWorker],
inspectSharedWorkerById: [Function: inspectSharedWorkerById],
getAllSharedWorkers: [Function: getAllSharedWorkers],
_print: [Function: _print],
_printToPDF: [Function: _printToPDF],
_setNextChildWebPreferences: [Function: _setNextChildWebPreferences],
addWorkSpace: [Function: addWorkSpace],
removeWorkSpace: [Function: removeWorkSpace],
showDefinitionForSelection: [Function: showDefinitionForSelection],
copyImageAt: [Function: copyImageAt],
capturePage: [Function: capturePage],
setEmbedder: [Function: setEmbedder],
setDevToolsWebContents: [Function: setDevToolsWebContents],
getNativeView: [Function: getNativeView],
incrementCapturerCount: [Function: incrementCapturerCount],
decrementCapturerCount: [Function: decrementCapturerCount],
isBeingCaptured: [Function: isBeingCaptured],
setWebRTCIPHandlingPolicy: [Function: setWebRTCIPHandlingPolicy],
getWebRTCIPHandlingPolicy: [Function: getWebRTCIPHandlingPolicy],
takeHeapSnapshot: [Function: takeHeapSnapshot],
setImageAnimationPolicy: [Function: setImageAnimationPolicy],
_getProcessMemoryInfo: [Function: _getProcessMemoryInfo],
id: 1,
session: [Getter],
hostWebContents: [Getter],
devToolsWebContents: [Getter],
debugger: [Getter],
mainFrame: [Getter],
_windowOpenHandler: null,
_events: [Object: null prototype] {
'-ipc-message': [Function (anonymous)],
'-ipc-invoke': [Function (anonymous)],
'-ipc-message-sync': [Function (anonymous)],
'-ipc-ports': [Function (anonymous)],
crashed: [Function (anonymous)],
'render-process-gone': [Function (anonymous)],
'devtools-reload-page': [Function (anonymous)],
'-new-window': [Function (anonymous)],
'-will-add-new-contents': [Function (anonymous)],
'-add-new-contents': [Function (anonymous)],
login: [Function (anonymous)],
'ready-to-show': [Function (anonymous)],
'select-bluetooth-device': [Function (anonymous)]
},
_eventsCount: 13
},
frameId: 1,
processId: 4,
reply: [Function (anonymous)]
}
在按钮上单击将调用来自预加载的函数,该函数将向主进程发送消息
renderer.js
button.addEventListener('click', function() {
window.LoadProductWindow.load();
});
preload.js
const {
contextBridge,
electron,
ipcRenderer
} = require('electron')
contextBridge.exposeInMainWorld('LoadProductWindow', {
load: () => ipcRenderer.send('load_product_window')
})
main.js
ipcMain.on('load_product_window', () => {
console.log('received')
});
请解决我的问题,我也想添加一个新的 window 单击按钮添加新项目我该怎么做?
谢谢
毫无疑问,很多例子展示了
Electron 的 Inter-Process Communication 和 preload.js
是
复杂且难以理解。一旦你有了坚定的认识
Context-Isolation 然后事情会开始下降
到位。
我最近发现的主要问题是很多人都在破解
Separation of Concerns 设计原则
他们的 preload.js
脚本。虽然这不是硬性规定,但通过移动所有内容来简化 preload.js
脚本
除了纯通信逻辑之外的逻辑使得代码非常简单、可读。
首先,让我们定义一个简单易读的 preload.js
脚本。
在此脚本中,您会发现一个包含 3 个数组(send
、receive
和 sendReceive
)的 ipc
对象,您可以在其中
定义您的 white-listed 频道名称。当从主调用时
thread 或 render thread,它们会毫无阻碍地通过。使用的任何和所有其他频道名称不是
在这 3 个数组中定义的将被阻止。
在这里,我已将通道名称 productWindow:load
添加到 send
(从渲染到主线程)数组。
preload.js
(主线程)
'use strict';
// 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': [
'productWindow:load'
],
// 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);
}
}
}
);
接下来,我们将快速定义您的 index.html
文件,其中将包含 Javascript 发送 IPC 消息的功能
按下按钮后到主线程。
index.html
(渲染线程)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Main Page</title>
</head>
<body>
<label for="button">Product Window: </label>
<input type="button" id="button" value="Load Now">
</body>
<script>
document.getElementById('button').addEventListener('click', () => {
window.ipcRender.send('productWindow:load');
})
</script>
</html>
product.html
(渲染线程)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Product Page</title>
</head>
<body>
<h1>It Works..!</h1>
</body>
</html>
最后,在您的 main.js
脚本(作为您的应用程序的入口点)中,我们会监听一条消息
productWindow:load
通道使用 Electron 的 ipcMain
模块,特别是 ipcMain.on()
方法。
main.js
(主线程)
const electronApp = require('electron').app;
const electronBrowserWindow = require('electron').BrowserWindow;
const electronIpcMain = require('electron').ipcMain;
const nodePath = require("path");
let mainWindow;
let productWindow;
function createMainWindow() {
const mainWindow = new electronBrowserWindow({
x: 0,
y: 0,
width: 800,
height: 600,
show: false,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: nodePath.join(__dirname, 'preload.js')
}
});
mainWindow.loadFile('index.html')
.then(() => { mainWindow.show(); });
return mainWindow;
}
function createProductWindow(parentWindow) {
const productWindow = new electronBrowserWindow({
x: 0,
y: 0,
width: 800,
height: 600,
parent: parentWindow,
show: false,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: nodePath.join(__dirname, 'preload.js')
}
});
productWindow.loadFile('product.html')
.then(() => { productWindow.show(); });
return productWindow;
}
electronApp.on('ready', () => {
mainWindow = createMainWindow();
});
// Let's listen for the 'productWindow:load' signal.
electronIpcMain.on('productWindow:load', () => {
productWindow = createProductWindow(mainWindow);
});
electronApp.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
electronApp.quit();
}
});
electronApp.on('activate', () => {
if (electronBrowserWindow.getAllWindows().length === 0) {
createMainWindow();
}
});
随着您的 Electron 应用程序开始增长,您将希望开始将 main.js
文件拆分成更小的文件,
到 'separate your concerns'。 IE:将不同的 window 创建代码段移动到它们自己的文件中,包括它们相关的 IPC 代码。
这将改善您的代码结构,简化可读性并使代码易于维护 代码库。