Bug 1371149 - Part 2. Add a chrome browser test for form autofill insecure field. r=MattN, seanlee draft
authorRay Lin <ralin@mozilla.com>
Fri, 28 Jul 2017 17:23:30 +0800
changeset 645747 891224193f7902a640c1acae94371e2706a812aa
parent 645746 d945a945edacc2868d50f7e93d1b9a7c17a72963
child 726004 a762bf1673b07930d083f241fc8bc716fce0625f
push id73865
push userbmo:ralin@mozilla.com
push dateMon, 14 Aug 2017 07:04:24 +0000
reviewersMattN, seanlee
bugs1371149
milestone57.0a1
Bug 1371149 - Part 2. Add a chrome browser test for form autofill insecure field. r=MattN, seanlee MozReview-Commit-ID: 7KjI9siHlqt
browser/extensions/formautofill/FormAutofillParent.jsm
browser/extensions/formautofill/test/browser/browser.ini
browser/extensions/formautofill/test/browser/browser_autocomplete_footer.js
browser/extensions/formautofill/test/browser/browser_autocomplete_marked_back_forward.js
browser/extensions/formautofill/test/browser/browser_autocomplete_marked_detached_tab.js
browser/extensions/formautofill/test/browser/browser_insecure_form.js
browser/extensions/formautofill/test/browser/head.js
browser/extensions/formautofill/test/fixtures/autocomplete_creditcard_basic.html
--- a/browser/extensions/formautofill/FormAutofillParent.jsm
+++ b/browser/extensions/formautofill/FormAutofillParent.jsm
@@ -77,16 +77,17 @@ FormAutofillParent.prototype = {
   /**
    * Initializes ProfileStorage and registers the message handler.
    */
   async init() {
     Services.obs.addObserver(this, "advanced-pane-loaded");
     Services.ppmm.addMessageListener("FormAutofill:InitStorage", this);
     Services.ppmm.addMessageListener("FormAutofill:GetRecords", this);
     Services.ppmm.addMessageListener("FormAutofill:SaveAddress", this);
+    Services.ppmm.addMessageListener("FormAutofill:SaveCreditCard", this);
     Services.ppmm.addMessageListener("FormAutofill:RemoveAddresses", 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.obs.addObserver(this, "formautofill-storage-changed");
   },
@@ -188,16 +189,20 @@ FormAutofillParent.prototype = {
       case "FormAutofill:SaveAddress": {
         if (data.guid) {
           this.profileStorage.addresses.update(data.guid, data.address);
         } else {
           this.profileStorage.addresses.add(data.address);
         }
         break;
       }
+      case "FormAutofill:SaveCreditCard": {
+        this.profileStorage.creditCards.add(data.creditcard);
+        break;
+      }
       case "FormAutofill:RemoveAddresses": {
         data.guids.forEach(guid => this.profileStorage.addresses.remove(guid));
         break;
       }
       case "FormAutofill:OnFormSubmit": {
         this._onFormSubmit(data, target);
         break;
       }
@@ -214,16 +219,17 @@ FormAutofillParent.prototype = {
    * @private
    */
   _uninit() {
     this.profileStorage._saveImmediately();
 
     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.obs.removeObserver(this, "advanced-pane-loaded");
     Services.prefs.removeObserver(ENABLED_PREF, this);
   },
 
   /**
    * Get the records from profile store and return results back to content
    * process.
--- a/browser/extensions/formautofill/test/browser/browser.ini
+++ b/browser/extensions/formautofill/test/browser/browser.ini
@@ -1,16 +1,18 @@
 [DEFAULT]
 head = head.js
 
 support-files =
   ../fixtures/autocomplete_basic.html
+  ../fixtures/autocomplete_creditcard_basic.html
 
 [browser_autocomplete_footer.js]
 [browser_autocomplete_marked_back_forward.js]
 [browser_autocomplete_marked_detached_tab.js]
 [browser_check_installed.js]
 [browser_editAddressDialog.js]
 [browser_first_time_use_doorhanger.js]
+[browser_insecure_form.js]
+[browser_manageAddressesDialog.js]
 [browser_privacyPreferences.js]
-[browser_manageAddressesDialog.js]
 [browser_submission_in_private_mode.js]
 [browser_update_doorhanger.js]
--- a/browser/extensions/formautofill/test/browser/browser_autocomplete_footer.js
+++ b/browser/extensions/formautofill/test/browser/browser_autocomplete_footer.js
@@ -16,72 +16,60 @@ async function expectWarningText(browser
 add_task(async function setup_storage() {
   await saveAddress(TEST_ADDRESS_1);
   await saveAddress(TEST_ADDRESS_2);
   await saveAddress(TEST_ADDRESS_3);
 });
 
 add_task(async function test_click_on_footer() {
   await BrowserTestUtils.withNewTab({gBrowser, url: URL}, async function(browser) {
-    const {autoCompletePopup, autoCompletePopup: {richlistbox: itemsBox}} = browser;
+    const {autoCompletePopup: {richlistbox: itemsBox}} = browser;
 
     await openPopupOn(browser, "#organization");
     // Click on the footer
     const optionButton = itemsBox.querySelector(".autocomplete-richlistitem:last-child")._optionButton;
     const prefTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, PRIVACY_PREF_URL);
     await EventUtils.synthesizeMouseAtCenter(optionButton, {});
     await BrowserTestUtils.removeTab(await prefTabPromise);
     ok(true, "Tab: preferences#privacy was successfully opened by clicking on the footer");
 
-    // Ensure the popup is closed before entering the next test.
-    await ContentTask.spawn(browser, {}, async function() {
-      content.document.getElementById("organization").blur();
-    });
-    await BrowserTestUtils.waitForCondition(() => !autoCompletePopup.popupOpen);
+    await closePopup(browser);
   });
 });
 
 add_task(async function test_press_enter_on_footer() {
   await BrowserTestUtils.withNewTab({gBrowser, url: URL}, async function(browser) {
-    const {autoCompletePopup, autoCompletePopup: {richlistbox: itemsBox}} = browser;
+    const {autoCompletePopup: {richlistbox: itemsBox}} = browser;
 
     await openPopupOn(browser, "#organization");
     // Navigate to the footer and press enter.
     const listItemElems = itemsBox.querySelectorAll(".autocomplete-richlistitem");
     const prefTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, PRIVACY_PREF_URL);
     for (let i = 0; i < listItemElems.length; i++) {
       await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
     }
     await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
     await BrowserTestUtils.removeTab(await prefTabPromise);
     ok(true, "Tab: preferences#privacy was successfully opened by pressing enter on the footer");
 
-    // Ensure the popup is closed before entering the next test.
-    await ContentTask.spawn(browser, {}, async function() {
-      content.document.getElementById("organization").blur();
-    });
-    await BrowserTestUtils.waitForCondition(() => !autoCompletePopup.popupOpen);
+    await closePopup(browser);
   });
 });
 
 add_task(async function test_phishing_warning() {
   await BrowserTestUtils.withNewTab({gBrowser, url: URL}, async function(browser) {
-    const {autoCompletePopup, autoCompletePopup: {richlistbox: itemsBox}} = browser;
+    const {autoCompletePopup: {richlistbox: itemsBox}} = browser;
 
     await openPopupOn(browser, "#street-address");
     const warningBox = itemsBox.querySelector(".autocomplete-richlistitem:last-child")._warningTextBox;
     ok(warningBox, "Got phishing warning box");
     await expectWarningText(browser, "Also autofills company, phone, email");
     await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
     await expectWarningText(browser, "Also autofills company, phone, email");
     await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
     await expectWarningText(browser, "Autofills address");
     await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
     await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
     await expectWarningText(browser, "Also autofills company, phone, email");
 
-    // Ensure the popup is closed before entering the next test.
-    await ContentTask.spawn(browser, {}, async function() {
-      content.document.getElementById("street-address").blur();
-    });
-    await BrowserTestUtils.waitForCondition(() => !autoCompletePopup.popupOpen);
+    await closePopup(browser);
   });
 });
--- a/browser/extensions/formautofill/test/browser/browser_autocomplete_marked_back_forward.js
+++ b/browser/extensions/formautofill/test/browser/browser_autocomplete_marked_back_forward.js
@@ -46,16 +46,11 @@ add_task(async function test_back_forwar
 
     // Check after hitting forward to the second page
     stoppedPromise = BrowserTestUtils.browserStopped(browser);
     browser.goForward();
     await stoppedPromise;
     await openPopupOn(browser, "#street-address");
     checkPopup(autoCompletePopup);
 
-    // Ensure the popup is closed before entering the next test.
-    await ContentTask.spawn(browser, {}, async function() {
-      content.document.getElementById("street-address").blur();
-    });
-    await BrowserTestUtils.waitForCondition(() => !autoCompletePopup.popupOpen,
-                                            "popup should have closed");
+    await closePopup(browser);
   });
 });
--- a/browser/extensions/formautofill/test/browser/browser_autocomplete_marked_detached_tab.js
+++ b/browser/extensions/formautofill/test/browser/browser_autocomplete_marked_detached_tab.js
@@ -38,17 +38,12 @@ add_task(async function test_detach_tab_
     let newBrowser = newWin.gBrowser.selectedBrowser;
     ok(newBrowser, "Found new <browser>");
     let newAutoCompletePopup = newBrowser.autoCompletePopup;
     ok(newAutoCompletePopup, "Found new autocomplete popup");
 
     await openPopupOn(newBrowser, "#street-address");
     checkPopup(newAutoCompletePopup);
 
-    // Ensure the popup is closed before entering the next test.
-    await ContentTask.spawn(newBrowser, {}, async function() {
-      content.document.getElementById("street-address").blur();
-    });
-    await BrowserTestUtils.waitForCondition(() => !newAutoCompletePopup.popupOpen,
-                                           "popup should have closed");
+    await closePopup(newBrowser);
     await BrowserTestUtils.closeWindow(newWin);
   });
 });
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/browser_insecure_form.js
@@ -0,0 +1,94 @@
+"use strict";
+
+const TEST_URL_PATH_CC = "://example.org/browser/browser/extensions/formautofill/test/browser/autocomplete_creditcard_basic.html";
+const TEST_URL_PATH = "://example.org/browser/browser/extensions/formautofill/test/browser/autocomplete_basic.html";
+
+add_task(async function setup_storage() {
+  await saveAddress(TEST_ADDRESS_1);
+  await saveAddress(TEST_ADDRESS_2);
+  await saveAddress(TEST_ADDRESS_3);
+
+  await saveCreditCard(TEST_CREDIT_CARD_1);
+  await saveCreditCard(TEST_CREDIT_CARD_2);
+  await saveCreditCard(TEST_CREDIT_CARD_3);
+});
+
+add_task(async function test_insecure_form() {
+  async function runTest({urlPath, protocol, focusInput, expectedType, expectedResultLength}) {
+    await BrowserTestUtils.withNewTab({gBrowser, url: protocol + urlPath}, async function(browser) {
+      await openPopupOn(browser, focusInput);
+
+      const items = getDisplayedPopupItems(browser);
+      is(items.length, expectedResultLength, `Should show correct amount of results in "${protocol}"`);
+      const firstItem = items[0];
+      is(firstItem.getAttribute("originaltype"), expectedType, `Item should attach with correct binding in "${protocol}"`);
+
+      await closePopup(browser);
+    });
+  }
+
+  const testSets = [{
+    urlPath: TEST_URL_PATH,
+    protocol: "https",
+    focusInput: "#organization",
+    expectedType: "autofill-profile",
+    expectedResultLength: 2,
+  }, {
+    urlPath: TEST_URL_PATH,
+    protocol: "http",
+    focusInput: "#organization",
+    expectedType: "autofill-profile",
+    expectedResultLength: 2,
+  }, {
+    urlPath: TEST_URL_PATH_CC,
+    protocol: "https",
+    focusInput: "#cc-name",
+    expectedType: "autofill-profile",
+    expectedResultLength: 3,
+  }, {
+    urlPath: TEST_URL_PATH_CC,
+    protocol: "http",
+    focusInput: "#cc-name",
+    expectedType: "autofill-insecureWarning", // insecure warning field
+    expectedResultLength: 1,
+  }];
+
+  await runTest(testSets[0]);
+  await runTest(testSets[1]);
+  await runTest(testSets[2]);
+  await runTest(testSets[3]);
+});
+
+add_task(async function test_click_on_insecure_warning() {
+  await BrowserTestUtils.withNewTab({gBrowser, url: "http" + TEST_URL_PATH_CC}, async function(browser) {
+    await openPopupOn(browser, "#cc-name");
+
+    const insecureItem = getDisplayedPopupItems(browser)[0];
+    await EventUtils.synthesizeMouseAtCenter(insecureItem, {});
+    // Check input's value after popup closed to ensure the completion of autofilling.
+    await expectPopupClose(browser);
+    const inputValue = await ContentTask.spawn(browser, {}, async function() {
+      return content.document.querySelector("#cc-name").value;
+    });
+    is(inputValue, "");
+
+    await closePopup(browser);
+  });
+});
+
+add_task(async function test_press_enter_on_insecure_warning() {
+  await BrowserTestUtils.withNewTab({gBrowser, url: "http" + TEST_URL_PATH_CC}, async function(browser) {
+    await openPopupOn(browser, "#cc-name");
+
+    await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
+    await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
+    // Check input's value after popup closed to ensure the completion of autofilling.
+    await expectPopupClose(browser);
+    const inputValue = await ContentTask.spawn(browser, {}, async function() {
+      return content.document.querySelector("#cc-name").value;
+    });
+    is(inputValue, "");
+
+    await closePopup(browser);
+  });
+});
--- a/browser/extensions/formautofill/test/browser/head.js
+++ b/browser/extensions/formautofill/test/browser/head.js
@@ -1,12 +1,14 @@
 /* exported MANAGE_ADDRESSES_DIALOG_URL, EDIT_ADDRESS_DIALOG_URL, BASE_URL,
             TEST_ADDRESS_1, TEST_ADDRESS_2, TEST_ADDRESS_3,
-            sleep, expectPopupOpen, openPopupOn, clickDoorhangerButton,
-            getAddresses, saveAddress, removeAddresses */
+            TEST_CREDIT_CARD_1, TEST_CREDIT_CARD_2, TEST_CREDIT_CARD_3,
+            sleep, expectPopupOpen, openPopupOn, expectPopupClose, closePopup, clickDoorhangerButton,
+            getAddresses, saveAddress, removeAddresses, saveCreditCard,
+            getDisplayedPopupItems */
 
 "use strict";
 
 const MANAGE_ADDRESSES_DIALOG_URL = "chrome://formautofill/content/manageAddresses.xhtml";
 const EDIT_ADDRESS_DIALOG_URL = "chrome://formautofill/content/editAddress.xhtml";
 const BASE_URL = "http://mochi.test:8888/browser/browser/extensions/formautofill/test/browser/";
 
 const TEST_ADDRESS_1 = {
@@ -28,49 +30,103 @@ const TEST_ADDRESS_2 = {
   country: "US",
 };
 
 const TEST_ADDRESS_3 = {
   "street-address": "Other Address",
   "postal-code": "12345",
 };
 
+const TEST_CREDIT_CARD_1 = {
+  "cc-name": "John Doe",
+  "cc-number": "1234567812345678",
+  // "cc-number-encrypted": "",
+  "cc-exp-month": 4,
+  "cc-exp-year": 2017,
+};
+
+const TEST_CREDIT_CARD_2 = {
+  "cc-name": "Timothy Berners-Lee",
+  "cc-number": "1111222233334444",
+  "cc-exp-month": 12,
+  "cc-exp-year": 2022,
+};
+
+const TEST_CREDIT_CARD_3 = {
+  "cc-number": "9999888877776666",
+  "cc-exp-month": 1,
+  "cc-exp-year": 2000,
+};
+
 const MAIN_BUTTON_INDEX = 0;
 const SECONDARY_BUTTON_INDEX = 1;
 
+function getDisplayedPopupItems(browser, selector = ".autocomplete-richlistitem") {
+  const {autoCompletePopup: {richlistbox: itemsBox}} = browser;
+  const listItemElems = itemsBox.querySelectorAll(selector);
+
+  return [...listItemElems].filter(item => item.getAttribute("collapsed") != "true");
+}
+
 async function sleep(ms = 500) {
   await new Promise(resolve => setTimeout(resolve, ms));
 }
 
 async function expectPopupOpen(browser) {
-  const {autoCompletePopup, autoCompletePopup: {richlistbox: itemsBox}} = browser;
-  const listItemElems = itemsBox.querySelectorAll(".autocomplete-richlistitem");
+  const {autoCompletePopup} = browser;
+  const listItemElems = getDisplayedPopupItems(browser);
 
   await BrowserTestUtils.waitForCondition(() => autoCompletePopup.popupOpen,
                                          "popup should be open");
   await BrowserTestUtils.waitForCondition(() => {
     return [...listItemElems].every(item => {
       return (item.getAttribute("originaltype") == "autofill-profile" ||
+             item.getAttribute("originaltype") == "insecureWarning" ||
              item.getAttribute("originaltype") == "autofill-footer") &&
              item.hasAttribute("formautofillattached");
     });
   }, "The popup should be a form autofill one");
 }
 
 async function openPopupOn(browser, selector) {
   await SimpleTest.promiseFocus(browser);
   /* eslint no-shadow: ["error", { "allow": ["selector"] }] */
-  await ContentTask.spawn(browser, {selector}, async function({selector}) {
-    content.document.querySelector(selector).focus();
+  const identified = await ContentTask.spawn(browser, {selector}, async function({selector}) {
+    const input = content.document.querySelector(selector);
+    const forms = content.document.getElementsByTagName("form");
+    const rootElement = [...forms].find(form => form.contains(input)) || content.document.body;
+
+    input.focus();
+    if (rootElement.hasAttribute("test-formautofill-identified")) {
+      return true;
+    }
+    rootElement.setAttribute("test-formautofill-identified", "true");
+    return false;
   });
-  await sleep(2000);
+  // Wait 2 seconds for identifyAutofillFields if the form hasn't been identified yet.
+  if (!identified) {
+    await sleep(2000);
+  }
   await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
   await expectPopupOpen(browser);
 }
 
+async function expectPopupClose(browser) {
+  await BrowserTestUtils.waitForCondition(() => !browser.autoCompletePopup.popupOpen,
+    "popup should have closed");
+}
+
+async function closePopup(browser) {
+  await ContentTask.spawn(browser, {}, async function() {
+    content.document.activeElement.blur();
+  });
+
+  await expectPopupClose(browser);
+}
+
 function getRecords(data) {
   return new Promise(resolve => {
     Services.cpmm.addMessageListener("FormAutofill:Records", function getResult(result) {
       Services.cpmm.removeMessageListener("FormAutofill:Records", getResult);
       resolve(result.data);
     });
     Services.cpmm.sendAsyncMessage("FormAutofill:GetRecords", data);
   });
@@ -80,16 +136,20 @@ function getAddresses() {
   return getRecords({collectionName: "addresses"});
 }
 
 function saveAddress(address) {
   Services.cpmm.sendAsyncMessage("FormAutofill:SaveAddress", {address});
   return TestUtils.topicObserved("formautofill-storage-changed");
 }
 
+function saveCreditCard(creditcard) {
+  Services.cpmm.sendAsyncMessage("FormAutofill:SaveCreditCard", {creditcard});
+  return TestUtils.topicObserved("formautofill-storage-changed");
+}
 function removeAddresses(guids) {
   Services.cpmm.sendAsyncMessage("FormAutofill:RemoveAddresses", {guids});
   return TestUtils.topicObserved("formautofill-storage-changed");
 }
 
 /**
  * Clicks the popup notification button and wait for popup hidden.
  *
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/test/fixtures/autocomplete_creditcard_basic.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Form Autofill Credit Card Demo Page</title>
+</head>
+<body>
+  <h1>Form Autofill Credit Card Demo Page</h1>
+  <form id="form">
+    <p><label>Name: <input id="cc-name" autocomplete="cc-name"></label></p>
+    <p><label>Card Number: <input id="cc-number" autocomplete="cc-number"></label></p>
+    <p><label>Expiration month: <input id="cc-exp-month" autocomplete="cc-exp-month"></label></p>
+    <p><label>Expiration year: <input id="cc-exp-year" autocomplete="cc-exp-year"></label></p>
+    <p><label>CSC: <input id="cc-csc" autocomplete="cc-csc"></label></p>
+    <p>
+      <input type="submit" value="Submit">
+      <button type="reset">Reset</button>
+    </p>
+  </form>
+</body>
+</html>