如何访问 <webview> (Electron/NW.JS) 的内容?

Nic*_*ick 2 webview electron nwjs

我使用的是<webview>标签而不是 iFrame,但在NWJS 文档电子文档以及有关访问其中内容的实际<webview>文档中找不到太多详细信息。

我想取回document.title从内<webview>,并发送回主过程。

Nic*_*ick 6

我在主进程和 a 内部之间进行通信的基本解决方案<webview>是使用 Webview 的ContentWindow.postMessage()方法。这与window.postMessage(). 通过使用postMessage()——特别是跟踪——event.source我们在主进程和<webview>.

const webview = document.getElementById('your-webview-element');

// <webview> Content is loaded
function contentload() {
  // The following will be injected in the webview
  const webviewInjectScript = `
      var data = {
        title: document.title,
        url: window.location.href
      };

      function respond(event) {
        event.source.postMessage(data, '*');
      }

      window.addEventListener("message", respond, false);
  `;

  webview.executeScript({
    code: webviewInjectScript
  });
}

// <webview> Loading has finished
function loadstop() {
  webview.contentWindow.postMessage("Send me your data!", "*"); // Send a request to the webview
}

// Bind events
webview.addEventListener("contentload", contentload);
webview.addEventListener("loadstop", loadstop);
window.addEventListener("message", receiveHandshake, false); // Listen for response

function receiveHandshake(event) {
  // Data is accessible as event.data.*
  // This is the custom object that was injected during contentload()
  // i.e. event.data.title, event.data.url
  console.log(event.data)

  // Unbind EventListeners
  removeListeners();
}

// Remove all event listeners
function removeListeners() {
  webview.removeEventListener("contentload", contentload);
  webview.removeEventListener("loadstop", loadstop);
  window.removeEventListener("message", receiveHandshake);
}
Run Code Online (Sandbox Code Playgroud)

它是如何工作的(至少我找到了一种方法):

  1. 首先,将主进程中的 EventListeners 绑定到<webview>和 窗口(稍后侦听来自 内部的消息<webview>
  2. <webview>元素加载一个 URL 时,它会触发contentload()
  3. contentload()将一个 EventListener 注入到 中<webview>并设置我们想要从<webview>.
  4. 一旦<webview>完成加载,它就会触发loadstop()
  5. loadstop()将向 发送消息<webview>以建立桥接。需要注意的是,这里我使用webview.contentWindow.postMessage()代替window.postMessage().
  6. <webview>与步骤1中的数据,我们建立响应
  7. 当主进程收到来自 的响应(通过 EventListener“消息”)时<webview>,它会触发receiveHandshake()
  8. 里面receiveHandshake()你现在有机会从内部传来的数据<webview>。这可以是页面标题,也可以是您在webviewInjectScript.
  9. 最后,我调用removeListeners()删除我们设置的所有 EventListeners,但您可以继续来回发送消息。

仅供参考 - 在 Electron 和 NWJS 的上下文中,该<webview>标签允许您呈现网站(如 iframe),其好处是它在单独的进程中运行。这比一堆 iframe 的性能要好得多。A<webview>包含一个标准的 HTML 文档,复杂的说 iframe 是因为它在一个单独的进程中运行。

还有其他一些解决方案的线程,例如使用 IPC 消息和使用preload标签。


更新:使用前端框架(即 Vue)

还有另一种更适合 Electron 的方法。在下面的代码中,我使用了 Vue (2) 和 Webpack,但与上述实现的主要区别是:

  • 使用 ElectronipcRendereripcMain发送消息,而不是postMessage
  • 使用preloadWebview的属性加载注入脚本,而不是使用executeScript(). 我已将该:preload值绑定到 Vue 计算属性 ( injectScript),它返回外部injectWebPageScript.js文件的路径。

组件/myComponent.vue

<template>
  <webview ref="frame" class="frame" :preload="injectScript"/>
</template>

<script>
export default {
  computed: {
    injectScript() {
      const appPath = require("electron").remote.app.getAppPath();
      return `file://${require("path").resolve(
        __dirname,
        "../../mixins/injectWebPageScript.js"
      )}`;
    }
  },
  methods: {
    mySiteLoaderScript(url) {
      const frame = this.$refs.frame;

      // Initialize event listeners on the Webview
      addListeners();

      // Set the URL, start loading
      frame.setAttribute("src", url);

      // Bind events
      function addListeners() {
        frame.addEventListener("dom-ready", contentloaded);
        frame.addEventListener("ipc-message", receiveHandshake);
      }

      // Remove all event listeners
      function removeListeners() {
        frame.removeEventListener("dom-ready", contentloaded);
        frame.removeEventListener("ipc-message", receiveHandshake);
      }

      // Once webview content is loaded, request its data
      function contentloaded() {
        frame.send("requestData");
      }

      // Triggered when we receive a response from the Webview
      // This is the `ipc-message` event
      function receiveHandshake(event) {
        // Only listen to replyData messages
        if (event.channel !== "replyData") return false;

        const data = event.args[0];
        const title = data.title;
        const favicon = data.favicon;

        // Remove listeners once data has been received
        removeListeners();
      }
    }
  },
  mounted() {
    this.mySiteLoaderScript("https://stackoverflow.com");
  }
};
</script>
Run Code Online (Sandbox Code Playgroud)

mixins/injectWebPageScript.js

<template>
  <webview ref="frame" class="frame" :preload="injectScript"/>
</template>

<script>
export default {
  computed: {
    injectScript() {
      const appPath = require("electron").remote.app.getAppPath();
      return `file://${require("path").resolve(
        __dirname,
        "../../mixins/injectWebPageScript.js"
      )}`;
    }
  },
  methods: {
    mySiteLoaderScript(url) {
      const frame = this.$refs.frame;

      // Initialize event listeners on the Webview
      addListeners();

      // Set the URL, start loading
      frame.setAttribute("src", url);

      // Bind events
      function addListeners() {
        frame.addEventListener("dom-ready", contentloaded);
        frame.addEventListener("ipc-message", receiveHandshake);
      }

      // Remove all event listeners
      function removeListeners() {
        frame.removeEventListener("dom-ready", contentloaded);
        frame.removeEventListener("ipc-message", receiveHandshake);
      }

      // Once webview content is loaded, request its data
      function contentloaded() {
        frame.send("requestData");
      }

      // Triggered when we receive a response from the Webview
      // This is the `ipc-message` event
      function receiveHandshake(event) {
        // Only listen to replyData messages
        if (event.channel !== "replyData") return false;

        const data = event.args[0];
        const title = data.title;
        const favicon = data.favicon;

        // Remove listeners once data has been received
        removeListeners();
      }
    }
  },
  mounted() {
    this.mySiteLoaderScript("https://stackoverflow.com");
  }
};
</script>
Run Code Online (Sandbox Code Playgroud)