v1.0.1: fully reflection-based browser (EmbeddedBrowser/EmbeddedJsBridge), zero compile-time JCEF deps; panel never blank
This commit is contained in:
+1
-1
@@ -6,7 +6,7 @@ plugins {
|
||||
}
|
||||
|
||||
group = "com.prepeep"
|
||||
version = "1.0.0"
|
||||
version = "1.0.1"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
package com.harmness.toolwindow
|
||||
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import java.lang.reflect.InvocationTargetException
|
||||
import javax.swing.JComponent
|
||||
|
||||
/**
|
||||
* JCEF 浏览器的纯反射封装。
|
||||
*
|
||||
* 目标:让插件代码在**编译期完全不引用** com.intellij.ui.jcef.*
|
||||
* (以及 org.cef.*),避免 IDE 缺少 JCEF 类时出现
|
||||
* NoClassDefFoundError 把整个面板拖垮。
|
||||
*
|
||||
* 所有 JCEF 调用都走反射;类不存在或调用失败时返回 null / 静默降级。
|
||||
*/
|
||||
class EmbeddedBrowser internal constructor(val instance: Any) {
|
||||
|
||||
/** 浏览器 Swing 组件(可加入面板);失败返回 null */
|
||||
val component: JComponent?
|
||||
get() = try {
|
||||
instance.invoke("getComponent") as? JComponent
|
||||
} catch (t: Throwable) {
|
||||
log.warn("getComponent failed", t)
|
||||
null
|
||||
}
|
||||
|
||||
fun load(url: String): Boolean = try {
|
||||
instance.invoke("loadURL", url)
|
||||
true
|
||||
} catch (t: Throwable) {
|
||||
log.warn("loadURL failed for $url", t)
|
||||
false
|
||||
}
|
||||
|
||||
fun canGoBack(): Boolean = cefCall("canGoBack") as? Boolean ?: false
|
||||
|
||||
fun goBack(): Boolean = cefCall("goBack") != null
|
||||
|
||||
fun canGoForward(): Boolean = cefCall("canGoForward") as? Boolean ?: false
|
||||
|
||||
fun goForward(): Boolean = cefCall("goForward") != null
|
||||
|
||||
fun reload(): Boolean = cefCall("reload") != null
|
||||
|
||||
fun dispose() {
|
||||
try {
|
||||
instance.invoke("dispose")
|
||||
} catch (t: Throwable) {
|
||||
log.warn("browser dispose failed", t)
|
||||
}
|
||||
}
|
||||
|
||||
// org.cef.browser.CefBrowser 上的方法(getCefBrowser() 返回它)
|
||||
private fun cefCall(name: String, vararg args: Any?): Any? = try {
|
||||
val cef = instance.invoke("getCefBrowser") ?: return null
|
||||
cef.invoke(name, *args)
|
||||
} catch (t: Throwable) {
|
||||
log.warn("cefCall $name failed", t)
|
||||
null
|
||||
}
|
||||
|
||||
private fun Any.invoke(method: String, vararg args: Any?): Any? {
|
||||
val clazz = this::class.java
|
||||
return try {
|
||||
for (m in clazz.methods) {
|
||||
if (m.name == method && m.parameterCount == args.size) {
|
||||
m.isAccessible = true
|
||||
return m.invoke(this, *args)
|
||||
}
|
||||
}
|
||||
// 按名字+精确参数类型再找一次
|
||||
val types = args.map { it?.javaClass ?: String::class.java }.toTypedArray()
|
||||
val m = clazz.getMethod(method, *types)
|
||||
m.isAccessible = true
|
||||
m.invoke(this, *args)
|
||||
} catch (t: Throwable) {
|
||||
log.warn("reflect invoke $method failed", t)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val log = Logger.getInstance(EmbeddedBrowser::class.java)
|
||||
|
||||
/** IDE 是否提供可用 JCEF(JBCefApp.isSupported 的反射版本) */
|
||||
fun isJcefSupported(): Boolean = try {
|
||||
val app = Class.forName("com.intellij.ui.jcef.JBCefApp")
|
||||
val m = app.getMethod("isSupported")
|
||||
m.invoke(null) as? Boolean ?: false
|
||||
} catch (t: Throwable) {
|
||||
log.info("JCEF not supported in this IDE: ${t.javaClass.simpleName}")
|
||||
false
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试创建嵌入式浏览器。
|
||||
* @return 成功返回封装对象;失败(类缺失 / 不支持 / 构造异常)返回 null
|
||||
*/
|
||||
fun create(): EmbeddedBrowser? {
|
||||
try {
|
||||
val cls = Class.forName("com.intellij.ui.jcef.JBCefBrowser")
|
||||
val ctor = cls.getConstructor()
|
||||
val inst = ctor.newInstance()
|
||||
// 验证 getComponent() 可用,避免构造成功但组件拿不到
|
||||
inst.javaClass.getMethod("getComponent").invoke(inst)
|
||||
return EmbeddedBrowser(inst)
|
||||
} catch (t: Throwable) {
|
||||
val real = when (t) {
|
||||
is InvocationTargetException -> t.targetException ?: t
|
||||
else -> t
|
||||
}
|
||||
log.warn("EmbeddedBrowser.create failed: ${real.javaClass.simpleName}: ${real.message}", real)
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,11 @@
|
||||
package com.harmness.toolwindow
|
||||
|
||||
import com.intellij.ide.BrowserUtil
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.ui.components.JBLabel
|
||||
import com.intellij.ui.components.JBPanel
|
||||
import com.intellij.ui.components.JBScrollPane
|
||||
import com.intellij.ui.jcef.JBCefApp
|
||||
import com.intellij.ui.jcef.JBCefBrowser
|
||||
import org.cef.browser.CefBrowser
|
||||
import org.cef.handler.CefLoadHandlerAdapter
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.CardLayout
|
||||
import java.awt.FlowLayout
|
||||
@@ -34,11 +29,12 @@ import javax.swing.SwingUtilities
|
||||
*
|
||||
* 四个视图(CardLayout):
|
||||
* - standby → 初始化中
|
||||
* - browser → 网页(JCEF,惰性初始化)
|
||||
* - browser → 网页(通过 [EmbeddedBrowser] 反射包装,惰性初始化)
|
||||
* - settings → 起始地址设置表单
|
||||
* - error → 错误信息 + 一键复制按钮
|
||||
* - error → 错误信息 + 一键复制
|
||||
*
|
||||
* 工具栏和设置表单不依赖 JCEF,始终可用。
|
||||
* 本类**不引用任何 JCEF 类**。浏览器能力全部走反射封装,
|
||||
* 即使 IDE 没有 JCEF,工具栏 / 设置表单 / 错误卡片也始终可用。
|
||||
*/
|
||||
class HarmnessBrowserPanel(
|
||||
private val project: com.intellij.openapi.project.Project,
|
||||
@@ -48,9 +44,9 @@ class HarmnessBrowserPanel(
|
||||
private val settings = SettingsState.getInstance()
|
||||
private val logger = Logger.getInstance(HarmnessBrowserPanel::class.java)
|
||||
|
||||
// JCEF 对象(惰性初始化,可能为 null)
|
||||
private var browser: JBCefBrowser? = null
|
||||
private var bridgeAvailable = false // 反射创建 JBCefJSQuery 是否成功
|
||||
// 反射浏览器(可能为 null)
|
||||
private var browser: EmbeddedBrowser? = null
|
||||
private var bridge: EmbeddedJsBridge? = null
|
||||
|
||||
private var addressField: JTextField = JTextField()
|
||||
private var settingsUrlField: JTextField = JTextField(30)
|
||||
@@ -59,9 +55,6 @@ class HarmnessBrowserPanel(
|
||||
private val center = JBPanel<JBPanel<*>>(cardLayout)
|
||||
private var currentCard = CARD_STANDBY
|
||||
|
||||
// 反射持有的 JSQuery 对象(类型为 Any,避免编译期依赖)
|
||||
private var jsQuery: Any? = null
|
||||
|
||||
init {
|
||||
Disposer.register(parentDisposable, this)
|
||||
|
||||
@@ -73,7 +66,7 @@ class HarmnessBrowserPanel(
|
||||
center.add(createErrorCard(), CARD_ERROR)
|
||||
cardLayout.show(center, CARD_STANDBY)
|
||||
|
||||
// 延迟初始化浏览器,不阻塞 EDT
|
||||
// 窗口就绪后惰性初始化浏览器,避免阻塞 EDT
|
||||
SwingUtilities.invokeLater { initBrowserSafely() }
|
||||
}
|
||||
|
||||
@@ -83,19 +76,13 @@ class HarmnessBrowserPanel(
|
||||
|
||||
private fun createToolbar(): JComponent {
|
||||
val back = JButton(object : AbstractAction("←") {
|
||||
override fun actionPerformed(e: ActionEvent) {
|
||||
runCatching { browser?.cefBrowser?.goBack() }
|
||||
}
|
||||
override fun actionPerformed(e: ActionEvent) { runCatching { browser?.goBack() } }
|
||||
})
|
||||
val forward = JButton(object : AbstractAction("→") {
|
||||
override fun actionPerformed(e: ActionEvent) {
|
||||
runCatching { browser?.cefBrowser?.goForward() }
|
||||
}
|
||||
override fun actionPerformed(e: ActionEvent) { runCatching { browser?.goForward() } }
|
||||
})
|
||||
val reload = JButton(object : AbstractAction("⟳") {
|
||||
override fun actionPerformed(e: ActionEvent) {
|
||||
runCatching { browser?.cefBrowser?.reload() }
|
||||
}
|
||||
override fun actionPerformed(e: ActionEvent) { runCatching { browser?.reload() } }
|
||||
})
|
||||
val settingsBtn = JButton(object : AbstractAction("⚙") {
|
||||
override fun actionPerformed(e: ActionEvent) {
|
||||
@@ -142,7 +129,7 @@ class HarmnessBrowserPanel(
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// JCEF 浏览器初始化(安全)
|
||||
// 浏览器初始化(反射,安全)
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
private fun initBrowserSafely() {
|
||||
@@ -152,108 +139,64 @@ class HarmnessBrowserPanel(
|
||||
return
|
||||
}
|
||||
try {
|
||||
if (!JBCefApp.isSupported()) {
|
||||
if (!EmbeddedBrowser.isJcefSupported()) {
|
||||
showError("JCEF 不受支持",
|
||||
"当前 IDE 构建未启用 JCEF。\n请使用 JetBrains 官方发行版(内置 JBR 运行时)。")
|
||||
"当前 IDE 构建未启用 JCEF(找不到 com.intellij.ui.jcef.JBCefBrowser)。\n" +
|
||||
"请使用 JetBrains 官方发行版 IDE,或更换为内置 JCEF 的构建。")
|
||||
return
|
||||
}
|
||||
val b = JBCefBrowser()
|
||||
|
||||
val b = EmbeddedBrowser.create()
|
||||
?: run {
|
||||
showError("无法创建浏览器",
|
||||
"反射创建 JBCefBrowser 失败。请检查 IDE 是否启用 JCEF。")
|
||||
return
|
||||
}
|
||||
browser = b
|
||||
|
||||
// 通过反射安全初始化 JS↔Java 桥
|
||||
setupBridgeViaReflection(b)
|
||||
// JS 桥(失败不影响浏览器)
|
||||
bridge = EmbeddedJsBridge.create(b) { request ->
|
||||
logger.debug("bridge request: $request")
|
||||
try {
|
||||
val responseClass = Class.forName("com.intellij.ui.jcef.JBCefJSQuery\$Response")
|
||||
val ctor = responseClass.getConstructor(String::class.java)
|
||||
ctor.newInstance(handleBridgeRequest(request))
|
||||
} catch (_: Throwable) {
|
||||
// 无 Response 类:直接返回字符串
|
||||
handleBridgeRequest(request)
|
||||
}
|
||||
}
|
||||
|
||||
// 注入浏览器组件
|
||||
center.add(b.component, CARD_BROWSER)
|
||||
showCard(CARD_BROWSER)
|
||||
loadCurrentUrl()
|
||||
// 浏览器组件加入 "browser" 卡片
|
||||
val comp = b.component
|
||||
if (comp != null) {
|
||||
center.add(comp, CARD_BROWSER)
|
||||
showCard(CARD_BROWSER)
|
||||
loadCurrentUrl()
|
||||
} else {
|
||||
showError("浏览器组件不可用", "JBCefBrowser.getComponent() 返回空。")
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
logger.error("JCEF init failed", t)
|
||||
showError("JCEF 初始化失败", buildErrorText(t))
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// JS ↔ Java 桥(全反射,编译期零 JBCefJSQuery 依赖)
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
private fun setupBridgeViaReflection(b: JBCefBrowser) {
|
||||
try {
|
||||
// 1) 检查 JBCefJSQuery 类是否可用
|
||||
val queryClass = try {
|
||||
Class.forName("com.intellij.ui.jcef.JBCefJSQuery")
|
||||
} catch (_: Throwable) {
|
||||
logger.info("JBCefJSQuery class not available; bridge disabled")
|
||||
return
|
||||
}
|
||||
|
||||
// 2) 反射创建 JBCefJSQuery.create(JBCefBrowser)
|
||||
val createMethod = queryClass.getMethod("create", JBCefBrowser::class.java)
|
||||
val queryInstance = createMethod.invoke(null, b) ?: return
|
||||
jsQuery = queryInstance
|
||||
|
||||
// 3) 用反射构建 Handler(避免编译期引用 Response 内部类)
|
||||
val handlerInterface = Class.forName("com.intellij.ui.jcef.JBCefJSQuery\$Handler")
|
||||
val handlerProxy = java.lang.reflect.Proxy.newProxyInstance(
|
||||
handlerInterface.classLoader,
|
||||
arrayOf(handlerInterface)
|
||||
) { _, method, args ->
|
||||
when (method.name) {
|
||||
"success" -> {
|
||||
val response = args?.getOrNull(0)?.toString() ?: ""
|
||||
logger.debug("Bridge success: $response")
|
||||
}
|
||||
"error" -> {
|
||||
logger.warn("Bridge error: code=${args?.getOrNull(0)}, msg=${args?.getOrNull(1)}")
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
val addHandlerMethod = queryClass.getMethod("addHandler", handlerInterface)
|
||||
addHandlerMethod.invoke(queryInstance, handlerProxy)
|
||||
|
||||
// 4) 反射调用 inject() 获取注入到页面的 JS 代码
|
||||
val injectMethod = queryClass.getMethod(
|
||||
"inject", String::class.java, String::class.java, String::class.java
|
||||
)
|
||||
val injectedJs = injectMethod.invoke(
|
||||
queryInstance,
|
||||
"payload.query",
|
||||
"function(r){}",
|
||||
"function(c,m){}"
|
||||
) as? String ?: ""
|
||||
|
||||
// 5) 在页面加载完成后注入桥接函数
|
||||
b.jbCefClient.addLoadHandler(object : CefLoadHandlerAdapter() {
|
||||
override fun onLoadEnd(
|
||||
cBrowser: CefBrowser,
|
||||
frame: org.cef.browser.CefFrame,
|
||||
httpStatusCode: Int
|
||||
) {
|
||||
if (!frame.isMain) return
|
||||
try {
|
||||
val js = """
|
||||
window.harmnessBridge = function(payload) {
|
||||
$injectedJs
|
||||
};
|
||||
if (window.__HARMNESS_READY__) window.__HARMNESS_READY__();
|
||||
""".trimIndent()
|
||||
cBrowser.executeJavaScript(js, cBrowser.url ?: "", 0)
|
||||
} catch (t: Throwable) {
|
||||
logger.warn("Bridge inject failed", t)
|
||||
}
|
||||
}
|
||||
}, b.cefBrowser)
|
||||
|
||||
bridgeAvailable = true
|
||||
logger.info("JS↔Java bridge initialized successfully via reflection")
|
||||
|
||||
} catch (t: Throwable) {
|
||||
logger.warn("Bridge init failed (browser still works without bridge): ${t.message}")
|
||||
bridgeAvailable = false
|
||||
// 不中断:浏览器本身仍可用,只是网页无法调用插件 API
|
||||
private fun handleBridgeRequest(request: String): String = when {
|
||||
request == "ping" -> "pong"
|
||||
request == "getUrl" -> browser?.let {
|
||||
try {
|
||||
val cef = it.instance.javaClass.getMethod("getCefBrowser").invoke(it.instance)
|
||||
cef.javaClass.getMethod("getUrl").invoke(cef) as? String ?: ""
|
||||
} catch (_: Throwable) { "" }
|
||||
} ?: ""
|
||||
request.startsWith("openExternal:") -> {
|
||||
try {
|
||||
com.intellij.ide.BrowserUtil.browse(request.removePrefix("openExternal:"))
|
||||
} catch (_: Throwable) {}
|
||||
"ok"
|
||||
}
|
||||
else -> "unknown request: $request"
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
@@ -271,11 +214,11 @@ class HarmnessBrowserPanel(
|
||||
appendLine(detail)
|
||||
appendLine()
|
||||
appendLine("--- 环境信息 ---")
|
||||
appendLine("插件版本: 1.0.0")
|
||||
appendLine("插件版本: 1.0.1")
|
||||
appendLine("IDE Build: $appInfo")
|
||||
appendLine("OS: ${System.getProperty("os.name")} ${System.getProperty("os.version")}")
|
||||
appendLine("Java: ${System.getProperty("java.version")}")
|
||||
appendLine("JCEF 支持: ${try { JBCefApp.isSupported() } catch (_: Throwable) { "检查失败" }}")
|
||||
appendLine("JCEF 支持: ${EmbeddedBrowser.isJcefSupported()}")
|
||||
}
|
||||
val area = center.getClientProperty("errorArea") as? JTextArea
|
||||
if (area != null) {
|
||||
@@ -340,24 +283,20 @@ class HarmnessBrowserPanel(
|
||||
anchor = GridBagConstraints.NORTHWEST
|
||||
}
|
||||
|
||||
// 标题
|
||||
panel.add(JBLabel("<html><h2 style='margin:0'>⚙ 设置</h2></html>"), gbc.also {
|
||||
it.gridx = 0; it.gridy = 0; it.gridwidth = 2
|
||||
})
|
||||
gbc.gridwidth = 1
|
||||
|
||||
// 起始地址
|
||||
panel.add(JBLabel("起始地址:"), gbc.also { it.gridx = 0; it.gridy = 1 })
|
||||
settingsUrlField = JTextField(settings.startUrl, 28)
|
||||
panel.add(settingsUrlField, gbc.also { it.gridx = 1; it.gridy = 1 })
|
||||
|
||||
// 提示
|
||||
panel.add(JBLabel("<html><small>例如:http://127.0.0.1:8899<br>" +
|
||||
"目标站点须允许被 iframe 嵌入(无 X-Frame-Options / CSP 限制)</small></html>"),
|
||||
gbc.also { it.gridx = 0; it.gridy = 2; it.gridwidth = 2 })
|
||||
gbc.gridwidth = 1
|
||||
|
||||
// 按钮
|
||||
val saveBtn = JButton("保存")
|
||||
saveBtn.addActionListener {
|
||||
settings.startUrl = settingsUrlField.text.trim()
|
||||
@@ -376,7 +315,6 @@ class HarmnessBrowserPanel(
|
||||
}
|
||||
panel.add(btnPanel, gbc.also { it.gridx = 0; it.gridy = 3; it.gridwidth = 2 })
|
||||
|
||||
// 弹性填充
|
||||
panel.add(JBPanel<JBPanel<*>>(), gbc.also {
|
||||
it.gridy = 4; it.weighty = 1.0
|
||||
})
|
||||
@@ -391,14 +329,14 @@ class HarmnessBrowserPanel(
|
||||
private fun navigate(url: String) {
|
||||
val b = browser ?: return
|
||||
val fixed = if (url.startsWith("http://") || url.startsWith("https://")) url else "http://$url"
|
||||
b.loadURL(fixed)
|
||||
b.load(fixed)
|
||||
}
|
||||
|
||||
fun loadCurrentUrl() {
|
||||
val b = browser ?: return
|
||||
val url = settings.startUrl
|
||||
addressField.text = url
|
||||
b.loadURL(url)
|
||||
b.load(url)
|
||||
}
|
||||
|
||||
fun onToolWindowSelected() {
|
||||
@@ -410,14 +348,10 @@ class HarmnessBrowserPanel(
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
override fun dispose() {
|
||||
// 反射释放 jsQuery
|
||||
try {
|
||||
jsQuery?.javaClass?.getMethod("dispose")?.invoke(jsQuery)
|
||||
} catch (_: Throwable) {}
|
||||
try { browser?.let { Disposer.dispose(it) } } catch (_: Throwable) {}
|
||||
try { bridge?.dispose() } catch (_: Throwable) {}
|
||||
try { browser?.dispose() } catch (_: Throwable) {}
|
||||
bridge = null
|
||||
browser = null
|
||||
jsQuery = null
|
||||
bridgeAvailable = false
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
Reference in New Issue
Block a user