Основное решение, которое я использовал для связи между основным процессом и внутри <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);
}
Как это работает (по крайней мере, один способ, который я нашел):
- Во-первых, привяжите EventListeners из основного процесса к
<webview>
и окну (чтобы позже прослушивать сообщение, приходящее из <webview>
)
- Когда элемент
<webview>
загружает URL-адрес, он запускает contentload()
contentload()
внедрит EventListener в <webview>
и настроит элементы данных/DOM, которые мы хотим получить изнутри <webview>
.
- Как только
<webview>
заканчивает загрузку, он запускает loadstop()
loadstop()
отправит сообщение <webview>
для установления моста. Важно отметить, что здесь я использую webview.contentWindow.postMessage()
вместо window.postMessage()
.
<webview>
отвечает данными, которые мы настроили на шаге 1.
- Когда основной процесс получает ответ (через «сообщение» EventListener) от
<webview>
, он запускает receiveHandshake()
- Теперь внутри
receiveHandshake()
у вас есть доступ к данным, полученным изнутри <webview>
. Это может быть заголовок страницы или то, что вы настроили в файле webviewInjectScript
.
- Наконец, я вызываю
removeListeners()
, чтобы удалить все EventListeners, которые мы установили, но вы можете продолжать отправлять сообщения туда и обратно.
К сведению: в контексте Electron и NWJS тег <webview>
позволяет отображать веб-сайты (например, iframe) с тем преимуществом, что он выполняется в отдельном процессе. Это намного лучше для производительности, чем куча фреймов. <webview>
содержит стандартный HTML-документ, сложность, скажем, iframe заключается в том, что он выполняется в отдельном процессе.
Также есть другой поток с некоторыми другие решения, такие как использование сообщений IPC и использование тега preload
.
ОБНОВЛЕНО: Использование внешнего интерфейса (например, Vue)
Есть другой способ сделать это, более подходящий для Electron. В приведенном ниже коде я использую Vue (2) и Webpack, но основные отличия от приведенной выше реализации заключаются в следующем:
- Используйте Электрон
ipcRenderer
и ipcMain
для отправки сообщений вместо postMessage
- Загрузите скрипт внедрения, используя свойство
preload
Webview вместо использования 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>
миксины/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
});
});
});
person
Nick
schedule
11.01.2019