feat(browser): 添加泛型支持和浏览器组件功能

- 为 BaseBrowserBuilder 添加泛型参数 R 以支持不同返回类型
- 新增 BrowserLog 日志工具类,提供格式化输出和异常处理
- 实现 BrowserPanel 组件,支持嵌入到 Swing 容器中
- 更新 BrowserCore 支持 Component 父容器参数
- 替换 System.out.println 为 BrowserLog 日志记录
- 修改 Builder 构建方法返回泛型 R 类型
- 添加 JsBridgeController attach 容器绑定功能
- 优化弹窗创建时的父组件尺寸获取逻辑
This commit is contained in:
2026-01-03 09:22:09 +08:00
parent 7badbb0d8e
commit 7a20c3988f
9 changed files with 351 additions and 66 deletions

View File

@@ -5,8 +5,9 @@ import com.axis.innovators.box.events.BrowserCreationCallback;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
// 增加泛型参数 R表示 build 方法返回的类型
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public abstract class BaseBrowserBuilder<T extends BaseBrowserBuilder<T>> { public abstract class BaseBrowserBuilder<T extends BaseBrowserBuilder<T, R>, R> {
protected String windowId; protected String windowId;
protected String title = "JCEF Window"; protected String title = "JCEF Window";
protected Dimension size = new Dimension(800, 600); protected Dimension size = new Dimension(800, 600);
@@ -20,22 +21,22 @@ public abstract class BaseBrowserBuilder<T extends BaseBrowserBuilder<T>> {
protected boolean openLinksInExternalBrowser = true; protected boolean openLinksInExternalBrowser = true;
protected BrowserCreationCallback browserCreationCallback; protected BrowserCreationCallback browserCreationCallback;
protected JsBridgeController controller; protected JsBridgeController controller;
public BaseBrowserBuilder(String windowId) { public BaseBrowserBuilder(String windowId) {
this.windowId = windowId; this.windowId = windowId;
} }
public T title(String title) { this.title = title; return (T) this; } public T title(String title) { this.title = title; return (T) this; }
public T size(int width, int height) { this.size = new Dimension(width, height); return (T) this; } public T size(int width, int height) { this.size = new Dimension(width, height); return (T) this; }
public T operationHandler(WindowOperationHandler handler) { this.operationHandler = handler; return (T) this; } public T operationHandler(WindowOperationHandler handler) { this.operationHandler = handler; return (T) this; }
public T htmlPath(String path) { this.htmlPath = path; return (T) this; } public T htmlPath(String path) { this.htmlPath = path; return (T) this; }
public T htmlUrl(String url) { this.htmlUrl = url; return (T) this; } public T htmlUrl(String url) { this.htmlUrl = url; return (T) this; }
public T icon(Image icon) { this.icon = icon; return (T) this; } public T icon(Image icon) { this.icon = icon; return (T) this; }
public T resizable(boolean resizable) { this.resizable = resizable; return (T) this; } public T resizable(boolean resizable) { this.resizable = resizable; return (T) this; }
public T controller(JsBridgeController controller) {this.controller = controller;return (T) this;} public T controller(JsBridgeController controller) { this.controller = controller; return (T) this; }
public T openLinksInBrowser(boolean openInBrowser) { this.openLinksInExternalBrowser = !openInBrowser; return (T) this; } public T openLinksInBrowser(boolean openInBrowser) { this.openLinksInExternalBrowser = !openInBrowser; return (T) this; }
public T setBrowserCreationCallback(BrowserCreationCallback callback) { this.browserCreationCallback = callback; return (T) this; } public T setBrowserCreationCallback(BrowserCreationCallback callback) { this.browserCreationCallback = callback; return (T) this; }
// 抽象构建方法 // 抽象构建方法返回泛型 R
public abstract Window build(); public abstract R build();
} }

View File

@@ -25,7 +25,9 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URI; import java.net.URI;
import java.util.Queue;
import java.util.Vector; import java.util.Vector;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.Consumer; import java.util.function.Consumer;
import static org.cef.callback.CefMenuModel.MenuId.MENU_ID_USER_FIRST; import static org.cef.callback.CefMenuModel.MenuId.MENU_ID_USER_FIRST;
@@ -41,15 +43,24 @@ public class BrowserCore {
private CefClient client; private CefClient client;
private CefBrowser browser; private CefBrowser browser;
private CefMessageRouter msgRouter; private CefMessageRouter msgRouter;
private final BaseBrowserBuilder<?> config; private final BaseBrowserBuilder<?,?> config;
private final java.util.Queue<String> pendingScripts = new java.util.concurrent.ConcurrentLinkedQueue<>(); private final Queue<String> pendingScripts = new ConcurrentLinkedQueue<>();
private volatile boolean isPageLoaded = false; private volatile boolean isPageLoaded = false;
private JsBridgeController jsController; private JsBridgeController jsController;
private final Component parentComponent;
public BrowserCore(Window parentWindow, String windowId, BaseBrowserBuilder<?> config) { public BrowserCore(Window parentWindow, String windowId, BaseBrowserBuilder<?,?> config) {
this.parentWindow = parentWindow; this.parentWindow = parentWindow;
this.windowId = windowId; this.windowId = windowId;
this.config = config; this.config = config;
this.parentComponent = null;
}
public BrowserCore(Component parentComponent, String windowId, BaseBrowserBuilder<?,?> config) {
this.parentComponent = parentComponent;
this.windowId = windowId;
this.config = config;
this.parentWindow = null;
} }
public Component initialize() throws MalformedURLException { public Component initialize() throws MalformedURLException {
@@ -76,10 +87,10 @@ public class BrowserCore {
String targetUrl; String targetUrl;
if (config.htmlUrl != null && !config.htmlUrl.isEmpty()) { if (config.htmlUrl != null && !config.htmlUrl.isEmpty()) {
targetUrl = config.htmlUrl; targetUrl = config.htmlUrl;
System.out.println("Loading URL: " + targetUrl); BrowserLog.info("Loading URL: {}", targetUrl);
} else if (config.htmlPath != null && !config.htmlPath.isEmpty()) { } else if (config.htmlPath != null && !config.htmlPath.isEmpty()) {
targetUrl = new File(config.htmlPath).toURI().toURL().toString(); targetUrl = new File(config.htmlPath).toURI().toURL().toString();
System.out.println("Loading File: " + targetUrl); BrowserLog.info("Loading File: {}", targetUrl);
} else { } else {
throw new IllegalArgumentException("URL or HTML path must be provided"); throw new IllegalArgumentException("URL or HTML path must be provided");
} }
@@ -102,7 +113,7 @@ public class BrowserCore {
if (controller != null) { if (controller != null) {
controller.attach(this); controller.attach(this);
if (browser != null && isPageLoaded) { if (browser != null && isPageLoaded) {
System.out.println("🔄 [BrowserCore] 动态更新 JSBridge 控制器,正在重新注入 JS..."); BrowserLog.bridge(windowId, "update", "{}");
String bridgeScript = controller.generateInjectionJs(); String bridgeScript = controller.generateInjectionJs();
browser.executeJavaScript(bridgeScript, browser.getURL(), 0); browser.executeJavaScript(bridgeScript, browser.getURL(), 0);
} }
@@ -132,14 +143,14 @@ public class BrowserCore {
// 只有主框架加载完毕,且没有发生严重错误,才执行脚本 // 只有主框架加载完毕,且没有发生严重错误,才执行脚本
// 只有主框架加载完毕才注入 // 只有主框架加载完毕才注入
if (frame.isMain()) { if (frame.isMain()) {
System.out.println("✅ [BrowserCore] 页面加载完成 (Code: " + httpStatusCode + ")"); BrowserLog.info("✅ [BrowserCore] 页面加载完成 (Code: {})",httpStatusCode);
isPageLoaded = true; isPageLoaded = true;
if (jsController != null) { if (jsController != null) {
System.out.println("💉 [BrowserCore] 正在注入 JSBridge 代码..."); BrowserLog.bridge(windowId, "update", "{}");
String bridgeScript = jsController.generateInjectionJs(); String bridgeScript = jsController.generateInjectionJs();
frame.executeJavaScript(bridgeScript, frame.getURL(), 0); frame.executeJavaScript(bridgeScript, frame.getURL(), 0);
} else { } else {
System.err.println("⚠️ [BrowserCore] jsController 为空,未注入 JS 对象"); BrowserLog.bridge(windowId, "update", "{}");
} }
while (!pendingScripts.isEmpty()) { while (!pendingScripts.isEmpty()) {
String script = pendingScripts.poll(); String script = pendingScripts.poll();
@@ -147,7 +158,7 @@ public class BrowserCore {
try { try {
frame.executeJavaScript(script, frame.getURL(), 0); frame.executeJavaScript(script, frame.getURL(), 0);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); BrowserLog.error("❌ [BrowserCore] 脚本执行失败", e);
} }
} }
} }
@@ -157,9 +168,7 @@ public class BrowserCore {
@Override @Override
public void onLoadError(CefBrowser browser, CefFrame frame, ErrorCode errorCode, String errorText, String failedUrl) { public void onLoadError(CefBrowser browser, CefFrame frame, ErrorCode errorCode, String errorText, String failedUrl) {
if (frame.isMain()) { if (frame.isMain()) {
System.err.println("❌ [BrowserCore] 页面加载失败: " + errorText); BrowserLog.error("❌ [BrowserCore] 错误码: {}", errorCode);
// 加载失败通常不应该执行脚本,或者你可以选择清空队列
// pendingScripts.clear();
} }
} }
}); });
@@ -172,23 +181,26 @@ public class BrowserCore {
client.addDisplayHandler(new CefDisplayHandlerAdapter() { client.addDisplayHandler(new CefDisplayHandlerAdapter() {
@Override @Override
public boolean onConsoleMessage(CefBrowser browser, CefSettings.LogSeverity level, String message, String source, int line) { public boolean onConsoleMessage(CefBrowser browser, CefSettings.LogSeverity level, String message, String source, int line) {
String symbol = switch (level) { BrowserLog.console(windowId, level.name(), source, line, message);
case LOGSEVERITY_ERROR -> "";
case LOGSEVERITY_WARNING -> "⚠️";
case LOGSEVERITY_DEFAULT -> "🐞";
default -> "";
};
System.out.printf("[Browser Console] %s %s (Line %d) -> %s%n", symbol, source, line, message);
return false; return false;
} }
@Override @Override
public void onTitleChange(CefBrowser browser, String title) { public void onTitleChange(CefBrowser browser, String title) {
SwingUtilities.invokeLater(() -> { SwingUtilities.invokeLater(() -> {
if (parentWindow instanceof JFrame) { if (parentWindow != null) {
((JFrame) parentWindow).setTitle(title); if (parentWindow instanceof JFrame) {
} else if (parentWindow instanceof JDialog) { ((JFrame) parentWindow).setTitle(title);
((JDialog) parentWindow).setTitle(title); } else if (parentWindow instanceof JDialog) {
((JDialog) parentWindow).setTitle(title);
}
} else if (parentComponent != null){
Window window = SwingUtilities.getWindowAncestor(parentComponent);
if (window instanceof JFrame) {
((JFrame) window).setTitle(title);
} else if (window instanceof JDialog) {
((JDialog) window).setTitle(title);
}
} }
}); });
} }
@@ -234,12 +246,20 @@ public class BrowserCore {
String request_url, String request_url,
CefCallback callback) { CefCallback callback) {
SwingUtilities.invokeLater(() -> { SwingUtilities.invokeLater(() -> {
int option = JOptionPane.showConfirmDialog(parentWindow, int option = 0;
"证书错误: " + cert_error + "\n是否继续访问", if (parentWindow != null) {
"安全警告", option = JOptionPane.showConfirmDialog(parentWindow,
JOptionPane.YES_NO_OPTION, "证书错误: " + cert_error + "\n是否继续访问",
JOptionPane.WARNING_MESSAGE); "安全警告",
JOptionPane.YES_NO_OPTION,
JOptionPane.WARNING_MESSAGE);
} else if (parentComponent != null) {
option = JOptionPane.showConfirmDialog(parentComponent,
"证书错误: " + cert_error + "\n是否继续访问",
"安全警告",
JOptionPane.YES_NO_OPTION,
JOptionPane.WARNING_MESSAGE);
}
if (option == JOptionPane.YES_OPTION) { if (option == JOptionPane.YES_OPTION) {
// JCEF 122 中 CefCallback.Continue() 无参数,表示"继续/忽略错误" // JCEF 122 中 CefCallback.Continue() 无参数,表示"继续/忽略错误"
callback.Continue(); callback.Continue();
@@ -301,7 +321,12 @@ public class BrowserCore {
fileChooser.setMultiSelectionEnabled(isMulti); fileChooser.setMultiSelectionEnabled(isMulti);
// 打开对话框 // 打开对话框
int result = fileChooser.showOpenDialog(parentWindow); int result = 0;
if (parentWindow != null) {
result = fileChooser.showOpenDialog(parentWindow);
} else if (parentComponent != null) {
result = fileChooser.showOpenDialog(parentComponent);
}
if (result == JFileChooser.APPROVE_OPTION) { if (result == JFileChooser.APPROVE_OPTION) {
Vector<String> filePaths = new Vector<>(); Vector<String> filePaths = new Vector<>();
if (isMulti) { if (isMulti) {
@@ -360,7 +385,7 @@ public class BrowserCore {
browser.executeJavaScript("if (document.activeElement) { document.activeElement.value += '" + escapedText + "'; document.dispatchEvent(new Event('input', { bubbles: true })); }", browser.getURL(), 0); browser.executeJavaScript("if (document.activeElement) { document.activeElement.value += '" + escapedText + "'; document.dispatchEvent(new Event('input', { bubbles: true })); }", browser.getURL(), 0);
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); BrowserLog.error("Failed to paste content: " , e);
} }
} }
}); });
@@ -372,13 +397,27 @@ public class BrowserCore {
public boolean onJSDialog(CefBrowser browser, String origin_url, JSDialogType dialog_type, String message_text, String default_prompt_text, CefJSDialogCallback callback, BoolRef suppress_message) { public boolean onJSDialog(CefBrowser browser, String origin_url, JSDialogType dialog_type, String message_text, String default_prompt_text, CefJSDialogCallback callback, BoolRef suppress_message) {
SwingUtilities.invokeLater(() -> { SwingUtilities.invokeLater(() -> {
if (dialog_type == JSDialogType.JSDIALOGTYPE_ALERT) { if (dialog_type == JSDialogType.JSDIALOGTYPE_ALERT) {
JOptionPane.showMessageDialog(parentWindow, message_text, "警告", JOptionPane.INFORMATION_MESSAGE); if (parentWindow != null) {
JOptionPane.showMessageDialog(parentWindow, message_text, "警告", JOptionPane.INFORMATION_MESSAGE);
} else if (parentComponent != null) {
JOptionPane.showMessageDialog(parentComponent, message_text, "警告", JOptionPane.INFORMATION_MESSAGE);
}
callback.Continue(true, ""); callback.Continue(true, "");
} else if (dialog_type == JSDialogType.JSDIALOGTYPE_CONFIRM) { } else if (dialog_type == JSDialogType.JSDIALOGTYPE_CONFIRM) {
int result = JOptionPane.showConfirmDialog(parentWindow, message_text, "确认", JOptionPane.YES_NO_OPTION); int result = 0;
if (parentWindow != null) {
result = JOptionPane.showConfirmDialog(parentWindow, message_text, "确认", JOptionPane.YES_NO_OPTION);
} else if (parentComponent != null) {
result = JOptionPane.showConfirmDialog(parentComponent, message_text, "确认", JOptionPane.YES_NO_OPTION);
}
callback.Continue(result == JOptionPane.YES_OPTION, ""); callback.Continue(result == JOptionPane.YES_OPTION, "");
} else if (dialog_type == JSDialogType.JSDIALOGTYPE_PROMPT) { } else if (dialog_type == JSDialogType.JSDIALOGTYPE_PROMPT) {
String result = JOptionPane.showInputDialog(parentWindow, message_text, default_prompt_text); String result = null;
if (parentWindow != null) {
result = JOptionPane.showInputDialog(parentWindow, message_text, default_prompt_text);
} else if (parentComponent != null) {
result = JOptionPane.showInputDialog(parentComponent, message_text, default_prompt_text);
}
if (result != null) callback.Continue(true, result); if (result != null) callback.Continue(true, result);
else callback.Continue(false, ""); else callback.Continue(false, "");
} }
@@ -400,14 +439,14 @@ public class BrowserCore {
CefDownloadItem downloadItem, CefDownloadItem downloadItem,
CefDownloadItemCallback callback) { CefDownloadItemCallback callback) {
if (downloadItem.isComplete()) { if (downloadItem.isComplete()) {
System.out.println("下载完成: " + downloadItem.getFullPath()); BrowserLog.debug("下载完成: {}" , downloadItem.getFullPath());
} else if (downloadItem.isCanceled()) { } else if (downloadItem.isCanceled()) {
System.out.println("下载取消"); BrowserLog.debug("下载取消: {}" , downloadItem.getFullPath());
} else { } else {
// 获取进度百分比 // 获取进度百分比
int percent = downloadItem.getPercentComplete(); int percent = downloadItem.getPercentComplete();
if (percent % 10 == 0) { // 减少日志输出频率 if (percent % 10 == 0) {
System.out.println("下载中: " + percent + "%"); BrowserLog.debug("下载中: {}" , downloadItem.getFullPath());
} }
} }
} }
@@ -430,11 +469,19 @@ public class BrowserCore {
SwingUtilities.invokeLater(() -> { SwingUtilities.invokeLater(() -> {
String popupWindowId = windowId + "_popup_" + System.currentTimeMillis(); String popupWindowId = windowId + "_popup_" + System.currentTimeMillis();
WindowRegistry.getInstance().createNewWindow(popupWindowId, popupBuilder -> { WindowRegistry.getInstance().createNewWindow(popupWindowId, popupBuilder -> {
popupBuilder.title("Popup") // 可以优化为获取页面标题 if (parentWindow != null) {
.size(parentWindow.getWidth(), parentWindow.getHeight()) popupBuilder.title("Popup") // 可以优化为获取页面标题
.htmlUrl(targetUrl) .size(parentWindow.getWidth(), parentWindow.getHeight())
.icon(config.icon) .htmlUrl(targetUrl)
.openLinksInBrowser(true); .icon(config.icon)
.openLinksInBrowser(true);
} else if (parentComponent != null) {
popupBuilder.title("Popup") // 可以优化为获取页面标题
.size(parentComponent.getWidth(), parentComponent.getHeight())
.htmlUrl(targetUrl)
.icon(config.icon)
.openLinksInBrowser(true);
}
if (config.operationHandler != null) { if (config.operationHandler != null) {
popupBuilder.operationHandler(config.operationHandler); popupBuilder.operationHandler(config.operationHandler);
} }
@@ -474,8 +521,7 @@ public class BrowserCore {
handler.handleOperation(new WindowOperation(operation, targetWindow, callback)); handler.handleOperation(new WindowOperation(operation, targetWindow, callback));
return true; return true;
} else { } else {
// 如果没有 handler但这又是一个 system 请求,则报错或忽略 BrowserLog.warn("收到 system 请求但 handler 为空: {}" , request);
System.err.println("收到 system 请求但 handler 为空: " + request);
callback.failure(404, "No handler for system operation"); callback.failure(404, "No handler for system operation");
return true; return true;
} }
@@ -505,7 +551,7 @@ public class BrowserCore {
private void injectJsBridge() { private void injectJsBridge() {
if (jsController != null && browser != null) { if (jsController != null && browser != null) {
String script = jsController.generateInjectionJs(); String script = jsController.generateInjectionJs();
System.out.println("💉 [Injecting JS Bridge]"); BrowserLog.warn("💉 [Injecting JS Bridge]");
browser.executeJavaScript(script, browser.getURL(), 0); browser.executeJavaScript(script, browser.getURL(), 0);
} }
} }
@@ -514,7 +560,7 @@ public class BrowserCore {
try { try {
Desktop.getDesktop().browse(new URI(url)); Desktop.getDesktop().browse(new URI(url));
} catch (Exception e) { } catch (Exception e) {
System.err.println("外部浏览器打开失败: " + e.getMessage()); BrowserLog.error("外部浏览器打开失败: ", e);
} }
} }
@@ -598,13 +644,12 @@ public class BrowserCore {
// 场景A页面已经加载好了直接执行 // 场景A页面已经加载好了直接执行
try { try {
browser.getMainFrame().executeJavaScript(script, browser.getMainFrame().getURL(), 0); browser.getMainFrame().executeJavaScript(script, browser.getMainFrame().getURL(), 0);
System.out.println("⚡ [直接执行] " + script); BrowserLog.debug("⚡ [直接执行] {}" , script);
} catch (Exception e) { } catch (Exception e) {
System.err.println("执行JS异常: " + e.getMessage()); BrowserLog.error("执行JS异常: " , e);
} }
} else { } else {
// 场景B页面还没好加入队列等待 onLoadEnd 自动处理 BrowserLog.debug("⏳ [加入队列] 页面未就绪: {}" , (script.length() > 20 ? script.substring(0, 20) + "..." : script));
System.out.println("⏳ [加入队列] 页面未就绪: " + (script.length() > 20 ? script.substring(0, 20) + "..." : script));
pendingScripts.add(script); pendingScripts.add(script);
} }
}); });

View File

@@ -0,0 +1,112 @@
package com.axis.innovators.box.browser;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.spi.ExtendedLogger;
/**
* 浏览器模块专用日志工具类
* 基于 Log4j 2 实现,提供格式化输出和异常处理
*/
public class BrowserLog {
private static final Logger LOGGER = LogManager.getLogger("BrowserSystem");
private static final String FQCN = BrowserLog.class.getName();
private BrowserLog() {}
/**
* 内部核心方法:利用 ExtendedLogger 传入 FQCN 来实现“堆栈剥离”
*/
private static void log(Level level, String format, Object... args) {
if (LOGGER instanceof ExtendedLogger) {
// 参数说明:
// FQCN: 告诉 Log4j 忽略这个类
// level: 级别
// null: Marker
// format / args: 消息内容
// null: Throwable (这里统一由专门的 error 重载处理)
((ExtendedLogger) LOGGER).logIfEnabled(FQCN, level, null, format, args);
} else {
// 兜底逻辑
LOGGER.log(level, format, args);
}
}
public static void trace(String format, Object... args) {
log(Level.TRACE, format, args);
}
public static void debug(String format, Object... args) {
log(Level.DEBUG, format, args);
}
public static void info(String format, Object... args) {
log(Level.INFO, format, args);
}
public static void warn(String format, Object... args) {
log(Level.WARN, format, args);
}
public static void error(String format, Object... args) {
log(Level.ERROR, format, args);
}
/**
* 带异常堆栈的错误日志,同样会剥离本类层级
*/
public static void error(String message, Throwable t) {
if (LOGGER instanceof ExtendedLogger) {
((ExtendedLogger) LOGGER).logIfEnabled(FQCN, Level.ERROR, null, message, t);
} else {
LOGGER.error(message, t);
}
}
// --- 浏览器特定场景优化 ---
/**
* 记录来自浏览器内核JCEF/Chromium的控制台消息
* @param windowId 窗口ID方便追踪是哪个页面
* @param level 日志等级 (INFO, ERR, etc.)
* @param source 源码文件
* @param line 行号
* @param message 消息内容
*/
public static void console(String windowId, String level, String source, int line, String message) {
String formatted = String.format("[%s][Console][%s] (%s:%d) %s",
windowId, level, source, line, message);
// 根据 JS 控制台级别映射到 Log4j 级别
if (level.contains("ERROR")) {
LOGGER.error(formatted);
} else if (level.contains("WARNING")) {
LOGGER.warn(formatted);
} else {
LOGGER.info(formatted);
}
}
/**
* 记录 JSBridge 的调用过程
*/
public static void bridge(String windowId, String action, String data) {
LOGGER.debug("[{}][JSBridge] Action: {} | Data: {}", windowId, action, data);
}
/**
* 记录生命周期事件
*/
public static void lifecycle(String windowId, String event) {
LOGGER.info("[{}][Lifecycle] {}", windowId, event);
}
/**
* 核心初始化日志
*/
public static void core(String message) {
LOGGER.info("[BrowserCore] {}", message);
}
}

View File

@@ -0,0 +1,114 @@
package com.axis.innovators.box.browser;
import com.axis.innovators.box.browser.bridge.JsBridgeController;
import org.cef.browser.CefBrowser;
import org.cef.browser.CefMessageRouter;
import javax.swing.*;
import java.awt.*;
/**
* BrowserPanel 是一个可以嵌入到任何 Swing 容器中的浏览器组件
*/
public class BrowserPanel extends JPanel implements BrowserContainer {
private final BrowserCore browserCore;
private final String windowId;
public static class Builder extends BaseBrowserBuilder<Builder,JPanel> {
public Builder(String windowId) {
super(windowId);
}
@Override
public BrowserPanel build() {
if ((htmlUrl == null || htmlUrl.isEmpty()) && (htmlPath == null || htmlPath.isEmpty())) {
BrowserLog.core("HTML path or URL cannot be empty");
}
return new BrowserPanel(this);
}
}
private BrowserPanel(Builder builder) {
this.windowId = builder.windowId;
this.setLayout(new BorderLayout());
this.browserCore = new BrowserCore(this, windowId, builder);
if (builder.controller != null) {
builder.controller.attach(this);
browserCore.setJsBridgeController(builder.controller);
}
try {
Component browserComponent = browserCore.initialize();
if (builder.browserCreationCallback != null) {
boolean handled = builder.browserCreationCallback.onLayoutCustomization(
null, this, browserComponent, builder
);
if (handled) return;
}
add(browserComponent, BorderLayout.CENTER);
} catch (Exception e) {
e.printStackTrace();
add(new JLabel("浏览器初始化失败: " + e.getMessage()), BorderLayout.CENTER);
BrowserLog.error("浏览器初始化失败: {} " , e.getMessage());
}
}
@Override
public String getWindowId() {
return windowId;
}
@Override
public CefMessageRouter getMsgRouter() {
return browserCore != null ? browserCore.getMsgRouter() : null;
}
@Override
public void executingJsCode(String script) {
if (browserCore != null) {
browserCore.executingJsCode(script);
}
}
@Override
public CefBrowser getBrowser() {
return browserCore != null ? browserCore.getBrowser() : null;
}
@Override
public void closeWindow() {
if (browserCore != null) {
browserCore.dispose();
}
WindowRegistry.getInstance().unregisterWindow(windowId);
Container parent = getParent();
if (parent != null) {
parent.remove(this);
parent.revalidate();
parent.repaint();
}
}
@Override
public void updateTheme() {
if (browserCore != null) {
browserCore.updateTheme();
}
}
@Override
public void setController(JsBridgeController controller) {
controller.attach(this);
browserCore.setJsBridgeController(controller);
}
/**
* 当该组件被销毁时,确保释放浏览器资源
*/
@Override
public void removeNotify() {
super.removeNotify();
}
}

View File

@@ -12,7 +12,7 @@ public class BrowserWindow extends JFrame implements BrowserContainer {
private final BrowserCore browserCore; private final BrowserCore browserCore;
private final String windowId; private final String windowId;
public static class Builder extends BaseBrowserBuilder<Builder> { public static class Builder extends BaseBrowserBuilder<Builder,Window> {
public Builder(String windowId) { super(windowId); } public Builder(String windowId) { super(windowId); }
@Override @Override
@@ -23,7 +23,7 @@ public class BrowserWindow extends JFrame implements BrowserContainer {
private void validatePaths() { private void validatePaths() {
if ((htmlUrl == null || htmlUrl.isEmpty()) && (htmlPath == null || htmlPath.isEmpty())) { if ((htmlUrl == null || htmlUrl.isEmpty()) && (htmlPath == null || htmlPath.isEmpty())) {
throw new IllegalArgumentException("HTML path or URL cannot be empty"); BrowserLog.error("HTML path or URL cannot be empty");
} }
} }
} }
@@ -36,6 +36,7 @@ public class BrowserWindow extends JFrame implements BrowserContainer {
this.browserCore = new BrowserCore(this, windowId, builder); this.browserCore = new BrowserCore(this, windowId, builder);
if (builder.controller != null) { if (builder.controller != null) {
builder.controller.attach( this);
browserCore.setJsBridgeController(builder.controller); browserCore.setJsBridgeController(builder.controller);
} }
@@ -144,6 +145,7 @@ public class BrowserWindow extends JFrame implements BrowserContainer {
@Override @Override
public void setController(JsBridgeController controller) { public void setController(JsBridgeController controller) {
controller.attach( this);
browserCore.setJsBridgeController(controller); browserCore.setJsBridgeController(controller);
} }

View File

@@ -12,7 +12,7 @@ public class BrowserWindowJDialog extends JDialog implements BrowserContainer {
private final BrowserCore browserCore; private final BrowserCore browserCore;
private final String windowId; private final String windowId;
public static class Builder extends BaseBrowserBuilder<Builder> { public static class Builder extends BaseBrowserBuilder<Builder,Window> {
private JFrame parentFrame; private JFrame parentFrame;
public Builder(String windowId) { super(windowId); } public Builder(String windowId) { super(windowId); }
@@ -25,7 +25,7 @@ public class BrowserWindowJDialog extends JDialog implements BrowserContainer {
@Override @Override
public BrowserWindowJDialog build() { public BrowserWindowJDialog build() {
if ((htmlUrl == null || htmlUrl.isEmpty()) && (htmlPath == null || htmlPath.isEmpty())) { if ((htmlUrl == null || htmlUrl.isEmpty()) && (htmlPath == null || htmlPath.isEmpty())) {
throw new IllegalArgumentException("HTML path or URL cannot be empty"); BrowserLog.error("HTML path or URL cannot be empty");
} }
return new BrowserWindowJDialog(this); return new BrowserWindowJDialog(this);
} }
@@ -39,6 +39,7 @@ public class BrowserWindowJDialog extends JDialog implements BrowserContainer {
this.browserCore = new BrowserCore(this, windowId, builder); this.browserCore = new BrowserCore(this, windowId, builder);
if (builder.controller != null) { if (builder.controller != null) {
builder.controller.attach(this);
browserCore.setJsBridgeController(builder.controller); browserCore.setJsBridgeController(builder.controller);
} }
@@ -141,6 +142,7 @@ public class BrowserWindowJDialog extends JDialog implements BrowserContainer {
@Override @Override
public void setController(JsBridgeController controller) { public void setController(JsBridgeController controller) {
controller.attach(this);
browserCore.setJsBridgeController(controller); browserCore.setJsBridgeController(controller);
} }

View File

@@ -84,9 +84,6 @@ public class CefAppManager {
String processType, String processType,
CefCommandLine commandLine CefCommandLine commandLine
) { ) {
//commandLine.appendSwitch("disable-dev-tools");
//commandLine.appendSwitch("disable-view-source");
LanguageManager.loadSavedLanguage(); LanguageManager.loadSavedLanguage();
LanguageManager.Language currentLang = LanguageManager.getLoadedLanguages(); LanguageManager.Language currentLang = LanguageManager.getLoadedLanguages();
if (currentLang != null){ if (currentLang != null){
@@ -98,7 +95,6 @@ public class CefAppManager {
commandLine.appendSwitchWithValue("--lang", langCode); commandLine.appendSwitchWithValue("--lang", langCode);
commandLine.appendSwitchWithValue("--accept-language", langCode); commandLine.appendSwitchWithValue("--accept-language", langCode);
} }
boolean isDarkTheme = isDarkTheme(); boolean isDarkTheme = isDarkTheme();
if (isDarkTheme) { if (isDarkTheme) {
commandLine.appendSwitch("force-dark-mode"); commandLine.appendSwitch("force-dark-mode");

View File

@@ -1,5 +1,6 @@
package com.axis.innovators.box.browser.bridge; package com.axis.innovators.box.browser.bridge;
import com.axis.innovators.box.browser.BrowserContainer;
import com.axis.innovators.box.browser.BrowserCore; import com.axis.innovators.box.browser.BrowserCore;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.JsonArray; import com.google.gson.JsonArray;
@@ -21,6 +22,7 @@ public abstract class JsBridgeController {
private final Map<String, Method> methodRegistry = new HashMap<>(); private final Map<String, Method> methodRegistry = new HashMap<>();
private final Gson gson = new Gson(); private final Gson gson = new Gson();
private BrowserCore browserCore; private BrowserCore browserCore;
private BrowserContainer container;
public JsBridgeController() { public JsBridgeController() {
scanMethods(); scanMethods();
@@ -33,6 +35,13 @@ public abstract class JsBridgeController {
this.browserCore = core; this.browserCore = core;
} }
/**
* 绑定 BrowserContainer用于执行回调或JS注入
*/
public void attach(BrowserContainer container) {
this.container = container;
}
/** /**
* 扫描子类中所有带 @JsMapping 的方法 * 扫描子类中所有带 @JsMapping 的方法
*/ */
@@ -166,4 +175,8 @@ public abstract class JsBridgeController {
protected BrowserCore getBrowserCore() { protected BrowserCore getBrowserCore() {
return browserCore; return browserCore;
} }
protected BrowserContainer getBrowserContainer() {
return container;
}
} }

View File

@@ -1,4 +1,4 @@
# Auto-generated build information # Auto-generated build information
version=0.0.1 version=0.0.1
buildTimestamp=2026-01-03T08:33:49.8039508 buildTimestamp=2026-01-03T09:20:12.6386031
buildSystem=WINDOWS buildSystem=WINDOWS