Bug 1648551: Treat form in <iframe> "pagehide" event as form submission r=zbraniecki

Differential Revision: https://phabricator.services.mozilla.com/D81931
This commit is contained in:
Adam Roach [:abr] 2020-07-11 00:51:05 +00:00
Родитель bfb49cc697
Коммит f676613244
4 изменённых файлов: 139 добавлений и 16 удалений

Просмотреть файл

@ -519,9 +519,15 @@ var FormAutofillContent = {
* 3. Number of filled fields is less than autofill threshold
*
* @param {HTMLElement} formElement Root element which receives submit event.
* @param {Window} domWin Content window only passed for unit tests
* @param {Window} domWin Content window; passed for unit tests and when
* invoked by the FormAutofillSection
* @param {Object} handler FormAutofillHander, if known by caller
*/
formSubmitted(formElement, domWin = formElement.ownerGlobal) {
formSubmitted(
formElement,
domWin = formElement.ownerGlobal,
handler = undefined
) {
this.debug("Handling form submission");
if (!FormAutofill.isAutofillEnabled) {
@ -535,7 +541,7 @@ var FormAutofillContent = {
return;
}
let handler = this._formsDetails.get(formElement);
handler = handler ?? this._formsDetails.get(formElement);
if (!handler) {
this.debug("Form element could not map to an existing handler");
return;
@ -724,7 +730,10 @@ var FormAutofillContent = {
let formHandler = this._getFormHandler(element);
if (!formHandler) {
let formLike = FormLikeFactory.createFromField(element);
formHandler = new FormAutofillHandler(formLike);
formHandler = new FormAutofillHandler(
formLike,
this.formSubmitted.bind(this)
);
} else if (!formHandler.updateFormIfNeeded(element)) {
this.debug("No control is removed or inserted since last collection.");
return;

Просмотреть файл

@ -885,8 +885,46 @@ class FormAutofillAddressSection extends FormAutofillSection {
}
class FormAutofillCreditCardSection extends FormAutofillSection {
constructor(fieldDetails, winUtils) {
/**
* Credit Card Section Constructor
*
* @param {Object} fieldDetails
* The fieldDetail objects for the fields in this section
* @param {Object} winUtils
* A WindowUtils reference for the Window the section appears in
* @param {Object} handler
* The FormAutofillHandler responsible for this section
*/
constructor(fieldDetails, winUtils, handler) {
super(fieldDetails, winUtils);
this.handler = handler;
// For valid sections, check whether the section is in an
// <iframe>; and, if so, watch for the <iframe> to pagehide.
// If the section is invalid, then the superclass constructor
// will have cleared out `this.fieldDetails`.
if (this.fieldDetails.length) {
if (handler.window.location != handler.window.parent?.location) {
log.debug(
"Credit card form is in an iframe -- watching for pagehide",
fieldDetails
);
handler.window.addEventListener(
"pagehide",
this._handlePageHide.bind(this)
);
}
}
}
_handlePageHide(event) {
this.handler.window.removeEventListener(
"pagehide",
this._handlePageHide.bind(this)
);
log.debug("Credit card subframe is pagehideing", this.handler.form);
this.handler.onFormSubmitted();
}
isValidSection() {
@ -1087,19 +1125,37 @@ class FormAutofillHandler {
* Initialize the form from `FormLike` object to handle the section or form
* operations.
* @param {FormLike} form Form that need to be auto filled
* @param {function} onFormSubmitted Function that can be invoked
* to simulate form submission. Function is passed
* three arguments: (1) a FormLike for the form being
* submitted, (2) the corresponding Window, and (3) the
* responsible FormAutofillHandler.
*/
constructor(form) {
constructor(form, onFormSubmitted = () => {}) {
this._updateForm(form);
/**
* The window to which this form belongs
*/
this.window = this.form.rootElement.ownerGlobal;
/**
* A WindowUtils reference of which Window the form belongs
*/
this.winUtils = this.form.rootElement.ownerGlobal.windowUtils;
this.winUtils = this.window.windowUtils;
/**
* Time in milliseconds since epoch when a user started filling in the form.
*/
this.timeStartedFillingMS = null;
/**
* This function is used if the form handler (or one of its sections)
* determines that it needs to act as if the form had been submitted.
*/
this.onFormSubmitted = () => {
onFormSubmitted(this.form, this.window, this);
};
}
set focusedInput(element) {
@ -1214,7 +1270,8 @@ class FormAutofillHandler {
} else if (type == FormAutofillUtils.SECTION_TYPES.CREDIT_CARD) {
section = new FormAutofillCreditCardSection(
fieldDetails,
this.winUtils
this.winUtils,
this
);
} else {
throw new Error("Unknown field type.");

Просмотреть файл

@ -194,7 +194,7 @@ add_task(async function test_submit_untouched_creditCard_form_iframe() {
creditCards = await getCreditCards();
is(creditCards.length, 1, "Still 1 credit card");
is(creditCards[0].timesUsed, 1, "timesUsed field set to 1");
is(creditCards[0].timesUsed, 2, "timesUsed field set to 2");
is(
SpecialPowers.getIntPref(CREDITCARDS_USED_STATUS_PREF),
3,
@ -204,6 +204,59 @@ add_task(async function test_submit_untouched_creditCard_form_iframe() {
await removeAllRecords();
});
add_task(async function test_iframe_unload_save_card() {
await SpecialPowers.pushPrefEnv({
set: [[CREDITCARDS_USED_STATUS_PREF, 0]],
});
await BrowserTestUtils.withNewTab(
{ gBrowser, url: CREDITCARD_FORM_IFRAME_URL },
async function(browser) {
let promiseShown = BrowserTestUtils.waitForEvent(
PopupNotifications.panel,
"popupshown"
);
let iframeBC = browser.browsingContext.children[0];
let onChanged = TestUtils.topicObserved("formautofill-storage-changed");
await SpecialPowers.spawn(iframeBC, [], async function() {
let form = content.document.getElementById("form");
let name = form.querySelector("#cc-name");
name.focus();
name.setUserInput("User 1");
form.querySelector("#cc-number").setUserInput("4556194630960970");
form.querySelector("#cc-exp-month").setUserInput("10");
form.querySelector("#cc-exp-year").setUserInput("2024");
form.querySelector("#cc-type").value = "visa";
// Wait 1000ms before submission to make sure the input value applied
await new Promise(resolve => content.setTimeout(resolve, 1000));
});
info("Removing iframe without submitting");
await SpecialPowers.spawn(browser, [], async function() {
let frame = content.document.querySelector("iframe");
frame.remove();
});
await promiseShown;
await clickDoorhangerButton(MAIN_BUTTON);
await onChanged;
}
);
let creditCards = await getCreditCards();
is(creditCards.length, 1, "1 credit card in storage");
is(creditCards[0]["cc-name"], "User 1", "Verify the name field");
is(creditCards[0]["cc-type"], "visa", "Verify the cc-type field");
is(
SpecialPowers.getIntPref(CREDITCARDS_USED_STATUS_PREF),
2,
"User has seen the doorhanger"
);
SpecialPowers.clearUserPref(CREDITCARDS_USED_STATUS_PREF);
await removeAllRecords();
});
add_task(async function test_submit_changed_subset_creditCard_form() {
await SpecialPowers.pushPrefEnv({
set: [[CREDITCARDS_USED_STATUS_PREF, 0]],
@ -685,6 +738,7 @@ add_task(async function test_update_autofill_form_exp_date() {
await openPopupOn(browser, "form #cc-name");
await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
await osKeyStoreLoginShown;
await SpecialPowers.spawn(browser, [], async function() {
await ContentTaskUtils.waitForCondition(() => {
let form = content.document.getElementById("form");
@ -693,13 +747,12 @@ add_task(async function test_update_autofill_form_exp_date() {
}, "Credit card detail never fills");
let form = content.document.getElementById("form");
let year = form.querySelector("#cc-exp-year");
year.setUserInput("2020");
year.setUserInput("2019");
// Wait 1000ms before submission to make sure the input value applied
await new Promise(resolve => content.setTimeout(resolve, 1000));
form.querySelector("input[type=submit]").click();
});
await promiseShown;
await clickDoorhangerButton(MAIN_BUTTON);
await osKeyStoreLoginShown;
@ -709,7 +762,7 @@ add_task(async function test_update_autofill_form_exp_date() {
creditCards = await getCreditCards();
is(creditCards.length, 1, "Still 1 credit card");
is(creditCards[0]["cc-exp-year"], "2020", "cc-exp-year field is updated");
is(creditCards[0]["cc-exp-year"], "2019", "cc-exp-year field is updated");
is(
creditCards[0]["cc-number"],
"************1111",

Просмотреть файл

@ -312,16 +312,20 @@ async function waitForPopupEnabled(browser) {
async function openPopupOn(browser, selector) {
await SimpleTest.promiseFocus(browser);
await focusAndWaitForFieldsIdentified(browser, selector);
info("openPopupOn: before VK_DOWN");
await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
if (!selector.includes("cc-")) {
info(`openPopupOn: before VK_DOWN on ${selector}`);
await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
}
await expectPopupOpen(browser);
}
async function openPopupForSubframe(browser, frameBrowsingContext, selector) {
await SimpleTest.promiseFocus(browser);
await focusAndWaitForFieldsIdentified(frameBrowsingContext, selector);
info("openPopupForSubframe: before VK_DOWN");
await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, frameBrowsingContext);
if (!selector.includes("cc-")) {
info(`openPopupForSubframe: before VK_DOWN on ${selector}`);
await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, frameBrowsingContext);
}
await expectPopupOpen(browser);
}