chromium/third_party/blink/web_tests/external/wpt/fenced-frame/notify-event-prevent-caching.https.html

<!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>