diff --git a/bundles/org.eclipse.swt/Eclipse SWT Browser/win32/org/eclipse/swt/browser/Edge.java b/bundles/org.eclipse.swt/Eclipse SWT Browser/win32/org/eclipse/swt/browser/Edge.java index 3a7638dd047..0ce5bf07263 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT Browser/win32/org/eclipse/swt/browser/Edge.java +++ b/bundles/org.eclipse.swt/Eclipse SWT Browser/win32/org/eclipse/swt/browser/Edge.java @@ -80,7 +80,7 @@ public WebViewEnvironment(ICoreWebView2Environment environment) { WebViewEnvironment containingEnvironment; - static boolean inCallback; + static int inCallback; boolean inNewWindow; private boolean inEvaluate; HashMap navigations = new HashMap<>(); @@ -235,11 +235,11 @@ static void error(int code, int hr) { static IUnknown newCallback(ICoreWebView2SwtCallback handler) { long punk = COM.CreateSwtWebView2Callback((arg0, arg1) -> { - inCallback = true; + inCallback++; try { return handler.Invoke(arg0, arg1); } finally { - inCallback = false; + inCallback--; } }); if (punk == 0) error(SWT.ERROR_NO_HANDLES, COM.E_OUTOFMEMORY); @@ -552,7 +552,7 @@ void checkDeadlock() { // and JavaScript callbacks are serialized. An event handler waiting // for a completion of another handler will deadlock. Detect this // situation and throw an exception instead. - if (inCallback || inNewWindow) { + if (inCallback > 0 || inNewWindow) { SWT.error(SWT.ERROR_FAILED_EVALUATE, null, " [WebView2: deadlock detected]"); } } @@ -821,7 +821,7 @@ void browserDispose(Event event) { if(controller != null) { // Bug in WebView2. Closing the controller from an event handler results // in a crash. The fix is to delay the closure with asyncExec. - if (inCallback) { + if (inCallback > 0) { ICoreWebView2Controller controller1 = controller; controller.put_IsVisible(false); browser.getDisplay().asyncExec(() -> { @@ -916,7 +916,7 @@ public Object evaluate(String script) throws SWTException { // Feature in WebView2. ExecuteScript works regardless of IsScriptEnabled setting. // Disallow programmatic execution manually. if (!jsEnabled) return null; - if(inCallback) { + if(inCallback > 0) { // Execute script, but do not wait for async call to complete as otherwise it // can cause a deadlock if execute inside a WebView callback. execute(script); @@ -1017,12 +1017,15 @@ long handleCallJava(int index, long bstrToken, long bstrArgsJson) { String token = bstrToString(bstrToken); BrowserFunction function = functions.get(index); if (function != null && token.equals (function.token)) { + inCallback++; try { String argsJson = bstrToString(bstrArgsJson); Object args = JSON.parse(argsJson.toCharArray()); result = function.function ((Object[]) args); } catch (Throwable e) { result = WebBrowser.CreateErrorString(e.getLocalizedMessage()); + } finally { + inCallback--; } } String json = JSON.stringify(result); diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_browser_Browser.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_browser_Browser.java index 07311486f82..d8a9e10eaa1 100644 --- a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_browser_Browser.java +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_browser_Browser.java @@ -2247,6 +2247,61 @@ function callCustomFunction() { assertTrue(message, passed); } +/** + * Test for stacked (cascaded) calls between Java and JS i.e. java calls JS + * which calls Java which calls JS and so on. + * + * @see https://github.com/eclipse-platform/eclipse.platform.swt/issues/1919 + * + */ +@Test +public void test_BrowserFunction_callback_stackedCalls() { + assumeFalse("Not currently working on Linux, see https://github.com/eclipse-platform/eclipse.platform.swt/issues/2021", SwtTestUtil.isGTK); + AtomicInteger javaCallbackExecuted = new AtomicInteger(); + final int DEPTH = 5; + + class DeepJavascriptCallback extends BrowserFunction { // Note: Local class defined inside method. + DeepJavascriptCallback(Browser browser, String name) { + super(browser, name); + } + + @Override + public Object function(Object[] arguments) { + if (javaCallbackExecuted.get() < DEPTH) { + javaCallbackExecuted.incrementAndGet(); + browser.evaluate("jsCallbackToJava();"); + } + + return null; + } + } + + // Define a javascript function and call it + String htmlWithScript = """ + + + + Going to make a callback to Java + + """; + + browser.setText(htmlWithScript); + new DeepJavascriptCallback(browser, "jsCallbackToJava"); + + browser.addProgressListener(callCustomFunctionUponLoad); + + shell.open(); + boolean passed = waitForPassCondition(() -> javaCallbackExecuted.get() == DEPTH); + String message = "Java failed to get a callback from javascript. Test timed out"; + assertTrue(message, passed); +} + /** * Test that javascript can call java and pass an integer to java. * loosely based on Snippet307.