зеркало из 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.firstTimeUse", true);
|
||||
pref("extensions.formautofill.heuristics.enabled", true);
|
||||
pref("extensions.formautofill.section.enabled", true);
|
||||
pref("extensions.formautofill.loglevel", "Warn");
|
||||
|
||||
// Whether or not to restore a session with lazy-browser tabs.
|
||||
|
|
|
@ -102,8 +102,9 @@ AutofillProfileAutoCompleteSearch.prototype = {
|
|||
let info = FormAutofillContent.getInputDetails(focusedInput);
|
||||
let isAddressField = FormAutofillUtils.isAddressField(info.fieldName);
|
||||
let handler = FormAutofillContent.getFormHandler(focusedInput);
|
||||
let allFieldNames = handler.allFieldNames;
|
||||
let filledRecordGUID = isAddressField ? handler.address.filledRecordGUID : handler.creditCard.filledRecordGUID;
|
||||
let section = handler.getSectionByElement(focusedInput);
|
||||
let allFieldNames = section.allFieldNames;
|
||||
let filledRecordGUID = isAddressField ? section.address.filledRecordGUID : section.creditCard.filledRecordGUID;
|
||||
let searchPermitted = isAddressField ?
|
||||
FormAutofillUtils.isAutofillAddressesEnabled :
|
||||
FormAutofillUtils.isAutofillCreditCardsEnabled;
|
||||
|
@ -149,7 +150,7 @@ AutofillProfileAutoCompleteSearch.prototype = {
|
|||
// Sort addresses by timeLastUsed for showing the lastest used address at top.
|
||||
records.sort((a, b) => b.timeLastUsed - a.timeLastUsed);
|
||||
|
||||
let adaptedRecords = handler.getAdaptedProfiles(records);
|
||||
let adaptedRecords = handler.getAdaptedProfiles(records, focusedInput);
|
||||
let result = null;
|
||||
if (isAddressField) {
|
||||
result = new AddressResult(searchString,
|
||||
|
@ -481,7 +482,7 @@ var FormAutofillContent = {
|
|||
|
||||
getAllFieldNames(element) {
|
||||
let formHandler = this.getFormHandler(element);
|
||||
return formHandler ? formHandler.allFieldNames : null;
|
||||
return formHandler ? formHandler.getAllFieldNames(element) : null;
|
||||
},
|
||||
|
||||
identifyAutofillFields(element) {
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["FormAutofillHandler"];
|
||||
this.EXPORTED_SYMBOLS = ["FormAutofillHandler"]; /* exported FormAutofillHandler */
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
||||
|
||||
|
@ -25,202 +25,81 @@ XPCOMUtils.defineLazyModuleGetter(this, "FormLikeFactory",
|
|||
this.log = null;
|
||||
FormAutofillUtils.defineLazyLogGetter(this, this.EXPORTED_SYMBOLS[0]);
|
||||
|
||||
/**
|
||||
* Handles profile autofill for a DOM Form element.
|
||||
* @param {FormLike} form Form that need to be auto filled
|
||||
*/
|
||||
function FormAutofillHandler(form) {
|
||||
this._updateForm(form);
|
||||
this.winUtils = this.form.rootElement.ownerGlobal.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
|
||||
this.address = {
|
||||
/**
|
||||
* Similar to the `fieldDetails` above but contains address fields only.
|
||||
*/
|
||||
fieldDetails: [],
|
||||
/**
|
||||
* String of the filled address' guid.
|
||||
*/
|
||||
filledRecordGUID: null,
|
||||
};
|
||||
|
||||
this.creditCard = {
|
||||
/**
|
||||
* 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;
|
||||
class FormAutofillSection {
|
||||
constructor(fieldDetails, winUtils) {
|
||||
this.address = {
|
||||
/**
|
||||
* Similar to the `_validDetails` but contains address fields only.
|
||||
*/
|
||||
fieldDetails: [],
|
||||
/**
|
||||
* String of the filled address' guid.
|
||||
*/
|
||||
filledRecordGUID: null,
|
||||
};
|
||||
this.creditCard = {
|
||||
/**
|
||||
* Similar to the `_validDetails` but contains credit card fields only.
|
||||
*/
|
||||
fieldDetails: [],
|
||||
/**
|
||||
* String of the filled creditCard's' guid.
|
||||
*/
|
||||
filledRecordGUID: null,
|
||||
};
|
||||
|
||||
let currentForm = element.form;
|
||||
if (!currentForm) {
|
||||
currentForm = getFormLike();
|
||||
}
|
||||
/**
|
||||
* Enum for form autofill MANUALLY_MANAGED_STATES values
|
||||
*/
|
||||
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) {
|
||||
log.debug("The count of form elements is changed.");
|
||||
this._updateForm(getFormLike());
|
||||
return true;
|
||||
}
|
||||
this.winUtils = winUtils;
|
||||
|
||||
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 = [];
|
||||
|
||||
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(
|
||||
this.address.fieldDetails = fieldDetails.filter(
|
||||
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) {
|
||||
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,
|
||||
"field(s)");
|
||||
this.address.fieldDetails = [];
|
||||
}
|
||||
|
||||
this.creditCard.fieldDetails = fieldDetails.filter(
|
||||
detail => FormAutofillUtils.isCreditCardField(detail.fieldName)
|
||||
);
|
||||
if (!this._isValidCreditCardForm(this.creditCard.fieldDetails)) {
|
||||
log.debug("Invalid credit card form");
|
||||
log.debug("Invalid credit card section.");
|
||||
this.creditCard.fieldDetails = [];
|
||||
}
|
||||
|
||||
let validDetails = Array.of(...(this.address.fieldDetails),
|
||||
...(this.creditCard.fieldDetails));
|
||||
for (let detail of validDetails) {
|
||||
let input = detail.elementWeakRef.get();
|
||||
if (!input) {
|
||||
continue;
|
||||
}
|
||||
input.addEventListener("input", this);
|
||||
}
|
||||
this._cacheValue = {
|
||||
allFieldNames: null,
|
||||
oneLineStreetAddress: null,
|
||||
matchingSelectOption: null,
|
||||
};
|
||||
|
||||
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) {
|
||||
let ccNumberReason = "";
|
||||
|
@ -242,17 +121,18 @@ FormAutofillHandler.prototype = {
|
|||
}
|
||||
|
||||
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) {
|
||||
return this.fieldDetails.find(detail => detail.fieldName == fieldName);
|
||||
},
|
||||
|
||||
getFieldDetailByElement(element) {
|
||||
return this.fieldDetails.find(
|
||||
detail => detail.elementWeakRef.get() == element
|
||||
);
|
||||
},
|
||||
return this._validDetails.find(detail => detail.fieldName == fieldName);
|
||||
}
|
||||
|
||||
getFieldDetailsByElement(element) {
|
||||
let fieldDetail = this.getFieldDetailByElement(element);
|
||||
|
@ -266,14 +146,7 @@ FormAutofillHandler.prototype = {
|
|||
return this.creditCard.fieldDetails;
|
||||
}
|
||||
return [];
|
||||
},
|
||||
|
||||
get allFieldNames() {
|
||||
if (!this._cacheValue.allFieldNames) {
|
||||
this._cacheValue.allFieldNames = this.fieldDetails.map(record => record.fieldName);
|
||||
}
|
||||
return this._cacheValue.allFieldNames;
|
||||
},
|
||||
}
|
||||
|
||||
_getOneLineStreetAddress(address) {
|
||||
if (!this._cacheValue.oneLineStreetAddress) {
|
||||
|
@ -283,7 +156,7 @@ FormAutofillHandler.prototype = {
|
|||
this._cacheValue.oneLineStreetAddress[address] = FormAutofillUtils.toOneLineAddress(address);
|
||||
}
|
||||
return this._cacheValue.oneLineStreetAddress[address];
|
||||
},
|
||||
}
|
||||
|
||||
_addressTransformer(profile) {
|
||||
if (profile["street-address"]) {
|
||||
|
@ -307,7 +180,7 @@ FormAutofillHandler.prototype = {
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace tel with tel-national if tel violates the input element's
|
||||
|
@ -361,7 +234,7 @@ FormAutofillHandler.prototype = {
|
|||
profile.tel = profile["tel-national"];
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
_matchSelectOptions(profile) {
|
||||
if (!this._cacheValue.matchingSelectOption) {
|
||||
|
@ -399,7 +272,7 @@ FormAutofillHandler.prototype = {
|
|||
delete profile[fieldName];
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
_creditCardExpDateTransformer(profile) {
|
||||
if (!profile["cc-exp"]) {
|
||||
|
@ -435,7 +308,7 @@ FormAutofillHandler.prototype = {
|
|||
result[2] +
|
||||
String(ccExpMonth).padStart(result[3].length, "0");
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
getAdaptedProfiles(originalProfiles) {
|
||||
for (let profile of originalProfiles) {
|
||||
|
@ -445,7 +318,7 @@ FormAutofillHandler.prototype = {
|
|||
this._creditCardExpDateTransformer(profile);
|
||||
}
|
||||
return originalProfiles;
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* card field.
|
||||
*/
|
||||
async autofillFormFields(profile, focusedInput) {
|
||||
async autofillFields(profile, focusedInput) {
|
||||
let focusedDetail = this.getFieldDetailByElement(focusedInput);
|
||||
if (!focusedDetail) {
|
||||
throw new Error("No fieldDetail for the focused input.");
|
||||
|
@ -485,7 +358,7 @@ FormAutofillHandler.prototype = {
|
|||
throw new Error("Unknown form fields");
|
||||
}
|
||||
|
||||
log.debug("profile in autofillFormFields:", profile);
|
||||
log.debug("profile in autofillFields:", profile);
|
||||
|
||||
targetSet.filledRecordGUID = profile.guid;
|
||||
for (let fieldDetail of targetSet.fieldDetails) {
|
||||
|
@ -532,41 +405,7 @@ FormAutofillHandler.prototype = {
|
|||
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.
|
||||
|
@ -577,7 +416,7 @@ FormAutofillHandler.prototype = {
|
|||
* A focused input element for determining credit card or address fields.
|
||||
*/
|
||||
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
|
||||
// disabled.
|
||||
|
@ -614,7 +453,7 @@ FormAutofillHandler.prototype = {
|
|||
element.previewValue = value;
|
||||
this.changeFieldState(fieldDetail, value ? "PREVIEW" : "NORMAL");
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear preview text and background highlight of all fields.
|
||||
|
@ -643,7 +482,7 @@ FormAutofillHandler.prototype = {
|
|||
|
||||
this.changeFieldState(fieldDetail, "NORMAL");
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* 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");
|
||||
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");
|
||||
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
|
||||
// managed states so we never need to add or remove it.
|
||||
if (!mmStateValue) {
|
||||
|
@ -680,7 +519,34 @@ FormAutofillHandler.prototype = {
|
|||
}
|
||||
|
||||
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) {
|
||||
let hasName = 0;
|
||||
|
@ -696,11 +562,11 @@ FormAutofillHandler.prototype = {
|
|||
length++;
|
||||
}
|
||||
return (length + hasName) >= FormAutofillUtils.AUTOFILL_FIELDS_THRESHOLD;
|
||||
},
|
||||
}
|
||||
|
||||
_isCreditCardRecordCreatable(record) {
|
||||
return record["cc-number"] && FormAutofillUtils.isCCNumber(record["cc-number"]);
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the records that is converted from address/creditCard fieldDetails and
|
||||
|
@ -794,7 +660,7 @@ FormAutofillHandler.prototype = {
|
|||
}
|
||||
|
||||
return data;
|
||||
},
|
||||
}
|
||||
|
||||
_normalizeAddress(address) {
|
||||
if (!address) {
|
||||
|
@ -837,7 +703,7 @@ FormAutofillHandler.prototype = {
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
async _decrypt(cipherText, reauth) {
|
||||
return new Promise((resolve) => {
|
||||
|
@ -848,7 +714,225 @@ FormAutofillHandler.prototype = {
|
|||
|
||||
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) {
|
||||
switch (event.type) {
|
||||
|
@ -867,5 +951,15 @@ FormAutofillHandler.prototype = {
|
|||
this.timeStartedFillingMS = Date.now();
|
||||
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]);
|
||||
|
||||
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
|
||||
|
@ -544,8 +545,9 @@ this.FormAutofillHeuristics = {
|
|||
},
|
||||
|
||||
/**
|
||||
* This function should provide all field details of a form. The details
|
||||
* contain the autocomplete info (e.g. fieldName, section, etc).
|
||||
* This function should provide all field details of a form which are placed
|
||||
* 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
|
||||
* the heuristics should be verified that some duplicated elements still can
|
||||
|
@ -556,8 +558,8 @@ this.FormAutofillHeuristics = {
|
|||
* @param {boolean} allowDuplicates
|
||||
* true to remain any duplicated field details otherwise to remove the
|
||||
* duplicated ones.
|
||||
* @returns {Array<Object>}
|
||||
* all field details in the form.
|
||||
* @returns {Array<Array<Object>>}
|
||||
* all sections within its field details in the form.
|
||||
*/
|
||||
getFormInfo(form, allowDuplicates = false) {
|
||||
const eligibleFields = Array.from(form.elements)
|
||||
|
@ -582,11 +584,19 @@ this.FormAutofillHeuristics = {
|
|||
|
||||
LabelUtils.clearLabelMap();
|
||||
|
||||
if (allowDuplicates) {
|
||||
return fieldScanner.fieldDetails;
|
||||
if (!this._sectionEnabled) {
|
||||
// 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) {
|
||||
|
@ -895,3 +905,11 @@ Services.prefs.addObserver(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.");
|
||||
|
||||
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 Expected Results: " + testPattern.expectedResult[formIndex].map(i => i.fieldName));
|
||||
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.setBoolPref("extensions.formautofill.creditCards.available", 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);
|
||||
|
||||
// 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.creditCards.available");
|
||||
Services.prefs.clearUserPref("extensions.formautofill.heuristics.enabled");
|
||||
Services.prefs.clearUserPref("extensions.formautofill.section.enabled");
|
||||
Services.prefs.clearUserPref("dom.forms.autocomplete.formautofill");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -504,7 +504,7 @@ function do_test(testcases, testFn) {
|
|||
let handler = new FormAutofillHandler(formLike);
|
||||
let promises = [];
|
||||
// Replace the interal decrypt method with MasterPassword API
|
||||
handler._decrypt = async (cipherText, reauth) => {
|
||||
let decryptHelper = async (cipherText, reauth) => {
|
||||
let string;
|
||||
try {
|
||||
string = await MasterPassword.decrypt(cipherText, reauth);
|
||||
|
@ -518,7 +518,14 @@ function do_test(testcases, testFn) {
|
|||
};
|
||||
|
||||
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 => {
|
||||
let element = field.elementWeakRef.get();
|
||||
if (!testcase.profileData[field.fieldName]) {
|
||||
|
@ -529,9 +536,9 @@ function do_test(testcases, testFn) {
|
|||
promises.push(...testFn(testcase, element));
|
||||
});
|
||||
|
||||
let [adaptedProfile] = handler.getAdaptedProfiles([testcase.profileData]);
|
||||
let focuedInput = doc.getElementById(testcase.focusedInputId);
|
||||
await handler.autofillFormFields(adaptedProfile, focuedInput);
|
||||
let focusedInput = doc.getElementById(testcase.focusedInputId);
|
||||
let [adaptedProfile] = handler.getAdaptedProfiles([testcase.profileData], focusedInput);
|
||||
await handler.autofillFormFields(adaptedProfile, focusedInput);
|
||||
Assert.equal(handlerInfo.filledRecordGUID, testcase.profileData.guid,
|
||||
"Check if filledRecordGUID is set correctly");
|
||||
await Promise.all(promises);
|
||||
|
|
|
@ -440,8 +440,11 @@ for (let tc of TESTCASES) {
|
|||
let handler = new FormAutofillHandler(formLike);
|
||||
let validFieldDetails = handler.collectFormFields(testcase.allowDuplicates);
|
||||
|
||||
verifyDetails(handler.address.fieldDetails, testcase.addressFieldDetails);
|
||||
verifyDetails(handler.creditCard.fieldDetails, testcase.creditCardFieldDetails);
|
||||
// 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.
|
||||
verifyDetails(handler.sections[0].address.fieldDetails, testcase.addressFieldDetails);
|
||||
verifyDetails(handler.sections[0].creditCard.fieldDetails, testcase.creditCardFieldDetails);
|
||||
verifyDetails(validFieldDetails, testcase.validFieldDetails);
|
||||
});
|
||||
})();
|
||||
|
|
|
@ -29,6 +29,8 @@ const TESTCASES = [
|
|||
{
|
||||
description: "Address form with street-address",
|
||||
document: `<form>
|
||||
<input autocomplete="given-name">
|
||||
<input autocomplete="family-name">
|
||||
<input id="street-addr" autocomplete="street-address">
|
||||
</form>`,
|
||||
profileData: [Object.assign({}, DEFAULT_ADDRESS_RECORD)],
|
||||
|
@ -70,6 +72,7 @@ const TESTCASES = [
|
|||
{
|
||||
description: "Address form with street-address, address-line1",
|
||||
document: `<form>
|
||||
<input autocomplete="given-name">
|
||||
<input id="street-addr" autocomplete="street-address">
|
||||
<input id="line1" autocomplete="address-line1">
|
||||
</form>`,
|
||||
|
@ -132,6 +135,7 @@ const TESTCASES = [
|
|||
{
|
||||
description: "Address form with exact matching options in select",
|
||||
document: `<form>
|
||||
<input autocomplete="given-name">
|
||||
<select autocomplete="address-level1">
|
||||
<option id="option-address-level1-XX" value="XX">Dummy</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",
|
||||
document: `<form>
|
||||
<input autocomplete="given-name">
|
||||
<select autocomplete="address-level1">
|
||||
<option id="option-address-level1-XX" value="XX">Dummy</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",
|
||||
document: `<form>
|
||||
<input autocomplete="given-name">
|
||||
<select autocomplete="address-level1">
|
||||
<option id="option-address-level1-1" value="">Dummy</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 ",
|
||||
document: `<form>
|
||||
<input autocomplete="given-name">
|
||||
<select autocomplete="address-level1">
|
||||
<option id="option-address-level1-same1" value="same">Dummy</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",
|
||||
document: `<form>
|
||||
<input autocomplete="given-name">
|
||||
<select autocomplete="address-level1">
|
||||
<option id="option-address-level1-dummy1" value="">Dummy</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",
|
||||
document: `<form>
|
||||
<input autocomplete="cc-number">
|
||||
<select autocomplete="cc-exp-month">
|
||||
<option id="option-cc-exp-month-01" value="1">01</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",
|
||||
document: `<form>
|
||||
<input autocomplete="cc-number">
|
||||
<select autocomplete="cc-exp-month">
|
||||
<option value="" selected="selected">Month</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}",
|
||||
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="1/25" id="selected-cc-exp">1/25</option>
|
||||
</select></form>`,
|
||||
|
@ -561,7 +573,9 @@ const TESTCASES = [
|
|||
},
|
||||
{
|
||||
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="1/2025" id="selected-cc-exp">1/2025</option>
|
||||
</select></form>`,
|
||||
|
@ -571,7 +585,9 @@ const TESTCASES = [
|
|||
},
|
||||
{
|
||||
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="01/25" id="selected-cc-exp">01/25</option>
|
||||
</select></form>`,
|
||||
|
@ -581,7 +597,9 @@ const TESTCASES = [
|
|||
},
|
||||
{
|
||||
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="01/2025" id="selected-cc-exp">01/2025</option>
|
||||
</select></form>`,
|
||||
|
@ -591,7 +609,9 @@ const TESTCASES = [
|
|||
},
|
||||
{
|
||||
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="1-25" id="selected-cc-exp">1-25</option>
|
||||
</select></form>`,
|
||||
|
@ -601,7 +621,9 @@ const TESTCASES = [
|
|||
},
|
||||
{
|
||||
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="1-2025" id="selected-cc-exp">1-2025</option>
|
||||
</select></form>`,
|
||||
|
@ -611,7 +633,9 @@ const TESTCASES = [
|
|||
},
|
||||
{
|
||||
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="01-25" id="selected-cc-exp">01-25</option>
|
||||
</select></form>`,
|
||||
|
@ -621,7 +645,9 @@ const TESTCASES = [
|
|||
},
|
||||
{
|
||||
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="01-2025" id="selected-cc-exp">01-2025</option>
|
||||
</select></form>`,
|
||||
|
@ -631,7 +657,9 @@ const TESTCASES = [
|
|||
},
|
||||
{
|
||||
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="25-01" id="selected-cc-exp">25-01</option>
|
||||
</select></form>`,
|
||||
|
@ -641,7 +669,9 @@ const TESTCASES = [
|
|||
},
|
||||
{
|
||||
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="2025-01" id="selected-cc-exp">2025-01</option>
|
||||
</select></form>`,
|
||||
|
@ -651,7 +681,9 @@ const TESTCASES = [
|
|||
},
|
||||
{
|
||||
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="2025/1" id="selected-cc-exp">2025/1</option>
|
||||
</select></form>`,
|
||||
|
@ -661,7 +693,9 @@ const TESTCASES = [
|
|||
},
|
||||
{
|
||||
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="0125" id="selected-cc-exp">0125</option>
|
||||
</select></form>`,
|
||||
|
@ -671,7 +705,9 @@ const TESTCASES = [
|
|||
},
|
||||
{
|
||||
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="2501" id="selected-cc-exp">2501</option>
|
||||
</select></form>`,
|
||||
|
@ -681,7 +717,9 @@ const TESTCASES = [
|
|||
},
|
||||
{
|
||||
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="01/25">01/25</option>
|
||||
</select></form>`,
|
||||
|
@ -697,7 +735,9 @@ const TESTCASES = [
|
|||
},
|
||||
{
|
||||
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="01/25">01/25</option>
|
||||
</select></form>`,
|
||||
|
@ -714,6 +754,7 @@ const TESTCASES = [
|
|||
{
|
||||
description: "Fill a cc-exp* without cc-exp-month value in the profile",
|
||||
document: `<form>
|
||||
<input autocomplete="cc-number">
|
||||
<select autocomplete="cc-exp-month">
|
||||
<option value="03">03</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",
|
||||
document: `<form>
|
||||
<input autocomplete="cc-number">
|
||||
<select autocomplete="cc-exp-month">
|
||||
<option value="03">03</option>
|
||||
<option value="01">01</option>
|
||||
|
@ -757,7 +799,8 @@ const TESTCASES = [
|
|||
},
|
||||
{
|
||||
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)],
|
||||
expectedResult: [Object.assign({}, DEFAULT_CREDITCARD_RECORD, {
|
||||
"cc-exp": "01/25",
|
||||
|
@ -765,7 +808,8 @@ const TESTCASES = [
|
|||
},
|
||||
{
|
||||
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)],
|
||||
expectedResult: [Object.assign({}, DEFAULT_CREDITCARD_RECORD, {
|
||||
"cc-exp": "01/25",
|
||||
|
@ -773,7 +817,8 @@ const TESTCASES = [
|
|||
},
|
||||
{
|
||||
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)],
|
||||
expectedResult: [Object.assign({}, DEFAULT_CREDITCARD_RECORD, {
|
||||
"cc-exp": "01/25",
|
||||
|
@ -781,7 +826,8 @@ const TESTCASES = [
|
|||
},
|
||||
{
|
||||
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)],
|
||||
expectedResult: [Object.assign({}, DEFAULT_CREDITCARD_RECORD, {
|
||||
"cc-exp": "01/2025",
|
||||
|
@ -789,7 +835,8 @@ const TESTCASES = [
|
|||
},
|
||||
{
|
||||
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)],
|
||||
expectedResult: [Object.assign({}, DEFAULT_CREDITCARD_RECORD, {
|
||||
"cc-exp": "01-2025",
|
||||
|
@ -797,7 +844,8 @@ const TESTCASES = [
|
|||
},
|
||||
{
|
||||
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)],
|
||||
expectedResult: [Object.assign({}, DEFAULT_CREDITCARD_RECORD, {
|
||||
"cc-exp": "2025-01",
|
||||
|
@ -805,7 +853,8 @@ const TESTCASES = [
|
|||
},
|
||||
{
|
||||
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)],
|
||||
expectedResult: [Object.assign({}, DEFAULT_CREDITCARD_RECORD, {
|
||||
"cc-exp": "025-01",
|
||||
|
@ -813,19 +862,22 @@ const TESTCASES = [
|
|||
},
|
||||
{
|
||||
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)],
|
||||
expectedResult: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
|
||||
},
|
||||
{
|
||||
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)],
|
||||
expectedResult: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
|
||||
},
|
||||
{
|
||||
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)],
|
||||
expectedResult: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
|
||||
},
|
||||
|
@ -842,7 +894,8 @@ for (let testcase of TESTCASES) {
|
|||
let handler = new FormAutofillHandler(formLike);
|
||||
|
||||
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);
|
||||
|
||||
if (testcase.expectedOptionElements) {
|
||||
|
@ -853,7 +906,8 @@ for (let testcase of TESTCASES) {
|
|||
Assert.notEqual(expectedOption, null);
|
||||
|
||||
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();
|
||||
Assert.notEqual(targetOption, null);
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче