Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 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
63 changes: 63 additions & 0 deletions src/Controls/tests/Core.UnitTests/WebViewExtensionsIOSTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using Microsoft.Maui.Handlers;
using Xunit;

#if IOS
using Foundation;
using Microsoft.Maui.Platform;
#endif

namespace Microsoft.Maui.UnitTests
{
#if IOS
public class WebViewExtensionsIOSTests
{
[Fact]
public void HandleWKWebViewResult_NSNull_ReturnsNullString()
{
var result = WebViewExtensions.HandleWKWebViewResult(NSNull.Null);
Assert.Equal("null", result);
}

[Fact]
public void HandleWKWebViewResult_Null_ReturnsNullString()
{
var result = WebViewExtensions.HandleWKWebViewResult(null);
Assert.Equal("null", result);
}

[Fact]
public void HandleWKWebViewResult_NSString_ReturnsString()
{
var nsString = new NSString("Hello World");
var result = WebViewExtensions.HandleWKWebViewResult(nsString);
Assert.Equal("Hello World", result);
}

[Fact]
public void HandleWKWebViewResult_NSNumber_ReturnsNumberString()
{
var nsNumber = NSNumber.FromInt32(42);
var result = WebViewExtensions.HandleWKWebViewResult(nsNumber);
Assert.Equal("42", result);
}

[Fact]
public void HandleWKWebViewResult_NSNumberBoolean_ReturnsBooleanString()
{
var nsNumber = NSNumber.FromBoolean(true);
var result = WebViewExtensions.HandleWKWebViewResult(nsNumber);
Assert.Equal("True", result);
}

[Fact]
public void HandleWKWebViewResult_NSDictionary_ReturnsJSONString()
{
var dict = new NSMutableDictionary();
dict.SetValueForKey(new NSString("value"), new NSString("key"));
var result = WebViewExtensions.HandleWKWebViewResult(dict);
Assert.Contains("key", result);
Assert.Contains("value", result);
}
}
#endif
}
65 changes: 65 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue20288.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Maui.Controls.Sample.Issues.Issue20288"
Title="Issue 20288">
<ScrollView>
<StackLayout Padding="20">
<Label x:Name="InstructionLabel"
Text="This test validates that WebView.EvaluateJavaScriptAsync works correctly on iOS for various JavaScript result types."
Margin="0,0,0,20" />

<WebView x:Name="TestWebView"
HeightRequest="300"
Source="https://httpbin.org/html" />

<Button x:Name="TestStringButton"
Text="Test String Result"
Clicked="OnTestStringClicked"
AutomationId="TestStringButton" />
<Label x:Name="StringResult"
Text="String result will appear here"
AutomationId="StringResult" />

<Button x:Name="TestNumberButton"
Text="Test Number Result"
Clicked="OnTestNumberClicked"
AutomationId="TestNumberButton" />
<Label x:Name="NumberResult"
Text="Number result will appear here"
AutomationId="NumberResult" />

<Button x:Name="TestBooleanButton"
Text="Test Boolean Result"
Clicked="OnTestBooleanClicked"
AutomationId="TestBooleanButton" />
<Label x:Name="BooleanResult"
Text="Boolean result will appear here"
AutomationId="BooleanResult" />

<Button x:Name="TestNullButton"
Text="Test Null Result"
Clicked="OnTestNullClicked"
AutomationId="TestNullButton" />
<Label x:Name="NullResult"
Text="Null result will appear here"
AutomationId="NullResult" />

<Button x:Name="TestObjectButton"
Text="Test Object Result"
Clicked="OnTestObjectClicked"
AutomationId="TestObjectButton" />
<Label x:Name="ObjectResult"
Text="Object result will appear here"
AutomationId="ObjectResult" />

<Button x:Name="TestInnerHTMLButton"
Text="Test innerHTML (Real World Case)"
Clicked="OnTestInnerHTMLClicked"
AutomationId="TestInnerHTMLButton" />
<Label x:Name="InnerHTMLResult"
Text="innerHTML result will appear here"
AutomationId="InnerHTMLResult" />
</StackLayout>
</ScrollView>
</ContentPage>
95 changes: 95 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue20288.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
using System;
using System.Threading.Tasks;
using Microsoft.Maui.Controls;

namespace Maui.Controls.Sample.Issues
{
[Issue(IssueTracker.Github, 20288, "Evaluating javascript in MAUI WebView on IOS returns NULL", PlatformAffected.iOS)]
public partial class Issue20288 : ContentPage
{
public Issue20288()
{
InitializeComponent();
}

private async void OnTestStringClicked(object sender, EventArgs e)
{
try
{
var result = await TestWebView.EvaluateJavaScriptAsync("'Hello World'");
StringResult.Text = $"String: '{result ?? "NULL"}'";
}
catch (Exception ex)
{
StringResult.Text = $"Error: {ex.Message}";
}
}

private async void OnTestNumberClicked(object sender, EventArgs e)
{
try
{
var result = await TestWebView.EvaluateJavaScriptAsync("42");
NumberResult.Text = $"Number: '{result ?? "NULL"}'";
}
catch (Exception ex)
{
NumberResult.Text = $"Error: {ex.Message}";
}
}

private async void OnTestBooleanClicked(object sender, EventArgs e)
{
try
{
var result = await TestWebView.EvaluateJavaScriptAsync("true");
BooleanResult.Text = $"Boolean: '{result ?? "NULL"}'";
}
catch (Exception ex)
{
BooleanResult.Text = $"Error: {ex.Message}";
}
}

private async void OnTestNullClicked(object sender, EventArgs e)
{
try
{
var result = await TestWebView.EvaluateJavaScriptAsync("null");
NullResult.Text = $"Null: '{result ?? "NULL"}'";
}
catch (Exception ex)
{
NullResult.Text = $"Error: {ex.Message}";
}
}

private async void OnTestObjectClicked(object sender, EventArgs e)
{
try
{
var result = await TestWebView.EvaluateJavaScriptAsync("({name: 'test', value: 123})");
ObjectResult.Text = $"Object: '{result ?? "NULL"}'";
}
catch (Exception ex)
{
ObjectResult.Text = $"Error: {ex.Message}";
}
}

private async void OnTestInnerHTMLClicked(object sender, EventArgs e)
{
try
{
// Wait a bit for the page to load
await Task.Delay(2000);
var result = await TestWebView.EvaluateJavaScriptAsync("document.documentElement.innerHTML");
InnerHTMLResult.Text = result != null ? $"innerHTML length: {result.Length}" : "NULL";
}
catch (Exception ex)
{
InnerHTMLResult.Text = $"Error: {ex.Message}";
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.TestCases.Tests.Issues
{
public class Issue20288 : _IssuesUITest
{
public Issue20288(TestDevice testDevice) : base(testDevice)
{
}

public override string Issue => "Evaluating javascript in MAUI WebView on IOS returns NULL";

[Test]
[Category(UITestCategories.WebView)]
public void WebViewEvaluateJavaScriptReturnsCorrectResults()
{
// Test String Result
App.WaitForElement("TestStringButton");
App.Tap("TestStringButton");
App.WaitForElement("StringResult");
var stringResult = App.FindElement("StringResult").GetText();
Assert.That(stringResult, Does.Contain("Hello World"), "String evaluation should return the correct string value");
Assert.That(stringResult, Does.Not.Contain("NULL"), "String evaluation should not return NULL");

// Test Number Result
App.Tap("TestNumberButton");
App.WaitForElement("NumberResult");
var numberResult = App.FindElement("NumberResult").GetText();
Assert.That(numberResult, Does.Contain("42"), "Number evaluation should return the correct number value");
Assert.That(numberResult, Does.Not.Contain("NULL"), "Number evaluation should not return NULL");

// Test Boolean Result
App.Tap("TestBooleanButton");
App.WaitForElement("BooleanResult");
var booleanResult = App.FindElement("BooleanResult").GetText();
Assert.That(booleanResult, Does.Contain("true"), "Boolean evaluation should return the correct boolean value");
Assert.That(booleanResult, Does.Not.Contain("NULL"), "Boolean evaluation should not return NULL");

// Test Null Result
App.Tap("TestNullButton");
App.WaitForElement("NullResult");
var nullResult = App.FindElement("NullResult").GetText();
Assert.That(nullResult, Does.Contain("NULL"), "Null evaluation should correctly return NULL");

// Test Object Result
App.Tap("TestObjectButton");
App.WaitForElement("ObjectResult");
var objectResult = App.FindElement("ObjectResult").GetText();
Assert.That(objectResult, Does.Contain("name"), "Object evaluation should return JSON representation");
Assert.That(objectResult, Does.Contain("test"), "Object evaluation should contain object values");
Assert.That(objectResult, Does.Not.Contain("NULL"), "Object evaluation should not return NULL");

// Test innerHTML (Real World Case) - this was the original failing case
App.Tap("TestInnerHTMLButton");
App.WaitForElement("InnerHTMLResult");
var innerHTMLResult = App.FindElement("InnerHTMLResult").GetText();
Assert.That(innerHTMLResult, Does.Not.Contain("NULL"), "innerHTML evaluation should not return NULL");
Assert.That(innerHTMLResult, Does.Contain("length:"), "innerHTML evaluation should return content with length");
}
}
}
33 changes: 32 additions & 1 deletion src/Core/src/Platform/iOS/WebViewExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Threading.Tasks;
using Foundation;
using WebKit;

namespace Microsoft.Maui.Platform
Expand Down Expand Up @@ -77,7 +78,37 @@
static async Task<string> EvaluateJavaScript(WKWebView webView, string script)
{
var result = await webView.EvaluateJavaScriptAsync(script);
return result?.ToString() ?? "null";
return HandleWKWebViewResult(result);
}

internal static string HandleWKWebViewResult(NSObject? result)
{
if (result == null || result is NSNull)
return "null";

if (result is NSString nsString)
return nsString.ToString();

if (result is NSNumber nsNumber)
return nsNumber.ToString();

// For other types (NSDictionary, NSArray, etc.), use JSON serialization
// This matches the behavior that would come from JSON.stringify() on the web side
try
{
var jsonData = NSJSONSerialization.Serialize(result, NSJsonWritingOptions.PrettyPrinted, out var error);

Check failure on line 99 in src/Core/src/Platform/iOS/WebViewExtensions.cs

View check run for this annotation

Azure Pipelines / MAUI-UITests-public (Build UITests Sample App Build Sample App)

src/Core/src/Platform/iOS/WebViewExtensions.cs#L99

src/Core/src/Platform/iOS/WebViewExtensions.cs(99,20): Error CS0103: The name 'NSJSONSerialization' does not exist in the current context

Check failure on line 99 in src/Core/src/Platform/iOS/WebViewExtensions.cs

View check run for this annotation

Azure Pipelines / MAUI-UITests-public (Build UITests Sample App Build Sample App)

src/Core/src/Platform/iOS/WebViewExtensions.cs#L99

src/Core/src/Platform/iOS/WebViewExtensions.cs(99,20): Error CS0103: The name 'NSJSONSerialization' does not exist in the current context

Check failure on line 99 in src/Core/src/Platform/iOS/WebViewExtensions.cs

View check run for this annotation

Azure Pipelines / MAUI-public (Build .NET MAUI macOS (Release))

src/Core/src/Platform/iOS/WebViewExtensions.cs#L99

src/Core/src/Platform/iOS/WebViewExtensions.cs(99,20): Error CS0103: The name 'NSJSONSerialization' does not exist in the current context

Check failure on line 99 in src/Core/src/Platform/iOS/WebViewExtensions.cs

View check run for this annotation

Azure Pipelines / MAUI-public (Build .NET MAUI macOS (Release))

src/Core/src/Platform/iOS/WebViewExtensions.cs#L99

src/Core/src/Platform/iOS/WebViewExtensions.cs(99,20): Error CS0103: The name 'NSJSONSerialization' does not exist in the current context
if (error == null && jsonData != null)
{
var jsonString = NSString.FromData(jsonData, NSStringEncoding.UTF8);
return jsonString?.ToString() ?? "null";
}
}
catch
{
// Fall back to ToString if JSON serialization fails
}

return result.ToString() ?? "null";
}
}
}
Loading