次の方法で共有


WebView2 アプリでのローカル コンテンツの操作

リモート コンテンツの読み込みに加えて、コンテンツを WebView2 にローカルに読み込むこともできます。 WebView2 コントロールにローカル コンテンツを読み込むには、次のようないくつかの方法があります。

  • ファイル URL への移動。
  • HTML 文字列への移動。
  • 仮想ホスト名マッピング。
  • WebResourceRequested イベントの処理。

これらのアプローチを以下に説明します。

アプローチの選択

WebView2 コントロールにローカル コンテンツを読み込むさまざまな方法では、次のシナリオがサポートされています。

シナリオ ファイル URL に移動する HTML 文字列に移動する 仮想ホスト名マッピングを使用する を使用する WebResourceRequested
配信元ベースの DOM API ✔️ ✔️ ✔️
セキュリティで保護されたコンテキストを必要とする DOM API ✔️ ✔️
動的コンテンツ ✔️ ✔️
その他の Web リソース ✔️ ✔️ ✔️
WebView2 プロセスで解決されたその他の Web リソース ✔️ ✔️

これらのシナリオについては、以下で詳しく説明します。

ファイル URL に移動してローカル コンテンツを読み込む

WebView2 を使用すると、ファイル URL へのナビゲーションを使用して、基本的な HTML または PDF を読み込むことができます。 これは、ローカル コンテンツを読み込むための最も簡単で効率的なアプローチです。 ただし、他のアプローチよりも柔軟性が低くなります。 Web ブラウザーと同様に、ファイル URL は一部の機能で制限されています。

  • ドキュメントには、ファイル パスに固有の配信元があります。 つまり、 localStorageindexedDB などの配信元を必要とする Web API は機能しますが、保存されたデータは、他のファイル パスから読み込まれた他のローカル ドキュメントでは使用できません。

  • 一部の Web API は、セキュリティで保護された HTTPS URL のみに制限されており、ファイル URL によって読み込まれたドキュメントでは使用できません。 これには、ビデオまたはサウンドを取得するための navigator.mediaDevices.getUserMedia() 、デバイスの場所にアクセスするための navigator.geolocation.getCurrentPosition() 、通知を表示するユーザーのアクセス許可を要求する Notification.requestPermission() などの API が含まれます。

  • リソースごとに、完全なパスを指定する必要があります。

  • ファイル URI から他のローカル ファイルへの参照を許可したり、XSL 変換が適用された XML ファイルを表示したりするには、ブラウザー引数 --allow-file-access-from-files 設定できます。 「CoreWebView2EnvironmentOptions.AdditionalBrowserArguments プロパティ」を参照してください。

ファイル URL に移動してローカル コンテンツを読み込む際の考慮事項

ファイル URL は、ブラウザーと同様に動作します。 たとえば、Web ページのコンテキストで作業していないため、ファイル URL に XMLHttpRequest (XHR) を作成することはできません。

リソースごとに、ファイルの完全なパスを指定する必要があります。 例:

file:///C:/Users/username/Documents/GitHub/Demos/demo-to-do/index.html
クロスオリジン リソース

ファイル URL を指定すると、アプリはネットワーク上のドメインではなく、ディスク上のファイルに移動します。 その結果、結果のドキュメントでクロスオリジン リソースを使用することはできません。

配信元ベースの DOM API

ファイル URL を介して読み込まれたドキュメントには、ブラウザーと同様に、ファイル パスに固有の配信元があります。 localStorageindexedDBなどの配信元を必要とする Web API は機能します。 ただし、異なるファイル URL から読み込まれたドキュメントは、同じ配信元とは見なされず、同じ保存されたデータにアクセスできません。

セキュリティで保護されたコンテキストを必要とする DOM API

一部の Web API は、セキュリティで保護された HTTPS URL のみに制限されており、ファイル URL によって読み込まれたドキュメントでは使用できません。 これには、ビデオまたはサウンドを取得するための navigator.mediaDevices.getUserMedia() 、デバイスの場所にアクセスするための navigator.geolocation.getCurrentPosition() 、通知を表示するユーザーのアクセス許可を要求する Notification.requestPermission() などの API が含まれます。 詳細については、「MDN での コンテキストのセキュリティ保護 」を参照してください。

動的コンテンツ

ファイル URL を使用してドキュメントを読み込む場合、ドキュメントのコンテンツはディスク上の静的ファイルから取得されます。 つまり、このローカル コンテンツを動的に変更することはできません。 これは、各応答を動的に生成できる Web サーバーからのドキュメントの読み込みとは異なります。

その他の Web リソース

相対 URL 解決は、ファイル URL を介して読み込まれたドキュメントでも機能します。 つまり、読み込まれたドキュメントには、CSS、スクリプト、イメージ ファイルなどの追加の Web リソースへの参照があり、ファイル URL を介して提供されることもあります。

WebView2 プロセスで解決されたその他の Web リソース

ファイル URL は WebView2 プロセスで解決されます。 これは、ホスト アプリ プロセス UI スレッドで解決される WebResourceRequestedよりも高速なオプションです。

ファイル URL に移動してローカル コンテンツを読み込むための API

ファイル URL の例

このセクションでは、プラットフォームに依存しない方法でローカル コンテンツ ファイル パスのファイル URL がどのように表示されるかを示します。

WebView2 アプリでは、 file:/// プレフィックスとスラッシュを使用してローカル ファイル URL をコーディングする必要があります。 たとえば、Demo To Do の例では、パスは次のようになります。

file:///C:/Users/username/Documents/GitHub/Demos/demo-to-do/index.html

ローカル ファイルの "file" プレフィックスを持つ完全なパスをコピーするには:

  1. 必要に応じて、ローカル コピーを作成できるように Demos リポジトリを複製します。 「Visual Studio Code 用 DevTools 拡張機能のインストール」の「手順 5: Demos リポジトリを複製する」を参照してください。

  2. Microsoft Edge で Ctrl キーを押しながら O キー を押してファイルを開きます。 ローカルに複製されたファイルDemos/demo-to-do/index.htmlなど、ローカル .html ファイルを開きます。

    C:\Users\username\Documents\GitHub\Demos\demo-to-do\index.html

    アドレス バーには、最初は file:/// プレフィックスは表示されませんが、ドライブ文字で始まります。

    C:/Users/username/Documents/GitHub/Demos/demo-to-do/index.html
    

    Microsoft Edge のアドレス バーでは、最初に file:/// プレフィックスが非表示になります

  3. アドレス バーをクリックし、 ホーム キーを押すか、 Ctrl キーを押しながら A キーを押してパス全体を選択します。

    Microsoft Edge のアドレス バーに file:/// プレフィックスが表示されるようになりました

    file:///を含むファイル パス全体がクリップボード バッファーにコピーされるため、file:/// プレフィックスを含む完全なパスを貼り付けることができます。

    file:///C:/Users/username/Documents/GitHub/Demos/demo-to-do/index.html
    

関連項目:

ファイル URL への移動の例

webView.CoreWebView2.Navigate(
          "file:///C:/Users/username/Documents/GitHub/Demos/demo-to-do/index.html");

HTML 文字列に移動してローカル コンテンツを読み込む

ローカル コンテンツを読み込むもう 1 つの方法は、 NavigateToString メソッドです。 この方法では、WebView2 に文字列から直接コンテンツを読み込みます。 これは、アプリ コードを使用してコンテンツをパッケージ化する場合や、コンテンツを動的に作成する場合に便利です。

文字列に移動すると便利な場合があるもう 1 つのシナリオは、URL を使用してアクセスできないコンテンツを読み込む場合です。 たとえば、HTML ドキュメントのメモリ内表現がある場合は、 NavigateToString メソッドを使用して、そのコンテンツを WebView2 コントロールに読み込むことができます。 これは、コンテンツをコントロールに読み込む前にファイルまたはサーバーに書き込む必要を避ける場合に便利です。

HTML 文字列に移動してローカル コンテンツを読み込む際の考慮事項

NavigateToString メソッドに渡される HTML コンテンツ文字列のサイズ制限は 2 MB です。 文字列にインライン追加リソースが含まれている場合、このサイズ制限を超える可能性があります。 このサイズ制限を超えると、"値が想定範囲内に収まらない" というエラーが返されます。

配信元ベースの DOM API

NavigateToString メソッドを使用して読み込まれたドキュメントの場所は about:blank に設定され、配信元は null に設定されます。 つまり、 localStorageindexedDBなど、定義されている配信元に依存する Web API は使用できません。

セキュリティで保護されたコンテキストを必要とする DOM API

一部の Web API はセキュリティで保護された HTTPS URL のみに制限されており、 NavigateToString メソッドを介して読み込まれたドキュメントでは使用できません。場所は about:blank に設定されているためです。 これには、ビデオまたはサウンドを取得するための navigator.mediaDevices.getUserMedia() 、デバイスの場所にアクセスするための navigator.geolocation.getCurrentPosition() 、通知を表示するユーザーのアクセス許可を要求する Notification.requestPermission() などの API が含まれます。 詳細については、「MDN での コンテキストのセキュリティ保護 」を参照してください。

動的コンテンツ

NavigateToString メソッドを使用してローカル コンテンツを読み込む場合は、メソッドのパラメーターとしてコンテンツを直接指定します。 これは、実行時にコンテンツを制御し、必要に応じて動的に生成できることを意味します。

その他の Web リソース

NavigateToString メソッドを使用してローカル コンテンツを読み込むため、結果のドキュメントで CSS、画像、スクリプト ファイルなどの追加の Web リソースを参照することはできません。 メソッドでは、HTML ドキュメントの文字列コンテンツのみを指定できます。 HTML ドキュメントから追加の Web リソースを参照するには、この記事で説明されている他のいずれかの方法を使用するか、HTML ドキュメント内の追加の Web リソースをインラインで表します。

WebView2 プロセスで解決されたその他の Web リソース

NavigateToString は、上記のように追加の Web リソースをサポートしていません。

HTML 文字列に移動してローカル コンテンツを読み込むための API

Web ページの文字列表現の例

Demo To Do Web ページの文字列表現を次に示します。 次の一覧では、読みやすくするために行折り返しが追加されています。 実際には、これらの行は 1 つの長い行に連結されます。

`<html lang="en"><head>\n    
<meta charset="UTF-8">\n    
<meta name="viewport" content="width=device-width, initial-scale=1.0">\n    
<title>TODO app</title>\n    
<link rel="stylesheet" href="styles/light-theme.css" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)">\n    
<link rel="stylesheet" href="styles/dark-theme.css" media="(prefers-color-scheme: dark)">\n    
<link rel="stylesheet" href="styles/base.css">\n    
<link rel="stylesheet" href="styles/to-do-styles.css">\n    
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>📋
</text></svg>">\n  
</head>\n\n  
<body>\n    
<h1>📋 My tasks</h1>\n    
<form>\n      
<div class="new-task-form" tabindex="0">\n        
<label for="new-task">➕ Add a task</label>\n        
<input id="new-task" autocomplete="off" type="text" placeholder="Try typing 'Buy milk'" title="Click to start adding a task">\n        
<input type="submit" value="➡️">\n      
</div>\n      
<ul id="tasks"><li class="divider">No tasks defined</li></ul>\n    
</form>\n\n    \x3Cscript src="to-do.js">\x3C/script>\n  \n\n
</body>
</html>`

上記の文字列を取得するには:

  1. デモに移動 して実行します

  2. Web ページを右クリックし、[ 検査 ] を選択して DevTools を開きます。

  3. DevTools の コンソール で、「: document.body.parentElement.outerHTML」と入力します。 コンソールは、Web ページの文字列表現を出力します。

    Demo To Do Web ページの文字列表現

HTML 文字列への移動の例

// Define htmlString with the string representation of HTML as above.
webView.CoreWebView2.NavigateToString(htmlString);

仮想ホスト名マッピングを使用したローカル コンテンツの読み込み

WebView2 コントロールにローカル コンテンツを読み込むもう 1 つの方法は、仮想ホスト名マッピングを使用することです。 これには、ローカル ドメイン名をローカル フォルダーにマッピングする必要があるため、WebView2 コントロールがそのドメインのリソースを読み込もうとすると、代わりに指定されたローカル フォルダーの場所からコンテンツが読み込まれます。 ドキュメントの配信元も仮想ホスト名になります。

この方法では、 CoreWebView2HostResourceAccessKind 列挙型を使用して、クロスオリジン アクセスを指定できます。

現在の制限により、仮想ホスト名を使用してアクセスされるメディア ファイルの読み込みに時間がかかる可能性があります。

仮想ホスト名マッピングを使用したローカル コンテンツの読み込みに関する考慮事項

配信元ベースの DOM API

仮想ホスト名マッピングを使用して読み込まれたローカル コンテンツは、HTTP または HTTPS URL とそれに対応する配信元を持つドキュメントになります。 つまり、 localStorageindexedDB などの配信元を必要とする Web API が機能し、同じ配信元に属する他のドキュメントは、保存されたデータを使用できます。 詳細については、「MDN の同一配信元ポリシー 」を参照してください。

セキュリティで保護されたコンテキストを必要とする DOM API

一部の Web API は、セキュリティで保護された HTTPS URL のみに制限されています。 仮想ホスト名マッピングを使用すると、ローカル コンテンツの HTTPS URL が提供されます。 つまり、ビデオやサウンドを取得する navigator.mediaDevices.getUserMedia() 、デバイスの場所にアクセスするための navigator.geolocation.getCurrentPosition() 、通知を表示するためのユーザーのアクセス許可を要求する Notification.requestPermission() などの API を使用できます。 詳細については、「MDN での コンテキストのセキュリティ保護 」を参照してください。

動的コンテンツ

仮想ホスト名マッピングを使用してローカル コンテンツを読み込む場合、仮想ホスト名をディスク上の静的ファイルを含むローカル フォルダーにマッピングします。 つまり、このローカル コンテンツを動的に変更することはできません。 これは、各応答を動的に生成できる Web サーバーからのドキュメントの読み込みとは異なります。

その他の Web リソース

仮想ホスト名マッピングによって読み込まれるローカル コンテンツには、相対 URL 解決をサポートする HTTP または HTTPS URL があります。 つまり、読み込まれたドキュメントは、CSS、スクリプト、イメージ ファイルなどの追加の Web リソースへの参照を持つことができます。これは、仮想ホスト名マッピングによっても提供されます。

WebView2 プロセスで解決されたその他の Web リソース

仮想ホスト名 URL は、WebView2 プロセスで解決されます。 これは、ホスト アプリ プロセス UI スレッドで解決される WebResourceRequestedよりも高速なオプションです。

仮想ホスト名マッピングを使用してローカル コンテンツを読み込むための API

仮想ホスト名マッピングの例

webView.CoreWebView2.SetVirtualHostNameToFolderMapping("demo", 
         "C:\Github\Demos\demo-to-do", CoreWebView2HostResourceAccessKind.DenyCors);
webView.CoreWebView2.Navigate("https://demo/index.html");

WebResourceRequested イベントを処理してローカル コンテンツを読み込む

WebView2 コントロールでローカル コンテンツをホストするもう 1 つの方法は、 WebResourceRequested イベントに依存することです。 このイベントは、コントロールがリソースを読み込もうとしたときにトリガーされます。 「ネットワーク要求の カスタム管理」で説明されているように、このイベントを使用して要求をインターセプトし、ローカル コンテンツを提供できます。

WebResourceRequested では、要求ごとにローカル コンテンツの動作をカスタマイズできます。 つまり、インターセプトして独自のコンテンツを提供する要求と、WebView2 コントロールが正常に処理できるようにする要求を決定できます。 ただし、動作をカスタマイズするには、仮想ホスト マッピングなどのより多くのコードが必要であり、適切な応答を構築するには HTTP に関する知識が必要です。

WebView2 の観点からは、リソースはネットワーク経由で取得され、WebView2 は応答の一部としてアプリによって設定されたヘッダーに準拠します。 また、 WebResourceRequested イベントの使用は、各要求に必要なプロセス間の通信と処理のために、他のアプローチよりも遅くなります。

カスタム スキームの登録

カスタム スキームを使用して、WebResourceRequested イベントを生成する Web リソース要求を作成する場合は、「WebView2 の機能と API の概要」の「カスタム スキームの登録」を参照してください。

WebResourceRequested イベントを処理してローカル コンテンツを読み込む際の考慮事項

配信元ベースの DOM API

WebResourceRequestedを介して読み込まれたローカル コンテンツは、HTTP または HTTPS URL とそれに対応する配信元を持つドキュメントになります。 つまり、 localStorageindexedDB などの配信元を必要とする Web API が機能し、同じ配信元に属する他のドキュメントは、保存されたデータを使用できます。 詳細については、「MDN の同一配信元ポリシー 」を参照してください。

セキュリティで保護されたコンテキストを必要とする DOM API

一部の Web API は、セキュリティで保護された HTTPS URL のみに制限されています。 WebResourceRequestedを使用すると、HTTPS URL Web リソース要求を独自のローカル コンテンツに置き換えることができます。 つまり、ビデオやサウンドを取得する navigator.mediaDevices.getUserMedia() 、デバイスの場所にアクセスするための navigator.geolocation.getCurrentPosition() 、通知を表示するためのユーザーのアクセス許可を要求する Notification.requestPermission() などの API を使用できます。 詳細については、「MDN での コンテキストのセキュリティ保護 」を参照してください。

動的コンテンツ

WebResourceRequested経由でローカル コンテンツを読み込む場合は、イベント ハンドラーで読み込むローカル コンテンツを指定します。 これは、実行時にコンテンツを制御し、必要に応じて動的に生成できることを意味します。

その他の Web リソース

WebResourceRequested は、相対 URL 解決をサポートする HTTP または HTTPS URL を介して読み込まれるコンテンツを変更します。 つまり、結果のドキュメントには、CSS、スクリプト、イメージ ファイルなどの追加の Web リソースへの参照があり、 WebResourceRequested経由でも提供されます。

WebView2 プロセスで解決されたその他の Web リソース

ファイル URL または仮想ホスト名マッピングを介してコンテンツを読み込む場合、解決は WebView2 プロセスで行われます。 ただし、 WebResourceRequested イベントはホスト アプリ プロセスの WebView2 UI スレッドで発生し、結果のドキュメントの読み込みが遅くなる可能性があります。

  1. WebView2 は、イベントがホスト アプリ プロセスに送信されるのを待つために、Web ページの読み込みを最初に一時停止します。
  2. その後、WebView2 は UI スレッドが使用可能になるまで待機します。
  3. その後、WebView2 は、アプリ コードがイベントを処理するまで待機します。

これには時間がかかる場合があります。 AddWebResourceRequestedFilterへの呼び出しは、WebResourceRequested イベントを発生させる必要がある Web リソースのみに制限してください。

WebResourceRequested イベントを処理してローカル コンテンツを読み込むための API

WebResourceRequested イベントの処理の例

// Reading of response content stream happens asynchronously, and WebView2 does not 
// directly dispose the stream once it read.  Therefore, use the following stream
// class, which properly disposes when WebView2 has read all data.  For details, see
// [CoreWebView2 does not close stream content](https://github.com/MicrosoftEdge/WebView2Feedback/issues/2513).
class ManagedStream : Stream {
    public ManagedStream(Stream s)
    {
        s_ = s;
    }

    public override bool CanRead => s_.CanRead;

    public override bool CanSeek => s_.CanSeek;

    public override bool CanWrite => s_.CanWrite;

    public override long Length => s_.Length;

    public override long Position { get => s_.Position; set => s_.Position = value; }

    public override void Flush()
    {
        throw new NotImplementedException();
    }

    public override long Seek(long offset, SeekOrigin origin)
    {
        return s_.Seek(offset, origin);
    }

    public override void SetLength(long value)
    {
        throw new NotImplementedException();
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
        int read = 0;
        try
        {
            read = s_.Read(buffer, offset, count);
            if (read == 0)
            {
                s_.Dispose();
            }
        } 
        catch
        {
            s_.Dispose();
            throw;
        }
        return read;
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        throw new NotImplementedException();
    }

   private Stream s_;
}
webView.CoreWebView2.AddWebResourceRequestedFilter("https://demo/*", 
                                                CoreWebView2WebResourceContext.All);
webView.CoreWebView2.WebResourceRequested += delegate (object sender, 
                                     CoreWebView2WebResourceRequestedEventArgs args)
{
    string assetsFilePath = "C:\\Demo\\" + 
                            args.Request.Uri.Substring("https://demo/*".Length - 1);
    try
    {
        FileStream fs = File.OpenRead(assetsFilePath);
        ManagedStream ms = new ManagedStream(fs);
        string headers = "";
        if (assetsFilePath.EndsWith(".html"))
        {
            headers = "Content-Type: text/html";
        }
        else if (assetsFilePath.EndsWith(".jpg"))
        {
            headers = "Content-Type: image/jpeg";
        } else if (assetsFilePath.EndsWith(".png"))
        {
            headers = "Content-Type: image/png";
        }
        else if (assetsFilePath.EndsWith(".css"))
        {
            headers = "Content-Type: text/css";
        }
        else if (assetsFilePath.EndsWith(".js"))
        {
            headers = "Content-Type: application/javascript";
        }

        args.Response = webView.CoreWebView2.Environment.CreateWebResourceResponse(
                                                            ms, 200, "OK", headers);
    }
    catch (Exception)
    {
        args.Response = webView.CoreWebView2.Environment.CreateWebResourceResponse(
                                                        null, 404, "Not found", "");
    }
};

関連項目