Skip to content

Commit 68028a8

Browse files
committed
Clean up the code a bit
1 parent 8bdeea8 commit 68028a8

File tree

4 files changed

+188
-159
lines changed

4 files changed

+188
-159
lines changed

src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.Windows.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Specialized;
23
using System.Diagnostics.CodeAnalysis;
34
using System.IO;
45
using System.Runtime.InteropServices.WindowsRuntime;
@@ -44,6 +45,7 @@ protected override void ConnectHandler(WebView2 platformView)
4445
PlatformView.Source = new Uri(new Uri(AppOriginUri, "/").ToString());
4546
}
4647
});
48+
4749
}
4850

4951
void OnWebViewLoaded(object sender, RoutedEventArgs e)
@@ -190,6 +192,7 @@ static async Task<IRandomAccessStream> CopyContentToRandomAccessStreamAsync(Stre
190192
}
191193
}
192194

195+
193196
[RequiresUnreferencedCode(DynamicFeatures)]
194197
#if !NETSTANDARD
195198
[RequiresDynamicCode(DynamicFeatures)]

src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.cs

Lines changed: 32 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
using System.Text.Json.Serialization;
3333
using System.Diagnostics.CodeAnalysis;
3434
using System.Reflection;
35+
using Microsoft.Extensions.Logging;
3536

3637
namespace Microsoft.Maui.Handlers
3738
{
@@ -160,7 +161,7 @@ void MessageReceived(string rawMessage)
160161
try
161162
{
162163
var invokeTarget = VirtualView.InvokeJavaScriptTarget ?? throw new InvalidOperationException($"The {nameof(IHybridWebView)}.{nameof(IHybridWebView.InvokeJavaScriptTarget)} property must have a value in order to invoke a .NET method from JavaScript.");
163-
var invokeTargetType = VirtualView.InvokeJavaScriptType ?? throw new InvalidOperationException($"The {nameof(IHybridWebView)}.{nameof(IHybridWebView.InvokeJavaScriptTarget)} property must have a value in order to invoke a .NET method from JavaScript.");
164+
var invokeTargetType = VirtualView.InvokeJavaScriptType ?? throw new InvalidOperationException($"The {nameof(IHybridWebView)}.{nameof(IHybridWebView.InvokeJavaScriptType)} property must have a value in order to invoke a .NET method from JavaScript.");
164165

165166
var invokeDataString = invokeQueryString["data"];
166167
if (string.IsNullOrEmpty(invokeDataString))
@@ -169,51 +170,50 @@ void MessageReceived(string rawMessage)
169170
}
170171

171172
var invokeData = JsonSerializer.Deserialize<JSInvokeMethodData>(invokeDataString, HybridWebViewHandlerJsonContext.Default.JSInvokeMethodData);
172-
173173
if (invokeData?.MethodName is null)
174174
{
175175
throw new ArgumentException("The invoke data did not provide a method name.", nameof(invokeQueryString));
176176
}
177177

178-
var invokeResult = await InvokeDotNetMethodAsync(invokeTargetType, invokeTarget, invokeData);
179-
var result = GetInvokeResult(invokeResult);
180-
var json = JsonSerializer.Serialize(result);
178+
var invokeResultRaw = await InvokeDotNetMethodAsync(invokeTargetType, invokeTarget, invokeData);
179+
var invokeResult = CreateInvokeResult(invokeResultRaw);
180+
var json = JsonSerializer.Serialize(invokeResult);
181181
var contentBytes = Encoding.UTF8.GetBytes(json);
182182

183183
return contentBytes;
184184
}
185-
catch (Exception)
185+
catch (Exception ex)
186186
{
187-
// TODO: Log this
187+
MauiContext?.CreateLogger<HybridWebViewHandler>()?.LogError(ex, "An error occurred while invoking a .NET method from JavaScript: {ErrorMessage}", ex.Message);
188188
}
189189

190190
return default;
191+
}
191192

192-
static DotNetInvokeResult GetInvokeResult(object? result)
193+
private static DotNetInvokeResult CreateInvokeResult(object? result)
194+
{
195+
// null invoke result means an empty result
196+
if (result is null)
193197
{
194-
// null invoke result means an empty result
195-
if (result is null)
196-
{
197-
return new();
198-
}
199-
200-
// a reference type or an array should be serialized to JSON
201-
var resultType = result.GetType();
202-
if (resultType.IsArray || resultType.IsClass)
203-
{
204-
return new DotNetInvokeResult()
205-
{
206-
Result = JsonSerializer.Serialize(result),
207-
IsJson = true,
208-
};
209-
}
198+
return new();
199+
}
210200

211-
// a value type should be returned as is
201+
// a reference type or an array should be serialized to JSON
202+
var resultType = result.GetType();
203+
if (resultType.IsArray || resultType.IsClass)
204+
{
212205
return new DotNetInvokeResult()
213206
{
214-
Result = result,
207+
Result = JsonSerializer.Serialize(result),
208+
IsJson = true,
215209
};
216210
}
211+
212+
// a value type should be returned as is
213+
return new DotNetInvokeResult()
214+
{
215+
Result = result,
216+
};
217217
}
218218

219219
private static async Task<object?> InvokeDotNetMethodAsync(
@@ -224,18 +224,19 @@ static DotNetInvokeResult GetInvokeResult(object? result)
224224
var requestMethodName = invokeData.MethodName!;
225225
var requestParams = invokeData.ParamValues;
226226

227+
// get the method and its parameters from the .NET object instance
227228
var dotnetMethod = targetType.GetMethod(requestMethodName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod);
228229
if (dotnetMethod is null)
229230
{
230231
throw new InvalidOperationException($"The method {requestMethodName} couldn't be found on the {nameof(jsInvokeTarget)} of type {jsInvokeTarget.GetType().FullName}.");
231232
}
232-
233233
var dotnetParams = dotnetMethod.GetParameters();
234234
if (requestParams is not null && dotnetParams.Length != requestParams.Length)
235235
{
236236
throw new InvalidOperationException($"The number of parameters on {nameof(jsInvokeTarget)}'s method {requestMethodName} ({dotnetParams.Length}) doesn't match the number of values passed from JavaScript code ({requestParams.Length}).");
237237
}
238238

239+
// deserialize the parameters from JSON to .NET types
239240
object?[]? invokeParamValues = null;
240241
if (requestParams is not null)
241242
{
@@ -249,14 +250,15 @@ static DotNetInvokeResult GetInvokeResult(object? result)
249250
}
250251
}
251252

253+
// invoke the .NET method
252254
var dotnetReturnValue = dotnetMethod.Invoke(jsInvokeTarget, invokeParamValues);
253255

254-
if (dotnetReturnValue is null)
256+
if (dotnetReturnValue is null) // null result
255257
{
256258
return null;
257259
}
258260

259-
if (dotnetReturnValue is Task task) // Task and/or Task<T>
261+
if (dotnetReturnValue is Task task) // Task or Task<T> result
260262
{
261263
await task;
262264

@@ -271,7 +273,7 @@ static DotNetInvokeResult GetInvokeResult(object? result)
271273
return null;
272274
}
273275

274-
return dotnetReturnValue;
276+
return dotnetReturnValue; // regular result
275277
}
276278

277279
private sealed class JSInvokeMethodData

src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.iOS.cs

Lines changed: 60 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Threading.Tasks;
77
using System.Web;
88
using Foundation;
9+
using Microsoft.Extensions.Logging;
910
using UIKit;
1011
using WebKit;
1112
using RectangleF = CoreGraphics.CGRect;
@@ -136,6 +137,9 @@ public SchemeHandler(HybridWebViewHandler webViewHandler)
136137

137138
private HybridWebViewHandler? Handler => _webViewHandler is not null && _webViewHandler.TryGetTarget(out var h) ? h : null;
138139

140+
// The `async void` is intentional here, as this is an event handler that represents the start
141+
// of a request for some data from the webview. Once the task is complete, the `IWKUrlSchemeTask`
142+
// object is used to send the response back to the webview.
139143
[Export("webView:startURLSchemeTask:")]
140144
[SupportedOSPlatform("ios11.0")]
141145
public async void StartUrlSchemeTask(WKWebView webView, IWKUrlSchemeTask urlSchemeTask)
@@ -149,79 +153,85 @@ public async void StartUrlSchemeTask(WKWebView webView, IWKUrlSchemeTask urlSche
149153

150154
var (bytes, contentType, statusCode) = await GetResponseBytesAsync(url);
151155

152-
if (statusCode != 200)
156+
if (statusCode == 200)
153157
{
154-
// TODO: maybe we need to handle errors?
155-
return;
156-
}
157-
158-
using (var dic = new NSMutableDictionary<NSString, NSString>())
159-
{
160-
dic.Add((NSString)"Content-Length", (NSString)bytes.Length.ToString(CultureInfo.InvariantCulture));
161-
dic.Add((NSString)"Content-Type", (NSString)contentType);
162-
// Disable local caching. This will prevent user scripts from executing correctly.
163-
dic.Add((NSString)"Cache-Control", (NSString)"no-cache, max-age=0, must-revalidate, no-store");
158+
// the method was invoked successfully, so we need to send the response back to the webview
164159

165-
if (urlSchemeTask.Request.Url != null)
160+
using (var dic = new NSMutableDictionary<NSString, NSString>())
166161
{
167-
using var response = new NSHttpUrlResponse(urlSchemeTask.Request.Url, statusCode, "HTTP/1.1", dic);
162+
dic.Add((NSString)"Content-Length", (NSString)bytes.Length.ToString(CultureInfo.InvariantCulture));
163+
dic.Add((NSString)"Content-Type", (NSString)contentType);
164+
// Disable local caching. This will prevent user scripts from executing correctly.
165+
dic.Add((NSString)"Cache-Control", (NSString)"no-cache, max-age=0, must-revalidate, no-store");
168166

169-
urlSchemeTask.DidReceiveResponse(response);
167+
if (urlSchemeTask.Request.Url != null)
168+
{
169+
using var response = new NSHttpUrlResponse(urlSchemeTask.Request.Url, statusCode, "HTTP/1.1", dic);
170+
urlSchemeTask.DidReceiveResponse(response);
171+
}
170172
}
173+
174+
urlSchemeTask.DidReceiveData(NSData.FromArray(bytes));
175+
urlSchemeTask.DidFinish();
171176
}
177+
else
178+
{
179+
// there was an error, so we need to handle it
172180

173-
urlSchemeTask.DidReceiveData(NSData.FromArray(bytes));
174-
urlSchemeTask.DidFinish();
181+
Handler?.MauiContext?.CreateLogger<HybridWebViewHandler>()?.LogError("Failed to load URL: {url}", url);
182+
}
175183
}
176184

177185
private async Task<(byte[] ResponseBytes, string ContentType, int StatusCode)> GetResponseBytesAsync(string? url)
178186
{
179-
if (Handler is not null)
187+
if (Handler is null)
180188
{
181-
var fullUrl = url;
182-
url = HybridWebViewQueryStringHelper.RemovePossibleQueryString(url);
189+
return (Array.Empty<byte>(), ContentType: string.Empty, StatusCode: 404);
190+
}
183191

184-
if (new Uri(url) is Uri uri && AppOriginUri.IsBaseOf(uri))
185-
{
186-
var relativePath = AppOriginUri.MakeRelativeUri(uri).ToString().Replace('\\', '/');
192+
var fullUrl = url;
193+
url = HybridWebViewQueryStringHelper.RemovePossibleQueryString(url);
194+
195+
if (new Uri(url) is Uri uri && AppOriginUri.IsBaseOf(uri))
196+
{
197+
var relativePath = AppOriginUri.MakeRelativeUri(uri).ToString().Replace('\\', '/');
187198

188-
var bundleRootDir = Path.Combine(NSBundle.MainBundle.ResourcePath, Handler.VirtualView.HybridRoot!);
199+
var bundleRootDir = Path.Combine(NSBundle.MainBundle.ResourcePath, Handler.VirtualView.HybridRoot!);
189200

190-
// 1. Try special InvokeDotNet path
191-
if (relativePath == InvokeDotNetPath)
201+
// 1. Try special InvokeDotNet path
202+
if (relativePath == InvokeDotNetPath)
203+
{
204+
var fullUri = new Uri(fullUrl!);
205+
var invokeQueryString = HttpUtility.ParseQueryString(fullUri.Query);
206+
var contentBytes = await Handler.InvokeDotNetAsync(invokeQueryString);
207+
if (contentBytes is not null)
192208
{
193-
var fullUri = new Uri(fullUrl!);
194-
var invokeQueryString = HttpUtility.ParseQueryString(fullUri.Query);
195-
var contentBytes = await Handler.InvokeDotNetAsync(invokeQueryString);
196-
if (contentBytes is not null)
197-
{
198-
return (contentBytes, "application/json", StatusCode: 200);
199-
}
209+
return (contentBytes, "application/json", StatusCode: 200);
200210
}
211+
}
201212

202-
string contentType;
213+
string contentType;
203214

204-
// 2. If nothing found yet, try to get static content from the asset path
205-
if (string.IsNullOrEmpty(relativePath))
206-
{
207-
relativePath = Handler.VirtualView.DefaultFile!.Replace('\\', '/');
208-
contentType = "text/html";
209-
}
210-
else
215+
// 2. If nothing found yet, try to get static content from the asset path
216+
if (string.IsNullOrEmpty(relativePath))
217+
{
218+
relativePath = Handler.VirtualView.DefaultFile!.Replace('\\', '/');
219+
contentType = "text/html";
220+
}
221+
else
222+
{
223+
if (!ContentTypeProvider.TryGetContentType(relativePath, out contentType!))
211224
{
212-
if (!ContentTypeProvider.TryGetContentType(relativePath, out contentType!))
213-
{
214-
// TODO: Log this
215-
contentType = "text/plain";
216-
}
225+
// TODO: Log this
226+
contentType = "text/plain";
217227
}
228+
}
218229

219-
var assetPath = Path.Combine(bundleRootDir, relativePath);
230+
var assetPath = Path.Combine(bundleRootDir, relativePath);
220231

221-
if (File.Exists(assetPath))
222-
{
223-
return (File.ReadAllBytes(assetPath), contentType, StatusCode: 200);
224-
}
232+
if (File.Exists(assetPath))
233+
{
234+
return (File.ReadAllBytes(assetPath), contentType, StatusCode: 200);
225235
}
226236
}
227237

0 commit comments

Comments
 (0)