Source code

Revision control

Copy as Markdown

Other Tools

Test Info:

<!DOCTYPE html>
<meta charset="utf-8">
<title>Popover combined with dialog/fullscreen behavior</title>
<link rel=author href="mailto:masonf@chromium.org">
<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="/common/top-layer.js"></script>
<script src="resources/popover-utils.js"></script>
<button id=visible>Visible button</button>
<div id=examples>
<dialog popover>Popover Dialog</dialog>
<dialog popover open style="top:50px;">Open Non-modal Popover Dialog</dialog>
<div popover class=fullscreen>Fullscreen Popover</div>
<dialog popover class=fullscreen>Fullscreen Popover Dialog</dialog>
<dialog popover open class=fullscreen style="top:200px;">Fullscreen Open Non-modal Popover Dialog</dialog>
</div>
<style>
[popover] {
inset:auto;
top:0;
left:0;
}
[popover].fullscreen.visible {
display:block;
}
</style>
<script>
const isDialog = (ex) => ex instanceof HTMLDialogElement;
const isFullscreen = (ex) => ex.classList.contains('fullscreen');
function ensureIsOpenPopover(ex,message) {
// Because :popover-open will eventually support <dialog>, this does extra work to
// verify we're dealing with an :popover-open Popover. Note that this will also throw
// if this is an element with the `popover` attribute that has been made
// visible via an explicit `display:block` style rule.
message = message || 'Error';
assert_true(ex.matches(':popover-open'),`${message}: Popover doesn\'t match :popover-open`);
ex.hidePopover(); // Shouldn't throw if this is a showing popover
ex.showPopover(); // Show it again to avoid state change
assert_true(ex.matches(':popover-open'),`${message}: Sanity`);
}
window.onload = () => requestAnimationFrame(() => requestAnimationFrame(() => {
const examples = Array.from(document.querySelectorAll('#examples>*'));
examples.forEach(ex => {
promise_test(async (t) => {
t.add_cleanup(() => ex.remove());
// Test initial conditions
if (ex.hasAttribute('open')) {
assert_true(isDialog(ex));
assert_true(isElementVisible(ex),'Open dialog should be visible by default');
ex.showPopover(); // Should not throw
ex.removeAttribute('open');
ex.hidePopover();
assert_false(isElementVisible(ex),'Removing the open attribute should hide the dialog');
} else {
ex.showPopover(); // Should not throw
ensureIsOpenPopover(ex,'showPopover should work');
ex.hidePopover(); // Should not throw
assert_false(ex.matches(':popover-open'),'hidePopover should work');
}
assert_false(isElementVisible(ex));
// Start with popover, try the other API
ex.showPopover();
ensureIsOpenPopover(ex);
let tested_something=false;
if (isDialog(ex)) {
tested_something=true;
assert_throws_dom("InvalidStateError",() => ex.showModal(),'Calling showModal() on an already-showing Popover should throw InvalidStateError');
ex.show(); // Should not throw
ex.close();
ex.showPopover();
}
if (isFullscreen(ex)) {
tested_something=true;
let requestSucceeded = false;
await blessTopLayer(ex);
await ex.requestFullscreen()
.then(() => {requestSucceeded = true;}) // We should not hit this.
.catch((exception) => {
// This exception is expected.
assert_equals(exception.name,'TypeError',`Invalid exception from requestFullscreen() (${exception.message})`);
});
assert_false(requestSucceeded,'requestFullscreen() should not succeed when the element is an already-showing Popover');
}
assert_true(tested_something);
ensureIsOpenPopover(ex);
ex.hidePopover();
// Start with the other API, then try popover
if (isDialog(ex)) {
ex.show();
assert_true(ex.hasAttribute('open'));
ex.showPopover(); // Should not throw
ex.close();
assert_false(ex.hasAttribute('open'));
ex.hidePopover();
ex.showModal();
assert_true(ex.hasAttribute('open'));
assert_throws_dom("InvalidStateError",() => ex.showPopover(),'Calling showPopover() on an already-showing modal dialog should throw InvalidStateError');
ex.close();
assert_false(ex.hasAttribute('open'));
ex.hidePopover();
} else if (isFullscreen(ex)) {
let requestSucceeded = false;
await blessTopLayer(visible);
await ex.requestFullscreen()
.then(() => {
assert_throws_dom("InvalidStateError",() => ex.showPopover(),'Calling showPopover() on an already-fullscreen element should throw InvalidStateError');
});
await document.exitFullscreen()
.then(() => assert_true(true));
}
// Finally, try invoking these combined popovers via a declarative invoker
const button = document.createElement('button');
t.add_cleanup(() => button.remove());
document.body.appendChild(button);
button.popoverTargetElement = ex;
button.popoverTargetAction = "toggle";
assert_false(ex.matches(':popover-open'));
await clickOn(button);
ensureIsOpenPopover(ex,'Invoking element should be able to invoke all popovers');
ex.hidePopover();
if (isDialog(ex)) {
ex.showModal();
assert_true(ex.hasAttribute('open'));
} else if (isFullscreen(ex)) {
// Popover fullscreen isn't visible by default, so explicitly add
// display:block, so that calls to "clickOn" can succeed.
ex.classList.add('visible');
await blessTopLayer(ex);
await ex.requestFullscreen();
} else {
assert_unreached('Not a dialog or fullscreen');
}
ex.appendChild(button); // Add button to the element, so it's visible to click
await clickOn(button);
assert_false(ex.matches(':popover-open'),'The invoker click should have failed on the already-open dialog/fullscreen');
if (isDialog(ex)) {
ex.close();
} else {
await document.exitFullscreen()
}
}, `Popover combination: ${ex.textContent}`);
});
}));
</script>