gecko-dev/toolkit/components/satchel/test/parent_utils.js
Masayuki Nakano 6aaf414572 Bug 1690358 - part 1: Make getPopupState in satchel's parent_utils.js wait to reply stabler state of the popup r=dimi
`MozAutocompleteRichlistboxPopup` sets its `mPopupOpen` to `false` when it
receives `popuphiding` instead of `popuphidden`.
https://searchfox.org/mozilla-central/rev/63fcc3f1a2cc73488d8986f4cf91fce2cd4b7564/toolkit/content/widgets/autocomplete-popup.js#575,582

Therefore, `getPopupState` in satchel's `parent_utils.js` may run before
`popuphidden`, and remote content may synthesize another event before
`popuphidden`.  This may cause intermittent failure of any tests which
use `satchel_common.js`.

This patch makes `getPopupState` send the reply message after the popup
state stable.

Differential Revision: https://phabricator.services.mozilla.com/D107172
2021-03-09 12:26:08 +00:00

223 lines
6 KiB
JavaScript

/* eslint-env mozilla/frame-script */
// assert is available to chrome scripts loaded via SpecialPowers.loadChromeScript.
/* global assert */
const { FormHistory } = ChromeUtils.import(
"resource://gre/modules/FormHistory.jsm"
);
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { ContentTaskUtils } = ChromeUtils.import(
"resource://testing-common/ContentTaskUtils.jsm"
);
const { TestUtils } = ChromeUtils.import(
"resource://testing-common/TestUtils.jsm"
);
var gAutocompletePopup = Services.ww.activeWindow.document.getElementById(
"PopupAutoComplete"
);
assert.ok(gAutocompletePopup, "Got autocomplete popup");
var ParentUtils = {
getMenuEntries() {
let entries = [];
let numRows = gAutocompletePopup.view.matchCount;
for (let i = 0; i < numRows; i++) {
entries.push(gAutocompletePopup.view.getValueAt(i));
}
return entries;
},
cleanUpFormHist(callback) {
FormHistory.update(
{ op: "remove" },
{
handleCompletion: callback,
}
);
},
updateFormHistory(changes) {
let handler = {
handleError(error) {
assert.ok(false, error);
sendAsyncMessage("formHistoryUpdated", { ok: false });
},
handleCompletion(reason) {
if (!reason) {
sendAsyncMessage("formHistoryUpdated", { ok: true });
}
},
};
FormHistory.update(changes, handler);
},
popupshownListener() {
let results = this.getMenuEntries();
sendAsyncMessage("onpopupshown", { results });
},
countEntries(name, value) {
let obj = {};
if (name) {
obj.fieldname = name;
}
if (value) {
obj.value = value;
}
let count = 0;
let listener = {
handleResult(result) {
count = result;
},
handleError(error) {
assert.ok(false, error);
sendAsyncMessage("entriesCounted", { ok: false });
},
handleCompletion(reason) {
if (!reason) {
sendAsyncMessage("entriesCounted", { ok: true, count });
}
},
};
FormHistory.count(obj, listener);
},
checkRowCount(expectedCount, expectedFirstValue = null) {
ContentTaskUtils.waitForCondition(() => {
// This may be called before gAutocompletePopup has initialised
// which causes it to throw
try {
return (
gAutocompletePopup.view.matchCount === expectedCount &&
(!expectedFirstValue ||
expectedCount <= 1 ||
gAutocompletePopup.view.getValueAt(0) === expectedFirstValue)
);
} catch (e) {
return false;
}
}, "Waiting for row count change: " + expectedCount + " First value: " + expectedFirstValue).then(
() => {
let results = this.getMenuEntries();
sendAsyncMessage("gotMenuChange", { results });
}
);
},
checkSelectedIndex(expectedIndex) {
ContentTaskUtils.waitForCondition(() => {
return (
gAutocompletePopup.popupOpen &&
gAutocompletePopup.selectedIndex === expectedIndex
);
}, "Checking selected index").then(() => {
sendAsyncMessage("gotSelectedIndex");
});
},
// Tests using this function need to flip pref for exceptional use of
// `new Function` / `eval()`.
// See test_autofill_and_ordinal_forms.html for example.
testMenuEntry(index, statement) {
ContentTaskUtils.waitForCondition(() => {
let el = gAutocompletePopup.richlistbox.getItemAtIndex(index);
let testFunc = new Services.ww.activeWindow.Function(
"el",
`return ${statement}`
);
return gAutocompletePopup.popupOpen && el && testFunc(el);
}, "Testing menu entry").then(() => {
sendAsyncMessage("menuEntryTested");
});
},
getPopupState() {
function reply() {
sendAsyncMessage("gotPopupState", {
open: gAutocompletePopup.popupOpen,
selectedIndex: gAutocompletePopup.selectedIndex,
direction: gAutocompletePopup.style.direction,
});
}
// If the popup state is stable, we can reply immediately. However, if
// it's showing or hiding, we should wait its finish and then, send the
// reply.
if (
gAutocompletePopup.state == "open" ||
gAutocompletePopup.state == "closed"
) {
reply();
return;
}
const stablerState =
gAutocompletePopup.state == "showing" ? "open" : "closed";
TestUtils.waitForCondition(
() => gAutocompletePopup.state == stablerState,
`Waiting for autocomplete popup getting "${stablerState}" state`
).then(reply);
},
observe(subject, topic, data) {
assert.ok(topic === "satchel-storage-changed");
sendAsyncMessage("satchel-storage-changed", { subject: null, topic, data });
},
cleanup() {
gAutocompletePopup.removeEventListener(
"popupshown",
this._popupshownListener
);
this.cleanUpFormHist(() => {
sendAsyncMessage("cleanup-done");
});
},
};
ParentUtils._popupshownListener = ParentUtils.popupshownListener.bind(
ParentUtils
);
gAutocompletePopup.addEventListener(
"popupshown",
ParentUtils._popupshownListener
);
ParentUtils.cleanUpFormHist();
addMessageListener("updateFormHistory", msg => {
ParentUtils.updateFormHistory(msg.changes);
});
addMessageListener("countEntries", ({ name, value }) => {
ParentUtils.countEntries(name, value);
});
addMessageListener(
"waitForMenuChange",
({ expectedCount, expectedFirstValue }) => {
ParentUtils.checkRowCount(expectedCount, expectedFirstValue);
}
);
addMessageListener("waitForSelectedIndex", ({ expectedIndex }) => {
ParentUtils.checkSelectedIndex(expectedIndex);
});
addMessageListener("waitForMenuEntryTest", ({ index, statement }) => {
ParentUtils.testMenuEntry(index, statement);
});
addMessageListener("getPopupState", () => {
ParentUtils.getPopupState();
});
addMessageListener("addObserver", () => {
Services.obs.addObserver(ParentUtils, "satchel-storage-changed");
});
addMessageListener("removeObserver", () => {
Services.obs.removeObserver(ParentUtils, "satchel-storage-changed");
});
addMessageListener("cleanup", () => {
ParentUtils.cleanup();
});