webブラウザの試作:ElectronでWebブラウザを作る(その1)

Electronの手習いとして、webブラウザを作成するシリーズです。その初回として、簡単なWebブラウザを試作します。

はじめに

前回の「Node.jsとElectronの開発環境を作成する」にて、Electronアプリの開発環境を作成しました。

本シリーズではElectronの手習いとして、前回作成した環境にてWebブラウザを作成していきます。

本稿ではその第一回として、最小限のブラウザを作成します。

Webブラウザの作成理由

ブラウザを作る理由は、以下です。

メインのプロセスでWebサーバーを立ち上げ、表示はそこを見るようにすれば、結果Webアプリとデスクトップアプリが一度にできます。(デスクトップアプリとしては、セキュリティ警告がでるのでなんですが…。)

そして今やHELPもネットの向こうの時代。たとえインターネットのつなぎ方がわからなくても、インターネットの向こうのHELPを見るしかありません。

ということで、一度ブラウザを作っておけば、様々なアプリに応用が利きそうです。

そして検索してみると、これまた先行してWEBブラウザを作られた方々の記事があり、参考になりそうです。

尚、Electronの環境でセキュアなアプリを作るのは課題が多い様で、単にネットを見るならElectronアプリではなく、専用のWEBブラウザを使うべきとのこと、予め記しておきます。(詳しくは次回にて。)

参考ページと環境

本稿においては、以下の参考にさせて頂きました。

特にOkamoto Mamoruさんの記事は、入門者には大変参考になりました。ありがとうございます。

そして本稿での環境は以下です。

  • Windows10:ver.1607
  • Node.js:v6.9.1
  • npm:3.10.8
  • Electron:1.4.10

特にElectronのバージョンが異なる場合、本稿のソースが動作しない可能性があります。

成熟して安定した技術やソフトウェアを「枯れた」なんていいますが、現時点でElectronは伸び盛りです。参照した様々なページは、例え数か月前だとしても現状と違っていました。この記事もElectronが1.5になったらもう違ってくるでしょう。

ということで、最初に挙げましたご本家のドキュメントと、実際の動作にて、逐一確認されることをお勧めします。

プロジェクト環境の整理

前回作成したプロジェクトのディレクトリ構造は以下のような状態です。

glasspot (B:\workspace\glasspot)
    +- package.json
    +- main.js
    +- index.html
    +- node_modules
        +- .sbin
        :

これをもう少し、整理します。

ディレクトリとファイルファイルの追加

プロジェクトディレクトリの下に「app」というディレクトリを作成し、そこにアプリケーションのソースを格納することにします。他の言語での「src」のイメージですね。そのうちdist、test、docsも作るのでしょう。

サンプルプログラムのmain.jsとindex.htmlはappに移動し、書き換えてアプリに流用します。

そしてindex.htmlはウィンドウのHTMLなので「window.html」にリネームし、さらにapp内にwindow.js、window.cssも作成します。

以上で、プロジェクトディレクトリは以下のようになりました。

glasspot (B:\workspace\glasspot)
+- package.json
+- app
:    +- main.js
:    +- window.html
:    +- window.css
:    +- window.js
+- node_modules
     +- .sbin
     :

package.jsonの修正

package.jsonの、mainの指定を変えます。

{
  "name": "glasspot",
  "version": "0.1.0",
  "description": "",
  "main": "app/main.js",
  "scripts": {
    "start": "node_modules/.bin/electron .",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "electron": "^1.4.10"
  }
}

5行目の値を”app/main.js”に変えました。

おまけに、7行目にアプリ実行のスクリプトを追加しました。「npm start」の入力でアプリが実行できます。
が、npmの起動に時間がかかるので、結局使っていません。

後で気づいたのですが、ここは単に「electron .」のみでOKです。スクリプト内では、node_modules/.binまでのパスをnpmがセットしてくれるようです。

ソースの作成

以下に作成したソースを示します。

main.js

const Electron = require('electron');
const App = Electron.app;
const BrowserWindow = Electron.BrowserWindow;

var MainWin = null;

function CreateWindow () {
    MainWin = new BrowserWindow({
        width: 800,
        height: 600
    });
    MainWin.setMenu(null);
    MainWin.loadURL( 'file://' + __dirname + '/window.html');
    //  MainWin.webContents.openDevTools();

    MainWin.on('closed', function() {
        MainWin = null;
    });
}

App.on('ready', CreateWindow);

App.on('window-all-closed', function() {
    if (process.platform !== 'darwin') {
        App.quit();
    }
});

App.on('activate', function() {
    if (MainWin === null) {
        CreateWindow();
    }
});

前回のサンプルプログラムとほぼ同じです。

ここはメインのプロセスで動きます。

12行のMainWin.setMenuにnullを指定し、メニューバーは表示しないようにしました。

13行のMainWin.loadURLが肝です。ここで次のwindow.htmlを読み込んでいますが、’http://’にすればそのままブラウズできるという…。(しかしセキュリティ的にはザルですが…。)

14行はコメントアウトしていますが、デバッグ時には必須ですので、外しておいた方がよいでしょう。

コーディング標準的には、グローバルなオブジェクトの名前は先頭を大文字にしています。これまたjavascript界の流儀とは異なるかもしれません。コーディング標準は徐々に決めていきたいと思います。

window.html

<html>
<head>
    <title>glasspot</title>
    <link rel="stylesheet" href="window.css">
    <script src="window.js" charset="utf-8"></script>
</head>
<body>
<div id="window">
    <header>
        <input id="urlin" type="text" onkeypress="KeyPressed(event.keyCode);"/>
    </header>
    <div id="view-container">
        <webview id="view" src=""></webview>
    </div>
</div>
</body>
</html>

ウィンドウのHTMLです。ここはレンダラーのプロセスで動きます。

(表示上の)ヘッダにURL入力用のテキストボックスが一つあり、キーを押すごとにKeyPressedという関数(後程登場)がコールされます。

14行目にあるwebviwタグが肝ですね。ここのsrc属性を設定するだけでiframeのように外の世界を表示できます。

window.js

'use strict';
var View = null;    //  element of webview
var Urlin = null;   //  element of input text

window.addEventListener( 'load', function() {
    View = document.getElementById('view');
    Urlin = document.getElementById('urlin');
    Urlin.value = 'https://translate.google.com/';
    View.setAttribute('src',
        'data:text/html,<h1>Hello World!</h1> \
        <p>Please enter only <b>reliable</b> URLs.</p> \
        <p>Electron <script>document.write(process.versions.electron)</script></p>');
}, false);

function KeyPressed(key) {
    if( key!=13) return;
    Urlin.blur();
    View.setAttribute('src', Urlin.value );
//    View.loadURL(Urlin.value);
}

レンダラーのプロセスで動くスクリプトです。

ページのロードが終わると6行目から実行されます。

ここでテキストボックスとwebviewのオブジェクトを取得し、その初期値として、テキストボックスにはGoogle翻訳のURLを、webviewには「Hello Word!」のHTMLを設定しています。

webviewのsrc属性にはURLの代わりに「data:<MIME-Type>,」として直接ページを書くことができます。

15行からが、テキストボックスでキーが押されたときに呼ばれる関数です。

Enterが押されたときに、テキストボックスからフォーカスを外し、その値をwebviewのsrc属性に設定しています。

これだけで、テキストボックスにURLを入力するとそのページが表示されるようになります。

webviewのエレメントからはメソッドのコールも可能です。19行目のコメントアウトはその例で、18行目と入れ替えても同じ動作をしました。

window.css

* {
    margin:0;
    padding:0;
    border: 0 none;
}
html, body, #window {
    width: 100%;
    height: 100%;
}
header {
    height: 22pt;
    background-color: lightgrey;
    font-size: 12pt;
}
input {
    margin: 4px;
    border: 1px solid darkgrey;
    width: calc(100% - 12px);
}
#view-container {
    width: 100%;
    height: calc(100% - 22pt);
}
webview {
    width: 100%;
    height: 100%;
}

いろいろな単位が混在して見づらいかもしれません。文字はpt、その周りはpxです。そしてウィンドウのサイズが変わっても追従するように、100%から固定領域のサイズを引き算しています。

実行する

それでは実行してみましょう。コマンドプロンプトにてプロジェクトのディレクトリに移動し、「npm start↲」(遅ければ「node_modules\.bin\electron .
↲」)で実行します。

初期表示はこんな感じです。

Electronの後のバージョン番号は表示されていません。これは次回のテーマ。

ここでテキストボックスをクリックしてからEnterを押します。

しばらくだんまりですが、そのうち…

Google翻訳が表示されます。

ここでウィンドウのサイズを変えてみると、コンテンツも自然にストレッチしますね。

下方のリンクをクリックすると、これまたそのページに遷移します。

テキストボックスの表示を変えるコードは入っていませんので、URLの表示はそのまま。

再びテキストボックスをクリックし、「translate.」を削除してEnterを押します。

Google検索に遷移しました。URLは恐らくwww付きにリダイレクトされていると思います。このあたりも自動ですね。

縦横のスクロールバーも問題ありません。

以上で確認は終了です。

まとめ

以上、Electronでは簡単にWebを読み込めることがわかりました。

しかしloading表示がないと心配になりますね。これは以降の課題です。

ここまでのコードはGithubに上げておきましたので、よろしければご参照ください。(package.jsonはさらに編集しています。)

次回はセキュリティについて検討してみようと思います。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です