Skip to content

Keep Firefox from submitting a form for the triggered submit event - #1651

Closed
reiern70 wants to merge 3 commits into
masterfrom
firefox-triggered-submit
Closed

reiern70 wants to merge 3 commits into
masterfrom
firefox-triggered-submit

Conversation

@reiern70

@reiern70 reiern70 commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

Fixes #1650.

An AjaxFormSubmitBehavior whose shouldTriggerJavaScriptSubmitEvent() returns true fires a submit event on the form before its Ajax request (UploadProgressBar starts its bar from it). Since the jQuery-free engine (615af3e) the precondition fires it with dispatchEvent(). Firefox still submits a form for a submit event fired by a script unless the event is cancelled, so in Firefox the button sent its Ajax request and submitted the form normally, reloading the page behind the Ajax response. Chrome does not submit for such an event.

The precondition now calls a new Wicket.Event.triggerSubmit(form), in both wicket-ajax-jquery.js and wicket-ajax.js. 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. A handler cancelling the event still stops the Ajax request.

Master only: Wicket 10.x triggers the event through jQuery and cancels it in its own handler, and was checked in Firefox not to submit.

Tests:

  • QUnit Wicket.Event.triggerSubmit (4 tests in event.js), run in Chrome and Firefox with both engines.
  • AjaxFormSubmitBehaviorTest.theSubmitEventIsTriggeredWithoutSubmittingTheForm.
  • Checked by hand in Firefox 157 and Chrome with the Ajax upload example: one Ajax request, no reload.

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
@reiern70
reiern70 requested a review from papegaaij October 5, 2026 16:18
@codecov-commenter

codecov-commenter commented Oct 5, 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 (e59b7aa).
⚠️ Report is 7 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff            @@
##             master    #1651   +/-   ##
=========================================
  Coverage     61.95%   61.95%           
- Complexity    11280    11282    +2     
=========================================
  Files          1250     1250           
  Lines         48429    48429           
  Branches       6792     6792           
=========================================
+ Hits          30002    30006    +4     
+ Misses        15729    15726    -3     
+ Partials       2698     2697    -1     
🚀 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.

@reiern70
reiern70 requested a review from solomax October 5, 2026 17:27
@papegaaij

Copy link
Copy Markdown
Contributor

I'm looking at this PR, and it seems its not correct yet. The main issue is an event that never reaches window because of stopPropagation().

@reiern70

reiern70 commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

I'm looking at this PR, and it seems its not correct yet. The main issue is an event that never reaches window because of stopPropagation().

Maybe. I just found this while working on #1637

…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>

@solomax solomax left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only nitpick: I would use let/const instead of var these days :))

Requested in the review of the pull request.

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

@reiern70 reiern70 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two things I found while going through the current state of the PR, see the inline comments. Not approving yet.

buffer.append(String.format("var f = document.getElementById('%s');", root.getMarkupId()));
buffer.append(String.format("f.action='%s';", action));
buffer.append("Wicket.Event.fire(f, 'submit');");
buffer.append("if (Wicket.Event.triggerSubmit(f)) { f.submit(); }");

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

f.submit() throws when the form contains a control named submit: a form control's name shadows the form's property, so f.submit is that element, not the method. In Wicket a control's name is its component path, so a button or field with the id submit directly in the form is enough (a common id; AjaxFormSubmitBehaviorTest uses it too). A FormComponentUpdatingBehavior then fails with TypeError: f.submit is not a function and the form is not submitted, with either Ajax engine. Before, the jQuery engine's trigger() only called the native method when it was a function.

Suggestion: HTMLFormElement.prototype.submit.call(f);

}
};
// a handler stopping the immediate propagation keeps cancelOwnEvent from running
const stopImmediatePropagation = event.stopImmediatePropagation;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only stopImmediatePropagation() is covered. A capturing submit listener on an ancestor (document, body) that calls event.stopPropagation() without cancelling keeps the event from reaching the form, so cancelOwnEvent never runs and the event is never cancelled: triggerSubmit returns true, the Ajax request goes out, and Firefox submits the form as well, the double submission this PR is about. Wrapping event.stopPropagation the same way would close it (with the caveat that other listeners on that same element still run afterwards and could no longer cancel the request).

}
};
// a handler stopping the immediate propagation keeps cancelOwnEvent from running
const stopImmediatePropagation = event.stopImmediatePropagation;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as in wicket-ajax.js: stopPropagation() by a capturing ancestor isn't covered, so Firefox still submits the form besides the Ajax request.

@reiern70

reiern70 commented Oct 6, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #1652, which is merged now (2c89c79).

@reiern70 reiern70 closed this Oct 6, 2026
@reiern70
reiern70 deleted the firefox-triggered-submit branch October 6, 2026 15:41
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.

AjaxButton triggering the submit event also submits the form in Firefox

4 participants