LoginSignup
4
3

More than 3 years have passed since last update.

Webviewでブラウジングっぽいことを実現する

Posted at

概要

VSCodeの拡張機能DoxygenPreviewerというのを作ってますが、その機能拡張時に勉強したことを書きます。

なにをしたか

  • Doxygenのプレビューを出せるようになっていたが、リンクを飛んだりできなかったので飛べるようにした

どう実現したか

  • extension側からWebview側のJavaScriptとの間で通信するようにした
  • aタグのclickをトリガーに通信を実行するようにした

このように通信を行い、extensionとwebviewが連携してWebブラウザのような操作をある程度再現できるようにしました。

詳細

何が問題か

VSCodeはHTMLを表示するAPIを提供していますが、セキュリティのためにそれでできることはとても限られています。
たとえばCSSを読み込めなかったりですが、同様にaタグとかのリンクも動かないです。
この対策をググってみると、下記のstackoverflowにヒントがありました1

VSCode: How to enable links in a webpage in the Webview

If you want to update the webview content itself and cannot do this within the webview itself for some reasons, use either a command uri or postMessage to call some function in your main extension's source that update's the webview's html

要するにpostMessageでextensionにwebviewのhtmlを更新させろとのことです。

postMessageの使い方

VSCodeのAPIリファレンスにはpostMessageについてこう書いてあります。

Post a message to the webview content.

Postしたらどうやって受け取れとは書いてません。あと、まずやりたいのはwebviewからextensionにメッセージを投げることです。
これについては実はwebviewのサンプルページ(例のCat Coding)に説明があります。
Passing messages from a webview to an extension
acquireVsCodeApiを使ってどうこう、という感じで、説明は面倒なのでコードを書きます。(私のコードから引用します。)

script/insert_script/message.js
const vscode = acquireVsCodeApi();
function vscode_post_msg(command, data)
{
    obj = {
        command: command,
        data:    data,
    }
    vscode.postMessage(obj);
}

このmessage.jsを前回CSSを埋め込んだ要領でHTMLに埋め込み、実行させてextension側で受け取ります。

src/extension.ts
    panel_obj.webview.onDidReceiveMessage( (ev) => {
        console.log("receive : " + ev);
        if (!ev.data) {
            return;
        }
        const data = ev.data;
        if (ev.command === "link") {
            next_html_load(data, panel_obj, css_src, insert_script_str);
        }
    });

panel_objはwebviewのインスタンスです。このインスタンスはwebviewメンバーでonDidRecieveMessageメソッドを持っていて、これでメッセージの受信イベントを登録できるわけです。

リンクをクリックされたら通信を行う

これは従来のJavaScriptのテクニックなので筆者より読者の方が詳しいと思いますが一応書きます。

script/insert_script/message.js
function link_message(href_str)
{
    const line_id = href_str.replace(/^.+#/, "");
    if (line_id.length > 2) {
        var elm = document.getElementById(line_id);
        if (elm) {
            elm.scrollIntoView();
        }    
    }
    vscode_post_msg("link", href_str);
}
window.onload = () => {
    var a_tags = document.getElementsByTagName("a");
    for (var i = 0; i < a_tags.length; i+=1)
    {   
        var element = a_tags.item(i);
        if (!element.className === "el") {
            continue;
        }
        const href_str = element.href;
        element.addEventListener('click',
            () => {
                link_message(href_str);
            })
    }
}

やってることは:

  • window.onloadでページのロードを待ち
  • getElementsByTagNameでaタグの要素を読み出し
  • リンクさせたいタグがelクラスなのでそれをチェックし
  • addEventListenerでメッセージを送るイベントを登録する

こうしてextensionでメッセージを受信したらwebviewのhtmlを書き換えるようにして、リンクを飛んだように見せかけることができました。

その他

hrefの値に関して(蛇足)

よくわからないのですが、hrefのままだとリンクを飛ぼうとして飛ばない(iframeの中身が初期化される)みたいな動きをします。
ここで、hrefに設定される値(URI)をfile:///にするとリンクを飛ばなくなりました。

webviewをスクロールさせる

これもJavaScriptのテクニックなので何も特別なことはないのですが。
上に示したlink_message()の中でscrollIntoView()を使っていますが、これにより指定した要素までスクロールさせることができます。

extensionからwebviewにメッセージを送る

用意だけしてまだ使ってないのですが、下記のようにしてできます。

src/extension.ts
        const obj = {
            command: "scroll",
            data:    scrl_pos,
        }
        panel_obj.webview.postMessage(obj);
script/insert_script/message.js
window.addEventListener('message', event => {
    const message = event.data; // The JSON data our extension sent
    switch (message.command) {
    }
});

postMessage()を使うのは同じですね。
addEventListenerをtypemessageで使うことでなぜextensionからのメッセージを受け取ることができるのか、そもそもaddEventListenerってなに状態ですが、このようにしてextensionからwebviewにメッセージを送れます。

まとめ

主にextensionとwebviewとのメッセージのやりとりと、あとはJavaScriptのいくつかのテクニックに触れました。JavaScriptはいろんな機能を持っているんだなあと思いました。あと、VSCodeのサンプルページはもうちょっと目次をちゃんと書いてほしいと思いました。しかしサンプルページをもっとよく読めば更なる発見があるかもしれません。


  1. なんか違う解決策で喜んでいる気がしますが、とにかくこの方針で解決させました。 

4
3
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
4
3