diff --git a/dist/autofill-debug.js b/dist/autofill-debug.js index 4d9064cd0..146aae452 100644 --- a/dist/autofill-debug.js +++ b/dist/autofill-debug.js @@ -11258,6 +11258,8 @@ class FormAnalyzer { signalType: `external link: ${string}`, shouldFlip }); + } else if (el.matches(this.matching.cssSelector('enabledCheckboxSelector')) && this.isPersistentSigninText(string)) { + this.decreaseSignalBy(3, 'checkbox: persistent sign-in'); } else { // any other case const isH1Element = el.tagName === 'H1'; @@ -11349,6 +11351,15 @@ class FormAnalyzer { this._isCCForm = Boolean(textMatches && deDupedMatches.size > 1); return this._isCCForm; } + + /** + * Checks if the text is a persistent sign-in text, e.g "stay signed in" or "remember me" + * @param {string} text + * @returns {boolean} + */ + isPersistentSigninText(text) { + return /stay.?signed.?in|remember.?me/i.test(text); + } } var _default = exports.default = FormAnalyzer; @@ -12855,6 +12866,7 @@ const matchingConfiguration = exports.matchingConfiguration = { formInputsSelectorWithoutSelect: 'input:not([type=button]):not([type=checkbox]):not([type=color]):not([type=file]):not([type=hidden]):not([type=radio]):not([type=range]):not([type=reset]):not([type=image]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week]):not([name^=fake i]):not([data-description^=dummy i]):not([name*=otp]):not([autocomplete="fake"]):not([placeholder^=search i]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=month]),[autocomplete=username]', formInputsSelector: 'input:not([type=button]):not([type=checkbox]):not([type=color]):not([type=file]):not([type=hidden]):not([type=radio]):not([type=range]):not([type=reset]):not([type=image]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week]):not([name^=fake i]):not([data-description^=dummy i]):not([name*=otp]):not([autocomplete="fake"]):not([placeholder^=search i]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=month]),[autocomplete=username],select', safeUniversalSelector: '*:not(select):not(option):not(script):not(noscript):not(style):not(br)', + enabledCheckboxSelector: 'input[type="checkbox"]:not([disabled])', emailAddress: 'input:not([type])[name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]), input[type=""][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([type=tel]), input[type=text][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=title i]):not([name*=tab i]):not([name*=code i]), input:not([type])[placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]), input[type=text][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]), input[type=""][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]), input[type=email], input[type=text][aria-label*=email i]:not([aria-label*=search i]), input:not([type])[aria-label*=email i]:not([aria-label*=search i]), input[name=username][type=email], input[autocomplete=username][type=email], input[autocomplete=username][placeholder*=email i], input[autocomplete=email],input[name="mail_tel" i],input[value=email i]', username: 'input:not([type=button]):not([type=checkbox]):not([type=color]):not([type=file]):not([type=hidden]):not([type=radio]):not([type=range]):not([type=reset]):not([type=image]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week]):not([name^=fake i]):not([data-description^=dummy i]):not([name*=otp]):not([autocomplete="fake"]):not([placeholder^=search i]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=month])[autocomplete^=user i],input[name=username i],input[name="loginId" i],input[name="userid" i],input[id="userid" i],input[name="user_id" i],input[name="user-id" i],input[id="login-id" i],input[id="login_id" i],input[id="loginid" i],input[name="login" i],input[name=accountname i],input[autocomplete=username i],input[name*=accountid i],input[name="j_username" i],input[id="j_username" i],input[name="uwinid" i],input[name="livedoor_id" i],input[name="ssousername" i],input[name="j_userlogin_pwd" i],input[name="user[login]" i],input[name="user" i],input[name$="_username" i],input[id="lmSsoinput" i],input[name="account_subdomain" i],input[name="masterid" i],input[name="tridField" i],input[id="signInName" i],input[id="w3c_accountsbundle_accountrequeststep1_login" i],input[id="username" i],input[name="_user" i],input[name="login_username" i],input[name^="login-user-account" i],input[id="loginusuario" i],input[name="usuario" i],input[id="UserLoginFormUsername" i],input[id="nw_username" i],input[can-field="accountName"],input[name="login[username]"],input[placeholder^="username" i]', password: 'input[type=password]:not([autocomplete*=cc]):not([autocomplete=one-time-code]):not([name*=answer i]):not([name*=mfa i]):not([name*=tin i]):not([name*=card i]):not([name*=cvv i]),input.js-cloudsave-phrase', @@ -17578,6 +17590,13 @@ const getTextShallow = el => { if (el.type === 'image') { return (0, _matching.removeExcessWhitespace)(el.alt || el.value || el.title || el.name); } + + // If it's checkbox, check for the first label to keep it simple. + // If there are multiple ones, we want to be on the safe side and ignore them, + // as the text can be noisy, resulting in false positives on the caller side. + if (el.type === 'checkbox' && el.labels?.length === 1) { + return (0, _matching.removeExcessWhitespace)(el.labels[0].textContent); + } } let text = ''; for (const childNode of el.childNodes) { diff --git a/dist/autofill.js b/dist/autofill.js index 8739a1d98..17f4d25bc 100644 --- a/dist/autofill.js +++ b/dist/autofill.js @@ -6895,6 +6895,8 @@ class FormAnalyzer { signalType: `external link: ${string}`, shouldFlip }); + } else if (el.matches(this.matching.cssSelector('enabledCheckboxSelector')) && this.isPersistentSigninText(string)) { + this.decreaseSignalBy(3, 'checkbox: persistent sign-in'); } else { // any other case const isH1Element = el.tagName === 'H1'; @@ -6986,6 +6988,15 @@ class FormAnalyzer { this._isCCForm = Boolean(textMatches && deDupedMatches.size > 1); return this._isCCForm; } + + /** + * Checks if the text is a persistent sign-in text, e.g "stay signed in" or "remember me" + * @param {string} text + * @returns {boolean} + */ + isPersistentSigninText(text) { + return /stay.?signed.?in|remember.?me/i.test(text); + } } var _default = exports.default = FormAnalyzer; @@ -8492,6 +8503,7 @@ const matchingConfiguration = exports.matchingConfiguration = { formInputsSelectorWithoutSelect: 'input:not([type=button]):not([type=checkbox]):not([type=color]):not([type=file]):not([type=hidden]):not([type=radio]):not([type=range]):not([type=reset]):not([type=image]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week]):not([name^=fake i]):not([data-description^=dummy i]):not([name*=otp]):not([autocomplete="fake"]):not([placeholder^=search i]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=month]),[autocomplete=username]', formInputsSelector: 'input:not([type=button]):not([type=checkbox]):not([type=color]):not([type=file]):not([type=hidden]):not([type=radio]):not([type=range]):not([type=reset]):not([type=image]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week]):not([name^=fake i]):not([data-description^=dummy i]):not([name*=otp]):not([autocomplete="fake"]):not([placeholder^=search i]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=month]),[autocomplete=username],select', safeUniversalSelector: '*:not(select):not(option):not(script):not(noscript):not(style):not(br)', + enabledCheckboxSelector: 'input[type="checkbox"]:not([disabled])', emailAddress: 'input:not([type])[name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]), input[type=""][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([type=tel]), input[type=text][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=title i]):not([name*=tab i]):not([name*=code i]), input:not([type])[placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]), input[type=text][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]), input[type=""][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]), input[type=email], input[type=text][aria-label*=email i]:not([aria-label*=search i]), input:not([type])[aria-label*=email i]:not([aria-label*=search i]), input[name=username][type=email], input[autocomplete=username][type=email], input[autocomplete=username][placeholder*=email i], input[autocomplete=email],input[name="mail_tel" i],input[value=email i]', username: 'input:not([type=button]):not([type=checkbox]):not([type=color]):not([type=file]):not([type=hidden]):not([type=radio]):not([type=range]):not([type=reset]):not([type=image]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week]):not([name^=fake i]):not([data-description^=dummy i]):not([name*=otp]):not([autocomplete="fake"]):not([placeholder^=search i]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=month])[autocomplete^=user i],input[name=username i],input[name="loginId" i],input[name="userid" i],input[id="userid" i],input[name="user_id" i],input[name="user-id" i],input[id="login-id" i],input[id="login_id" i],input[id="loginid" i],input[name="login" i],input[name=accountname i],input[autocomplete=username i],input[name*=accountid i],input[name="j_username" i],input[id="j_username" i],input[name="uwinid" i],input[name="livedoor_id" i],input[name="ssousername" i],input[name="j_userlogin_pwd" i],input[name="user[login]" i],input[name="user" i],input[name$="_username" i],input[id="lmSsoinput" i],input[name="account_subdomain" i],input[name="masterid" i],input[name="tridField" i],input[id="signInName" i],input[id="w3c_accountsbundle_accountrequeststep1_login" i],input[id="username" i],input[name="_user" i],input[name="login_username" i],input[name^="login-user-account" i],input[id="loginusuario" i],input[name="usuario" i],input[id="UserLoginFormUsername" i],input[id="nw_username" i],input[can-field="accountName"],input[name="login[username]"],input[placeholder^="username" i]', password: 'input[type=password]:not([autocomplete*=cc]):not([autocomplete=one-time-code]):not([name*=answer i]):not([name*=mfa i]):not([name*=tin i]):not([name*=card i]):not([name*=cvv i]),input.js-cloudsave-phrase', @@ -13215,6 +13227,13 @@ const getTextShallow = el => { if (el.type === 'image') { return (0, _matching.removeExcessWhitespace)(el.alt || el.value || el.title || el.name); } + + // If it's checkbox, check for the first label to keep it simple. + // If there are multiple ones, we want to be on the safe side and ignore them, + // as the text can be noisy, resulting in false positives on the caller side. + if (el.type === 'checkbox' && el.labels?.length === 1) { + return (0, _matching.removeExcessWhitespace)(el.labels[0].textContent); + } } let text = ''; for (const childNode of el.childNodes) { diff --git a/src/Form/FormAnalyzer.js b/src/Form/FormAnalyzer.js index 77647b70d..f1919fafe 100644 --- a/src/Form/FormAnalyzer.js +++ b/src/Form/FormAnalyzer.js @@ -298,6 +298,8 @@ class FormAnalyzer { shouldFlip = false; } this.updateSignal({ string, strength, signalType: `external link: ${string}`, shouldFlip }); + } else if (el.matches(this.matching.cssSelector('enabledCheckboxSelector')) && this.isPersistentSigninText(string)) { + this.decreaseSignalBy(3, 'checkbox: persistent sign-in'); } else { // any other case const isH1Element = el.tagName === 'H1'; @@ -383,6 +385,15 @@ class FormAnalyzer { this._isCCForm = Boolean(textMatches && deDupedMatches.size > 1); return this._isCCForm; } + + /** + * Checks if the text is a persistent sign-in text, e.g "stay signed in" or "remember me" + * @param {string} text + * @returns {boolean} + */ + isPersistentSigninText(text) { + return /stay.?signed.?in|remember.?me/i.test(text); + } } export default FormAnalyzer; diff --git a/src/Form/matching-config/__generated__/compiled-matching-config.js b/src/Form/matching-config/__generated__/compiled-matching-config.js index 65a419dcc..58f8fba28 100644 --- a/src/Form/matching-config/__generated__/compiled-matching-config.js +++ b/src/Form/matching-config/__generated__/compiled-matching-config.js @@ -208,6 +208,7 @@ const matchingConfiguration = { formInputsSelectorWithoutSelect: 'input:not([type=button]):not([type=checkbox]):not([type=color]):not([type=file]):not([type=hidden]):not([type=radio]):not([type=range]):not([type=reset]):not([type=image]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week]):not([name^=fake i]):not([data-description^=dummy i]):not([name*=otp]):not([autocomplete="fake"]):not([placeholder^=search i]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=month]),[autocomplete=username]', formInputsSelector: 'input:not([type=button]):not([type=checkbox]):not([type=color]):not([type=file]):not([type=hidden]):not([type=radio]):not([type=range]):not([type=reset]):not([type=image]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week]):not([name^=fake i]):not([data-description^=dummy i]):not([name*=otp]):not([autocomplete="fake"]):not([placeholder^=search i]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=month]),[autocomplete=username],select', safeUniversalSelector: '*:not(select):not(option):not(script):not(noscript):not(style):not(br)', + enabledCheckboxSelector: 'input[type="checkbox"]:not([disabled])', emailAddress: 'input:not([type])[name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]), input[type=""][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([type=tel]), input[type=text][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=title i]):not([name*=tab i]):not([name*=code i]), input:not([type])[placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]), input[type=text][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]), input[type=""][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]), input[type=email], input[type=text][aria-label*=email i]:not([aria-label*=search i]), input:not([type])[aria-label*=email i]:not([aria-label*=search i]), input[name=username][type=email], input[autocomplete=username][type=email], input[autocomplete=username][placeholder*=email i], input[autocomplete=email],input[name="mail_tel" i],input[value=email i]', username: 'input:not([type=button]):not([type=checkbox]):not([type=color]):not([type=file]):not([type=hidden]):not([type=radio]):not([type=range]):not([type=reset]):not([type=image]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week]):not([name^=fake i]):not([data-description^=dummy i]):not([name*=otp]):not([autocomplete="fake"]):not([placeholder^=search i]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=month])[autocomplete^=user i],input[name=username i],input[name="loginId" i],input[name="userid" i],input[id="userid" i],input[name="user_id" i],input[name="user-id" i],input[id="login-id" i],input[id="login_id" i],input[id="loginid" i],input[name="login" i],input[name=accountname i],input[autocomplete=username i],input[name*=accountid i],input[name="j_username" i],input[id="j_username" i],input[name="uwinid" i],input[name="livedoor_id" i],input[name="ssousername" i],input[name="j_userlogin_pwd" i],input[name="user[login]" i],input[name="user" i],input[name$="_username" i],input[id="lmSsoinput" i],input[name="account_subdomain" i],input[name="masterid" i],input[name="tridField" i],input[id="signInName" i],input[id="w3c_accountsbundle_accountrequeststep1_login" i],input[id="username" i],input[name="_user" i],input[name="login_username" i],input[name^="login-user-account" i],input[id="loginusuario" i],input[name="usuario" i],input[id="UserLoginFormUsername" i],input[id="nw_username" i],input[can-field="accountName"],input[name="login[username]"],input[placeholder^="username" i]', password: 'input[type=password]:not([autocomplete*=cc]):not([autocomplete=one-time-code]):not([name*=answer i]):not([name*=mfa i]):not([name*=tin i]):not([name*=card i]):not([name*=cvv i]),input.js-cloudsave-phrase', diff --git a/src/Form/matching-config/selectors-css.js b/src/Form/matching-config/selectors-css.js index e8f1ff084..e20a98c86 100644 --- a/src/Form/matching-config/selectors-css.js +++ b/src/Form/matching-config/selectors-css.js @@ -15,6 +15,8 @@ button:not([role=switch]):not([role=link]), a[href="#"][id*=button i], a[href="#"][id*=btn i]`; +const enabledCheckboxSelector = 'input[type="checkbox"]:not([disabled])'; + const safeUniversalSelector = '*:not(select):not(option):not(script):not(noscript):not(style):not(br)'; const emailAddress = [ @@ -279,6 +281,7 @@ const selectors = { formInputsSelectorWithoutSelect, formInputsSelector, safeUniversalSelector, + enabledCheckboxSelector, // Credentials emailAddress, diff --git a/src/autofill-utils.js b/src/autofill-utils.js index 99a11fdc4..25a763814 100644 --- a/src/autofill-utils.js +++ b/src/autofill-utils.js @@ -367,6 +367,13 @@ const getTextShallow = (el) => { if (el.type === 'image') { return removeExcessWhitespace(el.alt || el.value || el.title || el.name); } + + // If it's checkbox, check for the first label to keep it simple. + // If there are multiple ones, we want to be on the safe side and ignore them, + // as the text can be noisy, resulting in false positives on the caller side. + if (el.type === 'checkbox' && el.labels?.length === 1) { + return removeExcessWhitespace(el.labels[0].textContent); + } } let text = ''; diff --git a/swift-package/Resources/assets/autofill-debug.js b/swift-package/Resources/assets/autofill-debug.js index 4d9064cd0..146aae452 100644 --- a/swift-package/Resources/assets/autofill-debug.js +++ b/swift-package/Resources/assets/autofill-debug.js @@ -11258,6 +11258,8 @@ class FormAnalyzer { signalType: `external link: ${string}`, shouldFlip }); + } else if (el.matches(this.matching.cssSelector('enabledCheckboxSelector')) && this.isPersistentSigninText(string)) { + this.decreaseSignalBy(3, 'checkbox: persistent sign-in'); } else { // any other case const isH1Element = el.tagName === 'H1'; @@ -11349,6 +11351,15 @@ class FormAnalyzer { this._isCCForm = Boolean(textMatches && deDupedMatches.size > 1); return this._isCCForm; } + + /** + * Checks if the text is a persistent sign-in text, e.g "stay signed in" or "remember me" + * @param {string} text + * @returns {boolean} + */ + isPersistentSigninText(text) { + return /stay.?signed.?in|remember.?me/i.test(text); + } } var _default = exports.default = FormAnalyzer; @@ -12855,6 +12866,7 @@ const matchingConfiguration = exports.matchingConfiguration = { formInputsSelectorWithoutSelect: 'input:not([type=button]):not([type=checkbox]):not([type=color]):not([type=file]):not([type=hidden]):not([type=radio]):not([type=range]):not([type=reset]):not([type=image]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week]):not([name^=fake i]):not([data-description^=dummy i]):not([name*=otp]):not([autocomplete="fake"]):not([placeholder^=search i]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=month]),[autocomplete=username]', formInputsSelector: 'input:not([type=button]):not([type=checkbox]):not([type=color]):not([type=file]):not([type=hidden]):not([type=radio]):not([type=range]):not([type=reset]):not([type=image]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week]):not([name^=fake i]):not([data-description^=dummy i]):not([name*=otp]):not([autocomplete="fake"]):not([placeholder^=search i]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=month]),[autocomplete=username],select', safeUniversalSelector: '*:not(select):not(option):not(script):not(noscript):not(style):not(br)', + enabledCheckboxSelector: 'input[type="checkbox"]:not([disabled])', emailAddress: 'input:not([type])[name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]), input[type=""][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([type=tel]), input[type=text][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=title i]):not([name*=tab i]):not([name*=code i]), input:not([type])[placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]), input[type=text][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]), input[type=""][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]), input[type=email], input[type=text][aria-label*=email i]:not([aria-label*=search i]), input:not([type])[aria-label*=email i]:not([aria-label*=search i]), input[name=username][type=email], input[autocomplete=username][type=email], input[autocomplete=username][placeholder*=email i], input[autocomplete=email],input[name="mail_tel" i],input[value=email i]', username: 'input:not([type=button]):not([type=checkbox]):not([type=color]):not([type=file]):not([type=hidden]):not([type=radio]):not([type=range]):not([type=reset]):not([type=image]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week]):not([name^=fake i]):not([data-description^=dummy i]):not([name*=otp]):not([autocomplete="fake"]):not([placeholder^=search i]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=month])[autocomplete^=user i],input[name=username i],input[name="loginId" i],input[name="userid" i],input[id="userid" i],input[name="user_id" i],input[name="user-id" i],input[id="login-id" i],input[id="login_id" i],input[id="loginid" i],input[name="login" i],input[name=accountname i],input[autocomplete=username i],input[name*=accountid i],input[name="j_username" i],input[id="j_username" i],input[name="uwinid" i],input[name="livedoor_id" i],input[name="ssousername" i],input[name="j_userlogin_pwd" i],input[name="user[login]" i],input[name="user" i],input[name$="_username" i],input[id="lmSsoinput" i],input[name="account_subdomain" i],input[name="masterid" i],input[name="tridField" i],input[id="signInName" i],input[id="w3c_accountsbundle_accountrequeststep1_login" i],input[id="username" i],input[name="_user" i],input[name="login_username" i],input[name^="login-user-account" i],input[id="loginusuario" i],input[name="usuario" i],input[id="UserLoginFormUsername" i],input[id="nw_username" i],input[can-field="accountName"],input[name="login[username]"],input[placeholder^="username" i]', password: 'input[type=password]:not([autocomplete*=cc]):not([autocomplete=one-time-code]):not([name*=answer i]):not([name*=mfa i]):not([name*=tin i]):not([name*=card i]):not([name*=cvv i]),input.js-cloudsave-phrase', @@ -17578,6 +17590,13 @@ const getTextShallow = el => { if (el.type === 'image') { return (0, _matching.removeExcessWhitespace)(el.alt || el.value || el.title || el.name); } + + // If it's checkbox, check for the first label to keep it simple. + // If there are multiple ones, we want to be on the safe side and ignore them, + // as the text can be noisy, resulting in false positives on the caller side. + if (el.type === 'checkbox' && el.labels?.length === 1) { + return (0, _matching.removeExcessWhitespace)(el.labels[0].textContent); + } } let text = ''; for (const childNode of el.childNodes) { diff --git a/swift-package/Resources/assets/autofill.js b/swift-package/Resources/assets/autofill.js index 8739a1d98..17f4d25bc 100644 --- a/swift-package/Resources/assets/autofill.js +++ b/swift-package/Resources/assets/autofill.js @@ -6895,6 +6895,8 @@ class FormAnalyzer { signalType: `external link: ${string}`, shouldFlip }); + } else if (el.matches(this.matching.cssSelector('enabledCheckboxSelector')) && this.isPersistentSigninText(string)) { + this.decreaseSignalBy(3, 'checkbox: persistent sign-in'); } else { // any other case const isH1Element = el.tagName === 'H1'; @@ -6986,6 +6988,15 @@ class FormAnalyzer { this._isCCForm = Boolean(textMatches && deDupedMatches.size > 1); return this._isCCForm; } + + /** + * Checks if the text is a persistent sign-in text, e.g "stay signed in" or "remember me" + * @param {string} text + * @returns {boolean} + */ + isPersistentSigninText(text) { + return /stay.?signed.?in|remember.?me/i.test(text); + } } var _default = exports.default = FormAnalyzer; @@ -8492,6 +8503,7 @@ const matchingConfiguration = exports.matchingConfiguration = { formInputsSelectorWithoutSelect: 'input:not([type=button]):not([type=checkbox]):not([type=color]):not([type=file]):not([type=hidden]):not([type=radio]):not([type=range]):not([type=reset]):not([type=image]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week]):not([name^=fake i]):not([data-description^=dummy i]):not([name*=otp]):not([autocomplete="fake"]):not([placeholder^=search i]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=month]),[autocomplete=username]', formInputsSelector: 'input:not([type=button]):not([type=checkbox]):not([type=color]):not([type=file]):not([type=hidden]):not([type=radio]):not([type=range]):not([type=reset]):not([type=image]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week]):not([name^=fake i]):not([data-description^=dummy i]):not([name*=otp]):not([autocomplete="fake"]):not([placeholder^=search i]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=month]),[autocomplete=username],select', safeUniversalSelector: '*:not(select):not(option):not(script):not(noscript):not(style):not(br)', + enabledCheckboxSelector: 'input[type="checkbox"]:not([disabled])', emailAddress: 'input:not([type])[name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]), input[type=""][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([type=tel]), input[type=text][name*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=title i]):not([name*=tab i]):not([name*=code i]), input:not([type])[placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]):not([name*=code i]), input[type=text][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]), input[type=""][placeholder*=email i]:not([placeholder*=search i]):not([placeholder*=filter i]):not([placeholder*=subject i]), input[type=email], input[type=text][aria-label*=email i]:not([aria-label*=search i]), input:not([type])[aria-label*=email i]:not([aria-label*=search i]), input[name=username][type=email], input[autocomplete=username][type=email], input[autocomplete=username][placeholder*=email i], input[autocomplete=email],input[name="mail_tel" i],input[value=email i]', username: 'input:not([type=button]):not([type=checkbox]):not([type=color]):not([type=file]):not([type=hidden]):not([type=radio]):not([type=range]):not([type=reset]):not([type=image]):not([type=search]):not([type=submit]):not([type=time]):not([type=url]):not([type=week]):not([name^=fake i]):not([data-description^=dummy i]):not([name*=otp]):not([autocomplete="fake"]):not([placeholder^=search i]):not([type=date]):not([type=datetime-local]):not([type=datetime]):not([type=month])[autocomplete^=user i],input[name=username i],input[name="loginId" i],input[name="userid" i],input[id="userid" i],input[name="user_id" i],input[name="user-id" i],input[id="login-id" i],input[id="login_id" i],input[id="loginid" i],input[name="login" i],input[name=accountname i],input[autocomplete=username i],input[name*=accountid i],input[name="j_username" i],input[id="j_username" i],input[name="uwinid" i],input[name="livedoor_id" i],input[name="ssousername" i],input[name="j_userlogin_pwd" i],input[name="user[login]" i],input[name="user" i],input[name$="_username" i],input[id="lmSsoinput" i],input[name="account_subdomain" i],input[name="masterid" i],input[name="tridField" i],input[id="signInName" i],input[id="w3c_accountsbundle_accountrequeststep1_login" i],input[id="username" i],input[name="_user" i],input[name="login_username" i],input[name^="login-user-account" i],input[id="loginusuario" i],input[name="usuario" i],input[id="UserLoginFormUsername" i],input[id="nw_username" i],input[can-field="accountName"],input[name="login[username]"],input[placeholder^="username" i]', password: 'input[type=password]:not([autocomplete*=cc]):not([autocomplete=one-time-code]):not([name*=answer i]):not([name*=mfa i]):not([name*=tin i]):not([name*=card i]):not([name*=cvv i]),input.js-cloudsave-phrase', @@ -13215,6 +13227,13 @@ const getTextShallow = el => { if (el.type === 'image') { return (0, _matching.removeExcessWhitespace)(el.alt || el.value || el.title || el.name); } + + // If it's checkbox, check for the first label to keep it simple. + // If there are multiple ones, we want to be on the safe side and ignore them, + // as the text can be noisy, resulting in false positives on the caller side. + if (el.type === 'checkbox' && el.labels?.length === 1) { + return (0, _matching.removeExcessWhitespace)(el.labels[0].textContent); + } } let text = ''; for (const childNode of el.childNodes) {