fix: final review findings - thread safety, xss, cache

This commit is contained in:
2026-08-16 17:35:13 +08:00
parent a0f8873b7a
commit f7cffe27d9
8 changed files with 157 additions and 39 deletions
@@ -75,8 +75,10 @@ class JsBridge(
Log.d("HearthBridge", "openWebApp: unknown id=$id")
return
}
container.open(id, app.url, app.name)
// 状态变更与 View 变更统一在主线程串行,避免与 onBackPressed/listTabs 并发读写
// Mutate tab state and view on the main thread together, serializing access
onMain {
container.open(id, app.url, app.name)
host.openWebView(id, app.url)
host.syncTabs(container.tabs())
}
@@ -90,14 +92,14 @@ class JsBridge(
fun closeWebApp(id: String) {
val container = webAppContainer ?: return
val host = webAppHost ?: return
container.close(id)
val nextActiveId = container.activeTabId()
onMain {
container.close(id)
val nextActiveId = container.activeTabId()
host.closeWebView(id)
nextActiveId?.let { host.switchWebView(it) }
host.syncTabs(container.tabs())
Log.d("HearthBridge", "closeWebApp: id=$id remaining=${container.tabs().size}")
}
Log.d("HearthBridge", "closeWebApp: id=$id remaining=${container.tabs().size}")
}
// 切换激活标签:校验 id 存在后更新状态 → 主线程切换 WebView 可见性 → 同步顶栏
@@ -107,10 +109,11 @@ class JsBridge(
fun switchTab(id: String) {
val container = webAppContainer ?: return
val host = webAppHost ?: return
if (!container.switchTo(id)) return
onMain {
host.switchWebView(id)
host.syncTabs(container.tabs())
if (container.switchTo(id)) {
host.switchWebView(id)
host.syncTabs(container.tabs())
}
}
Log.d("HearthBridge", "switchTab: id=$id")
}
@@ -153,12 +156,15 @@ class JsBridge(
Log.d("HearthBridge", "webReload called")
}
// 清单内存缓存:首次拉取后缓存,后续复用(避免 openWebApp 重复 I/O
// In-memory manifest cache: fetch once, reuse afterwards (avoid repeated I/O)
// 清单内存缓存:首次拉取成功后缓存,后续复用(避免 openWebApp 重复 I/O
// 空结果(离线失败)不缓存,下次调用重试拉取,避免空清单被永久缓存
// In-memory manifest cache: cache only on a successful non-empty fetch (avoid
// repeated I/O); empty results (offline failure) are never cached so the next
// call retries instead of being permanently stuck with an empty manifest
private fun manifest(): List<WebApp> {
manifestCache?.let { return it }
val apps = webAppRepository?.fetchManifest() ?: emptyList<WebApp>()
manifestCache = apps
if (apps.isNotEmpty()) manifestCache = apps
return apps
}