feat(browser): 添加自定义浏览器创建回调和链接打开方式设置

- 新增 BrowserCreationCallback 接口,用于自定义浏览器布局
- 在 Builder 中添加 setBrowserCreationCallback 方法设置回调
- 增加 openLinksInBrowser 方法设置链接打开方式
- 修改 onBeforePopup 和 onBeforeBrowse 方法以支持新设置
- 优化 CefAppManager 中的语言设置和暗黑模式配置
- 更新 MainApplication 中的浏览器窗口创建方式
This commit is contained in:
tzdwindows 7
2025-08-17 15:57:46 +08:00
parent be88f3829a
commit 20567a6211
5 changed files with 262 additions and 48 deletions

View File

@@ -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";// 定义方法

View File

@@ -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";// 定义方法

View File

@@ -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 {

View File

@@ -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())
);

View File

@@ -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
);
}