[PM-37255] feat: Integrate fill-assist targeting rules into autofill parser#7066
[PM-37255] feat: Integrate fill-assist targeting rules into autofill parser#7066aj-rosado wants to merge 24 commits into
Conversation
…assist-data-layer
…emoved unnecessary deserialization tests
…assist-data-layer
…ow updates Updating code to schema with required values
…assist-data-layer
🤖 Bitwarden Claude Code ReviewOverall Assessment: APPROVE Reviewed the end-to-end wiring of the fill-assist targeting-rules pipeline into Code Review Details
Both are test-only cleanups; no production-behavior or correctness impact. |
| ?: return AutofillRequest.Unfillable | ||
|
|
||
| val packageName = | ||
| traversalDataList.buildPackageNameOrNull(assistStructure = assistStructure) |
There was a problem hiding this comment.
Can we clean this up a bit
val packageName = traversalDataList.buildPackageNameOrNull(
assistStructure = assistStructure,
)| ?: viewsLists | ||
| .flatten() | ||
| .filter { it !is AutofillView.Unused }) | ||
| .map { it.updateWebsiteIfNecessary(website = urlBarWebsite) } |
There was a problem hiding this comment.
I like that this was moved out into it's own method.
What do you think of this minor update:
private fun List<ViewNodeTraversalData>.toAutofillViews(
urlBarWebsite: String?,
): List<AutofillView> {
val viewsLists = map { it.autofillViews }
val autofillViewLists = viewsLists
.filter { views -> views.any { it.data.isFocused } }
.flatten()
.filter { it !is AutofillView.Unused }
.takeUnless { it.isEmpty() }
?: viewsLists
.flatten()
.filter { it !is AutofillView.Unused }
return autofillViewLists.map { it.updateWebsiteIfNecessary(website = urlBarWebsite) }
}I have never found that original block of code particularly readable 😄
| .map { it.updateWebsiteIfNecessary(website = urlBarWebsite) } | ||
|
|
||
| // Find the focused view, or fallback to the first fillable item on the screen (so | ||
| // we at least have something to hook into) |
There was a problem hiding this comment.
Can we leave in the comments in place? Autofill is confusing enough as it is, I want to make sure we leave goo notes for the next person.
// Find the focused view, or fallback to the first fillable item on the screen (so
// we at least have something to hook into)
val focusedView = autofillViews
.firstOrNull { it.data.isFocused }
?: autofillViews.firstOrNull()
// The view is unfillable if there are no focused views.
?: return AutofillRequest.Unfillable|
|
||
| val blockListedURIs = settingsRepository.blockedAutofillUris + BLOCK_LISTED_URIS | ||
| if (blockListedURIs.contains(uri)) { | ||
| // The view is unfillable if the URI is block listed. |
There was a problem hiding this comment.
Same here, let's leave the comments in for posterity.
|
|
||
| // Get inline information if available | ||
| val isInlineAutofillEnabled = settingsRepository.isInlineAutofillEnabled | ||
| Timber.d("Autofill request isInlineEnabled=$isInlineAutofillEnabled -- ${fillRequest?.id}") |
There was a problem hiding this comment.
Why are we removing this?
| * when the feature flag is enabled and the host rules cover the current partition type; | ||
| * otherwise returns the heuristic [autofillViews]. | ||
| */ | ||
| private fun resolveEffectiveViews( |
There was a problem hiding this comment.
What do you think about making this an extension function?
private fun List<AutofillView>.toEffectiveViews(
assistStructure: AssistStructure,
uri: String?,
focusedView: AutofillView,
urlBarWebsite: String?,
): List<AutofillView> {| ?.takeUnless { it.startsWith("androidapp://") } | ||
| ?.toUri() | ||
| ?.host | ||
| ?.takeIf { featureFlagManager.getFeatureFlag(FlagKey.FillAssistTargetingRules) } |
There was a problem hiding this comment.
Can we just return early instead of putting this check in the chain?
if (!featureFlagManager.getFeatureFlag(FlagKey.FillAssistTargetingRules)) {
return autofillViews
}| val urlBarWebsite = traversalDataList | ||
| .flatMap { it.urlBarWebsites } | ||
| .firstOrNull() | ||
| val autofillViews = traversalDataList.toAutofillViews(urlBarWebsite = urlBarWebsite) |
There was a problem hiding this comment.
The new buildFillAssistViews is awfully similar to the existing logic for parsing the assist structure. It's hard for me to tell if it is possible but can we optimize this to parse the structure only once?
🎟️ Tracking
https://bitwarden.atlassian.net/browse/PM-37255
📔 Objective
Wires the fill-assist targeting-rules pipeline end-to-end into the Android autofill framework.
When the
FillAssistTargetingRulesfeature flag is enabled and rules are available for the current host, the autofill parser replaces heuristic field detection with site-specific CSS-selector-based matching. Unmatched nodes are excluded entirely — there is no heuristic fallback when rules are active.What's included:
FillAssistApi,FillAssistService,FillAssistManifestJson,FillAssistFormsJson: fetches a versioned manifest and forms JSON from the fill-assist CDN endpoint.FillAssistManagerImpl,FillAssistDiskSource: parses CSS selectors intoFillAssistRules(tag, id, name, type, role constraints per field), caches rules on disk, syncs on server-config change with a 6-hour re-fetch throttle.>>>Shadow DOM notation, space-separated CSS descendant selectors (split on whitespace outside[…]attribute brackets to preserve attribute values that contain spaces),#idshorthand, and[attr='value']/[attr="value"]attribute selectors.AutofillParserImpllooks up host rules for the focused view's URI and callsAssistStructure.buildFillAssistViewsto replace the heuristic view list when rules match.FillAssistViewNodeExtensions.traverseForFillAssisttraverses theAssistStructuretree;HtmlInfoExtensions.matchesSelectorClausedoes the per-node attribute comparison (kept inHtmlInfoExtensionsalongsidehints()sinceHtmlInfo.attributesusesandroid.util.Pairand is untestable in unit tests).FillAssistManagerTest,FillAssistServiceTest,FillAssistViewNodeExtensionsTest(all previously commented-out tests now passing viamockkStatic(HtmlInfo::matchesSelectorClause)), andAutofillParserTestsupdated for the new constructor parameters.