LoginSignup
0
0

More than 1 year has passed since last update.

Electronで埋め込みページをwebViewで挿入し、webView内にあるリンク(target="_blank")を同一windowで開く方法

Last updated at Posted at 2023-01-13

Electronアプリを作成している中で、以下のようなシチュエーションにぶち当たりました。

ステップ1:Electronアプリ内でリンクをクリック
↓
ステップ2:新しいElectron windowでリンク先のページをナビゲーションバー付きで開く
↓
ステップ3:ステップ2で開いたwindow内にあるリンク(target="_blank"付き)をクリックした際に、新しいwindowで開くのではなく2個目に開いたwindow内で表示させたい

Electronの公式サイトでは以下3つの方法が紹介されていましたが、今回はWebViewsを利用した実装方法を紹介します。

  1. Iframes
  2. WebViews
  3. 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と合わせるとこんな感じ。

navigation.html
<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">&lt;</button>
            <button onclick="goForwardPage()" id="forward">&gt;</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プロセスで読み込んで表示させました。

main.ts
    // 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を使って制御ができるみたいです。
実際に追加したものとしては以下のような感じです。

main.ts
    // 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に移行したいと思っています。

何かのお役に立てれば幸いです :bow:

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0