fix(ui): reduce browser window cleanup complexity
- commit
- fece6d9b9b720bbeddfd7fe2deeb533e14066530
- author
- bnema · · 2026-06-17 20:15 UTC
- parent
- 52415e5
- signature
- unsigned
1 file · +90 -53
M internal/ui/browser_window.go +90 -53
| @@ -325,81 +325,118 @@ func (a *App) removeBrowserWindow(id string) { | |||
| 325 | 325 | return | |
| 326 | 326 | } | |
| 327 | 327 | removed := a.browserWindows[id] | |
| 328 | + | wasMainWindow := removed != nil && removed.mainWindow == a.mainWindow | |
| 329 | + | ||
| 330 | + | a.removeBrowserWindowOrder(id) | |
| 331 | + | a.releaseBrowserWindowTabs(context.Background(), id, removed) | |
| 332 | + | if removed != nil { | |
| 333 | + | removed.teardownForDestroy() | |
| 334 | + | } | |
| 335 | + | if a.contentCoord != nil { | |
| 336 | + | a.contentCoord.ClearPopupNamedContextsForWindow(id) | |
| 337 | + | } | |
| 338 | + | delete(a.browserWindows, id) | |
| 328 | 339 | ||
| 329 | - | // Remove from registration order | |
| 340 | + | fallback := a.deterministicBrowserWindowFallback() | |
| 341 | + | a.updateLastFocusedWindowAfterRemoval(id, fallback) | |
| 342 | + | if wasMainWindow { | |
| 343 | + | a.clearMainBrowserWindowAfterRemoval(fallback) | |
| 344 | + | } | |
| 345 | + | } | |
| 346 | + | ||
| 347 | + | func (a *App) removeBrowserWindowOrder(id string) { | |
| 330 | 348 | for i, wid := range a.browserWindowOrder { | |
| 331 | 349 | if wid == id { | |
| 332 | 350 | a.browserWindowOrder = append(a.browserWindowOrder[:i], a.browserWindowOrder[i+1:]...) | |
| 333 | - | break | |
| 351 | + | return | |
| 334 | 352 | } | |
| 335 | 353 | } | |
| 336 | - | wasMainWindow := removed != nil && removed.mainWindow == a.mainWindow | |
| 354 | + | } | |
| 355 | + | ||
| 356 | + | func (a *App) releaseBrowserWindowTabs(ctx context.Context, windowID string, bw *browserWindow) { | |
| 357 | + | ownedTabs := a.collectBrowserWindowTabs(windowID, bw) | |
| 358 | + | for _, tabID := range sortedTabIDs(ownedTabs) { | |
| 359 | + | a.releaseTabWorkspace(ctx, ownedTabs[tabID]) | |
| 360 | + | } | |
| 361 | + | a.cleanupStaleTabMappingsForWindow(windowID) | |
| 362 | + | } | |
| 363 | + | ||
| 364 | + | func (a *App) collectBrowserWindowTabs(windowID string, bw *browserWindow) map[entity.TabID]*entity.Tab { | |
| 337 | 365 | ownedTabs := make(map[entity.TabID]*entity.Tab) | |
| 338 | - | if removed != nil && removed.tabs != nil { | |
| 339 | - | for _, tab := range removed.tabs.Tabs { | |
| 366 | + | if bw != nil && bw.tabs != nil { | |
| 367 | + | for _, tab := range bw.tabs.Tabs { | |
| 340 | 368 | if tab != nil { | |
| 341 | 369 | ownedTabs[tab.ID] = tab | |
| 342 | 370 | } | |
| 343 | 371 | } | |
| 344 | 372 | } | |
| 345 | - | for tabID, bw := range a.windowForTab { | |
| 346 | - | if bw != nil && bw.id == id { | |
| 347 | - | if bw.tabs != nil { | |
| 348 | - | if tab := bw.tabs.Find(tabID); tab != nil { | |
| 349 | - | ownedTabs[tabID] = tab | |
| 350 | - | continue | |
| 351 | - | } | |
| 352 | - | } | |
| 353 | - | if a.tabs != nil { | |
| 354 | - | ownedTabs[tabID] = a.tabs.Find(tabID) | |
| 355 | - | } | |
| 373 | + | for tabID, owner := range a.windowForTab { | |
| 374 | + | if owner == nil || owner.id != windowID { | |
| 375 | + | continue | |
| 376 | + | } | |
| 377 | + | if tab := tabFromBrowserWindow(owner, tabID); tab != nil { | |
| 378 | + | ownedTabs[tabID] = tab | |
| 379 | + | continue | |
| 380 | + | } | |
| 381 | + | if a.tabs != nil { | |
| 382 | + | ownedTabs[tabID] = a.tabs.Find(tabID) | |
| 356 | 383 | } | |
| 357 | 384 | } | |
| 358 | - | ownedTabIDs := make([]entity.TabID, 0, len(ownedTabs)) | |
| 359 | - | for tabID := range ownedTabs { | |
| 360 | - | ownedTabIDs = append(ownedTabIDs, tabID) | |
| 385 | + | return ownedTabs | |
| 386 | + | } | |
| 387 | + | ||
| 388 | + | func tabFromBrowserWindow(bw *browserWindow, tabID entity.TabID) *entity.Tab { | |
| 389 | + | if bw == nil || bw.tabs == nil { | |
| 390 | + | return nil | |
| 361 | 391 | } | |
| 362 | - | sort.Slice(ownedTabIDs, func(i, j int) bool { return ownedTabIDs[i] < ownedTabIDs[j] }) | |
| 363 | - | for _, tabID := range ownedTabIDs { | |
| 364 | - | a.releaseTabWorkspace(context.Background(), ownedTabs[tabID]) | |
| 392 | + | return bw.tabs.Find(tabID) | |
| 393 | + | } | |
| 394 | + | ||
| 395 | + | func sortedTabIDs(tabs map[entity.TabID]*entity.Tab) []entity.TabID { | |
| 396 | + | ids := make([]entity.TabID, 0, len(tabs)) | |
| 397 | + | for tabID := range tabs { | |
| 398 | + | ids = append(ids, tabID) | |
| 365 | 399 | } | |
| 400 | + | sort.Slice(ids, func(i, j int) bool { return ids[i] < ids[j] }) | |
| 401 | + | return ids | |
| 402 | + | } | |
| 403 | + | ||
| 404 | + | func (a *App) cleanupStaleTabMappingsForWindow(windowID string) { | |
| 366 | 405 | for tabID, bw := range a.windowForTab { | |
| 367 | - | if bw != nil && bw.id == id { | |
| 368 | - | delete(a.workspaceViews, tabID) | |
| 369 | - | delete(a.windowForTab, tabID) | |
| 370 | - | if a.tabs != nil && a.tabs.Find(tabID) != nil { | |
| 371 | - | a.tabs.Remove(tabID) | |
| 372 | - | } | |
| 406 | + | if bw == nil || bw.id != windowID { | |
| 407 | + | continue | |
| 408 | + | } | |
| 409 | + | delete(a.workspaceViews, tabID) | |
| 410 | + | delete(a.windowForTab, tabID) | |
| 411 | + | if a.tabs != nil && a.tabs.Find(tabID) != nil { | |
| 412 | + | a.tabs.Remove(tabID) | |
| 373 | 413 | } | |
| 374 | 414 | } | |
| 375 | - | if removed != nil { | |
| 376 | - | removed.teardownForDestroy() | |
| 415 | + | } | |
| 416 | + | ||
| 417 | + | func (a *App) updateLastFocusedWindowAfterRemoval(removedID string, fallback *browserWindow) { | |
| 418 | + | if a.lastFocusedWindowID != removedID { | |
| 419 | + | return | |
| 377 | 420 | } | |
| 378 | - | if a.contentCoord != nil { | |
| 379 | - | a.contentCoord.ClearPopupNamedContextsForWindow(id) | |
| 421 | + | if fallback != nil { | |
| 422 | + | a.lastFocusedWindowID = fallback.id | |
| 423 | + | return | |
| 380 | 424 | } | |
| 381 | - | delete(a.browserWindows, id) | |
| 382 | - | fallback := a.deterministicBrowserWindowFallback() | |
| 383 | - | if a.lastFocusedWindowID == id { | |
| 384 | - | if fallback != nil { | |
| 385 | - | a.lastFocusedWindowID = fallback.id | |
| 386 | - | } else { | |
| 387 | - | a.lastFocusedWindowID = "" | |
| 388 | - | } | |
| 425 | + | a.lastFocusedWindowID = "" | |
| 426 | + | } | |
| 427 | + | ||
| 428 | + | func (a *App) clearMainBrowserWindowAfterRemoval(fallback *browserWindow) { | |
| 429 | + | a.clearResizeModeBorder() | |
| 430 | + | if fallback != nil { | |
| 431 | + | a.activateBrowserWindow(fallback) | |
| 432 | + | return | |
| 389 | 433 | } | |
| 390 | - | if wasMainWindow { | |
| 391 | - | a.clearResizeModeBorder() | |
| 392 | - | if fallback != nil { | |
| 393 | - | a.activateBrowserWindow(fallback) | |
| 394 | - | return | |
| 395 | - | } | |
| 396 | - | a.lastFocusedWindowID = "" | |
| 397 | - | a.mainWindow = nil | |
| 398 | - | a.keyboardHandler = nil | |
| 399 | - | a.globalShortcutHandler = nil | |
| 400 | - | if a.tabCoord != nil { | |
| 401 | - | a.tabCoord.SetCurrentTarget(coordinator.TabTarget{}) | |
| 402 | - | } | |
| 434 | + | a.lastFocusedWindowID = "" | |
| 435 | + | a.mainWindow = nil | |
| 436 | + | a.keyboardHandler = nil | |
| 437 | + | a.globalShortcutHandler = nil | |
| 438 | + | if a.tabCoord != nil { | |
| 439 | + | a.tabCoord.SetCurrentTarget(coordinator.TabTarget{}) | |
| 403 | 440 | } | |
| 404 | 441 | } | |
| 405 | 442 | ||
| @@ -325,81 +325,118 @@ func (a *App) removeBrowserWindow(id string) { | |||||
| 325 | return | 325 | return | ||
| 326 | } | 326 | } | ||
| 327 | removed := a.browserWindows[id] | 327 | removed := a.browserWindows[id] | ||
| 328 | + | wasMainWindow := removed != nil && removed.mainWindow == a.mainWindow | |||
| 329 | + | ||||
| 330 | + | a.removeBrowserWindowOrder(id) | |||
| 331 | + | a.releaseBrowserWindowTabs(context.Background(), id, removed) | |||
| 332 | + | if removed != nil { | |||
| 333 | + | removed.teardownForDestroy() | |||
| 334 | + | } | |||
| 335 | + | if a.contentCoord != nil { | |||
| 336 | + | a.contentCoord.ClearPopupNamedContextsForWindow(id) | |||
| 337 | + | } | |||
| 338 | + | delete(a.browserWindows, id) | |||
| 328 | 339 | ||||
| 329 | - | // Remove from registration order | 340 | + | fallback := a.deterministicBrowserWindowFallback() |
| 341 | + | a.updateLastFocusedWindowAfterRemoval(id, fallback) | |||
| 342 | + | if wasMainWindow { | |||
| 343 | + | a.clearMainBrowserWindowAfterRemoval(fallback) | |||
| 344 | + | } | |||
| 345 | + | } | |||
| 346 | + | ||||
| 347 | + | func (a *App) removeBrowserWindowOrder(id string) { | |||
| 330 | for i, wid := range a.browserWindowOrder { | 348 | for i, wid := range a.browserWindowOrder { | ||
| 331 | if wid == id { | 349 | if wid == id { | ||
| 332 | a.browserWindowOrder = append(a.browserWindowOrder[:i], a.browserWindowOrder[i+1:]...) | 350 | a.browserWindowOrder = append(a.browserWindowOrder[:i], a.browserWindowOrder[i+1:]...) | ||
| 333 | - | break | 351 | + | return |
| 334 | } | 352 | } | ||
| 335 | } | 353 | } | ||
| 336 | - | wasMainWindow := removed != nil && removed.mainWindow == a.mainWindow | 354 | + | } |
| 355 | + | ||||
| 356 | + | func (a *App) releaseBrowserWindowTabs(ctx context.Context, windowID string, bw *browserWindow) { | |||
| 357 | + | ownedTabs := a.collectBrowserWindowTabs(windowID, bw) | |||
| 358 | + | for _, tabID := range sortedTabIDs(ownedTabs) { | |||
| 359 | + | a.releaseTabWorkspace(ctx, ownedTabs[tabID]) | |||
| 360 | + | } | |||
| 361 | + | a.cleanupStaleTabMappingsForWindow(windowID) | |||
| 362 | + | } | |||
| 363 | + | ||||
| 364 | + | func (a *App) collectBrowserWindowTabs(windowID string, bw *browserWindow) map[entity.TabID]*entity.Tab { | |||
| 337 | ownedTabs := make(map[entity.TabID]*entity.Tab) | 365 | ownedTabs := make(map[entity.TabID]*entity.Tab) | ||
| 338 | - | if removed != nil && removed.tabs != nil { | 366 | + | if bw != nil && bw.tabs != nil { |
| 339 | - | for _, tab := range removed.tabs.Tabs { | 367 | + | for _, tab := range bw.tabs.Tabs { |
| 340 | if tab != nil { | 368 | if tab != nil { | ||
| 341 | ownedTabs[tab.ID] = tab | 369 | ownedTabs[tab.ID] = tab | ||
| 342 | } | 370 | } | ||
| 343 | } | 371 | } | ||
| 344 | } | 372 | } | ||
| 345 | - | for tabID, bw := range a.windowForTab { | 373 | + | for tabID, owner := range a.windowForTab { |
| 346 | - | if bw != nil && bw.id == id { | 374 | + | if owner == nil || owner.id != windowID { |
| 347 | - | if bw.tabs != nil { | 375 | + | continue |
| 348 | - | if tab := bw.tabs.Find(tabID); tab != nil { | 376 | + | } |
| 349 | - | ownedTabs[tabID] = tab | 377 | + | if tab := tabFromBrowserWindow(owner, tabID); tab != nil { |
| 350 | - | continue | 378 | + | ownedTabs[tabID] = tab |
| 351 | - | } | 379 | + | continue |
| 352 | - | } | 380 | + | } |
| 353 | - | if a.tabs != nil { | 381 | + | if a.tabs != nil { |
| 354 | - | ownedTabs[tabID] = a.tabs.Find(tabID) | 382 | + | ownedTabs[tabID] = a.tabs.Find(tabID) |
| 355 | - | } | |||
| 356 | } | 383 | } | ||
| 357 | } | 384 | } | ||
| 358 | - | ownedTabIDs := make([]entity.TabID, 0, len(ownedTabs)) | 385 | + | return ownedTabs |
| 359 | - | for tabID := range ownedTabs { | 386 | + | } |
| 360 | - | ownedTabIDs = append(ownedTabIDs, tabID) | 387 | + | |
| 388 | + | func tabFromBrowserWindow(bw *browserWindow, tabID entity.TabID) *entity.Tab { | |||
| 389 | + | if bw == nil || bw.tabs == nil { | |||
| 390 | + | return nil | |||
| 361 | } | 391 | } | ||
| 362 | - | sort.Slice(ownedTabIDs, func(i, j int) bool { return ownedTabIDs[i] < ownedTabIDs[j] }) | 392 | + | return bw.tabs.Find(tabID) |
| 363 | - | for _, tabID := range ownedTabIDs { | 393 | + | } |
| 364 | - | a.releaseTabWorkspace(context.Background(), ownedTabs[tabID]) | 394 | + | |
| 395 | + | func sortedTabIDs(tabs map[entity.TabID]*entity.Tab) []entity.TabID { | |||
| 396 | + | ids := make([]entity.TabID, 0, len(tabs)) | |||
| 397 | + | for tabID := range tabs { | |||
| 398 | + | ids = append(ids, tabID) | |||
| 365 | } | 399 | } | ||
| 400 | + | sort.Slice(ids, func(i, j int) bool { return ids[i] < ids[j] }) | |||
| 401 | + | return ids | |||
| 402 | + | } | |||
| 403 | + | ||||
| 404 | + | func (a *App) cleanupStaleTabMappingsForWindow(windowID string) { | |||
| 366 | for tabID, bw := range a.windowForTab { | 405 | for tabID, bw := range a.windowForTab { | ||
| 367 | - | if bw != nil && bw.id == id { | 406 | + | if bw == nil || bw.id != windowID { |
| 368 | - | delete(a.workspaceViews, tabID) | 407 | + | continue |
| 369 | - | delete(a.windowForTab, tabID) | 408 | + | } |
| 370 | - | if a.tabs != nil && a.tabs.Find(tabID) != nil { | 409 | + | delete(a.workspaceViews, tabID) |
| 371 | - | a.tabs.Remove(tabID) | 410 | + | delete(a.windowForTab, tabID) |
| 372 | - | } | 411 | + | if a.tabs != nil && a.tabs.Find(tabID) != nil { |
| 412 | + | a.tabs.Remove(tabID) | |||
| 373 | } | 413 | } | ||
| 374 | } | 414 | } | ||
| 375 | - | if removed != nil { | 415 | + | } |
| 376 | - | removed.teardownForDestroy() | 416 | + | |
| 417 | + | func (a *App) updateLastFocusedWindowAfterRemoval(removedID string, fallback *browserWindow) { | |||
| 418 | + | if a.lastFocusedWindowID != removedID { | |||
| 419 | + | return | |||
| 377 | } | 420 | } | ||
| 378 | - | if a.contentCoord != nil { | 421 | + | if fallback != nil { |
| 379 | - | a.contentCoord.ClearPopupNamedContextsForWindow(id) | 422 | + | a.lastFocusedWindowID = fallback.id |
| 423 | + | return | |||
| 380 | } | 424 | } | ||
| 381 | - | delete(a.browserWindows, id) | 425 | + | a.lastFocusedWindowID = "" |
| 382 | - | fallback := a.deterministicBrowserWindowFallback() | 426 | + | } |
| 383 | - | if a.lastFocusedWindowID == id { | 427 | + | |
| 384 | - | if fallback != nil { | 428 | + | func (a *App) clearMainBrowserWindowAfterRemoval(fallback *browserWindow) { |
| 385 | - | a.lastFocusedWindowID = fallback.id | 429 | + | a.clearResizeModeBorder() |
| 386 | - | } else { | 430 | + | if fallback != nil { |
| 387 | - | a.lastFocusedWindowID = "" | 431 | + | a.activateBrowserWindow(fallback) |
| 388 | - | } | 432 | + | return |
| 389 | } | 433 | } | ||
| 390 | - | if wasMainWindow { | 434 | + | a.lastFocusedWindowID = "" |
| 391 | - | a.clearResizeModeBorder() | 435 | + | a.mainWindow = nil |
| 392 | - | if fallback != nil { | 436 | + | a.keyboardHandler = nil |
| 393 | - | a.activateBrowserWindow(fallback) | 437 | + | a.globalShortcutHandler = nil |
| 394 | - | return | 438 | + | if a.tabCoord != nil { |
| 395 | - | } | 439 | + | a.tabCoord.SetCurrentTarget(coordinator.TabTarget{}) |
| 396 | - | a.lastFocusedWindowID = "" | |||
| 397 | - | a.mainWindow = nil | |||
| 398 | - | a.keyboardHandler = nil | |||
| 399 | - | a.globalShortcutHandler = nil | |||
| 400 | - | if a.tabCoord != nil { | |||
| 401 | - | a.tabCoord.SetCurrentTarget(coordinator.TabTarget{}) | |||
| 402 | - | } | |||
| 403 | } | 440 | } | ||
| 404 | } | 441 | } | ||
| 405 | 442 | ||||