fix(js): HTMLFormElement.submit() must not fire a cancelable submit event; add requestSubmit() (#395)
* fix(js): HTMLFormElement.submit() must not fire a cancelable submit event; add requestSubmit() Per the HTML standard, the submit() method submits without firing a submit event and cannot be canceled by a page submit listener; only requestSubmit() and user-initiated submits fire the cancelable event. bootstrap.js dispatched a cancelable submit event from submit() and aborted navigation when canceled, which breaks the common pattern where a submit listener preventDefault()s the native submit and a callback later calls form.submit() to actually send it (e.g. an invisible-reCAPTCHA data-callback): the listener also canceled the programmatic submit, so no navigation was ever issued. - submit(): navigate unconditionally, no submit event - requestSubmit(): fire the cancelable submit event, navigate only if not canceled - submit-button clicks route through requestSubmit() (a real click fires the event) Adds a regression test; full obscura-cdp suite (56 tests) passes. * fix(cdp): route synthetic submit-button clicks and Enter through requestSubmit A CDP Input.dispatchMouseEvent on a submit button and an Enter key in a form field are user-initiated submits, so per the HTML spec they must fire the cancelable submit event. input.rs called form.submit() (the method) directly, bypassing any preventDefault() submit listener, which mirrors the bootstrap.js bug fixed in the parent commit. Use requestSubmit() with a submit() fallback so the listener can veto. --------- Co-authored-by: SGavrl <[email protected]>
This commit is contained in:
1 parent
87fefef1ec
commit
7e2cabf411
3 files changed
+204
-5
No files matched your search
Vendored
+17
-2
@@ -1456,7 +1456,11 @@ class Element extends Node {
|
||||
const type = (this.getAttribute('type') || '').toLowerCase();
|
||||
if (type === 'submit' || (this.localName === 'button' && type !== 'button' && type !== 'reset')) {
|
||||
const form = this.closest ? this.closest('form') : null;
|
||||
if (form && typeof form.submit === 'function') {
|
||||
// A real submit-button click fires the cancelable submit event, so use
|
||||
// requestSubmit() (not the plain submit() method, which now bypasses it).
|
||||
if (form && typeof form.requestSubmit === 'function') {
|
||||
form.requestSubmit(this);
|
||||
} else if (form && typeof form.submit === 'function') {
|
||||
form.submit(this);
|
||||
}
|
||||
}
|
||||
@@ -1913,10 +1917,21 @@ class Element extends Node {
|
||||
opts[i]._selected = (i === v);
|
||||
}
|
||||
}
|
||||
// Per the HTML spec, the submit() METHOD submits the form WITHOUT firing a
|
||||
// cancelable `submit` event — a page's submit listener cannot veto it. Only
|
||||
// requestSubmit() and user-initiated submits fire the cancelable event.
|
||||
// Conflating the two broke sites whose submit listener preventDefault()s the
|
||||
// native submit and then calls form.submit() from a callback (e.g. an
|
||||
// invisible-reCAPTCHA data-callback) to actually send the form.
|
||||
submit(submitter) {
|
||||
this._navigateSubmit(submitter);
|
||||
}
|
||||
requestSubmit(submitter) {
|
||||
const cancelled = !this.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }));
|
||||
if (cancelled) return;
|
||||
|
||||
this._navigateSubmit(submitter);
|
||||
}
|
||||
_navigateSubmit(submitter) {
|
||||
const pairs = [];
|
||||
const fields = this.querySelectorAll('input, select, textarea');
|
||||
for (let i = 0; i < fields.length; i++) {
|
||||
|
||||
Reference in new issue
Block a user