Skip to content

Review fixes for #298: teardown resolves pending permissions + is terminal - #300

Merged
davertay-j merged 9 commits into
dt/cursor/topaz-multiple-tab-support-201bfrom
dt/cursor/pr-298-full-review-4cbf
Aug 10, 2026
Merged

Review fixes for #298: teardown resolves pending permissions + is terminal#300
davertay-j merged 9 commits into
dt/cursor/topaz-multiple-tab-support-201bfrom
dt/cursor/pr-298-full-review-4cbf

Conversation

@davertay-j

@davertay-j davertay-j commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Review fixes for #298 (stacked)

Stacked on #298 (dt/cursor/topaz-multiple-tab-support-201b) — the first 9 commits here are #298 itself; the top nine commits are new (2 critical fixes + 7 follow-up suggestions from the review). Draft PR opened primarily to run CI on the stacked commits (CI only triggers on PRs targeting main). Intended to be merged into #298's branch (or rebased once #298 lands), not merged to main independently.

Critical fixes

1. Resolve pending permissions request on session teardown (519f629)

WebPageModel.teardown() now denies (and thereby releases) a parked permissions continuation before releasing the web view, and resets presentPermissionsDialog. Without this, a background tab whose page requested Bluetooth authorization — the permissions alert chrome only mounts for the active tab — would, upon eviction, leak the CheckedContinuation, hold the WKScriptMessage reply open forever, and leave the page's promise permanently unsettled.

2. Make session teardown terminal: webView() refuses to resurrect (aca6c8a)

teardown() marks the model as torn down (isTornDown) and webView() returns nil from then on instead of lazily creating a replacement. Previously a stray host update after eviction could silently spin up a fresh WKWebView and re-initialize the session controller on a model that TabSessionCache no longer accounts for — a zombie session that would never be evicted or torn down again.

Follow-up fixes (review suggestions)

  • baeec30TabGatedDeviceSelector also gates showAdvertisement: a background tab's transient scan (before its awaitSelection is rejected) can no longer inject its advertisements into the active tab's open picker.
  • 6fc2bb0performInitialLoad now mirrors the submit path's staleness guard: a session evicted while the web config loaded no longer receives an orphaned WebContainerModel.
  • 3dee714 — Memory warning while the tab grid is showing evicts all sessions (nothing is displayed, so the still-pinned last-viewed tab is background too).
  • 742a5b1 — Re-activating a live session clears a stale goBackToPriorPage closure; only the window.open flow re-installs it.
  • 96948c7JsEventDeliveryQueue bounds each delivery with a 30 s timeout: a page that never resumes callAsyncJavaScript's (non-cancellable) callback converges like an overflow instead of stranding the drain task forever.
  • a46fea2 — The outgoing session resigns keyboard focus when the displayed tab changes, so a web view moving to the keep-alive underlay can't leave a stale keyboard over the incoming view. (The underlay keep-alive technique itself stays as-is, pending Integrated on-device verification of multi-tab + BLE behavior #297 on-device verification.)
  • 5060fb1 — "Remove all data" awaits the (now fully async) web data wipe before resetting sessions, so the reloaded page can't race the removal and re-persist wiped data. New SettingsTests target with an ordering test.

Tests

  • New WebViewTests/WebPageModelTests.swift (5 tests): teardown denies pending permissions (would hang pre-fix), teardown idempotent/terminal, webView() stable pre-teardown and nil post-teardown.
  • TabGatedDeviceSelectorTests: background showAdvertisement never reaches the picker.
  • JsEventDeliveryQueueTests: wedged delivery times out and abandons the page; timely deliveries don't trip.
  • New SettingsTests/SettingsModelTests.swift: session reset fires only after the wipe completes.
Open in Web Open in Cursor 

cursoragent and others added 2 commits July 15, 2026 16:36
teardown() now denies (and thereby releases) a parked permissions
continuation before releasing the web view. Without this, a background
tab whose page requested Bluetooth authorization - the permissions alert
chrome only mounts for the active tab - would, upon eviction, leak the
CheckedContinuation, hold the WKScriptMessage reply open forever, and
leave the page's promise permanently unsettled.

Co-authored-by: David T <davertay-j@users.noreply.github.com>
teardown() now marks the model as torn down and webView() returns nil
from then on instead of lazily creating a replacement. Previously a
stray host update after eviction could silently spin up a fresh
WKWebView and re-initialize the session controller on a model that the
TabSessionCache no longer accounts for - a zombie session that would
never be evicted or torn down again. View hosts treat a nil web view as
a stale mount and leave their container empty for SwiftUI to remove.

Co-authored-by: David T <davertay-j@users.noreply.github.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit aca6c8a. Configure here.

func evictAll() {
for tabIndex in lruOrder {
evict(tabIndex)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Evict loops skip live sessions

High Severity

evictAll() and evictAllExceptActive() walk lruOrder with for-in while evict(_:) removes entries from that same array. Shrinking the array during iteration skips tab indexes, so some cached sessions are never torn down. “Remove all data” and memory-pressure paths can leave live web views, JS contexts, and BLE state running after they should be gone.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit aca6c8a. Configure here.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dude, that isn't how Swift works.

cursoragent and others added 7 commits July 15, 2026 17:53
RequestDevice attaches its advertisement listener and starts scanning
before awaitSelection() reaches the visibility gate, so a background
tab's requestDevice briefly forwards advertisements - filtered by that
tab's options - to the shared selector. If the active tab has a picker
open, those ads would be injected into its list. Gate showAdvertisement
on tab visibility as well so they are dropped instead.

Co-authored-by: David T <davertay-j@users.noreply.github.com>
performInitialLoad's task completed unconditionally: a session evicted
(or replaced) while the web config loaded would still receive a fully
built WebContainerModel - an orphaned container graph nobody accounts
for. Mirror the staleness guard the submit path already has: only
populate the loading model if it still belongs to the displayed session
or a cached one.

Co-authored-by: David T <davertay-j@users.noreply.github.com>
The pin persists while the grid is displayed so that returning to the
last-viewed tab is cheap, but under actual memory pressure that meant
one invisible WKWebView survived evictAllExceptActive(). When no tab is
displayed, nothing is worth protecting: evict everything and let every
tab revert to reload-on-revisit.

Co-authored-by: David T <davertay-j@users.noreply.github.com>
goBackToPriorPage was installed on the opened session's nav bar by
window.open handling and then persisted for the session's lifetime, so
revisiting that tab later (e.g. from the grid) still showed an enabled
back button wired to a long-departed opener context. Activation now
resets the closure; the window.open flow re-installs it immediately
after activating, so genuine opener-back is unaffected.

Co-authored-by: David T <davertay-j@users.noreply.github.com>
callAsyncJavaScript's completion is not cancellable, so a page that
never resumes it (wedged, or the continuation lost inside WebKit) would
park the drain task forever while the buffer filled toward overflow.
Deliveries now race a 30s timeout; a timeout means the page is beyond
recovery and converges exactly like an overflow - cancel the queue and
report so the owner tears the session down. The losing delivery is left
to resolve (or leak inside WebKit) on its own.

Co-authored-by: David T <davertay-j@users.noreply.github.com>
A web view re-parented into the keep-alive underlay could remain first
responder, leaving its keyboard floating over the newly displayed tab
or the grid. The outgoing session now relinquishes editing focus
whenever the displayed session changes.

The other half of the review note - the underlay's opacity(0.001)
keep-alive technique being heuristic - stays deferred to the #297
on-device verification (checklist step 10 with the documented
offscreen-frame fallback); changing the hosting strategy blind could
break BLE keep-alive.

Co-authored-by: David T <davertay-j@users.noreply.github.com>
cleanWebCache() was fire-and-forget, so 'Remove all data' rebuilt and
reloaded the displayed tab while the removal was still in flight - the
reloading page could read (and re-persist) cookies and storage that
were about to be wiped. The wipe is now awaited (single async
removeData over all types since the distant past, which also covers the
per-record fan-out the old code did) before the session reset fires.
The wipe hook is injectable on SettingsModel for the new SettingsTests
ordering test.

Co-authored-by: David T <davertay-j@users.noreply.github.com>
@davertay-j
davertay-j marked this pull request as ready for review August 10, 2026 23:06
@davertay-j
davertay-j changed the base branch from main to dt/cursor/topaz-multiple-tab-support-201b August 10, 2026 23:06
@davertay-j
davertay-j merged commit 1c4a2de into dt/cursor/topaz-multiple-tab-support-201b Aug 10, 2026
4 checks passed
@davertay-j
davertay-j deleted the dt/cursor/pr-298-full-review-4cbf branch August 10, 2026 23:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants