How the JCEF Desktop Renderer Communicates with the Java Backend in Chat2DB
The JCEF desktop renderer in Chat2DB communicates with the Java backend through CefMessageRouter for JavaScript-to-Java calls and direct JavaScript injection via CallJsFunctionUtil for Java-to-JavaScript events, using JSON payloads transported asynchronously.
Chat2DB embeds a Chromium-based JCEF (Java CEF) browser inside a native desktop frame to render the UI. The bidirectional bridge between the frontend JavaScript and the Java backend relies on CEF's message-router mechanism and direct script execution, enabling the desktop application to invoke Spring MVC controllers and receive real-time updates.
Setting Up the CefMessageRouter Channel
Initializing the Message Router in MainJFrame
The communication channel originates in MainJFrame.java where the application creates a CefMessageRouter with custom query names. This router listens for JavaScript calls and dispatches them to Java handlers.
// MainJFrame.java (chat2db-community-jcef)
CefMessageRouter messageRouter = CefMessageRouter.create(
new CefMessageRouter.CefMessageRouterConfig("javaQuery", "javaCancelQuery"));
messageRouter.addHandler(new CefMessageRouterHandlerAdapter() {
@Override
public boolean onQuery(CefBrowser browser, CefFrame frame,
long queryId, String data,
boolean persistent, CefQueryCallback callback) {
// Handle the JSON request from the page
return true; // Tell CEF the query was processed
}
@Override
public void onQueryCanceled(CefBrowser browser, CefFrame frame,
long queryId) {
log.info("JS query canceled: {}", queryId);
}
}, true);
client_.addMessageRouter(messageRouter);
The configuration uses javaQuery as the primary channel name and javaCancelQuery for cancellation events. The onQuery method receives JSON payloads from the frontend, while onQueryCanceled handles aborted requests.
Registering the JavaScript Query Interface
On the frontend, the embedded browser exposes window.javaQuery (or window.cefQuery if using defaults) as the entry point for sending requests to Java. The page constructs a ConsoleMessage JSON payload and passes it through this global function.
// Frontend JavaScript (e.g., src/pages/console.ts)
function invokeBackend(action, method, payload) {
return new Promise((resolve, reject) => {
window.javaQuery({
request: JSON.stringify({
requestUrl: action,
method: method,
uuid: crypto.randomUUID(),
...payload
}),
onSuccess: resolve,
onFailure: reject
});
});
}
// Usage example
invokeBackend('/api/sql/execute', 'POST', {sql: 'SELECT 1'})
.then(resp => console.log('Backend reply:', resp))
.catch(err => console.error('Backend error:', err));
Processing JavaScript Requests in Java
Deserializing ConsoleMessage Payloads
When onQuery receives data, it deserializes the JSON into a ConsoleMessage object, which acts as the standard request envelope for all cross-layer communication.
// Inside the onQuery handler (MainJFrame.java)
ConsoleMessage wsMessage = JSONObject.parseObject(data, ConsoleMessage.class);
String action = wsMessage.getRequestUrl();
IJcefServerBridge bridge = JcefServerBridgeRegistry.getBridge();
bridge.setHeaders(wsMessage);
The ConsoleMessage class (located in chat2db-community-tools) encapsulates fields like requestUrl, method, uuid, and headers, providing a consistent structure for routing decisions.
Routing with @JcefAction Handlers and Spring Bridge
Chat2DB implements a dual routing strategy. First, it checks an action-handler map populated by scanning classes annotated with @JcefAction. If no custom handler matches the request URL and HTTP method pair, it falls back to the IJcefServerBridge, which forwards the request to the standard Spring MVC controller layer.
// Routing logic in MainJFrame.java
IJcefActionHandler handler = actionHandlers.get(
Pair.of(action, wsMessage.getMethod().toLowerCase()));
if (handler != null) {
handler.handle(wsMessage, wsResult, callback);
} else {
// Fallback to generic controller bridge
while (!bridge.isReady()) Thread.sleep(20);
ConsoleResult result = bridge.doController(wsMessage);
ResponseBuilder.buildSuccess(result, callback);
}
Custom handlers implement the IJcefActionHandler interface and are discovered at runtime via the @JcefAction annotation, which binds them to specific endpoints:
@JcefAction(value = "/api/sql/execute", method = "POST")
public class SqlExecuteHandler implements IJcefActionHandler {
@Override
public void handle(ConsoleMessage msg,
ConsoleResult result,
CefQueryCallback callback) throws Exception {
// Business logic: execute SQL, populate result
result.setMessage(Map.of("rows", List.of(...)));
ResponseBuilder.buildSuccess(result, callback);
}
}
Returning Responses to the Frontend
After processing, the backend serializes a ConsoleResult object to JSON and invokes callback.success() to resolve the JavaScript promise. The ResponseBuilder utility standardizes this pattern across all handlers.
// ResponseBuilder.java (chat2db-community-jcef)
public static void buildSuccess(ConsoleResult result,
CefQueryCallback callback) {
String json = JSON.toJSONString(result);
callback.success(json); // Returns payload to JS, resolving the promise
}
This asynchronous callback mechanism ensures the UI thread remains unblocked while waiting for database queries or controller logic to complete.
Pushing Events from Java to JavaScript
For server-initiated updates—such as login status changes or startup progress—the Java backend pushes data to the frontend without waiting for a query. This uses CallJsFunctionUtil to execute JavaScript directly in the browser context.
// MainJFrame.java – pushing a notification
ConsoleResult consoleResult = new ConsoleResult();
consoleResult.setMessage(Map.of("data", true));
consoleResult.setActionType(ActionTypeEnum.OSS_LOGIN.getName());
String result = JSON.toJSONString(consoleResult);
CallJsFunctionUtil.callHandleJavaMessage(
JcefContext.getInstance().getBrowser_(), result);
The utility injects a call to window.handleJavaMessage, which the frontend must register as a global handler:
// Frontend listener
window.handleJavaMessage = (json) => {
console.log('Received push from Java:', json);
// Update UI based on actionType
};
This enables proactive UI updates for events like OSS_LOGIN completion or background task completion.
Summary
- CefMessageRouter creates the primary channel (
javaQuery) for JavaScript-to-Java communication, configured inMainJFrame.javawith custom query and cancel names. ConsoleMessageandConsoleResultstandardize JSON payloads exchanged between layers, withConsoleMessagerepresenting requests andConsoleResultrepresenting responses.- Dual routing allows
@JcefAction-annotated handlers to process specific endpoints, while unmatched requests fall through to theIJcefServerBridgefor Spring MVC integration. - Asynchronous callbacks use
CefQueryCallback.success()to return data to JavaScript without blocking the renderer thread. - Java-to-JavaScript pushes leverage
CallJsFunctionUtil.callHandleJavaMessage()to executewindow.handleJavaMessagedirectly in the browser, enabling real-time backend-driven UI updates.
Frequently Asked Questions
How does JavaScript send data to the Java backend in Chat2DB?
JavaScript calls window.javaQuery() with a JSON request object containing request, onSuccess, and onFailure callbacks. This invokes the CefMessageRouter handler registered in MainJFrame.java, which deserializes the payload into a ConsoleMessage and routes it to the appropriate handler or Spring controller.
What is the purpose of the @JcefAction annotation?
The @JcefAction annotation marks classes that implement IJcefActionHandler and binds them to specific URL patterns and HTTP methods. At runtime, Chat2DB scans for these annotations to populate an action-handler map, allowing custom Java logic to intercept specific API calls before they reach the Spring MVC layer.
How does Chat2DB handle asynchronous communication between layers?
All communication is asynchronous by design. JavaScript uses Promise-based callbacks (onSuccess/onFailure) when calling javaQuery. On the Java side, the onQuery handler processes requests on a background thread and uses CefQueryCallback.success() to deliver responses, ensuring the CEF UI thread remains responsive during long-running database operations.
Can the Java backend push updates to the UI without a JavaScript request?
Yes. The Java backend uses CallJsFunctionUtil.callHandleJavaMessage() to execute window.handleJavaMessage(json) directly in the browser context. This mechanism allows the backend to proactively notify the frontend of events like login completion or configuration changes without requiring the UI to poll or initiate a request.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →