Skip to content

[PM-34491] Fill Targeting Rules implementation gaps#20970

Closed
jprusik wants to merge 9 commits into
mainfrom
pm-34491
Closed

[PM-34491] Fill Targeting Rules implementation gaps#20970
jprusik wants to merge 9 commits into
mainfrom
pm-34491

Conversation

@jprusik

@jprusik jprusik commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

🎟️ Tracking

PM-34491

📔 Objective

Fixed:

  • newPassword selector arrays match up to two elements (instead of one); instead of stopping on the first new password field, fill will handle up to two new password fields if specified in the targeting rules.

Deferred:

@jprusik jprusik self-assigned this Jun 1, 2026
@jprusik jprusik added the ai-review-vnext Request a Claude code review using the vNext workflow label Jun 1, 2026
@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

🤖 Bitwarden Claude Code Review

Overall Assessment: APPROVE

This PR closes several gaps in the Fill Targeting Rules implementation across the browser autofill pipeline: it stops newPassword targets from being filled with the current cipher password during normal Login fills, routes targeted newPassword fields to the password-generation inline menu, lets newPassword selector arrays match up to two elements (for confirm-password fields), bypasses heuristic filtering for targeted newPassword fields, and gates the save-login prompt so it does not surface on a current-password fill during password generation. Container selectors now build proper AutofillForm records and associate fields with them. Test coverage is broad and targeted at each fix, and the latest commit corrects a subtle issue where a failed iframe routing was previously consumed as a successful match slot.

Code Review Details

No findings.

The unresolved threads from earlier rounds of review have been substantively addressed by later commits: the hasAnyPassword/hasNewPassword reordering suggestion was applied in the cleanup commit, the as AutofillTargetingRuleType assertion was replaced with the isAutofillTargetingRuleType type guard, MAX_MATCHES_BY_FIELD_TYPE was moved into an Object.freeze declaration below the imports, and the dispatch-style refactor of setTargetedFieldFillType was applied. Remaining items flagged by reviewers (early-return style in setupSubmitListener*, the setupSubmitListenerOnFieldWithForms argument concern, and the null vs. undefined preference) have been explicitly moved to PM-38476 and are out of scope here.

The iframe-routing match-count heuristic is known to be imperfect — an outer-hop iframe match counts toward maxMatches even though the inner selector may not resolve inside the iframe. This is documented inline with a FIXME and is an intentional, tracked tradeoff to avoid blocking the broader fix; the no-source iframe case is now correctly excluded.

@jprusik jprusik marked this pull request as ready for review June 1, 2026 22:40
@jprusik jprusik requested a review from a team as a code owner June 1, 2026 22:40
@jprusik jprusik requested a review from audreyality June 1, 2026 22:40
@codecov

codecov Bot commented Jun 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 70.45455% with 26 lines in your changes missing coverage. Please review.
✅ Project coverage is 49.16%. Comparing base (6b5bde7) to head (e7d9f88).
⚠️ Report is 4 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...ofill/services/collect-autofill-content.service.ts 69.69% 13 Missing and 7 partials ⚠️
...ofill/services/autofill-overlay-content.service.ts 55.55% 3 Missing and 1 partial ⚠️
.../browser/src/autofill/services/autofill.service.ts 66.66% 0 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #20970      +/-   ##
==========================================
+ Coverage   49.11%   49.16%   +0.05%     
==========================================
  Files        4052     4052              
  Lines      126859   126955      +96     
  Branches    19348    19379      +31     
==========================================
+ Hits        62304    62415     +111     
+ Misses      59969    59949      -20     
- Partials     4586     4591       +5     

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

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@audreyality audreyality left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

♻️ I'm concerned about how verbose this code is. I've pointed out a few places where this impedes readability and in one case it appears to be related to a bug.

Comment thread apps/browser/src/autofill/background/overlay.background.ts Outdated
Comment thread apps/browser/src/autofill/models/autofill-form.ts Outdated
Comment thread apps/browser/src/autofill/services/autofill-overlay-content.service.ts Outdated
Comment on lines 452 to 474
if (
!elementIsFillableFormField(formFieldElement) ||
autofillFieldData.inlineMenuFillType === CipherType.Card
) {
return;
}

if (
autofillFieldData.targeted &&
autofillFieldData.form &&
this.targetedSubmitButtonsByFormOpid.has(autofillFieldData.form)
) {
this.setupSubmitListenerOnTargetedField(formFieldElement, autofillFieldData.form);
return;
}

if (autofillFieldData.form) {
await this.setupSubmitListenerOnFieldWithForms(formFieldElement);
return;
}

await this.setupSubmitListenerOnFormlessField(formFieldElement);
return;

@audreyality audreyality Jun 2, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🎨 Consider using if/else if/else instead of early returns. This makes it more clear that this function is performing dispatch.

    if (
      !elementIsFillableFormField(formFieldElement) ||
      autofillFieldData.inlineMenuFillType === CipherType.Card
    ) {
      return;
    } else if (
      autofillFieldData.targeted &&
      autofillFieldData.form &&
      this.targetedSubmitButtonsByFormOpid.has(autofillFieldData.form)
    ) {
      this.setupSubmitListenerOnTargetedField(formFieldElement, autofillFieldData.form);
    } else if (autofillFieldData.form) {
      await this.setupSubmitListenerOnFieldWithForms(formFieldElement);
    } else {
      await this.setupSubmitListenerOnFormlessField(formFieldElement);
    }

🤔 It also reveals a possible bug. The autofillFieldData.form branch should probably be:

await this.setupSubmitListenerOnFieldWithForms(autofillFieldData.form);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The autofillFieldData.form branch should probably be:

fwiw, my understanding of setupSubmitListenerOnFieldWithForms: this is meant to take a field and any form associated with it that isn't already in formElements is added to it and given submit event listeners

Comment thread apps/browser/src/autofill/services/autofill-overlay-content.service.ts Outdated
Comment thread apps/browser/src/autofill/services/collect-autofill-content.service.ts Outdated
Comment thread apps/browser/src/autofill/services/collect-autofill-content.service.ts Outdated
Comment on lines +439 to +451
for (const selector of container) {
if (typeof selector !== "string") {
continue;
}
if (this.domQueryService.findIframeCrossing(selector)) {
continue;
}
const match = this.domQueryService.queryDeepSelector(selector);
if (match instanceof HTMLElement) {
return match;
}
}
return null;

@audreyality audreyality Jun 2, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🎨 Consider using an array method. Control flow is harder to follow than comparison logic. If you'd like to preserve the return match breakpoint, split the find and return onto different lines.

Suggested change
for (const selector of container) {
if (typeof selector !== "string") {
continue;
}
if (this.domQueryService.findIframeCrossing(selector)) {
continue;
}
const match = this.domQueryService.queryDeepSelector(selector);
if (match instanceof HTMLElement) {
return match;
}
}
return null;
const found = container.find(
(s) =>
typeof selector s === "string" &&
!this.domQueryService.findIframeCrossing(selector) &&
this.domQueryService.queryDeepSelector(selector) instanceof HTMLElement
);
return found ?? null;

@sonarqubecloud

Copy link
Copy Markdown

@jprusik jprusik requested a review from audreyality June 11, 2026 19:03
@jprusik

jprusik commented Jun 11, 2026

Copy link
Copy Markdown
Contributor Author

Apologies for the rebase - I had to pull in some important related changes that have been merged to main.

@bw-ghapp

bw-ghapp Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Changes in this PR impact the Autofill experience of the browser client

BIT has tested the core experience with these changes and the feature flag configuration used by vault.bitwarden.com.

✅ Fortunately, these BIT tests have passed! 🎉

@bw-ghapp

bw-ghapp Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Changes in this PR impact the Autofill experience of the browser client

BIT has tested the core experience with these changes and all feature flags disabled.

✅ Fortunately, these BIT tests have passed! 🎉

Comment on lines +366 to +373
for (const selector of selectorAlternatives ?? []) {
if (matchCount >= maxMatches) {
break;
}

if (typeof selector !== "string") {
continue;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🎨 The number of continue branches in this loop suggest that pre-filtering selectorAlternatives would be worthwhile.

form.htmlAction = isFormElement
? (this.getFormActionAttribute(element as ElementWithOpId<HTMLFormElement>) ?? "")
: "";
form.htmlMethod = isFormElement

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

⛏️ The type assertion is incorrect. getFormActionAttribute() never uses opid, however, so you can fix this by narrowing the argument's type to HtmlFormElement.

@jprusik

jprusik commented Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

Remaining work has been carried over to #21364 - linking this pull request to preserve existing feedback.

@jprusik jprusik closed this Jun 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-review-vnext Request a Claude code review using the vNext workflow

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants