I'm working on making a possible contribution the Community Edition of Intellij IDEA, adding some enhancements to the Markdown preview feature.
The IDEA code base is primarily Java and Java's cousin, Kotlin. There is, however, some other open-source code I want to leverage, highlight.js, that's written it JavaScript.
I've found a functional way to get highlight.js to work for me in this code environment, but it took some ugly and unsatisfactory hacking to do it. I think I have the basic elements for a better solution in place, but just can't get it to actually work.
This is the start of what I hope to be a less kludgy way of running 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) // <-- Should reach this code, but it's not happening
return false
}
}, browser.cefBrowser)
browser.executeJavaScript("console.info('Hello, world.')")
If you're already feeling a little TL;DR at this point, you can skip past the following explanation of how I arrived here and go to the end of the post for the specific problem I'm running into.
IDEA uses JCEFBrowser (JCEF: Java Chromium Embedded Framework) for a number of things, including for its Markdown preview. Such browsers are capable of executing JavaScript code, so taking advantage of that seems to make a lot more sense that dragging in an additional heavy-weight dependency like GraalVM to run highlight.js.
As the IDEA code is set up, however, the MarkdownJCEFHtmlPanel
implementation of a JCEFBrowser can only work on changing code fence highlighting a bit late in the game, after all processing for syntax highlighting should have already been done. With my current solution the user momentarily sees un-highlighted code until the JavaScript running in MarkdownJCEFHtmlPanel
locates the code fence contents and then applies highlight.js highlighting.
This results in noticeable flicker when code fence contents are being edited. Without some additional ugly hacking, every code fence had flickered when anything was edited anywhere.
Hence the alternate solution I'm trying to find, and the problem with that solution so far: For reasons I don't understand the extra JBCefBrowser instance I'm creating is totally unresponsive, dead in the water and not helping me to do anything.
I get no errors and no warnings, just nothing useful happens. Things like waitForPageLoad
hang forever.
Does the JCEFBrowser
, even if it's meant to be offscreen, need to be attached to a live view of some sort? Does the browser need to be registered somewhere so that it receives 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 missing initialization or other form of a kick in the pants needed to get this JBCefBrowser
instance up and running in a fully functional state.
Can anyone suggest what that might be?
Update:
I've made some progress. If I add...
browser.createImmediately()
browser.waitForPageLoad("about:blank")
...the waitForPageLoad
doesn't hang anymore, and browser.cefBrowser.hasDocument()
is finally true
.
I can even execute a JavaScript alert
and the alert pops up, so I know JavaScript is functioning (to an extent). Oddly, however, the CefDisplayHandlerAdapter
still doesn't get any feedback from a console
statement.
Since that's the only reasonable way I know of to get usable feedback from highlight.js, I don't have a proper solution yet, but at least I think I'm closer.
...
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:
browser.createImmediately()
CefDisplayHandlerAdapter
after a browser instance has been created. It works better (and is perhaps required?) to add handlers like this during class initialization.