Skip to content

Commit

Permalink
Add dialog light dismiss behavior (the actual click outside part) 5/5
Browse files Browse the repository at this point in the history
This adds to the prior CLs to actually allow "click outside" to
function as a light dismiss trigger.

See spec PR for details:
  whatwg/html#10737

Bug: 376516550
Change-Id: I62158e092de9acac182777f2ad9864e818128907
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6013845
Commit-Queue: David Baron <[email protected]>
Reviewed-by: David Baron <[email protected]>
Auto-Submit: Mason Freed <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1383792}
  • Loading branch information
mfreed7 authored and chromium-wpt-export-bot committed Nov 15, 2024
1 parent 9cccb7c commit 5420c40
Show file tree
Hide file tree
Showing 2 changed files with 246 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<!DOCTYPE html>
<meta charset="utf-8">
<link rel="author" href="mailto:[email protected]">
<link rel=help href="https://html.spec.whatwg.org/multipage/interactive-elements.html#dialog-light-dismiss">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-actions.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="../../popovers/resources/popover-utils.js"></script>

<div id=unrelated>Unrelated</div>
<dialog id=dialogA closedby=any>Dialog 1
<div id=popoverA popover>Popover 1
<dialog id=dialogB closedby=any>Dialog 2
<div id=popoverB popover>Popover 2</div>
</dialog>
</div>
</dialog>

<style>
#dialogA { top: 100px; bottom: auto; padding:0; }
#popoverA { top: 150px; bottom: auto; padding:0; }
#dialogB { top: 200px; bottom: auto; padding:0; }
#popoverB { top: 250px; bottom: auto; padding:0; }
</style>

<script>
function openDialog(dialog,modal) {
assert_false(dialog.open);
if (modal) {
dialog.showModal();
} else {
dialog.show();
}
assert_true(dialog.open);
assert_equals(dialog.matches(':modal'),modal);
}
function assertStates(dialogAExpected,popoverAExpected,
dialogBExpected,popoverBExpected) {
assert_equals(dialogA.open,dialogAExpected,
`First dialog should be ${dialogAExpected ? 'open' : 'closed'}`);
assert_equals(popoverA.matches(':popover-open'),popoverAExpected,
`First popover should be ${popoverAExpected ? 'open' : 'closed'}`);
assert_equals(dialogB.open,dialogBExpected,
`Second dialog should be ${dialogBExpected ? 'open' : 'closed'}`);
assert_equals(popoverB.matches(':popover-open'),popoverBExpected,
`Second popover should be ${popoverBExpected ? 'open' : 'closed'}`);
}
function openDialogPopoverStack(t,modalA,modalB) {
t.add_cleanup(() => {
dialogA.close();
popoverA.hidePopover();
dialogB.close();
popoverB.hidePopover();
});
openDialog(dialogA,modalA);
popoverA.showPopover();
openDialog(dialogB,modalB);
popoverB.showPopover();
assertStates(true,true,true,true);
}

[false,true].forEach(modalA => {
[false,true].forEach(modalB => {
const modalAString = modalA ? 'modal dialogA' : 'modeless dialogA';
const modalBString = modalB ? 'modal dialogB' : 'modeless dialogB';
promise_test(async (t) => {
openDialogPopoverStack(t,modalA,modalB);
await clickOn(unrelated);
// Clicking outside all is actually a click on a dialog backdrop.
// If dialogB is modal, it'll be dialogB, which is nested inside popoverA.
assertStates(false,modalB,false,false);
},`clicking outside all with ${modalAString} and ${modalBString}`);

promise_test(async (t) => {
openDialogPopoverStack(t,modalA,modalB);
await clickOn(popoverB);
// Clicking popoverB will keep both popovers plus the intervening dialogB
// open, because they're a stack.
assertStates(false,true,true,true);
},`clicking popoverB with ${modalAString} and ${modalBString}`);

promise_test(async (t) => {
openDialogPopoverStack(t,modalA,modalB);
await clickOn(dialogB);
// dialogB is nested inside popoverA.
assertStates(false,true,true,false);
},`clicking dialogB with ${modalAString} and ${modalBString}`);

promise_test(async (t) => {
openDialogPopoverStack(t,modalA,modalB);
await clickOn(popoverA);
// If dialogB is modal, then clicking popoverA is actually a backdrop
// click on dialogB, which will close it. PopoverA stays open because
// dialogB is nested inside popoverA.
assertStates(false,true,!modalB,false);
},`clicking popoverA with ${modalAString} and ${modalBString}`);

promise_test(async (t) => {
openDialogPopoverStack(t,modalA,modalB);
await clickOn(dialogB);
// Again, this is a backdrop click on dialogB.
assertStates(false,true,true,false);
},`clicking dialogA with ${modalAString} and ${modalBString}`);
});
});
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
<!DOCTYPE html>
<meta charset="utf-8">
<link rel="author" href="mailto:[email protected]">
<link rel=help href="https://html.spec.whatwg.org/multipage/interactive-elements.html#dialog-light-dismiss">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-actions.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="../../popovers/resources/popover-utils.js"></script>

<div id=unrelated>Unrelated</div>
<dialog id=dialog_outer>Dialog outer
<div id=popover_inner popover>Popover inner</div>
</dialog>
<div id=popover_outer popover>Popover outer
<dialog id=dialog_inner>Dialog inner</dialog>
</div>

<style>
dialog { top: 50px; bottom: auto; padding:0; }
[popover] { top: 100px; bottom: auto; padding:0; }
</style>

<script>
function resetDialogOuterTest(dialog,popover) {
popover.hidePopover();
dialog.close();
dialog.showModal();
popover.showPopover();
assert_true(dialog.open && popover.matches(':popover-open'),'setup');
}
async function runDialogOuterTest(t,dialog,popover) {
t.add_cleanup(() => {
dialog.removeAttribute('closedby');
popover.hidePopover();
dialog.close();
});
resetDialogOuterTest(dialog,popover);
await clickOn(popover);
assert_true(popover.matches(':popover-open'),
'clicking on popover should always leave everything open');
assert_true(dialog.open,'dialog should stay open');
resetDialogOuterTest(dialog,popover);
await clickOn(dialog);
assert_false(popover.matches(':popover-open'),'popover should close');
assert_true(dialog.open,'dialog should stay open');
resetDialogOuterTest(dialog,popover);
await clickOn(unrelated);
assert_false(popover.matches(':popover-open'),'popover should always close');
assert_equals(dialog.open,dialog.closedBy !== 'any',
'dialog should close if closedby=any');
resetDialogOuterTest(dialog,popover);
const ESC = '\uE00C';
await new test_driver.send_keys(document.documentElement,ESC);
assert_false(popover.matches(':popover-open'),
'popover should close after first ESC');
assert_true(dialog.open,'dialog should stay open for first ESC');
await new test_driver.send_keys(document.documentElement,ESC);
assert_equals(dialog.open,dialog.closedBy === 'none',
'dialog should close on second ESC, if closedby is not none');
}
promise_test(async (t) => {
dialog_outer.setAttribute('closedby','any');
await runDialogOuterTest(t,dialog_outer,popover_inner);
},'Dialog closedby=any parent, popover child');
promise_test(async (t) => {
dialog_outer.setAttribute('closedby','closerequest');
await runDialogOuterTest(t,dialog_outer,popover_inner);
},'Dialog closedby=closerequest parent, popover child');
promise_test(async (t) => {
dialog_outer.setAttribute('closedby','none');
await runDialogOuterTest(t,dialog_outer,popover_inner);
},'Dialog closedby=none parent, popover child');


function resetPopoverOuterTest(dialog,popover) {
dialog.close();
popover.hidePopover();
popover.showPopover();
dialog.showModal();
assert_true(dialog.open && popover.matches(':popover-open'),'setup');
}
async function runPopoverOuterTest(t,dialog,popover) {
t.add_cleanup(() => {
dialog.removeAttribute('closedby');
dialog.close();
popover.hidePopover();
});
resetPopoverOuterTest(dialog,popover);
await clickOn(dialog);
assert_true(dialog.open,'clicking on dialog should always leave everything open');
assert_true(popover.matches(':popover-open'),'popover should stay open');
resetPopoverOuterTest(dialog,popover);
await clickOn(popover);
assert_equals(dialog.open,dialog.closedBy !== 'any',
'dialog should close if closedby=any');
// Note that "clicking on" popover really means clicking on dialog's
// ::backdrop, and the dialog is a child of the popover. So by popover's light
// dismiss logic, it will *not* close. That's semi-expected here, but not in
// the next case.
assert_true(popover.matches(':popover-open'),'popover should stay open');
resetPopoverOuterTest(dialog,popover);
await clickOn(unrelated);
assert_equals(dialog.open,dialog.closedBy !== 'any',
'dialog should close if closedby=any');
// See note above.
assert_true(popover.matches(':popover-open'),'popover should stay open');
if (!dialog.open) {
// If we light dismissed the dialog, check that the popover responds to a
// second click.
await clickOn(unrelated);
assert_false(popover.matches(':popover-open'),'popover should stay open');
}
resetPopoverOuterTest(dialog,popover);
const ESC = '\uE00C';
await new test_driver.send_keys(document.documentElement,ESC);
assert_equals(dialog.open,dialog.closedBy === 'none',
'dialog should close after first ESC, if closedby!=none');
assert_true(popover.matches(':popover-open'),
'popover should stay open for first ESC');
await new test_driver.send_keys(document.documentElement,ESC);
assert_equals(popover.matches(':popover-open'),dialog.closedBy === 'none',
'popover should close on second ESC, unless inner dialog prevents with closedby==none');
}
promise_test(async (t) => {
dialog_inner.setAttribute('closedby','any');
await runPopoverOuterTest(t,dialog_inner,popover_outer);
},'Popover parent, dialog closedby=any child');
promise_test(async (t) => {
dialog_inner.setAttribute('closedby','closerequest');
await runPopoverOuterTest(t,dialog_inner,popover_outer);
},'Popover parent, dialog closedby=closerequest child');
promise_test(async (t) => {
dialog_inner.setAttribute('closedby','none');
await runPopoverOuterTest(t,dialog_inner,popover_outer);
},'Popover parent, dialog closedby=none child');
</script>

0 comments on commit 5420c40

Please sign in to comment.