Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move HybridWebView platform code to handlers (attempt 2) #24952

Merged
merged 4 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 15 additions & 145 deletions src/Controls/src/Core/HybridWebView/HybridWebView.cs
Original file line number Diff line number Diff line change
@@ -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
{
Expand Down Expand Up @@ -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));
}

/// <summary>
Expand All @@ -91,36 +57,12 @@ public void SendRawMessage(string rawMessage)
});
}

private int _invokeTaskId;
private Dictionary<string, TaskCompletionSource<string>> asyncTaskCallbacks = new Dictionary<string, TaskCompletionSource<string>>();

/// <summary>
/// Handler for when the an Async JavaScript task has completed and needs to notify .NET.
/// </summary>
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);
}
}

/// <summary>
/// Invokes a JavaScript method named <paramref name="methodName"/> and optionally passes in the parameter values
/// specified by <paramref name="paramValues"/>.
/// </summary>
/// <param name="methodName">The name of the JavaScript method to invoke.</param>
/// <param name="paramValues">Optional array of objects to be passed to the JavaScript method.</param>
/// <param name="paramJsonTypeInfos">Optional array of metadata about serializing the types of the parameters specified by <paramref name="paramValues"/>.</param>
/// <returns>A string containing the return value of the called method.</returns>
#if WINDOWS || ANDROID || IOS || MACCATALYST
public async Task<string?> InvokeJavaScriptAsync(string methodName, object?[]? paramValues, JsonTypeInfo?[]? paramJsonTypeInfos = null)
/// <inheritdoc/>
public async Task<TReturnType?> InvokeJavaScriptAsync<TReturnType>(
string methodName,
JsonTypeInfo<TReturnType> returnTypeJsonTypeInfo,
object?[]? paramValues = null,
JsonTypeInfo?[]? paramJsonTypeInfos = null)
{
if (string.IsNullOrEmpty(methodName))
{
Expand All @@ -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<string>();
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<string?> 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

/// <summary>
/// Invokes a JavaScript method named <paramref name="methodName"/> and optionally passes in the parameter values specified
/// by <paramref name="paramValues"/> by JSON-encoding each one.
/// </summary>
/// <typeparam name="TReturnType">The type of the return value to deserialize from JSON.</typeparam>
/// <param name="methodName">The name of the JavaScript method to invoke.</param>
/// <param name="returnTypeJsonTypeInfo">Metadata about deserializing the type of the return value specified by <typeparamref name="TReturnType"/>.</param>
/// <param name="paramValues">Optional array of objects to be passed to the JavaScript method by JSON-encoding each one.</param>
/// <param name="paramJsonTypeInfos">Optional array of metadata about serializing the types of the parameters specified by <paramref name="paramValues"/>.</param>
/// <returns>An object of type <typeparamref name="TReturnType"/> containing the return value of the called method.</returns>
#if WINDOWS || ANDROID || IOS || MACCATALYST
public async Task<TReturnType?> InvokeJavaScriptAsync<TReturnType>(string methodName, JsonTypeInfo<TReturnType?> 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<TReturnType?>(stringResult, returnTypeJsonTypeInfo);
}
#else
public Task<TReturnType?> InvokeJavaScriptAsync<TReturnType>(string methodName, object returnTypeJsonTypeInfo, object?[]? paramValues, object?[]? paramJsonTypeInfos)
{
throw new NotImplementedException();
return (TReturnType)invokeResult;
}
#endif

/// <inheritdoc/>
public async Task<string?> EvaluateJavaScriptAsync(string script)
Expand All @@ -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;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<string?>!
Microsoft.Maui.Controls.HybridWebView.InvokeJavaScriptAsync<TReturnType>(string! methodName, System.Text.Json.Serialization.Metadata.JsonTypeInfo<TReturnType?>! returnTypeJsonTypeInfo, object?[]? paramValues = null, System.Text.Json.Serialization.Metadata.JsonTypeInfo?[]? paramJsonTypeInfos = null) -> System.Threading.Tasks.Task<TReturnType?>!
Microsoft.Maui.Controls.HybridWebView.InvokeJavaScriptAsync<TReturnType>(string! methodName, System.Text.Json.Serialization.Metadata.JsonTypeInfo<TReturnType>! returnTypeJsonTypeInfo, object?[]? paramValues = null, System.Text.Json.Serialization.Metadata.JsonTypeInfo?[]? paramJsonTypeInfos = null) -> System.Threading.Tasks.Task<TReturnType?>!
Microsoft.Maui.Controls.HybridWebView.RawMessageReceived -> System.EventHandler<Microsoft.Maui.Controls.HybridWebViewRawMessageReceivedEventArgs!>?
Microsoft.Maui.Controls.HybridWebView.SendRawMessage(string! rawMessage) -> void
Microsoft.Maui.Controls.HybridWebViewRawMessageReceivedEventArgs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<string?>!
Microsoft.Maui.Controls.HybridWebView.InvokeJavaScriptAsync<TReturnType>(string! methodName, System.Text.Json.Serialization.Metadata.JsonTypeInfo<TReturnType?>! returnTypeJsonTypeInfo, object?[]? paramValues = null, System.Text.Json.Serialization.Metadata.JsonTypeInfo?[]? paramJsonTypeInfos = null) -> System.Threading.Tasks.Task<TReturnType?>!
Microsoft.Maui.Controls.HybridWebView.InvokeJavaScriptAsync<TReturnType>(string! methodName, System.Text.Json.Serialization.Metadata.JsonTypeInfo<TReturnType>! returnTypeJsonTypeInfo, object?[]? paramValues = null, System.Text.Json.Serialization.Metadata.JsonTypeInfo?[]? paramJsonTypeInfos = null) -> System.Threading.Tasks.Task<TReturnType?>!
Microsoft.Maui.Controls.HybridWebView.RawMessageReceived -> System.EventHandler<Microsoft.Maui.Controls.HybridWebViewRawMessageReceivedEventArgs!>?
Microsoft.Maui.Controls.HybridWebView.SendRawMessage(string! rawMessage) -> void
Microsoft.Maui.Controls.HybridWebViewRawMessageReceivedEventArgs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<string?>!
Microsoft.Maui.Controls.HybridWebView.InvokeJavaScriptAsync<TReturnType>(string! methodName, System.Text.Json.Serialization.Metadata.JsonTypeInfo<TReturnType?>! returnTypeJsonTypeInfo, object?[]? paramValues = null, System.Text.Json.Serialization.Metadata.JsonTypeInfo?[]? paramJsonTypeInfos = null) -> System.Threading.Tasks.Task<TReturnType?>!
Microsoft.Maui.Controls.HybridWebView.InvokeJavaScriptAsync<TReturnType>(string! methodName, System.Text.Json.Serialization.Metadata.JsonTypeInfo<TReturnType>! returnTypeJsonTypeInfo, object?[]? paramValues = null, System.Text.Json.Serialization.Metadata.JsonTypeInfo?[]? paramJsonTypeInfos = null) -> System.Threading.Tasks.Task<TReturnType?>!
Microsoft.Maui.Controls.HybridWebView.RawMessageReceived -> System.EventHandler<Microsoft.Maui.Controls.HybridWebViewRawMessageReceivedEventArgs!>?
Microsoft.Maui.Controls.HybridWebView.SendRawMessage(string! rawMessage) -> void
Microsoft.Maui.Controls.HybridWebViewRawMessageReceivedEventArgs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<string?>!
Microsoft.Maui.Controls.HybridWebView.InvokeJavaScriptAsync<TReturnType>(string! methodName, object! returnTypeJsonTypeInfo, object?[]? paramValues, object?[]? paramJsonTypeInfos) -> System.Threading.Tasks.Task<TReturnType?>!
Microsoft.Maui.Controls.HybridWebView.InvokeJavaScriptAsync<TReturnType>(string! methodName, System.Text.Json.Serialization.Metadata.JsonTypeInfo<TReturnType>! returnTypeJsonTypeInfo, object?[]? paramValues = null, System.Text.Json.Serialization.Metadata.JsonTypeInfo?[]? paramJsonTypeInfos = null) -> System.Threading.Tasks.Task<TReturnType?>!
Microsoft.Maui.Controls.HybridWebView.RawMessageReceived -> System.EventHandler<Microsoft.Maui.Controls.HybridWebViewRawMessageReceivedEventArgs!>?
Microsoft.Maui.Controls.HybridWebView.SendRawMessage(string! rawMessage) -> void
Microsoft.Maui.Controls.HybridWebViewRawMessageReceivedEventArgs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<string?>!
Microsoft.Maui.Controls.HybridWebView.InvokeJavaScriptAsync<TReturnType>(string! methodName, System.Text.Json.Serialization.Metadata.JsonTypeInfo<TReturnType?>! returnTypeJsonTypeInfo, object?[]? paramValues = null, System.Text.Json.Serialization.Metadata.JsonTypeInfo?[]? paramJsonTypeInfos = null) -> System.Threading.Tasks.Task<TReturnType?>!
Microsoft.Maui.Controls.HybridWebView.InvokeJavaScriptAsync<TReturnType>(string! methodName, System.Text.Json.Serialization.Metadata.JsonTypeInfo<TReturnType>! returnTypeJsonTypeInfo, object?[]? paramValues = null, System.Text.Json.Serialization.Metadata.JsonTypeInfo?[]? paramJsonTypeInfos = null) -> System.Threading.Tasks.Task<TReturnType?>!
Microsoft.Maui.Controls.HybridWebView.RawMessageReceived -> System.EventHandler<Microsoft.Maui.Controls.HybridWebViewRawMessageReceivedEventArgs!>?
Microsoft.Maui.Controls.HybridWebView.SendRawMessage(string! rawMessage) -> void
Microsoft.Maui.Controls.HybridWebViewRawMessageReceivedEventArgs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<string?>!
Microsoft.Maui.Controls.HybridWebView.InvokeJavaScriptAsync<TReturnType>(string! methodName, object! returnTypeJsonTypeInfo, object?[]? paramValues, object?[]? paramJsonTypeInfos) -> System.Threading.Tasks.Task<TReturnType?>!
Microsoft.Maui.Controls.HybridWebView.InvokeJavaScriptAsync<TReturnType>(string! methodName, System.Text.Json.Serialization.Metadata.JsonTypeInfo<TReturnType>! returnTypeJsonTypeInfo, object?[]? paramValues = null, System.Text.Json.Serialization.Metadata.JsonTypeInfo?[]? paramJsonTypeInfos = null) -> System.Threading.Tasks.Task<TReturnType?>!
Microsoft.Maui.Controls.HybridWebView.RawMessageReceived -> System.EventHandler<Microsoft.Maui.Controls.HybridWebViewRawMessageReceivedEventArgs!>?
Microsoft.Maui.Controls.HybridWebView.SendRawMessage(string! rawMessage) -> void
Microsoft.Maui.Controls.HybridWebViewRawMessageReceivedEventArgs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<string?>!
Microsoft.Maui.Controls.HybridWebView.InvokeJavaScriptAsync<TReturnType>(string! methodName, object! returnTypeJsonTypeInfo, object?[]? paramValues, object?[]? paramJsonTypeInfos) -> System.Threading.Tasks.Task<TReturnType?>!
Microsoft.Maui.Controls.HybridWebView.InvokeJavaScriptAsync<TReturnType>(string! methodName, System.Text.Json.Serialization.Metadata.JsonTypeInfo<TReturnType>! returnTypeJsonTypeInfo, object?[]? paramValues = null, System.Text.Json.Serialization.Metadata.JsonTypeInfo?[]? paramJsonTypeInfos = null) -> System.Threading.Tasks.Task<TReturnType?>!
Microsoft.Maui.Controls.HybridWebView.RawMessageReceived -> System.EventHandler<Microsoft.Maui.Controls.HybridWebViewRawMessageReceivedEventArgs!>?
Microsoft.Maui.Controls.HybridWebView.SendRawMessage(string! rawMessage) -> void
Microsoft.Maui.Controls.HybridWebViewRawMessageReceivedEventArgs
Expand Down
2 changes: 1 addition & 1 deletion src/Controls/src/Core/WebView/WebView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ public IPlatformElementConfiguration<T, WebView> On<T>() where T : IConfigPlatfo
return _platformConfigurationRegistry.Value.On<T>();
}

internal static string EscapeJsString(string js)
private static string EscapeJsString(string js)
{
if (js == null)
return null;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#if !ANDROID
using System;
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
Expand Down Expand Up @@ -356,4 +355,3 @@ private async Task WaitForHybridWebViewLoaded(HybridWebView hybridWebView)
}
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
1 change: 1 addition & 0 deletions src/Core/src/Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
<ProjectReference Include="..\..\Graphics\src\Graphics\Graphics.csproj" />
<PackageReference Include="System.Numerics.Vectors" Condition="$(TargetFramework.StartsWith('netstandard'))" />
<PackageReference Include="System.Text.Json" Condition="$(TargetFramework.StartsWith('netstandard'))" />
</ItemGroup>
<ItemGroup Condition="$(TargetFramework.Contains('-windows'))">
<PackageReference Include="Microsoft.WindowsAppSDK" />
Expand Down
Loading
Loading