What is needed to get a simple instance of JBCefBrowser up and running to execute JavaScript?

As a follow-up to a related thread, I’m trying to use a simple instance of JBCefBrowser to execute JavaScript code. My latest attempt looks like this:

    var browser = JBCefBrowser()

    browser.jbCefClient.addJSDialogHandler(object : CefJSDialogHandler {
      override fun onJSDialog(browser: CefBrowser?, url: String?, dialogType: CefJSDialogHandler.JSDialogType?,
                              message: String?, prompt: String?, callback: CefJSDialogCallback?, suppress: BoolRef?): Boolean {
        println("onJSDialog: $message")
        return true
      }

      override fun onBeforeUnloadDialog(browser: CefBrowser?, message_text: String?, is_reload:
                                        Boolean, callback: CefJSDialogCallback?): Boolean { return true }
      override fun onResetDialogState(browser: CefBrowser?) {}
      override fun onDialogClosed(browser: CefBrowser?) {}

    }, browser.cefBrowser)
    browser.waitForPageLoad("about:blank") // <-- I've tried with and without this line
    browser.executeJavaScript("alert('Hello, world.')")

I’m expecting to reach a breakpoint at the println statement, but I never get there.

If I include the waitForPageLoad, I just get stuck there, never moving on. In case it would matter I tried a real URL instead of “about:blank”, a URL to a server of mine to see if I logged an attempted connection, but it doesn’t appear the URL was used.

If I skip the waitForPageLoad, the executeJavaScript never does anything as far as I can tell. Execution isn’t blocked, no exceptions are thrown, but I never reach the println either.

I’m sure I’m missing something, but have no idea what. Nothing in the documentation here gives me a clue what else to do next.

When I look at the implementation of MarkdownJCEFHtmlPanel there’s so much going on there I have no idea which aspects of that are vital for making my (hopefully) much simpler off-screen browser work.

Do I need to addRequestHandler before anything will work? Is the use of coroutineScope.launch somehow vital to get things going? Using a custom JPanel component instead of the default? Does the browser need to be registered somewhere so that it received processing time and/or event dispatches?

The debugger tells me browser.cefBrowser.hasDocument() is always false, so I’m sure that’s not a good sign.

I feel like there’s some type of initialization or other form of a kick in the pants needed to get the JBCefBrowser up and running in a fully functional state.

What might that be?

I found out that addJSDialogHandler isn’t even working for JBCefBrowser used by MarkdownJCEFHtmlPanel, a browser which I at least know is up and running.

What (finally!) does work (only with MarkdownJCEFHtmlPanel’s browser), and is a nicer interface than addJSDialogHandler anyway, is addDisplayHandler, which does a good job of reporting output from JavaScript console statements.

But even modified to use addJSDialogHandler, my standalone instand of JBCefBrowser still doesn’t give me any feedback from executing JavaScript code.

    var browser = JBCefBrowser()

    browser.jbCefClient.addDisplayHandler(object: CefDisplayHandlerAdapter() {
      override fun onConsoleMessage(browser: CefBrowser, level: CefSettings.LogSeverity,
        message: String, source: String, line: Int
      ): Boolean {
        println(message) // <-- Still not getting here
        return false
      }
    }, browser.cefBrowser)

    browser.executeJavaScript("console.info('Hello, world.')")

Very frustrating!

I’ve decided to ask about this issue on StackOverflow as well. Here’s a link to that post, as it might provide some additional insight into the problem I’m trying to solve.

Ah! Finally!

  override suspend fun execute(project: Project) {
    browser.createImmediately()
    browser.waitForPageLoad("about:blank")
    browser.executeJavaScript("console.info('Hello, world.')")
  }

  companion object {
    var browser = object : JBCefBrowser() {
      init {
        jbCefClient.addDisplayHandler(object: CefDisplayHandlerAdapter() {
          override fun onConsoleMessage(
            browser: CefBrowser, level: CefSettings.LogSeverity,
            message: String, source: String, line: Int,
          ): Boolean {
            println(message) // <-- This code is finally reached!
            return false
          }
        }, cefBrowser)
      }
    }

Two things you’d never know were necessary based on reading the associated documentation:

  • The need for browser.createImmediately()
  • That, for some unknown reason, it may do you no good to add a handler such as CefDisplayHandlerAdapter after a browser instance has been created. It works better (and is perhaps required?) to add handlers like this during class initialization.