◆ new-window イベントが起きない
◆ 最近ではウィンドウ関係は main プロセスでまとめて制御するのが良いらしい
◆ remote は推奨されなくなってるので ipc 通信で処理を書いていく必要あり
◆ 実現はできたけどすごく面倒

Electron の更新が辛い

Electron ってアップデートが早い上に破壊的変更が多すぎるので古いバージョンで作ったものを最新版に更新するのはとてもつらいです
セキュリティを理由に 以前は簡単にできたことができなくなってるケースもあります
main プロセスで実行しないといけなくなっていたりです

さらに破壊的変更は繰り返されています

指示通りに書き換えたけど動かない → remote が使われていたけど 推奨されなくなったので手動で有効にするオプションが必要
オプションを有効にしたけど動かない → 別パッケージが必要になってるらしい

と言った感じです
ドキュメント上で破壊的変更を順にたどれるならいいのですが 検索しても出たり出なかったりですし 動かなくてググるの繰り返しです
それがあちこちの機能で発生するのでとてもつらいです

Webview

webview では 以前は target="_blank" や window.open で新しいタブを開こうとすると 開かれませんでしたが代わりに new-window イベントが起きていました
タブのような機能をサポートするなら webview を複数作って並べたりもできますが ひとつの webview だけならこのイベント時に webview.src を書き換えればページを移動できます

最新版にするとこの動きができなくなってました
ドキュメント上は new-window というイベントは残っているのに webview からそのイベントは発生しません
allowpopups 属性をつけてポップアップを許可すればイベントは発生しましたが ウィンドウが開かれてしまいます
ウィンドウが開かれると困ります

調べていると main プロセスで webContents を使って制御が必要らしいです
renderer プロセスだけで完結したいのに とても面倒です
やりたいことは新しいタブで開かず ひとつの webview でページを移動するだけなのに

方法

最終的にこういうものになりました

[index.js] (main)
const { app, BrowserWindow, ipcMain, webContents } = require("electron")

const createWindow = () => {
const main_window = new BrowserWindow({
width: 1280,
height: 700,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
webviewTag: true,
},
})

ipcMain.on("webview-init", async (event, id) => {
const webview_webcontents = webContents.fromId(id)

webview_webcontents.setWindowOpenHandler(details => {
main_window.webContents.send("webview-new-window", details.url)
return { action: "deny" }
})
})

main_window.loadFile("page.html")
}
// ...

[page.html] (renderer)
<script type="module" src="page.js"></script>

<webview src="http://var.blog.jp/" allowpopups></webview>

[page.js] (renderer)
const { ipcRenderer } = require("electron")

const webview = document.querySelector("webview")
ipcRenderer.send("webview-init", webview.getWebContentsId())

ipcRenderer.on("webview-new-window", (event, url) => {
webview.src = url
})



remote は推奨されず簡単には使えなくなってるので 推奨される ipc 通信にしました
最初に webview の web contents id というものを取得して main プロセスに送っておきます

main プロセスで id を受け取ったら その id から webContents を取得します
その webContents に setWindowOpenHandler でウィンドウが開かれるときの動作を設定します
new-window イベントの代わりにウィンドウを開こうとしたことを伝えるため webview-new-window というメッセージを renderer プロセスに送っています
このときにウィンドウを開くアクションは全部拒否します

renderer プロセスでは webview-new-window というメッセージを受け取ったら url を webview に設定してページを移動します

webview タグに allowpopups をつけていないと main プロセスで setWindowOpenHandler を設定しても関数が呼び出されないので必要です
allow なのに全拒否されてポップアップは開かない とわかりづらいことになってますが仕方ないです

代替?

単純に新しいタブを使わずにページを移動するだけなのにすごく面倒です
これまで new-window イベントが起きなくなって とりあえずの対処としてやっていた方法の方が楽で良かったんじゃないかと思いました

その方法は単純に target="_blank" を外すというものです
preload を使い webview のページ内で JavaScript を実行します

for (const a of document.querySelectorAll("a[target]")) {
a.removeAttribute("target")
}

動的に DOM を構築してるページに対応するなら MutationObserver を使います
click や mousedown イベント時に都度削除や preventDefault して JavaScript でページ移動させるということもできます

window.open を使うケースはもっと単純で関数を置き換えておきます

window.open = (url) => location = url

目的が new-window イベントの検知ではなく 新しいタブで開こうとせず同じ webview 内で開くことなのでこれでも十分です