Bug 1398101 - Allow the FormAutofill fields with autocomplete="off" to proceed `startSearch`. r=MattN
authorSean Lee <selee@mozilla.com>
Mon, 11 Sep 2017 12:11:12 +0800
changeset 385836 50e7e1b822ec8601c01f20b1b69a5a2359d1ec75
parent 385835 122829e5d814262274b43cf62894fe5e1433cc6f
child 385837 f2808379c0282d2d69da2353fc89f23346294de2
push id53166
push userryanvm@gmail.com
push dateThu, 12 Oct 2017 13:52:11 +0000
treeherderautoland@f2808379c028 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMattN
bugs1398101
milestone58.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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(