| <!DOCTYPE html> |
| <meta name="timeout" content="long"> |
| <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/utils.js"></script> |
| <script src="/common/dispatcher/dispatcher.js"></script> |
| <script src="/common/get-host-info.sub.js"></script> |
| <script src="resources/utils.js"></script> |
| <title>Test that fenced frame notifyEvent() cannot reuse a cached event</title> |
| |
| <body> |
| <script> |
| promise_test(async (t) => { |
| const fencedframe = await attachFencedFrameContext( |
| {generator_api: 'fledge'}); |
| |
| let notified_promise = new Promise((resolve) => { |
| fencedframe.element.addEventListener('fencedtreeclick', () => resolve()); |
| }); |
| |
| await fencedframe.execute(() => { |
| window.first_click_listener = (e) => { |
| // Before calling notifyEvent, cache the event for later. After this |
| // first notifyEvent call fires, we'll attempt to re-use the cached |
| // event to scam additional notifyEvent calls later. |
| window.cached_event = e; |
| window.fence.notifyEvent(e); |
| }; |
| document.addEventListener('click', window.first_click_listener); |
| }); |
| |
| await multiClick(10, 10, fencedframe.element); |
| await notified_promise; |
| |
| // That notifyEvent call should have consumed user activation. |
| let frame_has_activation = await fencedframe.execute(() => { |
| return navigator.userActivation.isActive; |
| }); |
| assert_false(frame_has_activation); |
| |
| // Now, let's do another activation, and try to call notifyEvent on |
| // the cached event. |
| // If we click again, the frame will receive another activation. If we |
| // try to call notifyEvent with the cached event instead, the call should |
| // fail, because even though the trusted click event still exists and the |
| // frame has activation, the original event has finished dispatching. |
| let second_notified_promise = new Promise((resolve) => { |
| fencedframe.element.addEventListener('fencedtreeclick', () => resolve()); |
| }); |
| await fencedframe.execute(() => { |
| // Unfortunately, a failed assertion in an event handler won't fail the |
| // whole test. So we have to wrap the handler in a Promise that can |
| // be awaited and examined from the test code. |
| document.removeEventListener('click', window.first_click_listener); |
| window.activation_promise = new Promise((resolve, reject) => { |
| document.addEventListener('click', (e) => { |
| try { |
| assert_equals(window.cached_event.type, 'click'); |
| assert_true(window.cached_event.isTrusted); |
| assert_true(navigator.userActivation.isActive); |
| // 0 = NONE, no longer dispatching. |
| assert_equals(window.cached_event.eventPhase, 0); |
| window.fence.notifyEvent(window.cached_event); |
| reject('notifyEvent() should not fire.'); |
| } catch (err) { |
| if (err.name != 'SecurityError') { |
| reject('Unexpected error: ' + err.message); |
| return; |
| } |
| resolve('PASS'); |
| } |
| }); |
| }); |
| }); |
| |
| await multiClick(10, 10, fencedframe.element); |
| |
| // After sending the mousedown events to reactivate the frame, we have to |
| // wait for the fenced frame to indicate that the notifyEvent call fails. |
| // If we get an unexpected result, we'll unwrap the promise into an |
| // exception, which should fail the test. |
| await fencedframe.execute(async () => { |
| await window.activation_promise; |
| }); |
| |
| // Lastly, we need to make sure the notifyEvent call never reached the |
| // parent frame. |
| let result = await Promise.race([ |
| second_notified_promise, |
| new Promise((resolve) => { |
| t.step_timeout(() => resolve('timeout'), 2000); |
| }) |
| ]); |
| assert_equals(result, 'timeout'); |
| |
| }, 'Test that fenced frame notifyEvent() cannot reuse a cached event' + |
| ' after dispatch finishes.'); |
| |
| |
| promise_test(async (t) => { |
| const fencedframe = await attachFencedFrameContext( |
| {generator_api: 'fledge'}); |
| |
| await fencedframe.execute(() => { |
| window.first_click_listener = (e) => { |
| window.cached_event = e; |
| }; |
| document.addEventListener('click', window.first_click_listener); |
| }); |
| |
| await multiClick(10, 10, fencedframe.element); |
| |
| let notified_promise = new Promise((resolve) => { |
| fencedframe.element.addEventListener('fencedtreeclick', () => resolve()); |
| }); |
| |
| await fencedframe.execute(() => { |
| document.removeEventListener('click', window.first_click_listener); |
| window.activation_promise = new Promise((resolve, reject) => { |
| document.addEventListener('click', (e) => { |
| try { |
| assert_equals(window.cached_event.type, 'click'); |
| assert_true(window.cached_event.isTrusted); |
| assert_true(navigator.userActivation.isActive); |
| // 0 = NONE, no longer dispatching. |
| assert_equals(window.cached_event.eventPhase, 0); |
| window.fence.notifyEvent(window.cached_event); |
| reject('notifyEvent() should not fire.'); |
| } catch (err) { |
| if (err.name != 'SecurityError') { |
| reject('Unexpected error: ' + err.message); |
| return; |
| } |
| resolve('PASS'); |
| } |
| }); |
| }); |
| }); |
| |
| await multiClick(10, 10, fencedframe.element); |
| |
| await fencedframe.execute(async () => { |
| await window.activation_promise; |
| }); |
| |
| // Lastly, we need to make sure the notifyEvent call never reached the |
| // parent frame. |
| let result = await Promise.race([ |
| notified_promise, |
| new Promise((resolve) => { |
| t.step_timeout(() => resolve('timeout'), 2000); |
| }) |
| ]); |
| assert_equals(result, 'timeout'); |
| |
| }, 'Test that fenced frame notifyEvent() cannot reuse a cached event' + |
| ' after dispatch finishes, even if it has never been notified before.'); |
| |
| promise_test(async (t) => { |
| const fencedframe = await attachFencedFrameContext( |
| {generator_api: 'fledge'}); |
| |
| // First, click and cache a click event. |
| await fencedframe.execute(() => { |
| window.first_click_listener = (e) => { |
| window.cached_event = e; |
| }; |
| document.addEventListener('click', window.first_click_listener); |
| }); |
| |
| await multiClick(10, 10, fencedframe.element); |
| |
| // Next, register a new click listener to catch the cached event when it's |
| // re-dispatched. |
| await fencedframe.execute(async () => { |
| document.removeEventListener('click', window.first_click_listener); |
| window.click_promise = new Promise((resolve, reject) => { |
| document.addEventListener('click', (e) => { |
| try { |
| assert_true(navigator.userActivation.isActive); |
| window.fence.notifyEvent(e); |
| reject('notifyEvent() should not fire.') |
| } catch (err) { |
| if (err.name != 'SecurityError') { |
| reject('Unexpected error: ' + err.message); |
| } |
| |
| resolve('PASS'); |
| } |
| }); |
| }); |
| |
| // We need user activation when re-dispatching the event, which will |
| // ensure that the re-dispatch is the sole reason for notifyEvent() |
| // failing to fire. We'll simulate a mousedown to provide transient |
| // activation, and *then* re-dispatch the cached click event, in an |
| // attempt to scam an additional notifyEvent(). |
| document.addEventListener('mousedown', (e) => { |
| document.dispatchEvent(window.cached_event); |
| }); |
| }); |
| |
| // Send a mousedown event to the fenced frame. We can't send a full |
| // click because it will interfere with the manual event dispatch. |
| for (let i = 0; i < 3; i++) { |
| await new test_driver.Actions() |
| .pointerMove(10, 10, {origin: fencedframe.element}) |
| .pointerDown() |
| .send(); |
| } |
| |
| await fencedframe.execute(async () => { |
| await window.click_promise; |
| }); |
| |
| }, 'Test that re-dispatching a cached click event does not allow it to be' + |
| ' used with notifyEvent()'); |
| </script> |
| </body> |