diff options
Diffstat (limited to '')
-rw-r--r-- | browser/extensions/formautofill/test/unit/test_collectFormFields.js | 638 |
1 files changed, 638 insertions, 0 deletions
diff --git a/browser/extensions/formautofill/test/unit/test_collectFormFields.js b/browser/extensions/formautofill/test/unit/test_collectFormFields.js new file mode 100644 index 0000000000..ac56d29c69 --- /dev/null +++ b/browser/extensions/formautofill/test/unit/test_collectFormFields.js @@ -0,0 +1,638 @@ +/* + * Test for form auto fill content helper collectFormFields functions. + */ + +"use strict"; + +var FormAutofillHandler; +add_setup(async () => { + ({ FormAutofillHandler } = ChromeUtils.importESModule( + "resource://gre/modules/shared/FormAutofillHandler.sys.mjs" + )); +}); + +const TESTCASES = [ + { + description: "Form without autocomplete property", + document: `<form> + <input id="given-name"> + <input id="family-name"> + <input id="street-addr"> + <input id="city"> + <select id="country"></select> + <input id='email'> + <input id="phone"> + </form>`, + sections: [ + [ + { fieldName: "given-name" }, + { fieldName: "family-name" }, + { fieldName: "address-line1" }, + { fieldName: "address-level2" }, + { fieldName: "country" }, + { fieldName: "email" }, + { fieldName: "tel" }, + ], + ], + ids: [ + "given-name", + "family-name", + "street-addr", + "city", + "country", + "email", + "phone", + ], + }, + { + description: + "An address and credit card form with autocomplete properties and 1 token", + document: `<form> + <input id="given-name" autocomplete="given-name"> + <input id="family-name" autocomplete="family-name"> + <input id="street-address" autocomplete="street-address"> + <input id="address-level2" autocomplete="address-level2"> + <select id="country" autocomplete="country"></select> + <input id="email" autocomplete="email"> + <input id="tel" autocomplete="tel"> + <input id="cc-number" autocomplete="cc-number"> + <input id="cc-name" autocomplete="cc-name"> + <input id="cc-exp-month" autocomplete="cc-exp-month"> + <input id="cc-exp-year" autocomplete="cc-exp-year"> + </form>`, + sections: [ + [ + { fieldName: "given-name" }, + { fieldName: "family-name" }, + { fieldName: "street-address" }, + { fieldName: "address-level2" }, + { fieldName: "country" }, + { fieldName: "email" }, + { fieldName: "tel" }, + ], + [ + { fieldName: "cc-number" }, + { fieldName: "cc-name" }, + { fieldName: "cc-exp-month" }, + { fieldName: "cc-exp-year" }, + ], + ], + }, + { + description: "An address form with autocomplete properties and 2 tokens", + document: `<form><input id="given-name" autocomplete="shipping given-name"> + <input id="family-name" autocomplete="shipping family-name"> + <input id="street-address" autocomplete="shipping street-address"> + <input id="address-level2" autocomplete="shipping address-level2"> + <input id="country" autocomplete="shipping country"> + <input id='email' autocomplete="shipping email"> + <input id="tel" autocomplete="shipping tel"></form>`, + sections: [ + [ + { addressType: "shipping", fieldName: "given-name" }, + { addressType: "shipping", fieldName: "family-name" }, + { addressType: "shipping", fieldName: "street-address" }, + { addressType: "shipping", fieldName: "address-level2" }, + { addressType: "shipping", fieldName: "country" }, + { addressType: "shipping", fieldName: "email" }, + { addressType: "shipping", fieldName: "tel" }, + ], + ], + }, + { + description: + "Form with autocomplete properties and profile is partly matched", + document: `<form><input id="given-name" autocomplete="shipping given-name"> + <input id="family-name" autocomplete="shipping family-name"> + <input id="street-address" autocomplete="shipping street-address"> + <input id="address-level2" autocomplete="shipping address-level2"> + <select id="country" autocomplete="shipping country"></select> + <input id='email' autocomplete="shipping email"> + <input id="tel" autocomplete="shipping tel"></form>`, + sections: [ + [ + { addressType: "shipping", fieldName: "given-name" }, + { addressType: "shipping", fieldName: "family-name" }, + { addressType: "shipping", fieldName: "street-address" }, + { addressType: "shipping", fieldName: "address-level2" }, + { addressType: "shipping", fieldName: "country" }, + { addressType: "shipping", fieldName: "email" }, + { addressType: "shipping", fieldName: "tel" }, + ], + ], + }, + { + description: "It's a valid address and credit card form.", + document: `<form> + <input id="given-name" autocomplete="shipping given-name"> + <input id="family-name" autocomplete="shipping family-name"> + <input id="street-address" autocomplete="shipping street-address"> + <input id="cc-number" autocomplete="shipping cc-number"> + </form>`, + sections: [ + [ + { addressType: "shipping", fieldName: "given-name" }, + { addressType: "shipping", fieldName: "family-name" }, + { addressType: "shipping", fieldName: "street-address" }, + ], + [{ addressType: "shipping", fieldName: "cc-number" }], + ], + }, + { + description: "An invalid address form due to less than 3 fields.", + document: `<form> + <input id="given-name" autocomplete="shipping given-name"> + <input autocomplete="shipping address-level2"> + </form>`, + sections: [], + }, + /* + * Valid Credit Card Form with autocomplete attribute + */ + { + description: "@autocomplete - A valid credit card form", + document: `<form> + <input id="cc-number" autocomplete="cc-number"> + <input id="cc-name" autocomplete="cc-name"> + <input id="cc-exp" autocomplete="cc-exp"> + </form>`, + sections: [ + [ + { fieldName: "cc-number" }, + { fieldName: "cc-name" }, + { fieldName: "cc-exp" }, + ], + ], + }, + { + description: "@autocomplete - A valid credit card form without cc-numner", + document: `<form> + <input id="cc-name" autocomplete="cc-name"> + <input id="cc-exp" autocomplete="cc-exp"> + </form>`, + sections: [[{ fieldName: "cc-name" }, { fieldName: "cc-exp" }]], + }, + { + description: "@autocomplete - A valid cc-number only form", + document: `<form><input id="cc-number" autocomplete="cc-number"></form>`, + sections: [[{ fieldName: "cc-number" }]], + }, + { + description: "@autocomplete - A valid cc-name only form", + document: `<form><input id="cc-name" autocomplete="cc-name"></form>`, + sections: [[{ fieldName: "cc-name" }]], + }, + { + description: "@autocomplete - A valid cc-exp only form", + document: `<form><input id="cc-exp" autocomplete="cc-exp"></form>`, + sections: [[{ fieldName: "cc-exp" }]], + }, + { + description: "@autocomplete - A valid cc-exp-month + cc-exp-year form", + document: `<form> + <input id="cc-exp-month" autocomplete="cc-exp-month"> + <input id="cc-exp-year" autocomplete="cc-exp-year"> + </form>`, + sections: [[{ fieldName: "cc-exp-month" }, { fieldName: "cc-exp-year" }]], + }, + { + description: "@autocomplete - A valid cc-exp-month only form", + document: `<form><input id="cc-exp-month" autocomplete="cc-exp-month"></form>`, + sections: [[{ fieldName: "cc-exp-month" }]], + }, + { + description: "@autocomplete - A valid cc-exp-year only form", + document: `<form><input id="cc-exp-year" autocomplete="cc-exp-year"></form>`, + sections: [[{ fieldName: "cc-exp-year" }]], + }, + /* + * Valid Credit Card Form when cc-number or cc-name is detected by fathom + */ + { + description: + "A valid credit card form without autocomplete attribute (cc-number is detected by fathom)", + document: `<form> + <input id="cc-number" name="cc-number"> + <input id="cc-name" name="cc-name"> + <input id="cc-exp" name="cc-exp"> + </form>`, + sections: [ + [ + { fieldName: "cc-number" }, + { fieldName: "cc-name" }, + { fieldName: "cc-exp" }, + ], + ], + prefs: [ + [ + "extensions.formautofill.creditCards.heuristics.fathom.testConfidence", + "0.8", + ], + ], + }, + { + description: + "A valid credit card form without autocomplete attribute (only cc-number is detected by fathom)", + document: `<form> + <input id="cc-number" name="cc-number"> + <input id="cc-name" name="cc-name"> + <input id="cc-exp" name="cc-exp"> + </form>`, + sections: [ + [ + { fieldName: "cc-number" }, + { fieldName: "cc-name" }, + { fieldName: "cc-exp" }, + ], + ], + prefs: [ + [ + "extensions.formautofill.creditCards.heuristics.fathom.testConfidence", + "0.8", + ], + [ + "extensions.formautofill.creditCards.heuristics.fathom.types", + "cc-number", + ], + ], + }, + { + description: + "A valid credit card form without autocomplete attribute (only cc-name is detected by fathom)", + document: `<form> + <input id="cc-name" name="cc-name"> + <input id="cc-exp" name="cc-exp"> + </form>`, + sections: [[{ fieldName: "cc-name" }, { fieldName: "cc-exp" }]], + prefs: [ + [ + "extensions.formautofill.creditCards.heuristics.fathom.testConfidence", + "0.8", + ], + [ + "extensions.formautofill.creditCards.heuristics.fathom.types", + "cc-name", + ], + ], + }, + /* + * Invalid Credit Card Form when a cc-number or cc-name is detected by fathom + */ + { + description: + "A credit card form is invalid when a fathom detected cc-number field is the only field in the form", + document: `<form><input id="cc-number" name="cc-number"></form>`, + sections: [], + prefs: [ + [ + "extensions.formautofill.creditCards.heuristics.fathom.highConfidenceThreshold", + "0.9", + ], + [ + "extensions.formautofill.creditCards.heuristics.fathom.testConfidence", + "0.8", + ], + ], + }, + { + description: + "A credit card form is invalid when a fathom detected cc-name field is the only field in the form", + document: `<form><input id="cc-name" name="cc-name"></form>`, + sections: [], + prefs: [ + [ + "extensions.formautofill.creditCards.heuristics.fathom.highConfidenceThreshold", + "0.9", + ], + [ + "extensions.formautofill.creditCards.heuristics.fathom.testConfidence", + "0.8", + ], + ], + }, + /* + * Valid Credit Card Form when a cc-number or cc-name only form is detected by fathom (field is high confidence) + */ + { + description: + "A cc-number only form is considered a valid credit card form when fathom is confident and there is no other <input> in the form", + document: `<form><input id="cc-number" name="cc-number"></form>`, + sections: [[{ fieldName: "cc-number" }]], + prefs: [ + [ + "extensions.formautofill.creditCards.heuristics.fathom.highConfidenceThreshold", + "0.95", + ], + [ + "extensions.formautofill.creditCards.heuristics.fathom.testConfidence", + "0.99", + ], + ], + }, + { + description: + "A cc-name only form is considered a valid credit card form when fathom is confident and there is no other <input> in the form", + document: `<form><input id="cc-name" name="cc-name"></form>`, + sections: [[{ fieldName: "cc-name" }]], + prefs: [ + [ + "extensions.formautofill.creditCards.heuristics.fathom.highConfidenceThreshold", + "0.95", + ], + [ + "extensions.formautofill.creditCards.heuristics.fathom.testConfidence", + "0.99", + ], + ], + }, + /* + * Invalid Credit Card Form when none of the fields is identified by fathom + */ + { + description: + "A credit card form is invalid when none of the fields are identified by fathom or autocomplete", + document: `<form> + <input id="cc-number" name="cc-number"> + <input id="cc-name" name="cc-name"> + <input id="cc-exp" name="cc-exp"> + </form>`, + sections: [], + prefs: [ + ["extensions.formautofill.creditCards.heuristics.fathom.types", ""], + ], + }, + // Special Cases + { + description: + "A credit card form with a high-confidence cc-name field is still considered invalid when there is another <input> field", + document: `<form> + <input id="cc-name" name="cc-name"> + <input id="password" type="password"> + </form>`, + sections: [], + prefs: [ + [ + "extensions.formautofill.creditCards.heuristics.fathom.highConfidenceThreshold", + "0.95", + ], + [ + "extensions.formautofill.creditCards.heuristics.fathom.testConfidence", + "0.96", + ], + ], + }, + { + description: "A valid credit card form with multiple cc-number fields", + document: `<form> + <input id="cc-number1" maxlength="4"> + <input id="cc-number2" maxlength="4"> + <input id="cc-number3" maxlength="4"> + <input id="cc-number4" maxlength="4"> + <input id="cc-exp-month" autocomplete="cc-exp-month"> + <input id="cc-exp-year" autocomplete="cc-exp-year"> + </form>`, + sections: [ + [ + { fieldName: "cc-number" }, + { fieldName: "cc-number" }, + { fieldName: "cc-number" }, + { fieldName: "cc-number" }, + { fieldName: "cc-exp-month" }, + { fieldName: "cc-exp-year" }, + ], + ], + ids: [ + "cc-number1", + "cc-number2", + "cc-number3", + "cc-number4", + "cc-exp-month", + "cc-exp-year", + ], + }, + { + description: "Three sets of adjacent phone number fields", + document: `<form> + <input id="shippingAC" name="phone" maxlength="3"> + <input id="shippingPrefix" name="phone" maxlength="3"> + <input id="shippingSuffix" name="phone" maxlength="4"> + <input id="shippingTelExt" name="extension"> + + <input id="billingAC" name="phone" maxlength="3"> + <input id="billingPrefix" name="phone" maxlength="3"> + <input id="billingSuffix" name="phone" maxlength="4"> + + <input id="otherCC" name="phone" maxlength="3"> + <input id="otherAC" name="phone" maxlength="3"> + <input id="otherPrefix" name="phone" maxlength="3"> + <input id="otherSuffix" name="phone" maxlength="4"> + </form>`, + sections: [ + [ + { fieldName: "tel-area-code" }, + { fieldName: "tel-local-prefix" }, + { fieldName: "tel-local-suffix" }, + { fieldName: "tel-extension" }, + ], + [ + { fieldName: "tel-area-code" }, + { fieldName: "tel-local-prefix" }, + { fieldName: "tel-local-suffix" }, + + // TODO Bug 1421181 - "tel-country-code" field should belong to the next + // section. There should be a way to group the related fields during the + // parsing stage. + { fieldName: "tel-country-code" }, + ], + [ + { fieldName: "tel-area-code" }, + { fieldName: "tel-local-prefix" }, + { fieldName: "tel-local-suffix" }, + ], + ], + ids: [ + "shippingAC", + "shippingPrefix", + "shippingSuffix", + "shippingTelExt", + "billingAC", + "billingPrefix", + "billingSuffix", + "otherCC", + "otherAC", + "otherPrefix", + "otherSuffix", + ], + }, + { + description: + "Do not dedup the same field names of the different telephone fields.", + document: `<form> + <input id="i1" autocomplete="given-name"> + <input id="i2" autocomplete="family-name"> + <input id="i3" autocomplete="street-address"> + <input id="i4" autocomplete="email"> + + <input id="homePhone" maxlength="10"> + <input id="mobilePhone" maxlength="10"> + <input id="officePhone" maxlength="10"> + </form>`, + sections: [ + [ + { fieldName: "given-name" }, + { fieldName: "family-name" }, + { fieldName: "street-address" }, + { fieldName: "email" }, + { fieldName: "tel" }, + { fieldName: "tel" }, + { fieldName: "tel" }, + ], + ], + ids: ["i1", "i2", "i3", "i4", "homePhone", "mobilePhone", "officePhone"], + }, + { + description: + "The duplicated phones of a single one and a set with ac, prefix, suffix.", + document: `<form> + <input id="i1" autocomplete="shipping given-name"> + <input id="i2" autocomplete="shipping family-name"> + <input id="i3" autocomplete="shipping street-address"> + <input id="i4" autocomplete="shipping email"> + <input id="singlePhone" autocomplete="shipping tel"> + <input id="shippingAreaCode" autocomplete="shipping tel-area-code"> + <input id="shippingPrefix" autocomplete="shipping tel-local-prefix"> + <input id="shippingSuffix" autocomplete="shipping tel-local-suffix"> + </form>`, + sections: [ + [ + { addressType: "shipping", fieldName: "given-name" }, + { addressType: "shipping", fieldName: "family-name" }, + { addressType: "shipping", fieldName: "street-address" }, + { addressType: "shipping", fieldName: "email" }, + + // NOTES: Ideally, there is only one full telephone field(s) in a form for + // this case. We can see if there is any better solution later. + { addressType: "shipping", fieldName: "tel" }, + { addressType: "shipping", fieldName: "tel-area-code" }, + { addressType: "shipping", fieldName: "tel-local-prefix" }, + { addressType: "shipping", fieldName: "tel-local-suffix" }, + ], + ], + ids: [ + "i1", + "i2", + "i3", + "i4", + "singlePhone", + "shippingAreaCode", + "shippingPrefix", + "shippingSuffix", + ], + }, + { + description: "Always adopt the info from autocomplete attribute.", + document: `<form> + <input id="given-name" autocomplete="shipping given-name"> + <input id="family-name" autocomplete="shipping family-name"> + <input id="dummyAreaCode" autocomplete="shipping tel" maxlength="3"> + <input id="dummyPrefix" autocomplete="shipping tel" maxlength="3"> + <input id="dummySuffix" autocomplete="shipping tel" maxlength="4"> + </form>`, + sections: [ + [ + { addressType: "shipping", fieldName: "given-name" }, + { addressType: "shipping", fieldName: "family-name" }, + { addressType: "shipping", fieldName: "tel" }, + { addressType: "shipping", fieldName: "tel" }, + { addressType: "shipping", fieldName: "tel" }, + ], + ], + ids: [ + "given-name", + "family-name", + "dummyAreaCode", + "dummyPrefix", + "dummySuffix", + ], + }, +]; + +function verifyDetails(handlerDetails, testCaseDetails) { + if (handlerDetails === null) { + Assert.equal(handlerDetails, testCaseDetails); + return; + } + Assert.equal(handlerDetails.length, testCaseDetails.length, "field count"); + handlerDetails.forEach((detail, index) => { + Assert.equal( + detail.fieldName, + testCaseDetails[index].fieldName, + "fieldName" + ); + Assert.equal( + detail.section, + testCaseDetails[index].section ?? "", + "section" + ); + Assert.equal( + detail.addressType, + testCaseDetails[index].addressType ?? "", + "addressType" + ); + Assert.equal( + detail.contactType, + testCaseDetails[index].contactType ?? "", + "contactType" + ); + Assert.equal( + detail.elementWeakRef.get(), + testCaseDetails[index].elementWeakRef.get(), + "DOM reference" + ); + }); +} + +for (let tc of TESTCASES) { + (function () { + let testcase = tc; + add_task(async function () { + info("Starting testcase: " + testcase.description); + + if (testcase.prefs) { + testcase.prefs.forEach(pref => SetPref(pref[0], pref[1])); + } + + let doc = MockDocument.createTestDocument( + "http://localhost:8080/test/", + testcase.document + ); + testcase.sections.flat().forEach((field, idx) => { + let elementRef = doc.getElementById( + testcase.ids?.[idx] ?? field.fieldName + ); + field.elementWeakRef = Cu.getWeakReference(elementRef); + }); + + let form = doc.querySelector("form"); + let formLike = FormLikeFactory.createFromForm(form); + + let handler = new FormAutofillHandler(formLike); + let validFieldDetails = handler.collectFormFields(); + + Assert.equal( + handler.sections.length, + testcase.sections.length, + "section count" + ); + for (let i = 0; i < handler.sections.length; i++) { + let section = handler.sections[i]; + verifyDetails(section.fieldDetails, testcase.sections[i]); + } + verifyDetails(validFieldDetails, testcase.sections.flat()); + + if (testcase.prefs) { + testcase.prefs.forEach(pref => Services.prefs.clearUserPref(pref[0])); + } + }); + })(); +} |