The Pragmatic Ball boy

iOSを中心にやってる万年球拾いの老害エンジニアメモ

Electronに見えないwebviewを埋め込んで処理させる

すでにWeb版が存在してるけどショボいのでネイティブアプリでいい感じにしたいときがあったりすることもあります。

これを、Electronで作ったネイティブアプリの画面上に見えないwebviewを置くことで実現します。

方法

見えないwebviewの埋め込み

Electronアプリにwebviewを埋め込むことは簡単でただタグを使うだけでできます。

次にこれを非表示にします。 非表示というと普通はdispaly: noneを使いますが、display: noneでは残念ながらwebviewの中身が実行されません。 そこでwidthとheightを0にしてやることで非表示にします。

webviewの通信

webviewはrendererプロセスとは別プロセスになるのでwebview内のjsとrendererプロセスのjsでやり取りするにはプロセス間通信をする必要があります。

rendererプロセス→webview

webviewエレメントのsendというメソッドがあるのでこれを叩くことで、webview側にイベントを送ることができます。

webview側でこのイベントを受け取るには、受け取る用のjsファイルを用意してpreloadを使って読み込ませます。 そしてその受け取る用のjsファイルの中でipcRenderer.onを使ってrendererプロセスからのイベントを受け取ります。

webview->rendererプロセス

webviewからrendererプロセスへのイベントの送信はipcRenderer.sendToHostを使います。

rendererプロセス側でこのイベントを受け取るにはipc-messageをaddEventListererで登録すればよいです。

具体例

electron-vueでやる場合の例です。

<template>
    <div>
        <webview
                id="webview"
                v-bind:preload="preloadJS()"
                src="https://facebook.com"
                style="width:0;height:0;"
        >
        </webview>
    </div>
</template>
<script>
import path from 'path';
import { remote } from 'electron';

let webview = null;

export default {
  data: function() {
    return {
      userId: '',
      password: '',
    }
  }
  mounted: function() {
    // webviewからのイベントの受信
    webview = document.getElementById('webview');
    webview.addEventListener('ipc-message', event => {
      if (event.channel === 'login') {
        switch (event.args[0]) {
          case "loading":
            break;
          case "loaded":
            break;
          case "success":
            console.log('ログイン成功');
            break;
          case "failure":
            console.log('ログイン失敗');
            break;
        }
      }
    });
  },
  methods: {
    preloadJS() {
      return `file:${path.join(__static, 'fb.js')}`;
    },
    login() {
      // webviewへのイベントの送信
      webview.send('login', this.userId, this.password);
    }
  },
// fb.js
const { ipcRenderer } = require('electron');

document.addEventListener('DOMContentLoaded', event => {
  // rendererプロセスからのイベントの受信
  ipcRenderer.on('login', (event, userId, password) => {
    // DOMをゴニョゴニョしてログインする
  });
});

document.addEventListener('readystatechange', event => {
    if (
      window.location.href ===
      'https://facebook.com/sign'
    ) {
      // rendererプロセスへのイベントの送信
      ipcRenderer.sendToHost('login', 'loaded');
    }
});