diff --git a/src/Controls/src/Core/HybridWebView/HybridWebView.cs b/src/Controls/src/Core/HybridWebView/HybridWebView.cs
index 8b667474251f..af44fca85872 100644
--- a/src/Controls/src/Core/HybridWebView/HybridWebView.cs
+++ b/src/Controls/src/Core/HybridWebView/HybridWebView.cs
@@ -1,7 +1,13 @@
using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.Maui.Devices;
+using System.Collections.Generic;
+
+#if WINDOWS || ANDROID || IOS || MACCATALYST
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
-using System.Threading.Tasks;
+#endif
namespace Microsoft.Maui.Controls
{
@@ -33,9 +39,37 @@ public string? HybridRoot
set { SetValue(HybridRootProperty, value); }
}
- void IHybridWebView.RawMessageReceived(string rawMessage)
+ void IHybridWebView.MessageReceived(string rawMessage)
{
- RawMessageReceived?.Invoke(this, new HybridWebViewRawMessageReceivedEventArgs(rawMessage));
+ if (string.IsNullOrEmpty(rawMessage))
+ {
+ throw new ArgumentException($"The raw message cannot be null or empty.", nameof(rawMessage));
+ }
+ var indexOfPipe = rawMessage.IndexOf("|", StringComparison.Ordinal);
+ if (indexOfPipe == -1)
+ {
+ throw new ArgumentException($"The raw message must contain a pipe character ('|').", nameof(rawMessage));
+ }
+
+ var messageType = rawMessage.Substring(0, indexOfPipe);
+ var messageContent = rawMessage.Substring(indexOfPipe + 1);
+
+ switch (messageType)
+ {
+ case "InvokeMethodCompleted":
+ {
+ var sections = messageContent.Split('|');
+ var taskId = sections[0];
+ var result = sections[1];
+ AsyncTaskCompleted(taskId, result);
+ }
+ break;
+ case "RawMessage":
+ RawMessageReceived?.Invoke(this, new HybridWebViewRawMessageReceivedEventArgs(messageContent));
+ break;
+ default:
+ throw new ArgumentException($"The message type '{messageType}' is not recognized.", nameof(rawMessage));
+ }
}
///
@@ -57,12 +91,36 @@ public void SendRawMessage(string rawMessage)
});
}
- ///
- public async Task InvokeJavaScriptAsync(
- string methodName,
- JsonTypeInfo returnTypeJsonTypeInfo,
- object?[]? paramValues = null,
- JsonTypeInfo?[]? paramJsonTypeInfos = null)
+ private int _invokeTaskId;
+ private Dictionary> asyncTaskCallbacks = new Dictionary>();
+
+ ///
+ /// Handler for when the an Async JavaScript task has completed and needs to notify .NET.
+ ///
+ private void AsyncTaskCompleted(string taskId, string result)
+ {
+ //Look for the callback in the list of pending callbacks.
+ if (!string.IsNullOrEmpty(taskId) && asyncTaskCallbacks.ContainsKey(taskId))
+ {
+ //Get the callback and remove it from the list.
+ var callback = asyncTaskCallbacks[taskId];
+ callback.SetResult(result);
+
+ //Remove the callback.
+ asyncTaskCallbacks.Remove(taskId);
+ }
+ }
+
+ ///
+ /// Invokes a JavaScript method named and optionally passes in the parameter values
+ /// specified by .
+ ///
+ /// The name of the JavaScript method to invoke.
+ /// Optional array of objects to be passed to the JavaScript method.
+ /// Optional array of metadata about serializing the types of the parameters specified by .
+ /// A string containing the return value of the called method.
+#if WINDOWS || ANDROID || IOS || MACCATALYST
+ public async Task InvokeJavaScriptAsync(string methodName, object?[]? paramValues, JsonTypeInfo?[]? paramJsonTypeInfos = null)
{
if (string.IsNullOrEmpty(methodName))
{
@@ -81,16 +139,57 @@ public void SendRawMessage(string rawMessage)
throw new ArgumentException($"The number of parameter values does not match the number of parameter JSON type infos.", nameof(paramValues));
}
- var invokeResult = await Handler?.InvokeAsync(
- nameof(IHybridWebView.InvokeJavaScriptAsync),
- new HybridWebViewInvokeJavaScriptRequest(methodName, returnTypeJsonTypeInfo, paramValues, paramJsonTypeInfos))!;
+ // Create a callback for async JavaScript methods to invoke when they are done
+ var callback = new TaskCompletionSource();
+ var currentInvokeTaskId = $"{_invokeTaskId++}";
+ asyncTaskCallbacks.Add(currentInvokeTaskId, callback);
+
+ var paramsValuesStringArray =
+ paramValues == null
+ ? string.Empty
+ : string.Join(
+ ", ",
+ paramValues.Select((v, i) => (v == null ? "null" : JsonSerializer.Serialize(v, paramJsonTypeInfos![i]!))));
- if (invokeResult is null)
+ await EvaluateJavaScriptAsync($"window.HybridWebView.InvokeMethod({currentInvokeTaskId}, {methodName}, [{paramsValuesStringArray}])");
+
+ return await callback.Task;
+ }
+#else
+ public Task InvokeJavaScriptAsync(string methodName, object?[]? paramValues = null, object?[]? paramJsonTypeInfos = null)
+ {
+ _invokeTaskId++; // This is to avoid the compiler warning about the field not being used
+ throw new NotImplementedException();
+ }
+#endif
+
+ ///
+ /// Invokes a JavaScript method named and optionally passes in the parameter values specified
+ /// by by JSON-encoding each one.
+ ///
+ /// The type of the return value to deserialize from JSON.
+ /// The name of the JavaScript method to invoke.
+ /// Metadata about deserializing the type of the return value specified by .
+ /// Optional array of objects to be passed to the JavaScript method by JSON-encoding each one.
+ /// Optional array of metadata about serializing the types of the parameters specified by .
+ /// An object of type containing the return value of the called method.
+#if WINDOWS || ANDROID || IOS || MACCATALYST
+ public async Task InvokeJavaScriptAsync(string methodName, JsonTypeInfo returnTypeJsonTypeInfo, object?[]? paramValues = null, JsonTypeInfo?[]? paramJsonTypeInfos = null)
+ {
+ var stringResult = await InvokeJavaScriptAsync(methodName, paramValues, paramJsonTypeInfos);
+
+ if (stringResult is null)
{
return default;
}
- return (TReturnType)invokeResult;
+ return JsonSerializer.Deserialize(stringResult, returnTypeJsonTypeInfo);
+ }
+#else
+ public Task InvokeJavaScriptAsync(string methodName, object returnTypeJsonTypeInfo, object?[]? paramValues, object?[]? paramJsonTypeInfos)
+ {
+ throw new NotImplementedException();
}
+#endif
///
public async Task EvaluateJavaScriptAsync(string script)
@@ -100,9 +199,40 @@ public void SendRawMessage(string rawMessage)
return null;
}
- var result = await Handler!.InvokeAsync(nameof(IHybridWebView.EvaluateJavaScriptAsync),
+ // Make all the platforms mimic Android's implementation, which is by far the most complete.
+ if (DeviceInfo.Platform != DevicePlatform.Android)
+ {
+ script = WebView.EscapeJsString(script);
+
+ if (DeviceInfo.Platform != DevicePlatform.WinUI)
+ {
+ // Use JSON.stringify() method to converts a JavaScript value to a JSON string
+ script = "try{JSON.stringify(eval('" + script + "'))}catch(e){'null'};";
+ }
+ else
+ {
+ script = "try{eval('" + script + "')}catch(e){'null'};";
+ }
+ }
+
+ string? result;
+
+ // Use the handler command to evaluate the JS
+ result = await Handler!.InvokeAsync(nameof(IHybridWebView.EvaluateJavaScriptAsync),
new EvaluateJavaScriptAsyncRequest(script));
+ //if the js function errored or returned null/undefined treat it as null
+ if (result == "null")
+ {
+ result = null;
+ }
+ //JSON.stringify wraps the result in literal quotes, we just want the actual returned result
+ //note that if the js function returns the string "null" we will get here and not above
+ else if (result != null)
+ {
+ result = result.Trim('"');
+ }
+
return result;
}
}
diff --git a/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt
index a8ee9da554ec..8d0f89ae7f0e 100644
--- a/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt
+++ b/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt
@@ -73,7 +73,8 @@ Microsoft.Maui.Controls.HybridWebView.EvaluateJavaScriptAsync(string! script) ->
Microsoft.Maui.Controls.HybridWebView.HybridRoot.get -> string?
Microsoft.Maui.Controls.HybridWebView.HybridRoot.set -> void
Microsoft.Maui.Controls.HybridWebView.HybridWebView() -> void
-Microsoft.Maui.Controls.HybridWebView.InvokeJavaScriptAsync(string! methodName, System.Text.Json.Serialization.Metadata.JsonTypeInfo! returnTypeJsonTypeInfo, object?[]? paramValues = null, System.Text.Json.Serialization.Metadata.JsonTypeInfo?[]? paramJsonTypeInfos = null) -> System.Threading.Tasks.Task!
+Microsoft.Maui.Controls.HybridWebView.InvokeJavaScriptAsync(string! methodName, object?[]? paramValues, System.Text.Json.Serialization.Metadata.JsonTypeInfo?[]? paramJsonTypeInfos = null) -> System.Threading.Tasks.Task!
+Microsoft.Maui.Controls.HybridWebView.InvokeJavaScriptAsync(string! methodName, System.Text.Json.Serialization.Metadata.JsonTypeInfo! returnTypeJsonTypeInfo, object?[]? paramValues = null, System.Text.Json.Serialization.Metadata.JsonTypeInfo?[]? paramJsonTypeInfos = null) -> System.Threading.Tasks.Task!
Microsoft.Maui.Controls.HybridWebView.RawMessageReceived -> System.EventHandler?
Microsoft.Maui.Controls.HybridWebView.SendRawMessage(string! rawMessage) -> void
Microsoft.Maui.Controls.HybridWebViewRawMessageReceivedEventArgs
diff --git a/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt
index 4608b2ad1ff2..686a6fce5bfb 100644
--- a/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt
+++ b/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt
@@ -236,7 +236,8 @@ Microsoft.Maui.Controls.HybridWebView.EvaluateJavaScriptAsync(string! script) ->
Microsoft.Maui.Controls.HybridWebView.HybridRoot.get -> string?
Microsoft.Maui.Controls.HybridWebView.HybridRoot.set -> void
Microsoft.Maui.Controls.HybridWebView.HybridWebView() -> void
-Microsoft.Maui.Controls.HybridWebView.InvokeJavaScriptAsync(string! methodName, System.Text.Json.Serialization.Metadata.JsonTypeInfo! returnTypeJsonTypeInfo, object?[]? paramValues = null, System.Text.Json.Serialization.Metadata.JsonTypeInfo?[]? paramJsonTypeInfos = null) -> System.Threading.Tasks.Task!
+Microsoft.Maui.Controls.HybridWebView.InvokeJavaScriptAsync(string! methodName, object?[]? paramValues, System.Text.Json.Serialization.Metadata.JsonTypeInfo?[]? paramJsonTypeInfos = null) -> System.Threading.Tasks.Task!
+Microsoft.Maui.Controls.HybridWebView.InvokeJavaScriptAsync(string! methodName, System.Text.Json.Serialization.Metadata.JsonTypeInfo! returnTypeJsonTypeInfo, object?[]? paramValues = null, System.Text.Json.Serialization.Metadata.JsonTypeInfo?[]? paramJsonTypeInfos = null) -> System.Threading.Tasks.Task!
Microsoft.Maui.Controls.HybridWebView.RawMessageReceived -> System.EventHandler?
Microsoft.Maui.Controls.HybridWebView.SendRawMessage(string! rawMessage) -> void
Microsoft.Maui.Controls.HybridWebViewRawMessageReceivedEventArgs
diff --git a/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt
index 4608b2ad1ff2..686a6fce5bfb 100644
--- a/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt
+++ b/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt
@@ -236,7 +236,8 @@ Microsoft.Maui.Controls.HybridWebView.EvaluateJavaScriptAsync(string! script) ->
Microsoft.Maui.Controls.HybridWebView.HybridRoot.get -> string?
Microsoft.Maui.Controls.HybridWebView.HybridRoot.set -> void
Microsoft.Maui.Controls.HybridWebView.HybridWebView() -> void
-Microsoft.Maui.Controls.HybridWebView.InvokeJavaScriptAsync(string! methodName, System.Text.Json.Serialization.Metadata.JsonTypeInfo! returnTypeJsonTypeInfo, object?[]? paramValues = null, System.Text.Json.Serialization.Metadata.JsonTypeInfo?[]? paramJsonTypeInfos = null) -> System.Threading.Tasks.Task!
+Microsoft.Maui.Controls.HybridWebView.InvokeJavaScriptAsync(string! methodName, object?[]? paramValues, System.Text.Json.Serialization.Metadata.JsonTypeInfo?[]? paramJsonTypeInfos = null) -> System.Threading.Tasks.Task!
+Microsoft.Maui.Controls.HybridWebView.InvokeJavaScriptAsync(string! methodName, System.Text.Json.Serialization.Metadata.JsonTypeInfo! returnTypeJsonTypeInfo, object?[]? paramValues = null, System.Text.Json.Serialization.Metadata.JsonTypeInfo?[]? paramJsonTypeInfos = null) -> System.Threading.Tasks.Task!
Microsoft.Maui.Controls.HybridWebView.RawMessageReceived -> System.EventHandler?
Microsoft.Maui.Controls.HybridWebView.SendRawMessage(string! rawMessage) -> void
Microsoft.Maui.Controls.HybridWebViewRawMessageReceivedEventArgs
diff --git a/src/Controls/src/Core/PublicAPI/net-tizen/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-tizen/PublicAPI.Unshipped.txt
index 0a53e93e7fbc..92b3f1bc209b 100644
--- a/src/Controls/src/Core/PublicAPI/net-tizen/PublicAPI.Unshipped.txt
+++ b/src/Controls/src/Core/PublicAPI/net-tizen/PublicAPI.Unshipped.txt
@@ -72,7 +72,8 @@ Microsoft.Maui.Controls.HybridWebView.EvaluateJavaScriptAsync(string! script) ->
Microsoft.Maui.Controls.HybridWebView.HybridRoot.get -> string?
Microsoft.Maui.Controls.HybridWebView.HybridRoot.set -> void
Microsoft.Maui.Controls.HybridWebView.HybridWebView() -> void
-Microsoft.Maui.Controls.HybridWebView.InvokeJavaScriptAsync(string! methodName, System.Text.Json.Serialization.Metadata.JsonTypeInfo! returnTypeJsonTypeInfo, object?[]? paramValues = null, System.Text.Json.Serialization.Metadata.JsonTypeInfo?[]? paramJsonTypeInfos = null) -> System.Threading.Tasks.Task!
+Microsoft.Maui.Controls.HybridWebView.InvokeJavaScriptAsync(string! methodName, object?[]? paramValues = null, object?[]? paramJsonTypeInfos = null) -> System.Threading.Tasks.Task!
+Microsoft.Maui.Controls.HybridWebView.InvokeJavaScriptAsync(string! methodName, object! returnTypeJsonTypeInfo, object?[]? paramValues, object?[]? paramJsonTypeInfos) -> System.Threading.Tasks.Task!
Microsoft.Maui.Controls.HybridWebView.RawMessageReceived -> System.EventHandler?
Microsoft.Maui.Controls.HybridWebView.SendRawMessage(string! rawMessage) -> void
Microsoft.Maui.Controls.HybridWebViewRawMessageReceivedEventArgs
diff --git a/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt
index d9e2224b59d9..7d2d44e795fe 100644
--- a/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt
+++ b/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt
@@ -73,7 +73,8 @@ Microsoft.Maui.Controls.HybridWebView.EvaluateJavaScriptAsync(string! script) ->
Microsoft.Maui.Controls.HybridWebView.HybridRoot.get -> string?
Microsoft.Maui.Controls.HybridWebView.HybridRoot.set -> void
Microsoft.Maui.Controls.HybridWebView.HybridWebView() -> void
-Microsoft.Maui.Controls.HybridWebView.InvokeJavaScriptAsync(string! methodName, System.Text.Json.Serialization.Metadata.JsonTypeInfo! returnTypeJsonTypeInfo, object?[]? paramValues = null, System.Text.Json.Serialization.Metadata.JsonTypeInfo?[]? paramJsonTypeInfos = null) -> System.Threading.Tasks.Task!
+Microsoft.Maui.Controls.HybridWebView.InvokeJavaScriptAsync(string! methodName, object?[]? paramValues, System.Text.Json.Serialization.Metadata.JsonTypeInfo?[]? paramJsonTypeInfos = null) -> System.Threading.Tasks.Task!
+Microsoft.Maui.Controls.HybridWebView.InvokeJavaScriptAsync(string! methodName, System.Text.Json.Serialization.Metadata.JsonTypeInfo! returnTypeJsonTypeInfo, object?[]? paramValues = null, System.Text.Json.Serialization.Metadata.JsonTypeInfo?[]? paramJsonTypeInfos = null) -> System.Threading.Tasks.Task!
Microsoft.Maui.Controls.HybridWebView.RawMessageReceived -> System.EventHandler?
Microsoft.Maui.Controls.HybridWebView.SendRawMessage(string! rawMessage) -> void
Microsoft.Maui.Controls.HybridWebViewRawMessageReceivedEventArgs
diff --git a/src/Controls/src/Core/PublicAPI/net/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net/PublicAPI.Unshipped.txt
index 0a53e93e7fbc..92b3f1bc209b 100644
--- a/src/Controls/src/Core/PublicAPI/net/PublicAPI.Unshipped.txt
+++ b/src/Controls/src/Core/PublicAPI/net/PublicAPI.Unshipped.txt
@@ -72,7 +72,8 @@ Microsoft.Maui.Controls.HybridWebView.EvaluateJavaScriptAsync(string! script) ->
Microsoft.Maui.Controls.HybridWebView.HybridRoot.get -> string?
Microsoft.Maui.Controls.HybridWebView.HybridRoot.set -> void
Microsoft.Maui.Controls.HybridWebView.HybridWebView() -> void
-Microsoft.Maui.Controls.HybridWebView.InvokeJavaScriptAsync(string! methodName, System.Text.Json.Serialization.Metadata.JsonTypeInfo! returnTypeJsonTypeInfo, object?[]? paramValues = null, System.Text.Json.Serialization.Metadata.JsonTypeInfo?[]? paramJsonTypeInfos = null) -> System.Threading.Tasks.Task!
+Microsoft.Maui.Controls.HybridWebView.InvokeJavaScriptAsync(string! methodName, object?[]? paramValues = null, object?[]? paramJsonTypeInfos = null) -> System.Threading.Tasks.Task!
+Microsoft.Maui.Controls.HybridWebView.InvokeJavaScriptAsync(string! methodName, object! returnTypeJsonTypeInfo, object?[]? paramValues, object?[]? paramJsonTypeInfos) -> System.Threading.Tasks.Task!
Microsoft.Maui.Controls.HybridWebView.RawMessageReceived -> System.EventHandler?
Microsoft.Maui.Controls.HybridWebView.SendRawMessage(string! rawMessage) -> void
Microsoft.Maui.Controls.HybridWebViewRawMessageReceivedEventArgs
diff --git a/src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txt
index 0a53e93e7fbc..92b3f1bc209b 100644
--- a/src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txt
+++ b/src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txt
@@ -72,7 +72,8 @@ Microsoft.Maui.Controls.HybridWebView.EvaluateJavaScriptAsync(string! script) ->
Microsoft.Maui.Controls.HybridWebView.HybridRoot.get -> string?
Microsoft.Maui.Controls.HybridWebView.HybridRoot.set -> void
Microsoft.Maui.Controls.HybridWebView.HybridWebView() -> void
-Microsoft.Maui.Controls.HybridWebView.InvokeJavaScriptAsync(string! methodName, System.Text.Json.Serialization.Metadata.JsonTypeInfo! returnTypeJsonTypeInfo, object?[]? paramValues = null, System.Text.Json.Serialization.Metadata.JsonTypeInfo?[]? paramJsonTypeInfos = null) -> System.Threading.Tasks.Task!
+Microsoft.Maui.Controls.HybridWebView.InvokeJavaScriptAsync(string! methodName, object?[]? paramValues = null, object?[]? paramJsonTypeInfos = null) -> System.Threading.Tasks.Task!
+Microsoft.Maui.Controls.HybridWebView.InvokeJavaScriptAsync(string! methodName, object! returnTypeJsonTypeInfo, object?[]? paramValues, object?[]? paramJsonTypeInfos) -> System.Threading.Tasks.Task!
Microsoft.Maui.Controls.HybridWebView.RawMessageReceived -> System.EventHandler?
Microsoft.Maui.Controls.HybridWebView.SendRawMessage(string! rawMessage) -> void
Microsoft.Maui.Controls.HybridWebViewRawMessageReceivedEventArgs
diff --git a/src/Controls/src/Core/WebView/WebView.cs b/src/Controls/src/Core/WebView/WebView.cs
index 085b92bfe192..77950e87a05e 100644
--- a/src/Controls/src/Core/WebView/WebView.cs
+++ b/src/Controls/src/Core/WebView/WebView.cs
@@ -287,7 +287,7 @@ public IPlatformElementConfiguration On() where T : IConfigPlatfo
return _platformConfigurationRegistry.Value.On();
}
- private static string EscapeJsString(string js)
+ internal static string EscapeJsString(string js)
{
if (js == null)
return null;
diff --git a/src/Core/src/Core.csproj b/src/Core/src/Core.csproj
index 6f16c8da46d8..375f8505a939 100644
--- a/src/Core/src/Core.csproj
+++ b/src/Core/src/Core.csproj
@@ -37,7 +37,6 @@
-
diff --git a/src/Core/src/Core/IHybridWebView.cs b/src/Core/src/Core/IHybridWebView.cs
index 07bc7a1886db..e30c92f3af89 100644
--- a/src/Core/src/Core/IHybridWebView.cs
+++ b/src/Core/src/Core/IHybridWebView.cs
@@ -1,5 +1,4 @@
-using System.Text.Json.Serialization.Metadata;
-using System.Threading.Tasks;
+using System.Threading.Tasks;
namespace Microsoft.Maui
{
@@ -18,7 +17,7 @@ public interface IHybridWebView : IView
///
string? HybridRoot { get; }
- void RawMessageReceived(string rawMessage);
+ void MessageReceived(string rawMessage);
void SendRawMessage(string rawMessage);
@@ -28,21 +27,5 @@ public interface IHybridWebView : IView
/// The JavaScript code to run.
/// The return value (if any) of running the script.
Task EvaluateJavaScriptAsync(string script);
-
- ///
- /// Invokes a JavaScript method named and optionally passes in the parameter values specified
- /// by by JSON-encoding each one.
- ///
- /// The type of the return value.
- /// The name of the JavaScript method to invoke.
- /// Metadata about deserializing the type of the return value specified by .
- /// Optional array of objects to be passed to the JavaScript method by JSON-encoding each one.
- /// Optional array of metadata about serializing the types of the parameters specified by .
- /// An object of type containing the return value of the called method.
- Task InvokeJavaScriptAsync(
- string methodName,
- JsonTypeInfo returnTypeJsonTypeInfo,
- object?[]? paramValues = null,
- JsonTypeInfo?[]? paramJsonTypeInfos = null);
}
}
diff --git a/src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.Android.cs b/src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.Android.cs
index 4164f0e17b61..552949da2044 100644
--- a/src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.Android.cs
+++ b/src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.Android.cs
@@ -49,7 +49,7 @@ public HybridWebViewJavaScriptInterface(HybridWebViewHandler hybridWebViewHandle
[JavascriptInterface]
public void SendMessage(string message)
{
- Handler?.MessageReceived(message);
+ Handler?.VirtualView?.MessageReceived(message);
}
}
@@ -86,9 +86,12 @@ protected override void DisconnectHandler(AWebView platformView)
base.DisconnectHandler(platformView);
}
- internal static void EvaluateJavaScript(IHybridWebViewHandler handler, IHybridWebView hybridWebView, EvaluateJavaScriptAsyncRequest request)
+ public static void MapEvaluateJavaScriptAsync(IHybridWebViewHandler handler, IHybridWebView hybridWebView, object? arg)
{
- handler.PlatformView.EvaluateJavaScript(request);
+ if (arg is EvaluateJavaScriptAsyncRequest request)
+ {
+ handler.PlatformView.EvaluateJavaScript(request);
+ }
}
public static void MapSendRawMessage(IHybridWebViewHandler handler, IHybridWebView hybridWebView, object? arg)
diff --git a/src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.Windows.cs b/src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.Windows.cs
index 3b9e4c12ea24..9ecd61c8c830 100644
--- a/src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.Windows.cs
+++ b/src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.Windows.cs
@@ -5,6 +5,7 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.Web.WebView2.Core;
+using Windows.Foundation;
using Windows.Storage.Streams;
namespace Microsoft.Maui.Handlers
@@ -72,13 +73,20 @@ protected override void DisconnectHandler(WebView2 platformView)
base.DisconnectHandler(platformView);
}
- internal static void EvaluateJavaScript(IHybridWebViewHandler handler, IHybridWebView hybridWebView, EvaluateJavaScriptAsyncRequest request)
+ public static void MapEvaluateJavaScriptAsync(IHybridWebViewHandler handler, IHybridWebView hybridWebView, object? arg)
{
- if (handler.PlatformView is not MauiHybridWebView hybridPlatformWebView)
+ if (arg is not EvaluateJavaScriptAsyncRequest request ||
+ handler.PlatformView is not MauiHybridWebView hybridPlatformWebView)
{
return;
}
+ if (handler.PlatformView is null)
+ {
+ request.SetCanceled();
+ return;
+ }
+
hybridPlatformWebView.RunAfterInitialize(() => hybridPlatformWebView.EvaluateJavaScript(request));
}
@@ -94,7 +102,7 @@ public static void MapSendRawMessage(IHybridWebViewHandler handler, IHybridWebVi
private void OnWebMessageReceived(WebView2 sender, CoreWebView2WebMessageReceivedEventArgs args)
{
- MessageReceived(args.TryGetWebMessageAsString());
+ VirtualView?.MessageReceived(args.TryGetWebMessageAsString());
}
private async void OnWebResourceRequested(CoreWebView2 sender, CoreWebView2WebResourceRequestedEventArgs eventArgs)
diff --git a/src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.cs b/src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.cs
index f6f3fd9768bf..970ad636a005 100644
--- a/src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.cs
+++ b/src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.cs
@@ -18,17 +18,10 @@
using System.Threading.Tasks;
using Microsoft.Maui.Storage;
using System;
-using System.Collections.Concurrent;
-using System.Threading;
-using Microsoft.Maui.Devices;
-using System.Text.RegularExpressions;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text.Json;
namespace Microsoft.Maui.Handlers
{
- public partial class HybridWebViewHandler : IHybridWebViewHandler, IHybridWebViewTaskManager
+ public partial class HybridWebViewHandler : IHybridWebViewHandler
{
// Using an IP address means that the web view doesn't wait for any DNS resolution,
// making it substantially faster. Note that this isn't real HTTP traffic, since
@@ -49,221 +42,29 @@ public partial class HybridWebViewHandler : IHybridWebViewHandler, IHybridWebVie
internal static readonly string AppOrigin = $"{AppHostScheme}://{AppHostAddress}/";
internal static readonly Uri AppOriginUri = new(AppOrigin);
-
+
public static IPropertyMapper Mapper = new PropertyMapper(ViewHandler.ViewMapper)
- {
+ {
};
- public static CommandMapper CommandMapper = new(ViewCommandMapper)
- {
+ public static CommandMapper CommandMapper = new(ViewCommandMapper)
+ {
[nameof(IHybridWebView.EvaluateJavaScriptAsync)] = MapEvaluateJavaScriptAsync,
- [nameof(IHybridWebView.InvokeJavaScriptAsync)] = MapInvokeJavaScriptAsync,
[nameof(IHybridWebView.SendRawMessage)] = MapSendRawMessage,
- };
-
- public HybridWebViewHandler() : base(Mapper, CommandMapper)
- {
- }
-
- public HybridWebViewHandler(IPropertyMapper? mapper = null, CommandMapper? commandMapper = null)
- : base(mapper ?? Mapper, commandMapper ?? CommandMapper)
- {
- }
+ };
- IHybridWebView IHybridWebViewHandler.VirtualView => VirtualView;
+ public HybridWebViewHandler() : base(Mapper, CommandMapper)
+ {
+ }
- PlatformView IHybridWebViewHandler.PlatformView => PlatformView;
+ public HybridWebViewHandler(IPropertyMapper? mapper = null, CommandMapper? commandMapper = null)
+ : base(mapper ?? Mapper, commandMapper ?? CommandMapper)
+ {
+ }
+ IHybridWebView IHybridWebViewHandler.VirtualView => VirtualView;
-
- ///
- /// Handler for when the an Async JavaScript task has completed and needs to notify .NET.
- ///
- private void AsyncTaskCompleted(string taskId, string result)
- {
- // Look for the callback in the list of pending callbacks
- if (!string.IsNullOrEmpty(taskId) && _asyncTaskCallbacks.TryGetValue(taskId, out var callback))
- {
- // Get the callback and remove it from the list
- callback.SetResult(result);
-
- // Remove the callback
- _asyncTaskCallbacks.TryRemove(taskId, out var _);
- }
- }
-
- void MessageReceived(string rawMessage)
- {
- if (string.IsNullOrEmpty(rawMessage))
- {
- throw new ArgumentException($"The raw message cannot be null or empty.", nameof(rawMessage));
- }
-#if !NETSTANDARD2_0
- var indexOfPipe = rawMessage.IndexOf('|', StringComparison.Ordinal);
-#else
- var indexOfPipe = rawMessage.IndexOf("|", StringComparison.Ordinal);
-#endif
- if (indexOfPipe == -1)
- {
- throw new ArgumentException($"The raw message must contain a pipe character ('|').", nameof(rawMessage));
- }
-
- var messageType = rawMessage.Substring(0, indexOfPipe);
- var messageContent = rawMessage.Substring(indexOfPipe + 1);
-
- switch (messageType)
- {
- case "InvokeMethodCompleted":
- {
- var sections = messageContent.Split('|');
- var taskId = sections[0];
- var result = sections[1];
- AsyncTaskCompleted(taskId, result);
- }
- break;
- case "RawMessage":
- VirtualView?.RawMessageReceived(messageContent);
- break;
- default:
- throw new ArgumentException($"The message type '{messageType}' is not recognized.", nameof(rawMessage));
- }
- }
-
-#if PLATFORM && !TIZEN
- public static async void MapEvaluateJavaScriptAsync(IHybridWebViewHandler handler, IHybridWebView hybridWebView, object? arg)
- {
- if (arg is not EvaluateJavaScriptAsyncRequest request ||
- handler.PlatformView is not MauiHybridWebView hybridPlatformWebView)
- {
- return;
- }
-
- if (handler.PlatformView is null)
- {
- request.SetCanceled();
- return;
- }
-
- var script = request.Script;
- // Make all the platforms mimic Android's implementation, which is by far the most complete.
- if (!OperatingSystem.IsAndroid())
- {
- script = EscapeJsString(script);
-
- if (!OperatingSystem.IsWindows())
- {
- // Use JSON.stringify() method to converts a JavaScript value to a JSON string
- script = "try{JSON.stringify(eval('" + script + "'))}catch(e){'null'};";
- }
- else
- {
- script = "try{eval('" + script + "')}catch(e){'null'};";
- }
- }
-
- // Use the handler command to evaluate the JS
- var innerRequest = new EvaluateJavaScriptAsyncRequest(script);
- EvaluateJavaScript(handler, hybridWebView, innerRequest);
-
- var result = await innerRequest.Task;
-
- //if the js function errored or returned null/undefined treat it as null
- if (result == "null")
- {
- result = null;
- }
- //JSON.stringify wraps the result in literal quotes, we just want the actual returned result
- //note that if the js function returns the string "null" we will get here and not above
- else if (result != null)
- {
- result = result.Trim('"');
- }
-
- request.SetResult(result!);
-
- }
-#endif
-
- public static async void MapInvokeJavaScriptAsync(IHybridWebViewHandler handler, IHybridWebView hybridWebView, object? arg)
- {
-#if PLATFORM && !TIZEN
- if (arg is not HybridWebViewInvokeJavaScriptRequest invokeJavaScriptRequest ||
- handler.PlatformView is not MauiHybridWebView hybridPlatformWebView ||
- handler is not IHybridWebViewTaskManager taskManager)
- {
- return;
- }
-
- // Create a callback for async JavaScript methods to invoke when they are done
- var callback = new TaskCompletionSource();
- var currentInvokeTaskId = $"{taskManager.GetNextInvokeTaskId()}";
- taskManager.AsyncTaskCallbacks.TryAdd(currentInvokeTaskId, callback);
-
- var paramsValuesStringArray =
- invokeJavaScriptRequest.ParamValues == null
- ? string.Empty
- : string.Join(
- ", ",
- invokeJavaScriptRequest.ParamValues.Select((v, i) => (v == null ? "null" : JsonSerializer.Serialize(v, invokeJavaScriptRequest.ParamJsonTypeInfos![i]!))));
-
- await handler.InvokeAsync(nameof(IHybridWebView.EvaluateJavaScriptAsync),
- new EvaluateJavaScriptAsyncRequest($"window.HybridWebView.InvokeMethod({currentInvokeTaskId}, {invokeJavaScriptRequest.MethodName}, [{paramsValuesStringArray}])"));
-
- var stringResult = await callback.Task;
-
- if (stringResult is null)
- {
- invokeJavaScriptRequest.SetResult(null);
- }
- else
- {
- var typedResult = JsonSerializer.Deserialize(stringResult, invokeJavaScriptRequest.ReturnTypeJsonTypeInfo);
- invokeJavaScriptRequest.SetResult(typedResult);
- }
-#else
- await Task.CompletedTask;
-#endif
- }
-
-#if PLATFORM && !TIZEN
- // Copied from WebView.cs
- internal static string? EscapeJsString(string js)
- {
- if (js == null)
- return null;
-
- if (!js.Contains('\'', StringComparison.Ordinal))
- return js;
-
- //get every quote in the string along with all the backslashes preceding it
- var singleQuotes = Regex.Matches(js, @"(\\*?)'");
-
- var uniqueMatches = new List();
-
- for (var i = 0; i < singleQuotes.Count; i++)
- {
- var matchedString = singleQuotes[i].Value;
- if (!uniqueMatches.Contains(matchedString))
- {
- uniqueMatches.Add(matchedString);
- }
- }
-
- uniqueMatches.Sort((x, y) => y.Length.CompareTo(x.Length));
-
- //escape all quotes from the script as well as add additional escaping to all quotes that were already escaped
- for (var i = 0; i < uniqueMatches.Count; i++)
- {
- var match = uniqueMatches[i];
- var numberOfBackslashes = match.Length - 1;
- var slashesToAdd = (numberOfBackslashes * 2) + 1;
- var replacementStr = "'".PadLeft(slashesToAdd + 1, '\\');
- js = Regex.Replace(js, @"(?<=[^\\])" + Regex.Escape(match), replacementStr);
- }
-
- return js;
- }
-#endif
+ PlatformView IHybridWebViewHandler.PlatformView => PlatformView;
internal static async Task GetAssetContentAsync(string assetPath)
{
@@ -291,15 +92,5 @@ await handler.InvokeAsync(nameof(IHybridWebView.EvaluateJavaScriptAsync),
#if !NETSTANDARD
internal static readonly FileExtensionContentTypeProvider ContentTypeProvider = new();
#endif
-
- // IHybridWebViewTaskManager implementation
- ConcurrentDictionary> _asyncTaskCallbacks = new ConcurrentDictionary>();
- int _asyncInvokeTaskId;
-
- int IHybridWebViewTaskManager.GetNextInvokeTaskId()
- {
- return Interlocked.Increment(ref _asyncInvokeTaskId);
- }
- ConcurrentDictionary> IHybridWebViewTaskManager.AsyncTaskCallbacks => _asyncTaskCallbacks;
}
}
diff --git a/src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.iOS.cs b/src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.iOS.cs
index 1cfce539773f..aaec92d2cf63 100644
--- a/src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.iOS.cs
+++ b/src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.iOS.cs
@@ -59,9 +59,18 @@ protected override WKWebView CreatePlatformView()
return webview;
}
- internal static void EvaluateJavaScript(IHybridWebViewHandler handler, IHybridWebView hybridWebView, EvaluateJavaScriptAsyncRequest request)
+ public static void MapEvaluateJavaScriptAsync(IHybridWebViewHandler handler, IHybridWebView hybridWebView, object? arg)
{
- handler.PlatformView.EvaluateJavaScript(request);
+ if (arg is EvaluateJavaScriptAsyncRequest request)
+ {
+ if (handler.PlatformView is null)
+ {
+ request.SetCanceled();
+ return;
+ }
+
+ handler.PlatformView.EvaluateJavaScript(request);
+ }
}
public static void MapSendRawMessage(IHybridWebViewHandler handler, IHybridWebView hybridWebView, object? arg)
@@ -76,7 +85,7 @@ public static void MapSendRawMessage(IHybridWebViewHandler handler, IHybridWebVi
private void MessageReceived(Uri uri, string message)
{
- MessageReceived(message);
+ VirtualView?.MessageReceived(message);
}
protected override void ConnectHandler(WKWebView platformView)
diff --git a/src/Core/src/Handlers/HybridWebView/IHybridWebViewTaskManager.cs b/src/Core/src/Handlers/HybridWebView/IHybridWebViewTaskManager.cs
deleted file mode 100644
index 2064d681d5e2..000000000000
--- a/src/Core/src/Handlers/HybridWebView/IHybridWebViewTaskManager.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using System.Threading.Tasks;
-using System.Collections.Concurrent;
-
-namespace Microsoft.Maui.Handlers
-{
- internal interface IHybridWebViewTaskManager
- {
- int GetNextInvokeTaskId();
- ConcurrentDictionary> AsyncTaskCallbacks { get; }
- }
-}
diff --git a/src/Core/src/Primitives/HybridWebViewInvokeJavaScriptRequest.cs b/src/Core/src/Primitives/HybridWebViewInvokeJavaScriptRequest.cs
deleted file mode 100644
index 37d4fb1880e2..000000000000
--- a/src/Core/src/Primitives/HybridWebViewInvokeJavaScriptRequest.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using System.Text.Json.Serialization.Metadata;
-using System.Threading.Tasks;
-
-namespace Microsoft.Maui
-{
- public class HybridWebViewInvokeJavaScriptRequest(string methodName, JsonTypeInfo returnTypeJsonTypeInfo, object?[]? paramValues, JsonTypeInfo?[]? paramJsonTypeInfos)
- : TaskCompletionSource