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; package com.axis.innovators.box.browser;
import com.axis.innovators.box.events.BrowserCreationCallback;
import org.cef.CefApp; import org.cef.CefApp;
import org.cef.CefClient; import org.cef.CefClient;
import org.cef.CefSettings; import org.cef.CefSettings;
@@ -43,6 +44,7 @@ public class BrowserWindow extends JFrame {
private CefMessageRouter msgRouter; private CefMessageRouter msgRouter;
public static class Builder { public static class Builder {
private BrowserCreationCallback browserCreationCallback;
private String windowId; private String windowId;
private String title = "JCEF Window"; private String title = "JCEF Window";
private Dimension size = new Dimension(800, 600); private Dimension size = new Dimension(800, 600);
@@ -53,7 +55,7 @@ public class BrowserWindow extends JFrame {
private boolean maximizable = true; // 默认允许最大化 private boolean maximizable = true; // 默认允许最大化
private boolean minimizable = true; // 默认允许最小化 private boolean minimizable = true; // 默认允许最小化
private String htmlUrl = ""; private String htmlUrl = "";
private boolean openLinksInExternalBrowser = true; // 默认使用外部浏览器
public Builder resizable(boolean resizable) { public Builder resizable(boolean resizable) {
this.resizable = resizable; this.resizable = resizable;
@@ -65,6 +67,44 @@ public class BrowserWindow extends JFrame {
return this; 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) { public Builder minimizable(boolean minimizable) {
this.minimizable = minimizable; this.minimizable = minimizable;
return this; return this;
@@ -74,6 +114,15 @@ public class BrowserWindow extends JFrame {
this.windowId = windowId; this.windowId = windowId;
} }
/**
* 设置浏览器创建回调
* @param callback 回调
*/
public Builder setBrowserCreationCallback(BrowserCreationCallback callback){
this.browserCreationCallback = callback;
return this;
}
/** /**
* 设置浏览器窗口标题 * 设置浏览器窗口标题
* @param title 标题 * @param title 标题
@@ -244,12 +293,20 @@ public class BrowserWindow extends JFrame {
@Override @Override
public boolean onBeforePopup(CefBrowser browser, CefFrame frame, public boolean onBeforePopup(CefBrowser browser, CefFrame frame,
String targetUrl, String targetFrameName) { String targetUrl, String targetFrameName) {
try { // 处理弹出窗口:根据配置决定打开方式
Desktop.getDesktop().browse(new URI(targetUrl)); if (builder.openLinksInExternalBrowser) {
} catch (Exception e) { // 使用默认浏览器打开
System.out.println("Failed to open external browser: " + e.getMessage()); 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 @Override
public boolean onBeforeBrowse(CefBrowser browser, CefFrame frame, public boolean onBeforeBrowse(CefBrowser browser, CefFrame frame,
CefRequest request, boolean userGesture, boolean isRedirect) { CefRequest request, boolean userGesture, boolean isRedirect) {
// 处理主窗口导航
if (userGesture) { if (userGesture) {
try { if (builder.openLinksInExternalBrowser) {
Desktop.getDesktop().browse(new URI(request.getURL())); // 使用默认浏览器打开
return true; // 取消内置浏览器导航 try {
} catch (Exception e) { Desktop.getDesktop().browse(new URI(request.getURL()));
System.out.println("Failed to open external browser: " + e.getMessage()); return true; // 取消内置浏览器导航
} catch (Exception e) {
System.out.println("Failed to open external browser: " + e.getMessage());
}
} else {
// 允许在当前浏览器中打开
return false;
} }
} }
return false; return false;
@@ -371,7 +435,34 @@ public class BrowserWindow extends JFrame {
} }
Component browserComponent = browser.getUIComponent(); 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(); CefMessageRouter.CefMessageRouterConfig config = new CefMessageRouter.CefMessageRouterConfig();
config.jsQueryFunction = "javaQuery";// 定义方法 config.jsQueryFunction = "javaQuery";// 定义方法

View File

@@ -1,5 +1,6 @@
package com.axis.innovators.box.browser; package com.axis.innovators.box.browser;
import com.axis.innovators.box.events.BrowserCreationCallback;
import org.cef.CefApp; import org.cef.CefApp;
import org.cef.CefClient; import org.cef.CefClient;
import org.cef.CefSettings; import org.cef.CefSettings;
@@ -54,7 +55,8 @@ public class BrowserWindowJDialog extends JDialog {
private boolean maximizable = true; // 默认允许最大化 private boolean maximizable = true; // 默认允许最大化
private boolean minimizable = true; // 默认允许最小化 private boolean minimizable = true; // 默认允许最小化
private String htmlUrl = ""; private String htmlUrl = "";
private BrowserCreationCallback browserCreationCallback;
private boolean openLinksInExternalBrowser = true; // 默认使用外部浏览器
public Builder resizable(boolean resizable) { public Builder resizable(boolean resizable) {
this.resizable = resizable; this.resizable = resizable;
@@ -83,6 +85,53 @@ public class BrowserWindowJDialog extends JDialog {
this.title = title; this.title = title;
return this; 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) { private BrowserWindowJDialog(Builder builder) {
// 根据父窗口是否存在,设置是否为模态对话框 // 根据父窗口是否存在,设置是否为模态对话框
super(builder.parentFrame, builder.title, builder.parentFrame != null); super(builder.parentFrame, builder.title, builder.parentFrame != null);
@@ -191,19 +241,13 @@ public class BrowserWindowJDialog extends JDialog {
client = cefApp.createClient(); client = cefApp.createClient();
client.addDisplayHandler(new CefDisplayHandler (){ client.addDisplayHandler(new CefDisplayHandler (){
@Override @Override
public void onAddressChange(CefBrowser browser, CefFrame frame, String url) { public void onAddressChange(CefBrowser browser, CefFrame frame, String url) {}
}
@Override @Override
public void onTitleChange(CefBrowser browser, String title) { public void onTitleChange(CefBrowser browser, String title) {}
}
@Override @Override
public void OnFullscreenModeChange(CefBrowser browser, boolean fullscreen) { public void OnFullscreenModeChange(CefBrowser browser, boolean fullscreen) {}
}
@Override @Override
public boolean onTooltip(CefBrowser browser, String text) { public boolean onTooltip(CefBrowser browser, String text) {
@@ -211,9 +255,7 @@ public class BrowserWindowJDialog extends JDialog {
} }
@Override @Override
public void onStatusMessage(CefBrowser browser, String value) { public void onStatusMessage(CefBrowser browser, String value) {}
}
@Override @Override
public boolean onConsoleMessage( public boolean onConsoleMessage(
@@ -256,12 +298,20 @@ public class BrowserWindowJDialog extends JDialog {
@Override @Override
public boolean onBeforePopup(CefBrowser browser, CefFrame frame, public boolean onBeforePopup(CefBrowser browser, CefFrame frame,
String targetUrl, String targetFrameName) { String targetUrl, String targetFrameName) {
try { // 处理弹出窗口:根据配置决定打开方式
Desktop.getDesktop().browse(new URI(targetUrl)); if (builder.openLinksInExternalBrowser) {
} catch (Exception e) { // 使用默认浏览器打开
System.out.println("Failed to open external browser: " + e.getMessage()); 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 @Override
public boolean onBeforeBrowse(CefBrowser browser, CefFrame frame, public boolean onBeforeBrowse(CefBrowser browser, CefFrame frame,
CefRequest request, boolean userGesture, boolean isRedirect) { CefRequest request, boolean userGesture, boolean isRedirect) {
// 处理主窗口导航
if (userGesture) { if (userGesture) {
try { if (builder.openLinksInExternalBrowser) {
Desktop.getDesktop().browse(new URI(request.getURL())); // 使用默认浏览器打开
return true; // 取消内置浏览器导航 try {
} catch (Exception e) { Desktop.getDesktop().browse(new URI(request.getURL()));
System.out.println("Failed to open external browser: " + e.getMessage()); return true; // 取消内置浏览器导航
} catch (Exception e) {
System.out.println("Failed to open external browser: " + e.getMessage());
}
} else {
// 允许在当前浏览器中打开
return false;
} }
} }
return false; return false;
@@ -383,7 +440,36 @@ public class BrowserWindowJDialog extends JDialog {
} }
Component browserComponent = browser.getUIComponent(); 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(); CefMessageRouter.CefMessageRouterConfig config = new CefMessageRouter.CefMessageRouterConfig();
config.jsQueryFunction = "javaQuery";// 定义方法 config.jsQueryFunction = "javaQuery";// 定义方法

View File

@@ -1,6 +1,7 @@
package com.axis.innovators.box.browser; package com.axis.innovators.box.browser;
import com.axis.innovators.box.AxisInnovatorsBox; import com.axis.innovators.box.AxisInnovatorsBox;
import com.axis.innovators.box.register.LanguageManager;
import com.axis.innovators.box.tools.FolderCreator; import com.axis.innovators.box.tools.FolderCreator;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
@@ -72,21 +73,35 @@ public class CefAppManager {
//settings.background_color = new Color(255, 255, 255, 0); //settings.background_color = new Color(255, 255, 255, 0);
settings.command_line_args_disabled = false; settings.command_line_args_disabled = false;
boolean isDarkTheme = isDarkTheme(); // 转换语言标识格式system:zh_CN -> zh-CN
if (isDarkTheme) { CefApp.addAppHandler(new CefAppHandlerAdapter(null) {
CefApp.addAppHandler(new CefAppHandlerAdapter(null) { @Override
@Override public void onBeforeCommandLineProcessing(
public void onBeforeCommandLineProcessing( String processType,
String processType, CefCommandLine commandLine
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.appendSwitch("force-dark-mode");
commandLine.appendSwitchWithValue("enable-features", "WebContentsForceDark"); commandLine.appendSwitchWithValue("enable-features", "WebContentsForceDark");
} }
});
logger.info("Dark theme settings applied"); logger.info("CEF commandLine: {}", commandLine.getSwitches());
} }
});
logger.info("Optimized CEF settings initialized"); logger.info("Optimized CEF settings initialized");
} finally { } finally {

View File

@@ -41,7 +41,8 @@ public class MainApplication {
WindowRegistry.getInstance().createNewWindow("main", builder -> WindowRegistry.getInstance().createNewWindow("main", builder ->
window.set(builder.title("Axis Innovators Box AI 工具箱") window.set(builder.title("Axis Innovators Box AI 工具箱")
.size(1280, 720) .size(1280, 720)
.htmlPath("C:\\Users\\Administrator\\Downloads\\deepseek_html_20250817_d94647.html") .htmlUrl("https://chat.deepseek.com/")
.openLinksInBrowser(true)
.operationHandler(createOperationHandler()) .operationHandler(createOperationHandler())
.build()) .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
);
}