Bug 990219 - Part 1: Add earlyformsubmit observer and dispatch saveProfile message. r?MattN
MozReview-Commit-ID: 7cnFelRfb6f
--- a/browser/extensions/formautofill/FormAutofillContent.jsm
+++ b/browser/extensions/formautofill/FormAutofillContent.jsm
@@ -9,16 +9,17 @@
/* eslint-disable no-use-before-define */
"use strict";
this.EXPORTED_SYMBOLS = ["FormAutofillContent"];
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr, manager: Cm} = Components;
+Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://formautofill/FormAutofillUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ProfileAutoCompleteResult",
"resource://formautofill/ProfileAutoCompleteResult.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FormAutofillHandler",
"resource://formautofill/FormAutofillHandler.jsm");
@@ -308,30 +309,63 @@ var FormAutofillContent = {
if (Services.cpmm.initialProcessData.autofillEnabled) {
ProfileAutocomplete.ensureRegistered();
}
this.savedFieldNames =
Services.cpmm.initialProcessData.autofillSavedFieldNames || new Set();
},
- _onFormSubmit(handler) {
- // TODO: Handle form submit event for profile saving(bug 990219) and metrics(bug 1341569).
+ /**
+ * Send the profile to parent for doorhanger and storage saving/updating.
+ *
+ * @param {Object} profile Submitted form's address/creditcard guid and record.
+ */
+ _onFormSubmit(profile) {
+ Services.cpmm.sendAsyncMessage("FormAutofill:OnFormSubmit", profile);
},
- notify(formElement) {
- this.log.debug("notified for form early submission");
+ /**
+ * Handle earlyformsubmit event and early return when:
+ * 1. In private browsing mode.
+ * 2. Could not map any autofill handler by form element.
+ * 3. Number of filled fields is less than autofill threshold
+ *
+ * @param {HTMLElement} formElement Root element which receives earlyformsubmit event.
+ * @param {Object} domWin Content window
+ * @returns {boolean} Should always return true so form submission isn't canceled.
+ */
+ notify(formElement, domWin) {
+ this.log.debug("Notifying form early submission");
+
+ if (domWin && PrivateBrowsingUtils.isContentWindowPrivate(domWin)) {
+ this.log.debug("Ignoring submission in a private window");
+ return true;
+ }
let handler = this._formsDetails.get(formElement);
if (!handler) {
this.log.debug("Form element could not map to an existing handler");
- } else {
- this._onFormSubmit(handler);
+ return true;
+ }
+
+ let pendingAddress = handler.createProfile();
+ if (Object.keys(pendingAddress).length < AUTOFILL_FIELDS_THRESHOLD) {
+ this.log.debug(`Not saving since there are only ${Object.keys(pendingAddress).length} usable fields`);
+ return true;
}
+ this._onFormSubmit({
+ address: {
+ guid: handler.filledProfileGUID,
+ record: pendingAddress,
+ },
+ // creditCard: {}
+ });
+
return true;
},
receiveMessage({name, data}) {
switch (name) {
case "FormAutofill:enabledStatus": {
if (data) {
ProfileAutocomplete.ensureRegistered();
--- a/browser/extensions/formautofill/FormAutofillHandler.jsm
+++ b/browser/extensions/formautofill/FormAutofillHandler.jsm
@@ -128,9 +128,32 @@ FormAutofillHandler.prototype = {
// We keep the highlight of all fields if this form has
// already been auto-filled with a profile.
if (this.filledProfileGUID == null) {
// TODO: Remove highlight style
}
}
*/
},
+
+ /**
+ * Return the profile that is converted from fieldDetails and only non-empty fields
+ * are included.
+ *
+ * @returns {Object} The new profile that convert from details with trimmed result.
+ */
+ createProfile() {
+ let profile = {};
+
+ this.fieldDetails.forEach(detail => {
+ let element = detail.elementWeakRef.get();
+ // Remove the unnecessary spaces
+ let value = element && element.value.trim();
+ if (!value) {
+ return;
+ }
+
+ profile[detail.fieldName] = value;
+ });
+
+ return profile;
+ },
};
--- a/browser/extensions/formautofill/FormAutofillParent.jsm
+++ b/browser/extensions/formautofill/FormAutofillParent.jsm
@@ -67,16 +67,17 @@ FormAutofillParent.prototype = {
async init() {
log.debug("init");
await profileStorage.initialize();
Services.obs.addObserver(this, "advanced-pane-loaded");
Services.ppmm.addMessageListener("FormAutofill:GetAddresses", this);
Services.ppmm.addMessageListener("FormAutofill:SaveAddress", this);
Services.ppmm.addMessageListener("FormAutofill:RemoveAddresses", this);
+ Services.ppmm.addMessageListener("FormAutofill:OnFormSubmit", this);
// Observing the pref and storage changes
Services.prefs.addObserver(ENABLED_PREF, this);
Services.obs.addObserver(this, "formautofill-storage-changed");
// Force to trigger the onStatusChanged function for setting listeners properly
// while initizlization
this._setStatus(this._getStatus());
@@ -186,16 +187,19 @@ FormAutofillParent.prototype = {
profileStorage.addresses.add(data.address);
}
break;
}
case "FormAutofill:RemoveAddresses": {
data.guids.forEach(guid => profileStorage.addresses.remove(guid));
break;
}
+ case "FormAutofill:OnFormSubmit": {
+ this._onFormSubmit(data, target);
+ }
}
},
/**
* Uninitializes FormAutofillParent. This is for testing only.
*
* @private
*/
@@ -252,9 +256,22 @@ FormAutofillParent.prototype = {
// Remove the internal guid and metadata fields.
profileStorage.INTERNAL_FIELDS.forEach((fieldName) => {
Services.ppmm.initialProcessData.autofillSavedFieldNames.delete(fieldName);
});
Services.ppmm.broadcastAsyncMessage("FormAutofill:savedFieldNames",
Services.ppmm.initialProcessData.autofillSavedFieldNames);
},
+
+ _onFormSubmit(data, target) {
+ let {address} = data;
+
+ if (address.guid) {
+ // TODO: Show update doorhanger(bug 1303513) and set probe(bug 990200)
+ // if (!profileStorage.addresses.mergeIfPossible(address.guid, address.record)) {
+ // }
+ } else {
+ // TODO: Add first time use probe(bug 990199) and doorhanger(bug 1303510)
+ profileStorage.addresses.add(address.record);
+ }
+ },
};
--- a/browser/extensions/formautofill/test/unit/test_onFormSubmitted.js
+++ b/browser/extensions/formautofill/test/unit/test_onFormSubmitted.js
@@ -7,24 +7,118 @@ const MOCK_DOC = MockDocument.createTest
<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">
<input id="submit" type="submit">
</form>`);
-add_task(function* () {
- do_print("Starting testcase: Make sure content handle earlyformsubmit correctly");
+const TESTCASES = [
+ {
+ description: "Should not trigger saving if the number of fields is less than 3",
+ formValue: {
+ "street-addr": "331 E. Evelyn Avenue",
+ "tel": "1-650-903-0800",
+ },
+ expectedResult: {
+ formSubmission: false,
+ },
+ },
+ {
+ description: "Trigger profile saving",
+ formValue: {
+ "street-addr": "331 E. Evelyn Avenue",
+ "country": "USA",
+ "tel": "1-650-903-0800",
+ },
+ expectedResult: {
+ formSubmission: true,
+ profile: {
+ address: {
+ guid: null,
+ record: {
+ "street-address": "331 E. Evelyn Avenue",
+ "country": "USA",
+ "tel": "1-650-903-0800",
+ },
+ },
+ },
+ },
+ },
+ {
+ description: "Profile saved with trimmed string",
+ formValue: {
+ "street-addr": "331 E. Evelyn Avenue ",
+ "country": "USA",
+ "tel": " 1-650-903-0800",
+ },
+ expectedResult: {
+ formSubmission: true,
+ profile: {
+ address: {
+ guid: null,
+ record: {
+ "street-address": "331 E. Evelyn Avenue",
+ "country": "USA",
+ "tel": "1-650-903-0800",
+ },
+ },
+ },
+ },
+ },
+ {
+ description: "Eliminate the field that is empty after trimmed",
+ formValue: {
+ "street-addr": "331 E. Evelyn Avenue",
+ "country": "USA",
+ "email": " ",
+ "tel": "1-650-903-0800",
+ },
+ expectedResult: {
+ formSubmission: true,
+ profile: {
+ address: {
+ guid: null,
+ record: {
+ "street-address": "331 E. Evelyn Avenue",
+ "country": "USA",
+ "tel": "1-650-903-0800",
+ },
+ },
+ },
+ },
+ },
+];
- let form = MOCK_DOC.getElementById("form1");
- FormAutofillContent.identifyAutofillFields(MOCK_DOC);
+add_task(async function handle_earlyformsubmit_event() {
+ do_print("Starting testcase: Test an invalid form element");
+ let fakeForm = MOCK_DOC.createElement("form");
sinon.spy(FormAutofillContent, "_onFormSubmit");
- do_check_eq(FormAutofillContent.notify(form), true);
- do_check_eq(FormAutofillContent._onFormSubmit.called, true);
-
- let fakeForm = MOCK_DOC.createElement("form");
- FormAutofillContent._onFormSubmit.reset();
-
do_check_eq(FormAutofillContent.notify(fakeForm), true);
do_check_eq(FormAutofillContent._onFormSubmit.called, false);
+ FormAutofillContent._onFormSubmit.restore();
});
+
+TESTCASES.forEach(testcase => {
+ add_task(async function check_profile_saving_is_called_correctly() {
+ do_print("Starting testcase: " + testcase.description);
+
+ let form = MOCK_DOC.getElementById("form1");
+ for (let key in testcase.formValue) {
+ let input = MOCK_DOC.getElementById(key);
+ input.value = testcase.formValue[key];
+ }
+ sinon.spy(FormAutofillContent, "_onFormSubmit");
+
+ FormAutofillContent.identifyAutofillFields(MOCK_DOC);
+ FormAutofillContent.notify(form);
+
+ do_check_eq(FormAutofillContent._onFormSubmit.called,
+ testcase.expectedResult.formSubmission);
+ if (FormAutofillContent._onFormSubmit.called) {
+ Assert.deepEqual(FormAutofillContent._onFormSubmit.args[0][0],
+ testcase.expectedResult.profile);
+ }
+ FormAutofillContent._onFormSubmit.restore();
+ });
+});