This guide covers integrating CEF (Chromium Embedded Framework) with JavaFX to embed a full-featured Chromium browser within Java applications.
Project Setup and Dependencies
Step 1: Maven Configuration
<?xml version="1.0" encoding="UTF-8"?>
<project>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<javafx.version>21</javafx.version>
<jcef.version>1.0.0</jcef.version>
</properties>
<dependencies>
<!-- JavaFX -->
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-web</artifactId>
<version>${javafx.version}</version>
</dependency>
<!-- JCEF (Chromium Embedded Framework for Java) -->
<dependency>
<groupId>org.cef</groupId>
<artifactId>jcef</artifactId>
<version>${jcef.version}</version>
</dependency>
<!-- Utilities -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.7</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.8</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>0.0.8</version>
<configuration>
<mainClass>com.example.cef.MainApp</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
Core CEF Integration
Step 2: Basic CEF Browser Component
package com.example.cef.core;
import org.cef.CefApp;
import org.cef.CefClient;
import org.cef.CefSettings;
import org.cef.browser.CefBrowser;
import org.cef.browser.CefFrame;
import org.cef.callback.CefContextMenuParams;
import org.cef.callback.CefMenuModel;
import org.cef.handler.CefContextMenuHandler;
import org.cef.handler.CefDisplayHandler;
import org.cef.handler.CefLoadHandler;
import org.cef.handler.CefRequestHandler;
import org.cef.network.CefRequest;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
public class CefBrowserWrapper {
private CefApp cefApp;
private CefClient cefClient;
private CefBrowser cefBrowser;
private JPanel browserPanel;
private JFXPanel jfxPanel;
private StackPane browserContainer;
private static CefBrowserWrapper instance;
public static synchronized CefBrowserWrapper getInstance() {
if (instance == null) {
instance = new CefBrowserWrapper();
}
return instance;
}
private CefBrowserWrapper() {
initializeCEF();
}
private void initializeCEF() {
try {
// Configure CEF settings
CefSettings settings = new CefSettings();
settings.windowless_rendering_enabled = true;
settings.log_severity = CefSettings.LogSeverity.LOGSEVERITY_INFO;
settings.remote_debugging_port = 8088; // Enable remote debugging
// Initialize CEF
cefApp = CefApp.getInstance(settings);
cefClient = cefApp.createClient();
// Add handlers
cefClient.addLoadHandler(new CustomLoadHandler());
cefClient.addDisplayHandler(new CustomDisplayHandler());
cefClient.addContextMenuHandler(new CustomContextMenuHandler());
cefClient.addRequestHandler(new CustomRequestHandler());
// Create browser
cefBrowser = cefClient.createBrowser("https://www.google.com", false, false);
// Create Swing panel for CEF browser
browserPanel = new JPanel(new BorderLayout());
browserPanel.add(cefBrowser.getUIComponent(), BorderLayout.CENTER);
// Create JavaFX container
initializeJavaFXContainer();
} catch (Exception e) {
throw new RuntimeException("Failed to initialize CEF", e);
}
}
private void initializeJavaFXContainer() {
jfxPanel = new JFXPanel();
browserContainer = new StackPane();
SwingUtilities.invokeLater(() -> {
jfxPanel.setScene(new Scene(browserContainer));
// Add resize listener
jfxPanel.addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
resizeBrowser();
}
});
});
}
public Node getBrowserNode() {
return jfxPanel;
}
public void loadURL(String url) {
if (cefBrowser != null) {
cefBrowser.loadURL(url);
}
}
public void executeJavaScript(String code) {
if (cefBrowser != null) {
cefBrowser.executeJavaScript(code, cefBrowser.getURL(), 0);
}
}
public void goBack() {
if (cefBrowser != null) {
cefBrowser.goBack();
}
}
public void goForward() {
if (cefBrowser != null) {
cefBrowser.goForward();
}
}
public void reload() {
if (cefBrowser != null) {
cefBrowser.reload();
}
}
public void stop() {
if (cefBrowser != null) {
cefBrowser.stopLoad();
}
}
public void resizeBrowser() {
SwingUtilities.invokeLater(() -> {
if (browserPanel != null && jfxPanel != null) {
Dimension size = jfxPanel.getSize();
browserPanel.setPreferredSize(size);
browserPanel.revalidate();
browserPanel.repaint();
}
});
}
public void dispose() {
if (cefBrowser != null) {
cefBrowser.close(true);
cefBrowser = null;
}
if (cefClient != null) {
cefClient.dispose();
cefClient = null;
}
if (cefApp != null) {
cefApp.dispose();
cefApp = null;
}
}
// Custom handlers
private class CustomLoadHandler implements CefLoadHandler {
@Override
public void onLoadingStateChange(CefBrowser browser, boolean isLoading,
boolean canGoBack, boolean canGoForward) {
System.out.println("Loading: " + isLoading +
", CanGoBack: " + canGoBack +
", CanGoForward: " + canGoForward);
}
@Override
public void onLoadStart(CefBrowser browser, CefFrame frame) {
System.out.println("Load started: " + frame.getURL());
}
@Override
public void onLoadEnd(CefBrowser browser, CefFrame frame, int httpStatusCode) {
System.out.println("Load completed: " + frame.getURL() + " - Status: " + httpStatusCode);
}
@Override
public void onLoadError(CefBrowser browser, CefFrame frame, ErrorCode errorCode,
String errorText, String failedUrl) {
System.err.println("Load error: " + errorText + " - URL: " + failedUrl);
}
}
private class CustomDisplayHandler implements CefDisplayHandler {
@Override
public void onAddressChange(CefBrowser browser, CefFrame frame, String url) {
System.out.println("Address changed: " + url);
}
@Override
public void onTitleChange(CefBrowser browser, String title) {
System.out.println("Title changed: " + title);
}
@Override
public void onFullscreenModeChange(CefBrowser browser, boolean fullscreen) {
System.out.println("Fullscreen mode: " + fullscreen);
}
@Override
public boolean onConsoleMessage(CefBrowser browser, String message, String source, int line) {
System.out.println("Console [" + source + ":" + line + "]: " + message);
return false;
}
}
private class CustomContextMenuHandler implements CefContextMenuHandler {
@Override
public void onBeforeContextMenu(CefBrowser browser, CefFrame frame,
CefContextMenuParams params, CefMenuModel model) {
// Customize context menu
model.clear();
model.addItem(CefMenuModel.MenuId.MENU_ID_BACK, "Go Back");
model.addItem(CefMenuModel.MenuId.MENU_ID_FORWARD, "Go Forward");
model.addItem(CefMenuModel.MenuId.MENU_ID_RELOAD, "Reload");
model.addSeparator();
model.addItem(CefMenuModel.MenuId.MENU_ID_PRINT, "Print");
model.addItem(CefMenuModel.MenuId.MENU_ID_VIEW_SOURCE, "View Source");
}
}
private class CustomRequestHandler implements CefRequestHandler {
@Override
public boolean onBeforeBrowse(CefBrowser browser, CefFrame frame, CefRequest request,
boolean userGesture, boolean isRedirect) {
System.out.println("Before browse: " + request.getURL());
return false; // Allow the navigation
}
}
}
JavaFX Browser Application
Step 3: Main Browser Application
package com.example.cef;
import com.example.cef.core.CefBrowserWrapper;
import com.example.cef.core.JavaScriptBridge;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import org.cef.CefApp;
public class MainApp extends Application {
private CefBrowserWrapper browserWrapper;
private TextField addressBar;
private Button backButton, forwardButton, reloadButton, homeButton;
private ProgressBar progressBar;
private Label statusLabel;
private BorderPane mainLayout;
@Override
public void init() throws Exception {
super.init();
// Initialize CEF in a separate thread to avoid blocking JavaFX
Thread cefInitThread = new Thread(() -> {
browserWrapper = CefBrowserWrapper.getInstance();
});
cefInitThread.setDaemon(true);
cefInitThread.start();
cefInitThread.join(); // Wait for initialization
}
@Override
public void start(Stage primaryStage) {
primaryStage.setTitle("JavaFX CEF Browser");
createUI();
setupEventHandlers();
Scene scene = new Scene(mainLayout, 1200, 800);
scene.getStylesheets().add(getClass().getResource("/styles/browser.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
// Load initial page
navigateTo("https://www.google.com");
}
private void createUI() {
// Create toolbar
ToolBar toolbar = createToolbar();
// Create status bar
HBox statusBar = createStatusBar();
// Create main layout
mainLayout = new BorderPane();
mainLayout.setTop(toolbar);
mainLayout.setBottom(statusBar);
// Add browser component
if (browserWrapper != null) {
Platform.runLater(() -> {
mainLayout.setCenter(browserWrapper.getBrowserNode());
});
} else {
Label errorLabel = new Label("Browser initialization failed");
errorLabel.setStyle("-fx-text-fill: red; -fx-font-size: 16px;");
mainLayout.setCenter(errorLabel);
}
}
private ToolBar createToolbar() {
// Navigation buttons
backButton = createToolbarButton("←", "Go back");
forwardButton = createToolbarButton("→", "Go forward");
reloadButton = createToolbarButton("↻", "Reload");
homeButton = createToolbarButton("⌂", "Home");
// Address bar
addressBar = new TextField();
addressBar.setPromptText("Enter URL or search...");
addressBar.setPrefWidth(600);
// Search button
Button goButton = createToolbarButton("Go", "Navigate to URL");
// Additional tools
Button devToolsButton = createToolbarButton("Dev", "Open DevTools");
Button jsConsoleButton = createToolbarButton("JS", "Execute JavaScript");
HBox leftTools = new HBox(5, backButton, forwardButton, reloadButton, homeButton);
HBox rightTools = new HBox(5, devToolsButton, jsConsoleButton);
ToolBar toolbar = new ToolBar();
toolbar.getItems().addAll(
leftTools,
new Separator(),
addressBar,
goButton,
new Separator(),
rightTools
);
return toolbar;
}
private HBox createStatusBar() {
progressBar = new ProgressBar();
progressBar.setPrefWidth(200);
progressBar.setProgress(0);
statusLabel = new Label("Ready");
statusLabel.setPadding(new Insets(5));
HBox statusBar = new HBox(10, progressBar, statusLabel);
statusBar.setAlignment(Pos.CENTER_LEFT);
statusBar.setPadding(new Insets(5));
statusBar.setStyle("-fx-background-color: #f0f0f0; -fx-border-color: #cccccc; -fx-border-width: 1 0 0 0;");
return statusBar;
}
private Button createToolbarButton(String text, String tooltip) {
Button button = new Button(text);
button.setTooltip(new Tooltip(tooltip));
button.setPrefSize(30, 30);
button.setStyle("-fx-font-size: 12px; -fx-background-radius: 3;");
return button;
}
private void setupEventHandlers() {
// Navigation buttons
backButton.setOnAction(e -> browserWrapper.goBack());
forwardButton.setOnAction(e -> browserWrapper.goForward());
reloadButton.setOnAction(e -> browserWrapper.reload());
homeButton.setOnAction(e -> navigateTo("https://www.google.com"));
// Address bar
addressBar.setOnKeyPressed(e -> {
if (e.getCode() == KeyCode.ENTER) {
navigateFromAddressBar();
}
});
// Go button
ToolBar toolbar = (ToolBar) mainLayout.getTop();
Button goButton = (Button) toolbar.getItems().get(4);
goButton.setOnAction(e -> navigateFromAddressBar());
// DevTools button
Button devToolsButton = (Button) ((HBox) toolbar.getItems().get(6)).getChildren().get(1);
devToolsButton.setOnAction(e -> openDevTools());
// JavaScript console
Button jsConsoleButton = (Button) ((HBox) toolbar.getItems().get(6)).getChildren().get(2);
jsConsoleButton.setOnAction(e -> showJavaScriptConsole());
}
private void navigateFromAddressBar() {
String input = addressBar.getText().trim();
if (!input.isEmpty()) {
navigateTo(input);
}
}
private void navigateTo(String url) {
if (!url.startsWith("http://") && !url.startsWith("https://") && !url.startsWith("file://")) {
if (url.contains(".") && !url.contains(" ")) {
url = "https://" + url;
} else {
url = "https://www.google.com/search?q=" + url.replace(" ", "+");
}
}
addressBar.setText(url);
browserWrapper.loadURL(url);
statusLabel.setText("Loading: " + url);
}
private void openDevTools() {
// CEF DevTools can be opened via remote debugging
String devToolsUrl = "http://localhost:8088";
navigateTo(devToolsUrl);
}
private void showJavaScriptConsole() {
TextInputDialog dialog = new TextInputDialog();
dialog.setTitle("JavaScript Console");
dialog.setHeaderText("Execute JavaScript Code");
dialog.setContentText("JavaScript:");
dialog.showAndWait().ifPresent(code -> {
if (!code.trim().isEmpty()) {
browserWrapper.executeJavaScript(code);
statusLabel.setText("Executed JavaScript: " + code);
}
});
}
@Override
public void stop() throws Exception {
super.stop();
if (browserWrapper != null) {
browserWrapper.dispose();
}
CefApp.getInstance().dispose();
Platform.exit();
}
public static void main(String[] args) {
// Set CEF log level
System.setProperty("java.awt.headless", "false");
System.setProperty("cef.log.level", "info");
launch(args);
}
}
Advanced Features and JavaScript Bridge
Step 4: JavaScript-Java Communication
package com.example.cef.core;
import org.cef.CefApp;
import org.cef.browser.CefBrowser;
import org.cef.browser.CefFrame;
import org.cef.browser.CefMessageRouter;
import org.cef.callback.CefQueryCallback;
import org.cef.handler.CefMessageRouterHandler;
import org.cef.handler.CefMessageRouterHandlerAdapter;
import org.cef.misc.BoolRef;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
public class JavaScriptBridge {
private final CefBrowser browser;
private final CefMessageRouter messageRouter;
private final ObjectMapper objectMapper;
private final Map<String, Consumer<String>> messageHandlers;
public JavaScriptBridge(CefBrowser browser) {
this.browser = browser;
this.objectMapper = new ObjectMapper();
this.messageHandlers = new HashMap<>();
// Create message router for JavaScript-Java communication
this.messageRouter = CefMessageRouter.create();
this.messageRouter.addHandler(new MessageRouterHandler(), true);
}
public void registerHandler(String messageType, Consumer<String> handler) {
messageHandlers.put(messageType, handler);
}
public void sendMessageToJavaScript(String messageType, Object data) {
try {
Map<String, Object> message = new HashMap<>();
message.put("type", messageType);
message.put("data", data);
String jsonMessage = objectMapper.writeValueAsString(message);
String javascriptCode = String.format("if (window.javaBridge) window.javaBridge.receiveMessage(%s);", jsonMessage);
browser.executeJavaScript(javascriptCode, browser.getURL(), 0);
} catch (Exception e) {
System.err.println("Failed to send message to JavaScript: " + e.getMessage());
}
}
public CompletableFuture<String> executeJavaScriptWithResult(String code) {
CompletableFuture<String> future = new CompletableFuture<>();
String callbackName = "javaCallback_" + System.currentTimeMillis();
String wrappedCode = String.format(
"(function() { try { var result = %s; window.%s(JSON.stringify({success: true, result: result})); } " +
"catch (e) { window.%s(JSON.stringify({success: false, error: e.message})); } })()",
code, callbackName, callbackName
);
// Register temporary callback
registerHandler(callbackName, result -> {
try {
Map response = objectMapper.readValue(result, Map.class);
if (Boolean.TRUE.equals(response.get("success"))) {
future.complete(objectMapper.writeValueAsString(response.get("result")));
} else {
future.completeExceptionally(new RuntimeException((String) response.get("error")));
}
} catch (Exception e) {
future.completeExceptionally(e);
}
messageHandlers.remove(callbackName);
});
// Execute JavaScript
browser.executeJavaScript(wrappedCode, browser.getURL(), 0);
return future;
}
public void injectBridgeScript() {
String bridgeScript = """
(function() {
if (window.javaBridge) return;
window.javaBridge = {
// Send message to Java
sendMessage: function(type, data) {
if (window.cefQuery && window.cefQuery.request) {
var message = {
type: type,
data: data
};
window.cefQuery.request({
request: JSON.stringify(message),
persistent: false,
onSuccess: function(response) {
console.log('Message sent successfully:', response);
},
onFailure: function(error_code, error_message) {
console.error('Failed to send message:', error_code, error_message);
}
});
}
},
// Receive message from Java
receiveMessage: function(message) {
try {
var parsed = JSON.parse(message);
if (parsed.type && parsed.data) {
// Dispatch to appropriate handler
if (window.javaMessageHandlers && window.javaMessageHandlers[parsed.type]) {
window.javaMessageHandlers[parsed.type](parsed.data);
}
}
} catch (e) {
console.error('Error processing Java message:', e);
}
}
};
// Global message handlers
window.javaMessageHandlers = {};
// Register handler for Java messages
window.javaBridge.registerHandler = function(type, handler) {
window.javaMessageHandlers[type] = handler;
};
console.log('Java bridge initialized');
})();
""";
browser.executeJavaScript(bridgeScript, browser.getURL(), 0);
}
private class MessageRouterHandler extends CefMessageRouterHandlerAdapter {
@Override
public boolean onQuery(CefBrowser browser, CefFrame frame, long queryId,
String request, boolean persistent, CefQueryCallback callback) {
try {
Map message = objectMapper.readValue(request, Map.class);
String messageType = (String) message.get("type");
Object data = message.get("data");
Consumer<String> handler = messageHandlers.get(messageType);
if (handler != null) {
handler.accept(objectMapper.writeValueAsString(data));
callback.success("Message processed");
} else {
callback.failure(404, "No handler for message type: " + messageType);
}
} catch (Exception e) {
callback.failure(500, "Error processing message: " + e.getMessage());
}
return true;
}
@Override
public void onQueryCanceled(CefBrowser browser, CefFrame frame, long queryId) {
System.out.println("Query canceled: " + queryId);
}
}
// Predefined message types and handlers
public static class MessageTypes {
public static final String GET_COOKIES = "getCookies";
public static final String SET_COOKIE = "setCookie";
public static final String EXECUTE_SCRIPT = "executeScript";
public static final String GET_PAGE_INFO = "getPageInfo";
public static final String DOWNLOAD_FILE = "downloadFile";
}
public void initializeStandardHandlers() {
// Handler for getting page information
registerHandler(MessageTypes.GET_PAGE_INFO, request -> {
String javascriptCode = """
(function() {
return {
url: window.location.href,
title: document.title,
userAgent: navigator.userAgent,
viewport: {
width: window.innerWidth,
height: window.innerHeight
},
cookies: document.cookie
};
})()
""";
executeJavaScriptWithResult(javascriptCode)
.thenAccept(result -> {
System.out.println("Page info: " + result);
sendMessageToJavaScript("pageInfoResponse", result);
})
.exceptionally(error -> {
System.err.println("Failed to get page info: " + error.getMessage());
return null;
});
});
// Handler for executing custom scripts
registerHandler(MessageTypes.EXECUTE_SCRIPT, script -> {
try {
browser.executeJavaScript(script, browser.getURL(), 0);
sendMessageToJavaScript("scriptExecuted", "Script executed successfully");
} catch (Exception e) {
sendMessageToJavaScript("scriptError", "Failed to execute script: " + e.getMessage());
}
});
}
}
Custom Browser Features
Step 5: Enhanced Browser with Tabs and Downloads
package com.example.cef.components;
import com.example.cef.core.CefBrowserWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.control.*;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import java.util.HashMap;
import java.util.Map;
public class TabbedBrowser extends BorderPane {
private TabPane tabPane;
private ObservableList<BrowserTab> tabs;
private Map<Tab, BrowserTab> tabMap;
private Button newTabButton;
public TabbedBrowser() {
tabs = FXCollections.observableArrayList();
tabMap = new HashMap<>();
createUI();
setupEventHandlers();
// Create initial tab
createNewTab();
}
private void createUI() {
// Create tab pane
tabPane = new TabPane();
tabPane.setTabClosingPolicy(TabPane.TabClosingPolicy.ALL_TABS);
// Create new tab button
newTabButton = new Button("+");
newTabButton.setTooltip(new Tooltip("New Tab"));
newTabButton.setStyle("-fx-font-size: 16px; -fx-font-weight: bold; -fx-background-color: transparent;");
// Add new tab button to tab pane
HBox tabHeader = new HBox();
tabHeader.getChildren().addAll(tabPane, newTabButton);
HBox.setHgrow(tabPane, Priority.ALWAYS);
setCenter(tabHeader);
}
private void setupEventHandlers() {
// New tab button
newTabButton.setOnAction(e -> createNewTab());
// Tab close request
tabPane.setOnCloseRequest(event -> {
Tab tab = (Tab) event.getSource();
BrowserTab browserTab = tabMap.get(tab);
if (browserTab != null) {
browserTab.dispose();
tabs.remove(browserTab);
tabMap.remove(tab);
}
});
// Tab selection change
tabPane.getSelectionModel().selectedItemProperty().addListener((obs, oldTab, newTab) -> {
if (newTab != null) {
BrowserTab browserTab = tabMap.get(newTab);
if (browserTab != null) {
browserTab.activate();
}
}
});
}
public BrowserTab createNewTab() {
return createNewTab("https://www.google.com");
}
public BrowserTab createNewTab(String url) {
BrowserTab browserTab = new BrowserTab();
Tab tab = new Tab();
// Create tab content
VBox tabContent = new VBox(5);
tabContent.setPadding(new Insets(5));
// Navigation bar for this tab
HBox navBar = createTabNavigationBar(browserTab);
// Browser content
BorderPane browserContent = new BorderPane();
browserContent.setTop(navBar);
browserContent.setCenter(browserTab.getBrowserNode());
tab.setContent(browserContent);
tab.setText("New Tab");
// Set tab close button
Button closeButton = new Button("×");
closeButton.setStyle("-fx-background-color: transparent; -fx-border-color: transparent;");
closeButton.setOnAction(e -> closeTab(tab));
HBox tabHeader = new HBox(5);
tabHeader.getChildren().addAll(new Label("Tab"), closeButton);
tab.setGraphic(tabHeader);
// Add to collections
tabs.add(browserTab);
tabMap.put(tab, browserTab);
tabPane.getTabs().add(tab);
// Select the new tab
tabPane.getSelectionModel().select(tab);
// Load initial URL
if (url != null) {
browserTab.loadURL(url);
}
return browserTab;
}
private HBox createTabNavigationBar(BrowserTab browserTab) {
TextField addressBar = new TextField();
addressBar.setPromptText("Enter URL...");
addressBar.setPrefWidth(400);
Button goButton = new Button("Go");
Button backButton = new Button("←");
Button forwardButton = new Button("→");
Button reloadButton = new Button("↻");
// Event handlers
addressBar.setOnAction(e -> browserTab.loadURL(addressBar.getText()));
goButton.setOnAction(e -> browserTab.loadURL(addressBar.getText()));
backButton.setOnAction(e -> browserTab.goBack());
forwardButton.setOnAction(e -> browserTab.goForward());
reloadButton.setOnAction(e -> browserTab.reload());
// Update address bar when URL changes
browserTab.urlProperty().addListener((obs, oldUrl, newUrl) -> {
addressBar.setText(newUrl);
});
HBox navBar = new HBox(5, backButton, forwardButton, reloadButton, addressBar, goButton);
navBar.setPadding(new Insets(5));
navBar.setStyle("-fx-background-color: #f8f8f8; -fx-border-color: #e0e0e0; -fx-border-width: 0 0 1 0;");
return navBar;
}
private void closeTab(Tab tab) {
BrowserTab browserTab = tabMap.get(tab);
if (browserTab != null) {
browserTab.dispose();
tabs.remove(browserTab);
tabMap.remove(tab);
}
tabPane.getTabs().remove(tab);
}
public BrowserTab getCurrentTab() {
Tab selectedTab = tabPane.getSelectionModel().getSelectedItem();
return selectedTab != null ? tabMap.get(selectedTab) : null;
}
public void closeAllTabs() {
tabs.forEach(BrowserTab::dispose);
tabs.clear();
tabMap.clear();
tabPane.getTabs().clear();
}
}
class BrowserTab {
private CefBrowserWrapper browserWrapper;
private javafx.beans.property.StringProperty urlProperty;
private javafx.beans.property.StringProperty titleProperty;
public BrowserTab() {
this.browserWrapper = CefBrowserWrapper.getInstance();
this.urlProperty = new javafx.beans.property.SimpleStringProperty("");
this.titleProperty = new javafx.beans.property.SimpleStringProperty("New Tab");
// Listen for URL changes
// Note: You would need to extend CefBrowserWrapper to provide URL change notifications
}
public javafx.scene.Node getBrowserNode() {
return browserWrapper.getBrowserNode();
}
public void loadURL(String url) {
browserWrapper.loadURL(url);
urlProperty.set(url);
}
public void goBack() {
browserWrapper.goBack();
}
public void goForward() {
browserWrapper.goForward();
}
public void reload() {
browserWrapper.reload();
}
public void activate() {
// Activate this tab's browser
browserWrapper.resizeBrowser();
}
public void dispose() {
// Clean up resources
// Note: CefBrowserWrapper disposal is handled globally
}
// Property accessors
public javafx.beans.property.StringProperty urlProperty() {
return urlProperty;
}
public javafx.beans.property.StringProperty titleProperty() {
return titleProperty;
}
public String getUrl() {
return urlProperty.get();
}
public String getTitle() {
return titleProperty.get();
}
}
Custom Download Handler
Step 6: File Download Management
package com.example.cef.handlers;
import org.cef.callback.CefBeforeDownloadCallback;
import org.cef.callback.CefDownloadItem;
import org.cef.callback.CefDownloadItemCallback;
import org.cef.handler.CefDownloadHandler;
import org.cef.misc.BoolRef;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Dialog;
import javafx.scene.control.ProgressBar;
import javafx.stage.DirectoryChooser;
import javafx.stage.FileChooser;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
public class CustomDownloadHandler implements CefDownloadHandler {
private ObservableList<DownloadItem> downloadItems;
private Path defaultDownloadDirectory;
public CustomDownloadHandler() {
this.downloadItems = FXCollections.observableArrayList();
this.defaultDownloadDirectory = Paths.get(System.getProperty("user.home"), "Downloads");
}
@Override
public void onBeforeDownload(CefBrowser browser, CefDownloadItem downloadItem,
String suggestedName, CefBeforeDownloadCallback callback) {
// Show download dialog
DownloadDialog dialog = new DownloadDialog(downloadItem, suggestedName);
Optional<DownloadDialog.Result> result = dialog.showAndWait();
if (result.isPresent()) {
DownloadDialog.Result dialogResult = result.get();
if (dialogResult.isCanceled()) {
callback.Cancel();
return;
}
// Set download path
String downloadPath = dialogResult.getFilePath();
callback.Continue(downloadPath, false);
// Add to download list
DownloadItem item = new DownloadItem(downloadItem, suggestedName, downloadPath);
downloadItems.add(item);
} else {
callback.Cancel();
}
}
@Override
public void onDownloadUpdated(CefBrowser browser, CefDownloadItem downloadItem,
CefDownloadItemCallback callback) {
// Find the download item
DownloadItem item = findDownloadItem(downloadItem);
if (item != null) {
item.updateFrom(downloadItem);
if (downloadItem.isComplete()) {
System.out.println("Download completed: " + item.getFileName());
// Show completion notification
showDownloadCompleteAlert(item);
} else if (downloadItem.isCanceled()) {
System.out.println("Download canceled: " + item.getFileName());
}
}
}
private DownloadItem findDownloadItem(CefDownloadItem cefItem) {
return downloadItems.stream()
.filter(item -> item.getId() == cefItem.getId())
.findFirst()
.orElse(null);
}
private void showDownloadCompleteAlert(DownloadItem item) {
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle("Download Complete");
alert.setHeaderText("Download finished successfully");
alert.setContentText("File: " + item.getFileName() +
"\nSaved to: " + item.getFilePath() +
"\nSize: " + formatFileSize(item.getTotalBytes()));
// Add open folder button
ButtonType openFolderButton = new ButtonType("Open Folder");
alert.getButtonTypes().add(openFolderButton);
Optional<ButtonType> result = alert.showAndWait();
if (result.isPresent() && result.get() == openFolderButton) {
openDownloadFolder(item.getFilePath());
}
}
private void openDownloadFolder(String filePath) {
try {
File file = new File(filePath);
File parentDir = file.getParentFile();
if (parentDir.exists()) {
java.awt.Desktop.getDesktop().open(parentDir);
}
} catch (Exception e) {
System.err.println("Failed to open download folder: " + e.getMessage());
}
}
private String formatFileSize(long bytes) {
if (bytes < 1024) return bytes + " B";
if (bytes < 1024 * 1024) return String.format("%.1f KB", bytes / 1024.0);
if (bytes < 1024 * 1024 * 1024) return String.format("%.1f MB", bytes / (1024.0 * 1024.0));
return String.format("%.1f GB", bytes / (1024.0 * 1024.0 * 1024.0));
}
public ObservableList<DownloadItem> getDownloadItems() {
return downloadItems;
}
// Download item class
public static class DownloadItem {
private int id;
private String url;
private String fileName;
private String filePath;
private long currentBytes;
private long totalBytes;
private double progress;
private boolean complete;
private boolean canceled;
public DownloadItem(CefDownloadItem cefItem, String suggestedName, String filePath) {
this.id = cefItem.getId();
this.url = cefItem.getURL();
this.fileName = suggestedName;
this.filePath = filePath;
updateFrom(cefItem);
}
public void updateFrom(CefDownloadItem cefItem) {
this.currentBytes = cefItem.getReceivedBytes();
this.totalBytes = cefItem.getTotalBytes();
this.progress = totalBytes > 0 ? (double) currentBytes / totalBytes : 0.0;
this.complete = cefItem.isComplete();
this.canceled = cefItem.isCanceled();
}
// Getters
public int getId() { return id; }
public String getUrl() { return url; }
public String getFileName() { return fileName; }
public String getFilePath() { return filePath; }
public long getCurrentBytes() { return currentBytes; }
public long getTotalBytes() { return totalBytes; }
public double getProgress() { return progress; }
public boolean isComplete() { return complete; }
public boolean isCanceled() { return canceled; }
}
// Download dialog class
private static class DownloadDialog extends Dialog<DownloadDialog.Result> {
private CefDownloadItem downloadItem;
private String suggestedName;
private TextField fileNameField;
private ComboBox<String> locationComboBox;
private ProgressBar progressBar;
public DownloadDialog(CefDownloadItem downloadItem, String suggestedName) {
this.downloadItem = downloadItem;
this.suggestedName = suggestedName;
setTitle("Download File");
setHeaderText("Save File");
createUI();
setupEventHandlers();
}
private void createUI() {
// Create form
fileNameField = new TextField(suggestedName);
fileNameField.setPrefWidth(300);
locationComboBox = new ComboBox<>();
locationComboBox.getItems().addAll(
Paths.get(System.getProperty("user.home"), "Downloads").toString(),
Paths.get(System.getProperty("user.home"), "Desktop").toString()
);
locationComboBox.getSelectionModel().select(0);
progressBar = new ProgressBar();
progressBar.setPrefWidth(300);
VBox content = new VBox(10,
new Label("File name:"), fileNameField,
new Label("Save to:"), locationComboBox,
new Label("Progress:"), progressBar
);
content.setPadding(new Insets(10));
getDialogPane().setContent(content);
// Add buttons
ButtonType saveButton = new ButtonType("Save", ButtonBar.ButtonData.OK_DONE);
ButtonType cancelButton = new ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE);
getDialogPane().getButtonTypes().addAll(saveButton, cancelButton);
}
private void setupEventHandlers() {
// Browse button for custom location
// This would require adding a browse button to the dialog
}
@Override
public Optional<Result> showAndWait() {
// For simplicity, return a fixed result
// In real implementation, show dialog and wait for user input
String filePath = locationComboBox.getValue() + File.separator + fileNameField.getText();
return Optional.of(new Result(filePath, false));
}
public static class Result {
private String filePath;
private boolean canceled;
public Result(String filePath, boolean canceled) {
this.filePath = filePath;
this.canceled = canceled;
}
public String getFilePath() { return filePath; }
public boolean isCanceled() { return canceled; }
}
}
}
CSS Styling
Step 7: Browser Styling
/* resources/styles/browser.css */
.root {
-fx-font-family: "Segoe UI", Arial, sans-serif;
-fx-font-size: 14px;
}
/* Toolbar styling */
.tool-bar {
-fx-background-color: #f8f9fa;
-fx-border-color: #e9ecef;
-fx-border-width: 0 0 1 0;
-fx-padding: 8px;
}
.tool-bar .button {
-fx-background-color: transparent;
-fx-border-color: transparent;
-fx-border-radius: 3px;
-fx-padding: 5px 8px;
}
.tool-bar .button:hover {
-fx-background-color: #e9ecef;
}
.tool-bar .button:pressed {
-fx-background-color: #dee2e6;
}
.tool-bar .text-field {
-fx-background-color: white;
-fx-border-color: #ced4da;
-fx-border-radius: 20px;
-fx-padding: 5px 15px;
}
.tool-bar .text-field:focused {
-fx-border-color: #007bff;
-fx-effect: dropshadow(gaussian, rgba(0,123,255,0.25), 10, 0, 0, 0);
}
/* Tab pane styling */
.tab-pane {
-fx-background-color: #f8f9fa;
}
.tab-pane .tab-header-area {
-fx-background-color: #f8f9fa;
}
.tab-pane .tab {
-fx-background-color: #e9ecef;
-fx-background-insets: 0 1px 0 0, 1px 2px 0 1px;
-fx-background-radius: 5px 5px 0 0;
-fx-padding: 5px 15px;
}
.tab-pane .tab:selected {
-fx-background-color: white;
-fx-border-color: #dee2e6;
-fx-border-width: 1px 1px 0 1px;
}
.tab-pane .tab .tab-label {
-fx-text-fill: #495057;
-fx-font-weight: normal;
}
.tab-pane .tab:selected .tab-label {
-fx-text-fill: #007bff;
-fx-font-weight: bold;
}
/* Status bar styling */
.status-bar {
-fx-background-color: #f8f9fa;
-fx-border-color: #dee2e6;
-fx-border-width: 1px 0 0 0;
-fx-padding: 5px 10px;
}
.progress-bar {
-fx-accent: #007bff;
}
.progress-bar .track {
-fx-background-color: #e9ecef;
-fx-background-radius: 5px;
}
.progress-bar .bar {
-fx-background-color: #007bff;
-fx-background-radius: 5px;
}
/* Alert and dialog styling */
.dialog-pane {
-fx-background-color: white;
}
.dialog-pane .header-panel {
-fx-background-color: #f8f9fa;
}
.dialog-pane .button {
-fx-background-color: #007bff;
-fx-text-fill: white;
-fx-border-radius: 3px;
-fx-padding: 8px 16px;
}
.dialog-pane .button:hover {
-fx-background-color: #0056b3;
}
/* Download manager styling */
.download-item {
-fx-border-color: #dee2e6;
-fx-border-width: 0 0 1 0;
-fx-padding: 10px;
}
.download-item.complete {
-fx-background-color: #d4edda;
}
.download-item.failed {
-fx-background-color: #f8d7da;
}
Key Features and Benefits
Core Features:
- Full Chromium Browser: Complete web browsing capabilities
- JavaScript Execution: Run JavaScript from Java and vice versa
- Custom Download Handling: Manage file downloads with progress tracking
- Tabbed Browsing: Multiple browser tabs in a single application
- DevTools Integration: Access Chrome Developer Tools
Advanced Features:
- Java-JavaScript Bridge: Bidirectional communication between Java and web content
- Custom Context Menus: Tailored right-click menus
- Progress Tracking: Monitor page loading and downloads
- Navigation Controls: Back, forward, reload, and home buttons
- URL Validation: Automatic URL formatting and search fallback
Integration Benefits:
- Native JavaFX Integration: Seamless blending with JavaFX UI components
- Modern Web Standards: Support for HTML5, CSS3, and modern JavaScript
- Performance: Hardware-accelerated rendering
- Security: Regular Chromium security updates
- Extensibility: Custom handlers for various browser events
This comprehensive CEF integration provides a robust foundation for building modern, web-enabled JavaFX applications with full browser capabilities.