Bug 1390757 - Introduce pref extensions.formautofill.creditCards.enabled to form autofill activation process. r=lchang draft
authorRay Lin <ralin@mozilla.com>
Thu, 24 Aug 2017 15:38:37 +0800
changeset 651969 ba4f4f8676f228213d314f6935b3ee958cda164c
parent 651826 d1c70c20e7b52f7295411343e4dc5db8ee7c92b9
child 727949 4392a1d5bc4bca3ec761b733bc0e417f983ecad9
push id75905
push userbmo:ralin@mozilla.com
push dateThu, 24 Aug 2017 09:24:18 +0000
reviewerslchang
bugs1390757
milestone57.0a1
Bug 1390757 - Introduce pref extensions.formautofill.creditCards.enabled to form autofill activation process. r=lchang MozReview-Commit-ID: J5nyAeSoggr
browser/extensions/formautofill/FormAutofillContent.jsm
browser/extensions/formautofill/FormAutofillParent.jsm
browser/extensions/formautofill/FormAutofillPreferences.jsm
browser/extensions/formautofill/FormAutofillUtils.jsm
browser/extensions/formautofill/content/FormAutofillFrameScript.js
browser/extensions/formautofill/test/mochitest/test_basic_autocomplete_form.html
browser/extensions/formautofill/test/unit/test_activeStatus.js
--- a/browser/extensions/formautofill/FormAutofillContent.jsm
+++ b/browser/extensions/formautofill/FormAutofillContent.jsm
@@ -27,16 +27,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://formautofill/FormAutofillHandler.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FormLikeFactory",
                                   "resource://gre/modules/FormLikeFactory.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "InsecurePasswordUtils",
                                   "resource://gre/modules/InsecurePasswordUtils.jsm");
 
 const formFillController = Cc["@mozilla.org/satchel/form-fill-controller;1"]
                              .getService(Ci.nsIFormFillController);
+const {ADDRESSES_COLLECTION_NAME, CREDITCARDS_COLLECTION_NAME} = FormAutofillUtils;
 
 // Register/unregister a constructor as a factory.
 function AutocompleteFactory() {}
 AutocompleteFactory.prototype = {
   register(targetConstructor) {
     let proto = targetConstructor.prototype;
     this._classID = proto.classID;
 
@@ -98,35 +99,39 @@ AutofillProfileAutoCompleteSearch.protot
     let savedFieldNames = FormAutofillContent.savedFieldNames;
 
     let focusedInput = formFillController.focusedInput;
     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 searchPermitted = isAddressField ?
+                          FormAutofillUtils.isAutofillAddressesEnabled :
+                          FormAutofillUtils.isAutofillCreditCardsEnabled;
 
     // Fallback to form-history if ...
+    //   - specified autofill feature is pref off.
     //   - no profile can fill the currently-focused input.
     //   - the current form has already been populated.
     //   - (address only) less than 3 inputs are covered by all saved fields in the storage.
-    if (!savedFieldNames.has(info.fieldName) || filledRecordGUID || (isAddressField &&
+    if (!searchPermitted || !savedFieldNames.has(info.fieldName) || filledRecordGUID || (isAddressField &&
         allFieldNames.filter(field => savedFieldNames.has(field)).length < FormAutofillUtils.AUTOFILL_FIELDS_THRESHOLD)) {
       let formHistory = Cc["@mozilla.org/autocomplete/search;1?name=form-history"]
                           .createInstance(Ci.nsIAutoCompleteSearch);
       formHistory.startSearch(searchString, searchParam, previousResult, {
         onSearchResult: (search, result) => {
           listener.onSearchResult(this, result);
           ProfileAutocomplete.setProfileAutoCompleteResult(result);
         },
       });
       return;
     }
 
-    let collectionName = isAddressField ? "addresses" : "creditCards";
+    let collectionName = isAddressField ? ADDRESSES_COLLECTION_NAME : CREDITCARDS_COLLECTION_NAME;
 
     this._getRecords({collectionName, info, searchString}).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);
 
@@ -336,22 +341,23 @@ var FormAutofillContent = {
   init() {
     FormAutofillUtils.defineLazyLogGetter(this, "FormAutofillContent");
 
     Services.cpmm.addMessageListener("FormAutofill:enabledStatus", this);
     Services.cpmm.addMessageListener("FormAutofill:savedFieldNames", this);
     Services.obs.addObserver(this, "earlyformsubmit");
 
     let autofillEnabled = Services.cpmm.initialProcessData.autofillEnabled;
-    if (autofillEnabled ||
-        // If storage hasn't be initialized yet autofillEnabled is undefined but we need to ensure
-        // autocomplete is registered before the focusin so register it in this case as long as the
-        // pref is true.
-        (autofillEnabled === undefined &&
-         Services.prefs.getBoolPref("extensions.formautofill.addresses.enabled"))) {
+    // If storage hasn't be initialized yet autofillEnabled is undefined but we need to ensure
+    // autocomplete is registered before the focusin so register it in this case as long as the
+    // pref is true.
+    let shouldEnableAutofill = autofillEnabled === undefined &&
+                               (FormAutofillUtils.isAutofillAddressesEnabled ||
+                               FormAutofillUtils.isAutofillCreditCardsEnabled);
+    if (autofillEnabled || shouldEnableAutofill) {
       ProfileAutocomplete.ensureRegistered();
     }
 
     this.savedFieldNames =
       Services.cpmm.initialProcessData.autofillSavedFieldNames;
   },
 
   /**
--- a/browser/extensions/formautofill/FormAutofillParent.jsm
+++ b/browser/extensions/formautofill/FormAutofillParent.jsm
@@ -45,17 +45,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "MasterPassword",
                                   "resource://formautofill/MasterPassword.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
                                   "resource:///modules/RecentWindow.jsm");
 
 this.log = null;
 FormAutofillUtils.defineLazyLogGetter(this, this.EXPORTED_SYMBOLS[0]);
 
-const ENABLED_PREF = "extensions.formautofill.addresses.enabled";
+const {ENABLED_AUTOFILL_ADDRESSES_PREF, ENABLED_AUTOFILL_CREDITCARDS_PREF} = FormAutofillUtils;
 
 function FormAutofillParent() {
   // Lazily load the storage JSM to avoid disk I/O until absolutely needed.
   // Once storage is loaded we need to update saved field names and inform content processes.
   XPCOMUtils.defineLazyGetter(this, "profileStorage", () => {
     let {profileStorage} = Cu.import("resource://formautofill/ProfileStorage.jsm", {});
     log.debug("Loading profileStorage");
 
@@ -86,17 +86,18 @@ FormAutofillParent.prototype = {
     Services.ppmm.addMessageListener("FormAutofill:SaveAddress", this);
     Services.ppmm.addMessageListener("FormAutofill:SaveCreditCard", this);
     Services.ppmm.addMessageListener("FormAutofill:RemoveAddresses", this);
     Services.ppmm.addMessageListener("FormAutofill:RemoveCreditCards", this);
     Services.ppmm.addMessageListener("FormAutofill:OpenPreferences", this);
     Services.mm.addMessageListener("FormAutofill:OnFormSubmit", this);
 
     // Observing the pref and storage changes
-    Services.prefs.addObserver(ENABLED_PREF, this);
+    Services.prefs.addObserver(ENABLED_AUTOFILL_ADDRESSES_PREF, this);
+    Services.prefs.addObserver(ENABLED_AUTOFILL_CREDITCARDS_PREF, this);
     Services.obs.addObserver(this, "formautofill-storage-changed");
   },
 
   observe(subject, topic, data) {
     log.debug("observe:", topic, "with data:", data);
     switch (topic) {
       case "advanced-pane-loaded": {
         let useOldOrganization = Services.prefs.getBoolPref("browser.preferences.useOldOrganization",
@@ -149,21 +150,22 @@ FormAutofillParent.prototype = {
 
   /**
    * Query preference and storage status to determine the overall status of the
    * form autofill feature.
    *
    * @returns {boolean} whether form autofill is active (enabled and has data)
    */
   _computeStatus() {
-    if (!Services.prefs.getBoolPref(ENABLED_PREF)) {
-      return false;
-    }
+    const savedFieldNames = Services.ppmm.initialProcessData.autofillSavedFieldNames;
 
-    return Services.ppmm.initialProcessData.autofillSavedFieldNames.size > 0;
+    return (Services.prefs.getBoolPref(ENABLED_AUTOFILL_ADDRESSES_PREF) ||
+           Services.prefs.getBoolPref(ENABLED_AUTOFILL_CREDITCARDS_PREF)) &&
+           savedFieldNames &&
+           savedFieldNames.size > 0;
   },
 
   /**
    * Update the status and trigger _onStatusChanged, if necessary.
    */
   _updateStatus() {
     let wasActive = this._active;
     this._active = this._computeStatus();
@@ -231,17 +233,18 @@ FormAutofillParent.prototype = {
 
     Services.ppmm.removeMessageListener("FormAutofill:InitStorage", this);
     Services.ppmm.removeMessageListener("FormAutofill:GetRecords", this);
     Services.ppmm.removeMessageListener("FormAutofill:SaveAddress", this);
     Services.ppmm.removeMessageListener("FormAutofill:SaveCreditCard", this);
     Services.ppmm.removeMessageListener("FormAutofill:RemoveAddresses", this);
     Services.ppmm.removeMessageListener("FormAutofill:RemoveCreditCards", this);
     Services.obs.removeObserver(this, "advanced-pane-loaded");
-    Services.prefs.removeObserver(ENABLED_PREF, this);
+    Services.prefs.removeObserver(ENABLED_AUTOFILL_ADDRESSES_PREF, this);
+    Services.prefs.removeObserver(ENABLED_AUTOFILL_CREDITCARDS_PREF, this);
   },
 
   /**
    * Get the records from profile store and return results back to content
    * process.
    *
    * @private
    * @param  {string} data.collectionName
--- a/browser/extensions/formautofill/FormAutofillPreferences.jsm
+++ b/browser/extensions/formautofill/FormAutofillPreferences.jsm
@@ -8,48 +8,40 @@
 
 "use strict";
 
 this.EXPORTED_SYMBOLS = ["FormAutofillPreferences"];
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 // Add addresses enabled flag in telemetry environment for recording the number of
 // users who disable/enable the address autofill feature.
-const PREF_AUTOFILL_ENABLED = "extensions.formautofill.addresses.enabled";
-// Add credit card enabled flag in telemetry environment for recording the number of
-// users who disable/enable the credit card autofill feature.
-// TODO: Add const PREF_CREDITCARD_ENABLED = "extensions.formautofill.creditCards.enabled";
-//       when the credit card preferences UI is ready
 const BUNDLE_URI = "chrome://formautofill/locale/formautofill.properties";
 const MANAGE_ADDRESSES_URL = "chrome://formautofill/content/manageAddresses.xhtml";
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://formautofill/FormAutofillUtils.jsm");
 
+const {ENABLED_AUTOFILL_ADDRESSES_PREF} = FormAutofillUtils;
+// Add credit card enabled flag in telemetry environment for recording the number of
+// users who disable/enable the credit card autofill feature.
+// TODO: Add const PREF_CREDITCARD_ENABLED = "extensions.formautofill.creditCards.enabled";
+//       when the credit card preferences UI is ready
+
 this.log = null;
 FormAutofillUtils.defineLazyLogGetter(this, this.EXPORTED_SYMBOLS[0]);
 
 function FormAutofillPreferences({useOldOrganization}) {
   this.useOldOrganization = useOldOrganization;
   this.bundle = Services.strings.createBundle(BUNDLE_URI);
 }
 
 FormAutofillPreferences.prototype = {
   /**
-   * Check if Form Autofill feature is enabled.
-   *
-   * @returns {boolean}
-   */
-  get isAutofillEnabled() {
-    return Services.prefs.getBoolPref(PREF_AUTOFILL_ENABLED);
-  },
-
-  /**
    * Create the Form Autofill preference group.
    *
    * @param   {XULDocument} document
    * @returns {XULElement}
    */
   init(document) {
     this.createPreferenceGroup(document);
     this.attachEventListeners();
@@ -99,17 +91,17 @@ FormAutofillPreferences.prototype = {
     };
 
     formAutofillGroup.id = "formAutofillGroup";
     addressAutofill.id = "addressAutofill";
     savedAddressesBtn.setAttribute("label", this.bundle.GetStringFromName("savedAddresses"));
     addressAutofillCheckbox.setAttribute("label", this.bundle.GetStringFromName("enableAddressAutofill"));
 
     // Manually set the checked state
-    if (this.isAutofillEnabled) {
+    if (FormAutofillUtils.isAutofillAddressesEnabled) {
       addressAutofillCheckbox.setAttribute("checked", true);
     }
 
     addressAutofillCheckbox.flex = 1;
 
     formAutofillGroup.appendChild(addressAutofill);
     addressAutofill.appendChild(addressAutofillCheckbox);
     addressAutofill.appendChild(savedAddressesBtn);
@@ -122,17 +114,17 @@ FormAutofillPreferences.prototype = {
    */
   handleEvent(event) {
     switch (event.type) {
       case "command": {
         let target = event.target;
 
         if (target == this.refs.addressAutofillCheckbox) {
           // Set preference directly instead of relying on <Preference>
-          Services.prefs.setBoolPref(PREF_AUTOFILL_ENABLED, target.checked);
+          Services.prefs.setBoolPref(ENABLED_AUTOFILL_ADDRESSES_PREF, target.checked);
         } else if (target == this.refs.savedAddressesBtn) {
           target.ownerGlobal.gSubDialog.open(MANAGE_ADDRESSES_URL);
         }
         break;
       }
     }
   },
 
--- a/browser/extensions/formautofill/FormAutofillUtils.jsm
+++ b/browser/extensions/formautofill/FormAutofillUtils.jsm
@@ -11,22 +11,32 @@ const {classes: Cc, interfaces: Ci, util
 const ADDRESS_REFERENCES = "chrome://formautofill/content/addressReferences.js";
 
 // TODO: We only support US in MVP. We are going to support more countries in
 //       bug 1370193.
 const ALTERNATIVE_COUNTRY_NAMES = {
   "US": ["US", "United States of America", "United States", "America", "U.S.", "USA", "U.S.A.", "U.S.A"],
 };
 
+const ADDRESSES_COLLECTION_NAME = "addresses";
+const CREDITCARDS_COLLECTION_NAME = "creditCards";
+const ENABLED_AUTOFILL_ADDRESSES_PREF = "extensions.formautofill.addresses.enabled";
+const ENABLED_AUTOFILL_CREDITCARDS_PREF = "extensions.formautofill.creditCards.enabled";
+
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 this.FormAutofillUtils = {
   get AUTOFILL_FIELDS_THRESHOLD() { return 3; },
 
+  ADDRESSES_COLLECTION_NAME,
+  CREDITCARDS_COLLECTION_NAME,
+  ENABLED_AUTOFILL_ADDRESSES_PREF,
+  ENABLED_AUTOFILL_CREDITCARDS_PREF,
+
   _fieldNameInfo: {
     "name": "name",
     "given-name": "name",
     "additional-name": "name",
     "family-name": "name",
     "organization": "organization",
     "street-address": "address",
     "address-line1": "address",
@@ -466,8 +476,13 @@ XPCOMUtils.defineLazyGetter(this.FormAut
 });
 
 this.log = null;
 this.FormAutofillUtils.defineLazyLogGetter(this, this.EXPORTED_SYMBOLS[0]);
 
 XPCOMUtils.defineLazyGetter(FormAutofillUtils, "stringBundle", function() {
   return Services.strings.createBundle("chrome://formautofill/locale/formautofill.properties");
 });
+
+XPCOMUtils.defineLazyPreferenceGetter(this.FormAutofillUtils,
+                                      "isAutofillAddressesEnabled", ENABLED_AUTOFILL_ADDRESSES_PREF);
+XPCOMUtils.defineLazyPreferenceGetter(this.FormAutofillUtils,
+                                      "isAutofillCreditCardsEnabled", ENABLED_AUTOFILL_CREDITCARDS_PREF);
--- a/browser/extensions/formautofill/content/FormAutofillFrameScript.js
+++ b/browser/extensions/formautofill/content/FormAutofillFrameScript.js
@@ -30,17 +30,18 @@ var FormAutofillFrameScript = {
     addMessageListener("FormAutoComplete:PopupOpened", this);
   },
 
   handleEvent(evt) {
     if (!evt.isTrusted) {
       return;
     }
 
-    if (!Services.prefs.getBoolPref("extensions.formautofill.addresses.enabled")) {
+    if (!FormAutofillUtils.isAutofillAddressesEnabled &&
+        !FormAutofillUtils.isAutofillCreditCardsEnable) {
       return;
     }
 
     switch (evt.type) {
       case "focusin": {
         let element = evt.target;
         let doc = element.ownerDocument;
 
@@ -57,17 +58,18 @@ var FormAutofillFrameScript = {
           doIdentifyAutofillFields();
         }
         break;
       }
     }
   },
 
   receiveMessage(message) {
-    if (!Services.prefs.getBoolPref("extensions.formautofill.addresses.enabled")) {
+    if (!FormAutofillUtils.isAutofillAddressesEnabled &&
+        !FormAutofillUtils.isAutofillCreditCardsEnable) {
       return;
     }
 
     const doc = content.document;
     const {chromeEventHandler} = doc.ownerGlobal.getInterface(Ci.nsIDocShell);
 
     switch (message.name) {
       case "FormAutofill:PreviewProfile": {
--- a/browser/extensions/formautofill/test/mochitest/test_basic_autocomplete_form.html
+++ b/browser/extensions/formautofill/test/mochitest/test_basic_autocomplete_form.html
@@ -80,16 +80,17 @@ function checkFormFilled(address) {
   return Promise.all(promises);
 }
 
 async function setupAddressStorage() {
   await addAddress(MOCK_STORAGE[0]);
   await addAddress(MOCK_STORAGE[1]);
 }
 
+// TODO: Add form history fallback test cases for credit card fields. (bug 1393374)
 async function setupFormHistory() {
   await updateFormHistory([
     {op: "add", fieldname: "tel", value: "+1234567890"},
     {op: "add", fieldname: "email", value: "foo@mozilla.com"},
   ]);
 }
 
 initPopupListener();
@@ -167,16 +168,30 @@ add_task(async function check_menu_when_
 // Display history search result if no matched data in addresses.
 add_task(async function check_fallback_for_mismatched_field() {
   await setInput("#email", "");
   doKey("down");
   await expectPopup();
   checkMenuEntries(["foo@mozilla.com"], false);
 });
 
+// Display history search result if address autofill is disabled.
+add_task(async function check_search_result_for_pref_off() {
+  await SpecialPowers.pushPrefEnv({
+    set: [["extensions.formautofill.addresses.enabled", false]],
+  });
+
+  await setInput("#tel", "");
+  doKey("down");
+  await expectPopup();
+  checkMenuEntries(["+1234567890"], false);
+
+  await SpecialPowers.popPrefEnv();
+});
+
 // Autofill the address from dropdown menu.
 add_task(async function check_fields_after_form_autofill() {
   await setInput("#organization", "Moz");
   doKey("down");
   await expectPopup();
   checkMenuEntries(MOCK_STORAGE.map(address =>
     JSON.stringify({
       primary: address.organization,
--- a/browser/extensions/formautofill/test/unit/test_activeStatus.js
+++ b/browser/extensions/formautofill/test/unit/test_activeStatus.js
@@ -34,22 +34,24 @@ add_task(async function test_activeStatu
   let formAutofillParent = new FormAutofillParent();
   sinon.stub(formAutofillParent, "_computeStatus");
   sinon.spy(formAutofillParent, "_onStatusChanged");
 
   // _active = _computeStatus() => No need to trigger _onStatusChanged
   formAutofillParent._active = true;
   formAutofillParent._computeStatus.returns(true);
   formAutofillParent.observe(null, "nsPref:changed", "extensions.formautofill.addresses.enabled");
+  formAutofillParent.observe(null, "nsPref:changed", "extensions.formautofill.creditCards.enabled");
   do_check_eq(formAutofillParent._onStatusChanged.called, false);
 
   // _active != _computeStatus() => Need to trigger _onStatusChanged
   formAutofillParent._computeStatus.returns(false);
   formAutofillParent._onStatusChanged.reset();
   formAutofillParent.observe(null, "nsPref:changed", "extensions.formautofill.addresses.enabled");
+  formAutofillParent.observe(null, "nsPref:changed", "extensions.formautofill.creditCards.enabled");
   do_check_eq(formAutofillParent._onStatusChanged.called, true);
 
   // profile changed => Need to trigger _onStatusChanged
   ["add", "update", "remove", "reconcile", "merge"].forEach(event => {
     formAutofillParent._computeStatus.returns(!formAutofillParent._active);
     formAutofillParent._onStatusChanged.reset();
     formAutofillParent.observe(null, "formautofill-storage-changed", event);
     do_check_eq(formAutofillParent._onStatusChanged.called, true);
@@ -61,31 +63,45 @@ add_task(async function test_activeStatu
   formAutofillParent.observe(null, "formautofill-storage-changed", "notifyUsed");
   do_check_eq(formAutofillParent._onStatusChanged.called, false);
 });
 
 add_task(async function test_activeStatus_computeStatus() {
   let formAutofillParent = new FormAutofillParent();
   do_register_cleanup(function cleanup() {
     Services.prefs.clearUserPref("extensions.formautofill.addresses.enabled");
+    Services.prefs.clearUserPref("extensions.formautofill.creditCards.enabled");
   });
 
   sinon.stub(profileStorage.addresses, "getAll");
   profileStorage.addresses.getAll.returns([]);
 
   // pref is enabled and profile is empty.
   Services.prefs.setBoolPref("extensions.formautofill.addresses.enabled", true);
+  Services.prefs.setBoolPref("extensions.formautofill.creditCards.enabled", true);
   do_check_eq(formAutofillParent._computeStatus(), false);
 
   // pref is disabled and profile is empty.
   Services.prefs.setBoolPref("extensions.formautofill.addresses.enabled", false);
+  Services.prefs.setBoolPref("extensions.formautofill.creditCards.enabled", false);
   do_check_eq(formAutofillParent._computeStatus(), false);
 
   profileStorage.addresses.getAll.returns([{"given-name": "John"}]);
   formAutofillParent.observe(null, "formautofill-storage-changed", "add");
   // pref is enabled and profile is not empty.
   Services.prefs.setBoolPref("extensions.formautofill.addresses.enabled", true);
+  Services.prefs.setBoolPref("extensions.formautofill.addresses.enabled", true);
   do_check_eq(formAutofillParent._computeStatus(), true);
 
+  // pref is partial enabled and profile is not empty.
+  Services.prefs.setBoolPref("extensions.formautofill.addresses.enabled", true);
+  Services.prefs.setBoolPref("extensions.formautofill.creditCards.enabled", false);
+  do_check_eq(formAutofillParent._computeStatus(), true);
+  Services.prefs.setBoolPref("extensions.formautofill.addresses.enabled", false);
+  Services.prefs.setBoolPref("extensions.formautofill.creditCards.enabled", true);
+  do_check_eq(formAutofillParent._computeStatus(), true);
+
+
   // pref is disabled and profile is not empty.
   Services.prefs.setBoolPref("extensions.formautofill.addresses.enabled", false);
+  Services.prefs.setBoolPref("extensions.formautofill.creditCards.enabled", false);
   do_check_eq(formAutofillParent._computeStatus(), false);
 });