Search code examples
javabrowserloadloadingjxbrowser

JxBrowser TimeoutException when waiting for main frame to load (invokeAndWaitFinishLoadingMainFrame)


I am using JxBrowser in a project. I am only working with local HTML files, and therefore using the following method to render my HTML files:

    public static void loadHTMLFile(Browser browser, String filename){
        String current = PathUtils.getCurrentDir();
        browser.loadURL("file:///" + current + "/some/path/in/my/project/resources/web/" + filename);
    } 

In some of the cases I have to use the invokeAndWaitFinishLoadingMainFrame-method since I have to initialize buttons which have to be loaded completely first.

An example from the official JxBrowser website (https://jxbrowser.support.teamdev.com/support/solutions/articles/9000013107-loading-waiting) looks like this:

// Blocks current thread execution and waits until http://www.google.com web page is loaded completely
Browser.invokeAndWaitFinishLoadingMainFrame(browser, new Callback<Browser>() {
    @Override
    public void invoke(Browser value) {
        value.loadURL("http://www.google.com");
    }
});

In my case it looks like this:

public static void loadHTMLFileComplete(Browser browser, String filename){
    Browser.invokeAndWaitFinishLoadingMainFrame(browser, new Callback<Browser>() {
        @Override
        public void invoke(Browser value) {
            loadHTMLFile(value, filename);
        }
    });
}

Nothing surprising so far I guess...

Now comes the tricky part:

The invokeAndWaitFinishLoadingMainFrame-method is the problem:

  1. the very first view (a login view) is loaded correctly with the invokeAndWaitFinishLoadingMainFrame-method
  2. after that my program times out (full exception below) when using the invokeAndWaitFinishLoadingMainFrame-method again
    • for example when I click on the login button and want to be forwarded to the main view
  3. there is a little hack which I found accidentally to get it working for the moment (now it gets creepy):

    • the timeout problem disappears when I load an online webpage in between e.g. google
    • it can also be with the invokeAndWaitFinishLoadingMainFrame-method
    • this is how I do it

      public static void loadGoogle(Browser browser){
          Browser.invokeAndWaitFinishLoadingMainFrame(browser, new Callback<Browser>() {
          @Override
          public void invoke(Browser value) {
              value.loadURL("http://www.google.com");
      
          }
      });
      

      }

  4. here comes the timeoutException I mentioned earlier:

    -- Product name: JxBrowser
    -- Licensed version: 6.x
    -- Licensed to: 
    -- License type: Evaluation
    -- Generation date: 21.04.2019
    -- Expiration date: 21.05.2019
    -- License info: Single-user license
    -- Current date: 30.04.2019
    JxBrowser license valid.
    11:55:59 SCHWERWIEGEND:[0430/235559.671039:ERROR:sandbox_linux.cc(379)] InitializeSandbox() called with multiple threads in process gpu-process.
    com.teamdev.jxbrowser.chromium.Browser@6b9651f3
    11:56:49 SCHWERWIEGEND: Unexpected exception in DOMEventListener.handleEvent() method.
    java.lang.RuntimeException: java.util.concurrent.TimeoutException 
    at com.teamdev.jxbrowser.chromium.Browser.invokeAndWaitFinishLoadingMainFrame(SourceFile:570)
    at com.teamdev.jxbrowser.chromium.Browser.invokeAndWaitFinishLoadingMainFrame(SourceFile:532) 
    at browserActions.PageLoader.loadHTMLFileComplete(PageLoader.java:15)
    at browserViews.OrderCreationView.loadView(OrderCreationView.java:21)
    at browserActions.ButtonInitializer.lambda$0(ButtonInitializer.java:26)
    at com.teamdev.jxbrowser.chromium.dom.internal.EventTarget$a.onMessageReceived(SourceFile:179)
    at com.teamdev.jxbrowser.chromium.internal.ipc.q.a(SourceFile:1085)
    at com.teamdev.jxbrowser.chromium.internal.ipc.r.run(SourceFile:69)
    at com.teamdev.jxbrowser.chromium.internal.s.run(SourceFile:79)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)
    Caused by: java.util.concurrent.TimeoutException
    ... 14 more
    

To summarize:

It does not work:

  • load the login view
  • click the login button
  • try to load the main view -> timeout

It works:

  • load the login view
  • click the login button
  • load google
  • directly after loading google (no time for the user to interact), load the main view -> no timeout

What is happening here :O ?

Any help would be awesome!


Solution

  • JxBrowser is based on the Chromium engine and inherits its multi-process architecture. Each Browser instance is associated with at least one render process that handles DOM and JavaScript related functionality. When you navigate to a different domain, the Chromium engine creates a new render process and kills the old one. If you navigate within one domain, the render process remains the same.

    From the call stack, I see that you try to load a URL from within a DOM listener. DOM listeners are invoked synchronously, so the render process remains blocked while the event is being processed by Java.

    When you load a URL, e.g. google.com, everything works just fine, because a new render process is created and, as soon as the page is loaded, the Browser.invokeAndWaitFinishLoadingMainFrame method returns control and the old render process is killed. However, if you try to load a file:/// URL, the render process remains the same and the loading cannot start, because the render process is blocked by the Browser.invokeAndWaitFinishLoadingMainFrame method called from a DOM listener, so you’ll get a deadlock.

    In order to avoid this exception, you should not start synchronous navigation from within a DOM listener.

    According to the call stack, you call the loadView method from a DOMEventListener. In order to avoid the deadlock you should call this method asynchronously, for example:

    import com.teamdev.jxbrowser.chromium.dom.events.DOMEvent;
    import com.teamdev.jxbrowser.chromium.dom.events.DOMEventListener;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class MyDomEventListener implements DOMEventListener {
    
        private final ExecutorService executorService;
        private final OrderCreationView view;
    
        public MyDomEventListener(OrderCreationView view) {
            this.view = view;
            this.executorService = Executors.newCachedThreadPool();
        }
    
        @Override
        public void handleEvent(DOMEvent domEvent) {
            // Do not block the current thread and invoke the loadView method asynchronously.
            executorService.execute(view::loadView);
        }
    }
    

    In the loadView method you can load an HTML synchronously:

    public void loadView() {
        Browser.invokeAndWaitFinishLoadingMainFrame(browser, new Callback<Browser>() {
            @Override
            public void invoke(Browser browser) {
                browser.loadHTML("");
            }
        });
        initLoginButton();
    }