86 lines
3.5 KiB
Kotlin
86 lines
3.5 KiB
Kotlin
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
|
||
}
|
||
}
|
||
}
|
||
} |