- 新增 BrowserWindow 类,支持通过 Builder 模式创建可定制的浏览器窗口 - 新增 BrowserWindowJDialog 类,继承自 JDialog,用于创建模态或非模态浏览器对话框 - 实现基于 CEF 的浏览器组件加载与生命周期管理 - 支持自定义上下文菜单、键盘事件(如 F12 开发者工具)、JS 对话框拦截 - 提供链接打开方式配置(在当前窗口或外部浏览器中打开) - 集成消息路由机制,支持前端与后端通信 - 支持主题与字体信息注入至网页端 - 添加资源自动释放逻辑,防止内存泄漏 - 增加对粘贴板操作的支持(复制/粘贴文本)
834 lines
34 KiB
Java
834 lines
34 KiB
Java
package com.chuangzhou.vivid2D.browser;
|
||
|
||
import com.axis.innovators.box.AxisInnovatorsBox;
|
||
import com.axis.innovators.box.events.BrowserCreationCallback;
|
||
import com.google.gson.Gson;
|
||
import com.google.gson.JsonObject;
|
||
import org.cef.CefApp;
|
||
import org.cef.CefClient;
|
||
import org.cef.CefSettings;
|
||
import org.cef.browser.CefBrowser;
|
||
import org.cef.browser.CefFrame;
|
||
import org.cef.browser.CefMessageRouter;
|
||
import org.cef.callback.CefContextMenuParams;
|
||
import org.cef.callback.CefJSDialogCallback;
|
||
import org.cef.callback.CefMenuModel;
|
||
import org.cef.callback.CefQueryCallback;
|
||
import org.cef.handler.*;
|
||
import org.cef.misc.BoolRef;
|
||
import org.cef.network.CefRequest;
|
||
|
||
import javax.swing.*;
|
||
import java.awt.*;
|
||
import java.awt.datatransfer.Clipboard;
|
||
import java.awt.datatransfer.DataFlavor;
|
||
import java.awt.datatransfer.UnsupportedFlavorException;
|
||
import java.awt.event.*;
|
||
import java.io.File;
|
||
import java.io.IOException;
|
||
import java.net.MalformedURLException;
|
||
import java.net.URI;
|
||
import java.util.function.Consumer;
|
||
|
||
import static org.cef.callback.CefMenuModel.MenuId.MENU_ID_USER_FIRST;
|
||
|
||
/**
|
||
* @author tzdwindows 7
|
||
*/
|
||
public class BrowserWindowJDialog extends JDialog {
|
||
private final String windowId;
|
||
private final String htmlUrl;
|
||
private CefApp cefApp;
|
||
private CefClient client;
|
||
private CefBrowser browser;
|
||
private final Component browserComponent;
|
||
private final String htmlPath;
|
||
//private boolean isInitialized = false;
|
||
private WindowOperationHandler operationHandler;
|
||
private static Thread cefThread;
|
||
private CefMessageRouter msgRouter;
|
||
|
||
public static class Builder {
|
||
private String windowId;
|
||
private String title = "JCEF Window";
|
||
private Dimension size = new Dimension(800, 600);
|
||
private WindowOperationHandler operationHandler;
|
||
private String htmlPath;
|
||
private Image icon;
|
||
private JFrame parentFrame;
|
||
private boolean resizable = true; // 默认允许调整大小
|
||
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;
|
||
return this;
|
||
}
|
||
|
||
public Builder maximizable(boolean maximizable) {
|
||
this.maximizable = maximizable;
|
||
return this;
|
||
}
|
||
|
||
public Builder minimizable(boolean minimizable) {
|
||
this.minimizable = minimizable;
|
||
return this;
|
||
}
|
||
|
||
public Builder(String windowId) {
|
||
this.windowId = windowId;
|
||
}
|
||
|
||
/**
|
||
* 设置浏览器窗口标题
|
||
* @param title 标题
|
||
*/
|
||
public Builder title(String title) {
|
||
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;
|
||
}
|
||
|
||
/**
|
||
* 设置浏览器窗口大小
|
||
* @param width 宽度
|
||
* @param height 高度
|
||
*/
|
||
public Builder size(int width, int height) {
|
||
this.size = new Dimension(width, height);
|
||
return this;
|
||
}
|
||
|
||
/**
|
||
* 设置浏览器窗口父窗口
|
||
* @param parent 父窗口
|
||
*/
|
||
public Builder parentFrame(JFrame parent) {
|
||
this.parentFrame = parent;
|
||
return this;
|
||
}
|
||
|
||
|
||
/**
|
||
* 设置浏览器触发事件
|
||
* @param handler 事件处理器
|
||
*/
|
||
public Builder operationHandler(WindowOperationHandler handler) {
|
||
this.operationHandler = handler;
|
||
return this;
|
||
}
|
||
|
||
/**
|
||
* 设置浏览器图标
|
||
* @param icon 图标
|
||
*/
|
||
public Builder icon(Image icon) {
|
||
this.icon = icon;
|
||
return this;
|
||
}
|
||
|
||
/**
|
||
* 设置HTML路径
|
||
*/
|
||
public BrowserWindowJDialog build() {
|
||
if (htmlUrl.isEmpty()) {
|
||
if (this.htmlPath == null || this.htmlPath.isEmpty()) {
|
||
throw new IllegalArgumentException("HTML paths cannot be empty");
|
||
}
|
||
File htmlFile = new File(this.htmlPath);
|
||
if (!htmlFile.exists()) {
|
||
throw new RuntimeException("The HTML file does not exist: " + htmlFile.getAbsolutePath());
|
||
}
|
||
}
|
||
return new BrowserWindowJDialog(this);
|
||
}
|
||
|
||
/**
|
||
* 设置HTML路径
|
||
* @param path HTML路径
|
||
*/
|
||
public Builder htmlPath(String path) {
|
||
this.htmlPath = path;
|
||
return this;
|
||
}
|
||
|
||
/**
|
||
* 使用Url
|
||
* @param htmlUrl Url路径
|
||
*/
|
||
public Builder htmlUrl(String htmlUrl) {
|
||
this.htmlUrl = htmlUrl;
|
||
return this;
|
||
}
|
||
}
|
||
|
||
|
||
private BrowserWindowJDialog(Builder builder) {
|
||
// 根据父窗口是否存在,设置是否为模态对话框
|
||
super(builder.parentFrame, builder.title, builder.parentFrame != null);
|
||
this.windowId = builder.windowId;
|
||
this.htmlPath = builder.htmlPath;
|
||
this.htmlUrl = builder.htmlUrl;
|
||
this.operationHandler = builder.operationHandler;
|
||
|
||
// 设置图标(如果存在)
|
||
if (builder.icon != null) {
|
||
setIconImage(builder.icon);
|
||
}
|
||
|
||
// 初始化浏览器组件
|
||
try {
|
||
this.browserComponent = initializeCef(builder);
|
||
if (operationHandler != null) {
|
||
setupMessageHandlers(operationHandler);
|
||
}
|
||
} catch (Exception e) {
|
||
JOptionPane.showMessageDialog(this, "初始化失败: " + e.getMessage());
|
||
throw new RuntimeException(e);
|
||
}
|
||
}
|
||
private static boolean isInitialized = false;
|
||
private Component initializeCef(Builder builder) throws MalformedURLException {
|
||
if (!isInitialized) {
|
||
isInitialized = true;
|
||
try {
|
||
this.cefApp = CefAppManager.getInstance();
|
||
//CefAppManager.incrementBrowserCount();
|
||
client = cefApp.createClient();
|
||
client.addDisplayHandler(new CefDisplayHandler (){
|
||
@Override
|
||
public void onAddressChange(CefBrowser browser, CefFrame frame, String url) {}
|
||
|
||
@Override
|
||
public void onTitleChange(CefBrowser browser, String title) {}
|
||
|
||
@Override
|
||
public void OnFullscreenModeChange(CefBrowser browser, boolean fullscreen) {}
|
||
|
||
@Override
|
||
public boolean onTooltip(CefBrowser browser, String text) {
|
||
return false;
|
||
}
|
||
|
||
@Override
|
||
public void onStatusMessage(CefBrowser browser, String value) {}
|
||
|
||
@Override
|
||
public boolean onConsoleMessage(
|
||
CefBrowser browser,
|
||
CefSettings.LogSeverity level,
|
||
String message,
|
||
String source,
|
||
int line
|
||
) {
|
||
// 格式化输出到 Java 控制台
|
||
//if (level != CefSettings.LogSeverity.LOGSEVERITY_WARNING) {
|
||
String log = String.format(
|
||
"[Browser Console] %s %s (Line %d) -> %s",
|
||
getLogLevelSymbol(level),
|
||
source,
|
||
line,
|
||
message
|
||
);
|
||
System.out.println(log);
|
||
//}
|
||
return false;
|
||
}
|
||
|
||
@Override
|
||
public boolean onCursorChange(CefBrowser browser, int cursorType) {
|
||
return false;
|
||
}
|
||
|
||
private String getLogLevelSymbol(CefSettings.LogSeverity level) {
|
||
switch (level) {
|
||
case LOGSEVERITY_ERROR: return "⛔";
|
||
case LOGSEVERITY_WARNING: return "⚠️";
|
||
case LOGSEVERITY_DEFAULT: return "🐞";
|
||
default: return "ℹ️";
|
||
}
|
||
}
|
||
});
|
||
|
||
if (AxisInnovatorsBox.getMain() != null && AxisInnovatorsBox.getMain().isDebugEnvironment()) {
|
||
client.addKeyboardHandler(new CefKeyboardHandlerAdapter() {
|
||
@Override
|
||
public boolean onKeyEvent(CefBrowser browser, CefKeyEvent event) {
|
||
// 检测 F12
|
||
if (event.windows_key_code == 123) {
|
||
browser.getDevTools().createImmediately();
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
});
|
||
}
|
||
|
||
client.addLifeSpanHandler(new CefLifeSpanHandlerAdapter() {
|
||
@Override
|
||
public boolean onBeforePopup(CefBrowser browser, CefFrame frame,
|
||
String targetUrl, String targetFrameName) {
|
||
// 处理弹出窗口:根据配置决定打开方式
|
||
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; // 拦截弹窗并在当前窗口打开
|
||
}
|
||
}
|
||
});
|
||
|
||
client.addRequestHandler(new CefRequestHandlerAdapter() {
|
||
@Override
|
||
public boolean onBeforeBrowse(CefBrowser browser, CefFrame frame,
|
||
CefRequest request, boolean userGesture, boolean isRedirect) {
|
||
// 处理主窗口导航
|
||
if (userGesture) {
|
||
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;
|
||
}
|
||
});
|
||
|
||
client.addContextMenuHandler(new CefContextMenuHandlerAdapter() {
|
||
@Override
|
||
public void onBeforeContextMenu(CefBrowser browser, CefFrame frame,
|
||
CefContextMenuParams params, CefMenuModel model) {
|
||
model.clear();
|
||
if (!params.getSelectionText().isEmpty() || params.isEditable()) {
|
||
model.addItem(MENU_ID_USER_FIRST, "复制");
|
||
}
|
||
|
||
if (params.isEditable()) {
|
||
model.addItem(MENU_ID_USER_FIRST + 1, "粘贴");
|
||
model.addItem(MENU_ID_USER_FIRST + 2, "粘贴纯文本");
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public boolean onContextMenuCommand(CefBrowser browser, CefFrame frame,
|
||
CefContextMenuParams params, int commandId, int eventFlags) {
|
||
if (commandId == MENU_ID_USER_FIRST) {
|
||
if (params.isEditable()) {
|
||
browser.executeJavaScript("document.execCommand('copy');", browser.getURL(), 0);
|
||
} else {
|
||
browser.executeJavaScript(
|
||
"window.getSelection().toString();",
|
||
browser.getURL(),
|
||
0
|
||
);
|
||
}
|
||
return true;
|
||
} else if (commandId == MENU_ID_USER_FIRST + 1) {
|
||
pasteContent(browser, false);
|
||
return true;
|
||
} else if (commandId == MENU_ID_USER_FIRST + 2) {
|
||
pasteContent(browser, true);
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* 处理粘贴操作
|
||
* @param plainText 是否去除格式(纯文本模式)
|
||
*/
|
||
private void pasteContent(CefBrowser browser, boolean plainText) {
|
||
try {
|
||
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
|
||
if (clipboard.isDataFlavorAvailable(DataFlavor.stringFlavor)) {
|
||
String text = (String) clipboard.getData(DataFlavor.stringFlavor);
|
||
|
||
if (plainText) {
|
||
text = text.replaceAll("<[^>]+>", "");
|
||
}
|
||
|
||
String escapedText = text
|
||
.replace("\\", "\\\\")
|
||
.replace("'", "\\'")
|
||
.replace("\n", "\\n")
|
||
.replace("\r", "\\r");
|
||
|
||
String script = String.format(
|
||
"if (document.activeElement) {\n" +
|
||
" document.activeElement.value += '%s';\n" + // 简单追加文本
|
||
" document.dispatchEvent(new Event('input', { bubbles: true }));\n" + // 触发输入事件
|
||
"}",
|
||
escapedText
|
||
);
|
||
browser.executeJavaScript(script, browser.getURL(), 0);
|
||
}
|
||
} catch (UnsupportedFlavorException | IOException e) {
|
||
e.printStackTrace();
|
||
}
|
||
}
|
||
});
|
||
|
||
// 添加 alert 弹窗监控处理
|
||
client.addJSDialogHandler(new CefJSDialogHandlerAdapter() {
|
||
@Override
|
||
public boolean onJSDialog(CefBrowser browser, String origin_url, JSDialogType dialog_type, String message_text, String default_prompt_text, CefJSDialogCallback callback, BoolRef suppress_message) {
|
||
if (dialog_type == JSDialogType.JSDIALOGTYPE_ALERT) {
|
||
SwingUtilities.invokeLater(() -> {
|
||
JOptionPane.showMessageDialog(
|
||
BrowserWindowJDialog.this,
|
||
message_text,
|
||
"警告",
|
||
JOptionPane.INFORMATION_MESSAGE
|
||
);
|
||
});
|
||
callback.Continue(true, "");
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
});
|
||
|
||
|
||
|
||
// 3. 拦截所有新窗口(关键修复点!)
|
||
//client.addLifeSpanHandler(new CefLifeSpanHandlerAdapter() {
|
||
// @Override
|
||
// public boolean onBeforePopup(CefBrowser browser,
|
||
// CefFrame frame, String target_url, String target_frame_name) {
|
||
// return true; // 返回true表示拦截弹窗
|
||
// }
|
||
//});
|
||
|
||
|
||
Thread.currentThread().setName("BrowserRenderThread");
|
||
|
||
|
||
// 4. 加载HTML
|
||
if (htmlUrl.isEmpty()) {
|
||
String fileUrl = new File(htmlPath).toURI().toURL().toString();
|
||
System.out.println("Loading HTML from: " + fileUrl);
|
||
|
||
// 5. 创建浏览器组件(直接添加到内容面板)
|
||
browser = client.createBrowser(fileUrl, false, false);
|
||
} else {
|
||
System.out.println("Loading HTML from: " + htmlUrl);
|
||
browser = client.createBrowser(htmlUrl, false, false);
|
||
}
|
||
|
||
Component browserComponent = browser.getUIComponent();
|
||
|
||
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; // 直接返回,跳过默认布局
|
||
}
|
||
}
|
||
|
||
updateTheme();
|
||
|
||
CefMessageRouter.CefMessageRouterConfig config = new CefMessageRouter.CefMessageRouterConfig();
|
||
config.jsQueryFunction = "javaQuery";// 定义方法
|
||
config.jsCancelFunction = "javaQueryCancel";// 定义取消方法
|
||
|
||
|
||
// 6. 配置窗口布局(确保只添加一次)
|
||
SwingUtilities.invokeLater(() -> {
|
||
getContentPane().removeAll();
|
||
getContentPane().setLayout(new BorderLayout());
|
||
|
||
// 透明拖拽层(仅顶部可拖拽)
|
||
JPanel dragPanel = new JPanel(new BorderLayout());
|
||
dragPanel.setOpaque(false);
|
||
|
||
JPanel titleBar = new JPanel();
|
||
titleBar.setOpaque(false);
|
||
titleBar.setPreferredSize(new Dimension(builder.size.width, 20));
|
||
final Point[] dragStart = new Point[1];
|
||
titleBar.addMouseListener(new MouseAdapter() {
|
||
@Override
|
||
public void mousePressed(MouseEvent e) {
|
||
dragStart[0] = e.getPoint();
|
||
}
|
||
|
||
@Override
|
||
public void mouseReleased(MouseEvent e) {
|
||
dragStart[0] = null;
|
||
}
|
||
});
|
||
titleBar.addMouseMotionListener(new MouseMotionAdapter() {
|
||
@Override
|
||
public void mouseDragged(MouseEvent e) {
|
||
if (dragStart[0] != null) {
|
||
Point curr = e.getLocationOnScreen();
|
||
setLocation(curr.x - dragStart[0].x, curr.y - dragStart[0].y);
|
||
}
|
||
}
|
||
});
|
||
|
||
|
||
|
||
dragPanel.add(titleBar, BorderLayout.NORTH);
|
||
getContentPane().add(dragPanel, BorderLayout.CENTER);
|
||
getContentPane().add(browserComponent, BorderLayout.CENTER);
|
||
|
||
// 7. 窗口属性设置
|
||
setTitle(builder.title);
|
||
setSize(builder.size);
|
||
setLocationRelativeTo(builder.parentFrame);
|
||
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
|
||
|
||
// 8. 资源释放
|
||
addWindowListener(new WindowAdapter() {
|
||
@Override
|
||
public void windowClosed(WindowEvent e) {
|
||
browser.close(true);
|
||
client.dispose();
|
||
}
|
||
});
|
||
|
||
setVisible(true);
|
||
|
||
});
|
||
return browserComponent;
|
||
} catch (Exception e) {
|
||
e.printStackTrace();
|
||
JOptionPane.showMessageDialog(null, "初始化失败: " + e.getMessage(), "错误", JOptionPane.ERROR_MESSAGE);
|
||
}
|
||
} else {
|
||
isInitialized = false;
|
||
SwingUtilities.invokeLater(() -> {
|
||
dispose();
|
||
});
|
||
}
|
||
return null;
|
||
}
|
||
|
||
|
||
|
||
/**
|
||
* 更新主题
|
||
*/
|
||
public void updateTheme() {
|
||
// 1. 获取Java字体信息
|
||
String fontInfo = getSystemFontsInfo();
|
||
injectFontInfoToPage(browser, fontInfo);
|
||
|
||
// 2. 注入主题信息
|
||
if (AxisInnovatorsBox.getMain() != null) {
|
||
boolean isDarkTheme = AxisInnovatorsBox.getMain().getRegistrationTopic().isDarkMode();
|
||
injectThemeInfoToPage(browser, isDarkTheme);
|
||
}
|
||
|
||
//// 3. 刷新浏览器
|
||
//SwingUtilities.invokeLater(() -> {
|
||
// browser.reload();
|
||
//});
|
||
|
||
}
|
||
private void injectThemeInfoToPage(CefBrowser browser, boolean isDarkTheme) {
|
||
if (client == null) {
|
||
return;
|
||
}
|
||
|
||
client.addLoadHandler(new CefLoadHandlerAdapter() {
|
||
@Override
|
||
public void onLoadEnd(CefBrowser browser, CefFrame frame, int httpStatusCode) {
|
||
String themeInfo = String.format(
|
||
"{\"isDarkTheme\": %s, \"timestamp\": %d}",
|
||
isDarkTheme,
|
||
System.currentTimeMillis()
|
||
);
|
||
|
||
String script =
|
||
"window.javaThemeInfo = " + themeInfo + ";\n" +
|
||
"console.log('Java theme information has been loaded:', window.javaThemeInfo);\n" +
|
||
"\n" +
|
||
"if (typeof applyJavaTheme === 'function') {\n" +
|
||
" applyJavaTheme(window.javaThemeInfo);\n" +
|
||
"}\n" +
|
||
"\n" +
|
||
"var event = new CustomEvent('javaThemeChanged', {\n" +
|
||
" detail: window.javaThemeInfo\n" +
|
||
"});\n" +
|
||
"document.dispatchEvent(event);\n" +
|
||
"console.log('The javaThemeChanged event is dispatched');";
|
||
|
||
browser.executeJavaScript(script, browser.getURL(), 0);
|
||
|
||
browser.executeJavaScript(
|
||
"console.log('Theme information injection is complete,window.javaThemeInfo:', typeof window.javaThemeInfo);" +
|
||
"console.log('Number of theme event listeners:', document.eventListeners ? document.eventListeners('javaThemeChanged') : '无法获取');",
|
||
browser.getURL(), 0
|
||
);
|
||
}
|
||
});
|
||
}
|
||
|
||
|
||
/**
|
||
* 获取Java字体信息(从UIManager获取)
|
||
*/
|
||
private String getSystemFontsInfo() {
|
||
try {
|
||
Gson gson = new Gson();
|
||
JsonObject fontInfo = new JsonObject();
|
||
JsonObject uiFonts = new JsonObject();
|
||
|
||
String[] fontKeys = {
|
||
"Label.font", "Button.font", "ToggleButton.font", "RadioButton.font",
|
||
"CheckBox.font", "ColorChooser.font", "ComboBox.font", "EditorPane.font",
|
||
"TextArea.font", "TextField.font", "PasswordField.font", "TextPane.font",
|
||
"FormattedTextField.font", "Table.font", "TableHeader.font", "List.font",
|
||
"Tree.font", "TabbedPane.font", "MenuBar.font", "Menu.font", "MenuItem.font",
|
||
"PopupMenu.font", "CheckBoxMenuItem.font", "RadioButtonMenuItem.font",
|
||
"Spinner.font", "ToolBar.font", "TitledBorder.font", "OptionPane.messageFont",
|
||
"OptionPane.buttonFont", "Panel.font", "Viewport.font", "ToolTip.font"
|
||
};
|
||
|
||
for (String key : fontKeys) {
|
||
Font font = UIManager.getFont(key);
|
||
if (font != null) {
|
||
JsonObject fontObj = new JsonObject();
|
||
fontObj.addProperty("name", font.getFontName());
|
||
fontObj.addProperty("family", font.getFamily());
|
||
fontObj.addProperty("size", font.getSize());
|
||
fontObj.addProperty("style", font.getStyle());
|
||
fontObj.addProperty("bold", font.isBold());
|
||
fontObj.addProperty("italic", font.isItalic());
|
||
fontObj.addProperty("plain", font.isPlain());
|
||
uiFonts.add(key, fontObj);
|
||
}
|
||
}
|
||
|
||
fontInfo.add("uiFonts", uiFonts);
|
||
fontInfo.addProperty("timestamp", System.currentTimeMillis());
|
||
fontInfo.addProperty("lookAndFeel", UIManager.getLookAndFeel().getName());
|
||
|
||
return gson.toJson(fontInfo);
|
||
} catch (Exception e) {
|
||
return "{\"error\": \"无法获取UIManager字体信息: " + e.getMessage() + "\"}";
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 注入字体信息到页面并设置字体
|
||
*/
|
||
private void injectFontInfoToPage(CefBrowser browser, String fontInfo) {
|
||
if (client == null) {
|
||
return;
|
||
}
|
||
client.addLoadHandler(new CefLoadHandlerAdapter() {
|
||
@Override
|
||
public void onLoadEnd(CefBrowser browser, CefFrame frame, int httpStatusCode) {
|
||
// 使用更简单的脚本来注入字体信息
|
||
String script =
|
||
"if (typeof window.javaFontInfo === 'undefined') {" +
|
||
" window.javaFontInfo = " + fontInfo + ";" +
|
||
" console.log('Java font information has been loaded:', window.javaFontInfo);" +
|
||
" " +
|
||
" var event = new CustomEvent('javaFontsLoaded', {" +
|
||
" detail: window.javaFontInfo" +
|
||
" });" +
|
||
" document.dispatchEvent(event);" +
|
||
" console.log('The javaFontsLoaded event is dispatched');" +
|
||
"}";
|
||
|
||
System.out.println("正在注入字体信息到页面...");
|
||
browser.executeJavaScript(script, browser.getURL(), 0);
|
||
|
||
// 添加调试信息
|
||
browser.executeJavaScript(
|
||
"console.log('Font information injection is complete,window.javaFontInfo:', typeof window.javaFontInfo);" +
|
||
"console.log('Number of event listeners:', document.eventListeners ? document.eventListeners('javaFontsLoaded') : '无法获取');",
|
||
browser.getURL(), 0
|
||
);
|
||
}
|
||
});
|
||
|
||
}
|
||
|
||
public static void printStackTrace() {
|
||
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||
for (int i = 2; i < stackTrace.length; i++) {
|
||
StackTraceElement element = stackTrace[i];
|
||
System.out.println(element.getClassName() + "." + element.getMethodName() +
|
||
"(" + (element.getFileName() != null ? element.getFileName() : "Unknown Source") +
|
||
":" + element.getLineNumber() + ")");
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public void setVisible(boolean b) {
|
||
if (b) {
|
||
if (browser != null) {
|
||
updateTheme();
|
||
}
|
||
}
|
||
super.setVisible(b);
|
||
}
|
||
|
||
public Component getBrowserComponent() {
|
||
return browserComponent;
|
||
}
|
||
|
||
private void setupMessageHandlers(WindowOperationHandler handler) {
|
||
if (client != null) {
|
||
msgRouter = CefMessageRouter.create();
|
||
msgRouter.addHandler(new CefMessageRouterHandlerAdapter() {
|
||
@Override
|
||
public boolean onQuery(CefBrowser browser,
|
||
CefFrame frame,
|
||
long queryId,
|
||
String request,
|
||
boolean persistent,
|
||
CefQueryCallback callback) {
|
||
if (request.startsWith("system:")) {
|
||
String[] parts = request.split(":");
|
||
String operation = parts.length >= 2 ? parts[1] : null;
|
||
String targetWindow = parts.length > 2 ? parts[2] : null;
|
||
handler.handleOperation(
|
||
new WindowOperation(operation, targetWindow, callback) // [!code ++]
|
||
);
|
||
return true;
|
||
}
|
||
if (request.startsWith("java-response:")) {
|
||
String[] parts = request.split(":");
|
||
String requestId = parts[1];
|
||
String responseData = parts.length > 2 ? parts[2] : "";
|
||
Consumer<String> handler = WindowRegistry.getInstance().getCallback(requestId);
|
||
if (handler != null) {
|
||
handler.accept(responseData);
|
||
callback.success("");
|
||
} else {
|
||
callback.failure(-1, "无效的请求ID");
|
||
}
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
}, true);
|
||
client.addMessageRouter(msgRouter);
|
||
}
|
||
}
|
||
|
||
public String getWindowId() {
|
||
return windowId;
|
||
}
|
||
|
||
/**
|
||
* 获取消息路由器
|
||
* @return 消息路由器
|
||
*/
|
||
public CefMessageRouter getMsgRouter() {
|
||
return msgRouter;
|
||
}
|
||
|
||
/**
|
||
* 获取浏览器对象
|
||
* @return 浏览器对象
|
||
*/
|
||
public CefBrowser getBrowser() {
|
||
return browser;
|
||
}
|
||
|
||
public void closeWindow() {
|
||
SwingUtilities.invokeLater(() -> {
|
||
if (browser != null) {
|
||
browser.close(true);
|
||
}
|
||
dispose();
|
||
cefApp.dispose();
|
||
WindowRegistry.getInstance().unregisterWindow(windowId);
|
||
});
|
||
}
|
||
}
|
||
|