Skip to content

Commit 70fab96

Browse files
committed
Support cascaded java callbacks in Edge #1919
Add a test to it. The test is currently deactivated for Linux (see #2021) Fixes #1919
1 parent f734dc6 commit 70fab96

File tree

2 files changed

+64
-6
lines changed

2 files changed

+64
-6
lines changed

bundles/org.eclipse.swt/Eclipse SWT Browser/win32/org/eclipse/swt/browser/Edge.java

+9-6
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public WebViewEnvironment(ICoreWebView2Environment environment) {
8080

8181
WebViewEnvironment containingEnvironment;
8282

83-
static boolean inCallback;
83+
static int inCallback;
8484
boolean inNewWindow;
8585
private boolean inEvaluate;
8686
HashMap<Long, LocationEvent> navigations = new HashMap<>();
@@ -235,11 +235,11 @@ static void error(int code, int hr) {
235235

236236
static IUnknown newCallback(ICoreWebView2SwtCallback handler) {
237237
long punk = COM.CreateSwtWebView2Callback((arg0, arg1) -> {
238-
inCallback = true;
238+
inCallback++;
239239
try {
240240
return handler.Invoke(arg0, arg1);
241241
} finally {
242-
inCallback = false;
242+
inCallback--;
243243
}
244244
});
245245
if (punk == 0) error(SWT.ERROR_NO_HANDLES, COM.E_OUTOFMEMORY);
@@ -552,7 +552,7 @@ void checkDeadlock() {
552552
// and JavaScript callbacks are serialized. An event handler waiting
553553
// for a completion of another handler will deadlock. Detect this
554554
// situation and throw an exception instead.
555-
if (inCallback || inNewWindow) {
555+
if (inCallback > 0 || inNewWindow) {
556556
SWT.error(SWT.ERROR_FAILED_EVALUATE, null, " [WebView2: deadlock detected]");
557557
}
558558
}
@@ -821,7 +821,7 @@ void browserDispose(Event event) {
821821
if(controller != null) {
822822
// Bug in WebView2. Closing the controller from an event handler results
823823
// in a crash. The fix is to delay the closure with asyncExec.
824-
if (inCallback) {
824+
if (inCallback > 0) {
825825
ICoreWebView2Controller controller1 = controller;
826826
controller.put_IsVisible(false);
827827
browser.getDisplay().asyncExec(() -> {
@@ -916,7 +916,7 @@ public Object evaluate(String script) throws SWTException {
916916
// Feature in WebView2. ExecuteScript works regardless of IsScriptEnabled setting.
917917
// Disallow programmatic execution manually.
918918
if (!jsEnabled) return null;
919-
if(inCallback) {
919+
if(inCallback > 0) {
920920
// Execute script, but do not wait for async call to complete as otherwise it
921921
// can cause a deadlock if execute inside a WebView callback.
922922
execute(script);
@@ -1017,12 +1017,15 @@ long handleCallJava(int index, long bstrToken, long bstrArgsJson) {
10171017
String token = bstrToString(bstrToken);
10181018
BrowserFunction function = functions.get(index);
10191019
if (function != null && token.equals (function.token)) {
1020+
inCallback++;
10201021
try {
10211022
String argsJson = bstrToString(bstrArgsJson);
10221023
Object args = JSON.parse(argsJson.toCharArray());
10231024
result = function.function ((Object[]) args);
10241025
} catch (Throwable e) {
10251026
result = WebBrowser.CreateErrorString(e.getLocalizedMessage());
1027+
} finally {
1028+
inCallback--;
10261029
}
10271030
}
10281031
String json = JSON.stringify(result);

tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_browser_Browser.java

+55
Original file line numberDiff line numberDiff line change
@@ -2247,6 +2247,61 @@ function callCustomFunction() {
22472247
assertTrue(message, passed);
22482248
}
22492249

2250+
/**
2251+
* Test for stacked (cascaded) calls between Java and JS i.e. java calls JS
2252+
* which calls Java which calls JS and so on.
2253+
*
2254+
* @see https://github.com/eclipse-platform/eclipse.platform.swt/issues/1919
2255+
*
2256+
*/
2257+
@Test
2258+
public void test_BrowserFunction_callback_stackedCalls() {
2259+
assumeFalse("Not currently working on Linux, see https://github.com/eclipse-platform/eclipse.platform.swt/issues/2021", SwtTestUtil.isGTK);
2260+
AtomicInteger javaCallbackExecuted = new AtomicInteger();
2261+
final int DEPTH = 5;
2262+
2263+
class DeepJavascriptCallback extends BrowserFunction { // Note: Local class defined inside method.
2264+
DeepJavascriptCallback(Browser browser, String name) {
2265+
super(browser, name);
2266+
}
2267+
2268+
@Override
2269+
public Object function(Object[] arguments) {
2270+
if (javaCallbackExecuted.get() < DEPTH) {
2271+
javaCallbackExecuted.incrementAndGet();
2272+
browser.evaluate("jsCallbackToJava();");
2273+
}
2274+
2275+
return null;
2276+
}
2277+
}
2278+
2279+
// Define a javascript function and call it
2280+
String htmlWithScript = """
2281+
<html><head>
2282+
<script language="JavaScript">
2283+
function callCustomFunction() {
2284+
document.body.style.backgroundColor = 'red'
2285+
jsCallbackToJava()
2286+
}
2287+
2288+
</script>
2289+
</head>
2290+
<body> Going to make a callback to Java </body>
2291+
</html>
2292+
""";
2293+
2294+
browser.setText(htmlWithScript);
2295+
new DeepJavascriptCallback(browser, "jsCallbackToJava");
2296+
2297+
browser.addProgressListener(callCustomFunctionUponLoad);
2298+
2299+
shell.open();
2300+
boolean passed = waitForPassCondition(() -> javaCallbackExecuted.get() == DEPTH);
2301+
String message = "Java failed to get a callback from javascript. Test timed out";
2302+
assertTrue(message, passed);
2303+
}
2304+
22502305
/**
22512306
* Test that javascript can call java and pass an integer to java.
22522307
* loosely based on Snippet307.

0 commit comments

Comments
 (0)