Bug 1258860 - Make test_basic_form_autocomplete.html work in e10s. r=dolske draft
authorMatthew Noorenberghe <mozilla@noorenberghe.ca>
Tue, 05 Apr 2016 15:53:16 -0700
changeset 347831 6ececf99646cfc793d97135a4b01c4c6833f4bf9
parent 347760 17a0ded9bb99c05c25729c306b91771483109067
child 347839 d3aee5094a82ddb43f5b0d7344003713d493d5e2
push id14681
push usermozilla@noorenberghe.ca
push dateTue, 05 Apr 2016 22:54:21 +0000
reviewersdolske
bugs1258860
milestone48.0a1
Bug 1258860 - Make test_basic_form_autocomplete.html work in e10s. r=dolske MozReview-Commit-ID: B9D9h4uBOqw
toolkit/components/passwordmgr/test/mochitest.ini
toolkit/components/passwordmgr/test/mochitest/mochitest.ini
toolkit/components/passwordmgr/test/mochitest/test_basic_form_autocomplete.html
toolkit/components/passwordmgr/test/pwmgr_common.js
toolkit/components/passwordmgr/test/test_basic_form_autocomplete.html
toolkit/components/satchel/test/satchel_common.js
--- a/toolkit/components/passwordmgr/test/mochitest.ini
+++ b/toolkit/components/passwordmgr/test/mochitest.ini
@@ -13,24 +13,22 @@ support-files =
   subtst_notifications_11_popup.html
   subtst_privbrowsing_1.html
   subtst_privbrowsing_2.html
   subtst_privbrowsing_3.html
   subtst_privbrowsing_4.html
   subtst_prompt_async.html
 
 [test_basic_form_2pw_2.html]
-[test_basic_form_autocomplete.html]
-skip-if = toolkit == 'android' # Bug 1258975 on android.
 [test_master_password.html]
 skip-if = toolkit == 'android' # Bug 1258975 on android.
 [test_master_password_cleanup.html]
 skip-if = toolkit == 'android' # Bug 1258975 on android.
 [test_notifications_popup.html]
 skip-if = true || os == "linux" || toolkit == 'android' # bug 934057. Bug 1258975 on android.
 [test_prompt.html]
 skip-if = os == "linux" || toolkit == 'android' # Bug 1258975 on android.
 [test_prompt_async.html]
 skip-if = toolkit == 'android' # Bug 1258975 on android.
 [test_xhr.html]
 skip-if = toolkit == 'android' # Bug 1258975 on android.
 [test_xml_load.html]
-skip-if = toolkit == 'android' # Bug 1258975 on android.
\ No newline at end of file
+skip-if = toolkit == 'android' # Bug 1258975 on android.
--- a/toolkit/components/passwordmgr/test/mochitest/mochitest.ini
+++ b/toolkit/components/passwordmgr/test/mochitest/mochitest.ini
@@ -9,16 +9,18 @@ support-files =
 
 [test_autofill_password-only.html]
 [test_basic_form.html]
 [test_basic_form_0pw.html]
 [test_basic_form_1pw.html]
 [test_basic_form_1pw_2.html]
 [test_basic_form_2pw_1.html]
 [test_basic_form_3pw_1.html]
+[test_basic_form_autocomplete.html]
+skip-if = toolkit == 'android' || e10s # android:autocomplete. e10s:Requires code fix in bug 1258921.
 [test_basic_form_html5.html]
 [test_basic_form_pwevent.html]
 [test_basic_form_pwonly.html]
 [test_bug_627616.html]
 skip-if = toolkit == 'android' # Bug 1258975 on android.
 [test_bug_776171.html]
 [test_case_differences.html]
 skip-if = toolkit == 'android' # autocomplete
@@ -28,9 +30,9 @@ skip-if = toolkit == 'android' # autocom
 [test_formless_autofill.html]
 skip-if = toolkit == 'android' # Bug 1259768
 [test_input_events.html]
 [test_input_events_for_identical_values.html]
 [test_maxlength.html]
 [test_passwords_in_type_password.html]
 [test_recipe_login_fields.html]
 skip-if = (toolkit == 'android') # Bug 1258975 on android.
-[test_xhr_2.html]
\ No newline at end of file
+[test_xhr_2.html]
rename from toolkit/components/passwordmgr/test/test_basic_form_autocomplete.html
rename to toolkit/components/passwordmgr/test/mochitest/test_basic_form_autocomplete.html
--- a/toolkit/components/passwordmgr/test/test_basic_form_autocomplete.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_basic_form_autocomplete.html
@@ -1,115 +1,108 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <meta charset="utf-8">
-  <title>Test basic autocomplete</title>
+  <title>Test basic login autocomplete</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+  <script type="text/javascript" src="satchel_common.js"></script>
   <script type="text/javascript" src="pwmgr_common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 Login Manager test: multiple login autocomplete
 
 <script>
-commonInit();
-SimpleTest.waitForExplicitFinish();
+var chromeScript = runChecksAfterCommonInit();
+
+var setupScript = runInParent(function setup() {
+  const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+  Cu.import("resource://gre/modules/Services.jsm");
+
+  // Create some logins just for this form, since we'll be deleting them.
+  var nsLoginInfo = Components.Constructor("@mozilla.org/login-manager/loginInfo;1",
+                                           Ci.nsILoginInfo, "init");
+  assert.ok(nsLoginInfo != null, "nsLoginInfo constructor");
+
+  // login0 has no username, so should be filtered out from the autocomplete list.
+  var login0 = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete:8888", null,
+                               "", "user0pass", "", "pword");
+
+  var login1 = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete:8888", null,
+                               "tempuser1", "temppass1", "uname", "pword");
+
+  var login2 = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete:8888", null,
+                               "testuser2", "testpass2", "uname", "pword");
+
+  var login3 = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete:8888", null,
+                               "testuser3", "testpass3", "uname", "pword");
 
-// Get the pwmgr service
-var pwmgr = SpecialPowers.Cc["@mozilla.org/login-manager;1"]
-                         .getService(SpecialPowers.Ci.nsILoginManager);
-ok(pwmgr != null, "nsLoginManager service");
+  var login4 = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete:8888", null,
+                               "zzzuser4", "zzzpass4", "uname", "pword");
+
+  // login 5 only used in the single-user forms
+  var login5 = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete2", null,
+                               "singleuser5", "singlepass5", "uname", "pword");
+
+  var login6A = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete3", null,
+                                "form7user1", "form7pass1", "uname", "pword");
+  var login6B = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete3", null,
+                                "form7user2", "form7pass2", "uname", "pword");
 
-// Create some logins just for this form, since we'll be deleting them.
-var nsLoginInfo =
-SpecialPowers.wrap(SpecialPowers.Components).Constructor("@mozilla.org/login-manager/loginInfo;1",
-                          SpecialPowers.Ci.nsILoginInfo, "init");
-ok(nsLoginInfo != null, "nsLoginInfo constructor");
+  var login7  = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete4", null,
+                                "form8user", "form8pass", "uname", "pword");
+
+  var login8A = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete5", null,
+                                "form9userAB", "form9pass", "uname", "pword");
+  var login8B = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete5", null,
+                                "form9userAAB", "form9pass", "uname", "pword");
+  var login8C = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete5", null,
+                                "form9userAABzz", "form9pass", "uname", "pword");
+
+  var login9 = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete6", null,
+                               "testuser9", "testpass9", "uname", "pword");
+
+  var login10 = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete7", null,
+                                "testuser10", "testpass10", "uname", "pword");
 
 
-// login0 has no username, so should be filtered out from the autocomplete list.
-var login0 = new nsLoginInfo(
-    "http://mochi.test:8888", "http://autocomplete:8888", null,
-    "", "user0pass", "", "pword");
-
-var login1 = new nsLoginInfo(
-    "http://mochi.test:8888", "http://autocomplete:8888", null,
-    "tempuser1", "temppass1", "uname", "pword");
-
-var login2 = new nsLoginInfo(
-    "http://mochi.test:8888", "http://autocomplete:8888", null,
-    "testuser2", "testpass2", "uname", "pword");
-
-var login3 = new nsLoginInfo(
-    "http://mochi.test:8888", "http://autocomplete:8888", null,
-    "testuser3", "testpass3", "uname", "pword");
-
-var login4 = new nsLoginInfo(
-    "http://mochi.test:8888", "http://autocomplete:8888", null,
-    "zzzuser4", "zzzpass4", "uname", "pword");
-
-// login 5 only used in the single-user forms
-var login5 = new nsLoginInfo(
-    "http://mochi.test:8888", "http://autocomplete2", null,
-    "singleuser5", "singlepass5", "uname", "pword");
-
-var login6A = new nsLoginInfo(
-    "http://mochi.test:8888", "http://autocomplete3", null,
-    "form7user1", "form7pass1", "uname", "pword");
-var login6B = new nsLoginInfo(
-    "http://mochi.test:8888", "http://autocomplete3", null,
-    "form7user2", "form7pass2", "uname", "pword");
-
-var login7  = new nsLoginInfo(
-    "http://mochi.test:8888", "http://autocomplete4", null,
-    "form8user", "form8pass", "uname", "pword");
+  // try/catch in case someone runs the tests manually, twice.
+  try {
+    Services.logins.addLogin(login0);
+    Services.logins.addLogin(login1);
+    Services.logins.addLogin(login2);
+    Services.logins.addLogin(login3);
+    Services.logins.addLogin(login4);
+    Services.logins.addLogin(login5);
+    Services.logins.addLogin(login6A);
+    Services.logins.addLogin(login6B);
+    Services.logins.addLogin(login7);
+    Services.logins.addLogin(login8A);
+    Services.logins.addLogin(login8B);
+    // login8C is added later
+    Services.logins.addLogin(login9);
+    Services.logins.addLogin(login10);
+  } catch (e) {
+    assert.ok(false, "addLogin threw: " + e);
+  }
 
-var login8A = new nsLoginInfo(
-    "http://mochi.test:8888", "http://autocomplete5", null,
-    "form9userAB", "form9pass", "uname", "pword");
-
-var login8B = new nsLoginInfo(
-    "http://mochi.test:8888", "http://autocomplete5", null,
-    "form9userAAB", "form9pass", "uname", "pword");
-
-// login8C is added later
-var login8C = new nsLoginInfo(
-    "http://mochi.test:8888", "http://autocomplete5", null,
-    "form9userAABz", "form9pass", "uname", "pword");
-
-var login9 = new nsLoginInfo(
-    "http://mochi.test:8888", "http://autocomplete6", null,
-    "testuser9", "testpass9", "uname", "pword");
-
-var login10 = new nsLoginInfo(
-    "http://mochi.test:8888", "http://autocomplete7", null,
-    "testuser10", "testpass10", "uname", "pword");
-
-
-// try/catch in case someone runs the tests manually, twice.
-try {
-    pwmgr.addLogin(login0);
-    pwmgr.addLogin(login1);
-    pwmgr.addLogin(login2);
-    pwmgr.addLogin(login3);
-    pwmgr.addLogin(login4);
-    pwmgr.addLogin(login5);
-    pwmgr.addLogin(login6A);
-    pwmgr.addLogin(login6B);
-    pwmgr.addLogin(login7);
-    pwmgr.addLogin(login8A);
-    pwmgr.addLogin(login8B);
-    pwmgr.addLogin(login9);
-    pwmgr.addLogin(login10);
-} catch (e) {
-    ok(false, "addLogin threw: " + e);
-}
-
+  addMessageListener("addLogin", loginVariableName => {
+    let login = eval(loginVariableName);
+    assert.ok(!!login, "Login to add is defined: " + loginVariableName);
+    Services.logins.addLogin(login);
+  });
+  addMessageListener("removeLogin", loginVariableName => {
+    let login = eval(loginVariableName);
+    assert.ok(!!login, "Login to delete is defined: " + loginVariableName);
+    Services.logins.removeLogin(login);
+  });
+});
 </script>
 <p id="display"></p>
 
 <!-- we presumably can't hide the content for this test. -->
 <div id="content">
 
   <!-- form1 tests multiple matching logins -->
   <form id="form1" action="http://autocomplete:8888/formtest.js" onsubmit="return false;">
@@ -193,744 +186,647 @@ try {
    </div>
 </div>
 
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 /** Test for Login Manager: multiple login autocomplete. **/
 
-var tester;
-
 var uname = $_(1, "uname");
 var pword = $_(1, "pword");
 const shiftModifier = SpecialPowers.Ci.nsIDOMEvent.SHIFT_MASK;
 
 // Restore the form to the default state.
 function restoreForm() {
-    uname.value = "";
-    pword.value = "";
-    uname.focus();
+  uname.value = "";
+  pword.value = "";
+  uname.focus();
 }
 
-
 // Check for expected username/password in form.
 function checkACForm(expectedUsername, expectedPassword) {
   var formID = uname.parentNode.id;
   is(uname.value, expectedUsername, "Checking " + formID + " username is: " + expectedUsername);
   is(pword.value, expectedPassword, "Checking " + formID + " password is: " + expectedPassword);
 }
 
-
 function sendFakeAutocompleteEvent(element) {
-    var acEvent = document.createEvent("HTMLEvents");
-    acEvent.initEvent("DOMAutoComplete", true, false);
-    element.dispatchEvent(acEvent);
+  var acEvent = document.createEvent("HTMLEvents");
+  acEvent.initEvent("DOMAutoComplete", true, false);
+  element.dispatchEvent(acEvent);
 }
 
-function hitEventLoop(func, times) {
-  if (times > 0) {
-    setTimeout(hitEventLoop, 0, func, times - 1);
-  } else {
-    setTimeout(func, 0);
-  }
-}
-
-function addPopupListener(eventName, func, capture) {
-  autocompletePopup.addEventListener(eventName, func, capture);
-}
-
-function removePopupListener(eventName, func, capture) {
-  autocompletePopup.removeEventListener(eventName, func, capture);
+function spinEventLoop() {
+  return Promise.resolve();
 }
 
-/*
- * Main section of test...
- *
- * This test is, to a first approximation, event driven. Each time we need to
- * wait for an event, runTest sets an event listener (or timeout for a couple
- * of rare cases) and yields. The event listener then resumes the generator by
- * calling its |next| method.
- */
-function* runTest() {
-  var testNum = 1;
-  ok(true, "Starting test #" + testNum);
-
-  function waitForPopup() {
-    addPopupListener("popupshown", function popupshown() {
-      removePopupListener("popupshown", popupshown, false);
-
-      window.setTimeout(tester.next.bind(tester), 0);
-    }, false);
-  }
-
-  function runNextTest(expectPopup) {
-    var save = testNum++;
-    if (expectPopup === "expect popup")
-      return waitForPopup();
+add_task(function* setup() {
+  listenForUnexpectedPopupShown();
+});
 
-    var unexpectedPopup = function() {
-      removePopupListener("popupshown", unexpectedPopup, false);
-      ok(false, "Test " + save + " should not show a popup");
-    };
-    addPopupListener("popupshown", unexpectedPopup, false);
-
-    hitEventLoop(function() {
-      removePopupListener("popupshown", unexpectedPopup, false);
-      tester.next();
-    }, 100);
-  }
+add_task(function* test_form1_initial_empty() {
+  yield SimpleTest.promiseFocus(window);
 
-  // We use this function when we're trying to prove that something doesn't
-  // happen, but where if it did it would do so asynchronously. It isn't
-  // perfect, but it's better than nothing.
-  function spinEventLoop() {
-    setTimeout(function() { tester.next(); }, 0);
-  }
-
-  function waitForCompletion() {
-    var observer = SpecialPowers.wrapCallback(function(subject, topic, data) {
-      SpecialPowers.removeObserver(observer, "passwordmgr-processed-form");
-      tester.next();
-    });
-    SpecialPowers.addObserver(observer, "passwordmgr-processed-form", false);
-  }
-
-  function getLoginRecipes() {
-    getRecipeParent().then(function (recipeParent) {
-      tester.next(recipeParent);
-    });
-  }
-
-  /* test 1 */
   // Make sure initial form is empty.
   checkACForm("", "");
+  let popupState = yield getPopupState();
+  is(popupState.open, false, "Check popup is initially closed");
+  is(popupState.selectedIndex, -1, "Check no entries are selected");
+});
+
+add_task(function* test_form1_first_entry() {
+  yield SimpleTest.promiseFocus(window);
+  // Trigger autocomplete popup
+  restoreForm();
+  let shownPromise = promiseACShown();
+  doKey("down"); // open
+  yield shownPromise;
+  doKey("down"); // first
+  checkACForm("", ""); // value shouldn't update just by selecting
+  doKey("return"); // not "enter"!
+  yield promiseFormsProcessed();
+  checkACForm("tempuser1", "temppass1");
+});
+
+add_task(function* test_form1_second_entry() {
+  // Trigger autocomplete popup
+  restoreForm();
+  let shownPromise = promiseACShown();
+  doKey("down"); // open
+  yield shownPromise;
+
+  doKey("down"); // first
+  doKey("down"); // second
+  doKey("return"); // not "enter"!
+  yield promiseFormsProcessed();
+  checkACForm("testuser2", "testpass2");
+});
+
+add_task(function* test_form1_third_entry() {
   // Trigger autocomplete popup
   restoreForm();
-  doKey("down");
-  yield runNextTest("expect popup");
+  let shownPromise = promiseACShown();
+  doKey("down"); // open
+  yield shownPromise;
 
-  /* test 2 */
-  // Check first entry
-  doKey("down");
-  checkACForm("", ""); // value shouldn't update
-  doKey("return"); // not "enter"!
-  yield waitForCompletion();
-  checkACForm("tempuser1", "temppass1");
+  doKey("down"); // first
+  doKey("down"); // second
+  doKey("down"); // third
+  doKey("return");
+  yield promiseFormsProcessed();
+  checkACForm("testuser3", "testpass3");
+});
 
+add_task(function* test_form1_fourth_entry() {
   // Trigger autocomplete popup
   restoreForm();
-  doKey("down");
-  yield runNextTest("expect popup");
-
-  /* test 3 */
-  // Check second entry
-  doKey("down");
-  doKey("down");
-  doKey("return"); // not "enter"!
-  yield waitForCompletion();
-  checkACForm("testuser2", "testpass2");
+  let shownPromise = promiseACShown();
+  doKey("down"); // open
+  yield shownPromise;
 
-  // Trigger autocomplete popup
-  restoreForm();
-  doKey("down");
-  yield runNextTest("expect popup");
+  doKey("down"); // first
+  doKey("down"); // second
+  doKey("down"); // third
+  doKey("down"); // fourth
+  doKey("return");
+  yield promiseFormsProcessed();
+  checkACForm("zzzuser4", "zzzpass4");
+});
 
-  /* test 4 */
-  // Check third entry
-  doKey("down");
-  doKey("down");
-  doKey("down");
-  doKey("return");
-  yield waitForCompletion();
-  checkACForm("testuser3", "testpass3");
-
+add_task(function* test_form1_wraparound_first_entry() {
   // Trigger autocomplete popup
   restoreForm();
-  doKey("down");
-  yield runNextTest("expect popup");
+  yield spinEventLoop(); // let focus happen
+  let shownPromise = promiseACShown();
+  doKey("down"); // open
+  yield shownPromise;
 
-  /* test 5 */
-  // Check fourth entry
-  doKey("down");
-  doKey("down");
-  doKey("down");
-  doKey("down");
+  doKey("down"); // first
+  doKey("down"); // second
+  doKey("down"); // third
+  doKey("down"); // fourth
+  doKey("down"); // deselects
+  doKey("down"); // first
   doKey("return");
-  yield waitForCompletion();
-  checkACForm("zzzuser4", "zzzpass4");
+  yield promiseFormsProcessed();
+  checkACForm("tempuser1", "temppass1");
+});
 
+add_task(function* test_form1_wraparound_up_last_entry() {
   // Trigger autocomplete popup
   restoreForm();
-  doKey("down");
-  yield runNextTest("expect popup");
+  let shownPromise = promiseACShown();
+  doKey("down"); // open
+  yield shownPromise;
 
-  /* test 6 */
-  // Check first entry (wraparound)
-  doKey("down");
-  doKey("down");
-  doKey("down");
-  doKey("down");
-  doKey("down"); // deselects
-  doKey("down");
+  doKey("up"); // last (fourth)
   doKey("return");
-  yield waitForCompletion();
-  checkACForm("tempuser1", "temppass1");
+  yield promiseFormsProcessed();
+  checkACForm("zzzuser4", "zzzpass4");
+});
 
+add_task(function* test_form1_wraparound_down_up_up() {
   // Trigger autocomplete popup
   restoreForm();
-  doKey("down");
-  yield runNextTest("expect popup");
+  let shownPromise = promiseACShown();
+  doKey("down"); // open
+  yield shownPromise;
 
-  /* test 7 */
-  // Check the last entry via arrow-up
-  doKey("up");
-  doKey("return");
-  yield waitForCompletion();
-  checkACForm("zzzuser4", "zzzpass4");
-
-  // Trigger autocomplete popup
-  restoreForm();
-  doKey("down");
-  yield runNextTest("expect popup");
-
-  /* test 8 */
-  // Check the last entry via arrow-up
   doKey("down"); // select first entry
   doKey("up");   // selects nothing!
   doKey("up");   // select last entry
   doKey("return");
-  yield waitForCompletion();
+  yield promiseFormsProcessed();
   checkACForm("zzzuser4", "zzzpass4");
+});
 
-  // Trigger autocomplete popup
+add_task(function* test_form1_wraparound_up_last() {
   restoreForm();
-  doKey("down");
-  yield runNextTest("expect popup");
+  let shownPromise = promiseACShown();
+  doKey("down"); // open
+  yield shownPromise;
 
-  /* test 9 */
-  // Check the last entry via arrow-up (wraparound)
   doKey("down");
   doKey("up"); // deselects
   doKey("up"); // last entry
   doKey("up");
   doKey("up");
   doKey("up"); // first entry
   doKey("up"); // deselects
   doKey("up"); // last entry
   doKey("return");
-  yield waitForCompletion();
+  yield promiseFormsProcessed();
   checkACForm("zzzuser4", "zzzpass4");
+});
 
-  // Trigger autocomplete popup
+add_task(function* test_form1_fill_username_without_autofill_right() {
   restoreForm();
-  doKey("down");
-  yield runNextTest("expect popup");
+  let shownPromise = promiseACShown();
+  doKey("down"); // open
+  yield shownPromise;
 
-  /* test 10 */
   // Set first entry w/o triggering autocomplete
-  doKey("down");
+  doKey("down"); // first
   doKey("right");
   yield spinEventLoop();
   checkACForm("tempuser1", ""); // empty password
+});
 
-  // Trigger autocomplete popup
+add_task(function* test_form1_fill_username_without_autofill_left() {
   restoreForm();
-  doKey("down");
-  yield runNextTest("expect popup");
+  let shownPromise = promiseACShown();
+  doKey("down"); // open
+  yield shownPromise;
 
-  /* test 11 */
   // Set first entry w/o triggering autocomplete
-  doKey("down");
+  doKey("down"); // first
   doKey("left");
   checkACForm("tempuser1", ""); // empty password
+});
 
-  // Trigger autocomplete popup
+add_task(function* test_form1_pageup_first() {
   restoreForm();
-  doKey("down");
-  yield runNextTest("expect popup");
+  let shownPromise = promiseACShown();
+  doKey("down"); // open
+  yield shownPromise;
 
-  /* test 12 */
   // Check first entry (page up)
-  doKey("down");
-  doKey("down");
-  doKey("page_up");
+  doKey("down"); // first
+  doKey("down"); // second
+  doKey("page_up"); // first
   doKey("return");
-  yield waitForCompletion();
+  yield promiseFormsProcessed();
   checkACForm("tempuser1", "temppass1");
+});
 
-  // Trigger autocomplete popup
+add_task(function* test_form1_pagedown_last() {
   restoreForm();
-  doKey("down");
-  yield runNextTest("expect popup");
+  let shownPromise = promiseACShown();
+  doKey("down"); // open
+  yield shownPromise;
 
   /* test 13 */
   // Check last entry (page down)
-  doKey("down");
-  doKey("page_down");
+  doKey("down"); // first
+  doKey("page_down"); // last
   doKey("return");
-  yield waitForCompletion();
+  yield promiseFormsProcessed();
   checkACForm("zzzuser4", "zzzpass4");
+});
+
+add_task(function* test_form1_untrusted_event() {
   restoreForm();
-  yield runNextTest();
+  yield spinEventLoop();
 
-  /* test 14 */
   // Send a fake (untrusted) event.
   checkACForm("", "");
   uname.value = "zzzuser4";
   sendFakeAutocompleteEvent(uname);
   yield spinEventLoop();
   checkACForm("zzzuser4", "");
+});
 
-  // Trigger autocomplete popup
+add_task(function* test_form1_delete() {
   restoreForm();
-  doKey("down");
-  testNum = 49;
-  yield runNextTest("expect popup");
+  let shownPromise = promiseACShown();
+  doKey("down"); // open
+  yield shownPromise;
 
   // XXX tried sending character "t" before/during dropdown to test
   // filtering, but had no luck. Seemed like the character was getting lost.
   // Setting uname.value didn't seem to work either. This works with a human
   // driver, so I'm not sure what's up.
 
-
-  /* test 50 */
   // Delete the first entry (of 4), "tempuser1"
   doKey("down");
   var numLogins;
-  numLogins = pwmgr.countLogins("http://mochi.test:8888", "http://autocomplete:8888", null);
+  numLogins = countLogins(chromeScript, "http://mochi.test:8888", "http://autocomplete:8888", null);
   is(numLogins, 5, "Correct number of logins before deleting one");
 
+  var deletionPromise = promiseStorageChanged(["removeLogin"]);
   // On OS X, shift-backspace and shift-delete work, just delete does not.
   // On Win/Linux, shift-backspace does not work, delete and shift-delete do.
   doKey("delete", shiftModifier);
+  yield deletionPromise;
 
   checkACForm("", "");
-  numLogins = pwmgr.countLogins("http://mochi.test:8888", "http://autocomplete:8888", null);
+  numLogins = countLogins(chromeScript, "http://mochi.test:8888", "http://autocomplete:8888", null);
   is(numLogins, 4, "Correct number of logins after deleting one");
+  notifyMenuChanged(4);
   doKey("return");
-  yield waitForCompletion();
+  yield promiseFormsProcessed();
   checkACForm("testuser2", "testpass2");
+});
 
-  // Trigger autocomplete popup
+add_task(function* test_form1_first_after_deletion() {
   restoreForm();
-  doKey("down");
-  yield runNextTest("expect popup");
+  let shownPromise = promiseACShown();
+  doKey("down"); // open
+  yield shownPromise;
 
-  /* test 51 */
   // Check the new first entry (of 3)
   doKey("down");
   doKey("return");
-  yield waitForCompletion();
+  yield promiseFormsProcessed();
   checkACForm("testuser2", "testpass2");
+});
 
-  // Trigger autocomplete popup
+add_task(function* test_form1_delete_second() {
   restoreForm();
-  doKey("down");
-  yield runNextTest("expect popup");
+  let shownPromise = promiseACShown();
+  doKey("down"); // open
+  yield shownPromise;
 
-  /* test 52 */
   // Delete the second entry (of 3), "testuser3"
   doKey("down");
   doKey("down");
   doKey("delete", shiftModifier);
   checkACForm("", "");
-  numLogins = pwmgr.countLogins("http://mochi.test:8888", "http://autocomplete:8888", null);
+  numLogins = countLogins(chromeScript, "http://mochi.test:8888", "http://autocomplete:8888", null);
   is(numLogins, 3, "Correct number of logins after deleting one");
   doKey("return");
-  yield waitForCompletion();
+  yield promiseFormsProcessed();
   checkACForm("zzzuser4", "zzzpass4");
+});
 
-  // Trigger autocomplete popup
+add_task(function* test_form1_first_after_deletion2() {
   restoreForm();
-  doKey("down");
-  yield runNextTest("expect popup");
+  let shownPromise = promiseACShown();
+  doKey("down"); // open
+  yield shownPromise;
 
-  /* test 53 */
-  // Check the new second entry (of 2)
+  // Check the new first entry (of 2)
   doKey("down");
   doKey("return");
-  yield waitForCompletion();
+  yield promiseFormsProcessed();
   checkACForm("testuser2", "testpass2");
+});
 
-  // Trigger autocomplete popup
+add_task(function* test_form1_delete_last() {
   restoreForm();
-  doKey("down");
-  yield runNextTest("expect popup");
+  let shownPromise = promiseACShown();
+  doKey("down"); // open
+  yield shownPromise;
 
   /* test 54 */
   // Delete the last entry (of 2), "zzzuser4"
   doKey("down");
   doKey("down");
   doKey("delete", shiftModifier);
   checkACForm("", "");
-  numLogins = pwmgr.countLogins("http://mochi.test:8888", "http://autocomplete:8888", null);
+  numLogins = countLogins(chromeScript, "http://mochi.test:8888", "http://autocomplete:8888", null);
   is(numLogins, 2, "Correct number of logins after deleting one");
   doKey("return");
-  yield waitForCompletion();
+  yield promiseFormsProcessed();
   checkACForm("testuser2", "testpass2");
+});
 
-  // Trigger autocomplete popup
+add_task(function* test_form1_first_after_3_deletions() {
   restoreForm();
-  doKey("down");
-  yield runNextTest("expect popup");
+  let shownPromise = promiseACShown();
+  doKey("down"); // open
+  yield shownPromise;
 
-  /* test 55 */
-  // Check the new second entry (of 2)
+  // Check the only remaining entry
   doKey("down");
   doKey("return");
-  yield waitForCompletion();
+  yield promiseFormsProcessed();
   checkACForm("testuser2", "testpass2");
+});
 
-  // Trigger autocomplete popup
+add_task(function* test_form1_check_only_entry_remaining() {
   restoreForm();
-  doKey("down");
-  yield runNextTest("expect popup");
+  let shownPromise = promiseACShown();
+  doKey("down"); // open
+  yield shownPromise;
 
   /* test 56 */
   // Delete the only remaining entry, "testuser2"
   doKey("down");
   doKey("delete", shiftModifier);
   //doKey("return");
   checkACForm("", "");
-  numLogins = pwmgr.countLogins("http://mochi.test:8888", "http://autocomplete:8888", null);
+  numLogins = countLogins(chromeScript, "http://mochi.test:8888", "http://autocomplete:8888", null);
   is(numLogins, 1, "Correct number of logins after deleting one");
-  pwmgr.removeLogin(login0); // remove the login that's not shown in the list.
-  testNum = 99;
-  yield runNextTest();
 
+  // remove the login that's not shown in the list.
+  setupScript.sendSyncMessage("removeLogin", "login0");
+});
 
-  /* Tests for single-user forms for ignoring autocomplete=off */
-
-  /* test 100 */
+/* Tests for single-user forms for ignoring autocomplete=off */
+add_task(function* test_form2() {
   // Turn our attention to form2
   uname = $_(2, "uname");
   pword = $_(2, "pword");
   checkACForm("singleuser5", "singlepass5");
 
-  // Trigger autocomplete popup
   restoreForm();
-  doKey("down");
-  yield runNextTest("expect popup");
+  let shownPromise = promiseACShown();
+  doKey("down"); // open
+  yield shownPromise;
 
-  /* test 101 */
   // Check first entry
   doKey("down");
   checkACForm("", ""); // value shouldn't update
   doKey("return"); // not "enter"!
-  yield waitForCompletion();
+  yield promiseFormsProcessed();
   checkACForm("singleuser5", "singlepass5");
-  restoreForm(); // clear field, so reloading test doesn't fail
-  yield runNextTest();
+});
 
-  /* test 102 */
-  // Turn our attention to form3
+add_task(function* test_form3() {
   uname = $_(3, "uname");
   pword = $_(3, "pword");
   checkACForm("singleuser5", "singlepass5");
+  restoreForm();
+  let shownPromise = promiseACShown();
+  doKey("down"); // open
+  yield shownPromise;
 
-  // Trigger autocomplete popup
-  restoreForm();
-  doKey("down");
-  yield runNextTest("expect popup");
-
-  /* test 103 */
   // Check first entry
   doKey("down");
   checkACForm("", ""); // value shouldn't update
   doKey("return"); // not "enter"!
-  yield waitForCompletion();
+  yield promiseFormsProcessed();
   checkACForm("singleuser5", "singlepass5");
-  yield runNextTest();
+});
 
-  /* test 104 */
-  // Turn our attention to form4
+add_task(function* test_form4() {
   uname = $_(4, "uname");
   pword = $_(4, "pword");
   checkACForm("singleuser5", "singlepass5");
+  restoreForm();
+  let shownPromise = promiseACShown();
+  doKey("down"); // open
+  yield shownPromise;
 
-  // Trigger autocomplete popup
-  restoreForm();
-  doKey("down");
-  yield runNextTest("expect popup");
-
-  /* test 105 */
   // Check first entry
   doKey("down");
   checkACForm("", ""); // value shouldn't update
   doKey("return"); // not "enter"!
-  yield waitForCompletion();
+  yield promiseFormsProcessed();
   checkACForm("singleuser5", "singlepass5");
-  yield runNextTest();
+});
 
-  /* test 106 */
-  // Turn our attention to form5
+add_task(function* test_form5() {
   uname = $_(5, "uname");
   pword = $_(5, "pword");
   checkACForm("singleuser5", "singlepass5");
+  restoreForm();
+  let shownPromise = promiseACShown();
+  doKey("down"); // open
+  yield shownPromise;
 
-  // Trigger autocomplete popup
-  restoreForm();
-  doKey("down");
-  yield runNextTest("expect popup");
-
-  /* test 107 */
   // Check first entry
   doKey("down");
   checkACForm("", ""); // value shouldn't update
   doKey("return"); // not "enter"!
-  yield waitForCompletion();
+  yield promiseFormsProcessed();
   checkACForm("singleuser5", "singlepass5");
-  yield runNextTest();
+});
 
-  /* test 108 */
-  // Turn our attention to form6
+add_task(function* test_form6() {
   // (this is a control, w/o autocomplete=off, to ensure the login
   // that was being suppressed would have been filled in otherwise)
   uname = $_(6, "uname");
   pword = $_(6, "pword");
   checkACForm("singleuser5", "singlepass5");
-  yield runNextTest();
+});
 
-  /* test 109 */
+add_task(function* test_form6_changeUsername() {
   // Test that the password field remains filled in after changing
   // the username.
   uname.focus();
   doKey("right");
   sendChar("X");
   // Trigger the 'blur' event on uname
   pword.focus();
   yield spinEventLoop();
   checkACForm("sXingleuser5", "singlepass5");
 
-  pwmgr.removeLogin(login5);
-  testNum = 499;
-  yield runNextTest();
+  setupScript.sendSyncMessage("removeLogin", "login5");
+});
 
-  /* test 500 */
-  // Turn our attention to form7
+add_task(function* test_form7() {
   uname = $_(7, "uname");
   pword = $_(7, "pword");
   checkACForm("", "");
 
   // Insert a new username field into the form. We'll then make sure
   // that invoking the autocomplete doesn't try to fill the form.
   var newField = document.createElement("input");
   newField.setAttribute("type", "text");
   newField.setAttribute("name", "uname2");
   pword.parentNode.insertBefore(newField, pword);
   is($_(7, "uname2").value, "", "Verifying empty uname2");
 
   // Delete login6B. It was created just to prevent filling in a login
   // automatically, removing it makes it more likely that we'll catch a
   // future regression with form filling here.
-  pwmgr.removeLogin(login6B);
+  setupScript.sendSyncMessage("removeLogin", "login6B");
+});
 
-  // Trigger autocomplete popup
+add_task(function* test_form7_2() {
   restoreForm();
-  doKey("down");
-  yield runNextTest("expect popup");
+  let shownPromise = promiseACShown();
+  doKey("down"); // open
+  yield shownPromise;
 
-  /* test 501 */
   // Check first entry
   doKey("down");
   checkACForm("", ""); // value shouldn't update
   doKey("return"); // not "enter"!
   // The form changes, so we expect the old username field to get the
   // selected autocomplete value, but neither the new username field nor
   // the password field should have any values filled in.
   yield spinEventLoop();
   checkACForm("form7user1", "");
   is($_(7, "uname2").value, "", "Verifying empty uname2");
   restoreForm(); // clear field, so reloading test doesn't fail
 
-  pwmgr.removeLogin(login6A);
-  testNum = 599;
-  yield runNextTest();
+  setupScript.sendSyncMessage("removeLogin", "login6A");
+});
 
-  /* test 600 */
-  // Turn our attention to form8
+add_task(function* test_form8() {
   uname = $_(8, "uname");
   pword = $_(8, "pword");
   checkACForm("form8user", "form8pass");
   restoreForm();
-  yield runNextTest();
+});
 
-  /* test 601 */
+add_task(function* test_form8_blur() {
   checkACForm("", "");
   // Focus the previous form to trigger a blur.
   $_(7, "uname").focus();
-  yield runNextTest();
+});
 
-  /* test 602 */
+add_task(function* test_form8_2() {
   checkACForm("", "");
   restoreForm();
-  yield runNextTest();
+});
 
-  /* test 603 */
+add_task(function* test_form8_3() {
   checkACForm("", "");
-  pwmgr.removeLogin(login7);
+  setupScript.sendSyncMessage("removeLogin", "login7");
+});
 
-  testNum = 699;
-  yield runNextTest();
-
-  /* test 700 */
+add_task(function* test_form9_filtering() {
   // Turn our attention to form9 to test the dropdown - bug 497541
   uname = $_(9, "uname");
   pword = $_(9, "pword");
   uname.focus();
+  let shownPromise = promiseACShown();
   sendString("form9userAB");
-  yield runNextTest("expect popup");
+  yield shownPromise;
 
-  /* test 701 */
   checkACForm("form9userAB", "");
   uname.focus();
   doKey("left");
+  shownPromise = promiseACShown();
   sendChar("A");
-  yield runNextTest("expect popup");
+  let results = yield shownPromise;
 
-  /* test 702 */
-  // check dropdown is updated after inserting "A"
   checkACForm("form9userAAB", "");
-  checkMenuEntries(["form9userAAB"]);
+  checkArrayValues(results, ["form9userAAB"], "Check dropdown is updated after inserting 'A'");
   doKey("down");
   doKey("return");
-  yield waitForCompletion();
+  yield promiseFormsProcessed();
   checkACForm("form9userAAB", "form9pass");
-  yield runNextTest();
+});
 
-  /* test 703 */
+add_task(function* test_form9_autocomplete_cache() {
   // Note that this addLogin call will only be seen by the autocomplete
   // attempt for the sendChar if we do not successfully cache the
   // autocomplete results.
-  pwmgr.addLogin(login8C);
+  setupScript.sendSyncMessage("addLogin", "login8C");
   uname.focus();
+  let promise0 = notifyMenuChanged(0);
   sendChar("z");
-  yield runNextTest();
+  yield promise0;
+  let popupState = yield getPopupState();
+  is(popupState.open, false, "Check popup shouldn't open");
 
-  /* test 704 */
   // check that empty results are cached - bug 496466
-  checkMenuEntries([]);
+  promise0 = notifyMenuChanged(0);
+  sendChar("z");
+  yield promise0;
+  popupState = yield getPopupState();
+  is(popupState.open, false, "Check popup stays closed due to cached empty result");
+});
 
-  /* test 705 */
+add_task(function* test_form10_formSubmitURLScheme() {
   // Check that formSubmitURL with different schemes matches
-  // Turn our attention to form10
   uname = $_(10, "uname");
   pword = $_(10, "pword");
-
-  // Trigger autocomplete popup
   restoreForm();
-  doKey("down");
-  yield runNextTest("expect popup");
+  let shownPromise = promiseACShown();
+  doKey("down"); // open
+  yield shownPromise;
 
   // Check first entry
   doKey("down");
   checkACForm("", ""); // value shouldn't update
   doKey("return"); // not "enter"!
-  yield waitForCompletion();
+  yield promiseFormsProcessed();
   checkACForm("testuser9", "testpass9");
-  yield runNextTest();
+});
 
-  // Turn our attention to form11 to test recipes
-  var recipeParent = yield getLoginRecipes();
-  recipeParent.add({
-    "hosts": ["mochi.test:8888"],
-    "usernameSelector": "input[name='1']",
-    "passwordSelector": "input[name='2']"
+add_task(function* test_form11_recipes() {
+  yield loadRecipes({
+    siteRecipes: [{
+      "hosts": ["mochi.test:8888"],
+      "usernameSelector": "input[name='1']",
+      "passwordSelector": "input[name='2']"
+    }],
   });
   uname = $_(11, "1");
   pword = $_(11, "2");
 
   // First test DOMAutocomplete
   // Switch the password field to type=password so _fillForm marks the username
   // field for autocomplete.
   pword.type = "password";
-  yield waitForCompletion();
+  yield promiseFormsProcessed();
   restoreForm();
   checkACForm("", "");
-  doKey("down");
-  yield runNextTest("expect popup");
+  let shownPromise = promiseACShown();
+  doKey("down"); // open
+  yield shownPromise;
 
   doKey("down");
   checkACForm("", ""); // value shouldn't update
   doKey("return"); // not "enter"!
-  yield waitForCompletion();
+  yield promiseFormsProcessed();
   checkACForm("testuser10", "testpass10");
 
   // Now test recipes with blur on the username field.
   restoreForm();
   checkACForm("", "");
   uname.value = "testuser10";
   checkACForm("testuser10", "");
   doKey("tab");
-  yield waitForCompletion();
+  yield promiseFormsProcessed();
   checkACForm("testuser10", "testpass10");
+  yield resetRecipes();
+});
 
-  recipeParent.reset();
-  yield runNextTest();
-
+add_task(function* test_form12_formless() {
   // Test form-less autocomplete
   uname = $_(12, "uname")
   pword = $_(12, "pword")
   restoreForm();
   checkACForm("", "");
-  // Trigger autocomplete popup
-  doKey("down");
-  yield runNextTest("expect popup");
+  let shownPromise = promiseACShown();
+  doKey("down"); // open
+  yield shownPromise;
 
   // Trigger autocomplete
   doKey("down");
   checkACForm("", ""); // value shouldn't update
+  let processedPromise = promiseFormsProcessed();
   doKey("return"); // not "enter"!
-  yield waitForCompletion();
+  yield processedPromise;
   checkACForm("testuser", "testpass");
-  yield runNextTest();
-
-  SimpleTest.finish();
-  return;
-}
-
-
-function checkMenuEntries(expectedValues) {
-    var actualValues = getMenuEntries();
-    is(actualValues.length, expectedValues.length, "Checking length of expected menu");
-    for (var i = 0; i < expectedValues.length; i++)
-        is(actualValues[i], expectedValues[i], "Checking menu entry #"+i);
-}
-
-var autocompletePopup;
-function getMenuEntries() {
-    var entries = [];
-
-    // Could perhaps pull values directly from the controller, but it seems
-    // more reliable to test the values that are actually in the tree?
-    var column = autocompletePopup.tree.columns[0];
-    var numRows = autocompletePopup.tree.view.rowCount;
-    for (var i = 0; i < numRows; i++) {
-        entries.push(autocompletePopup.tree.view.getValueAt(i, column));
-    }
-    return entries;
-}
-
-function startTest() {
-    var Ci = SpecialPowers.Ci;
-    chromeWin = SpecialPowers.wrap(window)
-                    .QueryInterface(Ci.nsIInterfaceRequestor)
-                    .getInterface(Ci.nsIWebNavigation)
-                    .QueryInterface(Ci.nsIDocShellTreeItem)
-                    .rootTreeItem
-                    .QueryInterface(Ci.nsIInterfaceRequestor)
-                    .getInterface(Ci.nsIDOMWindow)
-                    .QueryInterface(Ci.nsIDOMChromeWindow);
-    // shouldn't reach into browser internals like this and
-    // shouldn't assume ID is consistent across products
-    autocompletePopup = chromeWin.document.getElementById("PopupAutoComplete");
-    ok(autocompletePopup, "Got autocomplete popup");
-    tester = runTest();
-    tester.next();
-}
-
-window.addEventListener("runTests", startTest);
+});
 </script>
 </pre>
 </body>
 </html>
-
--- a/toolkit/components/passwordmgr/test/pwmgr_common.js
+++ b/toolkit/components/passwordmgr/test/pwmgr_common.js
@@ -277,16 +277,54 @@ function promiseFormsProcessed(expectedC
         SpecialPowers.removeObserver(onProcessedForm, "passwordmgr-processed-form");
         resolve(subject, data);
       }
     }
     SpecialPowers.addObserver(onProcessedForm, "passwordmgr-processed-form", false);
   });
 }
 
+function promiseStorageChanged(expectedChangeTypes) {
+  return new Promise((resolve, reject) => {
+    let onStorageChanged = SpecialPowers.wrapCallback(function osc(subject, topic, data) {
+      let changeType = expectedChangeTypes.shift();
+      is(data, changeType, "Check expected passwordmgr-storage-changed type");
+      if (expectedChangeTypes.length === 0) {
+        SpecialPowers.removeObserver(onStorageChanged, "passwordmgr-storage-changed");
+        resolve(subject);
+      }
+    });
+    SpecialPowers.addObserver(onStorageChanged, "passwordmgr-storage-changed", false);
+  });
+}
+
+function countLogins(chromeScript, formOrigin, submitOrigin, httpRealm) {
+  return chromeScript.sendSyncMessage("countLogins", {formOrigin, submitOrigin, httpRealm})[0][0];
+}
+
+function loadRecipes(recipes) {
+  return new Promise(resolve => {
+    chromeScript.addMessageListener("loadedRecipes", function loaded() {
+      chromeScript.removeMessageListener("loadedRecipes", loaded);
+      resolve(recipes);
+    });
+    chromeScript.sendAsyncMessage("loadRecipes", recipes);
+  });
+}
+
+function resetRecipes() {
+  return new Promise(resolve => {
+    chromeScript.addMessageListener("recipesReset", function reset() {
+      chromeScript.removeMessageListener("recipesReset", reset);
+      resolve();
+    });
+    chromeScript.sendAsyncMessage("resetRecipes");
+  });
+}
+
 /**
  * Run a function synchronously in the parent process and destroy it in the test cleanup function.
  * @param {Function|String} aFunctionOrURL - either a function that will be stringified and run
  *                                           or the URL to a JS file.
  * @return {Object} - the return value of loadChromeScript providing message-related methods.
  *                    @see loadChromeScript in specialpowersAPI.js
  */
 function runInParent(aFunctionOrURL) {
@@ -305,40 +343,53 @@ function runInParent(aFunctionOrURL) {
 function runChecksAfterCommonInit(aFunction = null) {
   SimpleTest.waitForExplicitFinish();
   let pwmgrCommonScript = runInParent(SimpleTest.getTestFileURL("pwmgr_common.js"));
   if (aFunction) {
     window.addEventListener("runTests", aFunction);
     pwmgrCommonScript.addMessageListener("registerRunTests", () => registerRunTests());
   }
   pwmgrCommonScript.sendSyncMessage("setupParent");
+  return pwmgrCommonScript;
 }
 
 // Code to run when loaded as a chrome script in tests via loadChromeScript
 if (this.addMessageListener) {
   const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
   var SpecialPowers = { Cc, Ci, Cr, Cu, };
   var ok, is;
   // Ignore ok/is in commonInit since they aren't defined in a chrome script.
   ok = is = () => {}; // eslint-disable-line no-native-reassign
 
+  Cu.import("resource://gre/modules/Services.jsm");
   Cu.import("resource://gre/modules/Task.jsm");
 
   addMessageListener("setupParent", ({selfFilling = false} = {selfFilling: false}) => {
     commonInit(selfFilling);
     sendAsyncMessage("doneSetup");
   });
 
   addMessageListener("loadRecipes", Task.async(function* loadRecipes(recipes) {
     var { LoginManagerParent } = Cu.import("resource://gre/modules/LoginManagerParent.jsm", {});
     var recipeParent = yield LoginManagerParent.recipeParentPromise;
     yield recipeParent.load(recipes);
     sendAsyncMessage("loadedRecipes", recipes);
   }));
 
+  addMessageListener("resetRecipes", Task.async(function* resetRecipes() {
+    let { LoginManagerParent } = Cu.import("resource://gre/modules/LoginManagerParent.jsm", {});
+    let recipeParent = yield LoginManagerParent.recipeParentPromise;
+    yield recipeParent.reset();
+    sendAsyncMessage("recipesReset");
+  }));
+
+  addMessageListener("countLogins", ({formOrigin, submitOrigin, httpRealm}) => {
+    return Services.logins.countLogins(formOrigin, submitOrigin, httpRealm);
+  });
+
   var globalMM = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
   globalMM.addMessageListener("RemoteLogins:onFormSubmit", function onFormSubmit(message) {
     sendAsyncMessage("formSubmissionProcessed", message.data, message.objects);
   });
 } else {
   // Code to only run in the mochitest pages (not in the chrome script).
   SimpleTest.registerCleanupFunction(() => {
     runInParent(function cleanupParent() {
--- a/toolkit/components/satchel/test/satchel_common.js
+++ b/toolkit/components/satchel/test/satchel_common.js
@@ -1,12 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+var gPopupShownExpected = false;
 var gPopupShownListener;
 var gLastAutoCompleteResults;
 var gChromeScript;
 
 /*
  * Returns the element with the specified |name| attribute.
  */
 function $_(formNum, name) {
@@ -220,23 +221,33 @@ function getPopupState(then = null) {
       if (then) {
         then(state);
       }
       resolve(state);
     });
   });
 }
 
+function listenForUnexpectedPopupShown() {
+  gChromeScript.addMessageListener("onpopupshown", function onPopupShown() {
+    if (!gPopupShownExpected) {
+      ok(false, "Unexpected autocomplete popupshown event");
+    }
+  });
+}
+
 /**
  * Resolve at the next popupshown event for the autocomplete popup
  * @return {Promise} with the results
  */
 function promiseACShown() {
+  gPopupShownExpected = true;
   return new Promise(resolve => {
     gChromeScript.addMessageListener("onpopupshown", ({ results }) => {
+      gPopupShownExpected = false;
       resolve(results);
     });
   });
 }
 
 function satchelCommonSetup() {
   var chromeURL = SimpleTest.getTestFileURL("parent_utils.js");
   gChromeScript = SpecialPowers.loadChromeScript(chromeURL);