notify-event-prevent-caching.https.html (8672B)
1 <!DOCTYPE html> 2 <meta name="timeout" content="long"> 3 <script src="/resources/testharness.js"></script> 4 <script src="/resources/testharnessreport.js"></script> 5 <script src="/resources/testdriver.js"></script> 6 <script src="/resources/testdriver-actions.js"></script> 7 <script src="/resources/testdriver-vendor.js"></script> 8 <script src="/common/utils.js"></script> 9 <script src="/common/dispatcher/dispatcher.js"></script> 10 <script src="/common/get-host-info.sub.js"></script> 11 <script src="resources/utils.js"></script> 12 <title>Test that fenced frame notifyEvent() cannot reuse a cached event</title> 13 14 <body> 15 <script> 16 promise_test(async (t) => { 17 const fencedframe = await attachFencedFrameContext( 18 {generator_api: 'fledge'}); 19 20 let notified_promise = new Promise((resolve) => { 21 fencedframe.element.addEventListener('fencedtreeclick', () => resolve()); 22 }); 23 24 await fencedframe.execute(() => { 25 window.first_click_listener = (e) => { 26 // Before calling notifyEvent, cache the event for later. After this 27 // first notifyEvent call fires, we'll attempt to re-use the cached 28 // event to scam additional notifyEvent calls later. 29 window.cached_event = e; 30 window.fence.notifyEvent(e); 31 }; 32 document.addEventListener('click', window.first_click_listener); 33 }); 34 35 await multiClick(10, 10, fencedframe.element); 36 await notified_promise; 37 38 // That notifyEvent call should have consumed user activation. 39 let frame_has_activation = await fencedframe.execute(() => { 40 return navigator.userActivation.isActive; 41 }); 42 assert_false(frame_has_activation); 43 44 // Now, let's do another activation, and try to call notifyEvent on 45 // the cached event. 46 // If we click again, the frame will receive another activation. If we 47 // try to call notifyEvent with the cached event instead, the call should 48 // fail, because even though the trusted click event still exists and the 49 // frame has activation, the original event has finished dispatching. 50 let second_notified_promise = new Promise((resolve) => { 51 fencedframe.element.addEventListener('fencedtreeclick', () => resolve()); 52 }); 53 await fencedframe.execute(() => { 54 // Unfortunately, a failed assertion in an event handler won't fail the 55 // whole test. So we have to wrap the handler in a Promise that can 56 // be awaited and examined from the test code. 57 document.removeEventListener('click', window.first_click_listener); 58 window.activation_promise = new Promise((resolve, reject) => { 59 document.addEventListener('click', (e) => { 60 try { 61 assert_equals(window.cached_event.type, 'click'); 62 assert_true(window.cached_event.isTrusted); 63 assert_true(navigator.userActivation.isActive); 64 // 0 = NONE, no longer dispatching. 65 assert_equals(window.cached_event.eventPhase, 0); 66 window.fence.notifyEvent(window.cached_event); 67 reject('notifyEvent() should not fire.'); 68 } catch (err) { 69 if (err.name != 'SecurityError') { 70 reject('Unexpected error: ' + err.message); 71 return; 72 } 73 resolve('PASS'); 74 } 75 }); 76 }); 77 }); 78 79 await multiClick(10, 10, fencedframe.element); 80 81 // After sending the mousedown events to reactivate the frame, we have to 82 // wait for the fenced frame to indicate that the notifyEvent call fails. 83 // If we get an unexpected result, we'll unwrap the promise into an 84 // exception, which should fail the test. 85 await fencedframe.execute(async () => { 86 await window.activation_promise; 87 }); 88 89 // Lastly, we need to make sure the notifyEvent call never reached the 90 // parent frame. 91 let result = await Promise.race([ 92 second_notified_promise, 93 new Promise((resolve) => { 94 t.step_timeout(() => resolve('timeout'), 2000); 95 }) 96 ]); 97 assert_equals(result, 'timeout'); 98 99 }, 'Test that fenced frame notifyEvent() cannot reuse a cached event' + 100 ' after dispatch finishes.'); 101 102 103 promise_test(async (t) => { 104 const fencedframe = await attachFencedFrameContext( 105 {generator_api: 'fledge'}); 106 107 await fencedframe.execute(() => { 108 window.first_click_listener = (e) => { 109 window.cached_event = e; 110 }; 111 document.addEventListener('click', window.first_click_listener); 112 }); 113 114 await multiClick(10, 10, fencedframe.element); 115 116 let notified_promise = new Promise((resolve) => { 117 fencedframe.element.addEventListener('fencedtreeclick', () => resolve()); 118 }); 119 120 await fencedframe.execute(() => { 121 document.removeEventListener('click', window.first_click_listener); 122 window.activation_promise = new Promise((resolve, reject) => { 123 document.addEventListener('click', (e) => { 124 try { 125 assert_equals(window.cached_event.type, 'click'); 126 assert_true(window.cached_event.isTrusted); 127 assert_true(navigator.userActivation.isActive); 128 // 0 = NONE, no longer dispatching. 129 assert_equals(window.cached_event.eventPhase, 0); 130 window.fence.notifyEvent(window.cached_event); 131 reject('notifyEvent() should not fire.'); 132 } catch (err) { 133 if (err.name != 'SecurityError') { 134 reject('Unexpected error: ' + err.message); 135 return; 136 } 137 resolve('PASS'); 138 } 139 }); 140 }); 141 }); 142 143 await multiClick(10, 10, fencedframe.element); 144 145 await fencedframe.execute(async () => { 146 await window.activation_promise; 147 }); 148 149 // Lastly, we need to make sure the notifyEvent call never reached the 150 // parent frame. 151 let result = await Promise.race([ 152 notified_promise, 153 new Promise((resolve) => { 154 t.step_timeout(() => resolve('timeout'), 2000); 155 }) 156 ]); 157 assert_equals(result, 'timeout'); 158 159 }, 'Test that fenced frame notifyEvent() cannot reuse a cached event' + 160 ' after dispatch finishes, even if it has never been notified before.'); 161 162 promise_test(async (t) => { 163 const fencedframe = await attachFencedFrameContext( 164 {generator_api: 'fledge'}); 165 166 // First, click and cache a click event. 167 await fencedframe.execute(() => { 168 window.first_click_listener = (e) => { 169 window.cached_event = e; 170 }; 171 document.addEventListener('click', window.first_click_listener); 172 }); 173 174 await multiClick(10, 10, fencedframe.element); 175 176 // Next, register a new click listener to catch the cached event when it's 177 // re-dispatched. 178 await fencedframe.execute(async () => { 179 document.removeEventListener('click', window.first_click_listener); 180 window.click_promise = new Promise((resolve, reject) => { 181 document.addEventListener('click', (e) => { 182 try { 183 assert_true(navigator.userActivation.isActive); 184 window.fence.notifyEvent(e); 185 reject('notifyEvent() should not fire.') 186 } catch (err) { 187 if (err.name != 'SecurityError') { 188 reject('Unexpected error: ' + err.message); 189 } 190 191 resolve('PASS'); 192 } 193 }); 194 }); 195 196 // We need user activation when re-dispatching the event, which will 197 // ensure that the re-dispatch is the sole reason for notifyEvent() 198 // failing to fire. We'll simulate a mousedown to provide transient 199 // activation, and *then* re-dispatch the cached click event, in an 200 // attempt to scam an additional notifyEvent(). 201 document.addEventListener('mousedown', (e) => { 202 document.dispatchEvent(window.cached_event); 203 }); 204 }); 205 206 // Send a mousedown event to the fenced frame. We can't send a full 207 // click because it will interfere with the manual event dispatch. 208 for (let i = 0; i < 3; i++) { 209 await new test_driver.Actions() 210 .pointerMove(10, 10, {origin: fencedframe.element}) 211 .pointerDown() 212 .send(); 213 } 214 215 await fencedframe.execute(async () => { 216 await window.click_promise; 217 }); 218 219 }, 'Test that re-dispatching a cached click event does not allow it to be' + 220 ' used with notifyEvent()'); 221 </script> 222 </body>