зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1825575 - Update SignUpFormRuleset to 065e084 r=dimi
Differential Revision: https://phabricator.services.mozilla.com/D175736
This commit is contained in:
Родитель
d8f51df4fb
Коммит
e553a2aae1
|
@ -3,7 +3,16 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* Machine learning model for identifying sign up scenario forms
|
||||
* Fathom ML model for identifying sign up <forms>
|
||||
*
|
||||
* This is developed out-of-tree at https://github.com/mozilla-services/fathom-login-forms,
|
||||
* where there is also over a GB of training, validation, and
|
||||
* testing data. To make changes, do your edits there (whether adding new
|
||||
* training pages, adding new rules, or both), retrain and evaluate as
|
||||
* documented at https://mozilla.github.io/fathom/training.html, paste the
|
||||
* coefficients emitted by the trainer into the ruleset, and finally copy the
|
||||
* ruleset's "CODE TO COPY INTO PRODUCTION" section to this file's "CODE FROM
|
||||
* TRAINING REPOSITORY" section.
|
||||
*/
|
||||
|
||||
import {
|
||||
|
@ -15,11 +24,9 @@ import {
|
|||
type,
|
||||
element,
|
||||
utils,
|
||||
clusters,
|
||||
} from "resource://gre/modules/third_party/fathom/fathom.mjs";
|
||||
|
||||
let { isVisible, attributesMatch, min, setDefault } = utils;
|
||||
let { euclidean } = clusters;
|
||||
let { isVisible, attributesMatch, setDefault } = utils;
|
||||
|
||||
const DEVELOPMENT = false;
|
||||
|
||||
|
@ -27,60 +34,49 @@ const DEVELOPMENT = false;
|
|||
* --- START OF RULESET ---
|
||||
*/
|
||||
const coefficients = {
|
||||
signup: new Map([
|
||||
["formMethodIsPost", -0.7543129920959473],
|
||||
["formAttributesMatchRegisterRegex", 1.7264184951782227],
|
||||
["formAttributesMatchLoginRegex", -0.9329696297645569],
|
||||
["formAttributesMatchNewsletterRegex", -2.206372022628784],
|
||||
["formHasAcNewPassword", 1.5736613273620605],
|
||||
["formHasAcCurrentPassword", -0.12390841543674469],
|
||||
["formHasAcEmail", 0.6157014966011047],
|
||||
["formHasAcUsername", -0.7274730801582336],
|
||||
["formHasAcTel", -1.1380716562271118],
|
||||
["formHasEmailField", 1.7712397575378418],
|
||||
["formHasUsernameField", 1.311736822128296],
|
||||
["formHasPasswordField", 1.3901746273040771],
|
||||
["formHasEmailAndExtraNameField", 1.0147123336791992],
|
||||
["formHasFirstOrLastNameFields", 0.6080058217048645],
|
||||
["formHasBirthdayFields", 1.2841497659683228],
|
||||
["formHasPhoneField", 0.8433054685592651],
|
||||
["formHasRegisterButton", 1.1531426906585693],
|
||||
["formHasLoginButton", -1.1045818328857422],
|
||||
["formHasSubscribeButton", -2.290463447570801],
|
||||
["formHasContinueButton", 2.2985446453094482],
|
||||
["closestElementIsEmailLabelLike", 0.7776230573654175],
|
||||
["closestElementIsNewPasswordLabelLike", 0.814133882522583],
|
||||
["formHasTermsAndConditionsCheckbox", 0.6034160852432251],
|
||||
["formHasRememberMeCheckbox", -2.623626470565796],
|
||||
["formHasSubcriptionCheckbox", 0.5114848613739014],
|
||||
["docTitleMatchesRegisterRegex", 1.2882719039916992],
|
||||
["docTitleMatchesEditProfileRegex", -2.381089687347412],
|
||||
["docHasRegisterOrPasswordForgottenHyperlink", -1.792033076286316],
|
||||
["docHasLoginHyperlink", -0.621324896812439],
|
||||
["closestHeaderMatchesRegisterRegex", 1.826246738433838],
|
||||
["closestHeaderMatchesLoginRegex", -0.7161067128181458],
|
||||
["closestHeaderMatchesNewsletterRegex", -1.6708611249923706],
|
||||
form: new Map([
|
||||
["formAttributesMatchRegisterRegex", 0.4614015519618988],
|
||||
["formAttributesMatchLoginRegex", -2.608457326889038],
|
||||
["formAttributesMatchSubscriptionRegex", -3.253319501876831],
|
||||
["formAttributesMatchLoginAndRegisterRegex", 3.6423728466033936],
|
||||
["formHasAcNewPassword", 2.214113473892212],
|
||||
["formHasAcCurrentPassword", -0.43707895278930664],
|
||||
["formHasEmailField", 1.760241150856018],
|
||||
["formHasUsernameField", 1.1527059078216553],
|
||||
["formHasPasswordField", 1.6670876741409302],
|
||||
["formHasFirstOrLastNameField", 0.9517516493797302],
|
||||
["formHasRegisterButton", 1.574048638343811],
|
||||
["formHasLoginButton", -1.1688978672027588],
|
||||
["formHasSubscribeButton", -0.26299405097961426],
|
||||
["formHasContinueButton", 2.3797709941864014],
|
||||
["formHasTermsAndConditionsHyperlink", 1.764896035194397],
|
||||
["formHasPasswordForgottenHyperlink", -0.32138824462890625],
|
||||
["formHasAlreadySignedUpHyperlink", 3.160510301589966],
|
||||
["closestElementIsEmailLabelLike", 1.0336143970489502],
|
||||
["formHasRememberMeCheckbox", -1.2176686525344849],
|
||||
["formHasSubcriptionCheckbox", 0.6100747585296631],
|
||||
["docTitleMatchesRegisterRegex", 0.680654764175415],
|
||||
["docTitleMatchesEditProfileRegex", -4.104133605957031],
|
||||
["closestHeaderMatchesRegisterRegex", 1.3462989330291748],
|
||||
["closestHeaderMatchesLoginRegex", -0.1804502159357071],
|
||||
["closestHeaderMatchesSubscriptionRegex", -1.3057124614715576],
|
||||
]),
|
||||
};
|
||||
|
||||
const biases = [["signup", -2.210782766342163]];
|
||||
const biases = [["form", -4.402400970458984]];
|
||||
|
||||
const loginRegex = /login|log-in|log_in|log in|signon|sign-on|sign_on|sign on|signin|sign-in|sign_in|sign in|einloggen|anmelden|logon|log-on|lgon_on|log on|Войти|ورود|登录|Přihlásit se|Přihlaste|Авторизоваться|Авторизация|entrar|ログイン|로그인|inloggen|Συνδέσου|accedi|ログオン|Giriş Yap|登入|connecter|connectez-vous|Connexion|Вход|inicia|inloggen/gi;
|
||||
const registerRegex = /create|regist[a-z]|sign up|signup|sign-up|sign_up|join|new|登録|neu|erstellen|choose|設定|신규|Créer|Nouveau|baru|nouă|nieuw|create[a-zA-Z\s]+account|activate[a-zA-Z\s]+account|Zugang anlegen|Angaben prüfen|Konto erstellen|ثبت نام|登録|注册|cadastr|Зарегистрироваться|Регистрация|Bellige alynmak|تسجيل|ΕΓΓΡΑΦΗΣ|Εγγραφή|Créer mon compte|Créer un compte|Mendaftar|가입하기|inschrijving|Zarejestruj się|Deschideți un cont|Создать аккаунт|ร่วม|Üye Ol|ساخت حساب کاربری|Schrijf je|S'inscrire/gi;
|
||||
const emailRegex = /mail/gi;
|
||||
const usernameRegex = /user|name|member/gi;
|
||||
const newPasswordRegex = /new|create/gi;
|
||||
//const confirmAttrRegex = /confirm|retype|again|bevestigen|wiederhol|repeat|confirmation|verify|retype|repite|確認|の確認|تکرار|re-enter|확인|bevestigen|Повторите|tassyklamak|再次输入|ještě jednou|gentag|re-type|Répéter|conferma|Repetaţi|reenter|再入力|재입력|Ulangi|Bekræft/gi;
|
||||
const nameRegex = /first|last|middle/gi;
|
||||
const birthdateRegex = /birth|year|yyyy/gi;
|
||||
const phoneRegex = /phone|mobile|tel|number/gi;
|
||||
const newsletterRegex = /subscri|newsletter|trial|offer|information|angebote|probe|ニュースレター|abonn[a-z]/gi;
|
||||
const termsAndConditionsRegex = /accept|agree|read|terms|condition|rules|policy|privacy|akzeptier|gelesen|nutzungbedingungen|AGB|términos|condiciones|/gi;
|
||||
const pwForgottenRegex = /forgot|reset|set password|vergessen|vergeten|oublié|dimenticata|Esqueceu|esqueci|Забыли|忘记|找回|Zapomenuté|lost|忘れた|忘れられた|忘れの方|재설정|찾기|help|فراموشی| را فراموش کرده اید|Восстановить|Unuttu|perdus|重新設定|recover|remind|request|restore|trouble|olvidada/gi;
|
||||
const continueRegex = /continue|go on|weiter|fortfahren|ga verder|next|continuar/gi;
|
||||
const rememberMeRegex = /remember|stay|speichern|merken|bleiben|auto_login|auto-login|auto login|ricordami|manter|mantenha|savelogin|keep me logged in|keep me signed in|save email address|save id|stay signed in|次回からログオンIDの入力を省略する|メールアドレスを保存する|を保存|아이디저장|아이디 저장|로그인 상태 유지|lembrar|mantenha-me conectado|Запомни меня|запомнить меня|Запомните меня|Не спрашивать в следующий раз|下次自动登录|记住我|recordar/gi;
|
||||
const alreadySignedUpRegex = /already|bereits|schon|ya tienes cuenta/gi;
|
||||
const editProfile = /edit|profile/gi;
|
||||
const loginRegex = /login|log-in|log_in|log in|signon|sign-on|sign_on|sign on|signin|sign-in|sign_in|sign in|einloggen|anmelden|logon|log-on|log_on|log on|Войти|ورود|登录|Přihlásit se|Přihlaste|Авторизоваться|Авторизация|entrar|ログイン|로그인|inloggen|Συνδέσου|accedi|ログオン|Giriş Yap|登入|connecter|connectez-vous|Connexion|Вход|inicia/i;
|
||||
const registerRegex = /regist|sign up|signup|sign-up|sign_up|join|new|登録|neu|erstellen|設定|신규|Créer|Nouveau|baru|nouă|nieuw|create[a-zA-Z\s]+account|create[a-zA-Z\s]+profile|activate[a-zA-Z\s]+account|Zugang anlegen|Angaben prüfen|Konto erstellen|ثبت نام|登録|注册|cadastr|Зарегистрироваться|Регистрация|Bellige alynmak|تسجيل|ΕΓΓΡΑΦΗΣ|Εγγραφή|Créer mon compte|Créer un compte|Mendaftar|가입하기|inschrijving|Zarejestruj się|Deschideți un cont|Создать аккаунт|ร่วม|Üye Ol|ساخت حساب کاربری|Schrijf je|S'inscrire/i;
|
||||
const emailRegex = /mail/i;
|
||||
const usernameRegex = /user|member/i;
|
||||
const nameRegex = /first|last|middle/i;
|
||||
const subscriptionRegex = /subscri|trial|offer|information|angebote|probe|ニュースレター|abonn|promotion|news/i;
|
||||
const termsAndConditionsRegex = /terms|condition|rules|policy|privacy|nutzungsbedingungen|AGB|richtlinien|datenschutz|términos|condiciones/i;
|
||||
const pwForgottenRegex = /forgot|reset|set password|vergessen|vergeten|oublié|dimenticata|Esqueceu|esqueci|Забыли|忘记|找回|Zapomenuté|lost|忘れた|忘れられた|忘れの方|재설정|찾기|help|فراموشی| را فراموش کرده اید|Восстановить|Unuttu|perdus|重新設定|recover|remind|request|restore|trouble|olvidada/i;
|
||||
const continueRegex = /continue|go on|weiter|fortfahren|ga verder|next|continuar/i;
|
||||
const rememberMeRegex = /remember|stay|speichern|merken|bleiben|auto_login|auto-login|auto login|ricordami|manter|mantenha|savelogin|keep me logged in|keep me signed in|save email address|save id|stay signed in|次回からログオンIDの入力を省略する|メールアドレスを保存する|を保存|아이디저장|아이디 저장|로그인 상태 유지|lembrar|mantenha-me conectado|Запомни меня|запомнить меня|Запомните меня|Не спрашивать в следующий раз|下次自动登录|记住我|recordar|angemeldet bleiben/i;
|
||||
const alreadySignedUpRegex = /already|bereits|schon|ya tienes cuenta/i;
|
||||
const editProfile = /edit/i;
|
||||
|
||||
function createRuleset(coeffs, biases) {
|
||||
let elementToSelectors;
|
||||
|
@ -88,32 +84,12 @@ function createRuleset(coeffs, biases) {
|
|||
/**
|
||||
* Check document characteristics
|
||||
*/
|
||||
function docHasLoginHyperlink(fnode) {
|
||||
const links = getElementDescendants(fnode.element.ownerDocument, "a");
|
||||
|
||||
return links.some(
|
||||
link =>
|
||||
checkValueAgainstRegex(link.innerText, loginRegex) ||
|
||||
checkValueAgainstRegex(link.innerText, alreadySignedUpRegex)
|
||||
);
|
||||
}
|
||||
function docHasRegisterOrPasswordForgottenHyperlink(fnode) {
|
||||
const links = getElementDescendants(fnode.element.ownerDocument, "a");
|
||||
|
||||
return links.some(
|
||||
link =>
|
||||
checkValueAgainstRegex(link.innerText, registerRegex) ||
|
||||
checkValueAgainstRegex(link.innerText, pwForgottenRegex)
|
||||
);
|
||||
}
|
||||
function docTitleMatchesRegisterRegex(fnode) {
|
||||
const docTitle = fnode.element.ownerDocument.title;
|
||||
|
||||
return checkValueAgainstRegex(docTitle, registerRegex);
|
||||
}
|
||||
function docTitleMatchesEditProfileRegex(fnode) {
|
||||
const docTitle = fnode.element.ownerDocument.title;
|
||||
|
||||
return checkValueAgainstRegex(docTitle, editProfile);
|
||||
}
|
||||
|
||||
|
@ -121,169 +97,164 @@ function createRuleset(coeffs, biases) {
|
|||
* Check header
|
||||
*/
|
||||
function closestHeaderMatchesLoginRegex(fnode) {
|
||||
return (
|
||||
headerInFormMatchesRegex(fnode.element, loginRegex) ||
|
||||
closestHeaderAboveMatchesRegex(fnode.element, loginRegex)
|
||||
return closestHeaderMatchesPredicate(fnode.element, header =>
|
||||
checkValueAgainstRegex(header.innerText, loginRegex)
|
||||
);
|
||||
}
|
||||
|
||||
function closestHeaderMatchesRegisterRegex(fnode) {
|
||||
return (
|
||||
headerInFormMatchesRegex(fnode.element, registerRegex) ||
|
||||
closestHeaderAboveMatchesRegex(fnode.element, registerRegex)
|
||||
return closestHeaderMatchesPredicate(fnode.element, header =>
|
||||
checkValueAgainstRegex(header.innerText, registerRegex)
|
||||
);
|
||||
}
|
||||
function closestHeaderMatchesNewsletterRegex(fnode) {
|
||||
return (
|
||||
headerInFormMatchesRegex(fnode.element, newsletterRegex) ||
|
||||
closestHeaderAboveMatchesRegex(fnode.element, newsletterRegex)
|
||||
function closestHeaderMatchesSubscriptionRegex(fnode) {
|
||||
return closestHeaderMatchesPredicate(fnode.element, header =>
|
||||
checkValueAgainstRegex(header.innerText, subscriptionRegex)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check Checkboxes
|
||||
* Check checkboxes
|
||||
*/
|
||||
function formHasRememberMeCheckbox(fnode) {
|
||||
return checkboxInFormMatchesRegex(fnode.element, rememberMeRegex);
|
||||
return elementHasRegexMatchingCheckbox(fnode.element, rememberMeRegex);
|
||||
}
|
||||
function formHasSubcriptionCheckbox(fnode) {
|
||||
return checkboxInFormMatchesRegex(fnode.element, newsletterRegex);
|
||||
}
|
||||
function formHasTermsAndConditionsCheckbox(fnode) {
|
||||
return checkboxInFormMatchesRegex(fnode.element, termsAndConditionsRegex);
|
||||
return elementHasRegexMatchingCheckbox(fnode.element, subscriptionRegex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check input fields
|
||||
*/
|
||||
function formHasPhoneField(fnode) {
|
||||
return formContainsRegexMatchingElement(fnode.element, "input", phoneRegex);
|
||||
}
|
||||
|
||||
function formHasBirthdayFields(fnode) {
|
||||
return formContainsRegexMatchingElement(
|
||||
function formHasFirstOrLastNameField(fnode) {
|
||||
const acValues = ["name", "given-name", "family-name"];
|
||||
return elementHasPredicateMatchingInput(
|
||||
fnode.element,
|
||||
"input,select",
|
||||
birthdateRegex
|
||||
);
|
||||
}
|
||||
function formHasFirstOrLastNameFields(fnode) {
|
||||
return formContainsRegexMatchingElement(fnode.element, "input", nameRegex);
|
||||
}
|
||||
function formHasEmailAndExtraNameField(fnode) {
|
||||
const possibleFields = getElementDescendants(
|
||||
fnode.element,
|
||||
"input[type=email],input[type=text]"
|
||||
);
|
||||
let containsEmail = false;
|
||||
let containsAnyName = false;
|
||||
|
||||
for (const field of possibleFields) {
|
||||
if (
|
||||
attributesMatch(
|
||||
field,
|
||||
attr => checkValueAgainstRegex(attr, emailRegex),
|
||||
["id", "name", "className", "placeholder"]
|
||||
elem =>
|
||||
atLeastOne(acValues.filter(ac => elem.autocomplete == ac)) ||
|
||||
inputFieldMatchesPredicate(elem, attr =>
|
||||
checkValueAgainstRegex(attr, nameRegex)
|
||||
)
|
||||
) {
|
||||
if (containsAnyName) {
|
||||
return true;
|
||||
} else if (!containsEmail) {
|
||||
containsEmail = true;
|
||||
}
|
||||
} else if (
|
||||
attributesMatch(
|
||||
field,
|
||||
attr =>
|
||||
checkValueAgainstRegex(attr, usernameRegex) ||
|
||||
checkValueAgainstRegex(attr, nameRegex)
|
||||
)
|
||||
) {
|
||||
if (containsEmail) {
|
||||
return true;
|
||||
} else if (!containsAnyName) {
|
||||
containsAnyName = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
);
|
||||
}
|
||||
function formHasEmailField(fnode) {
|
||||
return atLeastOne(getEmailInputElements(fnode.element));
|
||||
return elementHasPredicateMatchingInput(
|
||||
fnode.element,
|
||||
elem =>
|
||||
elem.autocomplete == "email" ||
|
||||
elem.type == "email" ||
|
||||
inputFieldMatchesPredicate(elem, attr =>
|
||||
checkValueAgainstRegex(attr, emailRegex)
|
||||
)
|
||||
);
|
||||
}
|
||||
function formHasUsernameField(fnode) {
|
||||
return formContainsRegexMatchingElement(
|
||||
return elementHasPredicateMatchingInput(
|
||||
fnode.element,
|
||||
"input",
|
||||
usernameRegex
|
||||
elem =>
|
||||
elem.autocomplete == "username" ||
|
||||
inputFieldMatchesPredicate(elem, attr =>
|
||||
checkValueAgainstRegex(attr, usernameRegex)
|
||||
)
|
||||
);
|
||||
}
|
||||
function formHasPasswordField(fnode) {
|
||||
return checkInputFieldsForAttr(fnode.element, "type=password");
|
||||
const acValues = ["current-password", "new-password"];
|
||||
return elementHasPredicateMatchingInput(
|
||||
fnode.element,
|
||||
elem =>
|
||||
atLeastOne(acValues.filter(ac => elem.autocomplete == ac)) ||
|
||||
elem.type == "password"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check autocomplete values
|
||||
*/
|
||||
function formHasAcUsername(fnode) {
|
||||
return checkInputFieldsForAttr(fnode.element, "autocomplete=username");
|
||||
}
|
||||
function formHasAcEmail(fnode) {
|
||||
return checkInputFieldsForAttr(fnode.element, "autocomplete=email");
|
||||
}
|
||||
function formHasAcCurrentPassword(fnode) {
|
||||
return checkInputFieldsForAttr(
|
||||
return inputFieldMatchesSelector(
|
||||
fnode.element,
|
||||
"autocomplete=current-password"
|
||||
);
|
||||
}
|
||||
function formHasAcNewPassword(fnode) {
|
||||
return checkInputFieldsForAttr(fnode.element, "autocomplete=new-password");
|
||||
return inputFieldMatchesSelector(
|
||||
fnode.element,
|
||||
"autocomplete=new-password"
|
||||
);
|
||||
}
|
||||
function formHasAcTel(fnode) {
|
||||
return checkInputFieldsForAttr(fnode.element, "autocomplete*=tel");
|
||||
|
||||
/**
|
||||
* Check hyperlinks within form
|
||||
*/
|
||||
function formHasTermsAndConditionsHyperlink(fnode) {
|
||||
return elementHasPredicateMatchingHyperlink(
|
||||
fnode.element,
|
||||
termsAndConditionsRegex
|
||||
);
|
||||
}
|
||||
function formHasPasswordForgottenHyperlink(fnode) {
|
||||
return elementHasPredicateMatchingHyperlink(
|
||||
fnode.element,
|
||||
pwForgottenRegex
|
||||
);
|
||||
}
|
||||
function formHasAlreadySignedUpHyperlink(fnode) {
|
||||
return elementHasPredicateMatchingHyperlink(
|
||||
fnode.element,
|
||||
alreadySignedUpRegex
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check labels
|
||||
*/
|
||||
function closestElementIsNewPasswordLabelLike(fnode) {
|
||||
const passwordFields = getElementDescendants(
|
||||
fnode.element,
|
||||
"input[type=password][autocomplete=new-password]"
|
||||
);
|
||||
return closestElementIsRegexMatchingLabel(passwordFields, newPasswordRegex);
|
||||
}
|
||||
function closestElementIsEmailLabelLike(fnode) {
|
||||
const emailFields = getEmailInputElements(fnode.element);
|
||||
return closestElementIsRegexMatchingLabel(emailFields, emailRegex);
|
||||
return elementHasPredicateMatchingInput(fnode.element, elem =>
|
||||
previousSiblingLabelMatchesRegex(elem, emailRegex)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check buttons
|
||||
*/
|
||||
function formHasRegisterButton(fnode) {
|
||||
const buttons = getButtons(fnode.element);
|
||||
return buttons.some(button =>
|
||||
checkValueAgainstRegex(button.innerText, registerRegex)
|
||||
return elementHasPredicateMatchingButton(
|
||||
fnode.element,
|
||||
button =>
|
||||
checkValueAgainstRegex(button.innerText, registerRegex) ||
|
||||
buttonMatchesPredicate(button, attr =>
|
||||
checkValueAgainstRegex(attr, registerRegex)
|
||||
)
|
||||
);
|
||||
}
|
||||
function formHasLoginButton(fnode) {
|
||||
const buttons = getButtons(fnode.element);
|
||||
return buttons.some(button =>
|
||||
checkValueAgainstRegex(button.innerText, loginRegex)
|
||||
return elementHasPredicateMatchingButton(
|
||||
fnode.element,
|
||||
button =>
|
||||
checkValueAgainstRegex(button.innerText, loginRegex) ||
|
||||
buttonMatchesPredicate(button, attr =>
|
||||
checkValueAgainstRegex(attr, loginRegex)
|
||||
)
|
||||
);
|
||||
}
|
||||
function formHasContinueButton(fnode) {
|
||||
const buttons = getButtons(fnode.element);
|
||||
return buttons.some(button =>
|
||||
checkValueAgainstRegex(button.innerText, continueRegex)
|
||||
return elementHasPredicateMatchingButton(
|
||||
fnode.element,
|
||||
button =>
|
||||
checkValueAgainstRegex(button.innerText, continueRegex) ||
|
||||
buttonMatchesPredicate(button, attr =>
|
||||
checkValueAgainstRegex(attr, continueRegex)
|
||||
)
|
||||
);
|
||||
}
|
||||
function formHasSubscribeButton(fnode) {
|
||||
const buttons = getButtons(fnode.element);
|
||||
return buttons.some(button =>
|
||||
checkValueAgainstRegex(button.innerText, newsletterRegex)
|
||||
return elementHasPredicateMatchingButton(
|
||||
fnode.element,
|
||||
button =>
|
||||
checkValueAgainstRegex(button.innerText, subscriptionRegex) ||
|
||||
buttonMatchesPredicate(button, attr =>
|
||||
checkValueAgainstRegex(attr, subscriptionRegex)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -291,85 +262,129 @@ function createRuleset(coeffs, biases) {
|
|||
* Check form attributes
|
||||
*/
|
||||
function formAttributesMatchRegisterRegex(fnode) {
|
||||
return attributesMatch(
|
||||
fnode.element,
|
||||
attr => checkValueAgainstRegex(attr, registerRegex),
|
||||
["action", "id", "name", "className"]
|
||||
return formMatchesPredicate(fnode.element, attr =>
|
||||
checkValueAgainstRegex(attr, registerRegex)
|
||||
);
|
||||
}
|
||||
|
||||
function formAttributesMatchLoginRegex(fnode) {
|
||||
return attributesMatch(
|
||||
fnode.element,
|
||||
attr => checkValueAgainstRegex(attr, loginRegex),
|
||||
["action", "id", "name", "className"]
|
||||
return formMatchesPredicate(fnode.element, attr =>
|
||||
checkValueAgainstRegex(attr, loginRegex)
|
||||
);
|
||||
}
|
||||
function formAttributesMatchNewsletterRegex(fnode) {
|
||||
return attributesMatch(fnode.element, attr =>
|
||||
checkValueAgainstRegex(attr, newsletterRegex)
|
||||
function formAttributesMatchSubscriptionRegex(fnode) {
|
||||
return formMatchesPredicate(fnode.element, attr =>
|
||||
checkValueAgainstRegex(attr, subscriptionRegex)
|
||||
);
|
||||
}
|
||||
function formMethodIsPost(fnode) {
|
||||
return fnode.element.method === "post";
|
||||
function formAttributesMatchLoginAndRegisterRegex(fnode) {
|
||||
return formMatchesPredicate(fnode.element, attr =>
|
||||
checkValueAgainstAllRegex(attr, [registerRegex, loginRegex])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* HELPER FUNCTIONS
|
||||
*/
|
||||
function closestElementIsRegexMatchingLabel(elements, regexExp) {
|
||||
return elements.some(elem => {
|
||||
const previousElem = elem.previousElementSibling;
|
||||
const closestLabel = closestSelectorElementWithinElement(
|
||||
elem,
|
||||
previousElem,
|
||||
"label"
|
||||
);
|
||||
return (
|
||||
closestLabel && checkValueAgainstRegex(closestLabel.innerText, regexExp)
|
||||
);
|
||||
});
|
||||
function elementMatchesPredicate(element, predicate, additional = []) {
|
||||
return attributesMatch(
|
||||
element,
|
||||
predicate,
|
||||
["id", "name", "className"].concat(additional)
|
||||
);
|
||||
}
|
||||
function formContainsRegexMatchingElement(element, selector, regexExp) {
|
||||
function formMatchesPredicate(element, predicate) {
|
||||
return elementMatchesPredicate(element, predicate, ["action"]);
|
||||
}
|
||||
function inputFieldMatchesPredicate(element, predicate) {
|
||||
return elementMatchesPredicate(element, predicate, ["placeholder"]);
|
||||
}
|
||||
function inputFieldMatchesSelector(element, selector) {
|
||||
return atLeastOne(getElementDescendants(element, `input[${selector}]`));
|
||||
}
|
||||
function buttonMatchesPredicate(element, predicate) {
|
||||
return elementMatchesPredicate(element, predicate, [
|
||||
"value",
|
||||
"id",
|
||||
"title",
|
||||
]);
|
||||
}
|
||||
/**
|
||||
* ELEMENT HAS PREDICATE MATCHING X FUNCTIONS
|
||||
*/
|
||||
function elementHasPredicateMatchingDescendant(element, selector, predicate) {
|
||||
const matchingElements = getElementDescendants(element, selector);
|
||||
return matchingElements.some(predicate);
|
||||
}
|
||||
function elementHasPredicateMatchingHeader(element, predicate) {
|
||||
return (
|
||||
elementHasPredicateMatchingDescendant(
|
||||
element,
|
||||
"h1,h2,h3,h4,h5,h6",
|
||||
predicate
|
||||
) ||
|
||||
elementHasPredicateMatchingDescendant(
|
||||
element,
|
||||
"div[class*=heading],div[class*=header],div[class*=title],header",
|
||||
predicate
|
||||
)
|
||||
);
|
||||
}
|
||||
function elementHasPredicateMatchingButton(element, predicate) {
|
||||
return elementHasPredicateMatchingDescendant(
|
||||
element,
|
||||
"button,input[type=submit],input[type=button]",
|
||||
predicate
|
||||
);
|
||||
}
|
||||
function elementHasPredicateMatchingInput(element, predicate) {
|
||||
return elementHasPredicateMatchingDescendant(element, "input", predicate);
|
||||
}
|
||||
function elementHasPredicateMatchingHyperlink(element, regexExp) {
|
||||
return elementHasPredicateMatchingDescendant(
|
||||
element,
|
||||
"a",
|
||||
link =>
|
||||
previousSiblingLabelMatchesRegex(link, regexExp) ||
|
||||
checkValueAgainstRegex(link.innerText, regexExp) ||
|
||||
elementMatchesPredicate(
|
||||
link,
|
||||
attr => checkValueAgainstRegex(attr, regexExp),
|
||||
["href"]
|
||||
) ||
|
||||
nextSiblingLabelMatchesRegex(link, regexExp)
|
||||
);
|
||||
}
|
||||
function elementHasRegexMatchingCheckbox(element, regexExp) {
|
||||
return elementHasPredicateMatchingDescendant(
|
||||
element,
|
||||
"input[type=checkbox], div[class*=checkbox]",
|
||||
box =>
|
||||
elementMatchesPredicate(box, attr =>
|
||||
checkValueAgainstRegex(attr, regexExp)
|
||||
) || nextSiblingLabelMatchesRegex(box, regexExp)
|
||||
);
|
||||
}
|
||||
|
||||
return matchingElements.some(elem =>
|
||||
attributesMatch(elem, attr => checkValueAgainstRegex(attr, regexExp))
|
||||
);
|
||||
function nextSiblingLabelMatchesRegex(element, regexExp) {
|
||||
let nextElem = element.nextElementSibling;
|
||||
if (nextElem && nextElem.tagName == "LABEL") {
|
||||
return checkValueAgainstRegex(nextElem.innerText, regexExp);
|
||||
}
|
||||
let closestElem = closestElementFollowing(element, "label");
|
||||
return closestElem
|
||||
? checkValueAgainstRegex(closestElem.innerText, regexExp)
|
||||
: false;
|
||||
}
|
||||
function checkInputFieldsForAttr(element, attr) {
|
||||
return atLeastOne(getElementDescendants(element, `input[${attr}]`));
|
||||
}
|
||||
function headerInFormMatchesRegex(element, regexExp) {
|
||||
const headers = getElementDescendants(
|
||||
element,
|
||||
"h1,h2,h3,h4,h5,h6,div[class*=heading],div[class*=header],div[class*=title],header"
|
||||
);
|
||||
return headers.some(head =>
|
||||
checkValueAgainstRegex(head.innerText, regexExp)
|
||||
);
|
||||
}
|
||||
function checkboxInFormMatchesRegex(element, regexExp) {
|
||||
const checkboxes = getElementDescendants(element, "input[type=checkbox]");
|
||||
return checkboxes.some(box =>
|
||||
attributesMatch(box, attr => checkValueAgainstRegex(attr, regexExp))
|
||||
);
|
||||
}
|
||||
function getEmailInputElements(element) {
|
||||
const possibleEmailFields = getElementDescendants(element, "input");
|
||||
return possibleEmailFields.filter(field => {
|
||||
return attributesMatch(
|
||||
field,
|
||||
attr => checkValueAgainstRegex(attr, emailRegex),
|
||||
["type", "id", "name", "className", "placeholder", "innerText"]
|
||||
);
|
||||
});
|
||||
}
|
||||
function getButtons(element) {
|
||||
return getElementDescendants(
|
||||
element,
|
||||
"button,input[type=submit],input[type=button]"
|
||||
);
|
||||
|
||||
function previousSiblingLabelMatchesRegex(element, regexExp) {
|
||||
let previousElem = element.previousElementSibling;
|
||||
if (previousElem && previousElem.tagName == "LABEL") {
|
||||
return checkValueAgainstRegex(previousElem.innerText, regexExp);
|
||||
}
|
||||
let closestElem = closestElementAbove(element, "label");
|
||||
return closestElem
|
||||
? checkValueAgainstRegex(closestElem.innerText, regexExp)
|
||||
: false;
|
||||
}
|
||||
function getElementDescendants(element, selector) {
|
||||
const selectorToDescendants = setDefault(
|
||||
|
@ -378,38 +393,34 @@ function createRuleset(coeffs, biases) {
|
|||
() => new Map()
|
||||
);
|
||||
|
||||
return setDefault(selectorToDescendants, selector, () =>
|
||||
Array.from(element.querySelectorAll(selector))
|
||||
return setDefault(
|
||||
selectorToDescendants, // prettier-ignore
|
||||
selector,
|
||||
() => Array.from(element.querySelectorAll(selector))
|
||||
);
|
||||
}
|
||||
function clearCache() {
|
||||
elementToSelectors = new WeakMap();
|
||||
}
|
||||
function closestSelectorElementWithinElement(
|
||||
toElement,
|
||||
withinElement,
|
||||
querySelector
|
||||
) {
|
||||
if (withinElement) {
|
||||
let matchingElements = Array.from(
|
||||
withinElement.querySelectorAll(querySelector)
|
||||
);
|
||||
if (matchingElements.length) {
|
||||
return min(matchingElements, match => euclidean(match, toElement));
|
||||
function closestHeaderMatchesPredicate(element, predicate) {
|
||||
return (
|
||||
elementHasPredicateMatchingHeader(element, predicate) ||
|
||||
closestHeaderAboveMatchesPredicate(element, predicate)
|
||||
);
|
||||
}
|
||||
function closestHeaderAboveMatchesPredicate(element, predicate) {
|
||||
let closestHeader = closestElementAbove(element, "h1,h2,h3,h4,h5,h6");
|
||||
|
||||
if (closestHeader !== null) {
|
||||
if (predicate(closestHeader)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function closestHeaderAboveMatchesRegex(element, regex) {
|
||||
const closestHeader = closestElementAbove(
|
||||
closestHeader = closestElementAbove(
|
||||
element,
|
||||
"h1,h2,h3,h4,h5,h6,div[class*=heading],div[class*=header],div[class*=title],header"
|
||||
"div[class*=heading],div[class*=header],div[class*=title],header"
|
||||
);
|
||||
|
||||
if (closestHeader == null) {
|
||||
return false;
|
||||
}
|
||||
return checkValueAgainstRegex(closestHeader.innerText, regex);
|
||||
return closestHeader ? predicate(closestHeader) : false;
|
||||
}
|
||||
function closestElementAbove(element, selector) {
|
||||
let elements = Array.from(
|
||||
|
@ -425,32 +436,52 @@ function createRuleset(coeffs, biases) {
|
|||
}
|
||||
return null;
|
||||
}
|
||||
function closestElementFollowing(element, selector) {
|
||||
let elements = Array.from(
|
||||
getElementDescendants(element.ownerDocument, selector)
|
||||
);
|
||||
for (let i = 0; i < elements.length; ++i) {
|
||||
if (
|
||||
element.compareDocumentPosition(elements[i]) &
|
||||
Node.DOCUMENT_POSITION_FOLLOWING
|
||||
) {
|
||||
return elements[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function checkValueAgainstAllRegex(value, regexExp = []) {
|
||||
return regexExp.every(reg => checkValueAgainstRegex(value, reg));
|
||||
}
|
||||
|
||||
function checkValueAgainstRegex(value, regexExp) {
|
||||
const lowerCaseValue = value ? value.toLowerCase() : "";
|
||||
return regexExp.test(lowerCaseValue);
|
||||
return value ? regexExp.test(value) : false;
|
||||
}
|
||||
function atLeastOne(iter) {
|
||||
return iter.length >= 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* CREATION OF RULESET
|
||||
*/
|
||||
const rules = ruleset(
|
||||
[
|
||||
rule(
|
||||
DEVELOPMENT ? dom("form").when(isVisible) : element("form"),
|
||||
type("form").note(clearCache)
|
||||
),
|
||||
|
||||
// Check form attributes
|
||||
rule(type("form"), score(formAttributesMatchRegisterRegex), {
|
||||
name: "formAttributesMatchRegisterRegex",
|
||||
}),
|
||||
rule(type("form"), score(formMethodIsPost), { name: "formMethodIsPost" }),
|
||||
rule(type("form"), score(formAttributesMatchLoginRegex), {
|
||||
name: "formAttributesMatchLoginRegex",
|
||||
}),
|
||||
rule(type("form"), score(formAttributesMatchNewsletterRegex), {
|
||||
name: "formAttributesMatchNewsletterRegex",
|
||||
rule(type("form"), score(formAttributesMatchSubscriptionRegex), {
|
||||
name: "formAttributesMatchSubscriptionRegex",
|
||||
}),
|
||||
rule(type("form"), score(formAttributesMatchLoginAndRegisterRegex), {
|
||||
name: "formAttributesMatchLoginAndRegisterRegex",
|
||||
}),
|
||||
// Check autocomplete attributes
|
||||
rule(type("form"), score(formHasAcCurrentPassword), {
|
||||
|
@ -459,15 +490,6 @@ function createRuleset(coeffs, biases) {
|
|||
rule(type("form"), score(formHasAcNewPassword), {
|
||||
name: "formHasAcNewPassword",
|
||||
}),
|
||||
rule(type("form"), score(formHasAcTel), {
|
||||
name: "formHasAcTel",
|
||||
}),
|
||||
rule(type("form"), score(formHasAcUsername), {
|
||||
name: "formHasAcUsername",
|
||||
}),
|
||||
rule(type("form"), score(formHasAcEmail), {
|
||||
name: "formHasAcEmail",
|
||||
}),
|
||||
// Check input fields
|
||||
rule(type("form"), score(formHasEmailField), {
|
||||
name: "formHasEmailField",
|
||||
|
@ -475,20 +497,11 @@ function createRuleset(coeffs, biases) {
|
|||
rule(type("form"), score(formHasUsernameField), {
|
||||
name: "formHasUsernameField",
|
||||
}),
|
||||
rule(type("form"), score(formHasEmailAndExtraNameField), {
|
||||
name: "formHasEmailAndExtraNameField",
|
||||
}),
|
||||
rule(type("form"), score(formHasPasswordField), {
|
||||
name: "formHasPasswordField",
|
||||
}),
|
||||
rule(type("form"), score(formHasFirstOrLastNameFields), {
|
||||
name: "formHasFirstOrLastNameFields",
|
||||
}),
|
||||
rule(type("form"), score(formHasBirthdayFields), {
|
||||
name: "formHasBirthdayFields",
|
||||
}),
|
||||
rule(type("form"), score(formHasPhoneField), {
|
||||
name: "formHasPhoneField",
|
||||
rule(type("form"), score(formHasFirstOrLastNameField), {
|
||||
name: "formHasFirstOrLastNameField",
|
||||
}),
|
||||
// Check buttons
|
||||
rule(type("form"), score(formHasRegisterButton), {
|
||||
|
@ -503,17 +516,21 @@ function createRuleset(coeffs, biases) {
|
|||
rule(type("form"), score(formHasSubscribeButton), {
|
||||
name: "formHasSubscribeButton",
|
||||
}),
|
||||
// Check hyperlinks
|
||||
rule(type("form"), score(formHasTermsAndConditionsHyperlink), {
|
||||
name: "formHasTermsAndConditionsHyperlink",
|
||||
}),
|
||||
rule(type("form"), score(formHasPasswordForgottenHyperlink), {
|
||||
name: "formHasPasswordForgottenHyperlink",
|
||||
}),
|
||||
rule(type("form"), score(formHasAlreadySignedUpHyperlink), {
|
||||
name: "formHasAlreadySignedUpHyperlink",
|
||||
}),
|
||||
// Check labels
|
||||
rule(type("form"), score(closestElementIsEmailLabelLike), {
|
||||
name: "closestElementIsEmailLabelLike",
|
||||
}),
|
||||
rule(type("form"), score(closestElementIsNewPasswordLabelLike), {
|
||||
name: "closestElementIsNewPasswordLabelLike",
|
||||
}),
|
||||
// Check checkboxes
|
||||
rule(type("form"), score(formHasTermsAndConditionsCheckbox), {
|
||||
name: "formHasTermsAndConditionsCheckbox",
|
||||
}),
|
||||
rule(type("form"), score(formHasRememberMeCheckbox), {
|
||||
name: "formHasRememberMeCheckbox",
|
||||
}),
|
||||
|
@ -527,22 +544,16 @@ function createRuleset(coeffs, biases) {
|
|||
rule(type("form"), score(closestHeaderMatchesLoginRegex), {
|
||||
name: "closestHeaderMatchesLoginRegex",
|
||||
}),
|
||||
rule(type("form"), score(closestHeaderMatchesNewsletterRegex), {
|
||||
name: "closestHeaderMatchesNewsletterRegex",
|
||||
rule(type("form"), score(closestHeaderMatchesSubscriptionRegex), {
|
||||
name: "closestHeaderMatchesSubscriptionRegex",
|
||||
}),
|
||||
// Check doc characteristics
|
||||
// Check doc title
|
||||
rule(type("form"), score(docTitleMatchesRegisterRegex), {
|
||||
name: "docTitleMatchesRegisterRegex",
|
||||
}),
|
||||
rule(type("form"), score(docTitleMatchesEditProfileRegex), {
|
||||
name: "docTitleMatchesEditProfileRegex",
|
||||
}),
|
||||
rule(type("form"), score(docHasLoginHyperlink), {
|
||||
name: "docHasLoginHyperlink",
|
||||
}),
|
||||
rule(type("form"), score(docHasRegisterOrPasswordForgottenHyperlink), {
|
||||
name: "docHasRegisterOrPasswordForgottenHyperlink",
|
||||
}),
|
||||
rule(type("form"), out("form")),
|
||||
],
|
||||
coeffs,
|
||||
|
@ -557,5 +568,5 @@ function createRuleset(coeffs, biases) {
|
|||
|
||||
export const SignUpFormRuleset = {
|
||||
type: "form",
|
||||
rules: createRuleset([...coefficients.signup], biases),
|
||||
rules: createRuleset([...coefficients.form], biases),
|
||||
};
|
||||
|
|
Загрузка…
Ссылка в новой задаче