fune/browser/components/payments/test/browser/head.js

723 lines
28 KiB
JavaScript

"use strict";
/* eslint
"no-unused-vars": ["error", {
vars: "local",
args: "none",
}],
*/
const BLANK_PAGE_PATH = "/browser/browser/components/payments/test/browser/blank_page.html";
const BLANK_PAGE_URL = "https://example.com" + BLANK_PAGE_PATH;
const RESPONSE_TIMEOUT_PREF = "dom.payments.response.timeout";
const SAVE_CREDITCARD_DEFAULT_PREF = "dom.payments.defaults.saveCreditCard";
const SAVE_ADDRESS_DEFAULT_PREF = "dom.payments.defaults.saveAddress";
const paymentSrv = Cc["@mozilla.org/dom/payments/payment-request-service;1"]
.getService(Ci.nsIPaymentRequestService);
const paymentUISrv = Cc["@mozilla.org/dom/payments/payment-ui-service;1"]
.getService(Ci.nsIPaymentUIService).wrappedJSObject;
const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm", {});
const {formAutofillStorage} = ChromeUtils.import(
"resource://formautofill/FormAutofillStorage.jsm", {});
const {OSKeyStoreTestUtils} = ChromeUtils.import(
"resource://testing-common/OSKeyStoreTestUtils.jsm", {});
const {PaymentTestUtils: PTU} = ChromeUtils.import(
"resource://testing-common/PaymentTestUtils.jsm", {});
ChromeUtils.import("resource:///modules/BrowserWindowTracker.jsm");
ChromeUtils.import("resource://gre/modules/CreditCard.jsm");
function getPaymentRequests() {
return Array.from(paymentSrv.enumerate());
}
/**
* Return the container (e.g. dialog or overlay) that the payment request contents are shown in.
* This abstracts away the details of the widget used so that this can more easily transition to
* another kind of dialog/overlay.
* @param {string} requestId
* @returns {Promise}
*/
async function getPaymentWidget(requestId) {
return BrowserTestUtils.waitForCondition(() => {
let {dialogContainer} = paymentUISrv.findDialog(requestId);
if (!dialogContainer) {
return false;
}
let paymentFrame = dialogContainer.querySelector(".paymentDialogContainerFrame");
if (!paymentFrame) {
return false;
}
return {
get closed() {
return !paymentFrame.isConnected;
},
frameElement: paymentFrame,
};
}, "payment dialog should be opened");
}
async function getPaymentFrame(widget) {
return widget.frameElement;
}
function waitForMessageFromWidget(messageType, widget = null) {
info("waitForMessageFromWidget: " + messageType);
return new Promise(resolve => {
Services.mm.addMessageListener("paymentContentToChrome", function onMessage({data, target}) {
if (data.messageType != messageType) {
return;
}
if (widget && widget != target) {
return;
}
resolve();
info(`Got ${messageType} from widget`);
Services.mm.removeMessageListener("paymentContentToChrome", onMessage);
});
});
}
async function waitForWidgetReady(widget = null) {
return waitForMessageFromWidget("paymentDialogReady", widget);
}
function spawnPaymentDialogTask(paymentDialogFrame, taskFn, args = null) {
return ContentTask.spawn(paymentDialogFrame.frameLoader, args, taskFn);
}
async function withMerchantTab({browser = gBrowser, url = BLANK_PAGE_URL} = {
browser: gBrowser,
url: BLANK_PAGE_URL,
}, taskFn) {
await BrowserTestUtils.withNewTab({
gBrowser: browser,
url,
}, taskFn);
paymentSrv.cleanup(); // Temporary measure until bug 1408234 is fixed.
await new Promise(resolve => {
SpecialPowers.exactGC(resolve);
});
}
/**
* Load the privileged payment dialog wrapper document in a new tab and run the
* task function.
*
* @param {string} requestId of the PaymentRequest
* @param {Function} taskFn to run in the dialog with the frame as an argument.
* @returns {Promise} which resolves when the dialog document is loaded
*/
function withNewDialogFrame(requestId, taskFn) {
async function dialogTabTask(dialogBrowser) {
let paymentRequestFrame = dialogBrowser.contentDocument.getElementById("paymentRequestFrame");
// Ensure the inner frame is loaded
await spawnPaymentDialogTask(paymentRequestFrame, async function ensureLoaded() {
await ContentTaskUtils.waitForCondition(() => content.document.readyState == "complete",
"Waiting for the unprivileged frame to load");
});
await taskFn(paymentRequestFrame);
}
let args = {
gBrowser,
url: `chrome://payments/content/paymentDialogWrapper.xul?requestId=${requestId}`,
};
return BrowserTestUtils.withNewTab(args, dialogTabTask);
}
async function withNewTabInPrivateWindow(args = {}, taskFn) {
let privateWin = await BrowserTestUtils.openNewBrowserWindow({private: true});
let tabArgs = Object.assign(args, {
browser: privateWin.gBrowser,
});
await withMerchantTab(tabArgs, taskFn);
await BrowserTestUtils.closeWindow(privateWin);
}
/**
* Spawn a content task inside the inner unprivileged frame of a privileged Payment Request dialog.
*
* @param {string} requestId
* @param {Function} contentTaskFn
* @param {object?} [args = null] for the content task
* @returns {Promise}
*/
function spawnTaskInNewDialog(requestId, contentTaskFn, args = null) {
return withNewDialogFrame(requestId, async function spawnTaskInNewDialog_tabTask(reqFrame) {
await spawnPaymentDialogTask(reqFrame, contentTaskFn, args);
});
}
async function addAddressRecord(address) {
let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
(subject, data) => data == "add");
let guid = await formAutofillStorage.addresses.add(address);
await onChanged;
return guid;
}
async function addCardRecord(card) {
let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
(subject, data) => data == "add");
let guid = await formAutofillStorage.creditCards.add(card);
await onChanged;
return guid;
}
/**
* Add address and creditCard records to the formautofill store
*
* @param {array=} addresses - The addresses to add to the formautofill address store
* @param {array=} cards - The cards to add to the formautofill creditCards store
* @returns {Promise}
*/
async function addSampleAddressesAndBasicCard(
addresses = [
PTU.Addresses.TimBL,
PTU.Addresses.TimBL2,
],
cards = [
PTU.BasicCards.JohnDoe,
]) {
let guids = {};
for (let i = 0; i < addresses.length; i++) {
guids[`address${i + 1}GUID`] = await addAddressRecord(addresses[i]);
}
for (let i = 0; i < cards.length; i++) {
guids[`card${i + 1}GUID`] = await addCardRecord(cards[i]);
}
return guids;
}
/**
* Checks that an address from autofill storage matches a Payment Request PaymentAddress.
* @param {PaymentAddress} paymentAddress
* @param {object} storageAddress
* @param {string} msg to describe the check
*/
function checkPaymentAddressMatchesStorageAddress(paymentAddress, storageAddress, msg) {
info(msg);
let addressLines = storageAddress["street-address"].split("\n");
is(paymentAddress.addressLine[0], addressLines[0], "Address line 1 should match");
is(paymentAddress.addressLine[1], addressLines[1], "Address line 2 should match");
is(paymentAddress.country, storageAddress.country, "Country should match");
is(paymentAddress.region, storageAddress["address-level1"] || "", "Region should match");
is(paymentAddress.city, storageAddress["address-level2"], "City should match");
is(paymentAddress.postalCode, storageAddress["postal-code"], "Zip code should match");
is(paymentAddress.organization, storageAddress.organization, "Org should match");
is(paymentAddress.recipient,
`${storageAddress["given-name"]} ${storageAddress["additional-name"]} ` +
`${storageAddress["family-name"]}`,
"Recipient name should match");
is(paymentAddress.phone, storageAddress.tel, "Phone should match");
}
/**
* Checks that a card from autofill storage matches a Payment Request MethodDetails response.
* @param {MethodDetails} methodDetails
* @param {object} card
* @param {string} msg to describe the check
*/
function checkPaymentMethodDetailsMatchesCard(methodDetails, card, msg) {
info(msg);
// The card expiry month should be a zero-padded two-digit string.
let cardExpiryMonth = card["cc-exp-month"].toString().padStart(2, "0");
is(methodDetails.cardholderName, card["cc-name"], "Check cardholderName");
is(methodDetails.cardNumber, card["cc-number"], "Check cardNumber");
is(methodDetails.expiryMonth, cardExpiryMonth, "Check expiryMonth");
is(methodDetails.expiryYear, card["cc-exp-year"], "Check expiryYear");
}
/**
* Create a PaymentRequest object with the given parameters, then
* run the given merchantTaskFn.
*
* @param {Object} browser
* @param {Object} options
* @param {Object} options.methodData
* @param {Object} options.details
* @param {Object} options.options
* @param {Function} options.merchantTaskFn
* @returns {Object} References to the window, requestId, and frame
*/
async function setupPaymentDialog(browser, {methodData, details, options, merchantTaskFn}) {
let dialogReadyPromise = waitForWidgetReady();
let {requestId} = await ContentTask.spawn(browser,
{
methodData,
details,
options,
},
merchantTaskFn);
ok(requestId, "requestId should be defined");
// get a reference to the UI dialog and the requestId
let [win] = await Promise.all([getPaymentWidget(requestId), dialogReadyPromise]);
ok(win, "Got payment widget");
is(win.closed, false, "dialog should not be closed");
let frame = await getPaymentFrame(win);
ok(frame, "Got payment frame");
await dialogReadyPromise;
info("dialog ready");
await spawnPaymentDialogTask(frame, () => {
let elementHeight = (element) =>
element.getBoundingClientRect().height;
content.isHidden = (element) => elementHeight(element) == 0;
content.isVisible = (element) => elementHeight(element) > 0;
content.fillField = async function fillField(field, value) {
// Keep in-sync with the copy in payments_common.js but with EventUtils methods called on a
// EventUtils object.
field.focus();
if (field.localName == "select") {
if (field.value == value) {
// Do nothing
return;
}
field.value = value;
field.dispatchEvent(new content.window.Event("input", {bubbles: true}));
field.dispatchEvent(new content.window.Event("change", {bubbles: true}));
return;
}
while (field.value) {
EventUtils.sendKey("BACK_SPACE", content.window);
}
EventUtils.sendString(value, content.window);
}
;
});
await injectEventUtilsInContentTask(frame);
info("helper functions injected into frame");
return {win, requestId, frame};
}
/**
* Open a merchant tab with the given merchantTaskFn to create a PaymentRequest
* and then open the associated PaymentRequest dialog in a new tab and run the
* associated dialogTaskFn. The same taskArgs are passed to both functions.
*
* @param {Function} merchantTaskFn
* @param {Function} dialogTaskFn
* @param {Object} taskArgs
* @param {Object} options
* @param {string} options.origin
*/
async function spawnInDialogForMerchantTask(merchantTaskFn, dialogTaskFn, taskArgs, {
browser,
origin = "https://example.com",
} = {
origin: "https://example.com",
}) {
await withMerchantTab({
browser,
url: origin + BLANK_PAGE_PATH,
}, async merchBrowser => {
let {win, frame} = await setupPaymentDialog(merchBrowser, {
...taskArgs,
merchantTaskFn,
});
await spawnPaymentDialogTask(frame, dialogTaskFn, taskArgs);
spawnPaymentDialogTask(frame, PTU.DialogContentTasks.manuallyClickCancel);
await BrowserTestUtils.waitForCondition(() => win.closed, "dialog should be closed");
});
}
async function loginAndCompletePayment(frame) {
let osKeyStoreLoginShown = OSKeyStoreTestUtils.waitForOSKeyStoreLogin(true);
await spawnPaymentDialogTask(frame, PTU.DialogContentTasks.completePayment);
await osKeyStoreLoginShown;
}
async function setupFormAutofillStorage() {
await formAutofillStorage.initialize();
}
function cleanupFormAutofillStorage() {
formAutofillStorage.addresses.removeAll();
formAutofillStorage.creditCards.removeAll();
}
add_task(async function setup_head() {
SpecialPowers.registerConsoleListener(function onConsoleMessage(msg) {
if (msg.isWarning || !msg.errorMessage) {
// Ignore warnings and non-errors.
return;
}
if (msg.category == "CSP_CSPViolationWithURI" && msg.errorMessage.includes("at inline")) {
// Ignore unknown CSP error.
return;
}
if (msg.message && msg.message.match(/docShell is null.*BrowserUtils.jsm/)) {
// Bug 1478142 - Console spam from the Find Toolbar.
return;
}
if (msg.message && msg.message.match(/PrioEncoder is not defined/)) {
// Bug 1492638 - Console spam from TelemetrySession.
return;
}
if (msg.message && msg.message.match(/devicePixelRatio.*FaviconLoader.jsm/)) {
return;
}
if (msg.errorMessage == "AbortError: The operation was aborted. " &&
msg.sourceName == "" && msg.lineNumber == 0) {
return;
}
ok(false, msg.message || msg.errorMessage);
});
OSKeyStoreTestUtils.setup();
await setupFormAutofillStorage();
registerCleanupFunction(async function cleanup() {
paymentSrv.cleanup();
cleanupFormAutofillStorage();
await OSKeyStoreTestUtils.cleanup();
Services.prefs.clearUserPref(RESPONSE_TIMEOUT_PREF);
Services.prefs.clearUserPref(SAVE_CREDITCARD_DEFAULT_PREF);
Services.prefs.clearUserPref(SAVE_ADDRESS_DEFAULT_PREF);
SpecialPowers.postConsoleSentinel();
// CreditCard.jsm is imported into the global scope. It needs to be deleted
// else it outlives the test and is reported as a leak.
delete window.CreditCard;
});
});
function deepClone(obj) {
return JSON.parse(JSON.stringify(obj));
}
async function selectPaymentDialogShippingAddressByCountry(frame, country) {
await spawnPaymentDialogTask(frame,
PTU.DialogContentTasks.selectShippingAddressByCountry,
country);
}
async function navigateToAddAddressPage(frame, aOptions = {}) {
ok(aOptions.initialPageId, "initialPageId option supplied");
ok(aOptions.addressPageId, "addressPageId option supplied");
ok(aOptions.addLinkSelector, "addLinkSelector option supplied");
await spawnPaymentDialogTask(frame, async (options) => {
let {
PaymentTestUtils,
} = ChromeUtils.import("resource://testing-common/PaymentTestUtils.jsm", {});
info("navigateToAddAddressPage: check we're on the expected page first");
await PaymentTestUtils.DialogContentUtils.waitForState(content, (state) => {
info("current page state: " + state.page.id + " waiting for: " + options.initialPageId);
return state.page.id == options.initialPageId;
}, "Check initial page state");
// click through to add/edit address page
info("navigateToAddAddressPage: click the link");
let addLink = content.document.querySelector(options.addLinkSelector);
addLink.click();
info("navigateToAddAddressPage: wait for address page");
await PaymentTestUtils.DialogContentUtils.waitForState(content, (state) => {
return state.page.id == options.addressPageId && !state.page.guid;
}, "Check add page state");
}, aOptions);
}
async function navigateToAddShippingAddressPage(frame, aOptions = {}) {
let options = Object.assign({
addLinkSelector: "address-picker[selected-state-key=\"selectedShippingAddress\"] .add-link",
initialPageId: "payment-summary",
addressPageId: "shipping-address-page",
}, aOptions);
await navigateToAddAddressPage(frame, options);
}
async function fillInBillingAddressForm(frame, aAddress, aOptions = {}) {
// For now billing and shipping address forms have the same fields but that may
// change so use separarate helpers.
let address = Object.assign({}, aAddress);
// Email isn't used on address forms, only payer/contact ones.
delete address.email;
let options = Object.assign({
addressPageId: "billing-address-page",
expectedSelectedStateKey: ["basic-card-page", "billingAddressGUID"],
}, aOptions);
return fillInAddressForm(frame, address, options);
}
async function fillInShippingAddressForm(frame, aAddress, aOptions) {
let address = Object.assign({}, aAddress);
// Email isn't used on address forms, only payer/contact ones.
delete address.email;
return fillInAddressForm(frame, address, {
expectedSelectedStateKey: ["selectedShippingAddress"],
...aOptions,
});
}
async function fillInPayerAddressForm(frame, aAddress) {
let address = Object.assign({}, aAddress);
let payerFields = ["given-name", "additional-name", "family-name", "tel", "email"];
for (let fieldName of Object.keys(address)) {
if (payerFields.includes(fieldName)) {
continue;
}
delete address[fieldName];
}
return fillInAddressForm(frame, address, {
expectedSelectedStateKey: ["selectedPayerAddress"],
});
}
/**
* @param {HTMLElement} frame
* @param {object} aAddress
* @param {object} [aOptions = {}]
* @param {boolean} [aOptions.setPersistCheckedValue = undefined] How to set the persist checkbox.
* @param {string[]} [expectedSelectedStateKey = undefined] The expected selectedStateKey for
address-page.
*/
async function fillInAddressForm(frame, aAddress, aOptions = {}) {
await spawnPaymentDialogTask(frame, async (args) => {
let {address, options = {}} = args;
let {requestStore} = Cu.waiveXrays(content.document.querySelector("payment-dialog"));
let currentState = requestStore.getState();
let addressForm = content.document.getElementById(currentState.page.id);
ok(addressForm, "found the addressForm: " + addressForm.getAttribute("id"));
if (options.expectedSelectedStateKey) {
Assert.deepEqual(addressForm.getAttribute("selected-state-key").split("|"),
options.expectedSelectedStateKey,
"Check address page selectedStateKey");
}
if (typeof(address.country) != "undefined") {
// Set the country first so that the appropriate fields are visible.
let countryField = addressForm.querySelector("#country");
ok(!countryField.disabled, "Country Field shouldn't be disabled");
await content.fillField(countryField, address.country);
is(countryField.value, address.country, "country value is correct after fillField");
}
// fill the form
info("fillInAddressForm: fill the form with address: " + JSON.stringify(address));
for (let [key, val] of Object.entries(address)) {
let field = addressForm.querySelector(`#${key}`);
if (!field) {
ok(false, `${key} field not found`);
}
ok(!field.disabled, `Field #${key} shouldn't be disabled`);
await content.fillField(field, val);
is(field.value, val, `${key} value is correct after fillField`);
}
let persistCheckbox = Cu.waiveXrays(
addressForm.querySelector(".persist-checkbox"));
// only touch the checked state if explicitly told to in the options
if (options.hasOwnProperty("setPersistCheckedValue")) {
info("fillInAddressForm: Manually setting the persist checkbox checkedness to: " +
options.setPersistCheckedValue);
Cu.waiveXrays(persistCheckbox).checked = options.setPersistCheckedValue;
}
info(`fillInAddressForm, persistCheckbox.checked: ${persistCheckbox.checked}`);
}, {address: aAddress, options: aOptions});
}
async function verifyPersistCheckbox(frame, aOptions = {}) {
await spawnPaymentDialogTask(frame, async (args) => {
let {options = {}} = args;
// ensure card/address is persisted or not based on the temporary option given
info("verifyPersistCheckbox, got options: " + JSON.stringify(options));
let persistCheckbox = Cu.waiveXrays(
content.document.querySelector(options.checkboxSelector));
if (options.isEditing) {
ok(persistCheckbox.hidden, "checkbox should be hidden when editing a record");
} else {
ok(!persistCheckbox.hidden, "checkbox should be visible when adding a new record");
is(persistCheckbox.checked, options.expectPersist,
`persist checkbox state is expected to be ${options.expectPersist}`);
}
}, {options: aOptions});
}
async function verifyCardNetwork(frame, aOptions = {}) {
aOptions.supportedNetworks = CreditCard.SUPPORTED_NETWORKS;
await spawnPaymentDialogTask(frame, async (args) => {
let {options = {}} = args;
// ensure the network picker is visible, has the right contents and expected value
let networkSelect = Cu.waiveXrays(
content.document.querySelector(options.networkSelector));
ok(content.isVisible(networkSelect),
"The network selector should always be visible");
is(networkSelect.childElementCount, options.supportedNetworks.length + 1,
"Should have one more than the number of supported networks");
is(networkSelect.children[0].value, "",
"The first option should be the blank/empty option");
is(networkSelect.value, options.expectedNetwork,
`The network picker should have the expected value`);
}, {options: aOptions});
}
async function submitAddressForm(frame, aAddress, aOptions = {
nextPageId: "payment-summary",
}) {
await spawnPaymentDialogTask(frame, async (args) => {
let {options = {}} = args;
let nextPageId = options.nextPageId || "payment-summary";
let {
PaymentTestUtils,
} = ChromeUtils.import("resource://testing-common/PaymentTestUtils.jsm", {});
let oldState = await PaymentTestUtils.DialogContentUtils.getCurrentState(content);
let pageId = oldState.page.id;
// submit the form to return to summary page
content.document.querySelector(`#${pageId} button.primary`).click();
let currState = await PaymentTestUtils.DialogContentUtils.waitForState(content, (state) => {
return state.page.id == nextPageId;
}, `submitAddressForm: Switched back to ${nextPageId}`);
let savedCount = Object.keys(currState.savedAddresses).length;
let tempCount = Object.keys(currState.tempAddresses).length;
let oldSavedCount = Object.keys(oldState.savedAddresses).length;
let oldTempCount = Object.keys(oldState.tempAddresses).length;
if (options.isEditing) {
is(tempCount, oldTempCount, "tempAddresses count didn't change");
is(savedCount, oldSavedCount, "savedAddresses count didn't change");
} else if (options.expectPersist) {
is(tempCount, oldTempCount, "tempAddresses count didn't change");
is(savedCount, oldSavedCount + 1, "Entry added to savedAddresses");
} else {
is(tempCount, oldTempCount + 1, "Entry added to tempAddresses");
is(savedCount, oldSavedCount, "savedAddresses count didn't change");
}
}, {address: aAddress, options: aOptions});
}
async function manuallyAddShippingAddress(frame, aAddress, aOptions = {}) {
let options = Object.assign({
expectPersist: true,
isEditing: false,
}, aOptions, {
checkboxSelector: "#shipping-address-page .persist-checkbox",
});
await navigateToAddShippingAddressPage(frame);
info("manuallyAddShippingAddress, fill in address form with options: " + JSON.stringify(options));
await fillInShippingAddressForm(frame, aAddress, options);
info("manuallyAddShippingAddress, verifyPersistCheckbox with options: " +
JSON.stringify(options));
await verifyPersistCheckbox(frame, options);
await submitAddressForm(frame, aAddress, options);
}
async function navigateToAddCardPage(frame, aOptions = {
addLinkSelector: "payment-method-picker .add-link",
}) {
await spawnPaymentDialogTask(frame, async (options) => {
let {
PaymentTestUtils,
} = ChromeUtils.import("resource://testing-common/PaymentTestUtils.jsm", {});
// check were on the summary page first
await PaymentTestUtils.DialogContentUtils.waitForState(content, (state) => {
return !state.page.id || (state.page.id == "payment-summary");
}, "Check summary page state");
// click through to add/edit card page
let addLink = content.document.querySelector(options.addLinkSelector);
addLink.click();
// wait for card page
await PaymentTestUtils.DialogContentUtils.waitForState(content, (state) => {
return state.page.id == "basic-card-page";
}, "Check add/edit page state");
}, aOptions);
}
async function fillInCardForm(frame, aCard, aOptions = {}) {
await spawnPaymentDialogTask(frame, async (args) => {
let {card, options = {}} = args;
// fill the form
info("fillInCardForm: fill the form with card: " + JSON.stringify(card));
for (let [key, val] of Object.entries(card)) {
let field = content.document.getElementById(key);
if (!field) {
ok(false, `${key} field not found`);
}
ok(!field.disabled, `Field #${key} shouldn't be disabled`);
// Reset the value first so that we properly handle typing the value
// already selected which may select another option with the same prefix.
field.value = "";
ok(!field.value, "Field value should be reset before typing");
field.blur();
field.focus();
// Using waitForEvent here causes the test to hang, but
// waitForCondition and checking activeElement does the trick. The root cause
// of this should be investigated further.
await ContentTaskUtils.waitForCondition(() => field == content.document.activeElement,
`Waiting for field #${key} to get focus`);
// cc-exp-* fields are numbers so convert to strings and pad left with 0
let fillValue = val.toString().padStart(2, "0");
EventUtils.synthesizeKey(fillValue, {}, Cu.waiveXrays(content.window));
// cc-exp-* field values are not padded, so compare with unpadded string.
is(field.value, val.toString(), `${key} value is correct after sendString`);
}
info([...content.document.getElementById("cc-exp-year").options].map(op => op.label).join(","));
let persistCheckbox = content.document.querySelector(options.checkboxSelector);
// only touch the checked state if explicitly told to in the options
if (options.hasOwnProperty("setPersistCheckedValue")) {
info("fillInCardForm: Manually setting the persist checkbox checkedness to: " +
options.setPersistCheckedValue);
Cu.waiveXrays(persistCheckbox).checked = options.setPersistCheckedValue;
}
}, {card: aCard, options: aOptions});
}
// The JSDoc validator does not support @returns tags in abstract functions or
// star functions without return statements.
/* eslint-disable valid-jsdoc */
/**
* Inject `EventUtils` helpers into ContentTask scope.
*
* This helper is automatically exposed to mochitest browser tests,
* but is missing from content task scope.
* You should call this method only once per <browser> tag
*
* @param {xul:browser} browser
* Reference to the browser in which we load content task
*/
/* eslint-enable valid-jsdoc */
async function injectEventUtilsInContentTask(browser) {
await spawnPaymentDialogTask(browser, async function injectEventUtils() {
if ("EventUtils" in this) {
return;
}
const EventUtils = this.EventUtils = {};
EventUtils.window = {};
EventUtils.parent = EventUtils.window;
/* eslint-disable camelcase */
EventUtils._EU_Ci = Ci;
EventUtils._EU_Cc = Cc;
/* eslint-enable camelcase */
// EventUtils' `sendChar` function relies on the navigator to synthetize events.
EventUtils.navigator = content.navigator;
EventUtils.KeyboardEvent = content.KeyboardEvent;
Services.scriptloader.loadSubScript(
"chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
});
}