Skip to content

Commit f4d7bab

Browse files
committed
Merge branch 'master' into rel/4.0.0
2 parents 3d3e3aa + e94536d commit f4d7bab

File tree

6 files changed

+99
-39
lines changed

6 files changed

+99
-39
lines changed

Microsoft.Toolkit.Win32/Microsoft.Toolkit.Win32.UI.Controls/Interop/WinRT/WebViewControlHost.cs

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -413,15 +413,6 @@ internal bool GoForward()
413413
return retval;
414414
}
415415

416-
/// <exception cref="InvalidOperationException">When the underlying <see cref="WebViewControl"/> is not yet initialized.</exception>
417-
internal string InvokeScript(string scriptName) => InvokeScript(scriptName, null);
418-
419-
/// <exception cref="InvalidOperationException">When the underlying <see cref="WebViewControl"/> is not yet initialized.</exception>
420-
internal string InvokeScript(string scriptName, params string[] arguments) => InvokeScript(scriptName, (IEnumerable<string>)arguments);
421-
422-
/// <exception cref="InvalidOperationException">When the underlying <see cref="WebViewControl"/> is not yet initialized.</exception>
423-
internal string InvokeScript(string scriptName, IEnumerable<string> arguments) => InvokeScriptAsync(scriptName, arguments).GetAwaiter().GetResult();
424-
425416
/// <exception cref="InvalidOperationException">When the underlying <see cref="WebViewControl"/> is not yet initialized.</exception>
426417
internal Task<string> InvokeScriptAsync(string scriptName) => InvokeScriptAsync(scriptName, null);
427418

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Windows.Threading;
6+
7+
namespace System.Threading.Tasks
8+
{
9+
/// <summary>
10+
/// Contains extensions to process a task within a nested Windows Presentation Foundation (WPF) message loop
11+
/// </summary>
12+
internal static partial class TaskExtensions
13+
{
14+
public static T WaitWithNestedMessageLoop<T>(this Task<T> task, Dispatcher dispatcher)
15+
{
16+
// Check if we have a valid dispatcher
17+
if (dispatcher != null
18+
&& !dispatcher.HasShutdownStarted
19+
&& !dispatcher.HasShutdownFinished)
20+
{
21+
// Set the priority to ContextIdle to force the queue to flush higher priority events
22+
var frame = new DispatcherFrame();
23+
task.ContinueWith(_ => frame.Continue = false, TaskScheduler.Default);
24+
Dispatcher.PushFrame(frame);
25+
}
26+
27+
return task.Result;
28+
}
29+
}
30+
}

Microsoft.Toolkit.Win32/Microsoft.Toolkit.Win32.UI.Controls/WPF/WebView/WebView.cs

Lines changed: 6 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -521,34 +521,10 @@ public bool GoForward()
521521
}
522522

523523
/// <inheritdoc />
524-
public string InvokeScript(string scriptName)
525-
{
526-
VerifyAccess();
527-
528-
do
529-
{
530-
Dispatcher.CurrentDispatcher.DoEvents();
531-
}
532-
while (!_initializationComplete.WaitOne(InitializationBlockingTime));
533-
534-
Verify.IsNotNull(_webViewControl);
535-
return _webViewControl?.InvokeScript(scriptName);
536-
}
524+
public string InvokeScript(string scriptName) => InvokeScript(scriptName, null);
537525

538526
/// <inheritdoc />
539-
public string InvokeScript(string scriptName, params string[] arguments)
540-
{
541-
VerifyAccess();
542-
543-
do
544-
{
545-
Dispatcher.CurrentDispatcher.DoEvents();
546-
}
547-
while (!_initializationComplete.WaitOne(InitializationBlockingTime));
548-
549-
Verify.IsNotNull(_webViewControl);
550-
return _webViewControl?.InvokeScript(scriptName, arguments);
551-
}
527+
public string InvokeScript(string scriptName, params string[] arguments) => InvokeScript(scriptName, (IEnumerable<string>)arguments);
552528

553529
/// <inheritdoc />
554530
public string InvokeScript(string scriptName, IEnumerable<string> arguments)
@@ -562,7 +538,10 @@ public string InvokeScript(string scriptName, IEnumerable<string> arguments)
562538
while (!_initializationComplete.WaitOne(InitializationBlockingTime));
563539

564540
Verify.IsNotNull(_webViewControl);
565-
return _webViewControl?.InvokeScript(scriptName, arguments);
541+
542+
// WebViewControlHost ends up calling InvokeScriptAsync anyway
543+
// The problem we have is that InvokeScript could be called from a UI thread and waiting for an async result that could lead to deadlock
544+
return InvokeScriptAsync(scriptName, arguments).WaitWithNestedMessageLoop(Dispatcher.CurrentDispatcher);
566545
}
567546

568547
/// <inheritdoc />
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Windows.Forms;
6+
7+
namespace System.Threading.Tasks
8+
{
9+
/// <summary>
10+
/// Contains extensions to process a task within a nested Windows Forms message loop
11+
/// </summary>
12+
internal static partial class TaskExtensions
13+
{
14+
public static T WaitWithNestedMessageLoop<T>(this Task<T> task)
15+
{
16+
while (!task.IsCompleted)
17+
{
18+
Application.DoEvents();
19+
}
20+
21+
return task.Result;
22+
}
23+
}
24+
}

Microsoft.Toolkit.Win32/Microsoft.Toolkit.Win32.UI.Controls/WinForms/WebView/WebView.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -469,13 +469,18 @@ public void Close()
469469
public WebViewControlDeferredPermissionRequest GetDeferredPermissionRequestById(uint id) => _webViewControl?.GetDeferredPermissionRequestById(id);
470470

471471
/// <inheritdoc />
472-
public string InvokeScript(string scriptName) => _webViewControl?.InvokeScript(scriptName);
472+
public string InvokeScript(string scriptName) => InvokeScript(scriptName, null);
473473

474474
/// <inheritdoc />
475-
public string InvokeScript(string scriptName, params string[] arguments) => _webViewControl?.InvokeScript(scriptName, arguments);
475+
public string InvokeScript(string scriptName, params string[] arguments) => InvokeScript(scriptName, (IEnumerable<string>)arguments);
476476

477477
/// <inheritdoc />
478-
public string InvokeScript(string scriptName, IEnumerable<string> arguments) => _webViewControl?.InvokeScript(scriptName, arguments);
478+
public string InvokeScript(string scriptName, IEnumerable<string> arguments)
479+
{
480+
// WebViewControlHost ends up calling InvokeScriptAsync anyway
481+
// The problem we have is that InvokeScript could be called from a UI thread and waiting for an async result that could lead to deadlock
482+
return InvokeScriptAsync(scriptName, arguments).WaitWithNestedMessageLoop();
483+
}
479484

480485
/// <inheritdoc />
481486
public Task<string> InvokeScriptAsync(string scriptName) => _webViewControl?.InvokeScriptAsync(scriptName);

Microsoft.Toolkit.Win32/Tests/UnitTests.WebView.WinForms/FunctionalTests/InvokeScript/InvokeScriptTests.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,37 @@
99

1010
namespace Microsoft.Toolkit.Win32.UI.Controls.Test.WinForms.WebView.FunctionalTests.InvokeScript
1111
{
12+
// Issue #2367 - Deadlock when UI thread calls multiple InvokeScript with return value
13+
[TestClass]
14+
public class InvokeScriptMultipleTimesOnUIThread : HostFormWebViewContextSpecification
15+
{
16+
private string _actual;
17+
18+
protected override void Given()
19+
{
20+
base.Given();
21+
22+
WebView.NavigationCompleted += (o, e) =>
23+
{
24+
_actual = WebView.InvokeScript("eval", "document.title");
25+
_actual = WebView.InvokeScript("eval", "document.title");
26+
_actual = WebView.InvokeScript("eval", "document.title");
27+
Form.Close();
28+
};
29+
}
30+
31+
protected override void When()
32+
{
33+
NavigateAndWaitForFormClose(TestConstants.Uris.ExampleCom);
34+
}
35+
36+
[TestMethod]
37+
public void JavaScriptReturnsWithoutDeadlock()
38+
{
39+
_actual.ShouldEqual("Example Domain");
40+
}
41+
}
42+
1243
[TestClass]
1344
public class InvokeScriptAsyncNoArgumentsTests : HostFormWebViewContextSpecification
1445
{

0 commit comments

Comments
 (0)