diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/manager/FillAssistManagerImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/manager/FillAssistManagerImpl.kt index be587c472d..7fbdf3d870 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/manager/FillAssistManagerImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/manager/FillAssistManagerImpl.kt @@ -194,17 +194,24 @@ private fun parseCompositeSelectorArray(element: JsonElement): List>>")) return null - if (selector.trimStart().startsWith(".")) return null + // For shadow DOM / iframe selectors (>>>), extract the last segment — the actual target + // element. Android's autofill framework may expose these elements via htmlInfo when they + // are reachable (e.g. open shadow roots), so we parse their attributes for matching. + val effective = if (selector.contains(">>>")) { + selector.substringAfterLast(">>>").trim() + } else { + selector + } + if (effective.trimStart().startsWith(".")) return null - val tag = TAG_REGEX.find(selector)?.groupValues?.get(1) + val tag = TAG_REGEX.find(effective)?.groupValues?.get(1) var id: String? = null var name: String? = null var type: String? = null var role: String? = null - ATTRIBUTE_REGEX.findAll(selector).forEach { match -> + ATTRIBUTE_REGEX.findAll(effective).forEach { match -> val attrName = match.groupValues[1] val attrValue = match.groupValues[2] when (attrName) { @@ -217,7 +224,7 @@ internal fun parseSingleSelector(selector: String): SelectorClause? { // Fallback: extract id from #shorthand (e.g. input#oid) when not present as [id='...']. if (id == null) { - id = ID_SHORTHAND_REGEX.find(selector)?.groupValues?.get(1) + id = ID_SHORTHAND_REGEX.find(effective)?.groupValues?.get(1) } return SelectorClause(tag = tag, id = id, name = name, type = type, role = role) diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/manager/FillAssistManagerTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/manager/FillAssistManagerTest.kt index 13619ebad0..67c03f8daf 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/manager/FillAssistManagerTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/manager/FillAssistManagerTest.kt @@ -320,8 +320,31 @@ class FillAssistManagerTest { } @Test - fun `parseSingleSelector returns null for shadow DOM selector`() { - assertNull(parseSingleSelector("div#container >>> input#field")) + fun `parseSingleSelector extracts last segment of shadow DOM selector`() { + assertEquals( + FillAssistRules.SelectorClause( + tag = "input", + id = "field", + name = null, + type = null, + role = null, + ), + parseSingleSelector("div#container >>> input#field"), + ) + } + + @Test + fun `parseSingleSelector extracts last segment of multi-level shadow DOM selector`() { + assertEquals( + FillAssistRules.SelectorClause( + tag = "input", + name = "password", + id = null, + type = null, + role = null, + ), + parseSingleSelector("div#form-container >>> form > div >>> input[name='password']"), + ) } @Test