executeJsObject(Frame frame, String name) {
+ return Optional.ofNullable(frame.executeJavaScript(name));
+ }
+
+ /**
+ * 执行js脚本并返回,使用范围包含{@link JxUIPane#executeJavaScript(String)},可以代替使用
+ *
+ * @param name 变量命名
+ * @return js对象
+ */
+ public Optional
executeJS(String name) {
+ if (browser != null) {
+ Optional frame = browser.mainFrame();
+ if (frame.isPresent()) {
+ return Optional.ofNullable(frame.get().executeJavaScript(name));
+ }
+ }
+ return Optional.empty();
+ }
+
+ /**
+ * 由于自定义scheme目前走的是url,因此路径会被自动转化,比如windows路径下对冒号问题
+ * C:\\abc 变成 /C/abc,这里对冒号进行编码转义
+ */
+ private static String encodeWindowsPath(String path) {
+ if (OperatingSystem.isWindows() && path.startsWith(EMB_TAG + SCHEME_HEADER)) {
+ String s = path.split(EMB_TAG + SCHEME_HEADER)[1];
+ return EMB_TAG + SCHEME_HEADER + s.replace(COLON, COLON_ESCAPE);
+ }
+ return path;
+ }
+
+ /**
+ * JxUIPane 的建造者
+ *
+ * @param 参数
+ */
+ public static class Builder extends ModernUIPane.Builder {
+ private String namespace;
+ private String variable;
+ private String expression;
+ private InjectJsCallback callback;
+
+ private Pair listenerPair;
+ private final Map namespacePropertyMap;
+ private final Map propertyMap;
+
+ private final Map buildPropertyMap;
+ private Object variableProperty;
+ private Map parameterMap;
+ private AssembleComponent component;
+ private String url;
+ private String html;
+
+ public Builder() {
+ // 为了兼容继承关系,但又不允许创建,用这个方式先处理一下
+ super((ModernUIPane) null);
+ this.namespace = DEFAULT_NAMESPACE;
+ this.variable = DEFAULT_VARIABLE;
+ this.expression = DEFAULT_EXPRESSION;
+ this.callback = null;
+ this.listenerPair = null;
+ this.namespacePropertyMap = new HashMap<>();
+ this.propertyMap = new HashMap<>();
+ this.buildPropertyMap = new HashMap<>();
+ this.variableProperty = null;
+ this.parameterMap = null;
+ this.component = null;
+ this.url = StringUtils.EMPTY;
+ this.html = StringUtils.EMPTY;
+ }
+
+ /**
+ * 注入一个回调,回调的js会在初始化进行执行
+ *
+ * @param callback 回调
+ * @return builder
+ */
+ public Builder prepare(InjectJsCallback callback) {
+ this.callback = callback;
+ return this;
+ }
+
+ @Override
+ public Builder prepareForV6(ScriptContextListener contextListener) {
+ return this;
+ }
+
+ @Override
+ public Builder prepareForV6(LoadListener loadListener) {
+ return this;
+ }
+
+ @Override
+ public JxUIPane.Builder prepareForV7(InjectJsCallback callback) {
+ prepare(callback);
+ return this;
+ }
+
+ @Override
+ public JxUIPane.Builder prepareForV7(Class event, Observer listener) {
+ listenerPair = new Pair<>(event, listener);
+ return this;
+ }
+
+ /**
+ * 加载jar包中的资源
+ *
+ * @param path 资源路径
+ */
+ public JxUIPane.Builder withEMB(final String path) {
+ this.url = EMB_TAG + SCHEME_HEADER + path;
+ return this;
+ }
+
+ /**
+ * 加载jar包中的资源
+ *
+ * @param path 资源路径
+ */
+ public JxUIPane.Builder withEMB(final String path, Map map) {
+ this.parameterMap = map;
+ this.url = EMB_TAG + SCHEME_HEADER + path;
+ return this;
+ }
+
+ /**
+ * 加载url指向的资源
+ *
+ * @param url 文件的地址
+ */
+ public JxUIPane.Builder withURL(final String url) {
+ this.url = url;
+ return this;
+ }
+
+ /**
+ * 加载url指向的资源
+ *
+ * @param url 文件的地址
+ */
+ public JxUIPane.Builder withURL(final String url, Map map) {
+ this.parameterMap = map;
+ this.url = url;
+ return this;
+ }
+
+ /**
+ * 加载Atom组件
+ *
+ * @param component Atom组件
+ */
+ public JxUIPane.Builder withComponent(AssembleComponent component) {
+ return withComponent(component, null);
+ }
+
+ /**
+ * 加载Atom组件
+ *
+ * @param component Atom组件
+ */
+ public JxUIPane.Builder withComponent(AssembleComponent component, Map map) {
+ this.parameterMap = map;
+ this.component = component;
+ this.url = COMPONENT_TAG;
+ return this;
+ }
+
+
+ /**
+ * 加载html文本内容
+ *
+ * @param html 要加载html文本内容
+ */
+ public JxUIPane.Builder withHTML(String html) {
+ this.html = html;
+ return this;
+ }
+
+ /**
+ * 设置该前端页面做数据交换所使用的对象
+ * 相当于:
+ * const namespace = "Pool";
+ * 调用:
+ * window[namespace];
+ * 默认下结构如:
+ * window.Pool
+ *
+ * @param namespace 对象名
+ */
+ public JxUIPane.Builder namespace(String namespace) {
+ this.namespace = namespace;
+ return this;
+ }
+
+ /**
+ * java端往js端传数据时使用的变量名字
+ * 默认值为 data
+ * 相当于:
+ * const variable = "data";
+ * 调用:
+ * window[namespace][variable];
+ * 默认下结构如:
+ * window.Pool.data
+ *
+ * @param name 变量的名字
+ */
+ public JxUIPane.Builder variable(String name) {
+ this.variable = name;
+ return this;
+ }
+
+ /**
+ * js端往java端传数据时执行的函数表达式
+ *
+ * @param expression 函数表达式
+ */
+ public JxUIPane.Builder expression(String expression) {
+ this.expression = expression;
+ return this;
+ }
+
+ /**
+ * 注入一个java对象到js中,绑定在全局变量window的指定变量variable。
+ * variable 可由 {@link #namespace(String)} 设置,默认值为 data
+ * 这个方法仅在在加载的网页上执行 JavaScript 之前注入
+ * 相当于:
+ * window[namespace][property] = javaObject
+ * 默认下:
+ * window.Pool[property] = javaObject
+ *
+ * @param obj java对象
+ * @return 链式对象
+ */
+ public JxUIPane.Builder bindNamespace(String property, @Nullable Object obj) {
+ this.namespacePropertyMap.put(property, obj);
+ return this;
+ }
+
+ /**
+ * 注入一个java对象到js中,绑定在全局变量window的指定变量variable。
+ * variable 可由 {@link #variable(String)} 设置,默认值为 data
+ * 这个方法仅在在加载的网页上执行 JavaScript 之前注入
+ * 相当于:
+ * window[namespace][variable] = javaObject
+ * 默认下:
+ * window.Pool.data = javaObject
+ *
+ * @param obj java对象
+ * @return 链式对象
+ */
+ public JxUIPane.Builder bindVariable(@NotNull Object obj) {
+ this.variableProperty = obj;
+ return this;
+ }
+
+ /**
+ * 注入一个java对象到js中,绑定在全局变量 window的
+ * property指定的变量。这个方法仅在在加载的网页上执
+ * 行 JavaScript 之前注入
+ * 相当于:
+ * window[property] = javaObject
+ *
+ * @param property 属性
+ * @param obj java对象
+ * @return 链式对象
+ * @see #bindWindow(String, PropertyBuild)
+ */
+ public JxUIPane.Builder bindWindow(String property, @Nullable Object obj) {
+ this.propertyMap.put(property, obj);
+ return this;
+ }
+
+ /**
+ * 注入一个java对象到js中。绑定在全局变量 window的property指定的变量。
+ * PropertyBuild用于动态生成绑定属性。个方法仅在在加载的网页上执行
+ * JavaScript 之前注入
+ * 相当于:
+ * window[property] = javaObject
+ *
+ * @param property 属性构建器
+ * @param obj java对象
+ * @return 链式对象
+ * @see #bindWindow(String, Object)
+ */
+ public JxUIPane.Builder bindWindow(String property, PropertyBuild obj) {
+ buildPropertyMap.put(property, obj);
+ return this;
+ }
+
+ /**
+ * 构建
+ */
+ public JxUIPane build() {
+ JxUIPane pane = new JxUIPane<>();
+ pane.namespace = namespace;
+ pane.variable = variable;
+ pane.expression = expression;
+ pane.setMap(parameterMap);
+ pane.setComponent(component);
+ pane.initialize();
+ injectJs(pane);
+ if (!Objects.isNull(listenerPair)) {
+ pane.browser.navigation().on(listenerPair.getFirst(), listenerPair.getSecond());
+ }
+ if (StringUtils.isNotEmpty(this.url)) {
+ pane.browser.navigation().loadUrl(encodeWindowsPath(this.url));
+ } else if (StringUtils.isNotEmpty(this.html)) {
+ pane.browser.mainFrame().ifPresent(f -> f.loadHtml(html));
+ }
+ return pane;
+ }
+
+ /**
+ * 由于 InjectJsCallback 的回调机制,在初始化期间,只有
+ * 在 InjectJsCallback 中 putProperty 才能生效。
+ * 因此,嵌套回调分别做默认初始化、putProperty、外置初始化
+ */
+ private void injectJs(JxUIPane pane) {
+ pane.initInjectJs(params -> {
+ Frame frame = params.frame();
+ if (!propertyMap.isEmpty()) {
+ propertyMap.forEach((key, value) ->
+ executeJsObject(frame, WINDOW)
+ .ifPresent(window -> window.putProperty(key, value)));
+ }
+ if (!buildPropertyMap.isEmpty()) {
+ buildPropertyMap.forEach((key, value) ->
+ executeJsObject(frame, WINDOW)
+ .ifPresent(window -> window.putProperty(key, value.build(window))));
+ }
+ if (!namespacePropertyMap.isEmpty()) {
+ namespacePropertyMap.forEach((key, value) ->
+ executeJsObject(frame, WINDOW + DOT + namespace)
+ .ifPresent(pool -> pool.putProperty(key, value)));
+ }
+ if (variableProperty != null) {
+ executeJsObject(frame, WINDOW + DOT + namespace)
+ .ifPresent(pool -> pool.putProperty(variable, variableProperty));
+ }
+ if (callback != null) {
+ return callback.on(params);
+ }
+ return InjectJsCallback.Response.proceed();
+ });
+ }
+ }
+}
diff --git a/designer-base/src/main/java/com/fr/design/jxbrowser/MimeType.java b/designer-base/src/main/java/com/fr/design/jxbrowser/MimeType.java
new file mode 100644
index 0000000000..654de0d729
--- /dev/null
+++ b/designer-base/src/main/java/com/fr/design/jxbrowser/MimeType.java
@@ -0,0 +1,112 @@
+package com.fr.design.jxbrowser;
+
+import com.fr.stable.StringUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Optional;
+
+/**
+ * jxbrowser 使用的一些媒体类型
+ *
+ * @author vito
+ * @since 11.0
+ * Created on 2023/6/13
+ */
+public enum MimeType {
+ /**
+ * html 格式
+ */
+ HTML(".html", "text/html"),
+ /**
+ * CSS 格式
+ */
+ CSS(".css", "text/css"),
+ /**
+ * js 格式
+ */
+ JS(".js", "text/javascript"),
+ /**
+ * svg 格式
+ */
+ SVG(".svg", "image/svg+xml"),
+ /**
+ * png 格式
+ */
+ PNG(".png", "image/png"),
+
+ /**
+ * jpg 格式
+ */
+ JPG(".jpg", "image/jpeg"),
+
+ /**
+ * jpeg 格式
+ */
+ JPEG(".jpeg", "image/jpeg"),
+
+ /**
+ * gif 格式
+ */
+ GIF(".gif", "image/gif"),
+ /**
+ * woff 字体格式
+ */
+ WOFF(".woff", "font/woff"),
+ /**
+ * ttf 字体格式
+ */
+ TTF(".ttf", "truetype"),
+
+ /**
+ * MS 嵌入式开放字体
+ */
+ EOT(".eot", "embedded-opentype");
+
+ private final String suffix;
+ private final String mimeType;
+
+ MimeType(String suffix, String mimeType) {
+ this.suffix = suffix;
+ this.mimeType = mimeType;
+ }
+
+ public String getMimeType() {
+ return mimeType;
+ }
+
+ /**
+ * 获取指定路径对应的 mimetype,优先匹配常量中的类型
+ * 如果没有,尝试使用 Files.probeContentType 检测
+ * 如果没有,默认返回 text/html
+ *
+ * @param resourcePath 资源路径
+ * @return MimeType
+ */
+ public static String parseMimeType(String resourcePath) {
+ if (StringUtils.isBlank(resourcePath)) {
+ return HTML.mimeType;
+ }
+ Optional mimeType = Arrays.stream(values())
+ .filter(type -> resourcePath.endsWith(type.suffix))
+ .findFirst();
+ if (mimeType.isPresent()) {
+ return mimeType.get().mimeType;
+ } else {
+ return getFileMimeType(resourcePath);
+ }
+ }
+
+ private static String getFileMimeType(String finalPath) {
+ Path file = new File(finalPath).toPath();
+ try {
+ String s = Files.probeContentType(file);
+ return StringUtils.isEmpty(s) ? HTML.mimeType : s;
+ } catch (IOException e) {
+ return HTML.mimeType;
+ }
+ }
+}
diff --git a/designer-base/src/main/java/com/fr/design/jxbrowser/NxInterceptRequestCallback.java b/designer-base/src/main/java/com/fr/design/jxbrowser/NxInterceptRequestCallback.java
new file mode 100644
index 0000000000..58d45a61f2
--- /dev/null
+++ b/designer-base/src/main/java/com/fr/design/jxbrowser/NxInterceptRequestCallback.java
@@ -0,0 +1,183 @@
+package com.fr.design.jxbrowser;
+
+import com.fr.base.TemplateUtils;
+import com.fr.design.ui.ModernRequestClient;
+import com.fr.design.ui.ModernUIConstants;
+import com.fr.general.IOUtils;
+import com.fr.log.FineLoggerFactory;
+import com.fr.stable.EncodeConstants;
+import com.fr.stable.StringUtils;
+import com.fr.web.struct.AssembleComponent;
+import com.fr.web.struct.AtomBuilder;
+import com.fr.web.struct.PathGroup;
+import com.fr.web.struct.category.ScriptPath;
+import com.fr.web.struct.category.StylePath;
+import com.teamdev.jxbrowser.net.HttpHeader;
+import com.teamdev.jxbrowser.net.HttpStatus;
+import com.teamdev.jxbrowser.net.UrlRequest;
+import com.teamdev.jxbrowser.net.UrlRequestJob;
+import com.teamdev.jxbrowser.net.callback.InterceptUrlRequestCallback;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Supplier;
+
+import static com.fr.design.ui.ModernUIConstants.COMPONENT_TAG;
+
+/**
+ * jxbrowser7 自定义 scheme 处理回调
+ *
+ * @author vito
+ * @since 11.0
+ * Created on 2023/6/8
+ */
+public class NxInterceptRequestCallback implements InterceptUrlRequestCallback {
+
+ private static final String COLON_DECODE_ESCAPE = "/:";
+ private static final String SCHEME_SPLIT = ":/";
+ private Supplier component;
+
+ private Supplier