【问题标题】:Electron secondary window not receiving IPC messagesElectron 二级窗口不接收 IPC 消息
【发布时间】:2021-08-21 23:42:50
【问题描述】:

在附加到我的应用程序运行的 HTML 的打字稿中,我正在为我的设置创建一个新窗口。据我所知,预加载的脚本在打开后也会被加载到新窗口中,但该窗口没有从主脚本接收 IPC 消息。

这是预加载的脚本:

const { contextBridge, ipcRenderer } = require("electron");

console.log("preloaded!");

contextBridge.exposeInMainWorld("api", {
  send: (channel, data) => {
    let validChannels = ["toMain", "select-dirs", "toSettings", "fromSettings"];
    if (validChannels.includes(channel)) {
      ipcRenderer.send(channel, data);
    }
  },
  receive: (channel, func) => {
    let validChannels = ["fromMain", "toSettings", "fromSettings"];
    if (validChannels.includes(channel)) {
      ipcRenderer.on(channel, (event, ...args) => func(...args));
    }
  },
});

这是我附加到第二个窗口的 HTML 的打字稿文件。

(<any>window).api.receive("toSettings", (data: any) => {
  console.log(data);
})

var closeButton: HTMLButtonElement;

var settings = "";
var settignsDir = "";

document.onreadystatechange = () => {
  if (document.readyState == "interactive") {
    (<any>window).api.send("fromSettings", "ready")
    closeButton = <HTMLButtonElement>document.getElementById("closeButton");

    closeButton.addEventListener("click", () => {
      (<any>window).api.send("toMain", "refresh");
      self.close();
    });
  }
};

我正在为我的渲染器使用相同的预加载脚本并使用相同的接收代码,它工作得很好。从第二个窗口的打字稿中,我可以正确地将 IPC 消息发送到主进程。但我无法在第二个窗口收到任何消息。我想我需要通过window.open() 中的特征数组将文件直接重新加载到第二个窗口。哦,这是打开设置窗口的代码。

window.open(
          "./html/settings.html",
          "_blank",
          "top=200,left=600,frame=false,nodeIntegration=no"
        );

根据 Electron 文档,您还可以在第三个字符串中包含一个预加载,但我无法弄清楚文档中没有示例并且我在任何地方都找不到示例。

回应Kdau:

这是请求的代码:

(<any>window).api.receive("fromSettings", (data: any) => {
  (<any>window).api.send("toSettings", "WHAT!");
})

我主要是使用它来查看您所称的设置或子窗口是否正在接收消息。如果您可以澄清子窗口的地址是什么意思,因为我认为预加载脚本应该自动解析,“好的,所以这个通道需要转到这个接收器”。

我想指出,在您包含的代码 sn-p 中返回此错误: 类型参数 '({ url }: HandlerDetails) => { frame: boolean; webPreferences: { nodeIntegration: boolean;预载:字符串; }; } | undefined' 不能分配给类型为 '(details: HandlerDetails) => { action: "deny"; } | { 动作:“允许”; overrideBrowserWindowOptions?: BrowserWindowConstructorOptions |不明确的; }'。 类型'{框架:布尔值; webPreferences: { nodeIntegration: boolean;预载:字符串; }; } | undefined' 不可分配给类型 '{ action: "deny"; } | { 动作:“允许”; overrideBrowserWindowOptions?: BrowserWindowConstructorOptions |不明确的; }'。 类型 'undefined' 不能分配给类型 '{ action: "deny"; } | { 动作:“允许”; overrideBrowserWindowOptions?: BrowserWindowConstructorOptions |不明确的; }'。

我不知道该怎么办。

【问题讨论】:

    标签: javascript typescript electron


    【解决方案1】:

    虽然您可以在第三个参数中传递预加载,但更灵活和可靠的方法是使用 setWindowOpenHandler。所以,在构建你的主窗口之后(假设变量是mainWindow):

    mainWindow.webContents.setWindowOpenHandler(({ url }) => {
      if (url === './html/settings.html') {
        return {
          action: 'allow',
          overrideBrowserWindowOptions: {
            frame: false,
            webPreferences: {
              nodeIntegration: false,
              preload: 'my-child-window-preload-script.js'
            }
          }
        }
      }
    })
    

    但是,无论如何,大多数(全部??)选项都是从父窗口继承的,我想您的 preload 已被继承,这就是您可以从子窗口发送消息的原因。

    至于未在子窗口中接收消息,您的代码不会将消息发送到该窗口的webContents。发送到主窗口的webContents 将不起作用。不幸的是,Electron 中的 IPC 不是一个广播系统,其中每个进程都接收仅按通道过滤的每条消息。渠道功能位于基于流程的定位之上。每个render进程只能向主进程发送消息,并且主进程在发送时必须一次处理一个特定的render进程。

    因此,要在两个渲染进程之间进行通信,请将主进程用作中继。在主进程中,要获取通过window.open 打开的窗口的webContents,您可以侦听来自该进程的消息(例如您在加载后立即发送的消息)和/或调用webContents.getAllWebContents()并遍历以找到正确的。

    【讨论】:

    • 我在问题底部的编辑中包含了请求的代码。是的,在我将打印语句包含在预加载脚本中并显示在子窗口的控制台中之后,它继承了预加载脚本。我也非常感谢您为设置窗口打开处理程序提供一个示例,因为在我看来,电子的文档有点糟糕
    • @MCRM 哎呀!看起来 Electron 文档的一页显示了一种过时的调用 setWindowOpenHandler 的方式。我将答案切换到当前的 API。 (正如您所说,文档存在一些问题。)我还扩展了我的答案以更多地解释 Electron 的 IPC 模型,以及您需要在渲染器之间进行中继的方法。
    • @MCRM 最后一点:在 Electron 文档中提交对该错误的修复时,我注意到子窗口对非安全相关选项的继承将在下一个主要版本中删除版本。
    猜你喜欢
    • 2017-11-24
    • 2017-07-08
    • 2016-01-03
    • 2016-11-05
    • 2017-09-15
    • 1970-01-01
    • 2021-09-13
    • 1970-01-01
    • 2018-02-02
    相关资源
    最近更新 更多