<!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.
let actions = new test_driver.Actions();
for (let i = 0; i < 3; i++) {
await 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>