Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

<!DOCTYPE html>
<html>
<link rel=author href=mailto:dom@chromium.org>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<menubar>
<fieldset checkable=single>
<menuitem id=menubar-menuitem></menuitem>
</fieldset>
</menubar>
<menulist>
<menuitem id=menulist-menuitem-not-checkable></menuitem>
<fieldset checkable=single>
<menuitem id=menulist-menuitem-checkable></menuitem>
</fieldset>
<fieldset checkable=single>
<menuitem id=single1></menuitem>
<menuitem id=single2></menuitem>
<menuitem id=single3></menuitem>
</fieldset>
<fieldset checkable=multiple>
<menuitem id=multiple1></menuitem>
<menuitem id=multiple2></menuitem>
<menuitem id=multiple3></menuitem>
</fieldset>
<fieldset checkable=multiple>
<menuitem defaultchecked id=defaultCheckedItem>Default checked</menuitem>
</fieldset>
</menulist>
<script>
test(() => {
const mi = document.createElement('menuitem');
assert_false(mi.checked, "defaults to unchecked when detached");
assert_false(mi.matches(":checked"));
mi.checked = true;
assert_equals(mi.checked, false, "cannot be checked when detached");
assert_false(mi.matches(":checked"));
document.body.append(mi);
assert_equals(mi.checked, false, "defaults to unchecked when connected");
assert_false(mi.matches(":checked"));
mi.checked = true;
assert_equals(mi.checked, false,
"cannot be checked when not attached in a menulist");
assert_false(mi.matches(":checked"));
mi.remove();
}, "menuitem is not checkable on its own");
test(() => {
const mi = document.querySelector("#menubar-menuitem");
assert_equals(mi.checked, false, "menuitem in menubar is not checked");
assert_false(mi.matches(":checked"));
mi.checked = true;
assert_equals(mi.checked, false, "menuitem in menubar cannot be checked");
assert_false(mi.matches(":checked"));
}, "menuitem in menubar is not checkable");
test(() => {
const nonCheckable =
document.querySelector("#menulist-menuitem-not-checkable");
assert_equals(nonCheckable.checked, false,
"menuitem direct child of menulist is not checked");
assert_false(nonCheckable.matches(":checked"));
nonCheckable.checked = true;
assert_equals(nonCheckable.checked, false,
"menuitem direct child of menulist cannot be checked");
assert_false(nonCheckable.matches(":checked"));
const checkable = document.querySelector("#menulist-menuitem-checkable");
assert_equals(nonCheckable.checked, false,
"checkable menuitem is not checked");
assert_false(nonCheckable.matches(":checked"));
checkable.checked = true;
assert_equals(checkable.checked, true, "checkable menuitem is checked");
assert_true(checkable.matches(":checked"));
}, "menuitem in menulist");
test(() => {
assert_false(single1.checked, "single1 not checked");
assert_false(single1.matches(":checked"), "single1 not :checked");
assert_false(single2.checked, "single2 not checked");
assert_false(single2.matches(":checked"), "single2 not :checked");
assert_false(single3.checked, "single3 not checked");
assert_false(single3.matches(":checked"), "single3 not :checked");
single1.checked = true;
assert_true(single1.checked, "single1 IS checked [after single1 checked]");
assert_true(single1.matches(":checked"),
"single1 IS :checked [after single1 checked]");
assert_false(single2.checked, "single2 not checked [after single1 checked]");
assert_false(single2.matches(":checked"),
"single2 not :checked [after single1 checked]");
assert_false(single3.checked, "single3 not checked [after single1 checked]");
assert_false(single3.matches(":checked"),
"single3 not :checked [after single1 checked]");
single2.checked = true;
assert_false(single1.checked, "single1 not checked [after single2 checked]");
assert_false(single1.matches(":checked"),
"single1 not :checked [after single2 checked]");
assert_true(single2.checked, "single2 IS checked [after single1 checked]");
assert_true(single2.matches(":checked"),
"single2 IS :checked [after single2 checked]");
assert_false(single3.checked, "single3 not checked [after single2 checked]");
assert_false(single3.matches(":checked"),
"single3 not :checked [after single2 checked]");
single3.checked = true;
assert_false(single1.checked, "single1 not checked [after single3 checked]");
assert_false(single1.matches(":checked"),
"single1 not :checked [after single3 checked]");
assert_false(single2.checked, "single2 not checked [after single3 checked]");
assert_false(single2.matches(":checked"),
"single2 not :checked [after single3 checked]");
assert_true(single3.checked, "single3 IS checked [after single3 checked]");
assert_true(single3.matches(":checked"),
"single3 IS :checked [after single3 checked]");
}, "checkable menuitem exclusivity");
test(() => {
assert_false(multiple1.checked, "multiple1 not checked");
assert_false(multiple1.matches(":checked"), "multiple1 not :checked");
assert_false(multiple2.checked, "multiple2 not checked");
assert_false(multiple2.matches(":checked"), "multiple2 not :checked");
assert_false(multiple3.checked, "multiple3 not checked");
assert_false(multiple3.matches(":checked"), "multiple2 not :checked");
multiple1.checked = true;
assert_true(multiple1.checked, "multiple1 checked [after multiple1 checked]");
assert_true(multiple1.matches(":checked"),
"multiple1 IS :checked [after multiple1 checked]");
assert_false(multiple2.checked, "multiple2 not checked [after multiple1 checked]");
assert_false(multiple2.matches(":checked"),
"multiple2 not :checked [after multiple1 checked]");
assert_false(multiple3.checked, "multiple3 not checked [after multiple1 checked]");
assert_false(multiple3.matches(":checked"),
"multiple3 not :checked [after multiple1 checked]");
multiple2.checked = true;
assert_true(multiple1.checked, "multiple1 checked [after multiple2 checked]");
assert_true(multiple1.matches(":checked"),
"multiple1 IS :checked [after multiple2 checked]");
assert_true(multiple2.checked, "multiple2 checked [after multiple2 checked]");
assert_true(multiple2.matches(":checked"),
"multiple2 IS :checked [after multiple2 checked]");
assert_false(multiple3.checked, "multiple3 not checked [after multiple2 checked]");
assert_false(multiple3.matches(":checked"),
"multiple3 not :checked [after multiple2 checked]");
multiple3.checked = true;
assert_true(multiple1.checked, "multiple1 checked [after multiple3 checked]");
assert_true(multiple1.matches(":checked"),
"multiple1 IS :checked [after multiple3 checked]");
assert_true(multiple2.checked, "multiple2 checked [after multiple3 checked]");
assert_true(multiple2.matches(":checked"),
"multiple2 IS :checked [after multiple3 checked]");
assert_true(multiple3.checked, "multiple3 checked [after multiple3 checked]");
assert_true(multiple3.matches(":checked"),
"multiple3 IS checked [after multiple3 checked]");
}, "checkable multiple");
test(() => {
const menulist = document.createElement('menulist');
const fieldset = menulist.appendChild(document.createElement('fieldset'));
fieldset.setAttribute("checkable", "single");
const single1 = fieldset.appendChild(document.createElement('menuitem'));
const single2 = fieldset.appendChild(document.createElement('menuitem'));
single1.checked = true;
single2.checked = true;
assert_false(single1.checked, "single1 is unchecked");
assert_true(single2.checked, "single2 is checked");
}, "checkable menuitem exclusivity when disconnected");
test(() => {
const menulist = document.createElement('menulist');
const fieldset = menulist.appendChild(document.createElement('fieldset'));
fieldset.setAttribute("checkable", "single");
const single1 = fieldset.appendChild(document.createElement('menuitem'));
single1.checked = true;
assert_true(single1.checked, true);
fieldset.removeAttribute('checkable');
assert_false(single1.checked,
"menuitem gets unchecked after fieldset becomes uncheckable");
single1.checked = true;
assert_false(single1.checked,
"menuitem cannot become checked after fieldset becomes uncheckable");
}, "when fieldset becomes uncheckable, so do its menuitems");
test(() => {
const menulist = document.createElement('menulist');
const fieldset = menulist.appendChild(document.createElement('fieldset'));
fieldset.setAttribute("checkable", "multiple");
const single1 = fieldset.appendChild(document.createElement('menuitem'));
const single2 = fieldset.appendChild(document.createElement('menuitem'));
single1.checked = true;
single2.checked = true;
fieldset.setAttribute("checkable", "single");
assert_true(single1.checked, "first menuitem stays checked");
assert_false(single2.checked, "second menuitem becomes unchecked");
}, "fieldset multiple => single; all but the first checked menuitem gets " +
"reset");
test(() => {
const menulist = document.createElement('menulist');
const fieldset = menulist.appendChild(document.createElement('fieldset'));
fieldset.setAttribute("checkable", "");
const item1 = fieldset.appendChild(document.createElement('menuitem'));
const item2 = fieldset.appendChild(document.createElement('menuitem'));
assert_false(item1.checked, "item1 is unchecked");
assert_false(item2.checked, "item2 is unchecked");
item1.checked = true;
item2.checked = true;
assert_true(item1.checked, "item1 is checked");
assert_true(item2.checked, "item2 is checked");
}, "fieldset with checkable=\"\" allows multiple menuitems to be checked");
test(() => {
const menulist = document.createElement('menulist');
const fieldset = menulist.appendChild(document.createElement('fieldset'));
fieldset.setAttribute("checkable", "unknownvalue");
const item1 = fieldset.appendChild(document.createElement('menuitem'));
const item2 = fieldset.appendChild(document.createElement('menuitem'));
assert_false(item1.checked, "item1 is unchecked");
assert_false(item2.checked, "item2 is unchecked");
item1.checked = true;
item2.checked = true;
assert_true(item1.checked, "item1 is checked");
assert_true(item2.checked, "item2 is checked");
}, "fieldset with checkable=unknownvalue allows multiple menuitems to be checked");
test(() => {
const menulist = document.createElement('menulist');
const fieldset = menulist.appendChild(document.createElement('fieldset'));
fieldset.setAttribute("checkable", "multiple");
const item1 = fieldset.appendChild(document.createElement('menuitem'));
const item2 = fieldset.appendChild(document.createElement('menuitem'));
item1.checked = true;
item2.checked = true;
fieldset.setAttribute("checkable", "");
assert_true(item1.checked, "first menuitem stays checked");
assert_true(item2.checked, "second menuitem stays checked");
}, "fieldset multiple => ''; menuitems stay checked");
test(() => {
const menulist = document.createElement('menulist');
const fieldset = menulist.appendChild(document.createElement('fieldset'));
fieldset.setAttribute("checkable", "multiple");
const item1 = fieldset.appendChild(document.createElement('menuitem'));
const item2 = fieldset.appendChild(document.createElement('menuitem'));
item1.checked = true;
item2.checked = true;
fieldset.setAttribute("checkable", "unknownvalue");
assert_true(item1.checked, "first menuitem stays checked");
assert_true(item2.checked, "second menuitem stays checked");
}, "fieldset multiple => unknownvalue; menuitems stay checked");
test(() => {
const menulist = document.createElement('menulist');
const fieldset = menulist.appendChild(document.createElement('fieldset'));
fieldset.setAttribute("checkable", "");
const item1 = fieldset.appendChild(document.createElement('menuitem'));
const item2 = fieldset.appendChild(document.createElement('menuitem'));
item1.checked = true;
item2.checked = true;
fieldset.setAttribute("checkable", "single");
assert_true(item1.checked, "first menuitem stays checked");
assert_false(item2.checked, "second menuitem becomes unchecked");
}, "fieldset '' => single; all but the first checked menuitem gets reset");
test(() => {
const menulist = document.createElement('menulist');
const fieldset = menulist.appendChild(document.createElement('fieldset'));
fieldset.setAttribute("checkable", "unknownvalue");
const item1 = fieldset.appendChild(document.createElement('menuitem'));
const item2 = fieldset.appendChild(document.createElement('menuitem'));
item1.checked = true;
item2.checked = true;
fieldset.setAttribute("checkable", "single");
assert_true(item1.checked, "first menuitem stays checked");
assert_false(item2.checked, "second menuitem becomes unchecked");
}, "fieldset unknownvalue => single; all but the first checked menuitem gets reset");
test(() => {
const menulist = document.createElement('menulist');
const fieldset = menulist.appendChild(document.createElement('fieldset'));
fieldset.setAttribute("checkable", "");
const item1 = fieldset.appendChild(document.createElement('menuitem'));
item1.checked = true;
assert_true(item1.checked, true);
fieldset.removeAttribute("checkable");
assert_false(item1.checked,
"menuitem gets unchecked after fieldset becomes uncheckable");
item1.checked = true;
assert_false(item1.checked,
"menuitem cannot become checked after fieldset becomes uncheckable");
}, "when fieldset with checkable='' becomes uncheckable, so do its menuitems");
test(() => {
const menulist = document.createElement('menulist');
const fieldset = menulist.appendChild(document.createElement('fieldset'));
fieldset.setAttribute("checkable", "unknownvalue");
const item1 = fieldset.appendChild(document.createElement('menuitem'));
item1.checked = true;
assert_true(item1.checked, true);
fieldset.removeAttribute("checkable");
assert_false(item1.checked,
"menuitem gets unchecked after fieldset becomes uncheckable");
item1.checked = true;
assert_false(item1.checked,
"menuitem cannot become checked after fieldset becomes uncheckable");
}, "when fieldset with checkable=unknownvalue becomes uncheckable, so do its menuitems");
// Testing the `defaultchecked` content attribute and `defaultChecked` IDL
// attribute.
test(() => {
assert_true(defaultCheckedItem.checked,
"defaultchecked makes the menu item checked by default");
assert_true(defaultCheckedItem.matches(":default"),
":default matches when the defaultchecked attribute is present");
// Unset the default while checkedness is still clean.
defaultCheckedItem.removeAttribute('defaultchecked');
assert_false(defaultCheckedItem.defaultChecked,
"defaultChecked IDL attribute reflects the content attribute");
assert_false(defaultCheckedItem.checked,
"Unsetting defaultchecked when checkedness is clean unchecks the item");
assert_false(defaultCheckedItem.matches(":default"),
":default does not match when defaultchecked attribute is missing or false (1/2)");
// Toggle it again while checkedness is clean, but this time with the IDL
// attribute, which should reflect the content attribute.
defaultCheckedItem.defaultChecked = true;
assert_true(defaultCheckedItem.checked,
"Setting defaultChecked to true when checkedness is clean checks the item");
assert_true(defaultCheckedItem.matches(":default"),
":default matches when the defaultchecked attribute is set, even via IDL");
// Unset checkedness, dirtying the default checkedness.
defaultCheckedItem.checked = false;
assert_true(defaultCheckedItem.defaultChecked,
"Default checkedness is still true");
assert_false(defaultCheckedItem.checked, "checked becomes false");
assert_true(defaultCheckedItem.matches(":default"),
"still matches the default checkedness");
// Unset default checkedness. This will only have the effect of changing the
// :default pseudo-class matching.
defaultCheckedItem.defaultChecked = false;
assert_false(defaultCheckedItem.matches(":default"),
":default no longer matches");
// Set default checkedness back to true. Since the checkedness is now dirty,
// this won't change the actual checked state anymore, as the menu item has
// had its default checkedness overridden.
defaultCheckedItem.defaultChecked = true;
assert_false(defaultCheckedItem.checked,
"checked state is no longer dictated by default checkedness any");
assert_true(defaultCheckedItem.matches(":default"),
":default matches again, following the default checkedness, even " +
"though the checkedness is dirty");
}, "Default checkedness and checkedness dirtying are wired up correctly");
promise_test(async (t) => {
const menulist = document.createElement('menulist');
document.body.appendChild(menulist);
const fieldset = menulist.appendChild(document.createElement('fieldset'));
fieldset.setAttribute('checkable', 'single');
const item1 = fieldset.appendChild(document.createElement('menuitem'));
const item2 = fieldset.appendChild(document.createElement('menuitem'));
// 1. Verify that setting checked via IDL attribute does not fire checked
// event.
let item1CheckedEvents = 0;
item1.addEventListener('checked', () => {
item1CheckedEvents++;
});
item1.checked = true;
assert_equals(item1CheckedEvents, 0,
"setting .checked to true via IDL should not fire checked event");
item1.checked = false;
assert_equals(item1CheckedEvents, 0,
"setting .checked to false via IDL should not fire checked event");
await new Promise(resolve => t.step_timeout(resolve, 0));
assert_equals(item1CheckedEvents, 0,
"no checked event should be queued when setting .checked via IDL");
// 2. Verify that activation (click) queues a checked event on checkable menuitem.
let eventReceived = null;
const item1EventPromise = new Promise(resolve => {
item1.addEventListener('checked', (e) => {
eventReceived = e;
resolve();
}, { once: true });
});
item1.click();
// Checked event should be queued asynchronously on user interaction task source,
// so it does not fire synchronously during click().
assert_equals(eventReceived, null,
"checked event should not fire synchronously during click()");
assert_true(item1.checked,
"item should be checked synchronously after activation");
await item1EventPromise;
assert_not_equals(eventReceived, null,
"checked event should fire asynchronously after activation");
assert_equals(eventReceived.type, "checked",
"event type should be 'checked'");
assert_false(eventReceived.bubbles, "checked event should not bubble");
assert_false(eventReceived.cancelable, "checked event should not be cancelable");
assert_true(item1.checked,
"item should be checked after checked event fires");
assert_true(item1.matches(':checked'),
"item should match :checked after checked event fires");
// 3. Verify event timing with exclusivity update, and exclusivity update does not fire checked event.
let item2EventReceived = false;
let item1EventsDuringItem2Click = 0;
item1.addEventListener('checked', () => {
item1EventsDuringItem2Click++;
});
const item2EventPromise = new Promise(resolve => {
item2.addEventListener('checked', () => {
item2EventReceived = true;
resolve();
}, { once: true });
});
item2.click();
assert_false(item2EventReceived,
"item2 checked event should not fire synchronously");
assert_true(item2.checked,
"item2 should be checked synchronously after click");
assert_false(item1.checked,
"item1 should be unchecked synchronously after click due to exclusivity");
await item2EventPromise;
assert_true(item2EventReceived,
"checked event should fire on item2 activation");
assert_true(item2.checked,
"item2 should be checked after item2's checked event");
assert_false(item1.checked,
"item1 should be unchecked after item2's checked event");
assert_equals(item1EventsDuringItem2Click, 0,
"unchecking item1 due to exclusivity should not fire checked event on item1");
menulist.remove();
}, "checked event fires asynchronously on checkable menuitem activation, but not from IDL attribute setter or exclusivity updates");
promise_test(async () => {
const menulist = document.createElement('menulist');
document.body.appendChild(menulist);
const fieldset = menulist.appendChild(document.createElement('fieldset'));
fieldset.setAttribute('checkable', 'multiple');
const item = fieldset.appendChild(document.createElement('menuitem'));
item.checked = true;
let eventFired = false;
const eventPromise = new Promise(resolve => {
item.addEventListener('checked', () => {
eventFired = true;
resolve();
}, { once: true });
});
item.click();
assert_false(eventFired,
"checked event should not fire synchronously on uncheck");
assert_false(item.checked,
"item should be unchecked synchronously after click");
await eventPromise;
assert_true(eventFired,
"checked event should fire asynchronously when toggling from checked to unchecked");
assert_false(item.checked,
"item should be unchecked after event promise resolves");
menulist.remove();
}, "checked event fires asynchronously when toggling a checkable menuitem to unchecked");
promise_test(async (t) => {
const menulist = document.createElement('menulist');
document.body.appendChild(menulist);
const item = menulist.appendChild(document.createElement('menuitem'));
let eventFired = false;
item.addEventListener('checked', () => {
eventFired = true;
});
item.click();
assert_false(eventFired,
"checked event should not fire synchronously on non-checkable menuitem activation");
await new Promise(resolve => t.step_timeout(resolve, 0));
assert_false(eventFired,
"checked event should not fire asynchronously on non-checkable menuitem activation");
menulist.remove();
}, "checked event does not fire on non-checkable menuitem activation");
</script>