Skip to content
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,4 @@ UpgradeLog.htm
.idea
*.svclog
mono_crash.*.json
mono_crash.*.blob
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,10 @@ public static void EndInvokeJS(JSRuntime jsRuntime, string arguments)
var success = reader.GetBoolean();

reader.Read();
jsRuntime.EndInvokeJS(taskId, success, ref reader);
if (!jsRuntime.EndInvokeJS(taskId, success, ref reader))
{
return;
}

if (!reader.Read() || reader.TokenType != JsonTokenType.EndArray)
{
Expand Down
7 changes: 5 additions & 2 deletions src/JSInterop/Microsoft.JSInterop/src/JSRuntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -225,13 +225,13 @@ protected internal virtual Task<Stream> ReadJSDataAsStreamAsync(IJSStreamReferen
}

[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2072:RequiresUnreferencedCode", Justification = "We enforce trimmer attributes for JSON deserialized types on InvokeAsync.")]
internal void EndInvokeJS(long taskId, bool succeeded, ref Utf8JsonReader jsonReader)
internal bool EndInvokeJS(long taskId, bool succeeded, ref Utf8JsonReader jsonReader)
{
if (!_pendingTasks.TryRemove(taskId, out var tcs))
{
// We should simply return if we can't find an id for the invocation.
// This likely means that the method that initiated the call defined a timeout and stopped waiting.
return;
return false;
}

CleanupTasksAndRegistrations(taskId);
Expand All @@ -251,11 +251,14 @@ internal void EndInvokeJS(long taskId, bool succeeded, ref Utf8JsonReader jsonRe
var exceptionText = jsonReader.GetString() ?? string.Empty;
TaskGenericsUtil.SetTaskCompletionSourceException(tcs, new JSException(exceptionText));
}

return true;
}
catch (Exception exception)
{
var message = $"An exception occurred executing JS interop: {exception.Message}. See InnerException for more details.";
TaskGenericsUtil.SetTaskCompletionSourceException(tcs, new JSException(message, exception));
return false;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ public void CannotUseDotNetObjectRefAfterReleaseDotNetObject()
}

[Fact]
public void EndInvoke_WithSuccessValue()
public void EndInvokeJS_WithSuccessValue()
{
// Arrange
var jsRuntime = new TestJSRuntime();
Expand All @@ -282,7 +282,7 @@ public void EndInvoke_WithSuccessValue()
}

[Fact]
public async Task EndInvoke_WithErrorString()
public async Task EndInvokeJS_WithErrorString()
{
// Arrange
var jsRuntime = new TestJSRuntime();
Expand All @@ -299,7 +299,7 @@ public async Task EndInvoke_WithErrorString()
}

[Fact]
public async Task EndInvoke_WithNullError()
public async Task EndInvokeJS_WithNullError()
{
// Arrange
var jsRuntime = new TestJSRuntime();
Expand All @@ -314,6 +314,129 @@ public async Task EndInvoke_WithNullError()
Assert.Empty(ex.Message);
}

[Fact]
public void EndInvokeJS_DoesNotThrowJSONExceptionIfTaskCancelled()
{
// Arrange
var jsRuntime = new TestJSRuntime();
var testDTO = new TestDTO { StringVal = "Hello", IntVal = 4 };
var cts = new CancellationTokenSource();
var argsJson = JsonSerializer.Serialize(new object[] { jsRuntime.LastInvocationAsyncHandle, true, testDTO }, jsRuntime.JsonSerializerOptions);

// Act
var task = jsRuntime.InvokeAsync<TestDTO>("unimportant", cts.Token);

cts.Cancel();

DotNetDispatcher.EndInvokeJS(jsRuntime, argsJson);

// Assert
Assert.False(task.IsCompletedSuccessfully);
Assert.True(task.IsCanceled);
}

[Fact]
public void EndInvokeJS_ThrowsIfJsonIsEmptyString()
{
// Arrange
var jsRuntime = new TestJSRuntime();
var task = jsRuntime.InvokeAsync<TestDTO>("somemethod");

// Act & Assert
Assert.ThrowsAny<JsonException>(() => DotNetDispatcher.EndInvokeJS(jsRuntime, ""));
}

[Fact]
public void EndInvokeJS_ThrowsIfJsonIsNotArray()
{
// Arrange
var jsRuntime = new TestJSRuntime();
var task = jsRuntime.InvokeAsync<TestDTO>("somemethod");

// Act & Assert
Assert.ThrowsAny<JsonException>(() => DotNetDispatcher.EndInvokeJS(jsRuntime, $"{{\"key\": \"{jsRuntime.LastInvocationAsyncHandle}\"}}"));
}

[Fact]
public void EndInvokeJS_ThrowsIfJsonArrayIsInComplete()
{
// Arrange
var jsRuntime = new TestJSRuntime();
var task = jsRuntime.InvokeAsync<TestDTO>("somemethod");

// Act & Assert
Assert.ThrowsAny<JsonException>(() => DotNetDispatcher.EndInvokeJS(jsRuntime, $"[{jsRuntime.LastInvocationAsyncHandle}, false"));
}

[Fact]
public void EndInvokeJS_ThrowsIfJsonArrayHasMoreThan3Arguments()
{
// Arrange
var jsRuntime = new TestJSRuntime();
var task = jsRuntime.InvokeAsync<TestDTO>("somemethod");

// Act & Assert
Assert.ThrowsAny<JsonException>(() => DotNetDispatcher.EndInvokeJS(jsRuntime, $"[{jsRuntime.LastInvocationAsyncHandle}, false, \"Hello\", 5]"));
}

[Fact]
public void EndInvokeJS_DoesNotThrowJSONExceptionIfTaskCancelled_WithMoreThan3Arguments()
{
// Arrange
var jsRuntime = new TestJSRuntime();
var cts = new CancellationTokenSource();

// Act
var task = jsRuntime.InvokeAsync<TestDTO>("unimportant", cts.Token);

cts.Cancel();

DotNetDispatcher.EndInvokeJS(jsRuntime, $"[{jsRuntime.LastInvocationAsyncHandle}, false, \"Hello\", 5]");

// Assert
Assert.False(task.IsCompletedSuccessfully);
Assert.True(task.IsCanceled);
}

[Fact]
public void EndInvokeJS_Works()
{
// Arrange
var jsRuntime = new TestJSRuntime();
var task = jsRuntime.InvokeAsync<TestDTO>("somemethod");

// Act
DotNetDispatcher.EndInvokeJS(jsRuntime, $"[{jsRuntime.LastInvocationAsyncHandle}, true, {{\"intVal\": 7}}]");

// Assert
Assert.True(task.IsCompletedSuccessfully);
Assert.Equal(7, task.Result.IntVal);
}

[Fact]
public void EndInvokeJS_WithArrayValue()
{
var jsRuntime = new TestJSRuntime();
var task = jsRuntime.InvokeAsync<int[]>("somemethod");

DotNetDispatcher.EndInvokeJS(jsRuntime, $"[{jsRuntime.LastInvocationAsyncHandle}, true, [1, 2, 3]]");

Assert.True(task.IsCompletedSuccessfully);
Assert.Equal(new[] { 1, 2, 3 }, task.Result);
}

[Fact]
public void EndInvokeJS_WithNullValue()
{
var jsRuntime = new TestJSRuntime();
var task = jsRuntime.InvokeAsync<TestDTO>("somemethod");

DotNetDispatcher.EndInvokeJS(jsRuntime, $"[{jsRuntime.LastInvocationAsyncHandle}, true, null]");

Assert.True(task.IsCompletedSuccessfully);
Assert.Null(task.Result);
}

[Fact]
public void CanInvokeInstanceMethodWithParams()
{
Expand Down Expand Up @@ -652,66 +775,6 @@ public void ParseArguments_Throws_WithIncorrectDotNetObjectRefUsage()
Assert.Equal($"In call to '{method}', parameter of type '{nameof(TestDTO)}' at index 2 must be declared as type 'DotNetObjectRef<TestDTO>' to receive the incoming value.", ex.Message);
}

[Fact]
public void EndInvokeJS_ThrowsIfJsonIsEmptyString()
{
Assert.ThrowsAny<JsonException>(() => DotNetDispatcher.EndInvokeJS(new TestJSRuntime(), ""));
}

[Fact]
public void EndInvokeJS_ThrowsIfJsonIsNotArray()
{
Assert.ThrowsAny<JsonException>(() => DotNetDispatcher.EndInvokeJS(new TestJSRuntime(), "{\"key\": \"value\"}"));
}

[Fact]
public void EndInvokeJS_ThrowsIfJsonArrayIsInComplete()
{
Assert.ThrowsAny<JsonException>(() => DotNetDispatcher.EndInvokeJS(new TestJSRuntime(), "[7, false"));
}

[Fact]
public void EndInvokeJS_ThrowsIfJsonArrayHasMoreThan3Arguments()
{
Assert.ThrowsAny<JsonException>(() => DotNetDispatcher.EndInvokeJS(new TestJSRuntime(), "[7, false, \"Hello\", 5]"));
}

[Fact]
public void EndInvokeJS_Works()
{
var jsRuntime = new TestJSRuntime();
var task = jsRuntime.InvokeAsync<TestDTO>("somemethod");

DotNetDispatcher.EndInvokeJS(jsRuntime, $"[{jsRuntime.LastInvocationAsyncHandle}, true, {{\"intVal\": 7}}]");

Assert.True(task.IsCompletedSuccessfully);
Assert.Equal(7, task.Result.IntVal);
}

[Fact]
public void EndInvokeJS_WithArrayValue()
{
var jsRuntime = new TestJSRuntime();
var task = jsRuntime.InvokeAsync<int[]>("somemethod");

DotNetDispatcher.EndInvokeJS(jsRuntime, $"[{jsRuntime.LastInvocationAsyncHandle}, true, [1, 2, 3]]");

Assert.True(task.IsCompletedSuccessfully);
Assert.Equal(new[] { 1, 2, 3 }, task.Result);
}

[Fact]
public void EndInvokeJS_WithNullValue()
{
var jsRuntime = new TestJSRuntime();
var task = jsRuntime.InvokeAsync<TestDTO>("somemethod");

DotNetDispatcher.EndInvokeJS(jsRuntime, $"[{jsRuntime.LastInvocationAsyncHandle}, true, null]");

Assert.True(task.IsCompletedSuccessfully);
Assert.Null(task.Result);
}

[Fact]
public void ReceiveByteArray_Works()
{
Expand Down