tor-browser

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

ElementInternals-validation.html (14241B)


      1 <!DOCTYPE html>
      2 <title>Form validation features of ElementInternals, and :valid :invalid pseudo classes</title>
      3 <body>
      4 <script src="/resources/testharness.js"></script>
      5 <script src="/resources/testharnessreport.js"></script>
      6 <div id="container"></div>
      7 <script>
      8 class MyControl extends HTMLElement {
      9  static get formAssociated() { return true; }
     10 
     11  constructor() {
     12    super();
     13    this.internals_ = this.attachInternals();
     14  }
     15  get i() { return this.internals_; }
     16 }
     17 customElements.define('my-control', MyControl);
     18 
     19 class NotFormAssociatedElement extends HTMLElement {
     20  constructor() {
     21    super();
     22    this.internals_ = this.attachInternals();
     23  }
     24  get i() { return this.internals_; }
     25 }
     26 customElements.define('not-form-associated-element', NotFormAssociatedElement);
     27 
     28 test(() => {
     29  const control = new MyControl();
     30  assert_true(control.i.willValidate, 'default value is true');
     31 
     32  const datalist = document.createElement('datalist');
     33  datalist.appendChild(control);
     34  assert_false(control.i.willValidate, 'false in DATALIST');
     35  datalist.removeChild(control);
     36  assert_true(control.i.willValidate, 'remove from DATALIST');
     37 
     38  const fieldset = document.createElement('fieldset');
     39  fieldset.disabled = true;
     40  fieldset.appendChild(control);
     41  assert_false(control.i.willValidate, 'append to disabled FIELDSET');
     42  fieldset.disabled = false;
     43  assert_true(control.i.willValidate, 'FIELDSET becomes enabled');
     44  fieldset.disabled = true;
     45  assert_false(control.i.willValidate, 'FIELDSET becomes disabled');
     46  fieldset.removeChild(control);
     47  assert_true(control.i.willValidate, 'remove from disabled FIELDSET');
     48 
     49  control.setAttribute('disabled', '');
     50  assert_false(control.i.willValidate, 'with disabled attribute');
     51  control.removeAttribute('disabled');
     52  assert_true(control.i.willValidate, 'without disabled attribute');
     53 
     54  control.setAttribute('readonly', '');
     55  assert_false(control.i.willValidate, 'with readonly attribute');
     56  control.removeAttribute('readonly');
     57  assert_true(control.i.willValidate, 'without readonly attribute');
     58 }, 'willValidate');
     59 
     60 test(() => {
     61  const container = document.getElementById("container");
     62  container.innerHTML = '<will-be-defined></will-be-defined>' +
     63                        '<will-be-defined disabled></will-be-defined>' +
     64                        '<will-be-defined readonly></will-be-defined>' +
     65                        '<datalist><will-be-defined></will-be-defined></datalist>' +
     66                        '<fieldset disabled><will-be-defined></will-be-defined></fieldset>';
     67 
     68  class WillBeDefined extends HTMLElement {
     69    static get formAssociated() { return true; }
     70 
     71    constructor() {
     72      super();
     73      this.internals_ = this.attachInternals();
     74    }
     75    get i() { return this.internals_; }
     76  }
     77  customElements.define('will-be-defined', WillBeDefined);
     78  customElements.upgrade(container);
     79 
     80  const controls = document.querySelectorAll('will-be-defined');
     81  assert_true(controls[0].i.willValidate, 'default value');
     82  assert_false(controls[1].i.willValidate, 'with disabled attribute');
     83  assert_false(controls[2].i.willValidate, 'with readOnly attribute');
     84  assert_false(controls[3].i.willValidate, 'in datalist');
     85  assert_false(controls[4].i.willValidate, 'in disabled fieldset');
     86 }, 'willValidate after upgrade');
     87 
     88 test(t => {
     89  const control = document.createElement('will-be-defined-2');
     90 
     91  customElements.define('will-be-defined-2', class extends HTMLElement {
     92    static get formAssociated() { return true; }
     93  });
     94 
     95  container.append(control);
     96  t.add_cleanup(() => { container.innerHTML = ''; });
     97 
     98  const i = control.attachInternals();
     99  assert_true(i.willValidate);
    100 }, 'willValidate after upgrade (document.createElement)');
    101 
    102 test(() => {
    103  const element = new NotFormAssociatedElement();
    104  assert_throws_dom('NotSupportedError', () => element.i.willValidate);
    105 }, "willValidate should throw NotSupportedError if the target element is not a form-associated custom element");
    106 
    107 test(() => {
    108  const element = new NotFormAssociatedElement();
    109  assert_throws_dom('NotSupportedError', () => element.i.validity);
    110 }, "validity should throw NotSupportedError if the target element is not a form-associated custom element");
    111 
    112 test(() => {
    113  const element = new NotFormAssociatedElement();
    114  assert_throws_dom('NotSupportedError', () => element.i.validationMessage);
    115 }, "validationMessage should throw NotSupportedError if the target element is not a form-associated custom element");
    116 
    117 test(() => {
    118  const element = new NotFormAssociatedElement();
    119  assert_throws_dom('NotSupportedError', () => element.i.setValidity());
    120 }, "setValidity() should throw NotSupportedError if the target element is not a form-associated custom element");
    121 
    122 test(() => {
    123  const control = document.createElement('my-control');
    124  const validity = control.i.validity;
    125  assert_false(validity.valueMissing, 'default valueMissing');
    126  assert_false(validity.typeMismatch, 'default typeMismatch');
    127  assert_false(validity.patternMismatch, 'default patternMismatch');
    128  assert_false(validity.tooLong, 'default tooLong');
    129  assert_false(validity.tooShort, 'default tooShort');
    130  assert_false(validity.rangeUnderflow, 'default rangeUnderflow');
    131  assert_false(validity.rangeOverflow, 'default rangeOverflow');
    132  assert_false(validity.stepMismatch, 'default stepMismatch');
    133  assert_false(validity.badInput, 'default badInput');
    134  assert_false(validity.customError, 'default customError');
    135  assert_true(validity.valid, 'default valid');
    136 
    137  control.i.setValidity({valueMissing: true}, 'valueMissing message');
    138  assert_true(validity.valueMissing);
    139  assert_false(validity.valid);
    140  assert_equals(control.i.validationMessage, 'valueMissing message');
    141 
    142  control.i.setValidity({typeMismatch: true}, 'typeMismatch message');
    143  assert_true(validity.typeMismatch);
    144  assert_false(validity.valueMissing);
    145  assert_false(validity.valid);
    146  assert_equals(control.i.validationMessage, 'typeMismatch message');
    147 
    148  control.i.setValidity({patternMismatch: true}, 'patternMismatch message');
    149  assert_true(validity.patternMismatch);
    150  assert_false(validity.valid);
    151  assert_equals(control.i.validationMessage, 'patternMismatch message');
    152 
    153  control.i.setValidity({tooLong: true}, 'tooLong message');
    154  assert_true(validity.tooLong);
    155  assert_false(validity.valid);
    156  assert_equals(control.i.validationMessage, 'tooLong message');
    157 
    158  control.i.setValidity({tooShort: true}, 'tooShort message');
    159  assert_true(validity.tooShort);
    160  assert_false(validity.valid);
    161  assert_equals(control.i.validationMessage, 'tooShort message');
    162 
    163  control.i.setValidity({rangeUnderflow: true}, 'rangeUnderflow message');
    164  assert_true(validity.rangeUnderflow);
    165  assert_false(validity.valid);
    166  assert_equals(control.i.validationMessage, 'rangeUnderflow message');
    167 
    168  control.i.setValidity({rangeOverflow: true}, 'rangeOverflow message');
    169  assert_true(validity.rangeOverflow);
    170  assert_false(validity.valid);
    171  assert_equals(control.i.validationMessage, 'rangeOverflow message');
    172 
    173  control.i.setValidity({stepMismatch: true}, 'stepMismatch message');
    174  assert_true(validity.stepMismatch);
    175  assert_false(validity.valid);
    176  assert_equals(control.i.validationMessage, 'stepMismatch message');
    177 
    178  control.i.setValidity({badInput: true}, 'badInput message');
    179  assert_true(validity.badInput);
    180  assert_false(validity.valid);
    181  assert_equals(control.i.validationMessage, 'badInput message');
    182 
    183  control.i.setValidity({customError: true}, 'customError message');
    184  assert_true(validity.customError, 'customError should be true.');
    185  assert_false(validity.valid);
    186  assert_equals(control.i.validationMessage, 'customError message');
    187 
    188  // Set multiple flags
    189  control.i.setValidity({badInput: true, customError: true}, 'multiple errors');
    190  assert_true(validity.badInput);
    191  assert_true(validity.customError);
    192  assert_false(validity.valid);
    193  assert_equals(control.i.validationMessage, 'multiple errors');
    194 
    195  // Clear flags
    196  control.i.setValidity({}, 'unnecessary message');
    197  assert_false(validity.badInput);
    198  assert_false(validity.customError);
    199  assert_true(validity.valid);
    200  assert_equals(control.i.validationMessage, '');
    201 
    202  assert_throws_js(TypeError, () => { control.i.setValidity({valueMissing: true}); },
    203      'setValidity() requires the second argument if the first argument contains true');
    204 }, 'validity and setValidity()');
    205 
    206 test(() => {
    207  const control = document.createElement('my-control');
    208  control.i.setValidity({badInput: true}, 'error message');
    209  const validity = control.i.validity;
    210  assert_false(validity.customError);
    211  assert_false(validity.valid);
    212  assert_equals(control.i.validationMessage, 'error message');
    213 }, "validity.customError should be false if not explicitly set via setValidity()");
    214 
    215 test(() => {
    216  document.body.insertAdjacentHTML('afterbegin', '<my-control><light-child></my-control>');
    217  let control = document.body.firstChild;
    218  const flags = {valueMissing: true};
    219  const m = 'non-empty message';
    220 
    221  assert_throws_dom('NotFoundError', () => {
    222    control.i.setValidity(flags, m, document.body);
    223  }, 'Not a descendant');
    224 
    225  let notHTMLElement = document.createElementNS('some-random-namespace', 'foo');
    226  control.appendChild(notHTMLElement);
    227  assert_throws_js(TypeError, () => {
    228    control.i.setValidity(flags, m, notHTMLElement);
    229  }, 'Not a HTMLElement');
    230  notHTMLElement.remove();
    231 
    232  // A descendant
    233  control.i.setValidity(flags, m, control.querySelector('light-child'));
    234 
    235  // An element in the direct shadow tree
    236  let shadow1 = control.attachShadow({mode: 'open'});
    237  let shadowChild = document.createElement('div');
    238  shadow1.appendChild(shadowChild);
    239  control.i.setValidity(flags, m, shadowChild);
    240 
    241  // An element in an nested shadow trees
    242  let shadow2 = shadowChild.attachShadow({mode: 'closed'});
    243  let nestedShadowChild = document.createElement('span');
    244  shadow2.appendChild(nestedShadowChild);
    245  control.i.setValidity(flags, m, nestedShadowChild);
    246 }, '"anchor" argument of setValidity()');
    247 
    248 test(t => {
    249  const control = document.createElement('my-control');
    250  document.body.appendChild(control);
    251  t.add_cleanup(() => control.remove());
    252  const flags = {valueMissing: true};
    253  const m = 'non-empty message';
    254 
    255  // Self
    256  control.i.setValidity(flags, m, control);
    257 }, '"anchor" argument of setValidity(): passing self');
    258 
    259 test(() => {
    260  const element = new NotFormAssociatedElement();
    261  assert_throws_dom('NotSupportedError', () => element.i.checkValidity());
    262 }, "checkValidity() should throw NotSupportedError if the target element is not a form-associated custom element");
    263 
    264 test(() => {
    265  const control = document.createElement('my-control');
    266  let invalidCount = 0;
    267  control.addEventListener('invalid', e => {
    268    assert_equals(e.target, control);
    269    assert_true(e.cancelable);
    270    ++invalidCount;
    271  });
    272 
    273  assert_true(control.i.checkValidity(), 'default state');
    274  assert_equals(invalidCount, 0);
    275 
    276  control.i.setValidity({customError:true}, 'foo');
    277  assert_false(control.i.checkValidity());
    278  assert_equals(invalidCount, 1);
    279 }, 'checkValidity()');
    280 
    281 test(() => {
    282  const element = new NotFormAssociatedElement();
    283  assert_throws_dom('NotSupportedError', () => element.i.reportValidity());
    284 }, "reportValidity() should throw NotSupportedError if the target element is not a form-associated custom element");
    285 
    286 test(() => {
    287  const control = document.createElement('my-control');
    288  document.body.appendChild(control);
    289  control.tabIndex = 0;
    290  let invalidCount = 0;
    291  control.addEventListener('invalid', e => {
    292    assert_equals(e.target, control);
    293    assert_true(e.cancelable);
    294    ++invalidCount;
    295  });
    296 
    297  assert_true(control.i.reportValidity(), 'default state');
    298  assert_equals(invalidCount, 0);
    299 
    300  control.i.setValidity({customError:true}, 'foo');
    301  assert_false(control.i.reportValidity());
    302  assert_equals(invalidCount, 1);
    303 
    304  control.blur();
    305  control.addEventListener('invalid', e => e.preventDefault());
    306  assert_false(control.i.reportValidity());
    307 }, 'reportValidity()');
    308 
    309 test(() => {
    310  const container = document.getElementById('container');
    311  container.innerHTML = '<form><input type=submit><my-control>';
    312  const form = container.querySelector('form');
    313  const control = container.querySelector('my-control');
    314  control.tabIndex = 0;
    315  let invalidCount = 0;
    316  control.addEventListener('invalid', e => { ++invalidCount; });
    317 
    318  assert_true(control.i.checkValidity());
    319  assert_true(form.checkValidity());
    320  control.i.setValidity({valueMissing: true}, 'Please fill out this field');
    321  assert_false(form.checkValidity());
    322  assert_equals(invalidCount, 1);
    323 
    324  assert_false(form.reportValidity());
    325  assert_equals(invalidCount, 2);
    326 
    327  container.querySelector('input[type=submit]').click();
    328  assert_equals(invalidCount, 3);
    329 }, 'Custom control affects validation at the owner form');
    330 
    331 function isValidAccordingToCSS(element, comment) {
    332  assert_true(element.matches(':valid'), comment ? (comment + ' - :valid') : undefined);
    333  assert_false(element.matches(':invalid'), comment ? (comment + ' - :invalid') : undefined);
    334 }
    335 
    336 function isInvalidAccordingToCSS(element, comment) {
    337  assert_false(element.matches(':valid'), comment ? (comment + ' - :valid') : undefined);
    338  assert_true(element.matches(':invalid'), comment ? (comment + ' - :invalid') : undefined);
    339 }
    340 
    341 test(() => {
    342  const container = document.getElementById('container');
    343  container.innerHTML = '<form><fieldset><my-control>';
    344  const form = container.querySelector('form');
    345  const fieldset = container.querySelector('fieldset');
    346  const control = container.querySelector('my-control');
    347 
    348  isValidAccordingToCSS(control);
    349  isValidAccordingToCSS(form);
    350  isValidAccordingToCSS(fieldset);
    351 
    352  control.i.setValidity({typeMismatch: true}, 'Invalid format');
    353  isInvalidAccordingToCSS(control);
    354  isInvalidAccordingToCSS(form);
    355  isInvalidAccordingToCSS(fieldset);
    356 
    357  control.remove();
    358  isInvalidAccordingToCSS(control);
    359  isValidAccordingToCSS(form);
    360  isValidAccordingToCSS(fieldset);
    361 
    362  fieldset.appendChild(control);
    363  isInvalidAccordingToCSS(form);
    364  isInvalidAccordingToCSS(fieldset);
    365 
    366  control.i.setValidity({});
    367  isValidAccordingToCSS(control);
    368  isValidAccordingToCSS(form);
    369  isValidAccordingToCSS(fieldset);
    370 }, 'Custom control affects :valid :invalid for FORM and FIELDSET');
    371 </script>
    372 </body>