Skip to content

Commit ef936be

Browse files
Ana Sollano Kimchromium-wpt-export-bot
authored andcommitted
[Platform-provided behaviors] Implicit submission, dialog method, and
:default matching This CL updates the implicit submission logic to recognize CEs with submit behaviors. When a user triggers a submission through a text field (e.g., by pressing Enter), the form identifies and activates the first available submitter. Additionally, the form's "default button" logic has been generalized to return the first non-disabled submitter in the form, allowing authors to use the :default pseudo-class to style the primary action regardless of whether it is a native or a CE with submit behavior. This patch also enables support for the "dialog" submission method. When a CE with a submit behavior is used within a dialog form, it now provides its associated value to the dialog's return value upon closing. Explainer: https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/PlatformProvidedBehaviors/explainer.md Design doc: https://docs.google.com/document/d/1LA1hhzxmi4OmZoGtIdnwvL3g7y48YjXTOoUvFtxFugE/edit?usp=sharing Bug: 486928684 Change-Id: Ic1cd672a04ab073c5c74b781a130b0561c26f83a Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7727758 Commit-Queue: Ana Sollano Kim <[email protected]> Reviewed-by: Dan Clark <[email protected]> Reviewed-by: Mason Freed <[email protected]> Cr-Commit-Position: refs/heads/main@{#1614906}
1 parent cbffd5f commit ef936be

2 files changed

Lines changed: 183 additions & 11 deletions

File tree

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<!DOCTYPE html>
2+
<meta charset="utf-8">
3+
<title>HTMLSubmitButtonBehavior dialog method support</title>
4+
<link rel="author" href="mailto:[email protected]">
5+
<link rel="help" href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/PlatformProvidedBehaviors/explainer.md">
6+
<script src="/resources/testharness.js"></script>
7+
<script src="/resources/testharnessreport.js"></script>
8+
9+
<dialog>
10+
<form method="dialog">
11+
<custom-submit></custom-submit>
12+
</form>
13+
</dialog>
14+
15+
<script>
16+
class CustomSubmitButton extends HTMLElement {
17+
static formAssociated = true;
18+
constructor() {
19+
super();
20+
this.behavior_ = new HTMLSubmitButtonBehavior();
21+
this.internals_ = this.attachInternals({ behaviors: [this.behavior_] });
22+
}
23+
get submitBehavior() { return this.behavior_; }
24+
}
25+
customElements.define('custom-submit', CustomSubmitButton);
26+
27+
const dialog = document.querySelector('dialog');
28+
const form = document.querySelector('form');
29+
const customSubmit = document.querySelector('custom-submit');
30+
31+
function reset() {
32+
if (dialog.open) {
33+
dialog.close();
34+
}
35+
dialog.returnValue = '';
36+
form.method = 'dialog';
37+
customSubmit.submitBehavior.disabled = false;
38+
customSubmit.submitBehavior.formMethod = '';
39+
customSubmit.submitBehavior.value = '';
40+
}
41+
42+
test(t => {
43+
t.add_cleanup(reset);
44+
customSubmit.submitBehavior.value = 'custom-result';
45+
dialog.showModal();
46+
customSubmit.click();
47+
48+
assert_false(dialog.open, 'dialog should be closed after submission');
49+
assert_equals(dialog.returnValue, 'custom-result',
50+
'dialog.returnValue should be the behavior value');
51+
}, 'Custom submit button with form method=dialog closes dialog with behavior value');
52+
53+
test(t => {
54+
t.add_cleanup(reset);
55+
form.method = 'get';
56+
customSubmit.submitBehavior.formMethod = 'dialog';
57+
customSubmit.submitBehavior.value = 'override-result';
58+
dialog.showModal();
59+
customSubmit.click();
60+
61+
assert_false(dialog.open, 'dialog should be closed');
62+
assert_equals(dialog.returnValue, 'override-result',
63+
'behavior formMethod=dialog should override form method');
64+
}, 'Behavior formMethod=dialog overrides form method and closes dialog');
65+
66+
test(t => {
67+
t.add_cleanup(reset);
68+
customSubmit.submitBehavior.value = 'should-not-submit';
69+
customSubmit.submitBehavior.disabled = true;
70+
dialog.showModal();
71+
customSubmit.click();
72+
73+
assert_true(dialog.open, 'dialog should remain open');
74+
}, 'Disabled custom submit button does not close dialog');
75+
</script>

custom-elements/form-associated/ElementInternals-submit-behavior.tentative.html

Lines changed: 108 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,16 @@
1313
width: 100px;
1414
height: 30px;
1515
}
16+
17+
:default {
18+
outline: 3px solid blue;
19+
}
1620
</style>
1721

1822
<form>
1923
<input name="field" value="value">
2024
<custom-submit></custom-submit>
25+
<non-form-submit></non-form-submit>
2126
</form>
2227

2328
<div id="outside"></div>
@@ -34,9 +39,18 @@
3439
}
3540
customElements.define('custom-submit', CustomSubmitButton);
3641

42+
class NonFormAssociatedSubmit extends HTMLElement {
43+
constructor() {
44+
super();
45+
this.attachInternals({ behaviors: [new HTMLSubmitButtonBehavior()] });
46+
}
47+
}
48+
customElements.define('non-form-submit', NonFormAssociatedSubmit);
49+
3750
const form = document.querySelector('form');
3851
const outside = document.querySelector('#outside');
3952
const customSubmit = form.querySelector('custom-submit');
53+
const nonFormSubmit = form.querySelector('non-form-submit');
4054
const defaultInput = form.querySelector('input');
4155

4256
function reset() {
@@ -61,10 +75,23 @@
6175
customSubmit.submitBehavior.name = '';
6276
customSubmit.submitBehavior.value = '';
6377

78+
form.appendChild(nonFormSubmit);
6479
form.appendChild(customSubmit);
6580
form.prepend(defaultInput);
6681
}
6782

83+
// Creates a native <button type="submit"> and adds it to the form.
84+
function addNativeSubmitButton({prepend = false} = {}) {
85+
const nativeButton = document.createElement('button');
86+
nativeButton.type = 'submit';
87+
if (prepend) {
88+
form.prepend(nativeButton);
89+
} else {
90+
form.appendChild(nativeButton);
91+
}
92+
return nativeButton;
93+
}
94+
6895
// Clicks an element and returns whether the form's submit event fired.
6996
function clickSubmitsForm(element) {
7097
let submitted = false;
@@ -215,6 +242,34 @@
215242
'Form should be submitted with input values');
216243
}, 'Click on custom element with HTMLSubmitButtonBehavior submits form');
217244

245+
promise_test(async t => {
246+
t.add_cleanup(reset);
247+
let submitted = false;
248+
form.addEventListener('submit', (e) => {
249+
e.preventDefault();
250+
submitted = true;
251+
}, { once: true });
252+
defaultInput.focus();
253+
await test_driver.send_keys(defaultInput, '\uE007');
254+
assert_true(submitted,
255+
'Pressing Enter in a text field should trigger implicit submission via custom submit button');
256+
}, 'Custom element with HTMLSubmitButtonBehavior participates in implicit submission');
257+
258+
promise_test(async t => {
259+
t.add_cleanup(reset);
260+
let clickFired = false;
261+
customSubmit.addEventListener('click', () => {
262+
clickFired = true;
263+
}, { once: true });
264+
form.onsubmit = (e) => {
265+
e.preventDefault();
266+
};
267+
defaultInput.focus();
268+
await test_driver.send_keys(defaultInput, '\uE007');
269+
assert_true(clickFired,
270+
'Implicit submission should dispatch a click event on the custom submit button');
271+
}, 'Implicit submission dispatches click on custom element with HTMLSubmitButtonBehavior');
272+
218273
promise_test(async t => {
219274
t.add_cleanup(reset);
220275
let submitEventFired = false;
@@ -359,17 +414,7 @@
359414

360415
test(t => {
361416
t.add_cleanup(reset);
362-
class NonFormAssociatedSubmit extends HTMLElement {
363-
constructor() {
364-
super();
365-
this.attachInternals({ behaviors: [new HTMLSubmitButtonBehavior()] });
366-
}
367-
}
368-
customElements.define('non-form-submit', NonFormAssociatedSubmit);
369-
370-
const nonFormElement = document.createElement('non-form-submit');
371-
form.appendChild(nonFormElement);
372-
assert_false(clickSubmitsForm(nonFormElement),
417+
assert_false(clickSubmitsForm(nonFormSubmit),
373418
'Form should not be submitted by non-form-associated element');
374419
}, 'Non-form-associated custom element with HTMLSubmitButtonBehavior does not submit');
375420

@@ -415,4 +460,56 @@
415460
'User click on custom submit button should submit form');
416461
}, 'User-initiated click submits form via HTMLSubmitButtonBehavior');
417462

463+
test(t => {
464+
t.add_cleanup(reset);
465+
const nativeButton = addNativeSubmitButton({prepend: true});
466+
467+
assert_true(nativeButton.matches(':default'),
468+
'Native button should match :default when it appears first');
469+
}, 'Native button takes precedence over custom element when first in DOM');
470+
471+
test(t => {
472+
t.add_cleanup(reset);
473+
const nativeButton = addNativeSubmitButton();
474+
475+
assert_true(customSubmit.matches(':default'),
476+
'Custom element should match :default when it appears first');
477+
assert_false(nativeButton.matches(':default'),
478+
'Native button should not match :default when custom element is first');
479+
}, 'Custom element takes precedence over native button when first in DOM');
480+
481+
test(t => {
482+
t.add_cleanup(reset);
483+
484+
assert_equals(getComputedStyle(customSubmit).outlineColor, 'rgb(0, 0, 255)',
485+
'Custom element matching :default should have blue outline');
486+
}, 'Styling via :default selector applies to custom submit button');
487+
488+
test(t => {
489+
t.add_cleanup(reset);
490+
491+
assert_true(customSubmit.matches(':default'), 'Custom should initially match :default');
492+
customSubmit.submitBehavior.disabled = true;
493+
assert_true(customSubmit.matches(':default'),
494+
'Disabled custom element should still match :default, like native disabled submit buttons');
495+
}, 'Disabled custom element still matches :default');
496+
497+
test(t => {
498+
t.add_cleanup(reset);
499+
const secondSubmit = document.createElement('custom-submit');
500+
form.appendChild(secondSubmit);
501+
customSubmit.submitBehavior.disabled = true;
502+
503+
assert_true(customSubmit.matches(':default'),
504+
'Disabled first custom submit should still match :default');
505+
assert_false(secondSubmit.matches(':default'),
506+
'Second custom submit should not match :default when first is still default');
507+
}, 'Two custom submit buttons: disabled first is still :default');
508+
509+
test(t => {
510+
t.add_cleanup(reset);
511+
assert_false(nonFormSubmit.matches(':default'),
512+
'Non-form-associated element should not match :default');
513+
}, 'Non-form-associated custom element with HTMLSubmitButtonBehavior does not match :default');
514+
418515
</script>

0 commit comments

Comments
 (0)