dialog-requestclose.html (8234B)
1 <!doctype html> 2 <meta charset="utf-8"> 3 <link rel=help href="https://html.spec.whatwg.org/multipage/interactive-elements.html#dom-dialog-request-close"> 4 <script src="/resources/testharness.js"></script> 5 <script src="/resources/testharnessreport.js"></script> 6 <script src="/resources/testdriver.js"></script> 7 <script src="/resources/testdriver-actions.js"></script> 8 <script src="/resources/testdriver-vendor.js"></script> 9 10 <dialog>Dialog</dialog> 11 12 <script> 13 function waitForRender() { 14 return new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve))); 15 } 16 17 const dialog = document.querySelector('dialog'); 18 function openDialog(openMethod) { 19 assert_false(dialog.open); 20 assert_false(dialog.matches('[open]')); 21 switch (openMethod) { 22 case 'modeless': 23 dialog.show(); 24 break; 25 case 'modal': 26 dialog.showModal(); 27 break; 28 case 'open': 29 dialog.open = true; 30 break; 31 default: 32 assert_unreached('Unknown open method'); 33 } 34 assert_true(dialog.open); 35 assert_true(dialog.matches('[open]')); 36 assert_equals(dialog.matches(':modal'),openMethod === 'modal'); 37 } 38 39 function setup(t,closedby) { 40 t.add_cleanup(() => { 41 dialog.close(); 42 dialog.removeAttribute('closedby'); 43 dialog.returnValue = ''; 44 }); 45 assert_false(dialog.hasAttribute('closedby')); 46 if (closedby) { 47 dialog.setAttribute('closedby',closedby); 48 } 49 return t.get_signal(); 50 } 51 52 ['modeless','modal','open'].forEach(openMethod => { 53 [null,'any','closedrequest','none'].forEach(closedby => { 54 const testDescription = `for ${openMethod} dialog with closedby=${closedby}`; 55 promise_test(async (t) => { 56 setup(t,closedby); 57 openDialog(openMethod); 58 dialog.requestClose(); 59 assert_false(dialog.open); 60 assert_false(dialog.matches('[open]')); 61 },`requestClose basic behavior ${testDescription}`); 62 63 promise_test(async (t) => { 64 const signal = setup(t,closedby); 65 let events = []; 66 const { promise: untilFullyClosed, resolve: hasClosed } = Promise.withResolvers(); 67 dialog.addEventListener('cancel',() => events.push('cancel'),{signal}); 68 dialog.addEventListener('close',() => { 69 events.push('close'); 70 hasClosed(); 71 },{signal}); 72 openDialog(openMethod); 73 assert_array_equals(events,[]); 74 dialog.requestClose(); 75 assert_false(dialog.open); 76 assert_false(dialog.matches('[open]')); 77 assert_array_equals(events,['cancel'],'close is scheduled'); 78 await untilFullyClosed; 79 assert_array_equals(events,['cancel','close']); 80 },`requestClose fires both cancel and close ${testDescription}`); 81 82 promise_test(async (t) => { 83 const signal = setup(t,'none'); 84 let events = []; 85 const { promise: untilFullyClosed, resolve: hasClosed } = Promise.withResolvers(); 86 const { promise: untilFullyClosedAgain, resolve: hasClosedAgain } = Promise.withResolvers(); 87 const closeResolvers = [hasClosed, hasClosedAgain]; 88 dialog.addEventListener('cancel',() => events.push('cancel'),{signal}); 89 dialog.addEventListener('close',() => { 90 events.push('close'); 91 closeResolvers.shift()(); 92 },{signal}); 93 openDialog(openMethod); 94 dialog.setAttribute('closedby',closedby); 95 assert_array_equals(events,[]); 96 dialog.requestClose(); 97 assert_false(dialog.open,'Adding closedby after dialog is open'); 98 assert_false(dialog.matches('[open]')); 99 assert_array_equals(events,['cancel']); 100 await untilFullyClosed; 101 events=[]; 102 openDialog(openMethod); 103 dialog.removeAttribute('closedby'); 104 assert_array_equals(events,[]); 105 dialog.requestClose(); 106 assert_false(dialog.open,'Removing closedby after dialog is open'); 107 assert_array_equals(events,['cancel']); 108 await untilFullyClosedAgain; 109 assert_array_equals(events,['cancel', 'close']); 110 },`closedby has no effect on dialog.requestClose() ${testDescription}`); 111 112 if (closedby != "none") { 113 promise_test(async (t) => { 114 const signal = setup(t,closedby); 115 let shouldPreventDefault = true; 116 dialog.addEventListener('cancel',(e) => { 117 if (shouldPreventDefault) { 118 e.preventDefault(); 119 } 120 },{signal}); 121 openDialog(openMethod); 122 dialog.requestClose(); 123 assert_true(dialog.open,'cancel event was cancelled - dialog shouldn\'t close'); 124 assert_true(dialog.matches('[open]')); 125 shouldPreventDefault = false; 126 dialog.requestClose(); 127 assert_false(dialog.open,'cancel event was not cancelled - dialog should now close'); 128 assert_false(dialog.matches('[open]')); 129 },`requestClose can be cancelled ${testDescription}`); 130 131 promise_test(async (t) => { 132 const signal = setup(t,closedby); 133 dialog.addEventListener('cancel',(e) => e.preventDefault(),{signal}); 134 openDialog(openMethod); 135 // No user activation here. 136 dialog.requestClose(); 137 dialog.requestClose(); 138 dialog.requestClose(); 139 assert_true(dialog.open,'cancel event was cancelled - dialog shouldn\'t close'); 140 assert_true(dialog.matches('[open]')); 141 },`requestClose avoids abuse prevention logic ${testDescription}`); 142 143 promise_test(async (t) => { 144 setup(t,closedby); 145 openDialog(openMethod); 146 assert_equals(dialog.returnValue,'','Return value starts out empty'); 147 const returnValue = 'The return value'; 148 dialog.requestClose(returnValue); 149 assert_false(dialog.open); 150 assert_false(dialog.matches('[open]')); 151 assert_equals(dialog.returnValue,returnValue,'Return value should be set'); 152 dialog.show(); 153 dialog.close(); 154 assert_equals(dialog.returnValue,returnValue,'Return value should not be changed by close()'); 155 dialog.show(); 156 dialog.close('another'); 157 assert_equals(dialog.returnValue,'another','Return value changes via close(value)'); 158 },`requestClose(returnValue) passes along the return value ${testDescription}`); 159 160 promise_test(async (t) => { 161 setup(t,closedby); 162 dialog.addEventListener('cancel',(e) => e.preventDefault(),{once:true}); 163 openDialog(openMethod); 164 dialog.returnValue = 'foo'; 165 assert_equals(dialog.returnValue,'foo'); 166 dialog.requestClose('This should not get saved'); 167 assert_true(dialog.open,'cancelled'); 168 assert_true(dialog.matches('[open]')); 169 assert_equals(dialog.returnValue,'foo','Return value should not be changed'); 170 },`requestClose(returnValue) doesn't change returnvalue when cancelled ${testDescription}`); 171 } 172 }); 173 }); 174 175 promise_test(async (t) => { 176 setup(t); 177 dialog.open = true; 178 dialog.requestClose(); 179 assert_false(dialog.open); 180 },`requestClose basic behavior when dialog is open via attribute`); 181 182 promise_test(async (t) => { 183 const signal = setup(t); 184 let events = []; 185 const { promise: untilFullyClosed, resolve: hasClosed } = Promise.withResolvers(); 186 dialog.addEventListener('cancel',() => events.push('cancel'),{signal}); 187 dialog.addEventListener('close',() => { 188 events.push('close') 189 hasClosed(); 190 },{signal}); 191 dialog.open = true; 192 assert_array_equals(events,[]); 193 dialog.requestClose(); 194 assert_false(dialog.open); 195 assert_array_equals(events,['cancel'],'close is scheduled'); 196 await untilFullyClosed; 197 assert_array_equals(events,['cancel','close']); 198 },`requestClose fires cancel and close when dialog is open via attribute`); 199 200 promise_test(async (t) => { 201 await setup(t); 202 dialog.open = true; 203 assert_equals(dialog.returnValue,'','Return value starts out empty'); 204 const returnValue = 'The return value'; 205 dialog.requestClose(returnValue); 206 assert_false(dialog.open); 207 assert_equals(dialog.returnValue,returnValue,'Return value should be set'); 208 dialog.show(); 209 dialog.close(); 210 assert_equals(dialog.returnValue,returnValue,'Return value should not be changed by close()'); 211 dialog.show(); 212 dialog.close('another'); 213 assert_equals(dialog.returnValue,'another','Return value changes via close(value)'); 214 },`requestClose(returnValue) passes along the return value when dialog is open via attribute`); 215 </script>