IntelliJ Platform Plugin SDK Help

Embedded Browser (JCEF)

JCEF (Java Chromium Embedded Framework) is a Java port of CEF. It allows for embedding Chromium-based browsers in Swing applications.

Embedding of the browser component inside the IDE can be used for:

  • rendering HTML content

  • previewing generated HTML (e.g., from Markdown)

  • creating custom web-based components (e.g., diagram preview, image browser, etc.)

It is recommended to implement UI in the default IntelliJ Platform UI framework, which is Swing. Consider using the JCEF approach only in cases when a plugin needs to display HTML documents or the standard approach for creating UI is not enough.

JCEF replaces JavaFX, which was used to render web content in IDEs in the past.

Enabling JCEF

JCEF is available and enabled by default since 2020.2. No additional actions are required.

Using JCEF requires using a dedicated JetBrains Runtime and enabling JCEF in the IDE Registry.

  1. Go to the JetBrains Runtime releases list.

  2. Download "Binaries for launching IntelliJ IDEA" matching your operating system, e.g., jbr_jcef-17.0.9-osx-x64-b1087.7.tar.gz for macOS.

  3. Unpack the archive.

  4. Follow the steps described in the IDEA Web Help and choose the downloaded JBR.

  5. Invoke Help | Find Action..., type "Registry", and press enter to open the Registry dialog.

  6. Enable the ide.browser.jcef.enabled flag.

  7. Restart the IDE for changes to take effect.

    Using JCEF In a Plugin

    The core JCEF class exposed by IntelliJ Platform API is JBCefApp. It is responsible for initializing JCEF context and managing its lifecycle.

    There is no need for initializing JBCefApp explicitly. It is done when JBCefApp.getInstance() is called, or when browser or client objects are created.

    Before using JCEF API, it is required to check whether JCEF is supported in the running IDE. It is done by calling JBCefApp.isSupported():

    if (JBCefApp.isSupported()) { // use JCEF } else { // optional fallback to an alternative browser-less solution }

    JCEF can be unsupported when:

    • The IDE is started with an alternative JDK that does not include JCEF.

    • Its version is not compatible with the running IDE.

    Browser

    JCEF browser is represented by the JBCefBrowser class. It is responsible for loading and rendering requested documents in the actual Chromium-based browser.

    JCEF browsers can be created either by using the JBCefBrowser class' constructors or via JBCefBrowserBuilder. Use constructors in the cases when a browser with the default client and default options is enough. The builder approach allows using custom clients and configuring other options.

    Adding Browser to UI

    JBCefBrowser.getComponent() exposes the UI component embedding the actual browser. The component is an instance of Swing JComponent, which can be added to the plugin UI:

    // assume 'JPanel myPanel' is a part of a tool window UI JBCefBrowser browser = new JBCefBrowser(); myPanel.add(browser.getComponent());

    Loading Documents

    To load a document in the browser, use one of JBCefBrowserBase.load*() methods. Methods loading documents can be called from both EDT and background threads. It is possible to set an initial URL (passed to constructor or builder) that will be loaded when the browser is created and initialized.

    Browser Client

    Browser client provides an interface for setting up handlers related to various browser events, e.g.:

    • HTML document loaded

    • console message printed

    • the browser gained focus

    Handlers allow reacting to these events in plugin code and changing the browser's behavior. Each browser is tied to a single client, and a single client can be shared with multiple browser instances.

    Browser client is represented by JBCefClient, which is a wrapper for JCEF's CefClient. JBCefClient allows registering multiple handlers of the same type, which is not possible with CefClient. To access the underlying CefClient and its API, call JBCefClient.getCefClient().

    Creating and Accessing Client

    If a JBCefBrowser instance is created without passing a specific client, it is tied to a default client created implicitly. Implicit clients are disposed automatically, following the associated browser instance disposal.

    For more advanced use cases, create a custom client by calling JBCefApp.createClient() and register required handlers. Custom clients must be disposed explicitly in the plugin code.

    To access the client associated with a browser, call JBCefBrowser.getJBCefClient().

    Event Handlers

    JCEF API provides various event handler interfaces that allow handling a wide set of events emitted by the browser. Example handlers:

    • CefLoadHandler - handles browser loading events.
      Example: Implement CefLoadHandler.onLoadEnd() to execute scripts after a document is loaded.

    • CefDisplayHandler - handles events related to the browser display state.
      Example: Implement CefDisplayHandler.onAddressChange() to load project files in the browser when a local file link is clicked, or opening an external browser if an external link is clicked.

    • CefContextMenuHandler - handles context menu events.
      Example: Implement CefContextMenuHandler.onBeforeContextMenu() to change the items of the browser context menu.

    • CefDownloadHandler - file download events.
      Example: Implement CefDownloadHandler.onBeforeDownload() to enable downloading files in the embedded browser.

    See the org.cef.handler package for all available handlers.

    Handlers should be registered with JBCefClient.getCefClient().add*Handler() methods.

    Executing JavaScript

    JCEF API allows executing JavaScript code in the embedded browser from the plugin code. JavaScript can be used for manipulating DOM, creating functions required in implemented features, injecting styles, etc.

    In the simplest case, JavaScript code can be executed by using JBCefBrowser.getCefBrowser().executeJavaScript(), e.g.:

    browser.getCefBrowser().executeJavaScript( "alert('Hello World!')", url, lineNumber );

    The above snippet will be executed in the embedded browser and will display an alert box with the "Hello World!" message. The url and lineNumber parameters are used in the error report in the browser if the script throws an error. Their purpose is to help debugging in case of errors, and they are not crucial for the script execution. It is common to pass browser.getCefBrowser().getUrl() or null/empty string, and 0 as these parameters.

    Executing Plugin Code From JavaScript

    JCEF doesn't provide direct access to DOM from the plugin code (it may change in the future), and asynchronous communication with JavaScript is achieved with the callback mechanism. It allows executing plugin code from the embedded browser via JavaScript, e.g., when a button or link is clicked, a shortcut is pressed, a JavaScript function is called, etc.

    JavaScript query callback is represented by JBCefJSQuery. It is an object bound to a specific browser, and it holds a set of handlers that implement the required plugin behavior.

    Consider a case which requires opening local files links in the editor and external links in an external browser. Such a requirement could be implemented as follows (each step is explained under the code snippet):

    JBCefJSQuery openLinkQuery = JBCefJSQuery.create(browser); // 1 openLinkQuery.addHandler((link) -> { // 2 if (LinkUtil.isExternal(link)) { BrowserUtil.browse(link); } else { EditorUtil.openFileInEditor(link); } return null; // 3 }); browser.getCefBrowser().executeJavaScript( // 4 "window.openLink = function(link) {" + openLinkQuery.inject("link") + // 5 "};", browser.getCefBrowser().getURL(), 0 ); browser.getCefBrowser().executeJavaScript( // 6 """ document.addEventListener('click', function (e) { const link = e.target.closest('a').href; if (link) { window.openLink(link); } });""", browser.getCefBrowser().getURL(), 0 );
    1. Create a JBCefQuery instance. Make sure that the passed browser instance is of the type JBCefBrowserBase (casting may be necessary).

    2. Add a handler implementing a plugin code to be executed. Example implementation opens a link in the editor or an external browser depending on whether the link is local or external.

    3. Handlers can optionally return a JBCefJSQuery.Response object, which holds information about success or error occurred on the plugin code side. It can be handled in the browser if needed.

    4. Execute JavaScript, which creates a custom openLink function.

    5. Inject JavaScript code responsible for invoking plugin code implemented in step 2. The handler added to openLinkQuery will be invoked on each openLink function call.

      Note the "link" parameter of the JBCefJSQuery.inject() method. It is the name of the openLink function's link parameter. This value is injected to the query function call and can be any value required by the handler, e.g., "myJsObject.prop", "'JavaScript string'", etc.

    6. Execute JavaScript, which registers a click event listener in the browser. Whenever an a element is clicked in the browser, the listener will invoke the openLink function defined in step 4 with the href value of the clicked link.

    Loading Resources From Plugin Distribution

    In cases when a plugin feature implements a web-based UI, the plugin may provide HTML, CSS, and JavaScript files in its distribution or build them on the fly depending on some configuration. The browser cannot easily access such resources. They can be made accessible by implementing proper request handlers, which make them available to the browser at predefined URLs.

    This approach requires implementing CefRequestHandler, and CefResourceRequestHandler, which map resource paths to resource providers.

    Serving such resources is implemented by the Image Viewer component responsible for displaying SVG files in IntelliJ Platform-based IDEs. See JCefImageViewer and related classes for the implementation details.

    Scrollbars Look and Feel

    Default browser scrollbars may be not enough, for example, when they stand out of the IDE scrollbars look, or specific look and behavior is required.

    In JCEF browsers, scrollbars look and feel can be customized by CSS and JavaScript. IntelliJ Platform provides JBCefScrollbarsHelper that allows customizing scrollbars in two ways:

    1. Using JBCefScrollbarsHelper.buildScrollbarsStyle(), which provides the styles adapted to the IDE scrollbars (recommended).

    2. Using OverlayScrollbars library adapted to the IDE look and feel. For the details, see getOverlayScrollbarsSourceCSS(), getOverlayScrollbarsSourceJS(), and getOverlayScrollbarStyle() Javadocs. It should be used when transparent scrollbars or other advanced options are required.

    Disposing Resources

    JBCefBrowser, JBCefClient, and JBCefJSQuery classes implement JBCefDisposable, which extends Disposable. It means that these classes should clean up their resources according to the rules described on the Disposer and Disposable page.

    For example, a custom JBCefClient with registered handlers should remove them in the dispose() method implementation.

    Testing

    See JBCefTestHelper and tests in that package.

    Debugging

    The Chrome DevTools, embedded into JCEF, can be used as a debugging and profiling tool. It is active by default, so that a Chrome DevTools client can attach to it via the default port 9222. The default port can be changed via the registry key ide.browser.jcef.debug.port (go to Help | Find Action... and type "Registry").

    JavaScript debugger in IntelliJ IDEA Ultimate can thus be used to debug JavaScript code running in the IDE via the Chrome DevTools. Use the Attach to Node.js/Chrome configuration with a proper port number.

    Also, JCEF provides a default Chrome DevTools frontend (similar to the one in the Chrome browser) that can be opened from the JCEF's browser component context menu via Open DevTools. The menu item is available in the internal mode only, and since version 2021.3, the registry key ide.browser.jcef.contextMenu.devTools.enabled must be set to true explicitly.

    Accessing DevTools Programmatically

    To access the Chrome DevTools in the plugin code, use the following API:

    CefBrowser devTools = browser.getCefBrowser().getDevTools(); JBCefBrowser devToolsBrowser = JBCefBrowser.createBuilder() .setCefBrowser(devTools) .setClient(browser.getJBCefClient()) .build();

    To open DevTools in a separate window, call JBCefBrowser.openDevtools().

    JCEF Usage Examples

    Last modified: 29 August 2024