Revamped JCEF page (#1178)

* jcef.md: Rewrite the page

* Review fixes

* Update date in content_updates.md

* Change title to more meaningful

* Change title to more meaningful

* Add information about enabling file download

* Update title in navigation

* Set higher level for the Event Handlers section

* Remove redundant title definition in TOC element

* Fix link revert

* Rename examples section

* Do not suggest using JBCefClient.add*Handler() methods - they don't work well and will be probably removed

* Add information about customizing scrollbars
This commit is contained in:
Karol Lewandowski 2023-12-06 18:43:53 +01:00 committed by GitHub
parent 69a9b5d8a7
commit 27781210c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 261 additions and 142 deletions

View File

@ -87,7 +87,7 @@
<toc-element topic="color_scheme_management.md"/>
<toc-element topic="themes_metadata.md"/>
</toc-element>
<toc-element topic="jcef.md" toc-title="JCEF"/>
<toc-element topic="jcef.md"/>
<toc-element topic="basic_action_system.md" accepts-web-file-names="action_system.html">
<toc-element topic="action_system.md">
<toc-element topic="working_with_custom_actions.md"/>

View File

@ -12,6 +12,12 @@ See [GitHub Changelog](https://github.com/JetBrains/intellij-sdk-docs/commits/ma
## 2023
### December
{#december-23}
JCEF
: Revamp [JCEF (Java Chromium Embedded Framework)](jcef.md) page.
### November
{#november-23}

View File

@ -1,65 +1,282 @@
<!-- Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
# JCEF — Java Chromium Embedded Framework
# Embedded Browser (JCEF)
<link-summary>Embedding Chromium-based browser in IDE.</link-summary>
> JCEF is available since 2020.1 as an **experimental feature**.
> We plan to deprecate using JavaFX in 3rd party plugins and switch to JCEF in 2020.2.
> To continue using JavaFX in 2020.2 or later, an explicit dependency on [JavaFX Runtime for Plugins](https://plugins.jetbrains.com/plugin/14250-javafx-runtime-for-plugins) must be added.
> Please see also blog post [JavaFX and JCEF in the IntelliJ Platform](https://blog.jetbrains.com/platform/2020/07/javafx-and-jcef-in-the-intellij-platform/) for summary of plans.
>
{style="warning"}
> JCEF is available since 2020.1 as an experimental feature and is enabled by default since 2020.2.
JCEF is a Java port of [CEF](https://bitbucket.org/chromiumembedded/cef/wiki/Home) framework for embedding [Chromium-based browsers](https://www.chromium.org/Home) in applications using Swing.
JCEF (Java Chromium Embedded Framework) is a Java port of [CEF](https://bitbucket.org/chromiumembedded/cef/wiki/Home).
It allows for embedding [Chromium-based browsers](https://www.chromium.org/Home) in Swing applications.
Embedding of the browser component inside the IDE allows amongst others:
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., diagrams preview, image browser, etc.)
> Please see also [Creating IntelliJ plugin with WebView](https://medium.com/virtuslab/creating-intellij-plugin-with-webview-3b27c3f87aea) tutorial.
It is recommended to implement UI in the default IntelliJ Platform UI framework, which is Swing.
Consider using JCEF approach only in cases, when a plugin needs to display HTML documents or the standard approach for creating UI is insufficient.
JCEF replaces JavaFX, which was used to render web content in IDEs in the past.
> Using JavaFX in 3rd party plugins has been deprecated since 2020.2.
> To continue using JavaFX in 2020.2 or later, add an explicit [dependency](https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html) on [JavaFX Runtime for Plugins](https://plugins.jetbrains.com/plugin/14250-javafx-runtime-for-plugins) (not recommended).
>
> See [JavaFX and JCEF in the IntelliJ Platform](https://blog.jetbrains.com/platform/2020/07/javafx-and-jcef-in-the-intellij-platform/) blog post for the details.
>
{style="warning"}
## Enabling JCEF
<tabs>
<tab title="2020.2 and later">
> JCEF is available and enabled by default since 2020.2
>
{style="note"}
JCEF is available and enabled by default since 2020.2.
No additional actions are required.
</tab>
<tab title="Earlier versions">
Using JCEF requires using a dedicated JetBrains Runtime.
See [installation instructions](https://youtrack.jetbrains.com/issue/IDEA-231833#focus=streamItem-27-3993099.0-0) on how to obtain and activate it in your IDE.
Enable `ide.browser.jcef.enabled` in <control>Registry</control> dialog (invoke <ui-path>Help | Find Action...</ui-path> and type "Registry") and restart the IDE for changes to take effect.
Using JCEF requires using a dedicated JetBrains Runtime and enabling JCEF in the IDE Registry.
<procedure>
1. Go to the [JetBrains Runtime releases list](https://github.com/JetBrains/JetBrainsRuntime/releases).
2. Download "Binaries for launching IntelliJ IDEA" matching your operating system, e.g., <path>jbr_jcef-17.0.9-osx-x64-b1087.7.tar.gz</path> for macOS.
3. Unpack the archive.
4. Follow the steps described in the [IDEA Web Help](https://www.jetbrains.com/help/idea/2020.2/switching-boot-jdk.html) and choose the downloaded JBR.
5. Invoke <ui-path>Help | Find Action...</ui-path>, type "Registry", and press enter to open the <control>Registry</control> dialog.
6. Enable the `ide.browser.jcef.enabled` flag.
7. Restart the IDE for changes to take effect.
</procedure>
</tab>
</tabs>
## Using JCEF In a Plugin
The core JCEF class exposed by IntelliJ Platform API is [`JBCefApp`](%gh-ic%/platform/platform-api/src/com/intellij/ui/jcef/JBCefApp.java).
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](#browser) or [client](#browser-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()`:
```java
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 [`JBCefBrowser`](%gh-ic%/platform/platform-api/src/com/intellij/ui/jcef/JBCefBrowser.java) 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`](%gh-ic%/platform/platform-api/src/com/intellij/ui/jcef/JBCefBrowserBuilder.java).
Use constructors in the cases when a browser with the default [client](#browser-client) and default options is enough.
The builder approach allows using custom clients and configuring other options.
#### Adding Browser to UI
[`JBCefBrowser.getComponent()`](%gh-ic%/platform/platform-api/src/com/intellij/ui/jcef/JBCefBrowser.java) exposes the UI component embedding the actual browser.
The component is an instance of Swing `JComponent`, which can be added to the plugin UI:
```java
// 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*()`](%gh-ic%/platform/platform-api/src/com/intellij/ui/jcef/JBCefBrowserBase.java) methods.
Methods loading documents can be called from both UI and non-UI threads.
It is possible to set an initial URL (passed to constructor or builder) that will be loaded when browser is created and initialized.
### Browser Client
Browser client provides an interface for setting up [handlers](#event-handlers) related to various browser events, e.g.:
- HTML document loaded
- console message printed
- browser gained focus
Handlers allow reacting to these events in plugin code and change 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`](%gh-ic%/platform/platform-api/src/com/intellij/ui/jcef/JBCefClient.java), which is a wrapper for JCEF's [`CefClient`](https://github.com/JetBrains/jcef/blob/master/java/org/cef/CefClient.java).
`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](#disposing-resources) automatically, following the associated browser instance disposal.
For more advanced use cases, create a custom client by calling `JBCefApp.createClient()` and register required [handlers](#event-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 allows handling a wide set of events emitted by the browser.
Example handlers:
- [`CefLoadHandler`](https://github.com/JetBrains/jcef/blob/master/java/org/cef/handler/CefLoadHandler.java) - handles browser loading events.<br/>
**Example**: Implement `CefLoadHandler.onLoadEnd()` to [execute scripts](#executing-javascript) after document is loaded.
- [`CefDisplayHandler`](https://github.com/JetBrains/jcef/blob/master/java/org/cef/handler/CefDisplayHandler.java) - handles events related to browser display state.<br/>
**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`](https://github.com/JetBrains/jcef/blob/master/java/org/cef/handler/CefContextMenuHandler.java) - handles context menu events.<br/>
**Example**: Implement `CefContextMenuHandler.onBeforeContextMenu()` to change the items of the browser context menu.
- [`CefDownloadHandler`](https://github.com/JetBrains/jcef/blob/master/java/org/cef/handler/CefDownloadHandler.java) - file download events.<br/>
**Example**: Implement `CefDownloadHandler.onBeforeDownload()` to enable downloading files in the embedded browser.
See [org.cef.handler](https://github.com/JetBrains/jcef/tree/master/java/org/cef/handler) package for all available handlers.
> For each handler interface, JCEF API provides an adapter class, which can be extended to avoid implementing unused methods, e.g., [`CefLoadHandlerAdapter`](https://github.com/JetBrains/jcef/blob/master/java/org/cef/handler/CefLoadHandlerAdapter.java).
Handlers should be registered with `JBCefClient.getCefClient().add*Handler()` methods.
> Please note that `JBCefClient` exposes methods for adding handlers, but it is not recommended to use them.
>
{style="warning"}
### 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.:
```java
browser.getCefBrowser().executeJavaScript(
"alert('Hello World!')",
url, lineNumber
);
```
The above snippet will be executed in the embedded browser and will display 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](https://youtrack.jetbrains.com/issue/JBR-2046) 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`](%gh-ic%/platform/platform-api/src/com/intellij/ui/jcef/JBCefJSQuery.java).
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):
```java
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 `JBCefQuery` instance. Make sure that the passed browser instance is of type `JBCefBrowserBase` (casting may be needed).
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 `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`'s function `link` parameter.
This value is injected to the query function call, and can be any value that is required by 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](plugin_content.md) or build them on the fly depending on some configuration.
Such resources cannot be easily accessed by the browser.
They can be made accessible by implementing proper request [handlers](#event-handlers), which make them available to the browser at predefined URLs.
This approach requires implementing [`CefRequestHandler`](https://github.com/JetBrains/jcef/blob/master/java/org/cef/handler/CefRequestHandler.java), and [`CefResourceRequestHandler`](https://github.com/JetBrains/jcef/blob/master/java/org/cef/handler/CefResourceRequestHandler.java), 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`](%gh-ic%/images/src/org/intellij/images/editor/impl/jcef/JCefImageViewer.kt) and related classes for the implementation details.
### Scrollbars Look and Feel
Default browser scrollbars may be insufficient, e.g. 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`](%gh-ic%/platform/platform-api/src/com/intellij/ui/jcef/JBCefScrollbarsHelper.java) that allows to customize scrollbars in two ways:
1. Using `JBCefScrollbarsHelper.getOverlayScrollbarStyle()`, which provides the styles adapted to the IDE scrollbars.
2. Using [OverlayScrollbars](https://kingsora.github.io/OverlayScrollbars/) library adapted to the IDE look and feel.
For the details, see `getOverlayScrollbarsSourceCSS()`, `getOverlayScrollbarsSourceJS()`, and `buildScrollbarsStyle()` Javadocs.
It should be used when transparent scrollbars or other advanced options are required.
### Disposing Resources
`JBCefBrowser`, `JBCefClient`, and `JBCefJSQuery` classes implement [`JBCefDisposable`](%gh-ic%/platform/platform-api/src/com/intellij/ui/jcef/JBCefDisposable.java), which extends [`Disposable`](%gh-ic%/platform/util/src/com/intellij/openapi/Disposable.java).
It means that these classes should clean up their resources according to the rules described on the [](disposers.md) page.
For example, a custom `JBCefClient` with registered handlers should remove them in the `dispose()` method implementation.
## Testing
See [`JBCefTestHelper`](%gh-ic%/platform/platform-tests/testSrc/com/intellij/ui/jcef/JBCefTestHelper.java) and tests in that package.
## Debugging
The [Chrome DevTools](https://developers.google.com/web/tools/chrome-devtools/), embedded into JCEF, can be used as a debugging and profiling tool.
It's active by default, so that a Chrome DevTools client can attach to it via the default port number (9222).
The port number can be configured with the following registry key:
```
ide.browser.jcef.debug.port=9222
```
It is active by default, so that a Chrome DevTools client can attach to it via the default 9222 port.
Default port can be changed via the registry key `ide.browser.jcef.debug.port` (go to <ui-path>Help | Find Action...</ui-path> 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 <control>Attach to Node.js/Chrome</control> configurations with a proper port number.
Use the [<control>Attach to Node.js/Chrome</control>](https://www.jetbrains.com/help/idea/run-debug-configuration-node-js-remote-debug.html) configuration with a proper port number.
Also, JCEF provides a default Chrome DevTools front-end (similar to the one in the Chrome browser) that can be opened from the JCEF's browser component context menu via <ui-path>Open DevTools</ui-path>.
The menu item is available in [internal mode](enabling_internal.md) only.
Starting with version 2021.3, the registry key `ide.browser.jcef.contextMenu.devTools.enabled` must be set to `true` explicitly.
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 <ui-path>Open DevTools</ui-path>.
The menu item is available in the [internal mode](enabling_internal.md) only, and since version 2021.3, the registry key `ide.browser.jcef.contextMenu.devTools.enabled` must be set to `true` explicitly.
To access the Chrome DevTools in plugin code, use the following API:
### Accessing DevTools Programmatically
To access the Chrome DevTools in the plugin code, use the following API:
```java
JBCefBrowser browser = new JBCefBrowser(myUrl);
CefBrowser devTools = browser.getCefBrowser().getDevTools();
JBCefBrowser devToolsBrowser = JBCefBrowser.createBuilder()
.setCefBrowser(devTools)
@ -67,117 +284,13 @@ JBCefBrowser devToolsBrowser = JBCefBrowser.createBuilder()
.build();
```
Or in order to just open it in a separate window:
In order to open DevTools in a separate window, call `JBCefBrowser.openDevtools()`.
```java
JBCefBrowser browser = new JBCefBrowser(myUrl);
browser.openDevtools();
```
## JCEF Usage Examples
## Testing
See [`JBCefTestHelper`](%gh-ic%/platform/platform-tests/testSrc/com/intellij/ui/jcef/JBCefTestHelper.java) and tests in that package.
## API
### JBCefApp
[`JBCefApp`](%gh-ic%/platform/platform-api/src/com/intellij/ui/jcef/JBCefApp.java) performs JCEF auto-initialization, manages its lifecycle, and provides `JBCefClient` instances.
Before using JCEF, `JBCefApp.isSupported()` check must be called:
```java
if (!JBCefApp.isSupported()) {
// Fallback to an alternative browser-less solution
return;
}
// Use JCEF
```
JCEF can be unsupported when:
- It's not available in the IDE runtime (the IDE is started with an alternative OpenJDK).
- Its version is not compatible with the running IDE.
To avoid the above problems, the IDE should be run with the bundled JetBrains Runtime (JBR) (see also [](ide_development_instance.md)).
### JBCefClient
[`JBCefClient`](%gh-ic%/platform/platform-api/src/com/intellij/ui/jcef/JBCefClient.java) is tied to every browser component explicitly or implicitly.
It is used for adding handlers to the associated browser.
The same instance can be shared among multiple browsers.
It is up to the developer to use a shared or per-browser instance, depending on the handlers' logic.
If a client was created explicitly, it should be [disposed](disposers.md) by the developer.
Otherwise, it is disposed automatically following the associated browser instance disposal.
### JBCefBrowser
[`JBCefBrowser`](%gh-ic%/platform/platform-api/src/com/intellij/ui/jcef/JBCefBrowser.java) provides the browser UI component:
```java
JComponent getComponent();
```
It also provides the load methods (callable from non-EDT thread as well):
```java
void loadURL(String);
void loadHTML(String);
```
For executing JS code and callbacks (see [](#jbcefjsquery)), use the wrapped `CefBrowser` instance directly:
```java
getCefBrowser().executeJavaScript(myCode, myUrl, myLine);
```
By default, `JBCefBrowser` is created with implicit `JBCefClient` (disposed automatically).
It is possible to pass your own `JBCefClient` (disposed by the developer).
For accessing the browser use `JBCefClient`:
```java
JBCefClient getJBCefClient();
```
The simplest way to add a browser component to the plugin UI:
```java
JPanel myPanel = ...;
myPanel.add(new JBCefBrowser("https://example.com").getComponent());
```
### JBCefJSQuery
[`JBCefJSQuery`](%gh-ic%/platform/platform-api/src/com/intellij/ui/jcef/JBCefJSQuery.java) provides JS query callback mechanism.
There is no direct access to JS DOM from Java (like in JavaFX WebView, see also [this issue](https://youtrack.jetbrains.com/issue/JBR-2046)).
Still, JCEF provides an asynchronous way to communicate to JS.
The example below shows opening a link in an external browser, and handling it:
```java
JBCefBrowser browser = new JBCefBrowser();
CefBrowser cefBrowser = browser.getCefBrowser();
// Create a JS query instance
JBCefJSQuery openInBrowserJsQuery =
JBCefJSQuery.create((JBCefBrowserBase)browser);
// Add a query handler
openInBrowserJsQuery.addHandler((link) -> {
// handle link here
return null; // can respond back to JS with JBCefJSQuery.Response
});
// Inject the query callback into JS
cefBrowser.executeJavaScript(
"window.JavaPanelBridge = {" +
"openInExternalBrowser : function(link) {" +
openInBrowserJsQuery.inject("link") +
"}" +
"};",
cefBrowser.getURL(), 0);
// Dispose the query when necessary
Disposer.dispose(openInBrowserJsQuery);
```
- [Markdown preview panel](%gh-ic%/plugins/markdown/core/src/org/intellij/plugins/markdown/ui/preview/jcef/MarkdownJCEFHtmlPanel.kt)
- [SVG Image Viewer](%gh-ic%/images/src/org/intellij/images/editor/impl/jcef/JCefImageViewer.kt)
- [PDF Viewer](https://github.com/FirstTimeInForever/intellij-pdf-viewer) plugin
- [CodeStream](https://github.com/TeamCodeStream/codestream) plugin
- [Excalidraw Integration](https://github.com/bric3/excalidraw-jetbrains-plugin) plugin
- [Creating IntelliJ plugin with WebView](https://medium.com/virtuslab/creating-intellij-plugin-with-webview-3b27c3f87aea) blog post