Skip to content

[PM-28227] fix: Keep keyboard alive in autofillText mode via SwiftUI @FocusState anchor#2599

Open
fedemkr wants to merge 4 commits into
mainfrom
PM-28227/workaround-autofill-text-keyboard-always
Open

[PM-28227] fix: Keep keyboard alive in autofillText mode via SwiftUI @FocusState anchor#2599
fedemkr wants to merge 4 commits into
mainfrom
PM-28227/workaround-autofill-text-keyboard-always

Conversation

@fedemkr
Copy link
Copy Markdown
Member

@fedemkr fedemkr commented Apr 30, 2026

🎟️ Tracking

PM-28227

📔 Objective

Fixes the autofillText mode keyboard being dismissed (and the extension closing ~5 seconds later) when the user cancels an inline vault search.

In autofillText mode the extension is shown as a keyboard panel (InputUI) backed by a Remote Text Input (RTI) session. Any text field that becomes first responder via UIKit's explicit becomeFirstResponder() causes the RTI system to tag the session with delayEndInputSession:YES (fromBecomeFirstResponder:1), starting a ~5-second countdown that tears down the keyboard panel and dismisses the extension when it fires.

The previous approach used a UIKit KeyboardAnchorTextField and called becomeFirstResponder() on it when the search Cancel button was tapped — triggering exactly that timer. This PR replaces that path with a hidden SwiftUI TextField bound to a @FocusState enum (FocusableField.anchor / .search). SwiftUI's internal focus machinery uses fromBecomeFirstResponder:0delayEndInputSession:NO, so every focus handoff (including Cancel → anchor) is a clean RTI session continuation with no countdown timer.

As a secondary fix, .searchable / UISearchController is replaced with a custom inline search bar in autofillText mode, since UISearchController also calls explicit becomeFirstResponder() on resign.

📸 Screenshots

N/A — keyboard extension panel behaviour, no visual UI change.

fedemkr added 4 commits April 30, 2026 11:49
…chor in a workaround.

Replace the UIKit `KeyboardAnchorTextField.becomeFirstResponder()` call on
Cancel with a hidden SwiftUI `TextField` bound to a `@FocusState` enum.
UIKit's explicit `becomeFirstResponder()` produces `fromBecomeFirstResponder:1`
→ `delayEndInputSession:YES`, starting a ~5-second RTI countdown that
dismisses the extension. SwiftUI's focus machinery uses
`fromBecomeFirstResponder:0` → `delayEndInputSession:NO`, keeping the
keyboard panel alive indefinitely.
@github-actions github-actions Bot added app:password-manager Bitwarden Password Manager app context t:bug Change Type - Bug labels Apr 30, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 30, 2026

Codecov Report

❌ Patch coverage is 10.07752% with 116 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.15%. Comparing base (3ecf4ce) to head (d8c9a0e).
⚠️ Report is 47 commits behind head on main.

Files with missing lines Patch % Lines
...ult/Vault/AutofillList/VaultAutofillListView.swift 13.04% 60 Missing ⚠️
...llExtension/CredentialProviderViewController.swift 0.00% 56 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2599      +/-   ##
==========================================
- Coverage   87.20%   87.15%   -0.06%     
==========================================
  Files        1894     1895       +1     
  Lines      167519   167838     +319     
==========================================
+ Hits       146087   146279     +192     
- Misses      21432    21559     +127     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@fedemkr fedemkr marked this pull request as ready for review May 12, 2026 15:03
@fedemkr fedemkr requested review from a team and matt-livefront as code owners May 12, 2026 15:03
@fedemkr fedemkr added the bug Something isn't working label May 13, 2026
Comment on lines +541 to +543
/// `useKeyboard` is computed as `allowsSystemInputView && !hasCustomInputView && responderRequiresKeyboard`.
/// Any non-nil `inputView` makes `!hasCustomInputView = 0` → `useKeyboard = 0` → `delayEndInputSession:YES`
/// on every delegate transition, firing `endRemoteTextInputSessionWithID` after ~5 seconds.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

❓ Is this still a valid comment? I don't see useKeyboard declared anywhere? Some of the comments throughout this PR seem overly verbose, do you think these are all helpful for future context?

Comment on lines +319 to +327
// Create the anchor directly here — `self.context` is nil at this point so
// `installKeyboardAnchor`'s context guard would return early. This is the only
// synchronous call site where the view is in the window, no children exist, and
// nothing else can steal focus. `initializeApp` launches an async Task, so anchoring
// inside it would be too late.
let anchor = KeyboardAnchorTextField(frame: .zero)
view.addSubview(anchor)
keyboardAnchor = anchor
anchor.becomeFirstResponder()
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤔 This is very similar to installKeyboardAnchor(). I see why it's duplicated from the comment, but I wonder if we could simplify by:

  1. Keep this logic here, which creates and installs KeyboardAnchorTextField.
  2. In show(child:) the keyboard anchor should already exist at that point if you're in the autofill text context. So could we replace installKeyboardAnchor() with keyboardAnchor?.becomeFirstResponder() which will only cause it to become first responder if it has already been created in this flow here?
  3. Remove installKeyboardAnchor().

Comment on lines +329 to +335
// Safety net: reclaim anchor when keyboard starts hiding and anchor isn't FR.
NotificationCenter.default.addObserver(
self,
selector: #selector(keyboardWillHide),
name: UIResponder.keyboardWillHideNotification,
object: nil,
)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤔 Could we put this and keyboardWillHide() in KeyboardAnchorTextField? That would help keep this isolated to KeyboardAnchorTextField and hopefully easier to remove if the root issue is fixed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app:password-manager Bitwarden Password Manager app context bug Something isn't working t:bug Change Type - Bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants