Electronアプリを作成している中で、以下のようなシチュエーションにぶち当たりました。
ステップ1:Electronアプリ内でリンクをクリック
↓
ステップ2:新しいElectron windowでリンク先のページをナビゲーションバー付きで開く
↓
ステップ3:ステップ2で開いたwindow内にあるリンク(target="_blank"付き)をクリックした際に、新しいwindowで開くのではなく2個目に開いたwindow内で表示させたい
Electronの公式サイトでは以下3つの方法が紹介されていましたが、今回はWebViewsを利用した実装方法を紹介します。
- Iframes
- WebViews
- BrowserViews
ただしWebViewsはdeprecatedなので、将来的にはBrowserViewsに移行したい。
参考:https://www.electronjs.org/docs/latest/tutorial/web-embeds
ちなみにアプリは、Electron + webpack + react で開発しています。
WebViewsの使い方
使い方自体はとっても簡単で、<webview>
をHTML内に挿入するだけです。
<webview id="hoge" src="https://www.hogehoge"></webview>
埋め込む画面の高さ・幅を変えたい場合はstyleタグを入れます。
<webview id="hoge" src="https://www.hogehoge"
style="width:200px; height:150px"></webview>
ナビゲーションバーの作り方
今回はシンプルに「戻る」「進む」のみを作成しています。
cssも少量なので直接HTML内に書いています。
WebViewsと合わせるとこんな感じ。
<html>
<head>
<style>
.header {
top: 0;
width: 100%;
border:1px solid #ccc;
display: flex;
align-items: center;
}
button {
margin:5px;
}
</style>
</head>
<body style="height: 100%; margin:0">
<div>
<nav class="header">
<button onclick="goBackPage()" id="back"><</button>
<button onclick="goForwardPage()" id="forward">></button>
</nav>
<div>
<webview id="hoge" src="https://www.hogehoge"
style="width:200px; height:150px"
></webview>
</div>
</div>
</body>
</html>
Electron内でとあるリンクをクリックした際に、このHTMLを読み込んで新しいWindowで表示させる感じです。
「戻る」「進む」ボタンの機能実装
scriptもシンプルなのでnavigation.html内に入れちゃってます。
<script>
const gameView = document.getElementById('hoge')
const backButton = document.getElementById('back')
const forwardButton = document.getElementById('forward')
const goBackPage = () => {
if(gameView.canGoBack()) {
gameView.goBack()
}
}
const goForwardPage = () => {
if(gameView.canGoForward()) {
gameView.goForward()
}
}
// 戻るや進むページがない時に disableにしています
const check = () => {
backButton.disabled = !gameView.canGoBack();
forwardButton.disabled = !gameView.canGoForward();
}
const goBackToGame = () => {
gameView.goToIndex(0)
}
gameView.addEventListener("dom-ready", () => {
check()
})
gameView.addEventListener("did-frame-navigate", () => {
check()
})
gameView.addEventListener("did-navigate-in-page", () => {
check()
})
</script>
Electron側での読み込み
このようにして作成したHTMLをElectronのmainプロセスで読み込んで表示させました。
// ipcRender.onを使ってrendererでリンクがクリックされた際にこのイベントがよばれる形です。
// argumentとかpreloadとかは色々省略しています。
ipcMain.on("some-event" (e) => {
const width = 1400;
const height = 1300;
const win = new BrowserWindow({
width,
height,
title,
webPreferences: {
devTools: isDev,
preload,
contextIsolation: false,
nodeIntegrationInSubFrames: true,
webviewTag: true,
},
});
win.webContents.loadFile('./navigation.html'); // ここでHTMLを読み込み
//....
})
ここまでで一旦、ステップ2まで実装することができました。
ステップ3なんて簡単だろーって思っていましたが、結局一番時間がかかりました。。。
ステップ1:Electronアプリ内でリンクをクリック
↓
ステップ2:新しいelectron windowでリンク先のページをナビゲーションバー付きで開く
↓
ステップ3:ステップ2で開いたwindow内にあるリンク(target="_blank"付き)をクリックした際に、新しいwindowで開くのではなく2個目に開いたwindow内で表示させたい
まず前提として、WebViewsではポップアップがデフォルトでfalseになっているので、target="_blank"付きのリンクをクリックしても開きません。ポップアップを有効にするためには、allowpopups
をwebviewタグにつけてあげる必要があります。
<webview id="hoge" src="https://www.hogehoge"
style="width:200px; height:150px"
allowpopups
></webview>
これでリンク自体は開くようになりますが、これだと新しいwindowで開くことになります。
今回はあくまでもステップ2で生成されたwindow内でページ遷移をしたかったのです。
調べてみたところ、どうやらmainプロセスでwebContentsを使って制御ができるみたいです。
実際に追加したものとしては以下のような感じです。
// ipcMain.onを使ってrendererでリンクがクリックされた際にこのイベントがよばれる形です。
// argumentとかpreloadとかは色々省略しています。
ipcMain.on("some-event" (e) => {
const width = 1400;
const height = 1300;
const win = new BrowserWindow({
width,
height,
title,
webPreferences: {
devTools: isDev,
preload,
contextIsolation: false,
nodeIntegrationInSubFrames: true,
webviewTag: true,
},
});
win.webContents.loadFile('./navigation.html');
// 以下追加
win.webContents.on('will-attach-webview', (_, webPreferences, params) => {
webPreferences.contextIsolation = false;
webPreferences.nodeIntegrationInSubFrames = true;
webPreferences.preload = preload;
params.src = url;
});
win.webContents.on('did-attach-webview', (_, webViewContents) => {
webViewContents.setWindowOpenHandler(({ url }) => {
webViewContents.loadURL(url);
return {
action: 'deny',
};
});
});
//....
})
色々設定はしていますが、肝心の新しいwindowで開かないようにするために大事なのはここです。
win.webContents.on('did-attach-webview', (_, webViewContents) => {
webViewContents.setWindowOpenHandler(({ url }) => {
webViewContents.loadURL(url);
return {
action: 'deny',
};
});
});
action: deny
とすることで新しいwindowで開かないように設定できました。
allowpopups
でtarget付きのリンクを開くことは許容するけど、新しいwindowはやめてね、みたいな感じになっているのだと理解しています。
上記以外にも、ipcRendererを使った方法や、aタグ内のtarget="_blank"
を取り除いちゃう方法なども発見しました。
参考にした記事: http://var.blog.jp/archives/86316642.html
今回はwebpackも利用しているのでなるべくconfigをいじらずにできる方法を模索してたどり着きましたが、
きっともっといい方法あるはず!
そしていずれは、BrowserViewsに移行したいと思っています。
何かのお役に立てれば幸いです