v1.0.1: fully reflection-based browser (EmbeddedBrowser/EmbeddedJsBridge), zero compile-time JCEF deps; panel never blank

This commit is contained in:
harmness
2026-09-10 22:26:03 +08:00
parent 3fd4a01b4b
commit 47f3129dfb
4 changed files with 269 additions and 131 deletions
@@ -0,0 +1,86 @@
package com.harmness.toolwindow
import com.intellij.openapi.Disposable
import com.intellij.openapi.diagnostic.Logger
import java.lang.reflect.InvocationTargetException
import java.util.function.Function
import javax.swing.JComponent
/**
* JCEF JS↔Java 桥的反射封装。
*
* 负责两件事:
* 1. 通过反射创建 JBCefJSQuery 并注册 handler(把网页发来的字符串请求转发给 [onRequest]
* 2. 通过反射调用 JBCefJSQuery.inject() 生成注入到页面的 JS 代码
*
* 与 [EmbeddedBrowser] 一样,编译期零 JCEF 依赖。类缺失/调用失败时
* 仅返回 null / 空串,不影响浏览器主体功能。
*/
class EmbeddedJsBridge private constructor(
private val queryInstance: Any,
private val onRequest: (String) -> Any?
) {
/**
* 返回可注入到页面 JS 的代码片段(调用插件 handler),
* 失败返回空串。
*/
fun injectJs(): String = try {
val m = queryInstance.javaClass.getMethod(
"inject", String::class.java, String::class.java, String::class.java
)
(m.invoke(queryInstance, "payload.query",
"function(response){ if (payload.callbacks && payload.callbacks.onSuccess) payload.callbacks.onSuccess(response); }",
"function(code,msg){ if (payload.callbacks && payload.callbacks.onFailure) payload.callbacks.onFailure(code,msg); }"
) as? String) ?: ""
} catch (t: Throwable) {
log.warn("injectJs failed", t)
""
}
fun dispose() {
try {
queryInstance.javaClass.getMethod("dispose").invoke(queryInstance)
} catch (t: Throwable) {
log.warn("bridge dispose failed", t)
}
}
companion object {
private val log = Logger.getInstance(EmbeddedJsBridge::class.java)
/**
* 为已创建的 [browser] 建立 JS 桥。
* @return 成功返回桥对象;失败返回 null(浏览器仍可用,仅网页无法调用插件 API)
*/
fun create(browser: EmbeddedBrowser, onRequest: (String) -> Any?): EmbeddedJsBridge? {
try {
val queryClass = Class.forName("com.intellij.ui.jcef.JBCefJSQuery")
// create(JBCefBrowserBase) — 用可赋值参数类型查找
val browserInst = browser.instance
val createMethod = queryClass.methods.firstOrNull { m ->
m.name == "create" && m.parameterCount == 1 &&
m.parameterTypes[0].isAssignableFrom(browserInst.javaClass)
} ?: return null
val queryInstance = createMethod.invoke(null, browserInst)
// addHandler(java.util.function.Function<String, Response>)
val handlerFn = Function<String, Any?> { req -> onRequest(req ?: "") }
val addMethod = queryClass.methods.firstOrNull { m ->
m.name == "addHandler" && m.parameterCount == 1 &&
m.parameterTypes[0] == Function::class.java
} ?: return null
addMethod.invoke(queryInstance, handlerFn)
return EmbeddedJsBridge(queryInstance, onRequest)
} catch (t: Throwable) {
val real = when (t) {
is InvocationTargetException -> t.targetException ?: t
else -> t
}
log.warn("EmbeddedJsBridge.create failed: ${real.javaClass.simpleName}: ${real.message}", real)
return null
}
}
}
}