如何访问 <webview> (Electron/NW.JS) 的内容?
How can I access the contents of a <webview> (Electron/NW.JS)?
我使用的是 <webview>
标签而不是 iFrame,但是找不到关于 NWJS docs, the Electron docs, nor on the actual <webview>
docs 访问其中内容的详细信息。
我想从 <webview>
中检索 document.title
,并将其发送回主进程。
我在主进程和 <webview>
内部进行通信的基本解决方案是使用 Webview 的 ContentWindow.postMessage()
method. This is very similar to 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);
}
它是如何工作的(至少我找到了一种方法):
- 首先,将 EventListeners 从主进程绑定到
<webview>
和 window(稍后监听来自 <webview>
的消息)
- 当
<webview>
元素加载 URL 时,它会触发 contentload()
contentload()
会将 EventListener 注入 <webview>
并设置我们想要从 <webview>
. 内部获取的 data/DOM 元素
- 一旦
<webview>
完成加载,它会触发 loadstop()
loadstop()
将向 <webview>
发送消息以建立桥接。需要注意的是这里我用的是webview.contentWindow.postMessage()
而不是window.postMessage()
.
<webview>
使用我们在步骤 1 中设置的数据进行响应
- 当主进程收到来自
<webview>
的响应(通过EventListener"message")时,它触发receiveHandshake()
- 在
receiveHandshake()
内部,您现在可以访问来自 <webview>
内部的数据。这可以是页面标题——或者您在 webviewInjectScript
. 中配置的任何内容
- 最后,我调用
removeListeners()
删除我们设置的所有 EventListener,但您可以继续发送消息 back-and-forth。
仅供参考——在 Electron 和 NWJS 的上下文中,<webview>
标签允许您呈现网站(如 iframe),好处是它在单独的进程中运行。这比一堆 iframe 的性能要好得多。一个 <webview>
包含一个标准的 HTML 文档,说 iframe 的复杂性是因为它在一个单独的进程中运行。
还有 another thread 一些其他的解决方案,例如使用 IPC 消息,以及使用 preload
标签。
更新:使用 front-end 框架(即 Vue)
还有另一种方法更适合 Electron。在下面的代码中,我使用了 Vue (2) 和 Webpack,但与上述实现的主要区别是:
- 使用 Electron
ipcRenderer
和 ipcMain
发送消息,而不是 postMessage
- 使用 Webview 的
preload
属性 加载注入脚本,而不是使用 executeScript()
。我已经将 :preload
值绑定到 Vue 计算的 属性 (injectScript
),其中 returns 外部 injectWebPageScript.js
文件的路径。
components/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://whosebug.com");
}
};
</script>
mixins/injectWebPageScript.js
const { ipcRenderer } = require("electron");
// Once the Webview's document has been loaded, notify the ipcRenderer
document.addEventListener("DOMContentLoaded", () => {
ipcRenderer.on("requestData", () => {
ipcRenderer.sendToHost("replyData", {
title: document.title,
url: window.location.href
});
});
});
我使用的是 <webview>
标签而不是 iFrame,但是找不到关于 NWJS docs, the Electron docs, nor on the actual <webview>
docs 访问其中内容的详细信息。
我想从 <webview>
中检索 document.title
,并将其发送回主进程。
我在主进程和 <webview>
内部进行通信的基本解决方案是使用 Webview 的 ContentWindow.postMessage()
method. This is very similar to 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);
}
它是如何工作的(至少我找到了一种方法):
- 首先,将 EventListeners 从主进程绑定到
<webview>
和 window(稍后监听来自<webview>
的消息) - 当
<webview>
元素加载 URL 时,它会触发contentload()
contentload()
会将 EventListener 注入<webview>
并设置我们想要从<webview>
. 内部获取的 data/DOM 元素
- 一旦
<webview>
完成加载,它会触发loadstop()
loadstop()
将向<webview>
发送消息以建立桥接。需要注意的是这里我用的是webview.contentWindow.postMessage()
而不是window.postMessage()
.<webview>
使用我们在步骤 1 中设置的数据进行响应- 当主进程收到来自
<webview>
的响应(通过EventListener"message")时,它触发receiveHandshake()
- 在
receiveHandshake()
内部,您现在可以访问来自<webview>
内部的数据。这可以是页面标题——或者您在webviewInjectScript
. 中配置的任何内容
- 最后,我调用
removeListeners()
删除我们设置的所有 EventListener,但您可以继续发送消息 back-and-forth。
仅供参考——在 Electron 和 NWJS 的上下文中,<webview>
标签允许您呈现网站(如 iframe),好处是它在单独的进程中运行。这比一堆 iframe 的性能要好得多。一个 <webview>
包含一个标准的 HTML 文档,说 iframe 的复杂性是因为它在一个单独的进程中运行。
还有 another thread 一些其他的解决方案,例如使用 IPC 消息,以及使用 preload
标签。
更新:使用 front-end 框架(即 Vue)
还有另一种方法更适合 Electron。在下面的代码中,我使用了 Vue (2) 和 Webpack,但与上述实现的主要区别是:
- 使用 Electron
ipcRenderer
和ipcMain
发送消息,而不是postMessage
- 使用 Webview 的
preload
属性 加载注入脚本,而不是使用executeScript()
。我已经将:preload
值绑定到 Vue 计算的 属性 (injectScript
),其中 returns 外部injectWebPageScript.js
文件的路径。
components/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://whosebug.com");
}
};
</script>
mixins/injectWebPageScript.js
const { ipcRenderer } = require("electron");
// Once the Webview's document has been loaded, notify the ipcRenderer
document.addEventListener("DOMContentLoaded", () => {
ipcRenderer.on("requestData", () => {
ipcRenderer.sendToHost("replyData", {
title: document.title,
url: window.location.href
});
});
});