共用方式為


在 WebView2 應用程式中使用本地內容

除了載入遠端內容外,內容也可以在本地載入 WebView2。 有幾種方法可以用來將本地內容載入 WebView2 控制項,包括:

  • 正在瀏覽檔案網址。
  • 導航到一個 HTML 字串。
  • 虛擬主機名稱映射。
  • 處理這個 WebResourceRequested 事件。

以下將說明這些方法。

選擇方法

將本地內容載入 WebView2 控制項的各種方式支援以下情境:

案例 透過導航到檔案網址 透過導航到 HTML 字串 透過使用虛擬主機名稱映射 透過使用 WebResourceRequested
基於起源的 DOM API ✔️ ✔️ ✔️
需要安全上下文的 DOM API ✔️ ✔️
動態內容 ✔️ ✔️
其他網路資源 ✔️ ✔️ ✔️
WebView2 流程中解決的額外網路資源 ✔️ ✔️

以下將詳細描述這些情境。

透過導覽至檔案網址載入本地內容

WebView2 允許導覽檔案網址、載入基本 HTML 或 PDF。 這是載入本地內容最簡單且效率最高的方法。 然而,它比其他方法彈性較差。 與網頁瀏覽器類似,檔案網址在某些功能上有限制:

  • 該文件的來源與其檔案路徑是唯一的。 這表示需要來源(如 或localStorageindexedDB)的網頁 API 可以使用,但儲存的資料不會被其他從其他檔案路徑載入的本地文件存取。

  • 部分網頁 API 僅支援安全的 HTTPS URL,無法支援由檔案 URL 載入的文件。 這包括像是取得影片或聲音navigator.geolocation.getCurrentPosition()、存取裝置位置,或Notification.requestPermission()請求使用者允許顯示通知等 APInavigator.mediaDevices.getUserMedia()

  • 每種資源都必須指定完整路徑。

  • 若要允許從檔案 URI 中參考其他本地檔案,或顯示帶有 XSL 轉換的 XML 檔案,您可以設定 --allow-file-access-from-files 瀏覽器參數。 請參見 CoreWebView2EnvironmentOptions.AdditionalBrowserArguments 屬性

透過導覽至檔案網址載入本地內容的考量

檔案網址的行為就像瀏覽器裡一樣。 例如,你無法在檔案網址中建立 XMLHttpRequest (XHR) ,因為你不是在網頁的上下文中工作。

你必須為每個資源指定檔案的完整路徑。 例如:

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

在指定檔案 URL 時,應用程式會導覽到磁碟上的檔案,而非網路上的網域。 因此,無法在最終文件中使用跨來源資源。

基於起源的 DOM API

透過檔案網址載入的文件,其檔案路徑的起源是唯一的,就像瀏覽器一樣。 需要來源的 localStorage 網頁 API 例如 或 indexedDB 會有效。 然而,從不同檔案網址載入的不同文件不被視為來自相同來源,且無法存取相同的儲存資料。

需要安全上下文的 DOM API

部分網頁 API 僅支援安全的 HTTPS URL,無法支援由檔案 URL 載入的文件。 這包括像是取得影片或聲音navigator.geolocation.getCurrentPosition()、存取裝置位置,或Notification.requestPermission()請求使用者允許顯示通知等 APInavigator.mediaDevices.getUserMedia()。 更多資訊請參閱 MDN 上的安全上下文

動態內容

當透過檔案網址載入文件時,文件內容來自磁碟上的靜態檔案。 這表示無法動態修改這些本地內容。 這與從網頁伺服器載入文件不同,後者每個回應都可以動態產生。

其他網路資源

相對 URL 解析也適用於透過檔案 URL 載入的文件。 這表示載入的文件可以參考其他網路資源,如 CSS、腳本或影像檔案,這些資源同樣透過檔案網址提供。

WebView2 流程中解決的額外網路資源

檔案網址在 WebView2 程序中解析。 這比 快速的選項 WebResourceRequested,後者會在主機應用程式的 UI 執行緒中解決。

透過導覽至檔案網址載入本地內容的 API

檔案網址範例

本節以平台無關的方式展示本地內容檔案路徑的檔案 URL 樣貌。

WebView2 應用程式需要用 file:/// 前綴和斜線來編碼本地檔案 URL。 例如,以示範待辦為例,路徑會是:

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

要將本地檔案的完整路徑複製為「file」前綴:

  1. 可選擇性地複製 Demos 倉庫,這樣你就有本地副本。 請參考步驟 5:在安裝 Visual Studio Code 的 DevTools 擴充套件中複製 demos 倉庫

  2. 在 Microsoft Edge 中,按 Ctrl+O 開啟檔案。 開啟本地 .html 檔案,例如本地複製的檔案 Demos/demo-to-do/index.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. 點擊地址列,然後按 Home 鍵,或按 Ctrl+A 選擇整條路徑。

    Microsoft Edge 的位址列現在顯示 file:/// 前綴

    整個檔案路徑的包含file:///都會複製到剪貼簿緩衝區,所以你可以貼上完整路徑,包括前綴:file:///

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

另請參閱:

導航至檔案網址的範例

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

透過導覽到 HTML 字串載入本地內容

另一種載入本地內容的方法就是NavigateToString 此方法直接從字串載入內容至 WebView2。 如果你打算用應用程式代碼打包內容,或想動態創作內容,這會很有用。

另一個導航到字串可能有用的情境是,如果你想載入無法透過 URL 存取的內容。 例如,如果你有記憶體中的 HTML 文件表示法,你可以用這個 NavigateToString 方法將該內容載入 WebView2 控制項。 如果你想避免在將內容載入控制項前,必須先寫入檔案或伺服器,這會很有用。

透過導覽至HTML字串載入本地內容的考量

傳入 NavigateToString 方法的 HTML 內容字串大小限制為 2MB。 當字串包含內嵌的額外資源時,這個大小限制可能很容易被超越。 若超過此大小限制,則會回傳錯誤:「值不在預期範圍內」。

基於起源的 DOM API

使用該 NavigateToString 方法載入的文件,其位置設為 , about:blank 起點設為 null。 這表示依賴來源定義的網頁 API, localStorage 例如 或 indexedDB,無法使用。

需要安全上下文的 DOM API

部分網頁 API 僅限於安全的 HTTPS URL,且無法透過該 NavigateToString 方法載入的文件存取,因為它們的位置設定為 about:blank。 這包括像是取得影片或聲音navigator.geolocation.getCurrentPosition()、存取裝置位置,或Notification.requestPermission()請求使用者允許顯示通知等 APInavigator.mediaDevices.getUserMedia()。 更多資訊請參閱 MDN 上的安全上下文

動態內容

透過方法 NavigateToString 載入本地內容時,你直接將內容作為方法的參數提供。 這表示你在執行時可以控制內容,並且在需要時動態產生內容。

其他網路資源

使用此 NavigateToString 方法載入本地內容,無法讓最終文件引用額外的網路資源,如 CSS、圖片或腳本檔案。 這個方法只允許你指定 HTML 文件的字串內容。 若要參考 HTML 文件中的其他網路資源,請使用本文中描述的其他方法,或在 HTML 文件中內嵌表示這些額外網路資源。

WebView2 流程中解決的額外網路資源

NavigateToString 如前所述,不支援其他網路資源。

透過導覽至HTML字串載入本地內容的API。

網頁字串表示範例

以下是 Demo To Do 網頁的字串表示法。 以下列表新增了行行包裝以提升可讀性。 實務上,這些線會被串接成一條長線:

`<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. 右鍵點擊網頁,然後選擇 檢查 以開啟 DevTools。

  3. 在 DevTools 的主控台 中,輸入: document.body.parentElement.outerHTML控制台會輸出網頁的字串表示:

    Demo To Do 網頁的字串表示

導航到 HTML 字串的範例

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

利用虛擬主機名稱映射載入本地內容

另一種在 WebView2 控制項中載入本地內容的方法是使用虛擬主機名稱映射。 這涉及將本地網域名稱對應到本地資料夾,當 WebView2 控制器嘗試載入該網域的資源時,會從指定的本地資料夾位置載入內容。 文件的來源也會是虛擬主機名稱。

此方法允許你透過枚 CoreWebView2HostResourceAccessKind 舉來指定交叉來源存取。

由於目前的限制,使用虛擬主機名稱存取的媒體檔案載入速度可能較慢。

使用虛擬主機名稱映射載入本地內容的考量

基於起源的 DOM API

透過虛擬主機名稱映射載入的本地內容,會產生具有 HTTP 或 HTTPS URL 及對應來源的文件。 這表示需要來源(如 localStorageindexedDB )的網頁 API 將可使用,且屬於同一來源的其他文件也能使用儲存的資料。 欲了解更多資訊,請參閱MDN的 同源政策

需要安全上下文的 DOM API

有些網頁 API 僅限於安全的 HTTPS URL。 使用虛擬主機名稱映射,為你的本地內容提供 HTTPS URL。 這表示可以使用像是取得影片或聲音、navigator.geolocation.getCurrentPosition()存取裝置位置,或Notification.requestPermission()請求使用者允許顯示通知等 APInavigator.mediaDevices.getUserMedia()。 更多資訊請參閱 MDN 上的安全上下文

動態內容

當你透過虛擬主機名稱映射載入本地內容時,你其實是在將虛擬主機名稱映射到磁碟上包含靜態檔案的本地資料夾。 這表示無法動態修改這些本地內容。 這與從網頁伺服器載入文件不同,後者每個回應都可以動態產生。

其他網路資源

透過虛擬主機名稱映射載入的本地內容會有 HTTP 或 HTTPS 網址,支援相對 URL 解析。 這表示載入的文件可以參考其他網路資源,如 CSS、腳本或影像檔,這些資源也透過虛擬主機名稱映射提供,但原始碼映射除外;請參閱下方 帶有虛擬主機名稱映射的原始碼地圖

WebView2 流程中解決的額外網路資源

虛擬主機名稱網址在 WebView2 程序中解析。 這比 快速的選項 WebResourceRequested,後者會在主機應用程式的 UI 執行緒中解決。

具備虛擬主機名稱映射的原始碼映射

除錯已編譯內容的原始碼需要原始碼映射,包括:

  • 轉譯 JavaScript,例如 TypeScript 或縮小版 JavaScript。
  • 編譯 CSS,例如 SASS 或 SCSS。

WebView2 不會載入由使用虛擬主機名稱映射載入的內容所參考的原始碼映射。

例如,假設 WebView2 是透過虛擬主機名稱映射來 main.js 載入。 如果 main.js 參考 main.js.map 作為其來源映射, main.js.map 則不會自動載入。

若要同時使用來源映射與虛擬主機名稱映射,請在內容編譯時產生內嵌來源映射。 對應的編譯檔案中嵌入了內嵌的原始碼映射。

利用虛擬主機名稱映射載入本地內容的 API

虛擬主機名稱映射範例

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

透過處理 WebResourceRequested 事件載入本地內容

另一種在 WebView2 控制中託管本地內容的方式是依賴事件。WebResourceRequested 當控制裝置嘗試載入資源時,會觸發此事件。 你可以利用此事件攔截請求並提供本地內容,詳見網路 請求自訂管理

WebResourceRequested 允許你依需求自訂本地內容的行為。 這表示你可以決定攔截哪些請求並提供自己的內容,哪些請求則讓 WebView2 控制正常處理。 然而,客製化行為需要更多程式碼,例如虛擬主機映射,且需要了解 HTTP,才能構建適當的回應。

從 WebView2 的角度來看,資源會透過網路傳送,WebView2 會遵循應用程式在回應中設定的標頭。 使用 WebResourceRequested 事件的速度也比其他方法慢,因為每個請求都需要跨流程的溝通與處理。

自訂方案註冊

如果你想使用自訂方案來產生事件的 Web 資源請求WebResourceRequested,請參考 WebView2 API 概述中的自訂方案註冊

處理事件載入本地內容 WebResourceRequested 的考量

基於起源的 DOM API

透過 WebResourceRequested 載入的本地內容會產生一份具有 HTTP 或 HTTPS URL 及對應來源的文件。 這表示需要來源(如 localStorageindexedDB )的網頁 API 將可使用,且屬於同一來源的其他文件也能使用儲存的資料。 欲了解更多資訊,請參閱MDN的 同源政策

需要安全上下文的 DOM API

有些網頁 API 僅限於安全的 HTTPS URL。 使用 WebResourceRequested 後,你可以用自己的本地內容取代 HTTPS URL 網路資源請求。 這表示可以使用像是取得影片或聲音、navigator.geolocation.getCurrentPosition()存取裝置位置,或Notification.requestPermission()請求使用者允許顯示通知等 APInavigator.mediaDevices.getUserMedia()。 更多資訊請參閱 MDN 上的安全上下文

動態內容

當你透過 WebResourceRequested載入本地內容時,你會在事件處理程序中指定要載入的本地內容。 這表示你在執行時可以控制內容,並且在需要時動態產生內容。

其他網路資源

WebResourceRequested 修改透過 HTTP 或 HTTPS URL 載入的內容,這些 URL 支援相對 URL 解析。 這表示最終文件可以參考其他網路資源,如 CSS、腳本或影像檔,這些資源也透過 WebResourceRequested提供,但來源地圖除外;詳見下方「 事件來源 WebResourceRequested 地圖」。

WebView2 流程中解決的額外網路資源

當透過檔案網址或虛擬主機名稱映射載入內容時,解析會在 WebView2 程序中完成。 然而,該 WebResourceRequested 事件會在你主機應用程式程序的 WebView2 UI 執行緒中被提出,可能導致最終文件載入速度變慢。

  1. WebView2 首先會暫停載入網頁,等待事件被送達你的主機應用程式程序。
  2. WebView2 會等待你的 UI 執行緒可用。
  3. WebView2 會等待你的應用程式程式碼處理事件。

這可能需要一些時間。 務必將呼叫 AddWebResourceRequestedFilter 限制在必須提升 WebResourceRequested 事件的網路資源上。

事件來源 WebResourceRequested 地圖

除錯已編譯內容的原始碼需要原始碼映射,包括:

  • 轉譯 JavaScript,例如 TypeScript 或縮小版 JavaScript。
  • 編譯 CSS,例如 SASS 或 SCSS。

WebView2 不會載入由使用 WebResourceRequested 事件載入內容所參考的來源地圖。

例如,假設你main.jsWebResourceRequested在事件處理程序中設定Response了屬性CoreWebView2WebResourceRequestedEventArgs。 如果 main.jsmain.js.map 參考資料作為其來源地圖:

  • main.js.map 不會自動載入。
  • 你的 WebResourceRequested 事件處理器不會再次被呼叫來載入 main.js.map

若要使用與 WebResourceRequested的來源地圖,請採用以下方法之一:

  • 在內容編譯過程中產生線上來源地圖。 對應的編譯檔案中嵌入了內嵌的原始碼映射。

  • 或者,在你的 WebResourceRequested 事件處理器中,在執行時內嵌獨立的原始碼映射到內容。 只有當你的建置系統不支援內嵌原始碼映射時,才建議使用這種方法。

透過處理 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", "");
    }
};

另請參閱