Как я могу получить доступ к содержимому «веб-просмотра» (Электрон/NW.JS)?

Я использовал тег <webview> вместо iFrames, но не смог найти подробную информацию о документы NWJS, документы Electronic, а также фактический < href="https://developer.chrome.com/apps/tags/webview" rel="nofollow noreferrer"><webview> docs для доступа к содержимому внутри него.

Я хотел получить document.title из <webview> и отправить его обратно в основной процесс.


person Nick    schedule 11.01.2019    source источник


Ответы (1)


Основное решение, которое я использовал для связи между основным процессом и внутри <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);
}

Как это работает (по крайней мере, один способ, который я нашел):

  1. Во-первых, привяжите EventListeners из основного процесса к <webview> и окну (чтобы позже прослушивать сообщение, приходящее из <webview>)
  2. Когда элемент <webview> загружает URL-адрес, он запускает contentload()
  3. contentload() внедрит EventListener в <webview> и настроит элементы данных/DOM, которые мы хотим получить изнутри <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) с тем преимуществом, что он выполняется в отдельном процессе. Это намного лучше для производительности, чем куча фреймов. <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