From 7ce4df15e618084e90e4085b0e1a72b0a6049846 Mon Sep 17 00:00:00 2001 From: AskaEth Date: Sun, 16 Aug 2026 17:49:38 +0800 Subject: [PATCH] fix: synchronize webapp container state access --- .../java/top/yeij/hearth/webapp/WebAppContainer.kt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/src/main/java/top/yeij/hearth/webapp/WebAppContainer.kt b/app/src/main/java/top/yeij/hearth/webapp/WebAppContainer.kt index db77a95..b3d132a 100644 --- a/app/src/main/java/top/yeij/hearth/webapp/WebAppContainer.kt +++ b/app/src/main/java/top/yeij/hearth/webapp/WebAppContainer.kt @@ -9,12 +9,21 @@ data class Tab(val id: String, val url: String, val active: Boolean = false, val // 真实 WebView 导航(goBack/goForward/reload)留待 Task 13 集成时接入 // Multi-tab state management (pure Kotlin data layer, WebView-free, JVM-testable) // Real WebView navigation (goBack/goForward/reload) is wired up in Task 13 +// +// 状态方法全部 @Synchronized:open/close/switchTo 写操作在主线程,listTabs/tabs 读操作 +// 在 JavaBridge 线程,跨线程读写同一 mutableListOf,需用同一把锁串行,避免 +// ConcurrentModificationException(尤其 tabs() 的 toList() 迭代与写操作互斥) +// All state methods are @Synchronized: open/close/switchTo write on the main thread +// while listTabs/tabs read on the JavaBridge thread; reading and writing the same +// mutableListOf across threads must serialize on a single lock to avoid +// ConcurrentModificationException (especially toList() iteration vs writes) class WebAppContainer { private val tabs = mutableListOf() // 打开标签:已存在则激活它(不重复添加),否则新增并激活;name 记录展示名 // Open a tab: if it already exists just activate it, otherwise add and activate; // name records the display title from the manifest + @Synchronized fun open(id: String, url: String, name: String = "") { val existing = tabs.find { it.id == id } if (existing != null) { @@ -27,6 +36,7 @@ class WebAppContainer { // 关闭标签:若关闭后没有激活标签则激活第一个 // Close a tab: if none remain active, activate the first one + @Synchronized fun close(id: String) { tabs.removeAll { it.id == id } if (tabs.isNotEmpty() && tabs.none { it.active }) { @@ -36,6 +46,7 @@ class WebAppContainer { // 切换激活标签:id 不存在时返回 false 且不改动状态(保持原激活标签) // Switch the active tab: return false and keep current state when id is unknown + @Synchronized fun switchTo(id: String): Boolean { if (tabs.none { it.id == id }) return false setActive(id) @@ -44,10 +55,12 @@ class WebAppContainer { // 返回标签列表副本(外部无法直接改动内部状态) // Return a defensive copy of the tab list + @Synchronized fun tabs(): List = tabs.toList() // 返回当前激活标签 id,无标签或未激活返回 null // Return the active tab id, or null when empty/none active + @Synchronized fun activeTabId(): String? = tabs.firstOrNull { it.active }?.id private fun setActive(id: String) {