eclipse-platform / eclipse.platform.swt

Eclipse SWT
https://www.eclipse.org/swt/
Eclipse Public License 2.0
113 stars 133 forks source link

Edge Browser freezes indefinitely when launching one Browser from another via a BrowserFunction #669

Open daniemur opened 1 year ago

daniemur commented 1 year ago

Summary

Edge Browser freezes indefinitely when launching one Browser ("child" Browser) from another ("parent" Browser) via a BrowserFunction using Display#syncExec to create the child Browser. The same behavior is not exhibited when the child Browser is created within a Display#asyncExec call.

Also want to note when SWT gets into this waiting state, closing the window does not kill the process. The process will run until killed manually.

Code To Reproduce

package snippets;

import org.eclipse.swt.SWT;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.browser.BrowserFunction;
import org.eclipse.swt.browser.ProgressAdapter;
import org.eclipse.swt.browser.ProgressEvent;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

@SuppressWarnings({ "javadoc", "nls" })
public class EdgeSnippet
{
    private static Display display;
    private static Shell shell;

    public static void main(String[] args)
    {
        display = new Display();
        shell = new Shell(display);
        shell.setLayout(new FillLayout());

        Browser parentBrowser = new Browser(shell, SWT.EDGE);
        parentBrowser.setText("<!DOCTYPE html> \n"
                + "<html>\n"
                + "<head> \n"
                + "</head> \n"
                + "<body>\n"
                + "   <button onclick = \"openChildBrowser()\"> Open child browser </button> \n"
                + "   <p> \n"
                + "      <div id = \"result\"> </div> \n"
                + "   </p> \n"
                + "</body> \n"
                + "</html>");
        parentBrowser.addProgressListener(new ProgressAdapter()
        {
            @Override
            public void completed(ProgressEvent event)
            {
                new OpenChildBrowserBrowserFunction(parentBrowser, "openChildBrowser");
            }
        });

        shell.pack();
        shell.open();
        while (!shell.isDisposed())
        {
            if (!display.readAndDispatch())
            {
                display.sleep();
            }
        }
        display.dispose();
    }

    private static class OpenChildBrowserBrowserFunction extends BrowserFunction
    {
        public OpenChildBrowserBrowserFunction(Browser browser, String name)
        {
            super(browser, name);
        }

        @Override
        public Object function(Object[] arguments)
        {
            // If the following line is changed to asyncExec, the code executes just fine
            display.syncExec(() ->
            {
                System.out.println("Opening Child Shell");

                Shell childShell = new Shell(shell);
                childShell.setLayout(new FillLayout());
                childShell.setText("Child Browser");

                Browser childBrowser = new Browser(childShell, SWT.EDGE);
                childBrowser.setText("<!DOCTYPE html> \n"
                        + "<html>\n"
                        + "<head> \n"
                        + "</head> \n"
                        + "<body>\n"
                        + "   <p> \n"
                        + "      Child browser"
                        + "   </p> \n"
                        + "</body> \n"
                        + "</html>");

                childShell.pack();
                childShell.open();
            });
            return null;
        }
    }
}

Stack Trace

SWT waits indefinitely on the org.eclipse.swt.internal.win32.OS.WaitMessage call below:

org.eclipse.swt.internal.win32.OS.WaitMessage(Native Method)
org.eclipse.swt.widgets.Display.sleep(Display.java:4752)
org.eclipse.swt.browser.Edge.callAndWait(Edge.java:227)
org.eclipse.swt.browser.Edge.create(Edge.java:342)
org.eclipse.swt.browser.Browser.<init>(Browser.java:99)
snippets.EdgeSnippet$OpenChildBrowserBrowserFunction.lambda$0(EdgeSnippet.java:76)
org.eclipse.swt.widgets.Synchronizer.syncExec(Synchronizer.java:183)
org.eclipse.swt.widgets.Display.syncExec(Display.java:4785)
snippets.EdgeSnippet$OpenChildBrowserBrowserFunction.function(EdgeSnippet.java:68)
org.eclipse.swt.browser.Edge.handleCallJava(Edge.java:543)
org.eclipse.swt.internal.win32.OS.DispatchMessage(Native Method)
org.eclipse.swt.widgets.Display.readAndDispatch(Display.java:3640)
snippets.EdgeSnippet.main(EdgeSnippet.java:49)

Expected behavior The child Browser opens successfully.

Environment:

  1. Select the platform(s) on which the behavior is seen:
    • [ ] All OS
    • [x] Windows
    • [ ] Linux
    • [ ] macOS
  2. Additional OS info (e.g. OS version, Linux Desktop, etc) Windows 10, also observed on Windows Server 2016
  3. JRE/JDK version JDK 11.0.14.1

Version since SWT 3.117-Current (3.123 at the time of writing this)

Known Workarounds

  1. Make either the parent, child, or both Browser(s) non-Edge Browser(s)
  2. Use Display#asyncExec instead of Display#syncExec to create the child Browser
HeikoKlare commented 1 year ago

The root cause for this behavior is that the WebView2 threading model does simply not allow this: https://learn.microsoft.com/en-us/microsoft-edge/webview2/concepts/threading-model All handlers and callbacks are processed sequentially. Triggering another event from within a callback (as done in the example) leads to a deadlock.

A potential solution option is to execute whatever code is embedded into a callback in an asynchronous way. But since the current browser API is expected to operate synchronously and return results rather than futures, this may easily break compatibility.

At least the Edge browser implementation usually checks for these kinds of deadlocks and throws an exception rather than actually running into a deadlock: https://github.com/eclipse-platform/eclipse.platform.swt/blob/863dbe09a63f7a619320b90d19f8184e3f5d5959/bundles/org.eclipse.swt/Eclipse%20SWT%20Browser/win32/org/eclipse/swt/browser/Edge.java#L285-L293

At this specific point (handleCallJava), the deadlock check is missing. This is something we may fix.