feat(browser): 添加泛型支持和浏览器组件功能
- 为 BaseBrowserBuilder 添加泛型参数 R 以支持不同返回类型 - 新增 BrowserLog 日志工具类,提供格式化输出和异常处理 - 实现 BrowserPanel 组件,支持嵌入到 Swing 容器中 - 更新 BrowserCore 支持 Component 父容器参数 - 替换 System.out.println 为 BrowserLog 日志记录 - 修改 Builder 构建方法返回泛型 R 类型 - 添加 JsBridgeController attach 容器绑定功能 - 优化弹窗创建时的父组件尺寸获取逻辑
This commit is contained in:
@@ -5,8 +5,9 @@ import com.axis.innovators.box.events.BrowserCreationCallback;
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
|
||||
// 增加泛型参数 R,表示 build 方法返回的类型
|
||||
@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 title = "JCEF Window";
|
||||
protected Dimension size = new Dimension(800, 600);
|
||||
@@ -20,13 +21,13 @@ public abstract class BaseBrowserBuilder<T extends BaseBrowserBuilder<T>> {
|
||||
protected boolean openLinksInExternalBrowser = true;
|
||||
protected BrowserCreationCallback browserCreationCallback;
|
||||
protected JsBridgeController controller;
|
||||
|
||||
public BaseBrowserBuilder(String windowId) {
|
||||
this.windowId = windowId;
|
||||
}
|
||||
|
||||
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 operationHandler(WindowOperationHandler handler) { this.operationHandler = handler; 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; }
|
||||
@@ -36,6 +37,6 @@ public abstract class BaseBrowserBuilder<T extends BaseBrowserBuilder<T>> {
|
||||
public T openLinksInBrowser(boolean openInBrowser) { this.openLinksInExternalBrowser = !openInBrowser; return (T) this; }
|
||||
public T setBrowserCreationCallback(BrowserCreationCallback callback) { this.browserCreationCallback = callback; return (T) this; }
|
||||
|
||||
// 抽象构建方法
|
||||
public abstract Window build();
|
||||
// 抽象构建方法返回泛型 R
|
||||
public abstract R build();
|
||||
}
|
||||
@@ -25,7 +25,9 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.util.Queue;
|
||||
import java.util.Vector;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.cef.callback.CefMenuModel.MenuId.MENU_ID_USER_FIRST;
|
||||
@@ -41,15 +43,24 @@ public class BrowserCore {
|
||||
private CefClient client;
|
||||
private CefBrowser browser;
|
||||
private CefMessageRouter msgRouter;
|
||||
private final BaseBrowserBuilder<?> config;
|
||||
private final java.util.Queue<String> pendingScripts = new java.util.concurrent.ConcurrentLinkedQueue<>();
|
||||
private final BaseBrowserBuilder<?,?> config;
|
||||
private final Queue<String> pendingScripts = new ConcurrentLinkedQueue<>();
|
||||
private volatile boolean isPageLoaded = false;
|
||||
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.windowId = windowId;
|
||||
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 {
|
||||
@@ -76,10 +87,10 @@ public class BrowserCore {
|
||||
String targetUrl;
|
||||
if (config.htmlUrl != null && !config.htmlUrl.isEmpty()) {
|
||||
targetUrl = config.htmlUrl;
|
||||
System.out.println("Loading URL: " + targetUrl);
|
||||
BrowserLog.info("Loading URL: {}", targetUrl);
|
||||
} else if (config.htmlPath != null && !config.htmlPath.isEmpty()) {
|
||||
targetUrl = new File(config.htmlPath).toURI().toURL().toString();
|
||||
System.out.println("Loading File: " + targetUrl);
|
||||
BrowserLog.info("Loading File: {}", targetUrl);
|
||||
} else {
|
||||
throw new IllegalArgumentException("URL or HTML path must be provided");
|
||||
}
|
||||
@@ -102,7 +113,7 @@ public class BrowserCore {
|
||||
if (controller != null) {
|
||||
controller.attach(this);
|
||||
if (browser != null && isPageLoaded) {
|
||||
System.out.println("🔄 [BrowserCore] 动态更新 JSBridge 控制器,正在重新注入 JS...");
|
||||
BrowserLog.bridge(windowId, "update", "{}");
|
||||
String bridgeScript = controller.generateInjectionJs();
|
||||
browser.executeJavaScript(bridgeScript, browser.getURL(), 0);
|
||||
}
|
||||
@@ -132,14 +143,14 @@ public class BrowserCore {
|
||||
// 只有主框架加载完毕,且没有发生严重错误,才执行脚本
|
||||
// 只有主框架加载完毕才注入
|
||||
if (frame.isMain()) {
|
||||
System.out.println("✅ [BrowserCore] 页面加载完成 (Code: " + httpStatusCode + ")");
|
||||
BrowserLog.info("✅ [BrowserCore] 页面加载完成 (Code: {})",httpStatusCode);
|
||||
isPageLoaded = true;
|
||||
if (jsController != null) {
|
||||
System.out.println("💉 [BrowserCore] 正在注入 JSBridge 代码...");
|
||||
BrowserLog.bridge(windowId, "update", "{}");
|
||||
String bridgeScript = jsController.generateInjectionJs();
|
||||
frame.executeJavaScript(bridgeScript, frame.getURL(), 0);
|
||||
} else {
|
||||
System.err.println("⚠️ [BrowserCore] jsController 为空,未注入 JS 对象");
|
||||
BrowserLog.bridge(windowId, "update", "{}");
|
||||
}
|
||||
while (!pendingScripts.isEmpty()) {
|
||||
String script = pendingScripts.poll();
|
||||
@@ -147,7 +158,7 @@ public class BrowserCore {
|
||||
try {
|
||||
frame.executeJavaScript(script, frame.getURL(), 0);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
BrowserLog.error("❌ [BrowserCore] 脚本执行失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -157,9 +168,7 @@ public class BrowserCore {
|
||||
@Override
|
||||
public void onLoadError(CefBrowser browser, CefFrame frame, ErrorCode errorCode, String errorText, String failedUrl) {
|
||||
if (frame.isMain()) {
|
||||
System.err.println("❌ [BrowserCore] 页面加载失败: " + errorText);
|
||||
// 加载失败通常不应该执行脚本,或者你可以选择清空队列
|
||||
// pendingScripts.clear();
|
||||
BrowserLog.error("❌ [BrowserCore] 错误码: {}", errorCode);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -172,24 +181,27 @@ public class BrowserCore {
|
||||
client.addDisplayHandler(new CefDisplayHandlerAdapter() {
|
||||
@Override
|
||||
public boolean onConsoleMessage(CefBrowser browser, CefSettings.LogSeverity level, String message, String source, int line) {
|
||||
String symbol = switch (level) {
|
||||
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);
|
||||
BrowserLog.console(windowId, level.name(), source, line, message);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTitleChange(CefBrowser browser, String title) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
if (parentWindow != null) {
|
||||
if (parentWindow instanceof JFrame) {
|
||||
((JFrame) 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,
|
||||
CefCallback callback) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
int option = JOptionPane.showConfirmDialog(parentWindow,
|
||||
int option = 0;
|
||||
if (parentWindow != null) {
|
||||
option = JOptionPane.showConfirmDialog(parentWindow,
|
||||
"证书错误: " + cert_error + "\n是否继续访问?",
|
||||
"安全警告",
|
||||
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) {
|
||||
// JCEF 122 中 CefCallback.Continue() 无参数,表示"继续/忽略错误"
|
||||
callback.Continue();
|
||||
@@ -301,7 +321,12 @@ public class BrowserCore {
|
||||
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) {
|
||||
Vector<String> filePaths = new Vector<>();
|
||||
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);
|
||||
}
|
||||
} 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) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
if (dialog_type == JSDialogType.JSDIALOGTYPE_ALERT) {
|
||||
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, "");
|
||||
} 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, "");
|
||||
} 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);
|
||||
else callback.Continue(false, "");
|
||||
}
|
||||
@@ -400,14 +439,14 @@ public class BrowserCore {
|
||||
CefDownloadItem downloadItem,
|
||||
CefDownloadItemCallback callback) {
|
||||
if (downloadItem.isComplete()) {
|
||||
System.out.println("下载完成: " + downloadItem.getFullPath());
|
||||
BrowserLog.debug("下载完成: {}" , downloadItem.getFullPath());
|
||||
} else if (downloadItem.isCanceled()) {
|
||||
System.out.println("下载取消");
|
||||
BrowserLog.debug("下载取消: {}" , downloadItem.getFullPath());
|
||||
} else {
|
||||
// 获取进度百分比
|
||||
int percent = downloadItem.getPercentComplete();
|
||||
if (percent % 10 == 0) { // 减少日志输出频率
|
||||
System.out.println("下载中: " + percent + "%");
|
||||
if (percent % 10 == 0) {
|
||||
BrowserLog.debug("下载中: {}" , downloadItem.getFullPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -430,11 +469,19 @@ public class BrowserCore {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
String popupWindowId = windowId + "_popup_" + System.currentTimeMillis();
|
||||
WindowRegistry.getInstance().createNewWindow(popupWindowId, popupBuilder -> {
|
||||
if (parentWindow != null) {
|
||||
popupBuilder.title("Popup") // 可以优化为获取页面标题
|
||||
.size(parentWindow.getWidth(), parentWindow.getHeight())
|
||||
.htmlUrl(targetUrl)
|
||||
.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) {
|
||||
popupBuilder.operationHandler(config.operationHandler);
|
||||
}
|
||||
@@ -474,8 +521,7 @@ public class BrowserCore {
|
||||
handler.handleOperation(new WindowOperation(operation, targetWindow, callback));
|
||||
return true;
|
||||
} else {
|
||||
// 如果没有 handler,但这又是一个 system 请求,则报错或忽略
|
||||
System.err.println("收到 system 请求但 handler 为空: " + request);
|
||||
BrowserLog.warn("收到 system 请求但 handler 为空: {}" , request);
|
||||
callback.failure(404, "No handler for system operation");
|
||||
return true;
|
||||
}
|
||||
@@ -505,7 +551,7 @@ public class BrowserCore {
|
||||
private void injectJsBridge() {
|
||||
if (jsController != null && browser != null) {
|
||||
String script = jsController.generateInjectionJs();
|
||||
System.out.println("💉 [Injecting JS Bridge]");
|
||||
BrowserLog.warn("💉 [Injecting JS Bridge]");
|
||||
browser.executeJavaScript(script, browser.getURL(), 0);
|
||||
}
|
||||
}
|
||||
@@ -514,7 +560,7 @@ public class BrowserCore {
|
||||
try {
|
||||
Desktop.getDesktop().browse(new URI(url));
|
||||
} catch (Exception e) {
|
||||
System.err.println("外部浏览器打开失败: " + e.getMessage());
|
||||
BrowserLog.error("外部浏览器打开失败: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -598,13 +644,12 @@ public class BrowserCore {
|
||||
// 场景A:页面已经加载好了,直接执行
|
||||
try {
|
||||
browser.getMainFrame().executeJavaScript(script, browser.getMainFrame().getURL(), 0);
|
||||
System.out.println("⚡ [直接执行] " + script);
|
||||
BrowserLog.debug("⚡ [直接执行] {}" , script);
|
||||
} catch (Exception e) {
|
||||
System.err.println("执行JS异常: " + e.getMessage());
|
||||
BrowserLog.error("执行JS异常: " , e);
|
||||
}
|
||||
} else {
|
||||
// 场景B:页面还没好,加入队列,等待 onLoadEnd 自动处理
|
||||
System.out.println("⏳ [加入队列] 页面未就绪: " + (script.length() > 20 ? script.substring(0, 20) + "..." : script));
|
||||
BrowserLog.debug("⏳ [加入队列] 页面未就绪: {}" , (script.length() > 20 ? script.substring(0, 20) + "..." : script));
|
||||
pendingScripts.add(script);
|
||||
}
|
||||
});
|
||||
|
||||
112
src/main/java/com/axis/innovators/box/browser/BrowserLog.java
Normal file
112
src/main/java/com/axis/innovators/box/browser/BrowserLog.java
Normal 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);
|
||||
}
|
||||
}
|
||||
114
src/main/java/com/axis/innovators/box/browser/BrowserPanel.java
Normal file
114
src/main/java/com/axis/innovators/box/browser/BrowserPanel.java
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ public class BrowserWindow extends JFrame implements BrowserContainer {
|
||||
private final BrowserCore browserCore;
|
||||
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); }
|
||||
|
||||
@Override
|
||||
@@ -23,7 +23,7 @@ public class BrowserWindow extends JFrame implements BrowserContainer {
|
||||
|
||||
private void validatePaths() {
|
||||
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);
|
||||
|
||||
if (builder.controller != null) {
|
||||
builder.controller.attach( this);
|
||||
browserCore.setJsBridgeController(builder.controller);
|
||||
}
|
||||
|
||||
@@ -144,6 +145,7 @@ public class BrowserWindow extends JFrame implements BrowserContainer {
|
||||
|
||||
@Override
|
||||
public void setController(JsBridgeController controller) {
|
||||
controller.attach( this);
|
||||
browserCore.setJsBridgeController(controller);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ public class BrowserWindowJDialog extends JDialog implements BrowserContainer {
|
||||
private final BrowserCore browserCore;
|
||||
private final String windowId;
|
||||
|
||||
public static class Builder extends BaseBrowserBuilder<Builder> {
|
||||
public static class Builder extends BaseBrowserBuilder<Builder,Window> {
|
||||
private JFrame parentFrame;
|
||||
|
||||
public Builder(String windowId) { super(windowId); }
|
||||
@@ -25,7 +25,7 @@ public class BrowserWindowJDialog extends JDialog implements BrowserContainer {
|
||||
@Override
|
||||
public BrowserWindowJDialog build() {
|
||||
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);
|
||||
}
|
||||
@@ -39,6 +39,7 @@ public class BrowserWindowJDialog extends JDialog implements BrowserContainer {
|
||||
this.browserCore = new BrowserCore(this, windowId, builder);
|
||||
|
||||
if (builder.controller != null) {
|
||||
builder.controller.attach(this);
|
||||
browserCore.setJsBridgeController(builder.controller);
|
||||
}
|
||||
|
||||
@@ -141,6 +142,7 @@ public class BrowserWindowJDialog extends JDialog implements BrowserContainer {
|
||||
|
||||
@Override
|
||||
public void setController(JsBridgeController controller) {
|
||||
controller.attach(this);
|
||||
browserCore.setJsBridgeController(controller);
|
||||
}
|
||||
|
||||
|
||||
@@ -84,9 +84,6 @@ public class CefAppManager {
|
||||
String processType,
|
||||
CefCommandLine commandLine
|
||||
) {
|
||||
//commandLine.appendSwitch("disable-dev-tools");
|
||||
//commandLine.appendSwitch("disable-view-source");
|
||||
|
||||
LanguageManager.loadSavedLanguage();
|
||||
LanguageManager.Language currentLang = LanguageManager.getLoadedLanguages();
|
||||
if (currentLang != null){
|
||||
@@ -98,7 +95,6 @@ public class CefAppManager {
|
||||
commandLine.appendSwitchWithValue("--lang", langCode);
|
||||
commandLine.appendSwitchWithValue("--accept-language", langCode);
|
||||
}
|
||||
|
||||
boolean isDarkTheme = isDarkTheme();
|
||||
if (isDarkTheme) {
|
||||
commandLine.appendSwitch("force-dark-mode");
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.axis.innovators.box.browser.bridge;
|
||||
|
||||
import com.axis.innovators.box.browser.BrowserContainer;
|
||||
import com.axis.innovators.box.browser.BrowserCore;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
@@ -21,6 +22,7 @@ public abstract class JsBridgeController {
|
||||
private final Map<String, Method> methodRegistry = new HashMap<>();
|
||||
private final Gson gson = new Gson();
|
||||
private BrowserCore browserCore;
|
||||
private BrowserContainer container;
|
||||
|
||||
public JsBridgeController() {
|
||||
scanMethods();
|
||||
@@ -33,6 +35,13 @@ public abstract class JsBridgeController {
|
||||
this.browserCore = core;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定 BrowserContainer,用于执行回调或JS注入
|
||||
*/
|
||||
public void attach(BrowserContainer container) {
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描子类中所有带 @JsMapping 的方法
|
||||
*/
|
||||
@@ -166,4 +175,8 @@ public abstract class JsBridgeController {
|
||||
protected BrowserCore getBrowserCore() {
|
||||
return browserCore;
|
||||
}
|
||||
|
||||
protected BrowserContainer getBrowserContainer() {
|
||||
return container;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
# Auto-generated build information
|
||||
version=0.0.1
|
||||
buildTimestamp=2026-01-03T08:33:49.8039508
|
||||
buildTimestamp=2026-01-03T09:20:12.6386031
|
||||
buildSystem=WINDOWS
|
||||
|
||||
Reference in New Issue
Block a user