From f0114eb5db54814a02cd3082d45bbdfd89517184 Mon Sep 17 00:00:00 2001 From: Tanay Parikh Date: Sun, 7 Aug 2022 19:46:35 -0700 Subject: [PATCH 1/4] Stop copying content stream in .NET MAUI Blazor Windows Fixes: https://github.com/dotnet/maui/issues/9206 https://github.com/microsoft/CsWinRT/issues/670 and https://task.ms/31565319 have been marked as resolved. This PR removes the existing logic which copies the content into a memory stream, and replaces it with a call to `AsRandomAccessStream()` (`IRandomAccessStream` is still required per the `CoreWebView2` API). I tried out CSS hot reload with this change and things are working as expected. Not sure if there's a particular behavior/technique we can use to verify the validity of this change. Spoke with @Eilon regarding this, and per his recollection things were immediately hanging, and that's why this change was necessitated. --- .../src/Maui/Windows/WinUIWebViewManager.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs b/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs index 640d8dac3382..a6341f7054fa 100644 --- a/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs +++ b/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs @@ -83,15 +83,8 @@ protected override async Task HandleWebResourceRequest(CoreWebView2WebResourceRe if (TryGetResponseContent(requestUri, allowFallbackOnHostPage, out var statusCode, out var statusMessage, out var content, out var headers) && statusCode != 404) { - // NOTE: This is stream copying is to work around a hanging bug in WinRT with managed streams. - // See issue https://github.com/microsoft/CsWinRT/issues/670 - var memStream = new MemoryStream(); - content.CopyTo(memStream); - var ms = new InMemoryRandomAccessStream(); - await ms.WriteAsync(memStream.GetWindowsRuntimeBuffer()); - var headerString = GetHeaderString(headers); - eventArgs.Response = _coreWebView2Environment!.CreateWebResourceResponse(ms, statusCode, statusMessage, headerString); + eventArgs.Response = _coreWebView2Environment!.CreateWebResourceResponse(content.AsRandomAccessStream(), statusCode, statusMessage, headerString); } else if (new Uri(requestUri) is Uri uri && AppOriginUri.IsBaseOf(uri)) { From 8dd13168f91933db0938cb36ac5c9fe3bdcd420e Mon Sep 17 00:00:00 2001 From: Tanay Parikh Date: Wed, 24 Aug 2022 22:39:31 -0700 Subject: [PATCH 2/4] AutoCloseOnReadCompleteStream --- Microsoft.Maui.sln | 1 + .../AutoCloseOnReadCompleteStream.cs | 48 +++++++++++++++++++ .../SharedSource/WebView2WebViewManager.cs | 5 +- 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 src/BlazorWebView/src/SharedSource/AutoCloseOnReadCompleteStream.cs diff --git a/Microsoft.Maui.sln b/Microsoft.Maui.sln index 6eab52d37a32..5593758488b6 100644 --- a/Microsoft.Maui.sln +++ b/Microsoft.Maui.sln @@ -196,6 +196,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MauiBlazorWebView.DeviceTes EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SharedSource", "SharedSource", "{4F2926C8-43AB-4328-A735-D9EAD699F81D}" ProjectSection(SolutionItems) = preProject + src\BlazorWebView\src\SharedSource\AutoCloseOnReadCompleteStream.cs = src\BlazorWebView\src\SharedSource\AutoCloseOnReadCompleteStream.cs src\BlazorWebView\src\SharedSource\QueryStringHelper.cs = src\BlazorWebView\src\SharedSource\QueryStringHelper.cs src\BlazorWebView\src\SharedSource\UrlLoadingEventArgs.cs = src\BlazorWebView\src\SharedSource\UrlLoadingEventArgs.cs src\BlazorWebView\src\SharedSource\UrlLoadingStrategy.cs = src\BlazorWebView\src\SharedSource\UrlLoadingStrategy.cs diff --git a/src/BlazorWebView/src/SharedSource/AutoCloseOnReadCompleteStream.cs b/src/BlazorWebView/src/SharedSource/AutoCloseOnReadCompleteStream.cs new file mode 100644 index 000000000000..e12d66c81a8f --- /dev/null +++ b/src/BlazorWebView/src/SharedSource/AutoCloseOnReadCompleteStream.cs @@ -0,0 +1,48 @@ +#if WEBVIEW2_WINFORMS || WEBVIEW2_WPF + +using System.IO; + +namespace Microsoft.AspNetCore.Components.WebView.WebView2 +{ + internal class AutoCloseOnReadCompleteStream : Stream + { + private readonly Stream _baseStream; + + public AutoCloseOnReadCompleteStream(Stream baseStream) + { + _baseStream = baseStream; + } + + public override bool CanRead => _baseStream.CanRead; + + public override bool CanSeek => _baseStream.CanSeek; + + public override bool CanWrite => _baseStream.CanWrite; + + public override long Length => _baseStream.Length; + + public override long Position { get => _baseStream.Position; set => _baseStream.Position = value; } + + public override void Flush() => _baseStream?.Flush(); + + public override int Read(byte[] buffer, int offset, int count) + { + var bytesRead = _baseStream.Read(buffer, offset, count); + + if (bytesRead == 0) + { + _baseStream.Close(); + } + + return bytesRead; + } + + public override long Seek(long offset, SeekOrigin origin) => _baseStream.Seek(offset, origin); + + public override void SetLength(long value) => _baseStream.SetLength(value); + + public override void Write(byte[] buffer, int offset, int count) => _baseStream?.Write(buffer, offset, count); + } +} + +#endif diff --git a/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs b/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs index ee2fad5f4dac..dee38ecfa352 100644 --- a/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs +++ b/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs @@ -304,7 +304,10 @@ protected virtual Task HandleWebResourceRequest(CoreWebView2WebResourceRequested StaticContentHotReloadManager.TryReplaceResponseContent(_contentRootRelativeToAppRoot, requestUri, ref statusCode, ref content, headers); var headerString = GetHeaderString(headers); - eventArgs.Response = _coreWebView2Environment!.CreateWebResourceResponse(content, statusCode, statusMessage, headerString); + + var autoCloseStream = new AutoCloseOnReadCompleteStream(content); + + eventArgs.Response = _coreWebView2Environment!.CreateWebResourceResponse(autoCloseStream, statusCode, statusMessage, headerString); } #elif WEBVIEW2_MAUI // No-op here because all the work is done in the derived WinUIWebViewManager From ebdfdf48d2d35c47cd30ce93020e3ef3a9284380 Mon Sep 17 00:00:00 2001 From: Tanay Parikh Date: Wed, 24 Aug 2022 22:39:47 -0700 Subject: [PATCH 3/4] Ensure old response content is closed out for hot reload --- .../src/SharedSource/StaticContentHotReloadManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/BlazorWebView/src/SharedSource/StaticContentHotReloadManager.cs b/src/BlazorWebView/src/SharedSource/StaticContentHotReloadManager.cs index d25179194d7a..a2a7d81776f3 100644 --- a/src/BlazorWebView/src/SharedSource/StaticContentHotReloadManager.cs +++ b/src/BlazorWebView/src/SharedSource/StaticContentHotReloadManager.cs @@ -67,6 +67,7 @@ public static bool TryReplaceResponseContent(string contentRootRelativePath, str if (_updatedContent.TryGetValue((assemblyName, relativePath), out var values)) { responseStatusCode = 200; + responseContent.Close(); responseContent = new MemoryStream(values.Content); if (!string.IsNullOrEmpty(values.ContentType)) { From 122426c88ac7ca10ba6586db9017e622bf45eb8e Mon Sep 17 00:00:00 2001 From: Tanay Parikh Date: Fri, 26 Aug 2022 16:44:15 -0700 Subject: [PATCH 4/4] PR Feedback --- .../src/SharedSource/AutoCloseOnReadCompleteStream.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/BlazorWebView/src/SharedSource/AutoCloseOnReadCompleteStream.cs b/src/BlazorWebView/src/SharedSource/AutoCloseOnReadCompleteStream.cs index e12d66c81a8f..93a977cb8a8d 100644 --- a/src/BlazorWebView/src/SharedSource/AutoCloseOnReadCompleteStream.cs +++ b/src/BlazorWebView/src/SharedSource/AutoCloseOnReadCompleteStream.cs @@ -23,12 +23,15 @@ public AutoCloseOnReadCompleteStream(Stream baseStream) public override long Position { get => _baseStream.Position; set => _baseStream.Position = value; } - public override void Flush() => _baseStream?.Flush(); + public override void Flush() => _baseStream.Flush(); public override int Read(byte[] buffer, int offset, int count) { var bytesRead = _baseStream.Read(buffer, offset, count); + // Stream.Read only returns 0 when it has reached the end of stream + // and no further bytes are expected. Otherwise it blocks until + // one or more (and at most count) bytes can be read. if (bytesRead == 0) { _baseStream.Close(); @@ -41,7 +44,7 @@ public override int Read(byte[] buffer, int offset, int count) public override void SetLength(long value) => _baseStream.SetLength(value); - public override void Write(byte[] buffer, int offset, int count) => _baseStream?.Write(buffer, offset, count); + public override void Write(byte[] buffer, int offset, int count) => _baseStream.Write(buffer, offset, count); } }