Bug 1417803 - Part 1: Use activeSection to record the current focused field or section. r=lchang,ralin
MozReview-Commit-ID: 4mhmTcJOOz2
--- a/browser/extensions/formautofill/FormAutofillContent.jsm
+++ b/browser/extensions/formautofill/FormAutofillContent.jsm
@@ -101,18 +101,19 @@ AutofillProfileAutoCompleteSearch.protot
let savedFieldNames = FormAutofillContent.savedFieldNames;
let focusedInput = formFillController.focusedInput;
let info = FormAutofillContent.getInputDetails(focusedInput);
let isAddressField = FormAutofillUtils.isAddressField(info.fieldName);
let isInputAutofilled = info.state == FIELD_STATES.AUTO_FILLED;
let handler = FormAutofillContent.getFormHandler(focusedInput);
- let allFieldNames = handler.getAllFieldNames(focusedInput);
- let filledRecordGUID = handler.getFilledRecordGUID(focusedInput);
+ let activeSection = handler.activeSection;
+ let allFieldNames = activeSection.allFieldNames;
+ let filledRecordGUID = activeSection.getFilledRecordGUID();
let searchPermitted = isAddressField ?
FormAutofillUtils.isAutofillAddressesEnabled :
FormAutofillUtils.isAutofillCreditCardsEnabled;
let AutocompleteResult = isAddressField ? AddressResult : CreditCardResult;
ProfileAutocomplete.lastProfileAutoCompleteFocusedInput = focusedInput;
// Fallback to form-history if ...
// - specified autofill feature is pref off.
@@ -157,17 +158,17 @@ AutofillProfileAutoCompleteSearch.protot
this._getRecords(data).then((records) => {
if (this.forceStop) {
return;
}
// 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, focusedInput);
+ let adaptedRecords = activeSection.getAdaptedProfiles(records);
let result = null;
let isSecure = InsecurePasswordUtils.isFormSecure(handler.form);
result = new AutocompleteResult(searchString,
info.fieldName,
allFieldNames,
adaptedRecords,
{isSecure, isInputAutofilled});
@@ -294,30 +295,30 @@ let ProfileAutocomplete = {
this.lastProfileAutoCompleteResult.getStyleAt(selectedIndex) != "autofill-profile") {
return;
}
let profile = JSON.parse(this.lastProfileAutoCompleteResult.getCommentAt(selectedIndex));
let {fieldName} = FormAutofillContent.getInputDetails(focusedInput);
let formHandler = FormAutofillContent.getFormHandler(focusedInput);
- formHandler.autofillFormFields(profile, focusedInput).then(() => {
+ formHandler.autofillFormFields(profile).then(() => {
autocompleteController.searchString = profile[fieldName];
});
},
_clearProfilePreview() {
let focusedInput = formFillController.focusedInput || this.lastProfileAutoCompleteFocusedInput;
if (!focusedInput || !FormAutofillContent.getFormDetails(focusedInput)) {
return;
}
let formHandler = FormAutofillContent.getFormHandler(focusedInput);
- formHandler.clearPreviewedFormFields(focusedInput);
+ formHandler.activeSection.clearPreviewedFormFields();
},
_previewSelectedProfile(selectedIndex) {
let focusedInput = formFillController.focusedInput;
if (!focusedInput || !FormAutofillContent.getFormDetails(focusedInput)) {
// The observer notification is for a different process/frame.
return;
}
@@ -325,17 +326,17 @@ let ProfileAutocomplete = {
if (!this.lastProfileAutoCompleteResult ||
this.lastProfileAutoCompleteResult.getStyleAt(selectedIndex) != "autofill-profile") {
return;
}
let profile = JSON.parse(this.lastProfileAutoCompleteResult.getCommentAt(selectedIndex));
let formHandler = FormAutofillContent.getFormHandler(focusedInput);
- formHandler.previewFormFields(profile, focusedInput);
+ formHandler.activeSection.previewFormFields(profile);
},
};
/**
* Handles content's interactions for the process.
*
* NOTE: Declares it by "var" to make it accessible in unit tests.
*/
@@ -484,54 +485,56 @@ var FormAutofillContent = {
*/
getFormDetails(element) {
let formHandler = this.getFormHandler(element);
return formHandler ? formHandler.fieldDetails : null;
},
getAllFieldNames(element) {
let formHandler = this.getFormHandler(element);
- return formHandler ? formHandler.getAllFieldNames(element) : null;
+ return formHandler ? formHandler.activeSection.allFieldNames : null;
},
identifyAutofillFields(element) {
this.log.debug("identifyAutofillFields:", "" + element.ownerDocument.location);
if (!this.savedFieldNames) {
this.log.debug("identifyAutofillFields: savedFieldNames are not known yet");
Services.cpmm.sendAsyncMessage("FormAutofill:InitStorage");
}
let formHandler = this.getFormHandler(element);
if (!formHandler) {
let formLike = FormLikeFactory.createFromField(element);
formHandler = new FormAutofillHandler(formLike);
} else if (!formHandler.updateFormIfNeeded(element)) {
+ formHandler.focusedInput = element;
this.log.debug("No control is removed or inserted since last collection.");
return;
}
let validDetails = formHandler.collectFormFields();
this._formsDetails.set(formHandler.form.rootElement, formHandler);
this.log.debug("Adding form handler to _formsDetails:", formHandler);
validDetails.forEach(detail =>
this._markAsAutofillField(detail.elementWeakRef.get())
);
+ formHandler.focusedInput = element;
},
clearForm() {
let focusedInput = formFillController.focusedInput || ProfileAutocomplete._lastAutoCompleteFocusedInput;
if (!focusedInput) {
return;
}
let formHandler = this.getFormHandler(focusedInput);
- formHandler.clearPopulatedForm(focusedInput);
+ formHandler.activeSection.clearPopulatedForm();
autocompleteController.searchString = "";
},
previewProfile(doc) {
let docWin = doc.ownerGlobal;
let selectedIndex = ProfileAutocomplete._getSelectedIndex(docWin);
let lastAutoCompleteResult = ProfileAutocomplete.lastProfileAutoCompleteResult;
let focusedInput = formFillController.focusedInput;
--- a/browser/extensions/formautofill/FormAutofillHandler.jsm
+++ b/browser/extensions/formautofill/FormAutofillHandler.jsm
@@ -94,16 +94,20 @@ class FormAutofillSection {
...(this.creditCard.fieldDetails));
log.debug(this._validDetails.length, "valid fields in the section is collected.");
}
get validDetails() {
return this._validDetails;
}
+ set focusedInput(element) {
+ this._focusedDetail = this.getFieldDetailByElement(element);
+ }
+
getFieldDetailByElement(element) {
return this._validDetails.find(
detail => detail.elementWeakRef.get() == element
);
}
_isValidCreditCardForm(fieldDetails) {
let ccNumberReason = "";
@@ -133,37 +137,37 @@ class FormAutofillSection {
}
return this._cacheValue.allFieldNames;
}
getFieldDetailByName(fieldName) {
return this._validDetails.find(detail => detail.fieldName == fieldName);
}
- _getTargetSet(element) {
- let fieldDetail = this.getFieldDetailByElement(element);
+ _getTargetSet() {
+ let fieldDetail = this._focusedDetail;
if (!fieldDetail) {
return null;
}
if (FormAutofillUtils.isAddressField(fieldDetail.fieldName)) {
return this.address;
}
if (FormAutofillUtils.isCreditCardField(fieldDetail.fieldName)) {
return this.creditCard;
}
return null;
}
- getFieldDetailsByElement(element) {
- let targetSet = this._getTargetSet(element);
+ _getFieldDetails() {
+ let targetSet = this._getTargetSet();
return targetSet ? targetSet.fieldDetails : [];
}
- getFilledRecordGUID(element) {
- let targetSet = this._getTargetSet(element);
+ getFilledRecordGUID() {
+ let targetSet = this._getTargetSet();
return targetSet ? targetSet.filledRecordGUID : null;
}
_getOneLineStreetAddress(address) {
if (!this._cacheValue.oneLineStreetAddress) {
this._cacheValue.oneLineStreetAddress = {};
}
if (!this._cacheValue.oneLineStreetAddress[address]) {
@@ -361,26 +365,23 @@ class FormAutofillSection {
}
/**
* 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 autofillFields(profile, focusedInput) {
- let focusedDetail = this.getFieldDetailByElement(focusedInput);
+ async autofillFields(profile) {
+ let focusedDetail = this._focusedDetail;
if (!focusedDetail) {
throw new Error("No fieldDetail for the focused input.");
}
- let targetSet = this._getTargetSet(focusedInput);
+ let targetSet = this._getTargetSet();
if (FormAutofillUtils.isCreditCardField(focusedDetail.fieldName)) {
// When Master Password is enabled by users, the decryption process
// should prompt Master Password dialog to get the decrypted credit
// card number. Otherwise, the number can be decrypted with the default
// password.
if (profile["cc-number-encrypted"]) {
let decrypted = await this._decrypt(profile["cc-number-encrypted"], true);
@@ -410,16 +411,17 @@ class FormAutofillSection {
element.previewValue = "";
let value = profile[fieldDetail.fieldName];
if (element instanceof Ci.nsIDOMHTMLInputElement && value) {
// For the focused input element, it will be filled with a valid value
// anyway.
// For the others, the fields should be only filled when their values
// are empty.
+ let focusedInput = focusedDetail.elementWeakRef.get();
if (element == focusedInput ||
(element != focusedInput && !element.value)) {
element.setUserInput(value);
this.changeFieldState(fieldDetail, FIELD_STATES.AUTO_FILLED);
}
} else if (ChromeUtils.getClassName(element) === "HTMLSelectElement") {
let cache = this._cacheValue.matchingSelectOption.get(element) || {};
let option = cache[value] && cache[value].get();
@@ -442,29 +444,27 @@ class FormAutofillSection {
}
}
/**
* Populates result to the preview layers with given profile.
*
* @param {Object} profile
* A profile to be previewed with
- * @param {HTMLElement} focusedInput
- * A focused input element for determining credit card or address fields.
*/
- previewFormFields(profile, focusedInput) {
+ previewFormFields(profile) {
log.debug("preview profile: ", profile);
// Always show the decrypted credit card number when Master Password is
// disabled.
if (profile["cc-number-decrypted"]) {
profile["cc-number"] = profile["cc-number-decrypted"];
}
- let fieldDetails = this.getFieldDetailsByElement(focusedInput);
+ let fieldDetails = this._getFieldDetails();
for (let fieldDetail of fieldDetails) {
let element = fieldDetail.elementWeakRef.get();
let value = profile[fieldDetail.fieldName] || "";
// Skip the field that is null
if (!element) {
continue;
}
@@ -487,24 +487,21 @@ class FormAutofillSection {
}
element.previewValue = value;
this.changeFieldState(fieldDetail, value ? FIELD_STATES.PREVIEW : FIELD_STATES.NORMAL);
}
}
/**
* Clear preview text and background highlight of all fields.
- *
- * @param {HTMLElement} focusedInput
- * A focused input element for determining credit card or address fields.
*/
- clearPreviewedFormFields(focusedInput) {
+ clearPreviewedFormFields() {
log.debug("clear previewed fields in:", this.form);
- let fieldDetails = this.getFieldDetailsByElement(focusedInput);
+ let fieldDetails = this._getFieldDetails();
for (let fieldDetail of fieldDetails) {
let element = fieldDetail.elementWeakRef.get();
if (!element) {
log.warn(fieldDetail.fieldName, "is unreachable");
continue;
}
element.previewValue = "";
@@ -516,22 +513,19 @@ class FormAutofillSection {
}
this.changeFieldState(fieldDetail, FIELD_STATES.NORMAL);
}
}
/**
* Clear value and highlight style of all filled fields.
- *
- * @param {Object} focusedInput
- * A focused input element for determining credit card or address fields.
*/
- clearPopulatedForm(focusedInput) {
- let fieldDetails = this.getFieldDetailsByElement(focusedInput);
+ clearPopulatedForm() {
+ let fieldDetails = this._getFieldDetails();
for (let fieldDetail of fieldDetails) {
let element = fieldDetail.elementWeakRef.get();
if (!element) {
log.warn(fieldDetail.fieldName, "is unreachable");
continue;
}
// Only reset value for input element.
@@ -802,16 +796,36 @@ class FormAutofillHandler {
.getInterface(Ci.nsIDOMWindowUtils);
/**
* Time in milliseconds since epoch when a user started filling in the form.
*/
this.timeStartedFillingMS = null;
}
+ set focusedInput(element) {
+ let section = this._sectionCache.get(element);
+ if (!section) {
+ section = this.sections.find(
+ s => s.getFieldDetailByElement(element)
+ );
+ this._sectionCache.set(element, section);
+ }
+
+ this._focusedSection = section;
+
+ if (section) {
+ section.focusedInput = element;
+ }
+ }
+
+ get activeSection() {
+ return this._focusedSection;
+ }
+
/**
* 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
@@ -904,75 +918,30 @@ class FormAutofillHandler {
}
input.addEventListener("input", this, {mozSystemGroup: true});
}
this.fieldDetails = allValidDetails;
return allValidDetails;
}
- getSectionByElement(element) {
- let section = this._sectionCache.get(element);
- if (!section) {
- section = this.sections.find(
- s => s.getFieldDetailByElement(element)
- );
- this._sectionCache.set(element, section);
- }
- return section;
- }
-
- 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);
- }
-
- clearPopulatedForm(focusedInput) {
- let section = this.getSectionByElement(focusedInput);
- section.clearPopulatedForm(focusedInput);
- }
-
- getFilledRecordGUID(focusedInput) {
- let section = this.getSectionByElement(focusedInput);
- return section.getFilledRecordGUID(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) {
+ async autofillFormFields(profile) {
let noFilledSectionsPreviously = !this.hasFilledSection();
- await this.getSectionByElement(focusedInput).autofillFields(profile, focusedInput);
+ await this.activeSection.autofillFields(profile);
const onChangeHandler = e => {
if (!e.isTrusted) {
return;
}
if (e.type == "reset") {
for (let section of this.sections) {
section.resetFieldStates();
--- a/browser/extensions/formautofill/test/unit/test_autofillFormFields.js
+++ b/browser/extensions/formautofill/test/unit/test_autofillFormFields.js
@@ -532,17 +532,18 @@ function do_test(testcases, testFn) {
// Avoid waiting for `change` event of a input with a blank value to
// be filled.
return;
}
promises.push(...testFn(testcase, element));
});
let focusedInput = doc.getElementById(testcase.focusedInputId);
- let [adaptedProfile] = handler.getAdaptedProfiles([testcase.profileData], focusedInput);
+ handler.focusedInput = focusedInput;
+ let [adaptedProfile] = handler.activeSection.getAdaptedProfiles([testcase.profileData]);
await handler.autofillFormFields(adaptedProfile, focusedInput);
Assert.equal(handlerInfo.filledRecordGUID, testcase.profileData.guid,
"Check if filledRecordGUID is set correctly");
await Promise.all(promises);
});
})();
}
}
--- a/browser/extensions/formautofill/test/unit/test_getAdaptedProfiles.js
+++ b/browser/extensions/formautofill/test/unit/test_getAdaptedProfiles.js
@@ -929,30 +929,29 @@ for (let testcase of TESTCASES) {
let doc = MockDocument.createTestDocument("http://localhost:8080/test/",
testcase.document);
let form = doc.querySelector("form");
let formLike = FormLikeFactory.createFromForm(form);
let handler = new FormAutofillHandler(formLike);
handler.collectFormFields();
- let focusedInput = form.elements[0];
- let adaptedRecords = handler.getAdaptedProfiles(testcase.profileData, focusedInput);
+ handler.focusedInput = form.elements[0];
+ let adaptedRecords = handler.activeSection.getAdaptedProfiles(testcase.profileData);
Assert.deepEqual(adaptedRecords, testcase.expectedResult);
if (testcase.expectedOptionElements) {
testcase.expectedOptionElements.forEach((expectedOptionElement, i) => {
for (let field in expectedOptionElement) {
let select = form.querySelector(`[autocomplete=${field}]`);
let expectedOption = doc.getElementById(expectedOptionElement[field]);
Assert.notEqual(expectedOption, null);
let value = testcase.profileData[i][field];
- let section = handler.getSectionByElement(select);
- let cache = section._cacheValue.matchingSelectOption.get(select);
+ let cache = handler.activeSection._cacheValue.matchingSelectOption.get(select);
let targetOption = cache[value] && cache[value].get();
Assert.notEqual(targetOption, null);
Assert.equal(targetOption, expectedOption);
}
});
}
});