tor-browser

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

popover-attribute-basic.html (17067B)


      1 <!DOCTYPE html>
      2 <meta charset="utf-8">
      3 <link rel="author" href="mailto:masonf@chromium.org">
      4 <link rel=help href="https://html.spec.whatwg.org/multipage/popover.html">
      5 <link rel=help href="https://open-ui.org/components/popover.research.explainer">
      6 <meta name="timeout" content="long">
      7 <script src="/resources/testharness.js"></script>
      8 <script src="/resources/testharnessreport.js"></script>
      9 <script src="/resources/testdriver.js"></script>
     10 <script src="/resources/testdriver-actions.js"></script>
     11 <script src="/resources/testdriver-vendor.js"></script>
     12 <script src="resources/popover-utils.js"></script>
     13 
     14 <div id=popovers>
     15  <div popover id=boolean>Popover</div>
     16  <div popover="">Popover</div>
     17  <div popover=auto>Popover</div>
     18  <div popover=hint>Popover</div>
     19  <div popover=manual>Popover</div>
     20  <article popover>Different element type</article>
     21  <header popover>Different element type</header>
     22  <nav popover>Different element type</nav>
     23  <input type=text popover value="Different element type">
     24  <dialog popover>Dialog with popover attribute</dialog>
     25  <dialog popover="manual">Dialog with popover=manual</dialog>
     26  <div popover=true>Invalid popover value - defaults to popover=manual</div>
     27  <div popover=popover>Invalid popover value - defaults to popover=manual</div>
     28  <div popover=invalid>Invalid popover value - defaults to popover=manual</div>
     29 </div>
     30 
     31 <div id=nonpopovers>
     32  <div>Not a popover</div>
     33  <dialog open>Dialog without popover attribute</dialog>
     34 </div>
     35 
     36 <div id=outside></div>
     37 <style>
     38 [popover] {
     39  inset:auto;
     40  top:0;
     41  left:0;
     42 }
     43 #outside {
     44  position:fixed;
     45  top:200px;
     46  left:200px;
     47  height:10px;
     48  width:10px;
     49 }
     50 </style>
     51 
     52 <script>
     53 setup({ explicit_done: true });
     54 window.onload = () => {
     55  const outsideElement = document.getElementById('outside');
     56 
     57  // Start with the provided examples:
     58  Array.from(document.getElementById('popovers').children).forEach(popover => {
     59    test((t) => {
     60      assertIsFunctionalPopover(popover, true);
     61    }, `The element ${popover.outerHTML} should behave as a popover.`);
     62  });
     63  Array.from(document.getElementById('nonpopovers').children).forEach(nonPopover => {
     64    test((t) => {
     65      assertNotAPopover(nonPopover);
     66    }, `The element ${nonPopover.outerHTML} should *not* behave as a popover.`);
     67  });
     68 
     69  function createPopover(t) {
     70    const popover = document.createElement('div');
     71    document.body.appendChild(popover);
     72    t.add_cleanup(() => popover.remove());
     73    popover.setAttribute('popover','auto');
     74    return popover;
     75  }
     76 
     77  test((t) => {
     78    // You can set the `popover` attribute to anything.
     79    // Setting the `popover` IDL to a string sets the content attribute to exactly that, always.
     80    // Getting the `popover` IDL value only retrieves valid values.
     81    const popover = createPopover(t);
     82    assert_equals(popover.popover,'auto');
     83    popover.setAttribute('popover','auto');
     84    assert_equals(popover.popover,'auto');
     85    popover.setAttribute('popover','AuTo');
     86    assert_equals(popover.popover,'auto','Case is normalized in IDL');
     87    assert_equals(popover.getAttribute('popover'),'AuTo','Case is *not* normalized/changed in the content attribute');
     88    popover.popover='aUtO';
     89    assert_equals(popover.popover,'auto','Case is normalized in IDL');
     90    assert_equals(popover.getAttribute('popover'),'aUtO','Value set from IDL is propagated exactly to the content attribute');
     91    popover.setAttribute('popover','invalid');
     92    assert_equals(popover.popover,'manual','Invalid values should reflect as "manual"');
     93    popover.removeAttribute('popover');
     94    assert_equals(popover.popover,null,'No value should reflect as null');
     95    popover.popover='hint';
     96    assert_equals(popover.getAttribute('popover'),'hint');
     97    popover.popover='auto';
     98    assert_equals(popover.getAttribute('popover'),'auto');
     99    popover.popover='';
    100    assert_equals(popover.getAttribute('popover'),'');
    101    assert_equals(popover.popover,'auto');
    102    popover.popover='AuTo';
    103    assert_equals(popover.getAttribute('popover'),'AuTo');
    104    assert_equals(popover.popover,'auto');
    105    popover.popover='invalid';
    106    assert_equals(popover.getAttribute('popover'),'invalid','IDL setter allows any value');
    107    assert_equals(popover.popover,'manual','but IDL getter reflects "manual"');
    108    popover.popover='';
    109    assert_equals(popover.getAttribute('popover'),'','IDL setter propagates exactly');
    110    assert_equals(popover.popover,'auto','Empty should map to auto in IDL');
    111    popover.popover='auto';
    112    popover.popover=null;
    113    assert_equals(popover.getAttribute('popover'),null,'Setting null for the IDL property should remove the content attribute');
    114    assert_equals(popover.popover,null,'Null returns null');
    115    popover.popover='auto';
    116    popover.popover=undefined;
    117    assert_equals(popover.getAttribute('popover'),null,'Setting undefined for the IDL property should remove the content attribute');
    118    assert_equals(popover.popover,null,'undefined returns null');
    119  },'IDL attribute reflection');
    120 
    121  test((t) => {
    122    const popover = createPopover(t);
    123    assertIsFunctionalPopover(popover, true);
    124    popover.removeAttribute('popover');
    125    assertNotAPopover(popover);
    126    popover.setAttribute('popover','AuTo');
    127    assertIsFunctionalPopover(popover, true);
    128    popover.removeAttribute('popover');
    129    popover.setAttribute('PoPoVeR','AuTo');
    130    assertIsFunctionalPopover(popover, true);
    131    // Via IDL also
    132    popover.popover = 'auto';
    133    assertIsFunctionalPopover(popover, true);
    134    popover.popover = 'aUtO';
    135    assertIsFunctionalPopover(popover, true);
    136    popover.popover = 'invalid'; // treated as "manual"
    137    assertIsFunctionalPopover(popover, true);
    138  },'Popover attribute value should be case insensitive');
    139 
    140  test((t) => {
    141    const popover = createPopover(t);
    142    assertIsFunctionalPopover(popover, true);
    143    popover.setAttribute('popover','manual'); // Change popover type
    144    assertIsFunctionalPopover(popover, true);
    145    popover.setAttribute('popover','invalid'); // Change popover type to something invalid
    146    assertIsFunctionalPopover(popover, true);
    147    popover.popover = 'manual'; // Change popover type via IDL
    148    assertIsFunctionalPopover(popover, true);
    149    popover.popover = 'invalid'; // Make invalid via IDL (treated as "manual")
    150    assertIsFunctionalPopover(popover, true);
    151  },'Changing attribute values for popover should work');
    152 
    153  test((t) => {
    154    const popover = createPopover(t);
    155    popover.showPopover();
    156    assert_true(popover.matches(':popover-open'));
    157    popover.setAttribute('popover','hint'); // Change popover type
    158    assert_false(popover.matches(':popover-open'));
    159    popover.showPopover();
    160    assert_true(popover.matches(':popover-open'));
    161    popover.setAttribute('popover','manual');
    162    assert_false(popover.matches(':popover-open'));
    163    popover.showPopover();
    164    assert_true(popover.matches(':popover-open'));
    165    popover.setAttribute('popover','invalid');
    166    assert_true(popover.matches(':popover-open'),'From "manual" to "invalid" (which is interpreted as "manual") should not close the popover');
    167    popover.setAttribute('popover','auto');
    168    assert_false(popover.matches(':popover-open'),'From "invalid" ("manual") to "auto" should hide the popover');
    169    popover.showPopover();
    170    assert_true(popover.matches(':popover-open'));
    171    popover.setAttribute('popover','invalid');
    172    assert_false(popover.matches(':popover-open'),'From "auto" to "invalid" (which is interpreted as "manual") should close the popover');
    173  },'Changing attribute values should close open popovers');
    174 
    175  const validTypes = ["auto","hint","manual"];
    176  validTypes.forEach(type => {
    177    test((t) => {
    178      const popover = createPopover(t);
    179      popover.setAttribute('popover',type);
    180      popover.showPopover();
    181      assert_true(popover.matches(':popover-open'));
    182      popover.remove();
    183      assert_false(popover.matches(':popover-open'));
    184      document.body.appendChild(popover);
    185      assert_false(popover.matches(':popover-open'));
    186    },`Removing a visible popover=${type} element from the document should close the popover`);
    187 
    188    test((t) => {
    189      const popover = createPopover(t);
    190      popover.setAttribute('popover',type);
    191      popover.showPopover();
    192      assert_true(popover.matches(':popover-open'));
    193      assert_false(popover.matches(':modal'));
    194      popover.hidePopover();
    195    },`A showing popover=${type} does not match :modal`);
    196 
    197    test((t) => {
    198      const popover = createPopover(t);
    199      popover.setAttribute('popover',type);
    200      assert_false(popover.matches(':popover-open'));
    201      // FIXME: Once :open/:closed are defined in HTML we should remove these two constants.
    202      const openPseudoClassIsSupported = CSS.supports('selector(:open))');
    203      const closePseudoClassIsSupported = CSS.supports('selector(:closed))');
    204      assert_false(openPseudoClassIsSupported && popover.matches(':open'),'popovers never match :open');
    205      assert_false(closePseudoClassIsSupported && popover.matches(':closed'),'popovers never match :closed');
    206      popover.showPopover();
    207      assert_true(popover.matches(':popover-open'));
    208      assert_false(openPseudoClassIsSupported && popover.matches(':open'),'popovers never match :open');
    209      assert_false(closePseudoClassIsSupported && popover.matches(':closed'),'popovers never match :closed');
    210      popover.hidePopover();
    211    },`A popover=${type} never matches :open or :closed`);
    212  });
    213 
    214  test((t) => {
    215    const other_popover = createPopover(t);
    216    other_popover.setAttribute('popover','auto');
    217    other_popover.showPopover();
    218    const popover = createPopover(t);
    219    popover.setAttribute('popover','auto');
    220    other_popover.addEventListener('beforetoggle', (e) => {
    221      if (e.newState !== "closed")
    222        return;
    223      popover.setAttribute('popover','manual');
    224    },{once: true});
    225    assert_true(other_popover.matches(':popover-open'));
    226    assert_false(popover.matches(':popover-open'));
    227    assert_throws_dom('InvalidStateError', () => popover.showPopover());
    228    assert_false(other_popover.matches(':popover-open'),'unrelated popover is hidden');
    229    assert_false(popover.matches(':popover-open'),'popover is not shown if its type changed during show');
    230  },`Changing the popover type in a "beforetoggle" event handler should throw an exception (during showPopover())`);
    231 
    232  test((t) => {
    233    const other_popover = createPopover(t);
    234    other_popover.setAttribute('popover','auto');
    235    other_popover.showPopover();
    236    const popover = createPopover(t);
    237    popover.setAttribute('popover','auto');
    238    other_popover.addEventListener('beforetoggle', (e) => {
    239      if (e.newState !== "closed")
    240        return;
    241      popover.setAttribute('popover','manual');
    242    },{once: true});
    243    assert_true(other_popover.matches(':popover-open'));
    244    assert_false(popover.matches(':popover-open'));
    245 
    246    popover.id = 'type-change-test';
    247    const invoker = document.createElement('button');
    248    document.body.appendChild(invoker);
    249    t.add_cleanup(() => invoker.remove());
    250    invoker.setAttribute('popovertarget', 'type-change-test');
    251    invoker.click();
    252 
    253    assert_false(other_popover.matches(':popover-open'),'unrelated popover is hidden');
    254    assert_false(popover.matches(':popover-open'),'popover is not shown if its type changed during show');
    255  },`Changing the popover type in a "beforetoggle" event handler should not show the popover (during popovertarget invoking)`);
    256 
    257  test((t) => {
    258    const popover = createPopover(t);
    259    popover.setAttribute('popover','auto');
    260    const other_popover = createPopover(t);
    261    other_popover.setAttribute('popover','auto');
    262    popover.appendChild(other_popover);
    263    popover.showPopover();
    264    other_popover.showPopover();
    265    let nested_popover_hidden=false;
    266    other_popover.addEventListener('beforetoggle', (e) => {
    267      if (e.newState !== "closed")
    268        return;
    269      nested_popover_hidden = true;
    270      popover.setAttribute('popover','manual');
    271    },{once: true});
    272    popover.addEventListener('beforetoggle', (e) => {
    273      if (e.newState !== "closed")
    274        return;
    275      assert_true(nested_popover_hidden,'The nested popover should be hidden first');
    276    },{once: true});
    277    assert_true(popover.matches(':popover-open'));
    278    assert_true(other_popover.matches(':popover-open'));
    279    popover.hidePopover(); // Calling hidePopover on a hidden popover should not throw.
    280    assert_false(other_popover.matches(':popover-open'),'unrelated popover is hidden');
    281    assert_false(popover.matches(':popover-open'),'popover is still hidden if its type changed during hide event');
    282    other_popover.hidePopover(); // Calling hidePopover on a hidden popover should not throw.
    283  },`Changing the popover type in a "beforetoggle" event handler during hidePopover() should not throw an exception`);
    284 
    285  test(t => {
    286    const popover = document.createElement('div');
    287    assert_throws_dom('NotSupportedError', () => popover.hidePopover(),
    288      'Calling hidePopover on an element without a popover attribute should throw.');
    289    popover.setAttribute('popover', 'auto');
    290    popover.hidePopover(); // Calling hidePopover on a disconnected popover should not throw.
    291    assert_throws_dom('InvalidStateError', () => popover.showPopover(),
    292      'Calling showPopover on a disconnected popover should throw.');
    293  },'Calling hidePopover on a disconnected popover should not throw.');
    294 
    295  function interpretedType(typeString,method) {
    296    if (validTypes.includes(typeString))
    297      return typeString;
    298    if (typeString === undefined)
    299      return "invalid-value-undefined";
    300    if (method === "idl" && typeString === null)
    301      return "invalid-value-idl-null";
    302    return "manual"; // Invalid types default to "manual"
    303  }
    304  function setPopoverValue(popover,type,method) {
    305    switch (method) {
    306      case "attr":
    307        if (type === undefined) {
    308          popover.removeAttribute('popover');
    309        } else {
    310          popover.setAttribute('popover',type);
    311        }
    312        break;
    313      case "idl":
    314        popover.popover = type;
    315        break;
    316      default:
    317        assert_unreached();
    318    }
    319  }
    320  ["attr","idl"].forEach(method => {
    321    validTypes.forEach(type => {
    322      [...validTypes,"invalid",null,undefined].forEach(newType => {
    323        [...validTypes,"invalid",null,undefined].forEach(inEventType => {
    324          promise_test(async (t) => {
    325            const popover = createPopover(t);
    326            setPopoverValue(popover,type,method);
    327            popover.showPopover();
    328            assert_true(popover.matches(':popover-open'));
    329            let gotEvent = false;
    330            popover.addEventListener('beforetoggle', (e) => {
    331              if (e.newState !== "closed")
    332                return;
    333              gotEvent = true;
    334              setPopoverValue(popover,inEventType,method);
    335            },{once:true});
    336            setPopoverValue(popover,newType,method);
    337            if (type===interpretedType(newType,method)) {
    338              // Keeping the type the same should not hide it or fire events.
    339              assert_true(popover.matches(':popover-open'),'popover should remain open when not changing the type');
    340              assert_false(gotEvent);
    341              try {
    342                popover.hidePopover(); // Cleanup
    343              } catch (e) {}
    344            } else {
    345              // Changing the type at all should hide the popover. The hide event
    346              // handler should run, set a new type, and that type should end up
    347              // as the final result.
    348              assert_false(popover.matches(':popover-open'));
    349              assert_true(gotEvent);
    350              if (inEventType === undefined || (method ==="idl" && inEventType === null)) {
    351                assert_throws_dom("NotSupportedError",() => popover.showPopover(),'We should have removed the popover attribute, so showPopover should throw');
    352              } else {
    353                // Make sure the attribute is correct.
    354                assert_equals(popover.getAttribute('popover'),String(inEventType),'Content attribute');
    355                assert_equals(popover.popover, interpretedType(inEventType,method),'IDL attribute');
    356                // Make sure the type is really correct, via behavior.
    357                popover.showPopover(); // Show it
    358                assert_true(popover.matches(':popover-open'),'Popover should function');
    359                await clickOn(outsideElement); // Try to light dismiss
    360                switch (interpretedType(inEventType,method)) {
    361                  case 'manual':
    362                    assert_true(popover.matches(':popover-open'),'A popover=manual should not light-dismiss');
    363                    popover.hidePopover();
    364                    break;
    365                  case 'auto':
    366                  case 'hint':
    367                    assert_false(popover.matches(':popover-open'),'A popover=auto should light-dismiss');
    368                    break;
    369                }
    370              }
    371            }
    372          },`Changing a popover from ${type} to ${newType} (via ${method}), and then ${inEventType} during 'beforetoggle' works`);
    373        });
    374      });
    375    });
    376  });
    377 
    378  done();
    379 };
    380 </script>