зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1339731
- Refactor FormAutofillHandler to support multiple section machanism. r=lchang,ralin
MozReview-Commit-ID: D9g5fKTeTaL --HG-- extra : rebase_source : 1b19750a6f1d9137b9e21170b854d89cd6d2859c
This commit is contained in:
Родитель
532fb7bedc
Коммит
29e1c4d8d8
|
@ -1709,6 +1709,7 @@ pref("extensions.formautofill.creditCards.enabled", true);
|
||||||
pref("extensions.formautofill.creditCards.used", 0);
|
pref("extensions.formautofill.creditCards.used", 0);
|
||||||
pref("extensions.formautofill.firstTimeUse", true);
|
pref("extensions.formautofill.firstTimeUse", true);
|
||||||
pref("extensions.formautofill.heuristics.enabled", true);
|
pref("extensions.formautofill.heuristics.enabled", true);
|
||||||
|
pref("extensions.formautofill.section.enabled", true);
|
||||||
pref("extensions.formautofill.loglevel", "Warn");
|
pref("extensions.formautofill.loglevel", "Warn");
|
||||||
|
|
||||||
// Whether or not to restore a session with lazy-browser tabs.
|
// Whether or not to restore a session with lazy-browser tabs.
|
||||||
|
|
|
@ -102,8 +102,9 @@ AutofillProfileAutoCompleteSearch.prototype = {
|
||||||
let info = FormAutofillContent.getInputDetails(focusedInput);
|
let info = FormAutofillContent.getInputDetails(focusedInput);
|
||||||
let isAddressField = FormAutofillUtils.isAddressField(info.fieldName);
|
let isAddressField = FormAutofillUtils.isAddressField(info.fieldName);
|
||||||
let handler = FormAutofillContent.getFormHandler(focusedInput);
|
let handler = FormAutofillContent.getFormHandler(focusedInput);
|
||||||
let allFieldNames = handler.allFieldNames;
|
let section = handler.getSectionByElement(focusedInput);
|
||||||
let filledRecordGUID = isAddressField ? handler.address.filledRecordGUID : handler.creditCard.filledRecordGUID;
|
let allFieldNames = section.allFieldNames;
|
||||||
|
let filledRecordGUID = isAddressField ? section.address.filledRecordGUID : section.creditCard.filledRecordGUID;
|
||||||
let searchPermitted = isAddressField ?
|
let searchPermitted = isAddressField ?
|
||||||
FormAutofillUtils.isAutofillAddressesEnabled :
|
FormAutofillUtils.isAutofillAddressesEnabled :
|
||||||
FormAutofillUtils.isAutofillCreditCardsEnabled;
|
FormAutofillUtils.isAutofillCreditCardsEnabled;
|
||||||
|
@ -149,7 +150,7 @@ AutofillProfileAutoCompleteSearch.prototype = {
|
||||||
// Sort addresses by timeLastUsed for showing the lastest used address at top.
|
// Sort addresses by timeLastUsed for showing the lastest used address at top.
|
||||||
records.sort((a, b) => b.timeLastUsed - a.timeLastUsed);
|
records.sort((a, b) => b.timeLastUsed - a.timeLastUsed);
|
||||||
|
|
||||||
let adaptedRecords = handler.getAdaptedProfiles(records);
|
let adaptedRecords = handler.getAdaptedProfiles(records, focusedInput);
|
||||||
let result = null;
|
let result = null;
|
||||||
if (isAddressField) {
|
if (isAddressField) {
|
||||||
result = new AddressResult(searchString,
|
result = new AddressResult(searchString,
|
||||||
|
@ -481,7 +482,7 @@ var FormAutofillContent = {
|
||||||
|
|
||||||
getAllFieldNames(element) {
|
getAllFieldNames(element) {
|
||||||
let formHandler = this.getFormHandler(element);
|
let formHandler = this.getFormHandler(element);
|
||||||
return formHandler ? formHandler.allFieldNames : null;
|
return formHandler ? formHandler.getAllFieldNames(element) : null;
|
||||||
},
|
},
|
||||||
|
|
||||||
identifyAutofillFields(element) {
|
identifyAutofillFields(element) {
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
this.EXPORTED_SYMBOLS = ["FormAutofillHandler"];
|
this.EXPORTED_SYMBOLS = ["FormAutofillHandler"]; /* exported FormAutofillHandler */
|
||||||
|
|
||||||
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
||||||
|
|
||||||
|
@ -25,202 +25,81 @@ XPCOMUtils.defineLazyModuleGetter(this, "FormLikeFactory",
|
||||||
this.log = null;
|
this.log = null;
|
||||||
FormAutofillUtils.defineLazyLogGetter(this, this.EXPORTED_SYMBOLS[0]);
|
FormAutofillUtils.defineLazyLogGetter(this, this.EXPORTED_SYMBOLS[0]);
|
||||||
|
|
||||||
/**
|
class FormAutofillSection {
|
||||||
* Handles profile autofill for a DOM Form element.
|
constructor(fieldDetails, winUtils) {
|
||||||
* @param {FormLike} form Form that need to be auto filled
|
this.address = {
|
||||||
*/
|
/**
|
||||||
function FormAutofillHandler(form) {
|
* Similar to the `_validDetails` but contains address fields only.
|
||||||
this._updateForm(form);
|
*/
|
||||||
this.winUtils = this.form.rootElement.ownerGlobal.QueryInterface(Ci.nsIInterfaceRequestor)
|
fieldDetails: [],
|
||||||
.getInterface(Ci.nsIDOMWindowUtils);
|
/**
|
||||||
|
* String of the filled address' guid.
|
||||||
this.address = {
|
*/
|
||||||
/**
|
filledRecordGUID: null,
|
||||||
* Similar to the `fieldDetails` above but contains address fields only.
|
};
|
||||||
*/
|
this.creditCard = {
|
||||||
fieldDetails: [],
|
/**
|
||||||
/**
|
* Similar to the `_validDetails` but contains credit card fields only.
|
||||||
* String of the filled address' guid.
|
*/
|
||||||
*/
|
fieldDetails: [],
|
||||||
filledRecordGUID: null,
|
/**
|
||||||
};
|
* String of the filled creditCard's' guid.
|
||||||
|
*/
|
||||||
this.creditCard = {
|
filledRecordGUID: null,
|
||||||
/**
|
|
||||||
* Similar to the `fieldDetails` above but contains credit card fields only.
|
|
||||||
*/
|
|
||||||
fieldDetails: [],
|
|
||||||
/**
|
|
||||||
* String of the filled creditCard's guid.
|
|
||||||
*/
|
|
||||||
filledRecordGUID: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
this._cacheValue = {
|
|
||||||
allFieldNames: null,
|
|
||||||
oneLineStreetAddress: null,
|
|
||||||
matchingSelectOption: null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
FormAutofillHandler.prototype = {
|
|
||||||
/**
|
|
||||||
* DOM Form element to which this object is attached.
|
|
||||||
*/
|
|
||||||
form: null,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Array of collected data about relevant form fields. Each item is an object
|
|
||||||
* storing the identifying details of the field and a reference to the
|
|
||||||
* originally associated element from the form.
|
|
||||||
*
|
|
||||||
* The "section", "addressType", "contactType", and "fieldName" values are
|
|
||||||
* used to identify the exact field when the serializable data is received
|
|
||||||
* from the backend. There cannot be multiple fields which have
|
|
||||||
* the same exact combination of these values.
|
|
||||||
*
|
|
||||||
* A direct reference to the associated element cannot be sent to the user
|
|
||||||
* interface because processing may be done in the parent process.
|
|
||||||
*/
|
|
||||||
fieldDetails: null,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Subcategory of handler that contains address related data.
|
|
||||||
*/
|
|
||||||
address: null,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Subcategory of handler that contains credit card related data.
|
|
||||||
*/
|
|
||||||
creditCard: null,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A WindowUtils reference of which Window the form belongs
|
|
||||||
*/
|
|
||||||
winUtils: null,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enum for form autofill MANUALLY_MANAGED_STATES values
|
|
||||||
*/
|
|
||||||
fieldStateEnum: {
|
|
||||||
// not themed
|
|
||||||
NORMAL: null,
|
|
||||||
// highlighted
|
|
||||||
AUTO_FILLED: "-moz-autofill",
|
|
||||||
// highlighted && grey color text
|
|
||||||
PREVIEW: "-moz-autofill-preview",
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Time in milliseconds since epoch when a user started filling in the form.
|
|
||||||
*/
|
|
||||||
timeStartedFillingMS: null,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check the form is necessary to be updated. This function should be able to
|
|
||||||
* detect any changes including all control elements in the form.
|
|
||||||
* @param {HTMLElement} element The element supposed to be in the form.
|
|
||||||
* @returns {boolean} FormAutofillHandler.form is updated or not.
|
|
||||||
*/
|
|
||||||
updateFormIfNeeded(element) {
|
|
||||||
// When the following condition happens, FormAutofillHandler.form should be
|
|
||||||
// updated:
|
|
||||||
// * The count of form controls is changed.
|
|
||||||
// * When the element can not be found in the current form.
|
|
||||||
//
|
|
||||||
// However, we should improve the function to detect the element changes.
|
|
||||||
// e.g. a tel field is changed from type="hidden" to type="tel".
|
|
||||||
|
|
||||||
let _formLike;
|
|
||||||
let getFormLike = () => {
|
|
||||||
if (!_formLike) {
|
|
||||||
_formLike = FormLikeFactory.createFromField(element);
|
|
||||||
}
|
|
||||||
return _formLike;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let currentForm = element.form;
|
/**
|
||||||
if (!currentForm) {
|
* Enum for form autofill MANUALLY_MANAGED_STATES values
|
||||||
currentForm = getFormLike();
|
*/
|
||||||
}
|
this._FIELD_STATE_ENUM = {
|
||||||
|
// not themed
|
||||||
|
NORMAL: null,
|
||||||
|
// highlighted
|
||||||
|
AUTO_FILLED: "-moz-autofill",
|
||||||
|
// highlighted && grey color text
|
||||||
|
PREVIEW: "-moz-autofill-preview",
|
||||||
|
};
|
||||||
|
|
||||||
if (currentForm.elements.length != this.form.elements.length) {
|
this.winUtils = winUtils;
|
||||||
log.debug("The count of form elements is changed.");
|
|
||||||
this._updateForm(getFormLike());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.form.elements.indexOf(element) === -1) {
|
this.address.fieldDetails = fieldDetails.filter(
|
||||||
log.debug("The element can not be found in the current form.");
|
|
||||||
this._updateForm(getFormLike());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the form with a new FormLike, and the related fields should be
|
|
||||||
* updated or clear to ensure the data consistency.
|
|
||||||
* @param {FormLike} form a new FormLike to replace the original one.
|
|
||||||
*/
|
|
||||||
_updateForm(form) {
|
|
||||||
this.form = form;
|
|
||||||
this.fieldDetails = [];
|
|
||||||
|
|
||||||
if (this.address) {
|
|
||||||
this.address.fieldDetails = [];
|
|
||||||
}
|
|
||||||
if (this.creditCard) {
|
|
||||||
this.creditCard.fieldDetails = [];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set fieldDetails from the form about fields that can be autofilled.
|
|
||||||
*
|
|
||||||
* @param {boolean} allowDuplicates
|
|
||||||
* true to remain any duplicated field details otherwise to remove the
|
|
||||||
* duplicated ones.
|
|
||||||
* @returns {Array} The valid address and credit card details.
|
|
||||||
*/
|
|
||||||
collectFormFields(allowDuplicates = false) {
|
|
||||||
this._cacheValue.allFieldNames = null;
|
|
||||||
let fieldDetails = FormAutofillHeuristics.getFormInfo(this.form, allowDuplicates);
|
|
||||||
this.fieldDetails = fieldDetails ? fieldDetails : [];
|
|
||||||
log.debug("Collected details on", this.fieldDetails.length, "fields");
|
|
||||||
|
|
||||||
this.address.fieldDetails = this.fieldDetails.filter(
|
|
||||||
detail => FormAutofillUtils.isAddressField(detail.fieldName)
|
detail => FormAutofillUtils.isAddressField(detail.fieldName)
|
||||||
);
|
);
|
||||||
this.creditCard.fieldDetails = this.fieldDetails.filter(
|
|
||||||
detail => FormAutofillUtils.isCreditCardField(detail.fieldName)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (this.address.fieldDetails.length < FormAutofillUtils.AUTOFILL_FIELDS_THRESHOLD) {
|
if (this.address.fieldDetails.length < FormAutofillUtils.AUTOFILL_FIELDS_THRESHOLD) {
|
||||||
log.debug("Ignoring address related fields since it has only",
|
log.debug("Ignoring address related fields since the section has only",
|
||||||
this.address.fieldDetails.length,
|
this.address.fieldDetails.length,
|
||||||
"field(s)");
|
"field(s)");
|
||||||
this.address.fieldDetails = [];
|
this.address.fieldDetails = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.creditCard.fieldDetails = fieldDetails.filter(
|
||||||
|
detail => FormAutofillUtils.isCreditCardField(detail.fieldName)
|
||||||
|
);
|
||||||
if (!this._isValidCreditCardForm(this.creditCard.fieldDetails)) {
|
if (!this._isValidCreditCardForm(this.creditCard.fieldDetails)) {
|
||||||
log.debug("Invalid credit card form");
|
log.debug("Invalid credit card section.");
|
||||||
this.creditCard.fieldDetails = [];
|
this.creditCard.fieldDetails = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
let validDetails = Array.of(...(this.address.fieldDetails),
|
this._cacheValue = {
|
||||||
...(this.creditCard.fieldDetails));
|
allFieldNames: null,
|
||||||
for (let detail of validDetails) {
|
oneLineStreetAddress: null,
|
||||||
let input = detail.elementWeakRef.get();
|
matchingSelectOption: null,
|
||||||
if (!input) {
|
};
|
||||||
continue;
|
|
||||||
}
|
|
||||||
input.addEventListener("input", this);
|
|
||||||
}
|
|
||||||
|
|
||||||
return validDetails;
|
this._validDetails = Array.of(...(this.address.fieldDetails),
|
||||||
},
|
...(this.creditCard.fieldDetails));
|
||||||
|
log.debug(this._validDetails.length, "valid fields in the section is collected.");
|
||||||
|
}
|
||||||
|
|
||||||
|
get validDetails() {
|
||||||
|
return this._validDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
getFieldDetailByElement(element) {
|
||||||
|
return this._validDetails.find(
|
||||||
|
detail => detail.elementWeakRef.get() == element
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
_isValidCreditCardForm(fieldDetails) {
|
_isValidCreditCardForm(fieldDetails) {
|
||||||
let ccNumberReason = "";
|
let ccNumberReason = "";
|
||||||
|
@ -242,17 +121,18 @@ FormAutofillHandler.prototype = {
|
||||||
}
|
}
|
||||||
|
|
||||||
return hasCCNumber && (ccNumberReason == "autocomplete" || hasExpiryDate);
|
return hasCCNumber && (ccNumberReason == "autocomplete" || hasExpiryDate);
|
||||||
},
|
}
|
||||||
|
|
||||||
|
get allFieldNames() {
|
||||||
|
if (!this._cacheValue.allFieldNames) {
|
||||||
|
this._cacheValue.allFieldNames = this._validDetails.map(record => record.fieldName);
|
||||||
|
}
|
||||||
|
return this._cacheValue.allFieldNames;
|
||||||
|
}
|
||||||
|
|
||||||
getFieldDetailByName(fieldName) {
|
getFieldDetailByName(fieldName) {
|
||||||
return this.fieldDetails.find(detail => detail.fieldName == fieldName);
|
return this._validDetails.find(detail => detail.fieldName == fieldName);
|
||||||
},
|
}
|
||||||
|
|
||||||
getFieldDetailByElement(element) {
|
|
||||||
return this.fieldDetails.find(
|
|
||||||
detail => detail.elementWeakRef.get() == element
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
getFieldDetailsByElement(element) {
|
getFieldDetailsByElement(element) {
|
||||||
let fieldDetail = this.getFieldDetailByElement(element);
|
let fieldDetail = this.getFieldDetailByElement(element);
|
||||||
|
@ -266,14 +146,7 @@ FormAutofillHandler.prototype = {
|
||||||
return this.creditCard.fieldDetails;
|
return this.creditCard.fieldDetails;
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
},
|
}
|
||||||
|
|
||||||
get allFieldNames() {
|
|
||||||
if (!this._cacheValue.allFieldNames) {
|
|
||||||
this._cacheValue.allFieldNames = this.fieldDetails.map(record => record.fieldName);
|
|
||||||
}
|
|
||||||
return this._cacheValue.allFieldNames;
|
|
||||||
},
|
|
||||||
|
|
||||||
_getOneLineStreetAddress(address) {
|
_getOneLineStreetAddress(address) {
|
||||||
if (!this._cacheValue.oneLineStreetAddress) {
|
if (!this._cacheValue.oneLineStreetAddress) {
|
||||||
|
@ -283,7 +156,7 @@ FormAutofillHandler.prototype = {
|
||||||
this._cacheValue.oneLineStreetAddress[address] = FormAutofillUtils.toOneLineAddress(address);
|
this._cacheValue.oneLineStreetAddress[address] = FormAutofillUtils.toOneLineAddress(address);
|
||||||
}
|
}
|
||||||
return this._cacheValue.oneLineStreetAddress[address];
|
return this._cacheValue.oneLineStreetAddress[address];
|
||||||
},
|
}
|
||||||
|
|
||||||
_addressTransformer(profile) {
|
_addressTransformer(profile) {
|
||||||
if (profile["street-address"]) {
|
if (profile["street-address"]) {
|
||||||
|
@ -307,7 +180,7 @@ FormAutofillHandler.prototype = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replace tel with tel-national if tel violates the input element's
|
* Replace tel with tel-national if tel violates the input element's
|
||||||
|
@ -361,7 +234,7 @@ FormAutofillHandler.prototype = {
|
||||||
profile.tel = profile["tel-national"];
|
profile.tel = profile["tel-national"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
_matchSelectOptions(profile) {
|
_matchSelectOptions(profile) {
|
||||||
if (!this._cacheValue.matchingSelectOption) {
|
if (!this._cacheValue.matchingSelectOption) {
|
||||||
|
@ -399,7 +272,7 @@ FormAutofillHandler.prototype = {
|
||||||
delete profile[fieldName];
|
delete profile[fieldName];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
_creditCardExpDateTransformer(profile) {
|
_creditCardExpDateTransformer(profile) {
|
||||||
if (!profile["cc-exp"]) {
|
if (!profile["cc-exp"]) {
|
||||||
|
@ -435,7 +308,7 @@ FormAutofillHandler.prototype = {
|
||||||
result[2] +
|
result[2] +
|
||||||
String(ccExpMonth).padStart(result[3].length, "0");
|
String(ccExpMonth).padStart(result[3].length, "0");
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
getAdaptedProfiles(originalProfiles) {
|
getAdaptedProfiles(originalProfiles) {
|
||||||
for (let profile of originalProfiles) {
|
for (let profile of originalProfiles) {
|
||||||
|
@ -445,7 +318,7 @@ FormAutofillHandler.prototype = {
|
||||||
this._creditCardExpDateTransformer(profile);
|
this._creditCardExpDateTransformer(profile);
|
||||||
}
|
}
|
||||||
return originalProfiles;
|
return originalProfiles;
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes form fields that can be autofilled, and populates them with the
|
* Processes form fields that can be autofilled, and populates them with the
|
||||||
|
@ -457,7 +330,7 @@ FormAutofillHandler.prototype = {
|
||||||
* A focused input element needed to determine the address or credit
|
* A focused input element needed to determine the address or credit
|
||||||
* card field.
|
* card field.
|
||||||
*/
|
*/
|
||||||
async autofillFormFields(profile, focusedInput) {
|
async autofillFields(profile, focusedInput) {
|
||||||
let focusedDetail = this.getFieldDetailByElement(focusedInput);
|
let focusedDetail = this.getFieldDetailByElement(focusedInput);
|
||||||
if (!focusedDetail) {
|
if (!focusedDetail) {
|
||||||
throw new Error("No fieldDetail for the focused input.");
|
throw new Error("No fieldDetail for the focused input.");
|
||||||
|
@ -485,7 +358,7 @@ FormAutofillHandler.prototype = {
|
||||||
throw new Error("Unknown form fields");
|
throw new Error("Unknown form fields");
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug("profile in autofillFormFields:", profile);
|
log.debug("profile in autofillFields:", profile);
|
||||||
|
|
||||||
targetSet.filledRecordGUID = profile.guid;
|
targetSet.filledRecordGUID = profile.guid;
|
||||||
for (let fieldDetail of targetSet.fieldDetails) {
|
for (let fieldDetail of targetSet.fieldDetails) {
|
||||||
|
@ -532,41 +405,7 @@ FormAutofillHandler.prototype = {
|
||||||
this.changeFieldState(fieldDetail, "AUTO_FILLED");
|
this.changeFieldState(fieldDetail, "AUTO_FILLED");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Handle the highlight style resetting caused by user's correction afterward.
|
|
||||||
log.debug("register change handler for filled form:", this.form);
|
|
||||||
const onChangeHandler = e => {
|
|
||||||
let hasFilledFields;
|
|
||||||
|
|
||||||
if (!e.isTrusted) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let fieldDetail of targetSet.fieldDetails) {
|
|
||||||
let element = fieldDetail.elementWeakRef.get();
|
|
||||||
|
|
||||||
if (!element) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.target == element || (e.target == element.form && e.type == "reset")) {
|
|
||||||
this.changeFieldState(fieldDetail, "NORMAL");
|
|
||||||
}
|
|
||||||
|
|
||||||
hasFilledFields |= (fieldDetail.state == "AUTO_FILLED");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unregister listeners and clear guid once no field is in AUTO_FILLED state.
|
|
||||||
if (!hasFilledFields) {
|
|
||||||
this.form.rootElement.removeEventListener("input", onChangeHandler);
|
|
||||||
this.form.rootElement.removeEventListener("reset", onChangeHandler);
|
|
||||||
targetSet.filledRecordGUID = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.form.rootElement.addEventListener("input", onChangeHandler);
|
|
||||||
this.form.rootElement.addEventListener("reset", onChangeHandler);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Populates result to the preview layers with given profile.
|
* Populates result to the preview layers with given profile.
|
||||||
|
@ -577,7 +416,7 @@ FormAutofillHandler.prototype = {
|
||||||
* A focused input element for determining credit card or address fields.
|
* A focused input element for determining credit card or address fields.
|
||||||
*/
|
*/
|
||||||
previewFormFields(profile, focusedInput) {
|
previewFormFields(profile, focusedInput) {
|
||||||
log.debug("preview profile in autofillFormFields:", profile);
|
log.debug("preview profile: ", profile);
|
||||||
|
|
||||||
// Always show the decrypted credit card number when Master Password is
|
// Always show the decrypted credit card number when Master Password is
|
||||||
// disabled.
|
// disabled.
|
||||||
|
@ -614,7 +453,7 @@ FormAutofillHandler.prototype = {
|
||||||
element.previewValue = value;
|
element.previewValue = value;
|
||||||
this.changeFieldState(fieldDetail, value ? "PREVIEW" : "NORMAL");
|
this.changeFieldState(fieldDetail, value ? "PREVIEW" : "NORMAL");
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear preview text and background highlight of all fields.
|
* Clear preview text and background highlight of all fields.
|
||||||
|
@ -643,7 +482,7 @@ FormAutofillHandler.prototype = {
|
||||||
|
|
||||||
this.changeFieldState(fieldDetail, "NORMAL");
|
this.changeFieldState(fieldDetail, "NORMAL");
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Change the state of a field to correspond with different presentations.
|
* Change the state of a field to correspond with different presentations.
|
||||||
|
@ -660,12 +499,12 @@ FormAutofillHandler.prototype = {
|
||||||
log.warn(fieldDetail.fieldName, "is unreachable while changing state");
|
log.warn(fieldDetail.fieldName, "is unreachable while changing state");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!(nextState in this.fieldStateEnum)) {
|
if (!(nextState in this._FIELD_STATE_ENUM)) {
|
||||||
log.warn(fieldDetail.fieldName, "is trying to change to an invalid state");
|
log.warn(fieldDetail.fieldName, "is trying to change to an invalid state");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let [state, mmStateValue] of Object.entries(this.fieldStateEnum)) {
|
for (let [state, mmStateValue] of Object.entries(this._FIELD_STATE_ENUM)) {
|
||||||
// The NORMAL state is simply the absence of other manually
|
// The NORMAL state is simply the absence of other manually
|
||||||
// managed states so we never need to add or remove it.
|
// managed states so we never need to add or remove it.
|
||||||
if (!mmStateValue) {
|
if (!mmStateValue) {
|
||||||
|
@ -680,7 +519,34 @@ FormAutofillHandler.prototype = {
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldDetail.state = nextState;
|
fieldDetail.state = nextState;
|
||||||
},
|
}
|
||||||
|
|
||||||
|
clearFieldState(focusedInput) {
|
||||||
|
let fieldDetail = this.getFieldDetailByElement(focusedInput);
|
||||||
|
this.changeFieldState(fieldDetail, "NORMAL");
|
||||||
|
let targetSet;
|
||||||
|
if (FormAutofillUtils.isAddressField(focusedInput)) {
|
||||||
|
targetSet = this.address;
|
||||||
|
} else if (FormAutofillUtils.isCreditCardField(focusedInput)) {
|
||||||
|
targetSet = this.creditCard;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!targetSet.fieldDetails.some(detail => detail.state == "AUTO_FILLED")) {
|
||||||
|
targetSet.filledRecordGUID = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resetFieldStates() {
|
||||||
|
for (let fieldDetail of this._validDetails) {
|
||||||
|
this.changeFieldState(fieldDetail, "NORMAL");
|
||||||
|
}
|
||||||
|
this.address.filledRecordGUID = null;
|
||||||
|
this.creditCard.filledRecordGUID = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
isFilled() {
|
||||||
|
return !!(this.address.filledRecordGUID || this.creditCard.filledRecordGUID);
|
||||||
|
}
|
||||||
|
|
||||||
_isAddressRecordCreatable(record) {
|
_isAddressRecordCreatable(record) {
|
||||||
let hasName = 0;
|
let hasName = 0;
|
||||||
|
@ -696,11 +562,11 @@ FormAutofillHandler.prototype = {
|
||||||
length++;
|
length++;
|
||||||
}
|
}
|
||||||
return (length + hasName) >= FormAutofillUtils.AUTOFILL_FIELDS_THRESHOLD;
|
return (length + hasName) >= FormAutofillUtils.AUTOFILL_FIELDS_THRESHOLD;
|
||||||
},
|
}
|
||||||
|
|
||||||
_isCreditCardRecordCreatable(record) {
|
_isCreditCardRecordCreatable(record) {
|
||||||
return record["cc-number"] && FormAutofillUtils.isCCNumber(record["cc-number"]);
|
return record["cc-number"] && FormAutofillUtils.isCCNumber(record["cc-number"]);
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the records that is converted from address/creditCard fieldDetails and
|
* Return the records that is converted from address/creditCard fieldDetails and
|
||||||
|
@ -794,7 +660,7 @@ FormAutofillHandler.prototype = {
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
},
|
}
|
||||||
|
|
||||||
_normalizeAddress(address) {
|
_normalizeAddress(address) {
|
||||||
if (!address) {
|
if (!address) {
|
||||||
|
@ -837,7 +703,7 @@ FormAutofillHandler.prototype = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
async _decrypt(cipherText, reauth) {
|
async _decrypt(cipherText, reauth) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
|
@ -848,7 +714,225 @@ FormAutofillHandler.prototype = {
|
||||||
|
|
||||||
Services.cpmm.sendAsyncMessage("FormAutofill:GetDecryptedString", {cipherText, reauth});
|
Services.cpmm.sendAsyncMessage("FormAutofill:GetDecryptedString", {cipherText, reauth});
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles profile autofill for a DOM Form element.
|
||||||
|
*/
|
||||||
|
class FormAutofillHandler {
|
||||||
|
/**
|
||||||
|
* Initialize the form from `FormLike` object to handle the section or form
|
||||||
|
* operations.
|
||||||
|
* @param {FormLike} form Form that need to be auto filled
|
||||||
|
*/
|
||||||
|
constructor(form) {
|
||||||
|
/**
|
||||||
|
* DOM Form element to which this object is attached.
|
||||||
|
*/
|
||||||
|
this.form = null;
|
||||||
|
|
||||||
|
this._updateForm(form);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A WindowUtils reference of which Window the form belongs
|
||||||
|
*/
|
||||||
|
this.winUtils = this.form.rootElement.ownerGlobal.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||||
|
.getInterface(Ci.nsIDOMWindowUtils);
|
||||||
|
|
||||||
|
this.sections = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array of collected data about relevant form fields. Each item is an object
|
||||||
|
* storing the identifying details of the field and a reference to the
|
||||||
|
* originally associated element from the form.
|
||||||
|
*
|
||||||
|
* The "section", "addressType", "contactType", and "fieldName" values are
|
||||||
|
* used to identify the exact field when the serializable data is received
|
||||||
|
* from the backend. There cannot be multiple fields which have
|
||||||
|
* the same exact combination of these values.
|
||||||
|
*
|
||||||
|
* A direct reference to the associated element cannot be sent to the user
|
||||||
|
* interface because processing may be done in the parent process.
|
||||||
|
*/
|
||||||
|
this.fieldDetails = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Time in milliseconds since epoch when a user started filling in the form.
|
||||||
|
*/
|
||||||
|
this.timeStartedFillingMS = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the form is necessary to be updated. This function should be able to
|
||||||
|
* detect any changes including all control elements in the form.
|
||||||
|
* @param {HTMLElement} element The element supposed to be in the form.
|
||||||
|
* @returns {boolean} FormAutofillHandler.form is updated or not.
|
||||||
|
*/
|
||||||
|
updateFormIfNeeded(element) {
|
||||||
|
// When the following condition happens, FormAutofillHandler.form should be
|
||||||
|
// updated:
|
||||||
|
// * The count of form controls is changed.
|
||||||
|
// * When the element can not be found in the current form.
|
||||||
|
//
|
||||||
|
// However, we should improve the function to detect the element changes.
|
||||||
|
// e.g. a tel field is changed from type="hidden" to type="tel".
|
||||||
|
|
||||||
|
let _formLike;
|
||||||
|
let getFormLike = () => {
|
||||||
|
if (!_formLike) {
|
||||||
|
_formLike = FormLikeFactory.createFromField(element);
|
||||||
|
}
|
||||||
|
return _formLike;
|
||||||
|
};
|
||||||
|
|
||||||
|
let currentForm = element.form;
|
||||||
|
if (!currentForm) {
|
||||||
|
currentForm = getFormLike();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentForm.elements.length != this.form.elements.length) {
|
||||||
|
log.debug("The count of form elements is changed.");
|
||||||
|
this._updateForm(getFormLike());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.form.elements.indexOf(element) === -1) {
|
||||||
|
log.debug("The element can not be found in the current form.");
|
||||||
|
this._updateForm(getFormLike());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the form with a new FormLike, and the related fields should be
|
||||||
|
* updated or clear to ensure the data consistency.
|
||||||
|
* @param {FormLike} form a new FormLike to replace the original one.
|
||||||
|
*/
|
||||||
|
_updateForm(form) {
|
||||||
|
this.form = form;
|
||||||
|
this.fieldDetails = [];
|
||||||
|
this.sections = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set fieldDetails from the form about fields that can be autofilled.
|
||||||
|
*
|
||||||
|
* @param {boolean} allowDuplicates
|
||||||
|
* true to remain any duplicated field details otherwise to remove the
|
||||||
|
* duplicated ones.
|
||||||
|
* @returns {Array} The valid address and credit card details.
|
||||||
|
*/
|
||||||
|
collectFormFields(allowDuplicates = false) {
|
||||||
|
let sections = FormAutofillHeuristics.getFormInfo(this.form, allowDuplicates);
|
||||||
|
let allValidDetails = [];
|
||||||
|
for (let fieldDetails of sections) {
|
||||||
|
let section = new FormAutofillSection(fieldDetails, this.winUtils);
|
||||||
|
this.sections.push(section);
|
||||||
|
allValidDetails.push(...section.validDetails);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let detail of allValidDetails) {
|
||||||
|
let input = detail.elementWeakRef.get();
|
||||||
|
if (!input) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
input.addEventListener("input", this);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fieldDetails = allValidDetails;
|
||||||
|
return allValidDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
getFieldDetailByElement(element) {
|
||||||
|
return this.fieldDetails.find(
|
||||||
|
detail => detail.elementWeakRef.get() == element
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getSectionByElement(element) {
|
||||||
|
return this.sections.find(
|
||||||
|
section => section.getFieldDetailByElement(element)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getFieldDetailsByElement(element) {
|
||||||
|
let fieldDetail = this.getFieldDetailByElement(element);
|
||||||
|
if (!fieldDetail) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return this.getSectionByElement(element).getFieldDetailsByElement(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
getAllFieldNames(focusedInput) {
|
||||||
|
let section = this.getSectionByElement(focusedInput);
|
||||||
|
return section.allFieldNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
previewFormFields(profile, focusedInput) {
|
||||||
|
let section = this.getSectionByElement(focusedInput);
|
||||||
|
section.previewFormFields(profile, focusedInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearPreviewedFormFields(focusedInput) {
|
||||||
|
let section = this.getSectionByElement(focusedInput);
|
||||||
|
section.clearPreviewedFormFields(focusedInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
getAdaptedProfiles(originalProfiles, focusedInput) {
|
||||||
|
let section = this.getSectionByElement(focusedInput);
|
||||||
|
section.getAdaptedProfiles(originalProfiles);
|
||||||
|
return originalProfiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasFilledSection() {
|
||||||
|
return this.sections.some(section => section.isFilled());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes form fields that can be autofilled, and populates them with the
|
||||||
|
* profile provided by backend.
|
||||||
|
*
|
||||||
|
* @param {Object} profile
|
||||||
|
* A profile to be filled in.
|
||||||
|
* @param {HTMLElement} focusedInput
|
||||||
|
* A focused input element needed to determine the address or credit
|
||||||
|
* card field.
|
||||||
|
*/
|
||||||
|
async autofillFormFields(profile, focusedInput) {
|
||||||
|
let noFilledSections = !this.hasFilledSection();
|
||||||
|
await this.getSectionByElement(focusedInput).autofillFields(profile, focusedInput);
|
||||||
|
|
||||||
|
// Handle the highlight style resetting caused by user's correction afterward.
|
||||||
|
log.debug("register change handler for filled form:", this.form);
|
||||||
|
const onChangeHandler = e => {
|
||||||
|
if (!e.isTrusted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.type == "input") {
|
||||||
|
let section = this.getSectionByElement(e.target);
|
||||||
|
section.clearFieldState(e.target);
|
||||||
|
} else if (e.type == "reset") {
|
||||||
|
for (let section of this.sections) {
|
||||||
|
section.resetFieldStates();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unregister listeners once no field is in AUTO_FILLED state.
|
||||||
|
if (!this.hasFilledSection()) {
|
||||||
|
this.form.rootElement.removeEventListener("input", onChangeHandler);
|
||||||
|
this.form.rootElement.removeEventListener("reset", onChangeHandler);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (noFilledSections) {
|
||||||
|
this.form.rootElement.addEventListener("input", onChangeHandler);
|
||||||
|
this.form.rootElement.addEventListener("reset", onChangeHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleEvent(event) {
|
handleEvent(event) {
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
|
@ -867,5 +951,15 @@ FormAutofillHandler.prototype = {
|
||||||
this.timeStartedFillingMS = Date.now();
|
this.timeStartedFillingMS = Date.now();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
};
|
|
||||||
|
createRecords() {
|
||||||
|
// TODO [Bug 1415073] `FormAutofillHandler.createRecords` should traverse
|
||||||
|
// all sections and aggregate the records into one result.
|
||||||
|
if (this.sections.length > 0) {
|
||||||
|
return this.sections[0].createRecords();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ this.log = null;
|
||||||
FormAutofillUtils.defineLazyLogGetter(this, this.EXPORTED_SYMBOLS[0]);
|
FormAutofillUtils.defineLazyLogGetter(this, this.EXPORTED_SYMBOLS[0]);
|
||||||
|
|
||||||
const PREF_HEURISTICS_ENABLED = "extensions.formautofill.heuristics.enabled";
|
const PREF_HEURISTICS_ENABLED = "extensions.formautofill.heuristics.enabled";
|
||||||
|
const PREF_SECTION_ENABLED = "extensions.formautofill.section.enabled";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A scanner for traversing all elements in a form and retrieving the field
|
* A scanner for traversing all elements in a form and retrieving the field
|
||||||
|
@ -544,8 +545,9 @@ this.FormAutofillHeuristics = {
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function should provide all field details of a form. The details
|
* This function should provide all field details of a form which are placed
|
||||||
* contain the autocomplete info (e.g. fieldName, section, etc).
|
* in the belonging section. The details contain the autocomplete info
|
||||||
|
* (e.g. fieldName, section, etc).
|
||||||
*
|
*
|
||||||
* `allowDuplicates` is used for the xpcshell-test purpose currently because
|
* `allowDuplicates` is used for the xpcshell-test purpose currently because
|
||||||
* the heuristics should be verified that some duplicated elements still can
|
* the heuristics should be verified that some duplicated elements still can
|
||||||
|
@ -556,8 +558,8 @@ this.FormAutofillHeuristics = {
|
||||||
* @param {boolean} allowDuplicates
|
* @param {boolean} allowDuplicates
|
||||||
* true to remain any duplicated field details otherwise to remove the
|
* true to remain any duplicated field details otherwise to remove the
|
||||||
* duplicated ones.
|
* duplicated ones.
|
||||||
* @returns {Array<Object>}
|
* @returns {Array<Array<Object>>}
|
||||||
* all field details in the form.
|
* all sections within its field details in the form.
|
||||||
*/
|
*/
|
||||||
getFormInfo(form, allowDuplicates = false) {
|
getFormInfo(form, allowDuplicates = false) {
|
||||||
const eligibleFields = Array.from(form.elements)
|
const eligibleFields = Array.from(form.elements)
|
||||||
|
@ -582,11 +584,19 @@ this.FormAutofillHeuristics = {
|
||||||
|
|
||||||
LabelUtils.clearLabelMap();
|
LabelUtils.clearLabelMap();
|
||||||
|
|
||||||
if (allowDuplicates) {
|
if (!this._sectionEnabled) {
|
||||||
return fieldScanner.fieldDetails;
|
// When the section feature is disabled, `getFormInfo` should provide a
|
||||||
|
// single section result.
|
||||||
|
return [allowDuplicates ? fieldScanner.fieldDetails : fieldScanner.trimmedFieldDetail];
|
||||||
}
|
}
|
||||||
|
|
||||||
return fieldScanner.trimmedFieldDetail;
|
return this._groupingFields(fieldScanner, allowDuplicates);
|
||||||
|
},
|
||||||
|
|
||||||
|
_groupingFields(fieldScanner, allowDuplicates) {
|
||||||
|
// TODO [Bug 1415077] This function should be able to handle the section
|
||||||
|
// part of autocomplete attr.
|
||||||
|
return [allowDuplicates ? fieldScanner.fieldDetails : fieldScanner.trimmedFieldDetail];
|
||||||
},
|
},
|
||||||
|
|
||||||
_regExpTableHashValue(...signBits) {
|
_regExpTableHashValue(...signBits) {
|
||||||
|
@ -895,3 +905,11 @@ Services.prefs.addObserver(PREF_HEURISTICS_ENABLED, () => {
|
||||||
this.FormAutofillHeuristics._prefEnabled = Services.prefs.getBoolPref(PREF_HEURISTICS_ENABLED);
|
this.FormAutofillHeuristics._prefEnabled = Services.prefs.getBoolPref(PREF_HEURISTICS_ENABLED);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyGetter(this.FormAutofillHeuristics, "_sectionEnabled", () => {
|
||||||
|
return Services.prefs.getBoolPref(PREF_SECTION_ENABLED);
|
||||||
|
});
|
||||||
|
|
||||||
|
Services.prefs.addObserver(PREF_SECTION_ENABLED, () => {
|
||||||
|
this.FormAutofillHeuristics._sectionEnabled = Services.prefs.getBoolPref(PREF_SECTION_ENABLED);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
|
@ -100,7 +100,13 @@ function runHeuristicsTest(patterns, fixturePathPrefix) {
|
||||||
Assert.equal(forms.length, testPattern.expectedResult.length, "Expected form count.");
|
Assert.equal(forms.length, testPattern.expectedResult.length, "Expected form count.");
|
||||||
|
|
||||||
forms.forEach((form, formIndex) => {
|
forms.forEach((form, formIndex) => {
|
||||||
let formInfo = FormAutofillHeuristics.getFormInfo(form);
|
let sections = FormAutofillHeuristics.getFormInfo(form);
|
||||||
|
if (testPattern.expectedResult[formIndex].length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// TODO [Bug 1415077] the test should be able to support traversing all
|
||||||
|
// sections.
|
||||||
|
let formInfo = sections[0];
|
||||||
do_print("FieldName Prediction Results: " + formInfo.map(i => i.fieldName));
|
do_print("FieldName Prediction Results: " + formInfo.map(i => i.fieldName));
|
||||||
do_print("FieldName Expected Results: " + testPattern.expectedResult[formIndex].map(i => i.fieldName));
|
do_print("FieldName Expected Results: " + testPattern.expectedResult[formIndex].map(i => i.fieldName));
|
||||||
Assert.equal(formInfo.length, testPattern.expectedResult[formIndex].length, "Expected field count.");
|
Assert.equal(formInfo.length, testPattern.expectedResult[formIndex].length, "Expected field count.");
|
||||||
|
@ -167,6 +173,7 @@ add_task(async function head_initialize() {
|
||||||
Services.prefs.setStringPref("extensions.formautofill.available", "on");
|
Services.prefs.setStringPref("extensions.formautofill.available", "on");
|
||||||
Services.prefs.setBoolPref("extensions.formautofill.creditCards.available", true);
|
Services.prefs.setBoolPref("extensions.formautofill.creditCards.available", true);
|
||||||
Services.prefs.setBoolPref("extensions.formautofill.heuristics.enabled", true);
|
Services.prefs.setBoolPref("extensions.formautofill.heuristics.enabled", true);
|
||||||
|
Services.prefs.setBoolPref("extensions.formautofill.section.enabled", false);
|
||||||
Services.prefs.setBoolPref("dom.forms.autocomplete.formautofill", true);
|
Services.prefs.setBoolPref("dom.forms.autocomplete.formautofill", true);
|
||||||
|
|
||||||
// Clean up after every test.
|
// Clean up after every test.
|
||||||
|
@ -174,6 +181,7 @@ add_task(async function head_initialize() {
|
||||||
Services.prefs.clearUserPref("extensions.formautofill.available");
|
Services.prefs.clearUserPref("extensions.formautofill.available");
|
||||||
Services.prefs.clearUserPref("extensions.formautofill.creditCards.available");
|
Services.prefs.clearUserPref("extensions.formautofill.creditCards.available");
|
||||||
Services.prefs.clearUserPref("extensions.formautofill.heuristics.enabled");
|
Services.prefs.clearUserPref("extensions.formautofill.heuristics.enabled");
|
||||||
|
Services.prefs.clearUserPref("extensions.formautofill.section.enabled");
|
||||||
Services.prefs.clearUserPref("dom.forms.autocomplete.formautofill");
|
Services.prefs.clearUserPref("dom.forms.autocomplete.formautofill");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -504,7 +504,7 @@ function do_test(testcases, testFn) {
|
||||||
let handler = new FormAutofillHandler(formLike);
|
let handler = new FormAutofillHandler(formLike);
|
||||||
let promises = [];
|
let promises = [];
|
||||||
// Replace the interal decrypt method with MasterPassword API
|
// Replace the interal decrypt method with MasterPassword API
|
||||||
handler._decrypt = async (cipherText, reauth) => {
|
let decryptHelper = async (cipherText, reauth) => {
|
||||||
let string;
|
let string;
|
||||||
try {
|
try {
|
||||||
string = await MasterPassword.decrypt(cipherText, reauth);
|
string = await MasterPassword.decrypt(cipherText, reauth);
|
||||||
|
@ -518,7 +518,14 @@ function do_test(testcases, testFn) {
|
||||||
};
|
};
|
||||||
|
|
||||||
handler.collectFormFields();
|
handler.collectFormFields();
|
||||||
let handlerInfo = handler[testcase.expectedFillingForm];
|
for (let section of handler.sections) {
|
||||||
|
section._decrypt = decryptHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO [Bug 1415077] We can assume all test cases with only one section
|
||||||
|
// should be filled. Eventually, the test needs to verify the filling
|
||||||
|
// feature in a multiple section case.
|
||||||
|
let handlerInfo = handler.sections[0][testcase.expectedFillingForm];
|
||||||
handlerInfo.fieldDetails.forEach(field => {
|
handlerInfo.fieldDetails.forEach(field => {
|
||||||
let element = field.elementWeakRef.get();
|
let element = field.elementWeakRef.get();
|
||||||
if (!testcase.profileData[field.fieldName]) {
|
if (!testcase.profileData[field.fieldName]) {
|
||||||
|
@ -529,9 +536,9 @@ function do_test(testcases, testFn) {
|
||||||
promises.push(...testFn(testcase, element));
|
promises.push(...testFn(testcase, element));
|
||||||
});
|
});
|
||||||
|
|
||||||
let [adaptedProfile] = handler.getAdaptedProfiles([testcase.profileData]);
|
let focusedInput = doc.getElementById(testcase.focusedInputId);
|
||||||
let focuedInput = doc.getElementById(testcase.focusedInputId);
|
let [adaptedProfile] = handler.getAdaptedProfiles([testcase.profileData], focusedInput);
|
||||||
await handler.autofillFormFields(adaptedProfile, focuedInput);
|
await handler.autofillFormFields(adaptedProfile, focusedInput);
|
||||||
Assert.equal(handlerInfo.filledRecordGUID, testcase.profileData.guid,
|
Assert.equal(handlerInfo.filledRecordGUID, testcase.profileData.guid,
|
||||||
"Check if filledRecordGUID is set correctly");
|
"Check if filledRecordGUID is set correctly");
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
|
|
|
@ -440,8 +440,11 @@ for (let tc of TESTCASES) {
|
||||||
let handler = new FormAutofillHandler(formLike);
|
let handler = new FormAutofillHandler(formLike);
|
||||||
let validFieldDetails = handler.collectFormFields(testcase.allowDuplicates);
|
let validFieldDetails = handler.collectFormFields(testcase.allowDuplicates);
|
||||||
|
|
||||||
verifyDetails(handler.address.fieldDetails, testcase.addressFieldDetails);
|
// TODO [Bug 1415077] We can assume all test cases with only one section
|
||||||
verifyDetails(handler.creditCard.fieldDetails, testcase.creditCardFieldDetails);
|
// should be filled. Eventually, the test needs to verify the filling
|
||||||
|
// feature in a multiple section case.
|
||||||
|
verifyDetails(handler.sections[0].address.fieldDetails, testcase.addressFieldDetails);
|
||||||
|
verifyDetails(handler.sections[0].creditCard.fieldDetails, testcase.creditCardFieldDetails);
|
||||||
verifyDetails(validFieldDetails, testcase.validFieldDetails);
|
verifyDetails(validFieldDetails, testcase.validFieldDetails);
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -29,6 +29,8 @@ const TESTCASES = [
|
||||||
{
|
{
|
||||||
description: "Address form with street-address",
|
description: "Address form with street-address",
|
||||||
document: `<form>
|
document: `<form>
|
||||||
|
<input autocomplete="given-name">
|
||||||
|
<input autocomplete="family-name">
|
||||||
<input id="street-addr" autocomplete="street-address">
|
<input id="street-addr" autocomplete="street-address">
|
||||||
</form>`,
|
</form>`,
|
||||||
profileData: [Object.assign({}, DEFAULT_ADDRESS_RECORD)],
|
profileData: [Object.assign({}, DEFAULT_ADDRESS_RECORD)],
|
||||||
|
@ -70,6 +72,7 @@ const TESTCASES = [
|
||||||
{
|
{
|
||||||
description: "Address form with street-address, address-line1",
|
description: "Address form with street-address, address-line1",
|
||||||
document: `<form>
|
document: `<form>
|
||||||
|
<input autocomplete="given-name">
|
||||||
<input id="street-addr" autocomplete="street-address">
|
<input id="street-addr" autocomplete="street-address">
|
||||||
<input id="line1" autocomplete="address-line1">
|
<input id="line1" autocomplete="address-line1">
|
||||||
</form>`,
|
</form>`,
|
||||||
|
@ -132,6 +135,7 @@ const TESTCASES = [
|
||||||
{
|
{
|
||||||
description: "Address form with exact matching options in select",
|
description: "Address form with exact matching options in select",
|
||||||
document: `<form>
|
document: `<form>
|
||||||
|
<input autocomplete="given-name">
|
||||||
<select autocomplete="address-level1">
|
<select autocomplete="address-level1">
|
||||||
<option id="option-address-level1-XX" value="XX">Dummy</option>
|
<option id="option-address-level1-XX" value="XX">Dummy</option>
|
||||||
<option id="option-address-level1-CA" value="CA">California</option>
|
<option id="option-address-level1-CA" value="CA">California</option>
|
||||||
|
@ -162,6 +166,7 @@ const TESTCASES = [
|
||||||
{
|
{
|
||||||
description: "Address form with inexact matching options in select",
|
description: "Address form with inexact matching options in select",
|
||||||
document: `<form>
|
document: `<form>
|
||||||
|
<input autocomplete="given-name">
|
||||||
<select autocomplete="address-level1">
|
<select autocomplete="address-level1">
|
||||||
<option id="option-address-level1-XX" value="XX">Dummy</option>
|
<option id="option-address-level1-XX" value="XX">Dummy</option>
|
||||||
<option id="option-address-level1-OO" value="OO">California</option>
|
<option id="option-address-level1-OO" value="OO">California</option>
|
||||||
|
@ -192,6 +197,7 @@ const TESTCASES = [
|
||||||
{
|
{
|
||||||
description: "Address form with value-omitted options in select",
|
description: "Address form with value-omitted options in select",
|
||||||
document: `<form>
|
document: `<form>
|
||||||
|
<input autocomplete="given-name">
|
||||||
<select autocomplete="address-level1">
|
<select autocomplete="address-level1">
|
||||||
<option id="option-address-level1-1" value="">Dummy</option>
|
<option id="option-address-level1-1" value="">Dummy</option>
|
||||||
<option id="option-address-level1-2" value="">California</option>
|
<option id="option-address-level1-2" value="">California</option>
|
||||||
|
@ -222,6 +228,7 @@ const TESTCASES = [
|
||||||
{
|
{
|
||||||
description: "Address form with options with the same value in select ",
|
description: "Address form with options with the same value in select ",
|
||||||
document: `<form>
|
document: `<form>
|
||||||
|
<input autocomplete="given-name">
|
||||||
<select autocomplete="address-level1">
|
<select autocomplete="address-level1">
|
||||||
<option id="option-address-level1-same1" value="same">Dummy</option>
|
<option id="option-address-level1-same1" value="same">Dummy</option>
|
||||||
<option id="option-address-level1-same2" value="same">California</option>
|
<option id="option-address-level1-same2" value="same">California</option>
|
||||||
|
@ -252,6 +259,7 @@ const TESTCASES = [
|
||||||
{
|
{
|
||||||
description: "Address form without matching options in select for address-level1 and country",
|
description: "Address form without matching options in select for address-level1 and country",
|
||||||
document: `<form>
|
document: `<form>
|
||||||
|
<input autocomplete="given-name">
|
||||||
<select autocomplete="address-level1">
|
<select autocomplete="address-level1">
|
||||||
<option id="option-address-level1-dummy1" value="">Dummy</option>
|
<option id="option-address-level1-dummy1" value="">Dummy</option>
|
||||||
<option id="option-address-level1-dummy2" value="">Dummy 2</option>
|
<option id="option-address-level1-dummy2" value="">Dummy 2</option>
|
||||||
|
@ -465,6 +473,7 @@ const TESTCASES = [
|
||||||
{
|
{
|
||||||
description: "Credit Card form with matching options of cc-exp-year and cc-exp-month",
|
description: "Credit Card form with matching options of cc-exp-year and cc-exp-month",
|
||||||
document: `<form>
|
document: `<form>
|
||||||
|
<input autocomplete="cc-number">
|
||||||
<select autocomplete="cc-exp-month">
|
<select autocomplete="cc-exp-month">
|
||||||
<option id="option-cc-exp-month-01" value="1">01</option>
|
<option id="option-cc-exp-month-01" value="1">01</option>
|
||||||
<option id="option-cc-exp-month-02" value="2">02</option>
|
<option id="option-cc-exp-month-02" value="2">02</option>
|
||||||
|
@ -504,6 +513,7 @@ const TESTCASES = [
|
||||||
{
|
{
|
||||||
description: "Credit Card form with matching options which contain labels",
|
description: "Credit Card form with matching options which contain labels",
|
||||||
document: `<form>
|
document: `<form>
|
||||||
|
<input autocomplete="cc-number">
|
||||||
<select autocomplete="cc-exp-month">
|
<select autocomplete="cc-exp-month">
|
||||||
<option value="" selected="selected">Month</option>
|
<option value="" selected="selected">Month</option>
|
||||||
<option label="01 - January" id="option-cc-exp-month-01" value="object:17">dummy</option>
|
<option label="01 - January" id="option-cc-exp-month-01" value="object:17">dummy</option>
|
||||||
|
@ -551,7 +561,9 @@ const TESTCASES = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Compound cc-exp: {MON1}/{YEAR2}",
|
description: "Compound cc-exp: {MON1}/{YEAR2}",
|
||||||
document: `<form><select autocomplete="cc-exp">
|
document: `<form>
|
||||||
|
<input autocomplete="cc-number">
|
||||||
|
<select autocomplete="cc-exp">
|
||||||
<option value="3/17">3/17</option>
|
<option value="3/17">3/17</option>
|
||||||
<option value="1/25" id="selected-cc-exp">1/25</option>
|
<option value="1/25" id="selected-cc-exp">1/25</option>
|
||||||
</select></form>`,
|
</select></form>`,
|
||||||
|
@ -561,7 +573,9 @@ const TESTCASES = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Compound cc-exp: {MON1}/{YEAR4}",
|
description: "Compound cc-exp: {MON1}/{YEAR4}",
|
||||||
document: `<form><select autocomplete="cc-exp">
|
document: `<form>
|
||||||
|
<input autocomplete="cc-number">
|
||||||
|
<select autocomplete="cc-exp">
|
||||||
<option value="3/2017">3/2017</option>
|
<option value="3/2017">3/2017</option>
|
||||||
<option value="1/2025" id="selected-cc-exp">1/2025</option>
|
<option value="1/2025" id="selected-cc-exp">1/2025</option>
|
||||||
</select></form>`,
|
</select></form>`,
|
||||||
|
@ -571,7 +585,9 @@ const TESTCASES = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Compound cc-exp: {MON2}/{YEAR2}",
|
description: "Compound cc-exp: {MON2}/{YEAR2}",
|
||||||
document: `<form><select autocomplete="cc-exp">
|
document: `<form>
|
||||||
|
<input autocomplete="cc-number">
|
||||||
|
<select autocomplete="cc-exp">
|
||||||
<option value="03/17">03/17</option>
|
<option value="03/17">03/17</option>
|
||||||
<option value="01/25" id="selected-cc-exp">01/25</option>
|
<option value="01/25" id="selected-cc-exp">01/25</option>
|
||||||
</select></form>`,
|
</select></form>`,
|
||||||
|
@ -581,7 +597,9 @@ const TESTCASES = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Compound cc-exp: {MON2}/{YEAR4}",
|
description: "Compound cc-exp: {MON2}/{YEAR4}",
|
||||||
document: `<form><select autocomplete="cc-exp">
|
document: `<form>
|
||||||
|
<input autocomplete="cc-number">
|
||||||
|
<select autocomplete="cc-exp">
|
||||||
<option value="03/2017">03/2017</option>
|
<option value="03/2017">03/2017</option>
|
||||||
<option value="01/2025" id="selected-cc-exp">01/2025</option>
|
<option value="01/2025" id="selected-cc-exp">01/2025</option>
|
||||||
</select></form>`,
|
</select></form>`,
|
||||||
|
@ -591,7 +609,9 @@ const TESTCASES = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Compound cc-exp: {MON1}-{YEAR2}",
|
description: "Compound cc-exp: {MON1}-{YEAR2}",
|
||||||
document: `<form><select autocomplete="cc-exp">
|
document: `<form>
|
||||||
|
<input autocomplete="cc-number">
|
||||||
|
<select autocomplete="cc-exp">
|
||||||
<option value="3-17">3-17</option>
|
<option value="3-17">3-17</option>
|
||||||
<option value="1-25" id="selected-cc-exp">1-25</option>
|
<option value="1-25" id="selected-cc-exp">1-25</option>
|
||||||
</select></form>`,
|
</select></form>`,
|
||||||
|
@ -601,7 +621,9 @@ const TESTCASES = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Compound cc-exp: {MON1}-{YEAR4}",
|
description: "Compound cc-exp: {MON1}-{YEAR4}",
|
||||||
document: `<form><select autocomplete="cc-exp">
|
document: `<form>
|
||||||
|
<input autocomplete="cc-number">
|
||||||
|
<select autocomplete="cc-exp">
|
||||||
<option value="3-2017">3-2017</option>
|
<option value="3-2017">3-2017</option>
|
||||||
<option value="1-2025" id="selected-cc-exp">1-2025</option>
|
<option value="1-2025" id="selected-cc-exp">1-2025</option>
|
||||||
</select></form>`,
|
</select></form>`,
|
||||||
|
@ -611,7 +633,9 @@ const TESTCASES = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Compound cc-exp: {MON2}-{YEAR2}",
|
description: "Compound cc-exp: {MON2}-{YEAR2}",
|
||||||
document: `<form><select autocomplete="cc-exp">
|
document: `<form>
|
||||||
|
<input autocomplete="cc-number">
|
||||||
|
<select autocomplete="cc-exp">
|
||||||
<option value="03-17">03-17</option>
|
<option value="03-17">03-17</option>
|
||||||
<option value="01-25" id="selected-cc-exp">01-25</option>
|
<option value="01-25" id="selected-cc-exp">01-25</option>
|
||||||
</select></form>`,
|
</select></form>`,
|
||||||
|
@ -621,7 +645,9 @@ const TESTCASES = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Compound cc-exp: {MON2}-{YEAR4}",
|
description: "Compound cc-exp: {MON2}-{YEAR4}",
|
||||||
document: `<form><select autocomplete="cc-exp">
|
document: `<form>
|
||||||
|
<input autocomplete="cc-number">
|
||||||
|
<select autocomplete="cc-exp">
|
||||||
<option value="03-2017">03-2017</option>
|
<option value="03-2017">03-2017</option>
|
||||||
<option value="01-2025" id="selected-cc-exp">01-2025</option>
|
<option value="01-2025" id="selected-cc-exp">01-2025</option>
|
||||||
</select></form>`,
|
</select></form>`,
|
||||||
|
@ -631,7 +657,9 @@ const TESTCASES = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Compound cc-exp: {YEAR2}-{MON2}",
|
description: "Compound cc-exp: {YEAR2}-{MON2}",
|
||||||
document: `<form><select autocomplete="cc-exp">
|
document: `<form>
|
||||||
|
<input autocomplete="cc-number">
|
||||||
|
<select autocomplete="cc-exp">
|
||||||
<option value="17-03">17-03</option>
|
<option value="17-03">17-03</option>
|
||||||
<option value="25-01" id="selected-cc-exp">25-01</option>
|
<option value="25-01" id="selected-cc-exp">25-01</option>
|
||||||
</select></form>`,
|
</select></form>`,
|
||||||
|
@ -641,7 +669,9 @@ const TESTCASES = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Compound cc-exp: {YEAR4}-{MON2}",
|
description: "Compound cc-exp: {YEAR4}-{MON2}",
|
||||||
document: `<form><select autocomplete="cc-exp">
|
document: `<form>
|
||||||
|
<input autocomplete="cc-number">
|
||||||
|
<select autocomplete="cc-exp">
|
||||||
<option value="2017-03">2017-03</option>
|
<option value="2017-03">2017-03</option>
|
||||||
<option value="2025-01" id="selected-cc-exp">2025-01</option>
|
<option value="2025-01" id="selected-cc-exp">2025-01</option>
|
||||||
</select></form>`,
|
</select></form>`,
|
||||||
|
@ -651,7 +681,9 @@ const TESTCASES = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Compound cc-exp: {YEAR4}/{MON2}",
|
description: "Compound cc-exp: {YEAR4}/{MON2}",
|
||||||
document: `<form><select autocomplete="cc-exp">
|
document: `<form>
|
||||||
|
<input autocomplete="cc-number">
|
||||||
|
<select autocomplete="cc-exp">
|
||||||
<option value="2017/3">2017/3</option>
|
<option value="2017/3">2017/3</option>
|
||||||
<option value="2025/1" id="selected-cc-exp">2025/1</option>
|
<option value="2025/1" id="selected-cc-exp">2025/1</option>
|
||||||
</select></form>`,
|
</select></form>`,
|
||||||
|
@ -661,7 +693,9 @@ const TESTCASES = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Compound cc-exp: {MON2}{YEAR2}",
|
description: "Compound cc-exp: {MON2}{YEAR2}",
|
||||||
document: `<form><select autocomplete="cc-exp">
|
document: `<form>
|
||||||
|
<input autocomplete="cc-number">
|
||||||
|
<select autocomplete="cc-exp">
|
||||||
<option value="0317">0317</option>
|
<option value="0317">0317</option>
|
||||||
<option value="0125" id="selected-cc-exp">0125</option>
|
<option value="0125" id="selected-cc-exp">0125</option>
|
||||||
</select></form>`,
|
</select></form>`,
|
||||||
|
@ -671,7 +705,9 @@ const TESTCASES = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Compound cc-exp: {YEAR2}{MON2}",
|
description: "Compound cc-exp: {YEAR2}{MON2}",
|
||||||
document: `<form><select autocomplete="cc-exp">
|
document: `<form>
|
||||||
|
<input autocomplete="cc-number">
|
||||||
|
<select autocomplete="cc-exp">
|
||||||
<option value="1703">1703</option>
|
<option value="1703">1703</option>
|
||||||
<option value="2501" id="selected-cc-exp">2501</option>
|
<option value="2501" id="selected-cc-exp">2501</option>
|
||||||
</select></form>`,
|
</select></form>`,
|
||||||
|
@ -681,7 +717,9 @@ const TESTCASES = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Fill a cc-exp without cc-exp-month value in the profile",
|
description: "Fill a cc-exp without cc-exp-month value in the profile",
|
||||||
document: `<form><select autocomplete="cc-exp">
|
document: `<form>
|
||||||
|
<input autocomplete="cc-number">
|
||||||
|
<select autocomplete="cc-exp">
|
||||||
<option value="03/17">03/17</option>
|
<option value="03/17">03/17</option>
|
||||||
<option value="01/25">01/25</option>
|
<option value="01/25">01/25</option>
|
||||||
</select></form>`,
|
</select></form>`,
|
||||||
|
@ -697,7 +735,9 @@ const TESTCASES = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Fill a cc-exp without cc-exp-year value in the profile",
|
description: "Fill a cc-exp without cc-exp-year value in the profile",
|
||||||
document: `<form><select autocomplete="cc-exp">
|
document: `<form>
|
||||||
|
<input autocomplete="cc-number">
|
||||||
|
<select autocomplete="cc-exp">
|
||||||
<option value="03/17">03/17</option>
|
<option value="03/17">03/17</option>
|
||||||
<option value="01/25">01/25</option>
|
<option value="01/25">01/25</option>
|
||||||
</select></form>`,
|
</select></form>`,
|
||||||
|
@ -714,6 +754,7 @@ const TESTCASES = [
|
||||||
{
|
{
|
||||||
description: "Fill a cc-exp* without cc-exp-month value in the profile",
|
description: "Fill a cc-exp* without cc-exp-month value in the profile",
|
||||||
document: `<form>
|
document: `<form>
|
||||||
|
<input autocomplete="cc-number">
|
||||||
<select autocomplete="cc-exp-month">
|
<select autocomplete="cc-exp-month">
|
||||||
<option value="03">03</option>
|
<option value="03">03</option>
|
||||||
<option value="01">01</option>
|
<option value="01">01</option>
|
||||||
|
@ -736,6 +777,7 @@ const TESTCASES = [
|
||||||
{
|
{
|
||||||
description: "Fill a cc-exp* without cc-exp-year value in the profile",
|
description: "Fill a cc-exp* without cc-exp-year value in the profile",
|
||||||
document: `<form>
|
document: `<form>
|
||||||
|
<input autocomplete="cc-number">
|
||||||
<select autocomplete="cc-exp-month">
|
<select autocomplete="cc-exp-month">
|
||||||
<option value="03">03</option>
|
<option value="03">03</option>
|
||||||
<option value="01">01</option>
|
<option value="01">01</option>
|
||||||
|
@ -757,7 +799,8 @@ const TESTCASES = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Use placeholder to adjust cc-exp format [mm/yy].",
|
description: "Use placeholder to adjust cc-exp format [mm/yy].",
|
||||||
document: `<form><input placeholder="mm/yy" autocomplete="cc-exp"></form>`,
|
document: `<form><input autocomplete="cc-number">
|
||||||
|
<input placeholder="mm/yy" autocomplete="cc-exp"></form>`,
|
||||||
profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
|
profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
|
||||||
expectedResult: [Object.assign({}, DEFAULT_CREDITCARD_RECORD, {
|
expectedResult: [Object.assign({}, DEFAULT_CREDITCARD_RECORD, {
|
||||||
"cc-exp": "01/25",
|
"cc-exp": "01/25",
|
||||||
|
@ -765,7 +808,8 @@ const TESTCASES = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Use placeholder to adjust cc-exp format [mm / yy].",
|
description: "Use placeholder to adjust cc-exp format [mm / yy].",
|
||||||
document: `<form><input placeholder="mm / yy" autocomplete="cc-exp"></form>`,
|
document: `<form><input autocomplete="cc-number">
|
||||||
|
<input placeholder="mm / yy" autocomplete="cc-exp"></form>`,
|
||||||
profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
|
profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
|
||||||
expectedResult: [Object.assign({}, DEFAULT_CREDITCARD_RECORD, {
|
expectedResult: [Object.assign({}, DEFAULT_CREDITCARD_RECORD, {
|
||||||
"cc-exp": "01/25",
|
"cc-exp": "01/25",
|
||||||
|
@ -773,7 +817,8 @@ const TESTCASES = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Use placeholder to adjust cc-exp format [MM / YY].",
|
description: "Use placeholder to adjust cc-exp format [MM / YY].",
|
||||||
document: `<form><input placeholder="MM / YY" autocomplete="cc-exp"></form>`,
|
document: `<form><input autocomplete="cc-number">
|
||||||
|
<input placeholder="MM / YY" autocomplete="cc-exp"></form>`,
|
||||||
profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
|
profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
|
||||||
expectedResult: [Object.assign({}, DEFAULT_CREDITCARD_RECORD, {
|
expectedResult: [Object.assign({}, DEFAULT_CREDITCARD_RECORD, {
|
||||||
"cc-exp": "01/25",
|
"cc-exp": "01/25",
|
||||||
|
@ -781,7 +826,8 @@ const TESTCASES = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Use placeholder to adjust cc-exp format [mm / yyyy].",
|
description: "Use placeholder to adjust cc-exp format [mm / yyyy].",
|
||||||
document: `<form><input placeholder="mm / yyyy" autocomplete="cc-exp"></form>`,
|
document: `<form><input autocomplete="cc-number">
|
||||||
|
<input placeholder="mm / yyyy" autocomplete="cc-exp"></form>`,
|
||||||
profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
|
profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
|
||||||
expectedResult: [Object.assign({}, DEFAULT_CREDITCARD_RECORD, {
|
expectedResult: [Object.assign({}, DEFAULT_CREDITCARD_RECORD, {
|
||||||
"cc-exp": "01/2025",
|
"cc-exp": "01/2025",
|
||||||
|
@ -789,7 +835,8 @@ const TESTCASES = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Use placeholder to adjust cc-exp format [mm - yyyy].",
|
description: "Use placeholder to adjust cc-exp format [mm - yyyy].",
|
||||||
document: `<form><input placeholder="mm - yyyy" autocomplete="cc-exp"></form>`,
|
document: `<form><input autocomplete="cc-number">
|
||||||
|
<input placeholder="mm - yyyy" autocomplete="cc-exp"></form>`,
|
||||||
profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
|
profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
|
||||||
expectedResult: [Object.assign({}, DEFAULT_CREDITCARD_RECORD, {
|
expectedResult: [Object.assign({}, DEFAULT_CREDITCARD_RECORD, {
|
||||||
"cc-exp": "01-2025",
|
"cc-exp": "01-2025",
|
||||||
|
@ -797,7 +844,8 @@ const TESTCASES = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Use placeholder to adjust cc-exp format [yyyy-mm].",
|
description: "Use placeholder to adjust cc-exp format [yyyy-mm].",
|
||||||
document: `<form><input placeholder="yyyy-mm" autocomplete="cc-exp"></form>`,
|
document: `<form><input autocomplete="cc-number">
|
||||||
|
<input placeholder="yyyy-mm" autocomplete="cc-exp"></form>`,
|
||||||
profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
|
profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
|
||||||
expectedResult: [Object.assign({}, DEFAULT_CREDITCARD_RECORD, {
|
expectedResult: [Object.assign({}, DEFAULT_CREDITCARD_RECORD, {
|
||||||
"cc-exp": "2025-01",
|
"cc-exp": "2025-01",
|
||||||
|
@ -805,7 +853,8 @@ const TESTCASES = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Use placeholder to adjust cc-exp format [yyy-mm].",
|
description: "Use placeholder to adjust cc-exp format [yyy-mm].",
|
||||||
document: `<form><input placeholder="yyy-mm" autocomplete="cc-exp"></form>`,
|
document: `<form><input autocomplete="cc-number">
|
||||||
|
<input placeholder="yyy-mm" autocomplete="cc-exp"></form>`,
|
||||||
profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
|
profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
|
||||||
expectedResult: [Object.assign({}, DEFAULT_CREDITCARD_RECORD, {
|
expectedResult: [Object.assign({}, DEFAULT_CREDITCARD_RECORD, {
|
||||||
"cc-exp": "025-01",
|
"cc-exp": "025-01",
|
||||||
|
@ -813,19 +862,22 @@ const TESTCASES = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Use placeholder to adjust cc-exp format [mmm yyyy].",
|
description: "Use placeholder to adjust cc-exp format [mmm yyyy].",
|
||||||
document: `<form><input placeholder="mmm yyyy" autocomplete="cc-exp"></form>`,
|
document: `<form><input autocomplete="cc-number">
|
||||||
|
<input placeholder="mmm yyyy" autocomplete="cc-exp"></form>`,
|
||||||
profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
|
profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
|
||||||
expectedResult: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
|
expectedResult: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Use placeholder to adjust cc-exp format [mm foo yyyy].",
|
description: "Use placeholder to adjust cc-exp format [mm foo yyyy].",
|
||||||
document: `<form><input placeholder="mm foo yyyy" autocomplete="cc-exp"></form>`,
|
document: `<form><input autocomplete="cc-number">
|
||||||
|
<input placeholder="mm foo yyyy" autocomplete="cc-exp"></form>`,
|
||||||
profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
|
profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
|
||||||
expectedResult: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
|
expectedResult: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Use placeholder to adjust cc-exp format [mm - - yyyy].",
|
description: "Use placeholder to adjust cc-exp format [mm - - yyyy].",
|
||||||
document: `<form><input placeholder="mm - - yyyy" autocomplete="cc-exp"></form>`,
|
document: `<form><input autocomplete="cc-number">
|
||||||
|
<input placeholder="mm - - yyyy" autocomplete="cc-exp"></form>`,
|
||||||
profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
|
profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
|
||||||
expectedResult: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
|
expectedResult: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
|
||||||
},
|
},
|
||||||
|
@ -842,7 +894,8 @@ for (let testcase of TESTCASES) {
|
||||||
let handler = new FormAutofillHandler(formLike);
|
let handler = new FormAutofillHandler(formLike);
|
||||||
|
|
||||||
handler.collectFormFields();
|
handler.collectFormFields();
|
||||||
let adaptedRecords = handler.getAdaptedProfiles(testcase.profileData);
|
let focusedInput = form.elements[0];
|
||||||
|
let adaptedRecords = handler.getAdaptedProfiles(testcase.profileData, focusedInput);
|
||||||
Assert.deepEqual(adaptedRecords, testcase.expectedResult);
|
Assert.deepEqual(adaptedRecords, testcase.expectedResult);
|
||||||
|
|
||||||
if (testcase.expectedOptionElements) {
|
if (testcase.expectedOptionElements) {
|
||||||
|
@ -853,7 +906,8 @@ for (let testcase of TESTCASES) {
|
||||||
Assert.notEqual(expectedOption, null);
|
Assert.notEqual(expectedOption, null);
|
||||||
|
|
||||||
let value = testcase.profileData[i][field];
|
let value = testcase.profileData[i][field];
|
||||||
let cache = handler._cacheValue.matchingSelectOption.get(select);
|
let section = handler.getSectionByElement(select);
|
||||||
|
let cache = section._cacheValue.matchingSelectOption.get(select);
|
||||||
let targetOption = cache[value] && cache[value].get();
|
let targetOption = cache[value] && cache[value].get();
|
||||||
Assert.notEqual(targetOption, null);
|
Assert.notEqual(targetOption, null);
|
||||||
|
|
||||||
|
|
Загрузка…
Ссылка в новой задаче