Bug 1398101 - Allow the FormAutofill fields with autocomplete="off" to proceed `startSearch`. r=MattN draft
authorSean Lee <selee@mozilla.com>
Mon, 11 Sep 2017 12:11:12 +0800
changeset 678954 1d476384f375c7b4b62945029dd124549e33693c
parent 678367 acfdf5d0d1f33f0550d4f566adb1084d9c724b41
child 678955 cc0b2fe4d45a3647c6151ff99578cd63406623d7
push id84082
push userbmo:selee@mozilla.com
push dateThu, 12 Oct 2017 03:34:15 +0000
reviewersMattN
bugs1398101
milestone58.0a1
Bug 1398101 - Allow the FormAutofill fields with autocomplete="off" to proceed `startSearch`. r=MattN MozReview-Commit-ID: 6pZUPY1Fray
browser/extensions/formautofill/FormAutofillContent.jsm
browser/extensions/formautofill/test/mochitest/formautofill_common.js
browser/extensions/formautofill/test/mochitest/mochitest.ini
browser/extensions/formautofill/test/mochitest/test_basic_autocomplete_form.html
browser/extensions/formautofill/test/mochitest/test_basic_creditcard_autocomplete_form.html
browser/extensions/formautofill/test/mochitest/test_creditcard_autocomplete_off.html
toolkit/components/satchel/nsFormFillController.cpp
--- a/browser/extensions/formautofill/FormAutofillContent.jsm
+++ b/browser/extensions/formautofill/FormAutofillContent.jsm
@@ -110,16 +110,22 @@ AutofillProfileAutoCompleteSearch.protot
 
     // 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 (!searchPermitted || !savedFieldNames.has(info.fieldName) || filledRecordGUID || (isAddressField &&
         allFieldNames.filter(field => savedFieldNames.has(field)).length < FormAutofillUtils.AUTOFILL_FIELDS_THRESHOLD)) {
+      if (focusedInput.autocomplete == "off") {
+        // Create a dummy AddressResult as an empty search result.
+        let result = new AddressResult("", "", [], [], {});
+        listener.onSearchResult(this, result);
+        return;
+      }
       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);
         },
       });
--- a/browser/extensions/formautofill/test/mochitest/formautofill_common.js
+++ b/browser/extensions/formautofill/test/mochitest/formautofill_common.js
@@ -2,16 +2,18 @@
 /* import-globals-from ../../../../../toolkit/components/satchel/test/satchel_common.js */
 /* eslint-disable no-unused-vars */
 
 "use strict";
 
 let formFillChromeScript;
 let expectingPopup = null;
 
+const {FormAutofillUtils} = SpecialPowers.Cu.import("resource://formautofill/FormAutofillUtils.jsm");
+
 async function sleep(ms = 500, reason = "Intentionally wait for UI ready") {
   SimpleTest.requestFlakyTimeout(reason);
   await new Promise(resolve => setTimeout(resolve, ms));
 }
 
 async function setInput(selector, value) {
   let input = document.querySelector("input" + selector);
   input.value = value;
@@ -107,26 +109,44 @@ async function cleanUpCreditCards() {
   return invokeAsyncChromeTask("FormAutofillTest:CleanUpCreditCards", "FormAutofillTest:CreditCardsCleanedUp");
 }
 
 async function cleanUpStorage() {
   await cleanUpAddresses();
   await cleanUpCreditCards();
 }
 
+function patchRecordCCNumber(record) {
+  const ccNumber = record["cc-number"];
+  const normalizedCCNumber = "*".repeat(ccNumber.length - 4) + ccNumber.substr(-4);
+  const ccNumberFmt = FormAutofillUtils.fmtMaskedCreditCardLabel(normalizedCCNumber);
+
+  return Object.assign({}, record, {ccNumberFmt});
+}
+
 // Utils for registerPopupShownListener(in satchel_common.js) that handles dropdown popup
 // Please call "initPopupListener()" in your test and "await expectPopup()"
 // if you want to wait for dropdown menu displayed.
 function expectPopup() {
   info("expecting a popup");
   return new Promise(resolve => {
     expectingPopup = resolve;
   });
 }
 
+function notExpectPopup(ms = 500) {
+  info("not expecting a popup");
+  return new Promise((resolve, reject) => {
+    expectingPopup = reject.bind(this, "Unexpected Popup");
+    // TODO: We don't have an event to notify no popup showing, so wait for 500
+    // ms (in default) to predict any unexpected popup showing.
+    setTimeout(resolve, ms);
+  });
+}
+
 function popupShownListener() {
   info("popup shown for test ");
   if (expectingPopup) {
     expectingPopup();
     expectingPopup = null;
   }
 }
 
--- a/browser/extensions/formautofill/test/mochitest/mochitest.ini
+++ b/browser/extensions/formautofill/test/mochitest/mochitest.ini
@@ -6,11 +6,13 @@ support-files =
   ../../../../../toolkit/components/satchel/test/parent_utils.js
   formautofill_common.js
   formautofill_parent_utils.js
 
 [test_autofocus_form.html]
 [test_basic_autocomplete_form.html]
 [test_basic_creditcard_autocomplete_form.html]
 scheme=https
+[test_creditcard_autocomplete_off.html]
+scheme=https
 [test_formautofill_preview_highlight.html]
 [test_multiple_forms.html]
 [test_on_address_submission.html]
--- a/browser/extensions/formautofill/test/mochitest/test_basic_autocomplete_form.html
+++ b/browser/extensions/formautofill/test/mochitest/test_basic_autocomplete_form.html
@@ -14,18 +14,16 @@ Form autofill test: simple form address 
 
 <script>
 /* import-globals-from ../../../../../testing/mochitest/tests/SimpleTest/SpawnTask.js */
 /* import-globals-from ../../../../../toolkit/components/satchel/test/satchel_common.js */
 /* import-globals-from formautofill_common.js */
 
 "use strict";
 
-const {FormAutofillUtils} = SpecialPowers.Cu.import("resource://formautofill/FormAutofillUtils.jsm");
-
 let MOCK_STORAGE = [{
   organization: "Sesame Street",
   "street-address": "123 Sesame Street.\n2-line\n3-line",
   tel: "+13453453456",
   country: "US",
   "address-level1": "NY",
 }, {
   organization: "Mozilla",
--- a/browser/extensions/formautofill/test/mochitest/test_basic_creditcard_autocomplete_form.html
+++ b/browser/extensions/formautofill/test/mochitest/test_basic_creditcard_autocomplete_form.html
@@ -14,18 +14,16 @@ Form autofill test: simple form credit c
 
 <script>
 /* import-globals-from ../../../../../testing/mochitest/tests/SimpleTest/SpawnTask.js */
 /* import-globals-from ../../../../../toolkit/components/satchel/test/satchel_common.js */
 /* import-globals-from formautofill_common.js */
 
 "use strict";
 
-const {FormAutofillUtils} = SpecialPowers.Cu.import("resource://formautofill/FormAutofillUtils.jsm");
-
 const MOCK_STORAGE = [{
   "cc-name": "John Doe",
   "cc-number": "1234567812345678",
   "cc-exp-month": 4,
   "cc-exp-year": 2017,
 }, {
   "cc-name": "Timothy Berners-Lee",
   "cc-number": "1111222233334444",
@@ -33,24 +31,16 @@ const MOCK_STORAGE = [{
   "cc-exp-year": 2022,
 }];
 
 const reducedMockRecord = {
   "cc-name": "John Doe",
   "cc-number": "1234123456785678",
 };
 
-function patchRecordCCNumber(record) {
-  const ccNumber = record["cc-number"];
-  const normalizedCCNumber = "*".repeat(ccNumber.length - 4) + ccNumber.substr(-4);
-  const ccNumberFmt = FormAutofillUtils.fmtMaskedCreditCardLabel(normalizedCCNumber);
-
-  return Object.assign({}, record, {ccNumberFmt});
-}
-
 function checkElementFilled(element, expectedvalue) {
   const focusedElem = document.activeElement;
   const promises = [];
 
   promises.push(new Promise(resolve => {
     element.addEventListener("input", function onInput() {
       ok(true, "Checking " + element.name + " field fires input event");
       resolve();
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/test/mochitest/test_creditcard_autocomplete_off.html
@@ -0,0 +1,98 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test basic autofill</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+  <script type="text/javascript" src="formautofill_common.js"></script>
+  <script type="text/javascript" src="satchel_common.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+Form autofill test: simple form credit card autofill
+
+<script>
+/* import-globals-from ../../../../../testing/mochitest/tests/SimpleTest/SpawnTask.js */
+/* import-globals-from ../../../../../toolkit/components/satchel/test/satchel_common.js */
+/* import-globals-from formautofill_common.js */
+
+"use strict";
+
+const MOCK_STORAGE = [{
+  "cc-name": "John Doe",
+  "cc-number": "1234567812345678",
+  "cc-exp-month": 4,
+  "cc-exp-year": 2017,
+}, {
+  "cc-name": "Timothy Berners-Lee",
+  "cc-number": "1111222233334444",
+  "cc-exp-month": 12,
+  "cc-exp-year": 2022,
+}];
+
+async function setupCreditCardStorage() {
+  await addCreditCard(MOCK_STORAGE[0]);
+  await addCreditCard(MOCK_STORAGE[1]);
+}
+
+async function setupFormHistory() {
+  await updateFormHistory([
+    {op: "add", fieldname: "cc-name", value: "John Smith"},
+    {op: "add", fieldname: "cc-number", value: "1234000056780000"},
+  ]);
+}
+
+initPopupListener();
+
+// Show Form History popup for non-autocomplete="off" field only
+add_task(async function history_only_menu_checking() {
+  await setupFormHistory();
+
+  await setInput("#cc-number", "");
+  doKey("down");
+  await expectPopup();
+  checkMenuEntries(["1234000056780000"], false);
+
+  await setInput("#cc-name", "");
+  doKey("down");
+  await notExpectPopup();
+});
+
+// Show Form Autofill popup for the credit card fields.
+add_task(async function check_menu_when_both_with_autocomplete_off() {
+  await setupCreditCardStorage();
+
+  await setInput("#cc-number", "");
+  doKey("down");
+  await expectPopup();
+  checkMenuEntries(MOCK_STORAGE.map(patchRecordCCNumber).map(cc => JSON.stringify({
+    primaryAffix: cc.ccNumberFmt.affix,
+    primary: cc.ccNumberFmt.label,
+    secondary: cc["cc-name"],
+  })));
+
+  await setInput("#cc-name", "");
+  doKey("down");
+  await expectPopup();
+  checkMenuEntries(MOCK_STORAGE.map(patchRecordCCNumber).map(cc => JSON.stringify({
+    primary: cc["cc-name"],
+    secondary: cc.ccNumberFmt.affix + cc.ccNumberFmt.label,
+  })));
+});
+
+</script>
+
+<p id="display"></p>
+
+<div id="content">
+  <form id="form1">
+    <p>This is a Credit Card form with autocomplete="off" cc-name field.</p>
+    <p><label>Name: <input id="cc-name" autocomplete="off"></label></p>
+    <p><label>Card Number: <input id="cc-number" autocomplete="cc-number"></label></p>
+  </form>
+</div>
+
+<pre id="test"></pre>
+</body>
+</html>
--- a/toolkit/components/satchel/nsFormFillController.cpp
+++ b/toolkit/components/satchel/nsFormFillController.cpp
@@ -1062,17 +1062,22 @@ nsFormFillController::MaybeStartControll
   bool isPwmgrInput = false;
   nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(aInput);
   MOZ_ASSERT(formControl, "If we have a text control, we have a form control!");
   if (mPwmgrInputs.Get(inputNode) ||
       formControl->ControlType() == NS_FORM_INPUT_PASSWORD) {
     isPwmgrInput = true;
   }
 
-  if (isPwmgrInput || hasList || autocomplete) {
+  bool isAutofillInput = false;
+  if (mAutofillInputs.Get(inputNode)) {
+    isAutofillInput = true;
+  }
+
+  if (isAutofillInput || isPwmgrInput ||  hasList || autocomplete) {
     StartControllingInput(aInput);
   }
 }
 
 nsresult
 nsFormFillController::Focus(nsIDOMEvent* aEvent)
 {
   nsCOMPtr<nsIDOMHTMLInputElement> input = do_QueryInterface(