Skip to content

Keep Firefox from submitting a form for the triggered submit event, take II - #1652

Merged
reiern70 merged 5 commits into
masterfrom
firefox-triggered-submit-take-2
Oct 6, 2026
Merged

reiern70 merged 5 commits into
masterfrom
firefox-triggered-submit-take-2

Conversation

@reiern70

@reiern70 reiern70 commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

Take II of #1651, for #1650. It holds the commits of #1651 (mine and Emond's) and one more, fixing the two things from my review there:

  • A control named submit: Form#getJsForListenerUrl() submitted with f.submit(). A form control's name shadows the form's property, and a Wicket control's name is its component path, so a button or field with the id submit directly in the form made f.submit that element: TypeError: f.submit is not a function, the form was not submitted (either engine). It now calls HTMLFormElement.prototype.submit.call(f).
  • stopPropagation() before the form: a capturing handler on an ancestor (document, body) stopping the propagation without cancelling kept the event from reaching the form, so it was never cancelled and Firefox submitted the form besides the Ajax request. stopPropagation() is now wrapped like stopImmediatePropagation(). Caveat: a handler on that same element running afterwards can no longer stop the Ajax request by cancelling.

Tests:

  • QUnit Wicket.Event.triggerSubmit: a capturing handler stopping the propagation (Ajax goes on, event cancelled, no submission), one cancelling and stopping (Ajax stopped), and a form with a control named submit being submitted. Run in Chrome and Firefox with both engines; mvn verify -Pjs-test -pl testing/wicket-js-tests green (154/153).
  • FormTest updated to the new script; FormTest, AjaxFormSubmitBehaviorTest, AjaxButtonTest green.

Master only, like #1651: Wicket 10.x triggers the event through jQuery and cancels it in its own handler, and its getJsForListenerUrl() fires the event through jQuery, which only calls the native submit() when it is a function.

reiern70 and others added 4 commits October 5, 2026 11:16
An AjaxFormSubmitBehavior whose shouldTriggerJavaScriptSubmitEvent() returns
true fires a submit event on the form before its Ajax request, so that submit
handlers run - UploadProgressBar starts its bar from one. Since the jQuery-free
engine, the precondition fired it with dispatchEvent(). Firefox still submits
a form for a submit event fired by a script unless the event is cancelled, so
an AjaxButton like the one of the upload example sent its Ajax request and
submitted the form the normal way as well: the page was reloaded behind the
Ajax response. Chrome does not submit for such an event.

The precondition now calls the new Wicket.Event.triggerSubmit(form) of both
engines. It fires the event, tells whether a submit handler cancelled it, and
cancels it once it has bubbled up to the window, so no browser submits the
form; the Ajax request does. A handler cancelling the event still stops the
request.

Applications see the Ajax request only, as in Chrome. Wicket 10.x is not
affected: its precondition triggers the event through jQuery and cancels it.

GitHub issue #1650
…ner URL

Wicket.Event.triggerSubmit() cancelled the submit event from a listener
on the window. A handler that stopped the propagation, or a form in a
shadow root, kept the event from getting there, so it stayed uncancelled
and Firefox still submitted the form besides the Ajax request. The
listener on the window also cancelled a submit event triggered from
inside a submit handler, so the inner call reported a cancellation that
no handler made.

The event is now cancelled by a listener added last on the form itself,
as the jQuery code before the jQuery-free engine did, and when a handler
stops its immediate propagation. Only a capturing handler stopping the
event before it reaches the form still lets Firefox submit. A handler
on the form, or one capturing the event on its way there, can cancel the
Ajax request. Handlers above the form see the event cancelled already,
so a document level listener can no longer stop the Ajax request, as
before the jQuery-free engine.

Form.getJsForListenerUrl() fired a submit event through
Wicket.Event.fire() to submit the form. With the jQuery-free engine that
is a plain dispatchEvent(), for which Chrome never submits a form, so a
FormComponentUpdatingBehavior did nothing in Chrome. It now calls
triggerSubmit() and submits the form unless a handler cancelled the
event, in both engines, which is what jQuery's trigger() did. Listeners
added with addEventListener() now see that event with the jQuery engine
too.

SubmitEventSeleniumTest drives the Ajax upload and form input examples
with both engines, and can run in Firefox or on a Selenium grid.

GitHub issue #1650

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Requested in the review of the pull request.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two gaps in the triggered submit event of the previous commits:

Form#getJsForListenerUrl() submitted the form with f.submit() once the
event was not cancelled. A form control's name shadows the property of
the form with that name, and the name of a Wicket form control is its
component path, so a button or field with the id "submit" directly in
the form made f.submit that element: the script failed with "f.submit is
not a function" and the form was not submitted, with either Ajax engine.
It now calls HTMLFormElement.prototype.submit on the form.

Wicket.Event.triggerSubmit() cancelled the event on the form, and when a
handler stopped its immediate propagation. A capturing handler further up,
on the document or the body, that stopped the propagation without
cancelling kept the event from reaching the form: it was never cancelled,
so Firefox submitted the form besides the Ajax request, the double
submission #1650 is about. stopPropagation() now cancels the event as
well. A handler on that same element that runs afterwards can no longer
stop the Ajax request by cancelling the event, as the event is cancelled
already.

Applications see their forms submitted for a listener URL whatever their
controls are named, and the Ajax request alone in Firefox also with a
capturing handler stopping the propagation.

GitHub issue #1650
@reiern70
reiern70 requested a review from papegaaij October 6, 2026 10:37
@codecov-commenter

codecov-commenter commented Oct 6, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 61.95%. Comparing base (ee62ec8) to head (5b0d6a4).
⚠️ Report is 7 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff            @@
##             master    #1652   +/-   ##
=========================================
  Coverage     61.95%   61.95%           
- Complexity    11280    11281    +1     
=========================================
  Files          1250     1250           
  Lines         48429    48429           
  Branches       6792     6792           
=========================================
+ Hits          30002    30005    +3     
+ Misses        15729    15726    -3     
  Partials       2698     2698           
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

…e form

Wicket.Event.triggerSubmit() cancelled the event whenever a handler
stopped its propagation, at any phase. A handler on the form stopping the
propagation thus cancelled the event before the handlers registered after
it on the form ran: they saw it cancelled already, and one of them
cancelling it no longer stopped the Ajax request.

The listener added last on the form cancels the event once it gets
there, so stopPropagation() only has to cancel it in the capturing phase,
when a handler on an ancestor keeps it from reaching the form.
stopImmediatePropagation() still cancels it at once, as it skips that
listener on the form too.

Applications see a handler on the form cancelling the event stop the
Ajax request again, also when an earlier handler on the form stopped the
propagation.

GitHub issue #1650

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@papegaaij

Copy link
Copy Markdown
Contributor

I pushed 5b0d6a4 on top of this branch.

Wrapping stopPropagation() so that it cancels the event at any phase broke handlers on the form. When one handler on the form stopped the propagation, the event was cancelled before the handlers registered after it on the same form ran: they saw it cancelled already, and one of them cancelling it no longer stopped the Ajax request, as triggerSubmit() returned true.

stopPropagation() now only cancels the event in the capturing phase, when a handler on an ancestor keeps it from reaching the form. Once the event is at the form, the listener added last on the form cancels it as before. stopImmediatePropagation() still cancels it at once, as it skips that listener too.

The new QUnit test "a handler on the form cancelling after another one stopped the propagation stops the Ajax request" fails without the change with both engines. With it, the suite passes in Chrome (155/154) and Firefox 156.

@reiern70

reiern70 commented Oct 6, 2026

Copy link
Copy Markdown
Contributor Author

I pushed 5b0d6a4 on top of this branch.

Wrapping stopPropagation() so that it cancels the event at any phase broke handlers on the form. When one handler on the form stopped the propagation, the event was cancelled before the handlers registered after it on the same form ran: they saw it cancelled already, and one of them cancelling it no longer stopped the Ajax request, as triggerSubmit() returned true.

stopPropagation() now only cancels the event in the capturing phase, when a handler on an ancestor keeps it from reaching the form. Once the event is at the form, the listener added last on the form cancels it as before. stopImmediatePropagation() still cancels it at once, as it skips that listener too.

The new QUnit test "a handler on the form cancelling after another one stopped the propagation stops the Ajax request" fails without the change with both engines. With it, the suite passes in Chrome (155/154) and Firefox 156.

Then we are ready to squash and merge 👍

@papegaaij

Copy link
Copy Markdown
Contributor

Yes, agreed. You can go ahead and merge this.

@reiern70
reiern70 merged commit 2c89c79 into master Oct 6, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants