Don't throw when popovers and dialogs are in requested state
This is being changed in the HTML spec here:
https://github.com/whatwg/html/pull/9142
Change-Id: Ib8aaaf314c2a1de5d082494e5172e029d531c8e8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4494823
Reviewed-by: Mason Freed <masonf@chromium.org>
Commit-Queue: Joey Arhar <jarhar@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1144952}
diff --git a/third_party/blink/renderer/core/html/html_dialog_element.cc b/third_party/blink/renderer/core/html/html_dialog_element.cc
index ca1bcd9..1f1a0a0 100644
--- a/third_party/blink/renderer/core/html/html_dialog_element.cc
+++ b/third_party/blink/renderer/core/html/html_dialog_element.cc
@@ -188,8 +188,21 @@
}
void HTMLDialogElement::show(ExceptionState& exception_state) {
- if (FastHasAttribute(html_names::kOpenAttr))
- return;
+ if (RuntimeEnabledFeatures::PopoverDialogDontThrowEnabled()) {
+ if (FastHasAttribute(html_names::kOpenAttr)) {
+ if (is_modal_) {
+ exception_state.ThrowDOMException(
+ DOMExceptionCode::kInvalidStateError,
+ "The dialog is already open as a modal dialog, and therefore "
+ "cannot be opened as a non-modal dialog.");
+ }
+ return;
+ }
+ } else {
+ if (FastHasAttribute(html_names::kOpenAttr)) {
+ return;
+ }
+ }
if (RuntimeEnabledFeatures::HTMLPopoverAttributeEnabled(
GetDocument().GetExecutionContext()) &&
@@ -247,12 +260,24 @@
};
void HTMLDialogElement::showModal(ExceptionState& exception_state) {
- if (FastHasAttribute(html_names::kOpenAttr)) {
- return exception_state.ThrowDOMException(
- DOMExceptionCode::kInvalidStateError,
- "The element already has an 'open' "
- "attribute, and therefore cannot be "
- "opened modally.");
+ if (RuntimeEnabledFeatures::PopoverDialogDontThrowEnabled()) {
+ if (FastHasAttribute(html_names::kOpenAttr)) {
+ if (!is_modal_) {
+ exception_state.ThrowDOMException(
+ DOMExceptionCode::kInvalidStateError,
+ "The dialog is already open as a non-modal dialog, and therefore "
+ "cannot be opened as a modal dialog.");
+ }
+ return;
+ }
+ } else {
+ if (FastHasAttribute(html_names::kOpenAttr)) {
+ return exception_state.ThrowDOMException(
+ DOMExceptionCode::kInvalidStateError,
+ "The element already has an 'open' "
+ "attribute, and therefore cannot be "
+ "opened modally.");
+ }
}
if (!isConnected()) {
return exception_state.ThrowDOMException(
diff --git a/third_party/blink/renderer/core/html/html_element.cc b/third_party/blink/renderer/core/html/html_element.cc
index 7b82aadf..2f1e0de 100644
--- a/third_party/blink/renderer/core/html/html_element.cc
+++ b/third_party/blink/renderer/core/html/html_element.cc
@@ -1330,6 +1330,26 @@
"value for the 'popover' attribute.");
return false;
}
+ if (action == PopoverTriggerAction::kShow &&
+ GetPopoverData()->visibilityState() != PopoverVisibilityState::kHidden) {
+ if (!RuntimeEnabledFeatures::PopoverDialogDontThrowEnabled()) {
+ maybe_throw_exception(DOMExceptionCode::kInvalidStateError,
+ "Invalid on popover elements which aren't hidden.");
+ }
+ return false;
+ }
+ if (action == PopoverTriggerAction::kHide &&
+ GetPopoverData()->visibilityState() != PopoverVisibilityState::kShowing) {
+ // Important to check that visibility is not kShowing (rather than
+ // popoverOpen()), because a hide transition might have been started on this
+ // popover already, and we don't want to allow a double-hide.
+ if (!RuntimeEnabledFeatures::PopoverDialogDontThrowEnabled()) {
+ maybe_throw_exception(
+ DOMExceptionCode::kInvalidStateError,
+ "Invalid on popover elements that aren't already showing.");
+ }
+ return false;
+ }
if (!isConnected()) {
maybe_throw_exception(DOMExceptionCode::kInvalidStateError,
"Invalid on disconnected popover elements.");
@@ -1341,22 +1361,6 @@
"hiding a popover element.");
return false;
}
- if (action == PopoverTriggerAction::kShow &&
- GetPopoverData()->visibilityState() != PopoverVisibilityState::kHidden) {
- maybe_throw_exception(DOMExceptionCode::kInvalidStateError,
- "Invalid on popover elements which aren't hidden.");
- return false;
- }
- if (action == PopoverTriggerAction::kHide &&
- GetPopoverData()->visibilityState() != PopoverVisibilityState::kShowing) {
- // Important to check that visibility is not kShowing (rather than
- // popoverOpen()), because a hide transition might have been started on this
- // popover already, and we don't want to allow a double-hide.
- maybe_throw_exception(
- DOMExceptionCode::kInvalidStateError,
- "Invalid on popover elements that aren't already showing.");
- return false;
- }
if (action == PopoverTriggerAction::kShow && IsA<HTMLDialogElement>(this) &&
hasAttribute(html_names::kOpenAttr)) {
maybe_throw_exception(DOMExceptionCode::kInvalidStateError,
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 758ca4d..98d16de0 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -2697,6 +2697,13 @@
name: "PointerEventDeviceId",
status: "test",
},
+ // PopoverDialogDontThrow makes popover and dialog elements stop throwing
+ // exceptions when calling their show and hide methods when they are
+ // already in their requested state. See https://github.com/whatwg/html/pull/9142
+ {
+ name: "PopoverDialogDontThrow",
+ status: "stable",
+ },
{
name: "Portals",
// Portals must be enabled by blink::features::kPortals as we require the
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/interactive-elements/the-dialog-element/dialog-no-throw-requested-state.html b/third_party/blink/web_tests/external/wpt/html/semantics/interactive-elements/the-dialog-element/dialog-no-throw-requested-state.html
new file mode 100644
index 0000000..c86cbe84
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/interactive-elements/the-dialog-element/dialog-no-throw-requested-state.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/whatwg/html/pull/9142">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<dialog>hello</dialog>
+
+<script>
+test(() => {
+ const dialog = document.querySelector('dialog');
+
+ // calling close() on a dialog that is already closed should not throw.
+ dialog.close();
+
+ dialog.show();
+ // calling show() on a dialog that is already showing non-modal should not throw.
+ dialog.show();
+ assert_throws_dom('InvalidStateError', () => dialog.showModal(),
+ 'Calling showModal() on a dialog that is already showing non-modal should throw.');
+ dialog.close();
+
+ dialog.showModal();
+ assert_throws_dom('InvalidStateError', () => dialog.show(),
+ 'Calling show() on a dialog that is already showing modal should throw.');
+ // calling showModal() on a dialog that is already showing modal should not throw.
+ dialog.showModal();
+});
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-attribute-basic.html b/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-attribute-basic.html
index eab61407c..32d3deb 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-attribute-basic.html
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-attribute-basic.html
@@ -254,11 +254,21 @@
},{once: true});
assert_true(popover.matches(':popover-open'));
assert_true(other_popover.matches(':popover-open'));
- assert_throws_dom('InvalidStateError', () => popover.hidePopover());
+ popover.hidePopover(); // Calling hidePopover on a hidden popover should not throw.
assert_false(other_popover.matches(':popover-open'),'unrelated popover is hidden');
assert_false(popover.matches(':popover-open'),'popover is still hidden if its type changed during hide event');
- assert_throws_dom("InvalidStateError",() => other_popover.hidePopover(),'Nested popover should already be hidden');
- },`Changing the popover type in a "beforetoggle" event handler should throw an exception (during hidePopover())`);
+ other_popover.hidePopover(); // Calling hidePopover on a hidden popover should not throw.
+ },`Changing the popover type in a "beforetoggle" event handler during hidePopover() should not throw an exception`);
+
+ test(t => {
+ const popover = document.createElement('div');
+ assert_throws_dom('NotSupportedError', () => popover.hidePopover(),
+ 'Calling hidePopover on an element without a popover attribute should throw.');
+ popover.setAttribute('popover', 'auto');
+ popover.hidePopover(); // Calling hidePopover on a disconnected popover should not throw.
+ assert_throws_dom('InvalidStateError', () => popover.showPopover(),
+ 'Calling showPopover on a disconnected popover should throw.');
+ },'Calling hidePopover on a disconnected popover should not throw.');
function interpretedType(typeString,method) {
if (validTypes.includes(typeString))
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-light-dismiss.html b/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-light-dismiss.html
index d7d1edd3..4b888169 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-light-dismiss.html
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-light-dismiss.html
@@ -532,7 +532,7 @@
p14.hidePopover();
},{once:true});
assert_true(p13.matches(':popover-open') && p14.matches(':popover-open') && p15.matches(':popover-open'),'all three should be open');
- assert_throws_dom('InvalidStateError',() => p14.hidePopover(),'should throw because the event listener has already hidden the popover');
+ p14.hidePopover();
assert_true(p13.matches(':popover-open'),'p13 should still be open');
assert_false(p14.matches(':popover-open'));
assert_false(p15.matches(':popover-open'));
@@ -579,10 +579,7 @@
p20.showPopover();
});
p20.addEventListener('beforetoggle', logEvents);
- // Because the `beforetoggle` handler shows a different popover,
- // and that action closes the p19 popover, the call to hidePopover()
- // will result in an exception.
- assert_throws_dom('InvalidStateError',() => p19.hidePopover());
+ p19.hidePopover();
assert_array_equals(events,['hide p19','show p20'],'There should not be a second hide event for 19');
assert_false(p19.matches(':popover-open'));
assert_true(p20.matches(':popover-open'));
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-move-documents.html b/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-move-documents.html
index 9feaa4b..11f52c2 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-move-documents.html
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-move-documents.html
@@ -4,10 +4,30 @@
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
+<script>
+async function iframeLoaded(iframe) {
+ return new Promise(resolve => {
+ if (iframe.contentWindow.document.readyState == 'complete') {
+ resolve();
+ } else {
+ iframe.onload = resolve;
+ }
+ });
+}
+</script>
+
<iframe id=myframe srcdoc="<p>iframe</p>"></iframe>
<div id=p1 popover=auto>p1</div>
<script>
-test(() => {
+promise_test(async () => {
+ await iframeLoaded(myframe);
+ await new Promise(resolve => {
+ if (myframe.contentWindow.document.readyState == 'complete') {
+ resolve();
+ } else {
+
+ }
+ });
p1.addEventListener('beforetoggle', () => {
myframe.contentWindow.document.body.appendChild(p1);
});
@@ -18,7 +38,8 @@
<iframe id=myframe2 srcdoc="<p>iframe</p>"></iframe>
<div id=p2 popover=auto>p2</div>
<script>
-test(() => {
+promise_test(async () => {
+ await iframeLoaded(myframe2);
const p2 = document.getElementById('p2');
p2.showPopover();
p2.addEventListener('beforetoggle', () => {
@@ -27,10 +48,7 @@
assert_true(p2.matches(':popover-open'),
'The popover should be open after calling showPopover()');
- // Because the `beforetoggle` handler changes the document,
- // and that action closes the popover, the call to hidePopover()
- // will result in an exception.
- assert_throws_dom('InvalidStateError',() => p2.hidePopover());
+ p2.hidePopover();
assert_false(p2.matches(':popover-open'),
'The popover should be closed after moving it between documents.');
}, 'Moving popovers between documents while hiding should not throw an exception.');
@@ -43,7 +61,8 @@
<div id=p5 popover=auto>p5</div>
</div>
<script>
-test(() => {
+promise_test(async () => {
+ await iframeLoaded(myframe3);
p3.showPopover();
p4.showPopover();
p4.addEventListener('beforetoggle', event => {
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/popovers/resources/popover-utils.js b/third_party/blink/web_tests/external/wpt/html/semantics/popovers/resources/popover-utils.js
index aa69b7d4..5f0e634 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/popovers/resources/popover-utils.js
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/popovers/resources/popover-utils.js
@@ -153,10 +153,10 @@
assertPopoverVisibility(popover, /*isPopover*/true, /*expectedVisibility*/false, 'A popover should start out hidden');
popover.showPopover();
if (checkVisibility) assertPopoverVisibility(popover, /*isPopover*/true, /*expectedVisibility*/true, 'After showPopover(), a popover should be visible');
- assert_throws_dom("InvalidStateError",() => popover.showPopover(),'Calling showPopover on a showing popover should throw InvalidStateError');
+ popover.showPopover(); // Calling showPopover on a showing popover should not throw.
popover.hidePopover();
if (checkVisibility) assertPopoverVisibility(popover, /*isPopover*/true, /*expectedVisibility*/false, 'After hidePopover(), a popover should be hidden');
- assert_throws_dom("InvalidStateError",() => popover.hidePopover(),'Calling hidePopover on a hidden popover should throw InvalidStateError');
+ popover.hidePopover(); // Calling hidePopover on a hidden popover should not throw.
popover.togglePopover();
if (checkVisibility) assertPopoverVisibility(popover, /*isPopover*/true, /*expectedVisibility*/true, 'After togglePopover() on hidden popover, it should be visible');
popover.togglePopover();
@@ -172,7 +172,7 @@
const parent = popover.parentElement;
popover.remove();
assert_throws_dom("InvalidStateError",() => popover.showPopover(),'Calling showPopover on a disconnected popover should throw InvalidStateError');
- assert_throws_dom("InvalidStateError",() => popover.hidePopover(),'Calling hidePopover on a disconnected popover should throw InvalidStateError');
+ popover.hidePopover(); // Calling hidePopover on a disconnected popover should not throw.
assert_throws_dom("InvalidStateError",() => popover.togglePopover(),'Calling hidePopover on a disconnected popover should throw InvalidStateError');
parent.appendChild(popover);
}