--- a/browser/extensions/formautofill/content/FormAutofillContent.js
+++ b/browser/extensions/formautofill/content/FormAutofillContent.js
@@ -1,13 +1,13 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-/* global content */
+/* eslint-disable no-use-before-define */
/*
* Form Autofill frame script.
*/
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr, manager: Cm} = Components;
@@ -225,27 +225,26 @@ AutofillProfileAutoCompleteSearch.protot
*
* @param {string} searchString the string to search for
* @param {string} searchParam
* @param {Object} previousResult a previous result to use for faster searchinig
* @param {Object} listener the listener to notify when the search is complete
*/
startSearch(searchString, searchParam, previousResult, listener) {
this.forceStop = false;
- let inputInfo;
+ let info = this.getInputDetails();
- this.getInputInfo().then((info) => {
- inputInfo = info;
- return this.getProfiles({info, searchString});
- }).then((profiles) => {
+ this.getProfiles({info, searchString}).then((profiles) => {
if (this.forceStop) {
return;
}
- let result = new ProfileAutoCompleteResult(searchString, inputInfo, profiles, {});
+ // TODO: Set formInfo for ProfileAutoCompleteResult
+ // let formInfo = this.getFormDetails();
+ let result = new ProfileAutoCompleteResult(searchString, info, profiles, {});
listener.onSearchResult(this, result);
});
},
/**
* Stops an asynchronous search that is in progress
*/
@@ -274,29 +273,35 @@ AutofillProfileAutoCompleteSearch.protot
});
sendAsyncMessage("FormAutofill:GetProfiles", data);
});
},
/**
- * Get the input's information from FormAutofill heuristics.
+ * Get the input's information from FormAutofillContent's cache.
*
- * @returns {Promise}
- * Promise that resolves when profiles returned from heuristics getInfo API.
+ * @returns {Object}
+ * Target input's information that cached in FormAutofillContent.
*/
- getInputInfo() {
- let input = formFillController.getFocusedInput();
+ getInputDetails() {
+ // TODO: Maybe we'll need to wait for cache ready if detail is empty.
+ return FormAutofillContent.getInputDetails(formFillController.getFocusedInput());
+ },
- // It could be synchronous API since FormAutofillHeuristics.getInfo is still
- // not in parent process yet.
- return new Promise((resolve) => {
- resolve(FormAutofillHeuristics.getInfo(input));
- });
+ /**
+ * Get the form's information from FormAutofillContent's cache.
+ *
+ * @returns {Array<Object>}
+ * Array of the inputs' information for the target form.
+ */
+ getFormDetails() {
+ // TODO: Maybe we'll need to wait for cache ready if details is empty.
+ return FormAutofillContent.getFormDetails(formFillController.getFocusedInput());
},
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([AutofillProfileAutoCompleteSearch]);
let ProfileAutocomplete = {
_registered: false,
_factory: null,
@@ -345,36 +350,95 @@ var FormAutofillContent = {
if (!(doc instanceof Ci.nsIDOMHTMLDocument)) {
return;
}
this._identifyAutofillFields(doc);
break;
}
},
+ /**
+ * Get the input's information from cache which is created after page identified.
+ *
+ * @param {HTMLInputElement} element Focused input which triggered profile searching
+ * @returns {Object|null}
+ * Return target input's information that cloned from content cache
+ * (or return null if the information is not found in the cache).
+ */
+ getInputDetails(element) {
+ for (let formDetails of this._formsDetails) {
+ for (let detail of formDetails) {
+ if (element == detail.element) {
+ return this._serializeInfo(detail);
+ }
+ }
+ }
+ return null;
+ },
+
+ /**
+ * Get the form's information from cache which is created after page identified.
+ *
+ * @param {HTMLInputElement} element Focused input which triggered profile searching
+ * @returns {Array<Object>|null}
+ * Return target form's information that cloned from content cache
+ * (or return null if the information is not found in the cache).
+ *
+ */
+ getFormDetails(element) {
+ for (let formDetails of this._formsDetails) {
+ if (formDetails.some((detail) => detail.element == element)) {
+ return formDetails.map((detail) => this._serializeInfo(detail));
+ }
+ }
+ return null;
+ },
+
+ /**
+ * Create a clone the information object without element reference.
+ *
+ * @param {Object} detail Profile autofill information for specific input.
+ * @returns {Object}
+ * Return a copy of cached information object without element reference
+ * since it's not needed for creating result.
+ */
+ _serializeInfo(detail) {
+ let info = Object.assign({}, detail);
+ delete info.element;
+ return info;
+ },
+
_identifyAutofillFields(doc) {
let forms = [];
+ this._formsDetails = [];
// Collects root forms from inputs.
for (let field of doc.getElementsByTagName("input")) {
+ // We only consider text-like fields for now until we support radio and
+ // checkbox buttons in the future.
+ if (!field.mozIsTextField(true)) {
+ continue;
+ }
+
let formLike = FormLikeFactory.createFromField(field);
if (!forms.some(form => form.rootElement === formLike.rootElement)) {
forms.push(formLike);
}
}
// Collects the fields that can be autofilled from each form and marks them
// as autofill fields if the amount is above the threshold.
forms.forEach(form => {
let formHandler = new FormAutofillHandler(form);
formHandler.collectFormFields();
if (formHandler.fieldDetails.length < AUTOFILL_FIELDS_THRESHOLD) {
return;
}
+ this._formsDetails.push(formHandler.fieldDetails);
formHandler.fieldDetails.forEach(
detail => this._markAsAutofillField(detail.element));
});
},
_markAsAutofillField(field) {
formFillController.markAsAutofillField(field);
},
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/test/unit/test_getFormInputDetails.js
@@ -0,0 +1,90 @@
+"use strict";
+
+let {FormAutofillContent} = loadFormAutofillContent();
+
+const TESTCASES = [
+ {
+ description: "Form containing 5 fields with autocomplete attribute.",
+ document: `<form>
+ <input id="street-addr" autocomplete="street-address">
+ <input id="city" autocomplete="address-level2">
+ <input id="country" autocomplete="country">
+ <input id="email" autocomplete="email">
+ <input id="tel" autocomplete="tel">
+ </form>`,
+ targetInput: ["street-addr", "country"],
+ expectedResult: [{
+ input: {"section": "", "addressType": "", "contactType": "", "fieldName": "street-address"},
+ form: [
+ {"section": "", "addressType": "", "contactType": "", "fieldName": "street-address"},
+ {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"},
+ {"section": "", "addressType": "", "contactType": "", "fieldName": "country"},
+ {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
+ {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
+ ],
+ },
+ {
+ input: {"section": "", "addressType": "", "contactType": "", "fieldName": "country"},
+ form: [
+ {"section": "", "addressType": "", "contactType": "", "fieldName": "street-address"},
+ {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"},
+ {"section": "", "addressType": "", "contactType": "", "fieldName": "country"},
+ {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
+ {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
+ ],
+ }],
+ },
+ {
+ description: "2 forms that are able to be auto filled",
+ document: `<form>
+ <input id="home-addr" autocomplete="street-address">
+ <input id="city" autocomplete="address-level2">
+ <input id="country" autocomplete="country">
+ </form>
+ <form>
+ <input id="office-addr" autocomplete="street-address">
+ <input id="email" autocomplete="email">
+ <input id="tel" autocomplete="tel">
+ </form>`,
+ targetInput: ["home-addr", "office-addr"],
+ expectedResult: [{
+ input: {"section": "", "addressType": "", "contactType": "", "fieldName": "street-address"},
+ form: [
+ {"section": "", "addressType": "", "contactType": "", "fieldName": "street-address"},
+ {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"},
+ {"section": "", "addressType": "", "contactType": "", "fieldName": "country"},
+ ],
+ },
+ {
+ input: {"section": "", "addressType": "", "contactType": "", "fieldName": "street-address"},
+ form: [
+ {"section": "", "addressType": "", "contactType": "", "fieldName": "street-address"},
+ {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
+ {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
+ ],
+ }],
+ },
+];
+
+
+TESTCASES.forEach(testcase => {
+ add_task(function* () {
+ do_print("Starting testcase: " + testcase.description);
+
+ let doc = MockDocument.createTestDocument(
+ "http://localhost:8080/test/", testcase.document);
+ FormAutofillContent._identifyAutofillFields(doc);
+
+ for (let i in testcase.targetInput) {
+ let input = doc.getElementById(testcase.targetInput[i]);
+
+ Assert.deepEqual(FormAutofillContent.getInputDetails(input),
+ testcase.expectedResult[i].input,
+ "Check if returned input information is correct.");
+
+ Assert.deepEqual(FormAutofillContent.getFormDetails(input),
+ testcase.expectedResult[i].form,
+ "Check if returned form information is correct.");
+ }
+ });
+});