tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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>