fix: synchronize webapp container state access

This commit is contained in:
2026-08-16 17:49:38 +08:00
parent f7cffe27d9
commit 7ce4df15e6
@@ -9,12 +9,21 @@ data class Tab(val id: String, val url: String, val active: Boolean = false, val
// 真实 WebView 导航(goBack/goForward/reload)留待 Task 13 集成时接入 // 真实 WebView 导航(goBack/goForward/reload)留待 Task 13 集成时接入
// Multi-tab state management (pure Kotlin data layer, WebView-free, JVM-testable) // Multi-tab state management (pure Kotlin data layer, WebView-free, JVM-testable)
// Real WebView navigation (goBack/goForward/reload) is wired up in Task 13 // Real WebView navigation (goBack/goForward/reload) is wired up in Task 13
//
// 状态方法全部 @Synchronizedopen/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 { class WebAppContainer {
private val tabs = mutableListOf<Tab>() private val tabs = mutableListOf<Tab>()
// 打开标签:已存在则激活它(不重复添加),否则新增并激活;name 记录展示名 // 打开标签:已存在则激活它(不重复添加),否则新增并激活;name 记录展示名
// Open a tab: if it already exists just activate it, otherwise add and activate; // Open a tab: if it already exists just activate it, otherwise add and activate;
// name records the display title from the manifest // name records the display title from the manifest
@Synchronized
fun open(id: String, url: String, name: String = "") { fun open(id: String, url: String, name: String = "") {
val existing = tabs.find { it.id == id } val existing = tabs.find { it.id == id }
if (existing != null) { if (existing != null) {
@@ -27,6 +36,7 @@ class WebAppContainer {
// 关闭标签:若关闭后没有激活标签则激活第一个 // 关闭标签:若关闭后没有激活标签则激活第一个
// Close a tab: if none remain active, activate the first one // Close a tab: if none remain active, activate the first one
@Synchronized
fun close(id: String) { fun close(id: String) {
tabs.removeAll { it.id == id } tabs.removeAll { it.id == id }
if (tabs.isNotEmpty() && tabs.none { it.active }) { if (tabs.isNotEmpty() && tabs.none { it.active }) {
@@ -36,6 +46,7 @@ class WebAppContainer {
// 切换激活标签:id 不存在时返回 false 且不改动状态(保持原激活标签) // 切换激活标签:id 不存在时返回 false 且不改动状态(保持原激活标签)
// Switch the active tab: return false and keep current state when id is unknown // Switch the active tab: return false and keep current state when id is unknown
@Synchronized
fun switchTo(id: String): Boolean { fun switchTo(id: String): Boolean {
if (tabs.none { it.id == id }) return false if (tabs.none { it.id == id }) return false
setActive(id) setActive(id)
@@ -44,10 +55,12 @@ class WebAppContainer {
// 返回标签列表副本(外部无法直接改动内部状态) // 返回标签列表副本(外部无法直接改动内部状态)
// Return a defensive copy of the tab list // Return a defensive copy of the tab list
@Synchronized
fun tabs(): List<Tab> = tabs.toList() fun tabs(): List<Tab> = tabs.toList()
// 返回当前激活标签 id,无标签或未激活返回 null // 返回当前激活标签 id,无标签或未激活返回 null
// Return the active tab id, or null when empty/none active // Return the active tab id, or null when empty/none active
@Synchronized
fun activeTabId(): String? = tabs.firstOrNull { it.active }?.id fun activeTabId(): String? = tabs.firstOrNull { it.active }?.id
private fun setActive(id: String) { private fun setActive(id: String) {