This is an automated email from the ASF dual-hosted git repository. reiern70 pushed a commit to branch firefox-triggered-submit-take-2 in repository https://gitbox.apache.org/repos/asf/wicket.git
commit 2ef3965565235b195785d3984194f6be90596b3d Author: reiern70 <[email protected]> AuthorDate: Tue Oct 6 05:36:55 2026 -0500 Submit a form with a control named submit, and cover stopPropagation() 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 --- .../apache/wicket/markup/html/form/FormTest.java | 4 +- .../wicket/ajax/form/AjaxFormSubmitBehavior.java | 2 +- .../wicket/ajax/res/js/wicket-ajax-jquery.js | 16 ++++---- .../org/apache/wicket/ajax/res/js/wicket-ajax.js | 16 ++++---- .../org/apache/wicket/markup/html/form/Form.java | 3 +- wicket-core/src/test/js/event.js | 47 ++++++++++++++++++++++ 6 files changed, 70 insertions(+), 18 deletions(-) diff --git a/wicket-core-tests/src/test/java/org/apache/wicket/markup/html/form/FormTest.java b/wicket-core-tests/src/test/java/org/apache/wicket/markup/html/form/FormTest.java index e7977fb6b6..65c7cca929 100644 --- a/wicket-core-tests/src/test/java/org/apache/wicket/markup/html/form/FormTest.java +++ b/wicket-core-tests/src/test/java/org/apache/wicket/markup/html/form/FormTest.java @@ -85,11 +85,11 @@ public class FormTest extends WicketTestCase FormMethodTestPage page = (FormMethodTestPage)tester.getLastRenderedPage(); // form action contains path and query - assertEquals("var f = document.getElementById('formpost1');f.action='path?param1¶m2=val%20ue';if (Wicket.Event.triggerSubmit(f)) { f.submit(); }", + assertEquals("var f = document.getElementById('formpost1');f.action='path?param1¶m2=val%20ue';if (Wicket.Event.triggerSubmit(f)) { HTMLFormElement.prototype.submit.call(f); }", page.postForm.getJsForListenerUrl("path?param1¶m2=val%20ue").toString()); // form action must not contain query (since ignored by browser), put into hidden form parameters instead - assertEquals("document.getElementById('formget2_hf_0').innerHTML = '<input type=\"hidden\" name=\"param1\" value=\"\" /><input type=\"hidden\" name=\"param2\" value=\"val ue\" />';var f = document.getElementById('formget2');f.action='path';if (Wicket.Event.triggerSubmit(f)) { f.submit(); }", + assertEquals("document.getElementById('formget2_hf_0').innerHTML = '<input type=\"hidden\" name=\"param1\" value=\"\" /><input type=\"hidden\" name=\"param2\" value=\"val ue\" />';var f = document.getElementById('formget2');f.action='path';if (Wicket.Event.triggerSubmit(f)) { HTMLFormElement.prototype.submit.call(f); }", page.getForm.getJsForListenerUrl("path?param1¶m2=val%20ue").toString()); } diff --git a/wicket-core/src/main/java/org/apache/wicket/ajax/form/AjaxFormSubmitBehavior.java b/wicket-core/src/main/java/org/apache/wicket/ajax/form/AjaxFormSubmitBehavior.java index 3cceb41943..d35625bd9d 100644 --- a/wicket-core/src/main/java/org/apache/wicket/ajax/form/AjaxFormSubmitBehavior.java +++ b/wicket-core/src/main/java/org/apache/wicket/ajax/form/AjaxFormSubmitBehavior.java @@ -147,7 +147,7 @@ public abstract class AjaxFormSubmitBehavior extends AjaxEventBehavior * The event runs the submit handlers of the form. A handler on the form, or one capturing the * event on its way there, cancelling it stops the Ajax request; handlers further up see it * cancelled already. The browser does not submit the form for this event, the Ajax request - * does, unless a capturing handler stops the event before it reaches the form. + * does, also when a handler stops the propagation of the event. * * @return true if <code>submit</code> should be triggered, false otherwise */ diff --git a/wicket-core/src/main/java/org/apache/wicket/ajax/res/js/wicket-ajax-jquery.js b/wicket-core/src/main/java/org/apache/wicket/ajax/res/js/wicket-ajax-jquery.js index d48fd83bf5..a6fa670045 100644 --- a/wicket-core/src/main/java/org/apache/wicket/ajax/res/js/wicket-ajax-jquery.js +++ b/wicket-core/src/main/java/org/apache/wicket/ajax/res/js/wicket-ajax-jquery.js @@ -2529,12 +2529,15 @@ cancel(); } }; - // a handler stopping the immediate propagation keeps cancelOwnEvent from running - const stopImmediatePropagation = event.stopImmediatePropagation; - event.stopImmediatePropagation = function () { - stopImmediatePropagation.call(this); - cancel(); - }; + // a handler stopping the propagation before the form, or the immediate propagation, + // keeps cancelOwnEvent from running + ['stopPropagation', 'stopImmediatePropagation'].forEach(function (name) { + const stop = event[name]; + event[name] = function () { + stop.call(this); + cancel(); + }; + }); let notCancelled; form.addEventListener('submit', cancelOwnEvent); try { @@ -2542,7 +2545,6 @@ } finally { form.removeEventListener('submit', cancelOwnEvent); } - // null when a capturing handler stopped the propagation before the form return cancelledByHandler === null ? notCancelled : !cancelledByHandler; }, diff --git a/wicket-core/src/main/java/org/apache/wicket/ajax/res/js/wicket-ajax.js b/wicket-core/src/main/java/org/apache/wicket/ajax/res/js/wicket-ajax.js index a03e3ec878..756de443bb 100644 --- a/wicket-core/src/main/java/org/apache/wicket/ajax/res/js/wicket-ajax.js +++ b/wicket-core/src/main/java/org/apache/wicket/ajax/res/js/wicket-ajax.js @@ -2809,12 +2809,15 @@ cancel(); } }; - // a handler stopping the immediate propagation keeps cancelOwnEvent from running - const stopImmediatePropagation = event.stopImmediatePropagation; - event.stopImmediatePropagation = function () { - stopImmediatePropagation.call(this); - cancel(); - }; + // a handler stopping the propagation before the form, or the immediate propagation, + // keeps cancelOwnEvent from running + ['stopPropagation', 'stopImmediatePropagation'].forEach(function (name) { + const stop = event[name]; + event[name] = function () { + stop.call(this); + cancel(); + }; + }); let notCancelled; form.addEventListener('submit', cancelOwnEvent); try { @@ -2822,7 +2825,6 @@ } finally { form.removeEventListener('submit', cancelOwnEvent); } - // null when a capturing handler stopped the propagation before the form return cancelledByHandler === null ? notCancelled : !cancelledByHandler; }, diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/html/form/Form.java b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/Form.java index 3efd616277..805610c3fb 100644 --- a/wicket-core/src/main/java/org/apache/wicket/markup/html/form/Form.java +++ b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/Form.java @@ -554,7 +554,8 @@ public class Form<T> extends WebMarkupContainer } buffer.append(String.format("var f = document.getElementById('%s');", root.getMarkupId())); buffer.append(String.format("f.action='%s';", action)); - buffer.append("if (Wicket.Event.triggerSubmit(f)) { f.submit(); }"); + buffer.append( + "if (Wicket.Event.triggerSubmit(f)) { HTMLFormElement.prototype.submit.call(f); }"); return buffer; } diff --git a/wicket-core/src/test/js/event.js b/wicket-core/src/test/js/event.js index cbc3f9b59a..b896b0d58a 100644 --- a/wicket-core/src/test/js/event.js +++ b/wicket-core/src/test/js/event.js @@ -350,6 +350,53 @@ Wicket.Event.add(window, 'domready', function() { assert.equal(submissions.length, 0, 'the browser does not submit the form'); }); + test('a capturing handler stopping the propagation before the form lets the Ajax request go on', assert => { + const form = createTestForm(); + const submissions = recordSubmissions(form); + let event; + const stop = function (e) { + event = e; + e.stopPropagation(); + }; + document.addEventListener('submit', stop, true); + try { + assert.ok(Wicket.Event.triggerSubmit(form), 'the Ajax request may go on'); + } finally { + document.removeEventListener('submit', stop, true); + } + assert.ok(event.defaultPrevented, 'the event is cancelled'); + assert.equal(submissions.length, 0, 'the browser does not submit the form'); + }); + + test('a capturing handler cancelling the event and stopping the propagation stops the Ajax request', assert => { + const form = createTestForm(); + const stop = function (e) { + e.preventDefault(); + e.stopPropagation(); + }; + document.addEventListener('submit', stop, true); + try { + assert.notOk(Wicket.Event.triggerSubmit(form)); + } finally { + document.removeEventListener('submit', stop, true); + } + }); + + test('a form with a control named submit is submitted by the form\'s own method', assert => { + const form = createTestForm(); + const control = document.createElement('input'); + control.name = 'submit'; + form.appendChild(control); + const submissions = recordSubmissions(form); + + // as Form#getJsForListenerUrl() submits it + if (Wicket.Event.triggerSubmit(form)) { + HTMLFormElement.prototype.submit.call(form); + } + + assert.equal(submissions.length, 1, 'the form is submitted once'); + }); + test('a handler stopping the immediate propagation without cancelling lets the Ajax request go on', assert => { const form = createTestForm(); const submissions = recordSubmissions(form);
