tor-browser

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

dialog-requestclose.html (8234B)


      1 <!doctype html>
      2 <meta charset="utf-8">
      3 <link rel=help href="https://html.spec.whatwg.org/multipage/interactive-elements.html#dom-dialog-request-close">
      4 <script src="/resources/testharness.js"></script>
      5 <script src="/resources/testharnessreport.js"></script>
      6 <script src="/resources/testdriver.js"></script>
      7 <script src="/resources/testdriver-actions.js"></script>
      8 <script src="/resources/testdriver-vendor.js"></script>
      9 
     10 <dialog>Dialog</dialog>
     11 
     12 <script>
     13 function waitForRender() {
     14  return new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
     15 }
     16 
     17 const dialog = document.querySelector('dialog');
     18 function openDialog(openMethod) {
     19  assert_false(dialog.open);
     20  assert_false(dialog.matches('[open]'));
     21  switch (openMethod) {
     22    case 'modeless':
     23      dialog.show();
     24      break;
     25    case 'modal':
     26      dialog.showModal();
     27      break;
     28    case 'open':
     29      dialog.open = true;
     30      break;
     31    default:
     32      assert_unreached('Unknown open method');
     33  }
     34  assert_true(dialog.open);
     35  assert_true(dialog.matches('[open]'));
     36  assert_equals(dialog.matches(':modal'),openMethod === 'modal');
     37 }
     38 
     39 function setup(t,closedby) {
     40  t.add_cleanup(() => {
     41    dialog.close();
     42    dialog.removeAttribute('closedby');
     43    dialog.returnValue = '';
     44  });
     45  assert_false(dialog.hasAttribute('closedby'));
     46  if (closedby) {
     47    dialog.setAttribute('closedby',closedby);
     48  }
     49  return t.get_signal();
     50 }
     51 
     52 ['modeless','modal','open'].forEach(openMethod => {
     53  [null,'any','closedrequest','none'].forEach(closedby => {
     54    const testDescription = `for ${openMethod} dialog with closedby=${closedby}`;
     55    promise_test(async (t) => {
     56      setup(t,closedby);
     57      openDialog(openMethod);
     58      dialog.requestClose();
     59      assert_false(dialog.open);
     60      assert_false(dialog.matches('[open]'));
     61    },`requestClose basic behavior ${testDescription}`);
     62 
     63    promise_test(async (t) => {
     64      const signal = setup(t,closedby);
     65      let events = [];
     66      const { promise: untilFullyClosed, resolve: hasClosed } = Promise.withResolvers();
     67      dialog.addEventListener('cancel',() => events.push('cancel'),{signal});
     68      dialog.addEventListener('close',() => {
     69        events.push('close');
     70        hasClosed();
     71      },{signal});
     72      openDialog(openMethod);
     73      assert_array_equals(events,[]);
     74      dialog.requestClose();
     75      assert_false(dialog.open);
     76      assert_false(dialog.matches('[open]'));
     77      assert_array_equals(events,['cancel'],'close is scheduled');
     78      await untilFullyClosed;
     79      assert_array_equals(events,['cancel','close']);
     80    },`requestClose fires both cancel and close ${testDescription}`);
     81 
     82    promise_test(async (t) => {
     83      const signal = setup(t,'none');
     84      let events = [];
     85      const { promise: untilFullyClosed, resolve: hasClosed } = Promise.withResolvers();
     86      const { promise: untilFullyClosedAgain, resolve: hasClosedAgain } = Promise.withResolvers();
     87      const closeResolvers = [hasClosed, hasClosedAgain];
     88      dialog.addEventListener('cancel',() => events.push('cancel'),{signal});
     89      dialog.addEventListener('close',() => {
     90        events.push('close');
     91        closeResolvers.shift()();
     92      },{signal});
     93      openDialog(openMethod);
     94      dialog.setAttribute('closedby',closedby);
     95      assert_array_equals(events,[]);
     96      dialog.requestClose();
     97      assert_false(dialog.open,'Adding closedby after dialog is open');
     98      assert_false(dialog.matches('[open]'));
     99      assert_array_equals(events,['cancel']);
    100      await untilFullyClosed;
    101      events=[];
    102      openDialog(openMethod);
    103      dialog.removeAttribute('closedby');
    104      assert_array_equals(events,[]);
    105      dialog.requestClose();
    106      assert_false(dialog.open,'Removing closedby after dialog is open');
    107      assert_array_equals(events,['cancel']);
    108      await untilFullyClosedAgain;
    109      assert_array_equals(events,['cancel', 'close']);
    110    },`closedby has no effect on dialog.requestClose() ${testDescription}`);
    111 
    112    if (closedby != "none") {
    113      promise_test(async (t) => {
    114        const signal = setup(t,closedby);
    115        let shouldPreventDefault = true;
    116        dialog.addEventListener('cancel',(e) => {
    117          if (shouldPreventDefault) {
    118            e.preventDefault();
    119          }
    120        },{signal});
    121        openDialog(openMethod);
    122        dialog.requestClose();
    123        assert_true(dialog.open,'cancel event was cancelled - dialog shouldn\'t close');
    124        assert_true(dialog.matches('[open]'));
    125        shouldPreventDefault = false;
    126        dialog.requestClose();
    127        assert_false(dialog.open,'cancel event was not cancelled - dialog should now close');
    128        assert_false(dialog.matches('[open]'));
    129      },`requestClose can be cancelled ${testDescription}`);
    130 
    131      promise_test(async (t) => {
    132        const signal = setup(t,closedby);
    133        dialog.addEventListener('cancel',(e) => e.preventDefault(),{signal});
    134        openDialog(openMethod);
    135        // No user activation here.
    136        dialog.requestClose();
    137        dialog.requestClose();
    138        dialog.requestClose();
    139        assert_true(dialog.open,'cancel event was cancelled - dialog shouldn\'t close');
    140        assert_true(dialog.matches('[open]'));
    141      },`requestClose avoids abuse prevention logic ${testDescription}`);
    142 
    143      promise_test(async (t) => {
    144        setup(t,closedby);
    145        openDialog(openMethod);
    146        assert_equals(dialog.returnValue,'','Return value starts out empty');
    147        const returnValue = 'The return value';
    148        dialog.requestClose(returnValue);
    149        assert_false(dialog.open);
    150        assert_false(dialog.matches('[open]'));
    151        assert_equals(dialog.returnValue,returnValue,'Return value should be set');
    152        dialog.show();
    153        dialog.close();
    154        assert_equals(dialog.returnValue,returnValue,'Return value should not be changed by close()');
    155        dialog.show();
    156        dialog.close('another');
    157        assert_equals(dialog.returnValue,'another','Return value changes via close(value)');
    158      },`requestClose(returnValue) passes along the return value ${testDescription}`);
    159 
    160      promise_test(async (t) => {
    161        setup(t,closedby);
    162        dialog.addEventListener('cancel',(e) => e.preventDefault(),{once:true});
    163        openDialog(openMethod);
    164        dialog.returnValue = 'foo';
    165        assert_equals(dialog.returnValue,'foo');
    166        dialog.requestClose('This should not get saved');
    167        assert_true(dialog.open,'cancelled');
    168        assert_true(dialog.matches('[open]'));
    169        assert_equals(dialog.returnValue,'foo','Return value should not be changed');
    170      },`requestClose(returnValue) doesn't change returnvalue when cancelled ${testDescription}`);
    171    }
    172  });
    173 });
    174 
    175 promise_test(async (t) => {
    176  setup(t);
    177  dialog.open = true;
    178  dialog.requestClose();
    179  assert_false(dialog.open);
    180 },`requestClose basic behavior when dialog is open via attribute`);
    181 
    182 promise_test(async (t) => {
    183  const signal = setup(t);
    184  let events = [];
    185  const { promise: untilFullyClosed, resolve: hasClosed } = Promise.withResolvers();
    186  dialog.addEventListener('cancel',() => events.push('cancel'),{signal});
    187  dialog.addEventListener('close',() => {
    188    events.push('close')
    189    hasClosed();
    190  },{signal});
    191  dialog.open = true;
    192  assert_array_equals(events,[]);
    193  dialog.requestClose();
    194  assert_false(dialog.open);
    195  assert_array_equals(events,['cancel'],'close is scheduled');
    196  await untilFullyClosed;
    197  assert_array_equals(events,['cancel','close']);
    198 },`requestClose fires cancel and close when dialog is open via attribute`);
    199 
    200 promise_test(async (t) => {
    201  await setup(t);
    202  dialog.open = true;
    203  assert_equals(dialog.returnValue,'','Return value starts out empty');
    204  const returnValue = 'The return value';
    205  dialog.requestClose(returnValue);
    206  assert_false(dialog.open);
    207  assert_equals(dialog.returnValue,returnValue,'Return value should be set');
    208  dialog.show();
    209  dialog.close();
    210  assert_equals(dialog.returnValue,returnValue,'Return value should not be changed by close()');
    211  dialog.show();
    212  dialog.close('another');
    213  assert_equals(dialog.returnValue,'another','Return value changes via close(value)');
    214 },`requestClose(returnValue) passes along the return value when dialog is open via attribute`);
    215 </script>