| <!doctype html> |
| <link rel="author" href="mailto:jarhar@chromium.org" /> |
| <link rel="author" title="Keith Cirkel" href="mailto:wpt@keithcirkel.co.uk" /> |
| <link rel="help" href="https://github.com/whatwg/html/pull/10091" /> |
| <link rel="help" href="https://github.com/whatwg/html/issues/9733" /> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| |
| <dialog id="mydialog">dialog</dialog> |
| |
| <script> |
| ["show", "showModal"].forEach((methodName) => { |
| const waitForTick = () => new Promise(resolve => step_timeout(resolve, 0)); |
| |
| promise_test(async () => { |
| let openingBeforetoggle = null; |
| let openingToggle = null; |
| |
| mydialog.addEventListener( |
| "beforetoggle", |
| (event) => { |
| assert_equals( |
| event.oldState, |
| "closed", |
| 'Opening beforetoggle should have oldState be "closed".', |
| ); |
| assert_equals( |
| event.newState, |
| "open", |
| 'Opening beforetoggle should have newState be "open".', |
| ); |
| assert_false( |
| mydialog.hasAttribute("open"), |
| "Opening beforetoggle should fire before open attribute is added.", |
| ); |
| openingBeforetoggle = event; |
| }, |
| { once: true }, |
| ); |
| |
| mydialog.addEventListener( |
| "toggle", |
| (event) => { |
| assert_equals( |
| event.oldState, |
| "closed", |
| 'Opening toggle should have oldState be "closed".', |
| ); |
| assert_equals( |
| event.newState, |
| "open", |
| 'Opening toggle should have newState be "open".', |
| ); |
| assert_true( |
| mydialog.hasAttribute("open"), |
| "Opening toggle should fire after open attribute is added.", |
| ); |
| openingToggle = event; |
| }, |
| { once: true }, |
| ); |
| |
| mydialog[methodName](); |
| assert_true( |
| !!openingBeforetoggle, |
| "Opening beforetoggle should fire synchronously.", |
| ); |
| assert_false( |
| !!openingToggle, |
| "Opening toggle should fire asynchronously.", |
| ); |
| |
| await waitForTick(); |
| assert_true( |
| !!openingToggle, |
| "Opening toggle should have fired after tick.", |
| ); |
| |
| let closingBeforetoggle = null; |
| let closingToggle = null; |
| |
| mydialog.addEventListener( |
| "beforetoggle", |
| (event) => { |
| assert_equals( |
| event.oldState, |
| "open", |
| 'Closing beforetoggle should have oldState be "open".', |
| ); |
| assert_equals( |
| event.newState, |
| "closed", |
| 'Closing beforetoggle should have newState be "closed".', |
| ); |
| assert_true( |
| mydialog.hasAttribute("open"), |
| "Closing beforetoggle should fire before open attribute is removed.", |
| ); |
| closingBeforetoggle = event; |
| }, |
| { once: true }, |
| ); |
| mydialog.addEventListener( |
| "toggle", |
| (event) => { |
| assert_equals( |
| event.oldState, |
| "open", |
| 'Closing toggle should have oldState be "open".', |
| ); |
| assert_equals( |
| event.newState, |
| "closed", |
| 'Closing toggle should have newState be "closed".', |
| ); |
| assert_false( |
| mydialog.hasAttribute("open"), |
| "Closing toggle should fire after open attribute is removed.", |
| ); |
| closingToggle = event; |
| }, |
| { once: true }, |
| ); |
| |
| mydialog.close(); |
| assert_true( |
| !!closingBeforetoggle, |
| "Closing beforetoggle should fire synchronously.", |
| ); |
| assert_false( |
| !!closingToggle, |
| "Closing toggle should fire asynchronously.", |
| ); |
| |
| await waitForTick(); |
| assert_true( |
| !!closingToggle, |
| "Closing toggle should have fired after tick.", |
| ); |
| }, `dialog.${methodName}() should fire beforetoggle and toggle events.`); |
| |
| promise_test(async () => { |
| let openingBeforetoggle = null; |
| let openingToggle = null; |
| |
| mydialog.addEventListener( |
| "beforetoggle", |
| (event) => { |
| event.preventDefault(); |
| openingBeforetoggle = event; |
| }, |
| { once: true }, |
| ); |
| |
| mydialog.addEventListener( |
| "toggle", |
| (event) => { |
| openingToggle = event; |
| }, |
| { once: true }, |
| ); |
| |
| mydialog[methodName](); |
| assert_true( |
| !!openingBeforetoggle, |
| "Opening beforetoggle should fire synchronously.", |
| ); |
| assert_false( |
| !!openingToggle, |
| "Opening toggle should fire.", |
| ); |
| |
| await waitForTick(); |
| assert_false( |
| !!openingToggle, |
| "Opening toggle should still not have fired.", |
| ); |
| |
| assert_false(mydialog.open, 'dialog should not be open'); |
| }, `dialog.${methodName}() should fire cancelable beforetoggle which does not open dialog if canceled`); |
| |
| promise_test(async () => { |
| let openCloseToggleEvent = null; |
| mydialog.addEventListener( |
| "toggle", |
| (event) => { |
| assert_equals( |
| event.oldState, |
| "closed", |
| 'Opening and closing dialog should result in oldState being "closed".', |
| ); |
| assert_equals( |
| event.newState, |
| "closed", |
| 'Opening and closing dialog should result in newState being "closed".', |
| ); |
| assert_false( |
| mydialog.hasAttribute("open"), |
| "Opening and closing dialog should result in open attribute being removed.", |
| ); |
| openCloseToggleEvent = event; |
| }, |
| { once: true }, |
| ); |
| |
| mydialog[methodName](); |
| assert_false( |
| !!openCloseToggleEvent, |
| "Toggle event should not fire synchronously.", |
| ); |
| mydialog.close(); |
| await waitForTick(); |
| assert_true( |
| !!openCloseToggleEvent, |
| "Toggle event should have fired after tick.", |
| ); |
| |
| mydialog[methodName](); |
| await waitForTick(); |
| |
| let closeOpenToggleEvent = null; |
| mydialog.addEventListener( |
| "toggle", |
| (event) => { |
| assert_equals( |
| event.oldState, |
| "open", |
| 'Closing and opening dialog should result in oldState being "open".', |
| ); |
| assert_equals( |
| event.newState, |
| "open", |
| 'Closing and opening dialog should result in newState being "open".', |
| ); |
| assert_true( |
| mydialog.hasAttribute("open"), |
| "Closing and opening dialog should result in open attribute being added.", |
| ); |
| closeOpenToggleEvent = event; |
| }, |
| { once: true }, |
| ); |
| |
| mydialog.close(); |
| assert_false( |
| !!closeOpenToggleEvent, |
| "Toggle event should not fire synchronously.", |
| ); |
| mydialog[methodName](); |
| await waitForTick(); |
| assert_true( |
| !!closeOpenToggleEvent, |
| "Toggle event should have fired after tick.", |
| ); |
| |
| // Clean up for the next test. |
| mydialog.close(); |
| await waitForTick(); |
| }, `dialog.${methodName}() should coalesce asynchronous toggle events.`); |
| |
| promise_test(async (t) => { |
| let attributeChanges = 0; |
| const mo = new MutationObserver((records) => { |
| attributeChanges += records.length; |
| }); |
| mo.observe(mydialog, { attributeFilter: ['open'] }); |
| t.add_cleanup(() => { |
| mo.disconnect(); |
| }); |
| mydialog.addEventListener("beforetoggle", () => { |
| mydialog[methodName](); |
| }, { once: true }); |
| |
| mydialog[methodName](); |
| assert_true(mydialog.open, "Dialog is open"); |
| await waitForTick(); |
| mo.takeRecords(); |
| assert_equals(attributeChanges, 1, "Should have set open once"); |
| |
| attributeChanges = 0; |
| mydialog.addEventListener("beforetoggle", () => { |
| mydialog.close(); |
| }, { once: true }); |
| |
| mydialog.close(); |
| assert_false(mydialog.open, "Dialog is closed"); |
| await waitForTick(); |
| mo.takeRecords(); |
| assert_equals(attributeChanges, 1, "Should have removed open once"); |
| }, `dialog.${methodName}() should not double-set open/close if beforetoggle re-opens`); |
| |
| promise_test(async (t) => { |
| const abortController = new AbortController(); |
| const signal = abortController.signal; |
| const mydialog = document.getElementById("mydialog"); |
| t.add_cleanup(() => { |
| abortController.abort(); |
| mydialog.close(); |
| document.body.prepend(mydialog); |
| }); |
| mydialog.addEventListener("beforetoggle", () => { |
| mydialog.remove(); |
| }, { once: true }); |
| let toggleEventCounter = 0; |
| mydialog.addEventListener( |
| "toggle", |
| (event) => { |
| toggleEventCounter += 1; |
| }, |
| { signal } |
| ); |
| |
| mydialog[methodName](); |
| assert_false(mydialog.isConnected, "Dialog is not connected"); |
| if (methodName == 'show') { |
| assert_true(mydialog.open, "Dialog did open"); |
| } else { |
| assert_false(mydialog.open, "Dialog did not open"); |
| assert_false(mydialog.matches(':modal'), "Dialog is not modal"); |
| } |
| await waitForTick(); |
| if (methodName == 'show') { |
| assert_equals(toggleEventCounter, 1, "toggle event was fired"); |
| } else { |
| assert_equals(toggleEventCounter, 0, "toggle event not fired"); |
| } |
| // Clean up for the next test. |
| document.body.prepend(mydialog); |
| mydialog.close(); |
| await waitForTick(); |
| }, `dialog.${methodName}() should not open if beforetoggle removes`); |
| |
| promise_test(async (t) => { |
| assert_true(document.body.contains(mydialog),'still in the document'); |
| assert_false(mydialog.open,'initially closed'); |
| const abortController = new AbortController(); |
| const signal = abortController.signal; |
| t.add_cleanup(() => { |
| try { mydialog.hidePopover(); } catch {} |
| try { mydialog.close(); } catch {} |
| mydialog.removeAttribute('popover'); |
| abortController.abort(); |
| waitForTick(); // Note that cleanups can't await |
| }); |
| mydialog.setAttribute('popover', ''); |
| mydialog.addEventListener("beforetoggle", () => { |
| mydialog.showPopover(); |
| }, { once: true }); |
| let toggleEventCounter = 0; |
| mydialog.addEventListener( |
| "toggle", |
| (event) => { |
| toggleEventCounter += 1; |
| }, |
| { signal } |
| ); |
| |
| mydialog[methodName](); |
| if (methodName == 'show') { |
| assert_true(mydialog.open, "Dialog did open"); |
| } else { |
| assert_false(mydialog.open, "Dialog did not open"); |
| assert_false(mydialog.matches(':modal'), "Dialog is not modal"); |
| } |
| await waitForTick(); |
| if (methodName == 'show') { |
| assert_equals(toggleEventCounter, 2, "toggle event was fired for show+showPopover"); |
| } else { |
| assert_equals(toggleEventCounter, 1, "toggle event was fired for showPopover"); |
| } |
| // Clean up for the next test. |
| mydialog.close(); |
| await waitForTick(); |
| }, `dialog.${methodName}() should not open if beforetoggle calls showPopover`); |
| }); |
| </script> |