Bug 1258860 - Make test_basic_form_autocomplete.html work in e10s. r=dolske
authorMatthew Noorenberghe <mozilla@noorenberghe.ca>
Wed, 13 Apr 2016 16:29:49 -0700
changeset 330958 b75748217b756e21c72362ee93373a70b76086a2
parent 330957 6107339190d155bf69311ac3fa694f8982664645
child 330959 d77a91e73d4a4c631f2185618dcafcbea5ca55e1
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdolske
bugs1258860
milestone48.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 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,18 +13,16 @@ 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' # Tests desktop autocomplete UI
 [test_master_password.html]
 skip-if = toolkit == 'android' # Tests desktop prompts
 [test_master_password_cleanup.html]
 skip-if = toolkit == 'android' # Tests desktop prompts
 [test_notifications_popup.html]
 skip-if = true || os == "linux" || toolkit == 'android' # bug 934057. Tests desktop doorhangers
 [test_prompt.html]
 skip-if = os == "linux" || toolkit == 'android' # Tests desktop prompts
--- 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' # Tests desktop prompts
 [test_bug_776171.html]
 [test_case_differences.html]
 skip-if = toolkit == 'android' # autocomplete
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,746 +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();
-
-    var unexpectedPopup = function() {
-      removePopupListener("popupshown", unexpectedPopup, false);
-      ok(false, "Test " + save + " should not show a popup");
-    };
-    addPopupListener("popupshown", unexpectedPopup, false);
+add_task(function* setup() {
+  listenForUnexpectedPopupShown();
+});
 
-    hitEventLoop(function() {
-      removePopupListener("popupshown", unexpectedPopup, false);
-      tester.next();
-    }, 100);
-
-    return undefined;
-  }
-
-  // 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);
-  }
+add_task(function* test_form1_initial_empty() {
+  yield SimpleTest.promiseFocus(window);
 
-  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")
+  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
@@ -297,16 +297,34 @@ function resetRecipes() {
     chromeScript.addMessageListener("recipesReset", function reset() {
       chromeScript.removeMessageListener("recipesReset", reset);
       resolve();
     });
     chromeScript.sendAsyncMessage("resetRecipes");
   });
 }
 
+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];
+}
+
 /**
  * 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) {
@@ -336,16 +354,17 @@ function runChecksAfterCommonInit(aFunct
 // 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) {
@@ -357,16 +376,20 @@ if (this.addMessageListener) {
 
   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);