google-site-verification: google61fe8ba583a51912.html
Embedding CEF (Chromium Embedded Framework) in JavaFX: Comprehensive Guide

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:

  1. Full Chromium Browser: Complete web browsing capabilities
  2. JavaScript Execution: Run JavaScript from Java and vice versa
  3. Custom Download Handling: Manage file downloads with progress tracking
  4. Tabbed Browsing: Multiple browser tabs in a single application
  5. DevTools Integration: Access Chrome Developer Tools

Advanced Features:

  1. Java-JavaScript Bridge: Bidirectional communication between Java and web content
  2. Custom Context Menus: Tailored right-click menus
  3. Progress Tracking: Monitor page loading and downloads
  4. Navigation Controls: Back, forward, reload, and home buttons
  5. URL Validation: Automatic URL formatting and search fallback

Integration Benefits:

  1. Native JavaFX Integration: Seamless blending with JavaFX UI components
  2. Modern Web Standards: Support for HTML5, CSS3, and modern JavaScript
  3. Performance: Hardware-accelerated rendering
  4. Security: Regular Chromium security updates
  5. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper