Bug 1364818 - [Form Autofill] popup won't apply to an auto-focused input until it's refocused. r=MattN draft
authorLuke Chang <lchang@mozilla.com>
Mon, 15 May 2017 13:26:25 +0800
changeset 589520 2cb0d4294ce31aa7e2e0e68b15b03e554e07d729
parent 589519 58ce95bc58ce4ba200413c8bed87786dccf3d105
child 631910 bcacfbba623165b5c004a41f3e7f7a782a47814b
push id62404
push userbmo:lchang@mozilla.com
push dateTue, 06 Jun 2017 09:52:18 +0000
reviewersMattN
bugs1364818
milestone55.0a1
Bug 1364818 - [Form Autofill] popup won't apply to an auto-focused input until it's refocused. r=MattN MozReview-Commit-ID: H3CZEFzAJm6
browser/extensions/formautofill/content/FormAutofillFrameScript.js
browser/extensions/formautofill/test/mochitest/formautofill_common.js
browser/extensions/formautofill/test/mochitest/formautofill_parent_utils.js
browser/extensions/formautofill/test/mochitest/mochitest.ini
browser/extensions/formautofill/test/mochitest/test_autofocus_form.html
browser/extensions/formautofill/test/mochitest/test_basic_autocomplete_form.html
--- a/browser/extensions/formautofill/content/FormAutofillFrameScript.js
+++ b/browser/extensions/formautofill/content/FormAutofillFrameScript.js
@@ -36,20 +36,30 @@ var FormAutofillFrameScript = {
 
     if (!Services.prefs.getBoolPref("extensions.formautofill.addresses.enabled")) {
       return;
     }
 
     switch (evt.type) {
       case "focusin": {
         let element = evt.target;
+        let doc = element.ownerDocument;
+
         if (!FormAutofillUtils.isFieldEligibleForAutofill(element)) {
           return;
         }
-        FormAutofillContent.identifyAutofillFields(element.ownerDocument);
+
+        let doIdentifyAutofillFields =
+          () => setTimeout(() => FormAutofillContent.identifyAutofillFields(doc));
+
+        if (doc.readyState === "loading") {
+          doc.addEventListener("DOMContentLoaded", doIdentifyAutofillFields, {once: true});
+        } else {
+          doIdentifyAutofillFields();
+        }
         break;
       }
     }
   },
 
   receiveMessage(message) {
     if (!Services.prefs.getBoolPref("extensions.formautofill.addresses.enabled")) {
       return;
--- a/browser/extensions/formautofill/test/mochitest/formautofill_common.js
+++ b/browser/extensions/formautofill/test/mochitest/formautofill_common.js
@@ -5,16 +5,24 @@
 "use strict";
 
 let formFillChromeScript;
 
 function setInput(selector, value) {
   let input = document.querySelector("input" + selector);
   input.value = value;
   input.focus();
+
+  // "identifyAutofillFields" is invoked asynchronously in "focusin" event. We
+  // should make sure fields are ready for popup before doing tests.
+  //
+  // TODO: "setTimeout" is used here temporarily because there's no event to
+  //       notify us of the state of "identifyAutofillFields" for now. We should
+  //       figure out a better way after the heuristics land.
+  return new Promise(resolve => setTimeout(resolve));
 }
 
 function checkMenuEntries(expectedValues) {
   let actualValues = getMenuEntries();
 
   is(actualValues.length, expectedValues.length, " Checking length of expected menu");
   for (let i = 0; i < expectedValues.length; i++) {
     is(actualValues[i], expectedValues[i], " Checking menu entry #" + i);
--- a/browser/extensions/formautofill/test/mochitest/formautofill_parent_utils.js
+++ b/browser/extensions/formautofill/test/mochitest/formautofill_parent_utils.js
@@ -38,17 +38,16 @@ var ParentUtils = {
   },
 
   cleanup() {
     Services.obs.removeObserver(this, "formautofill-storage-changed");
     this.cleanUpAddress();
   },
 };
 
-ParentUtils.cleanUpAddress();
 Services.obs.addObserver(ParentUtils, "formautofill-storage-changed");
 
 addMessageListener("FormAutofillTest:AddAddress", (msg) => {
   ParentUtils.updateAddress("add", "FormAutofill:SaveAddress", msg, "FormAutofillTest:AddressAdded");
 });
 
 addMessageListener("FormAutofillTest:RemoveAddress", (msg) => {
   ParentUtils.updateAddress("remove", "FormAutofill:RemoveAddress", msg, "FormAutofillTest:AddressRemoved");
--- a/browser/extensions/formautofill/test/mochitest/mochitest.ini
+++ b/browser/extensions/formautofill/test/mochitest/mochitest.ini
@@ -1,9 +1,9 @@
 [DEFAULT]
 support-files =
   ../../../../../toolkit/components/satchel/test/satchel_common.js
   ../../../../../toolkit/components/satchel/test/parent_utils.js
   formautofill_common.js
   formautofill_parent_utils.js
 
+[test_autofocus_form.html]
 [test_basic_autocomplete_form.html]
-
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/test/mochitest/test_autofocus_form.html
@@ -0,0 +1,86 @@
+<!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: autocomplete on an autofocus form
+
+<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";
+
+let expectingPopup = null;
+let MOCK_STORAGE = [{
+  organization: "Sesame Street",
+  "street-address": "123 Sesame Street.",
+  tel: "1-345-345-3456",
+}, {
+  organization: "Mozilla",
+  "street-address": "331 E. Evelyn Avenue",
+  tel: "1-650-903-0800",
+}];
+
+function expectPopup() {
+  info("expecting a popup");
+  return new Promise(resolve => {
+    expectingPopup = resolve;
+  });
+}
+
+function popupShownListener() {
+  info("popup shown for test ");
+  if (expectingPopup) {
+    expectingPopup();
+    expectingPopup = null;
+  }
+}
+
+async function setupAddressStorage() {
+  await addAddress(MOCK_STORAGE[0]);
+  await addAddress(MOCK_STORAGE[1]);
+}
+
+add_task(async function check_autocomplete_on_autofocus_field() {
+  await setupAddressStorage();
+  doKey("down");
+  await expectPopup();
+  checkMenuEntries(MOCK_STORAGE.map(address =>
+    JSON.stringify({primary: address.organization, secondary: address["street-address"]})
+  ));
+});
+
+registerPopupShownListener(popupShownListener);
+
+</script>
+
+<p id="display"></p>
+
+<div id="content">
+
+  <form id="form1">
+    <p>This is a basic form.</p>
+    <p><label>organization: <input id="organization" name="organization" autocomplete="organization" type="text"></label></p>
+    <script>
+      // Focuses the input before DOMContentLoaded
+      document.getElementById("organization").focus();
+    </script>
+    <p><label>streetAddress: <input id="street-address" name="street-address" autocomplete="street-address" type="text"></label></p>
+    <p><label>tel: <input id="tel" name="tel" autocomplete="tel" type="text"></label></p>
+    <p><label>country: <input id="country" name="country" autocomplete="country" type="text"></label></p>
+  </form>
+
+</div>
+
+<pre id="test"></pre>
+</body>
+</html>
--- a/browser/extensions/formautofill/test/mochitest/test_basic_autocomplete_form.html
+++ b/browser/extensions/formautofill/test/mochitest/test_basic_autocomplete_form.html
@@ -90,71 +90,71 @@ async function setupFormHistory() {
     {op: "add", fieldname: "email", value: "foo@mozilla.com"},
   ]);
 }
 
 // Form with history only.
 add_task(async function history_only_menu_checking() {
   await setupFormHistory();
 
-  setInput("#tel", "");
+  await setInput("#tel", "");
   doKey("down");
   await expectPopup();
   checkMenuEntries(["1-234-567-890"]);
 });
 
 // Form with both history and address storage.
 add_task(async function check_menu_when_both_existed() {
   await setupAddressStorage();
 
-  setInput("#organization", "");
+  await setInput("#organization", "");
   doKey("down");
   await expectPopup();
   checkMenuEntries(MOCK_STORAGE.map(address =>
     JSON.stringify({primary: address.organization, secondary: address["street-address"]})
   ));
 
-  setInput("#street-address", "");
+  await setInput("#street-address", "");
   doKey("down");
   await expectPopup();
   checkMenuEntries(MOCK_STORAGE.map(address =>
     JSON.stringify({primary: address["street-address"], secondary: address.organization})
   ));
 
-  setInput("#tel", "");
+  await setInput("#tel", "");
   doKey("down");
   await expectPopup();
   checkMenuEntries(MOCK_STORAGE.map(address =>
     JSON.stringify({primary: address.tel, secondary: address["street-address"]})
   ));
 });
 
 // Display history search result if no matched data in addresses.
 add_task(async function check_fallback_for_mismatched_field() {
-  setInput("#email", "");
+  await setInput("#email", "");
   doKey("down");
   await expectPopup();
   checkMenuEntries(["foo@mozilla.com"]);
 });
 
 // Autofill the address from dropdown menu.
 add_task(async function check_fields_after_form_autofill() {
-  setInput("#organization", "Moz");
+  await setInput("#organization", "Moz");
   doKey("down");
   await expectPopup();
   checkMenuEntries(MOCK_STORAGE.map(address =>
     JSON.stringify({primary: address.organization, secondary: address["street-address"]})
   ).slice(1));
   doKey("down");
   await checkFormFilled(MOCK_STORAGE[1]);
 });
 
 // Fallback to history search after autofill address.
 add_task(async function check_fallback_after_form_autofill() {
-  setInput("#tel", "");
+  await setInput("#tel", "");
   doKey("down");
   await expectPopup();
   checkMenuEntries(["1-234-567-890"]);
 });
 
 registerPopupShownListener(popupShownListener);
 
 </script>