From 20567a6211c9d3d6b67464f966a93a28e6c56cc8 Mon Sep 17 00:00:00 2001 From: tzdwindows 7 <3076584115@qq.com> Date: Sun, 17 Aug 2025 15:57:46 +0800 Subject: [PATCH] =?UTF-8?q?feat(browser):=20=E6=B7=BB=E5=8A=A0=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E6=B5=8F=E8=A7=88=E5=99=A8=E5=88=9B=E5=BB=BA?= =?UTF-8?q?=E5=9B=9E=E8=B0=83=E5=92=8C=E9=93=BE=E6=8E=A5=E6=89=93=E5=BC=80?= =?UTF-8?q?=E6=96=B9=E5=BC=8F=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 BrowserCreationCallback 接口,用于自定义浏览器布局 - 在 Builder 中添加 setBrowserCreationCallback 方法设置回调 - 增加 openLinksInBrowser 方法设置链接打开方式 - 修改 onBeforePopup 和 onBeforeBrowse 方法以支持新设置 - 优化 CefAppManager 中的语言设置和暗黑模式配置 - 更新 MainApplication 中的浏览器窗口创建方式 --- .../innovators/box/browser/BrowserWindow.java | 115 +++++++++++++-- .../box/browser/BrowserWindowJDialog.java | 134 ++++++++++++++---- .../innovators/box/browser/CefAppManager.java | 37 +++-- .../box/browser/MainApplication.java | 3 +- .../box/events/BrowserCreationCallback.java | 21 +++ 5 files changed, 262 insertions(+), 48 deletions(-) create mode 100644 src/main/java/com/axis/innovators/box/events/BrowserCreationCallback.java diff --git a/src/main/java/com/axis/innovators/box/browser/BrowserWindow.java b/src/main/java/com/axis/innovators/box/browser/BrowserWindow.java index da41708..ca267ed 100644 --- a/src/main/java/com/axis/innovators/box/browser/BrowserWindow.java +++ b/src/main/java/com/axis/innovators/box/browser/BrowserWindow.java @@ -1,5 +1,6 @@ package com.axis.innovators.box.browser; +import com.axis.innovators.box.events.BrowserCreationCallback; import org.cef.CefApp; import org.cef.CefClient; import org.cef.CefSettings; @@ -43,6 +44,7 @@ public class BrowserWindow extends JFrame { private CefMessageRouter msgRouter; public static class Builder { + private BrowserCreationCallback browserCreationCallback; private String windowId; private String title = "JCEF Window"; private Dimension size = new Dimension(800, 600); @@ -53,7 +55,7 @@ public class BrowserWindow extends JFrame { private boolean maximizable = true; // 默认允许最大化 private boolean minimizable = true; // 默认允许最小化 private String htmlUrl = ""; - + private boolean openLinksInExternalBrowser = true; // 默认使用外部浏览器 public Builder resizable(boolean resizable) { this.resizable = resizable; @@ -65,6 +67,44 @@ public class BrowserWindow extends JFrame { return this; } + /** + * 设置链接打开方式 + * + * @param openInBrowser 是否在当前浏览器窗口中打开链接 + * true - 在当前浏览器窗口中打开链接(本地跳转) + * false - 使用系统默认浏览器打开链接(外部跳转) + * @return Builder实例,支持链式调用 + * + * @apiNote 此方法控制两种不同的链接打开行为: + * 1. 当设置为true时: + * - 所有链接将在当前CEF浏览器窗口内打开 + * + * 2. 当设置为false时(默认值): + * - 所有链接将在系统默认浏览器中打开 + * - 更安全,避免潜在的安全风险 + * - 适用于简单的信息展示场景 + * + * @implNote 内部实现说明: + * - 实际存储的是反向值(openLinksInExternalBrowser) + * - 这样设置是为了保持与历史版本的兼容性 + * - 方法名使用"openInBrowser"更符合用户直觉 + * + * @example 使用示例: + * // 在当前窗口打开链接 + * new Builder().openLinksInBrowser(true).build(); + * + * // 使用系统浏览器打开链接(默认) + * new Builder().openLinksInBrowser(false).build(); + * + * @see #openLinksInExternalBrowser 内部存储字段 + * @see CefLifeSpanHandler#onBeforePopup 弹窗处理实现 + * @see CefRequestHandler#onBeforeBrowse 导航处理实现 + */ + public Builder openLinksInBrowser(boolean openInBrowser) { + this.openLinksInExternalBrowser = !openInBrowser; + return this; + } + public Builder minimizable(boolean minimizable) { this.minimizable = minimizable; return this; @@ -74,6 +114,15 @@ public class BrowserWindow extends JFrame { this.windowId = windowId; } + /** + * 设置浏览器创建回调 + * @param callback 回调 + */ + public Builder setBrowserCreationCallback(BrowserCreationCallback callback){ + this.browserCreationCallback = callback; + return this; + } + /** * 设置浏览器窗口标题 * @param title 标题 @@ -244,12 +293,20 @@ public class BrowserWindow extends JFrame { @Override public boolean onBeforePopup(CefBrowser browser, CefFrame frame, String targetUrl, String targetFrameName) { - try { - Desktop.getDesktop().browse(new URI(targetUrl)); - } catch (Exception e) { - System.out.println("Failed to open external browser: " + e.getMessage()); + // 处理弹出窗口:根据配置决定打开方式 + if (builder.openLinksInExternalBrowser) { + // 使用默认浏览器打开 + try { + Desktop.getDesktop().browse(new URI(targetUrl)); + } catch (Exception e) { + System.out.println("Failed to open external browser: " + e.getMessage()); + } + return true; // 拦截弹窗 + } else { + // 在当前浏览器中打开 + browser.loadURL(targetUrl); + return true; // 拦截弹窗并在当前窗口打开 } - return true; // 拦截弹窗 } }); @@ -257,12 +314,19 @@ public class BrowserWindow extends JFrame { @Override public boolean onBeforeBrowse(CefBrowser browser, CefFrame frame, CefRequest request, boolean userGesture, boolean isRedirect) { + // 处理主窗口导航 if (userGesture) { - try { - Desktop.getDesktop().browse(new URI(request.getURL())); - return true; // 取消内置浏览器导航 - } catch (Exception e) { - System.out.println("Failed to open external browser: " + e.getMessage()); + if (builder.openLinksInExternalBrowser) { + // 使用默认浏览器打开 + try { + Desktop.getDesktop().browse(new URI(request.getURL())); + return true; // 取消内置浏览器导航 + } catch (Exception e) { + System.out.println("Failed to open external browser: " + e.getMessage()); + } + } else { + // 允许在当前浏览器中打开 + return false; } } return false; @@ -371,7 +435,34 @@ public class BrowserWindow extends JFrame { } Component browserComponent = browser.getUIComponent(); - browser.executeJavaScript("console.log('Java -> HTML 消息测试')",null,2); + if (builder.browserCreationCallback != null) { + boolean handled = builder.browserCreationCallback.onLayoutCustomization( + this, // 当前窗口 + getContentPane(), // 内容面板 + browserComponent, // 浏览器组件 + builder // 构建器对象 + ); + + // 如果回调返回true,跳过默认布局 + if (handled) { + // 设置窗口基本属性 + setTitle(builder.title); + setSize(builder.size); + setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + + // 添加资源释放监听器 + addWindowListener(new WindowAdapter() { + @Override + public void windowClosed(WindowEvent e) { + browser.close(true); + client.dispose(); + } + }); + + setVisible(true); + return browserComponent; // 直接返回,跳过默认布局 + } + } CefMessageRouter.CefMessageRouterConfig config = new CefMessageRouter.CefMessageRouterConfig(); config.jsQueryFunction = "javaQuery";// 定义方法 diff --git a/src/main/java/com/axis/innovators/box/browser/BrowserWindowJDialog.java b/src/main/java/com/axis/innovators/box/browser/BrowserWindowJDialog.java index 9ae3fbd..1e79e98 100644 --- a/src/main/java/com/axis/innovators/box/browser/BrowserWindowJDialog.java +++ b/src/main/java/com/axis/innovators/box/browser/BrowserWindowJDialog.java @@ -1,5 +1,6 @@ package com.axis.innovators.box.browser; +import com.axis.innovators.box.events.BrowserCreationCallback; import org.cef.CefApp; import org.cef.CefClient; import org.cef.CefSettings; @@ -54,7 +55,8 @@ public class BrowserWindowJDialog extends JDialog { private boolean maximizable = true; // 默认允许最大化 private boolean minimizable = true; // 默认允许最小化 private String htmlUrl = ""; - + private BrowserCreationCallback browserCreationCallback; + private boolean openLinksInExternalBrowser = true; // 默认使用外部浏览器 public Builder resizable(boolean resizable) { this.resizable = resizable; @@ -83,6 +85,53 @@ public class BrowserWindowJDialog extends JDialog { this.title = title; return this; } + /** + * 设置链接打开方式 + * + * @param openInBrowser 是否在当前浏览器窗口中打开链接 + * true - 在当前浏览器窗口中打开链接(本地跳转) + * false - 使用系统默认浏览器打开链接(外部跳转) + * @return Builder实例,支持链式调用 + * + * @apiNote 此方法控制两种不同的链接打开行为: + * 1. 当设置为true时: + * - 所有链接将在当前CEF浏览器窗口内打开 + * + * 2. 当设置为false时(默认值): + * - 所有链接将在系统默认浏览器中打开 + * - 更安全,避免潜在的安全风险 + * - 适用于简单的信息展示场景 + * + * @implNote 内部实现说明: + * - 实际存储的是反向值(openLinksInExternalBrowser) + * - 这样设置是为了保持与历史版本的兼容性 + * - 方法名使用"openInBrowser"更符合用户直觉 + * + * @example 使用示例: + * // 在当前窗口打开链接 + * new Builder().openLinksInBrowser(true).build(); + * + * // 使用系统浏览器打开链接(默认) + * new Builder().openLinksInBrowser(false).build(); + * + * @see #openLinksInExternalBrowser 内部存储字段 + * @see CefLifeSpanHandler#onBeforePopup 弹窗处理实现 + * @see CefRequestHandler#onBeforeBrowse 导航处理实现 + */ + public Builder openLinksInBrowser(boolean openInBrowser) { + this.openLinksInExternalBrowser = !openInBrowser; + return this; + } + + + /** + * 设置浏览器创建回调 + * @param callback 回调 + */ + public Builder setBrowserCreationCallback(BrowserCreationCallback callback){ + this.browserCreationCallback = callback; + return this; + } /** * 设置浏览器窗口大小 @@ -157,6 +206,7 @@ public class BrowserWindowJDialog extends JDialog { } } + private BrowserWindowJDialog(Builder builder) { // 根据父窗口是否存在,设置是否为模态对话框 super(builder.parentFrame, builder.title, builder.parentFrame != null); @@ -191,19 +241,13 @@ public class BrowserWindowJDialog extends JDialog { client = cefApp.createClient(); client.addDisplayHandler(new CefDisplayHandler (){ @Override - public void onAddressChange(CefBrowser browser, CefFrame frame, String url) { - - } + public void onAddressChange(CefBrowser browser, CefFrame frame, String url) {} @Override - public void onTitleChange(CefBrowser browser, String title) { - - } + public void onTitleChange(CefBrowser browser, String title) {} @Override - public void OnFullscreenModeChange(CefBrowser browser, boolean fullscreen) { - - } + public void OnFullscreenModeChange(CefBrowser browser, boolean fullscreen) {} @Override public boolean onTooltip(CefBrowser browser, String text) { @@ -211,9 +255,7 @@ public class BrowserWindowJDialog extends JDialog { } @Override - public void onStatusMessage(CefBrowser browser, String value) { - - } + public void onStatusMessage(CefBrowser browser, String value) {} @Override public boolean onConsoleMessage( @@ -256,12 +298,20 @@ public class BrowserWindowJDialog extends JDialog { @Override public boolean onBeforePopup(CefBrowser browser, CefFrame frame, String targetUrl, String targetFrameName) { - try { - Desktop.getDesktop().browse(new URI(targetUrl)); - } catch (Exception e) { - System.out.println("Failed to open external browser: " + e.getMessage()); + // 处理弹出窗口:根据配置决定打开方式 + if (builder.openLinksInExternalBrowser) { + // 使用默认浏览器打开 + try { + Desktop.getDesktop().browse(new URI(targetUrl)); + } catch (Exception e) { + System.out.println("Failed to open external browser: " + e.getMessage()); + } + return true; // 拦截弹窗 + } else { + // 在当前浏览器中打开 + browser.loadURL(targetUrl); + return true; // 拦截弹窗并在当前窗口打开 } - return true; // 拦截弹窗 } }); @@ -269,12 +319,19 @@ public class BrowserWindowJDialog extends JDialog { @Override public boolean onBeforeBrowse(CefBrowser browser, CefFrame frame, CefRequest request, boolean userGesture, boolean isRedirect) { + // 处理主窗口导航 if (userGesture) { - try { - Desktop.getDesktop().browse(new URI(request.getURL())); - return true; // 取消内置浏览器导航 - } catch (Exception e) { - System.out.println("Failed to open external browser: " + e.getMessage()); + if (builder.openLinksInExternalBrowser) { + // 使用默认浏览器打开 + try { + Desktop.getDesktop().browse(new URI(request.getURL())); + return true; // 取消内置浏览器导航 + } catch (Exception e) { + System.out.println("Failed to open external browser: " + e.getMessage()); + } + } else { + // 允许在当前浏览器中打开 + return false; } } return false; @@ -383,7 +440,36 @@ public class BrowserWindowJDialog extends JDialog { } Component browserComponent = browser.getUIComponent(); - browser.executeJavaScript("console.log('Java -> HTML 消息测试')",null,2); + + if (builder.browserCreationCallback != null) { + boolean handled = builder.browserCreationCallback.onLayoutCustomization( + this, // 当前窗口 + getContentPane(), // 内容面板 + browserComponent, // 浏览器组件 + builder // 构建器对象 + ); + + // 如果回调返回true,跳过默认布局 + if (handled) { + // 设置窗口基本属性 + setTitle(builder.title); + setSize(builder.size); + setLocationRelativeTo(builder.parentFrame); + setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + + // 添加资源释放监听器 + addWindowListener(new WindowAdapter() { + @Override + public void windowClosed(WindowEvent e) { + browser.close(true); + client.dispose(); + } + }); + + setVisible(true); + return browserComponent; // 直接返回,跳过默认布局 + } + } CefMessageRouter.CefMessageRouterConfig config = new CefMessageRouter.CefMessageRouterConfig(); config.jsQueryFunction = "javaQuery";// 定义方法 diff --git a/src/main/java/com/axis/innovators/box/browser/CefAppManager.java b/src/main/java/com/axis/innovators/box/browser/CefAppManager.java index 7507578..195ed80 100644 --- a/src/main/java/com/axis/innovators/box/browser/CefAppManager.java +++ b/src/main/java/com/axis/innovators/box/browser/CefAppManager.java @@ -1,6 +1,7 @@ package com.axis.innovators.box.browser; import com.axis.innovators.box.AxisInnovatorsBox; +import com.axis.innovators.box.register.LanguageManager; import com.axis.innovators.box.tools.FolderCreator; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -72,21 +73,35 @@ public class CefAppManager { //settings.background_color = new Color(255, 255, 255, 0); settings.command_line_args_disabled = false; - boolean isDarkTheme = isDarkTheme(); + // 转换语言标识格式:system:zh_CN -> zh-CN - if (isDarkTheme) { - CefApp.addAppHandler(new CefAppHandlerAdapter(null) { - @Override - public void onBeforeCommandLineProcessing( - String processType, - CefCommandLine commandLine - ) { + CefApp.addAppHandler(new CefAppHandlerAdapter(null) { + @Override + public void onBeforeCommandLineProcessing( + String processType, + CefCommandLine commandLine + ) { + LanguageManager.loadSavedLanguage(); + LanguageManager.Language currentLang = LanguageManager.getLoadedLanguages(); + if (currentLang != null){ + String langCode = currentLang.getRegisteredName() + .replace("system:", "") + .replace("_", "-") + .toLowerCase(); + settings.locale = langCode; + commandLine.appendSwitchWithValue("--lang", langCode); + commandLine.appendSwitchWithValue("--accept-language", langCode); + } + + boolean isDarkTheme = isDarkTheme(); + if (isDarkTheme) { commandLine.appendSwitch("force-dark-mode"); commandLine.appendSwitchWithValue("enable-features", "WebContentsForceDark"); } - }); - logger.info("Dark theme settings applied"); - } + + logger.info("CEF commandLine: {}", commandLine.getSwitches()); + } + }); logger.info("Optimized CEF settings initialized"); } finally { diff --git a/src/main/java/com/axis/innovators/box/browser/MainApplication.java b/src/main/java/com/axis/innovators/box/browser/MainApplication.java index 213cdf9..60d200c 100644 --- a/src/main/java/com/axis/innovators/box/browser/MainApplication.java +++ b/src/main/java/com/axis/innovators/box/browser/MainApplication.java @@ -41,7 +41,8 @@ public class MainApplication { WindowRegistry.getInstance().createNewWindow("main", builder -> window.set(builder.title("Axis Innovators Box AI 工具箱") .size(1280, 720) - .htmlPath("C:\\Users\\Administrator\\Downloads\\deepseek_html_20250817_d94647.html") + .htmlUrl("https://chat.deepseek.com/") + .openLinksInBrowser(true) .operationHandler(createOperationHandler()) .build()) ); diff --git a/src/main/java/com/axis/innovators/box/events/BrowserCreationCallback.java b/src/main/java/com/axis/innovators/box/events/BrowserCreationCallback.java new file mode 100644 index 0000000..8ce45fa --- /dev/null +++ b/src/main/java/com/axis/innovators/box/events/BrowserCreationCallback.java @@ -0,0 +1,21 @@ +package com.axis.innovators.box.events; + +import java.awt.*; + +public interface BrowserCreationCallback { + /** + * 布局自定义回调方法 + * + * @param window 当前浏览器窗口 + * @param contentPane 窗口内容面板 + * @param browserComponent 浏览器UI组件 + * @param builder 构建器对象 + * @return 返回true表示已处理布局,将跳过默认布局;返回false将继续执行默认布局 + */ + boolean onLayoutCustomization( + Window window, + Container contentPane, + Component browserComponent, + Object builder + ); +}