Creating IntelliJ plugin with WebView

Rafał Mucha
VirtusLab
Published in
5 min readDec 21, 2020

Hello!

In this post, I would like to show how to create a simple plugin for IntelliJ with JCEF, the new WebView framework. In our company, we use it to build such tools as GraphBuddy or CodeTale. The first one is already released, the second one we are still working on in our R&D team. It is a plugin to your IDE, which enables you to browse comments from pull requests. To make the UI easier to reuse with many IDEs, we have decided to use WebView. If you are not familiar with it — it’s an embedded web browser, which allows you to create a UI once in the form of a web page. From the beginning of the project, we have decided to support two IDEs: IntelliJ and VSCode, as one of the most popular IDEs. VSCode bases on WebView technology — Electron, so it uses an efficient Chromium web engine. On the opposite, IntelliJ supported only the obsolete JavaFX version of WebView up to the middle of this year. It uses an old Safari engine, which generates some problems with the UI (mainly poor performance). However, in version 2020.2 there was introduced a new officially supported implementation of WebView — JCEF. The community requested this change for a long time, and finally, there it is! It uses Chromium, so it is much faster and easier to debug. What’s more, the old one is no longer officially supported. Maybe in the next blog post, I could present an example of migration — please, let me know if you are interested :) But now, I would like to show you how to build a toy plugin for IntelliJ. It would render a web page saved in plugin’s resources folder — what is a bit tricky part since JCEF doesn’t support jar protocol.

What do you need?

  • installed JDK
  • installed or downloaded Gradle
  • your favourite IDE
  • 10 minutes
  • a cup of coffee or mug of tea ;)

So, let’s start!

At first, we need to create plugin.xml file:

It should be located in src/main/resources/META-INF/plugin.xml. It is a file with some general information about the plugin, like version, name, or id. Its important part is extensions — it contains a list of declared services or tool windows, like in this case. I.e. Projects is one of the default tool windows in IntelliJ.

Let’s look closer to it:

The tool window has a few properties. Some of them — like id or factoryClass are obligatory, but others, like an icon , are optional. Keep in mind that id has, in this case, two functions — it is the name displayed on the button, but also is tool window ID. So, if later in code, you want to access this tool window, you can do it only by that name.
And factoryClass — it has to contain a classpath to class in your project, which implements ToolWindowFactory with its method createToolWindowContent. This method is invoked by IDE, when the tool window button is pressed. The next step would be creating a factory class mentioned above.

Save it to src/main/scala/com/catviewer/WindowFactory.scala. Surprised about language? Yes, it is possible to develop plugins for IntelliJ also using Scala!
In the above method — createToolWindowContent the WebView window is injected into the ToolWindow component. As IntelliJ UI is written using Swing, it must be passed as a compatible component. But what about the ServiceManager? It is a typical way of implementing singleton in plugins — create service, which creates and keeps stateful objects. In this case, it is a WebView window. In the next step let's create service and window class itself, so everything should be clear:

Save it to src/main/scala/com/catviewer/CatViewerWindowService.scala. Also, add it as project service by adding a line to extensions field in plugin.xml:

Services in IntelliJ are initialized lazily, so if you want to make sure that your service is running, remember to access it (as it is done in WindowFactory).
And CatViewerWindow (src/main/scala/com/catviewer/CatViewerWindow.scala):

As you can see, it is almost the same as in the JetBrains documentation. There are a few places worth looking. Notice the "myapp" domain registered in WebView — it allows us to use custom resource handler and load files from the resources folder in our jar archive. To do this, we need to implement CustomResourceHandler and CustomSchemeHandlerFactory. Another not obvious point is registering WebView in disposer — it helps IDE with memory management (and is a must to avoid "Memory leak detected" in IntelliJ logs). src/main/scala/com/catviewer/CustomResourceHandler:

In terms of logic — it is quite straightforward. We need to implement opening files, returning headers, sending data, and handling cases of cancellation. Path to files is constructed by simple replacement of prefix http://myapp with webview (page sources are located inwebview folder in resources).
Update: in the 2021.1 release there were some changes in the resource loader in JBR. To create code compatible with 2020.2+ and 2021.1, more generic URLConnection should be used instead of JarURLConnection and better mime types recognition should be added (new loader don’t detect those types). So if your page loads other files than CSS, HTML, or js — it may be needed to extend the above implementation.

And CustomSchemeHandlerFactory:

And finally, we can create WebView folder in resources and insert their website files.
index.html ( src/main/resources/webview/index.html)

Insert there also your favourite image with the name cat.png ( src/main/resources/webview/cat.png). To build everything we would use Gradle. build.gradle:

Place it in the root of the project. And finally — to run IntelliJ with our plugin: $ gradle runIde On the right toolbar, you should see the CatViewer button. After clicking on it, a tool window with your photo should appear.

Of course, WebView can show much more. My colleagues develop quite a heavy app for browsing code like a graph — and for presenting it in IntelliJ, they use JCEF. The project has the name GraphBuddy — please check it, even to see how complex things can be presented using WebView.

Thank you for reading!

More materials:

--

--