diff --git a/src/Controls/src/Core/HybridWebView/HybridWebView.cs b/src/Controls/src/Core/HybridWebView/HybridWebView.cs
index af44fca85872..8b667474251f 100644
--- a/src/Controls/src/Core/HybridWebView/HybridWebView.cs
+++ b/src/Controls/src/Core/HybridWebView/HybridWebView.cs
@@ -1,13 +1,7 @@
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;
-#endif
+using System.Threading.Tasks;
namespace Microsoft.Maui.Controls
{
@@ -39,37 +33,9 @@ public string? HybridRoot
set { SetValue(HybridRootProperty, value); }
}
- void IHybridWebView.MessageReceived(string rawMessage)
+ void IHybridWebView.RawMessageReceived(string 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));
- }
+ RawMessageReceived?.Invoke(this, new HybridWebViewRawMessageReceivedEventArgs(rawMessage));
}
///
@@ -91,36 +57,12 @@ public void SendRawMessage(string rawMessage)
});
}
- 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)
+ ///
+ public async Task InvokeJavaScriptAsync(
+ string methodName,
+ JsonTypeInfo returnTypeJsonTypeInfo,
+ object?[]? paramValues = null,
+ JsonTypeInfo?[]? paramJsonTypeInfos = null)
{
if (string.IsNullOrEmpty(methodName))
{
@@ -139,57 +81,16 @@ private void AsyncTaskCompleted(string taskId, string result)
throw new ArgumentException($"The number of parameter values does not match the number of parameter JSON type infos.", nameof(paramValues));
}
- // 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]!))));
+ var invokeResult = await Handler?.InvokeAsync(
+ nameof(IHybridWebView.InvokeJavaScriptAsync),
+ new HybridWebViewInvokeJavaScriptRequest(methodName, returnTypeJsonTypeInfo, paramValues, paramJsonTypeInfos))!;
- 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)
+ if (invokeResult is null)
{
return default;
}
- return JsonSerializer.Deserialize(stringResult, returnTypeJsonTypeInfo);
- }
-#else
- public Task InvokeJavaScriptAsync(string methodName, object returnTypeJsonTypeInfo, object?[]? paramValues, object?[]? paramJsonTypeInfos)
- {
- throw new NotImplementedException();
+ return (TReturnType)invokeResult;
}
-#endif
///
public async Task EvaluateJavaScriptAsync(string script)
@@ -199,40 +100,9 @@ private void AsyncTaskCompleted(string taskId, string result)
return null;
}
- // 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),
+ var 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 8d0f89ae7f0e..a8ee9da554ec 100644
--- a/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt
+++ b/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt
@@ -73,8 +73,7 @@ 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, 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.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 686a6fce5bfb..4608b2ad1ff2 100644
--- a/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt
+++ b/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt
@@ -236,8 +236,7 @@ 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, 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.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 686a6fce5bfb..4608b2ad1ff2 100644
--- a/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt
+++ b/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt
@@ -236,8 +236,7 @@ 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, 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.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 92b3f1bc209b..0a53e93e7fbc 100644
--- a/src/Controls/src/Core/PublicAPI/net-tizen/PublicAPI.Unshipped.txt
+++ b/src/Controls/src/Core/PublicAPI/net-tizen/PublicAPI.Unshipped.txt
@@ -72,8 +72,7 @@ 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, 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.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-windows/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt
index 7d2d44e795fe..d9e2224b59d9 100644
--- a/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt
+++ b/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt
@@ -73,8 +73,7 @@ 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, 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.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 92b3f1bc209b..0a53e93e7fbc 100644
--- a/src/Controls/src/Core/PublicAPI/net/PublicAPI.Unshipped.txt
+++ b/src/Controls/src/Core/PublicAPI/net/PublicAPI.Unshipped.txt
@@ -72,8 +72,7 @@ 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, 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.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/netstandard/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txt
index 92b3f1bc209b..0a53e93e7fbc 100644
--- a/src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txt
+++ b/src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txt
@@ -72,8 +72,7 @@ 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, 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.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/WebView/WebView.cs b/src/Controls/src/Core/WebView/WebView.cs
index 77950e87a05e..085b92bfe192 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();
}
- internal static string EscapeJsString(string js)
+ private static string EscapeJsString(string js)
{
if (js == null)
return null;
diff --git a/src/Controls/tests/DeviceTests/Elements/HybridWebView/HybridWebViewTests.cs b/src/Controls/tests/DeviceTests/Elements/HybridWebView/HybridWebViewTests.cs
index 07f2092e9fbf..3e4124ece727 100644
--- a/src/Controls/tests/DeviceTests/Elements/HybridWebView/HybridWebViewTests.cs
+++ b/src/Controls/tests/DeviceTests/Elements/HybridWebView/HybridWebViewTests.cs
@@ -1,5 +1,4 @@
-#if !ANDROID
-using System;
+using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
@@ -356,4 +355,3 @@ private async Task WaitForHybridWebViewLoaded(HybridWebView hybridWebView)
}
}
}
-#endif
\ No newline at end of file
diff --git a/src/Core/AndroidNative/maui/src/main/java/com/microsoft/maui/HybridJavaScriptInterface.java b/src/Core/AndroidNative/maui/src/main/java/com/microsoft/maui/HybridJavaScriptInterface.java
index 74638e8f20e9..1cca8de26564 100644
--- a/src/Core/AndroidNative/maui/src/main/java/com/microsoft/maui/HybridJavaScriptInterface.java
+++ b/src/Core/AndroidNative/maui/src/main/java/com/microsoft/maui/HybridJavaScriptInterface.java
@@ -2,6 +2,7 @@
import androidx.annotation.NonNull;
-public interface HybridJavaScriptInterface {
- void sendMessage(@NonNull String message);
+public abstract class HybridJavaScriptInterface {
+ @android.webkit.JavascriptInterface
+ public abstract void sendMessage(@NonNull String message);
}
diff --git a/src/Core/src/Core.csproj b/src/Core/src/Core.csproj
index 2e2dd7f2dbf2..2030d29c2a60 100644
--- a/src/Core/src/Core.csproj
+++ b/src/Core/src/Core.csproj
@@ -37,6 +37,7 @@
+
diff --git a/src/Core/src/Core/IHybridWebView.cs b/src/Core/src/Core/IHybridWebView.cs
index e30c92f3af89..07bc7a1886db 100644
--- a/src/Core/src/Core/IHybridWebView.cs
+++ b/src/Core/src/Core/IHybridWebView.cs
@@ -1,4 +1,5 @@
-using System.Threading.Tasks;
+using System.Text.Json.Serialization.Metadata;
+using System.Threading.Tasks;
namespace Microsoft.Maui
{
@@ -17,7 +18,7 @@ public interface IHybridWebView : IView
///
string? HybridRoot { get; }
- void MessageReceived(string rawMessage);
+ void RawMessageReceived(string rawMessage);
void SendRawMessage(string rawMessage);
@@ -27,5 +28,21 @@ 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 552949da2044..495fdbfd5128 100644
--- a/src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.Android.cs
+++ b/src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.Android.cs
@@ -35,7 +35,7 @@ protected override AWebView CreatePlatformView()
return platformView;
}
- private sealed class HybridWebViewJavaScriptInterface : Java.Lang.Object, IHybridJavaScriptInterface
+ private sealed class HybridWebViewJavaScriptInterface : HybridJavaScriptInterface
{
private readonly WeakReference _hybridWebViewHandler;
@@ -47,9 +47,9 @@ public HybridWebViewJavaScriptInterface(HybridWebViewHandler hybridWebViewHandle
private HybridWebViewHandler? Handler => _hybridWebViewHandler is not null && _hybridWebViewHandler.TryGetTarget(out var h) ? h : null;
[JavascriptInterface]
- public void SendMessage(string message)
+ public override void SendMessage(string message)
{
- Handler?.VirtualView?.MessageReceived(message);
+ Handler?.MessageReceived(message);
}
}
@@ -86,12 +86,9 @@ protected override void DisconnectHandler(AWebView platformView)
base.DisconnectHandler(platformView);
}
- public static void MapEvaluateJavaScriptAsync(IHybridWebViewHandler handler, IHybridWebView hybridWebView, object? arg)
+ internal static void EvaluateJavaScript(IHybridWebViewHandler handler, IHybridWebView hybridWebView, EvaluateJavaScriptAsyncRequest request)
{
- if (arg is EvaluateJavaScriptAsyncRequest request)
- {
- handler.PlatformView.EvaluateJavaScript(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 9ecd61c8c830..3b9e4c12ea24 100644
--- a/src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.Windows.cs
+++ b/src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.Windows.cs
@@ -5,7 +5,6 @@
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
@@ -73,20 +72,13 @@ protected override void DisconnectHandler(WebView2 platformView)
base.DisconnectHandler(platformView);
}
- public static void MapEvaluateJavaScriptAsync(IHybridWebViewHandler handler, IHybridWebView hybridWebView, object? arg)
+ internal static void EvaluateJavaScript(IHybridWebViewHandler handler, IHybridWebView hybridWebView, EvaluateJavaScriptAsyncRequest request)
{
- if (arg is not EvaluateJavaScriptAsyncRequest request ||
- handler.PlatformView is not MauiHybridWebView hybridPlatformWebView)
+ if (handler.PlatformView is not MauiHybridWebView hybridPlatformWebView)
{
return;
}
- if (handler.PlatformView is null)
- {
- request.SetCanceled();
- return;
- }
-
hybridPlatformWebView.RunAfterInitialize(() => hybridPlatformWebView.EvaluateJavaScript(request));
}
@@ -102,7 +94,7 @@ public static void MapSendRawMessage(IHybridWebViewHandler handler, IHybridWebVi
private void OnWebMessageReceived(WebView2 sender, CoreWebView2WebMessageReceivedEventArgs args)
{
- VirtualView?.MessageReceived(args.TryGetWebMessageAsString());
+ 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 970ad636a005..f6f3fd9768bf 100644
--- a/src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.cs
+++ b/src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.cs
@@ -18,10 +18,17 @@
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
+ public partial class HybridWebViewHandler : IHybridWebViewHandler, IHybridWebViewTaskManager
{
// 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
@@ -42,29 +49,221 @@ public partial class HybridWebViewHandler : IHybridWebViewHandler
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)
+ {
+ }
- public HybridWebViewHandler() : base(Mapper, CommandMapper)
- {
- }
+ IHybridWebView IHybridWebViewHandler.VirtualView => VirtualView;
- public HybridWebViewHandler(IPropertyMapper? mapper = null, CommandMapper? commandMapper = null)
- : base(mapper ?? Mapper, commandMapper ?? CommandMapper)
- {
- }
+ PlatformView IHybridWebViewHandler.PlatformView => PlatformView;
- IHybridWebView IHybridWebViewHandler.VirtualView => VirtualView;
- PlatformView IHybridWebViewHandler.PlatformView => PlatformView;
+
+ ///
+ /// 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
internal static async Task GetAssetContentAsync(string assetPath)
{
@@ -92,5 +291,15 @@ public HybridWebViewHandler(IPropertyMapper? mapper = null, CommandMapper? comma
#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 aaec92d2cf63..1cfce539773f 100644
--- a/src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.iOS.cs
+++ b/src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.iOS.cs
@@ -59,18 +59,9 @@ protected override WKWebView CreatePlatformView()
return webview;
}
- public static void MapEvaluateJavaScriptAsync(IHybridWebViewHandler handler, IHybridWebView hybridWebView, object? arg)
+ internal static void EvaluateJavaScript(IHybridWebViewHandler handler, IHybridWebView hybridWebView, EvaluateJavaScriptAsyncRequest request)
{
- if (arg is EvaluateJavaScriptAsyncRequest request)
- {
- if (handler.PlatformView is null)
- {
- request.SetCanceled();
- return;
- }
-
- handler.PlatformView.EvaluateJavaScript(request);
- }
+ handler.PlatformView.EvaluateJavaScript(request);
}
public static void MapSendRawMessage(IHybridWebViewHandler handler, IHybridWebView hybridWebView, object? arg)
@@ -85,7 +76,7 @@ public static void MapSendRawMessage(IHybridWebViewHandler handler, IHybridWebVi
private void MessageReceived(Uri uri, string message)
{
- VirtualView?.MessageReceived(message);
+ 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
new file mode 100644
index 000000000000..2064d681d5e2
--- /dev/null
+++ b/src/Core/src/Handlers/HybridWebView/IHybridWebViewTaskManager.cs
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 000000000000..37d4fb1880e2
--- /dev/null
+++ b/src/Core/src/Primitives/HybridWebViewInvokeJavaScriptRequest.cs
@@ -0,0 +1,14 @@
+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