Bug 1965052 - Only check top-most dialog for closedby in HandleEsc r=edgar,dom-core
We do not need to iterate over all open dialogs, as the top-most dialog must respond _somehow_ to the escape key press, it either closes, or it blocks the escape key-press, disallowing outer dialogs to handle the key-press. Differential Revision: https://phabricator.services.mozilla.com/D248309
This commit is contained in:
committed by
mozilla@keithcirkel.co.uk
parent
fdf96a1cd2
commit
c71aafeaae
@@ -15029,14 +15029,17 @@ void Document::HandleEscKey() {
|
||||
}
|
||||
}
|
||||
// Not all dialogs exist in the top layer, so despite already iterating
|
||||
// through all top layer elements we also need to iterate over non-modal
|
||||
// dialogs, as they may have a specified `closedby` value which may allow them
|
||||
// to be closed via Escape key.
|
||||
for (RefPtr<HTMLDialogElement> dialog : Reversed(mOpenDialogs)) {
|
||||
// through all top layer elements we also need to check open dialogs that are
|
||||
// _not_ open via the top-layer (showModal).
|
||||
// The top-most dialog in mOpenDialogs may need to be closed.
|
||||
if (RefPtr<HTMLDialogElement> dialog =
|
||||
mOpenDialogs.SafeLastElement(nullptr)) {
|
||||
if (dialog->GetClosedBy() != HTMLDialogElement::ClosedBy::None) {
|
||||
MOZ_ASSERT(StaticPrefs::dom_dialog_light_dismiss_enabled(),
|
||||
"Light Dismiss must have been enabled for GetClosedBy() "
|
||||
"returns != ClosedBy::None");
|
||||
const mozilla::dom::Optional<nsAString> returnValue;
|
||||
dialog->RequestClose(returnValue);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
prefs: [dom.closewatcher.enabled: true, dom.dialog.light-dismiss.enabled: true]
|
||||
@@ -0,0 +1,127 @@
|
||||
<!DOCTYPE html>
|
||||
<meta charset="utf-8">
|
||||
<link rel="author" href="mailto:wpt@keithcirkel.co.uk">
|
||||
<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>
|
||||
|
||||
<button id="outside">Outside</button>
|
||||
<dialog id=outer closedby="closerequest">
|
||||
<dialog id=inner>
|
||||
</dialog>
|
||||
</dialog>
|
||||
|
||||
<script>
|
||||
function awaitEvent(el, type, signal) {
|
||||
const {promise, resolve} = Promise.withResolvers();
|
||||
el.addEventListener(type, resolve, { once: true, signal });
|
||||
return promise
|
||||
}
|
||||
|
||||
promise_test(async (t) => {
|
||||
inner.setAttribute('closedby', 'none');
|
||||
outer.show();
|
||||
// Need to ensure that CloseWatcher does not collapse
|
||||
// both of these shows into a single CloseWatcher group.
|
||||
await test_driver.bless();
|
||||
inner.show();
|
||||
assert_true(inner.open, "The inner dialog is open");
|
||||
assert_true(outer.open, "The outer dialog is open");
|
||||
|
||||
let cancelFired = false;
|
||||
inner.addEventListener('cancel', () => cancelFired = true, t.get_signal());
|
||||
outer.addEventListener('cancel', () => cancelFired = true, t.get_signal());
|
||||
|
||||
const ESC = '\uE00C';
|
||||
await test_driver.send_keys(document.documentElement,ESC);
|
||||
|
||||
assert_false(cancelFired, "The cancel event was not fired");
|
||||
assert_true(inner.open, "The inner dialog is still open");
|
||||
assert_true(outer.open, "The outer dialog is still open");
|
||||
},'With an inner closedby=none, the outer & inner dialogs stays open when Esc is pressed');
|
||||
|
||||
promise_test(async (t) => {
|
||||
inner.setAttribute('closedby', 'none');
|
||||
outer.show();
|
||||
// Need to ensure that CloseWatcher does not collapse
|
||||
// both of these shows into a single CloseWatcher group.
|
||||
await test_driver.bless();
|
||||
inner.show();
|
||||
assert_true(inner.open, "The inner dialog is open");
|
||||
assert_true(outer.open, "The outer dialog is open");
|
||||
|
||||
let cancelFired = false;
|
||||
inner.addEventListener('cancel', () => cancelFired = true, t.get_signal());
|
||||
outer.addEventListener('cancel', () => cancelFired = true, t.get_signal());
|
||||
|
||||
// Try clicking outside
|
||||
await clickOn(outside);
|
||||
|
||||
assert_false(cancelFired, "The cancel event was not fired");
|
||||
assert_true(inner.open, "The inner dialog is open");
|
||||
assert_true(outer.open, "The outer dialog is open");
|
||||
},'With an inner closedby=none, the outer & inner dialogs stays open when clicked outside');
|
||||
|
||||
promise_test(async (t) => {
|
||||
inner.setAttribute('closedby', 'any');
|
||||
outer.show();
|
||||
// Need to ensure that CloseWatcher does not collapse
|
||||
// both of these shows into a single CloseWatcher group.
|
||||
await test_driver.bless();
|
||||
inner.show();
|
||||
assert_true(inner.open, "The inner dialog is open");
|
||||
assert_true(outer.open, "The outer dialog is open");
|
||||
|
||||
let innerCancelled = false;
|
||||
let outerCancelled = false;
|
||||
inner.addEventListener('cancel', () => innerCancelled = true, t.get_signal());
|
||||
outer.addEventListener('cancel', () => outerCancelled = true, t.get_signal());
|
||||
|
||||
let innerClosed = awaitEvent(inner, 'close', t.get_signal());
|
||||
|
||||
// Try clicking outside
|
||||
const ESC = '\uE00C';
|
||||
await test_driver.send_keys(document.documentElement,ESC);
|
||||
|
||||
await innerClosed;
|
||||
|
||||
assert_false(outerCancelled, "The outer cancel event was not fired");
|
||||
assert_true(innerCancelled, "The inner cancel event was fired");
|
||||
|
||||
assert_false(inner.open, "The inner dialog is NOT open");
|
||||
assert_true(outer.open, "The outer dialog is open");
|
||||
},'With an inner closedby=any, the outer dialog stays open but the inner dialogs should close, when Esc is pressed');
|
||||
|
||||
promise_test(async (t) => {
|
||||
inner.setAttribute('closedby', 'any');
|
||||
outer.show();
|
||||
// Need to ensure that CloseWatcher does not collapse
|
||||
// both of these shows into a single CloseWatcher group.
|
||||
await test_driver.bless();
|
||||
inner.show();
|
||||
assert_true(inner.open, "The inner dialog is open");
|
||||
assert_true(outer.open, "The outer dialog is open");
|
||||
|
||||
let innerCancelled = false;
|
||||
let outerCancelled = false;
|
||||
inner.addEventListener('cancel', () => innerCancelled = true, t.get_signal());
|
||||
outer.addEventListener('cancel', () => outerCancelled = true, t.get_signal());
|
||||
|
||||
let innerClosed = awaitEvent(inner, 'close', t.get_signal());
|
||||
|
||||
// Try clicking outside
|
||||
await clickOn(outside);
|
||||
|
||||
await innerClosed;
|
||||
|
||||
assert_false(outerCancelled, "The outer cancel event was not fired");
|
||||
assert_true(innerCancelled, "The inner cancel event was fired");
|
||||
|
||||
assert_false(inner.open, "The inner dialog is NOT open");
|
||||
assert_true(outer.open, "The outer dialog is open");
|
||||
},'With an inner closedby=any, the outer dialog stays open but the inner dialogs should close, when clicked outside');
|
||||
</script>
|
||||
Reference in New Issue
Block a user